| /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * libmbim-glib -- GLib/GIO based library to control MBIM devices |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301 USA. |
| * |
| * Copyright (C) 2013-2014 Aleksander Morgado <aleksander@aleksander.es> |
| * Copyright (C) 2014 Smith Micro Software, Inc. |
| * |
| * Implementation based on the 'QmiDevice' GObject from libqmi-glib. |
| */ |
| |
| #include <config.h> |
| |
| #define _GNU_SOURCE |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <termios.h> |
| #include <unistd.h> |
| #include <gio/gio.h> |
| #include <gio/gunixsocketaddress.h> |
| #include <sys/ioctl.h> |
| #define IOCTL_WDM_MAX_COMMAND _IOR('H', 0xA0, guint16) |
| #define RETRY_TIMEOUT_SECS 5 |
| |
| #if WITH_UDEV |
| # include <gudev/gudev.h> |
| #endif |
| |
| #include "mbim-utils.h" |
| #include "mbim-device.h" |
| #include "mbim-message.h" |
| #include "mbim-message-private.h" |
| #include "mbim-error-types.h" |
| #include "mbim-enum-types.h" |
| #include "mbim-proxy.h" |
| #include "mbim-proxy-control.h" |
| |
| /** |
| * SECTION:mbim-device |
| * @title: MbimDevice |
| * @short_description: Generic MBIM device handling routines |
| * |
| * #MbimDevice is a generic type in charge of controlling the access to the |
| * managed MBIM port. |
| * |
| * A #MbimDevice can only handle one single MBIM port. |
| */ |
| |
| static void async_initable_iface_init (GAsyncInitableIface *iface); |
| |
| G_DEFINE_TYPE_EXTENDED (MbimDevice, mbim_device, G_TYPE_OBJECT, 0, |
| G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)) |
| |
| enum { |
| PROP_0, |
| PROP_FILE, |
| PROP_TRANSACTION_ID, |
| PROP_IN_SESSION, |
| PROP_LAST |
| }; |
| |
| static GParamSpec *properties[PROP_LAST]; |
| |
| enum { |
| SIGNAL_INDICATE_STATUS, |
| SIGNAL_ERROR, |
| SIGNAL_REMOVED, |
| SIGNAL_LAST |
| }; |
| |
| static guint signals[SIGNAL_LAST] = { 0 }; |
| |
| typedef enum { |
| TRANSACTION_TYPE_HOST = 0, |
| TRANSACTION_TYPE_MODEM = 1, |
| TRANSACTION_TYPE_LAST = 2 |
| } TransactionType; |
| |
| typedef enum { |
| OPEN_STATUS_CLOSED = 0, |
| OPEN_STATUS_OPENING = 1, |
| OPEN_STATUS_OPEN = 2 |
| } OpenStatus; |
| |
| struct _MbimDevicePrivate { |
| /* File */ |
| GFile *file; |
| gchar *path; |
| gchar *path_display; |
| |
| /* I/O channel, set when the file is open */ |
| GIOChannel *iochannel; |
| GSource *iochannel_source; |
| GByteArray *response; |
| OpenStatus open_status; |
| |
| /* Support for mbim-proxy */ |
| GSocketClient *socket_client; |
| GSocketConnection *socket_connection; |
| |
| /* HT to keep track of ongoing host/function transactions |
| * Host transactions: created by us |
| * Modem transactions: modem-created indications with multiple fragments |
| */ |
| GHashTable *transactions[TRANSACTION_TYPE_LAST]; |
| |
| /* Transaction ID in the device */ |
| guint32 transaction_id; |
| |
| /* Flag to specify whether we're in a session */ |
| gboolean in_session; |
| |
| /* message size */ |
| guint16 max_control_transfer; |
| }; |
| |
| #define MAX_SPAWN_RETRIES 10 |
| #define MAX_CONTROL_TRANSFER 4096 |
| #define MAX_TIME_BETWEEN_FRAGMENTS_MS 1250 |
| |
| static void device_report_error (MbimDevice *self, |
| guint32 transaction_id, |
| const GError *error); |
| |
| /*****************************************************************************/ |
| /* Message transactions (private) */ |
| |
| typedef struct { |
| MbimDevice *self; |
| guint32 transaction_id; |
| TransactionType type; |
| } TransactionWaitContext; |
| |
| typedef struct { |
| MbimDevice *self; |
| MbimMessage *fragments; |
| MbimMessageType type; |
| guint32 transaction_id; |
| GSimpleAsyncResult *result; |
| GSource *timeout_source; |
| GCancellable *cancellable; |
| gulong cancellable_id; |
| TransactionWaitContext *wait_ctx; |
| } Transaction; |
| |
| /* #define TRACE_TRANSACTION 1 */ |
| #ifdef TRACE_TRANSACTION |
| static void |
| trace_transaction (Transaction *tr, |
| const gchar *state) |
| { |
| g_debug ("[%s,%u] transaction %s: %s", |
| tr->self->priv->path_display, |
| tr->transaction_id, |
| mbim_message_type_get_string (tr->type), |
| state); |
| } |
| #else |
| # define trace_transaction(...) |
| #endif |
| |
| static Transaction * |
| transaction_new (MbimDevice *self, |
| MbimMessageType type, |
| guint32 transaction_id, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| Transaction *tr; |
| |
| tr = g_slice_new0 (Transaction); |
| tr->type = type; |
| tr->transaction_id = transaction_id; |
| tr->self = g_object_ref (self); |
| tr->result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| transaction_new); |
| if (cancellable) |
| tr->cancellable = g_object_ref (cancellable); |
| |
| trace_transaction (tr, "new"); |
| |
| return tr; |
| } |
| |
| static void |
| transaction_complete_and_free (Transaction *tr, |
| const GError *error) |
| { |
| if (tr->timeout_source) |
| g_source_destroy (tr->timeout_source); |
| |
| if (tr->cancellable) { |
| if (tr->cancellable_id) |
| g_cancellable_disconnect (tr->cancellable, tr->cancellable_id); |
| g_object_unref (tr->cancellable); |
| } |
| |
| if (tr->wait_ctx) |
| g_slice_free (TransactionWaitContext, tr->wait_ctx); |
| |
| if (error) { |
| trace_transaction (tr, "complete: response"); |
| g_simple_async_result_set_from_error (tr->result, error); |
| if (tr->fragments) |
| mbim_message_unref (tr->fragments); |
| } else { |
| trace_transaction (tr, "complete: error"); |
| g_assert (tr->fragments != NULL); |
| g_simple_async_result_set_op_res_gpointer (tr->result, |
| tr->fragments, |
| (GDestroyNotify) mbim_message_unref); |
| } |
| |
| g_simple_async_result_complete_in_idle (tr->result); |
| g_object_unref (tr->result); |
| g_object_unref (tr->self); |
| g_slice_free (Transaction, tr); |
| } |
| |
| static Transaction * |
| device_release_transaction (MbimDevice *self, |
| TransactionType type, |
| MbimMessageType expected_type, |
| guint32 transaction_id) |
| { |
| Transaction *tr = NULL; |
| |
| /* Only return transaction if it was released from the HT */ |
| if (self->priv->transactions[type]) { |
| tr = g_hash_table_lookup (self->priv->transactions[type], GUINT_TO_POINTER (transaction_id)); |
| if (tr && ((tr->type == expected_type) || (expected_type == MBIM_MESSAGE_TYPE_INVALID))) { |
| /* If found, remove it from the HT */ |
| trace_transaction (tr, "release"); |
| g_hash_table_remove (self->priv->transactions[type], GUINT_TO_POINTER (transaction_id)); |
| return tr; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static gboolean |
| transaction_timed_out (TransactionWaitContext *ctx) |
| { |
| Transaction *tr; |
| GError *error = NULL; |
| |
| tr = device_release_transaction (ctx->self, |
| ctx->type, |
| MBIM_MESSAGE_TYPE_INVALID, |
| ctx->transaction_id); |
| if (!tr) |
| /* transaction already completed */ |
| return FALSE; |
| |
| tr->timeout_source = NULL; |
| |
| /* If no fragment was received, complete transaction with a timeout error */ |
| if (!tr->fragments) |
| error = g_error_new (MBIM_CORE_ERROR, |
| MBIM_CORE_ERROR_TIMEOUT, |
| "Transaction timed out"); |
| else { |
| /* Fragment timeout... */ |
| error = g_error_new (MBIM_PROTOCOL_ERROR, |
| MBIM_PROTOCOL_ERROR_TIMEOUT_FRAGMENT, |
| "Fragment timed out"); |
| |
| /* Also notify to the modem */ |
| device_report_error (ctx->self, |
| tr->transaction_id, |
| error); |
| } |
| |
| transaction_complete_and_free (tr, error); |
| g_error_free (error); |
| |
| return FALSE; |
| } |
| |
| static void |
| transaction_cancelled (GCancellable *cancellable, |
| TransactionWaitContext *ctx) |
| { |
| Transaction *tr; |
| GError *error = NULL; |
| |
| tr = device_release_transaction (ctx->self, |
| ctx->type, |
| MBIM_MESSAGE_TYPE_INVALID, |
| ctx->transaction_id); |
| |
| /* The transaction may have already been cancelled before we stored it in |
| * the tracking table */ |
| if (!tr) |
| return; |
| |
| tr->cancellable_id = 0; |
| |
| /* Complete transaction with an abort error */ |
| error = g_error_new (MBIM_CORE_ERROR, |
| MBIM_CORE_ERROR_ABORTED, |
| "Transaction aborted"); |
| transaction_complete_and_free (tr, error); |
| g_error_free (error); |
| } |
| |
| static gboolean |
| device_store_transaction (MbimDevice *self, |
| TransactionType type, |
| Transaction *tr, |
| guint timeout_ms, |
| GError **error) |
| { |
| trace_transaction (tr, "store"); |
| |
| if (G_UNLIKELY (!self->priv->transactions[type])) |
| self->priv->transactions[type] = g_hash_table_new (g_direct_hash, g_direct_equal); |
| |
| tr->wait_ctx = g_slice_new (TransactionWaitContext); |
| tr->wait_ctx->self = self; |
| /* valid as long as the transaction is in the HT */ |
| tr->wait_ctx->transaction_id = tr->transaction_id; |
| tr->wait_ctx->type = type; |
| |
| /* don't add timeout if one already exists */ |
| if (!tr->timeout_source) { |
| tr->timeout_source = g_timeout_source_new (timeout_ms); |
| g_source_set_callback (tr->timeout_source, (GSourceFunc)transaction_timed_out, tr->wait_ctx, NULL); |
| g_source_attach (tr->timeout_source, g_main_context_get_thread_default ()); |
| g_source_unref (tr->timeout_source); |
| } |
| |
| if (tr->cancellable && !tr->cancellable_id) { |
| /* Note: transaction_cancelled() will also be called directly if the |
| * cancellable is already cancelled */ |
| tr->cancellable_id = g_cancellable_connect (tr->cancellable, |
| (GCallback)transaction_cancelled, |
| tr->wait_ctx, |
| NULL); |
| if (!tr->cancellable_id) { |
| g_set_error_literal (error, |
| MBIM_CORE_ERROR, |
| MBIM_CORE_ERROR_ABORTED, |
| "Request is already cancelled"); |
| return FALSE; |
| } |
| } |
| |
| /* Keep in the HT */ |
| g_hash_table_insert (self->priv->transactions[type], GUINT_TO_POINTER (tr->transaction_id), tr); |
| |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| |
| /** |
| * mbim_device_get_file: |
| * @self: a #MbimDevice. |
| * |
| * Get the #GFile associated with this #MbimDevice. |
| * |
| * Returns: a #GFile that must be freed with g_object_unref(). |
| */ |
| GFile * |
| mbim_device_get_file (MbimDevice *self) |
| { |
| GFile *file = NULL; |
| |
| g_return_val_if_fail (MBIM_IS_DEVICE (self), NULL); |
| |
| g_object_get (G_OBJECT (self), |
| MBIM_DEVICE_FILE, &file, |
| NULL); |
| return file; |
| } |
| |
| /** |
| * mbim_device_peek_file: |
| * @self: a #MbimDevice. |
| * |
| * Get the #GFile associated with this #MbimDevice, without increasing the reference count |
| * on the returned object. |
| * |
| * Returns: a #GFile. Do not free the returned object, it is owned by @self. |
| */ |
| GFile * |
| mbim_device_peek_file (MbimDevice *self) |
| { |
| g_return_val_if_fail (MBIM_IS_DEVICE (self), NULL); |
| |
| return self->priv->file; |
| } |
| |
| /** |
| * mbim_device_get_path: |
| * @self: a #MbimDevice. |
| * |
| * Get the system path of the underlying MBIM device. |
| * |
| * Returns: the system path of the device. |
| */ |
| const gchar * |
| mbim_device_get_path (MbimDevice *self) |
| { |
| g_return_val_if_fail (MBIM_IS_DEVICE (self), NULL); |
| |
| return self->priv->path; |
| } |
| |
| /** |
| * mbim_device_get_path_display: |
| * @self: a #MbimDevice. |
| * |
| * Get the system path of the underlying MBIM device in UTF-8. |
| * |
| * Returns: UTF-8 encoded system path of the device. |
| */ |
| const gchar * |
| mbim_device_get_path_display (MbimDevice *self) |
| { |
| g_return_val_if_fail (MBIM_IS_DEVICE (self), NULL); |
| |
| return self->priv->path_display; |
| } |
| |
| /** |
| * mbim_device_is_open: |
| * @self: a #MbimDevice. |
| * |
| * Checks whether the #MbimDevice is open for I/O. |
| * |
| * Returns: %TRUE if @self is open, %FALSE otherwise. |
| */ |
| gboolean |
| mbim_device_is_open (MbimDevice *self) |
| { |
| g_return_val_if_fail (MBIM_IS_DEVICE (self), FALSE); |
| |
| return (self->priv->open_status == OPEN_STATUS_OPEN); |
| } |
| |
| /*****************************************************************************/ |
| /* Open device */ |
| |
| static void |
| indication_ready (MbimDevice *self, |
| GAsyncResult *res) |
| { |
| GError *error = NULL; |
| MbimMessage *indication; |
| |
| if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), &error)) { |
| g_debug ("[%s] Error processing indication message: %s", |
| self->priv->path_display, |
| error->message); |
| g_error_free (error); |
| return; |
| } |
| |
| indication = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); |
| g_signal_emit (self, signals[SIGNAL_INDICATE_STATUS], 0, indication); |
| } |
| |
| static void |
| process_message (MbimDevice *self, |
| const MbimMessage *message) |
| { |
| gboolean is_partial_fragment; |
| |
| is_partial_fragment = (_mbim_message_is_fragment (message) && |
| _mbim_message_fragment_get_total (message) > 1); |
| |
| if (mbim_utils_get_traces_enabled ()) { |
| gchar *printable; |
| |
| printable = __mbim_utils_str_hex (((GByteArray *)message)->data, |
| ((GByteArray *)message)->len, |
| ':'); |
| g_debug ("[%s] Received message...%s\n" |
| ">>>>>> RAW:\n" |
| ">>>>>> length = %u\n" |
| ">>>>>> data = %s\n", |
| self->priv->path_display, |
| is_partial_fragment ? " (partial fragment)" : "", |
| ((GByteArray *)message)->len, |
| printable); |
| g_free (printable); |
| |
| if (is_partial_fragment) { |
| printable = mbim_message_get_printable (message, ">>>>>> ", TRUE); |
| g_debug ("[%s] Received message fragment (translated)...\n%s", |
| self->priv->path_display, |
| printable); |
| g_free (printable); |
| } |
| } |
| |
| switch (MBIM_MESSAGE_GET_MESSAGE_TYPE (message)) { |
| case MBIM_MESSAGE_TYPE_OPEN_DONE: |
| case MBIM_MESSAGE_TYPE_CLOSE_DONE: |
| case MBIM_MESSAGE_TYPE_COMMAND_DONE: |
| case MBIM_MESSAGE_TYPE_INDICATE_STATUS: { |
| GError *error = NULL; |
| Transaction *tr; |
| |
| if (MBIM_MESSAGE_GET_MESSAGE_TYPE (message) == MBIM_MESSAGE_TYPE_INDICATE_STATUS) { |
| /* Grab transaction */ |
| tr = device_release_transaction (self, |
| TRANSACTION_TYPE_MODEM, |
| MBIM_MESSAGE_TYPE_INDICATE_STATUS, |
| mbim_message_get_transaction_id (message)); |
| |
| if (!tr) |
| /* Create new transaction for the indication */ |
| tr = transaction_new (self, |
| MBIM_MESSAGE_TYPE_INDICATE_STATUS, |
| mbim_message_get_transaction_id (message), |
| NULL, /* no cancellable */ |
| (GAsyncReadyCallback)indication_ready, |
| NULL); |
| } else { |
| /* Grab transaction. This is a _DONE message, so look for the request |
| * that generated the _DONE */ |
| tr = device_release_transaction (self, |
| TRANSACTION_TYPE_HOST, |
| (MBIM_MESSAGE_GET_MESSAGE_TYPE (message) - 0x80000000), |
| mbim_message_get_transaction_id (message)); |
| if (!tr) { |
| gchar *printable; |
| |
| g_debug ("[%s] No transaction matched in received message", |
| self->priv->path_display); |
| /* Attempt to print a user friendly dump of the packet anyway */ |
| printable = mbim_message_get_printable (message, ">>>>>> ", is_partial_fragment); |
| if (printable) { |
| g_debug ("[%s] Received unexpected message (translated)...\n%s", |
| self->priv->path_display, |
| printable); |
| g_free (printable); |
| } |
| return; |
| } |
| |
| /* If the message doesn't have fragments, we're done */ |
| if (!_mbim_message_is_fragment (message)) { |
| g_assert (tr->fragments == NULL); |
| tr->fragments = mbim_message_dup (message); |
| transaction_complete_and_free (tr, NULL); |
| return; |
| } |
| } |
| |
| /* More than one fragment expected; is this the first one? */ |
| if (!tr->fragments) |
| tr->fragments = _mbim_message_fragment_collector_init (message, &error); |
| else |
| _mbim_message_fragment_collector_add (tr->fragments, message, &error); |
| |
| if (error) { |
| device_report_error (self, |
| tr->transaction_id, |
| error); |
| transaction_complete_and_free (tr, error); |
| g_error_free (error); |
| return; |
| } |
| |
| /* Did we get all needed fragments? */ |
| if (_mbim_message_fragment_collector_complete (tr->fragments)) { |
| /* Now, translate the whole message */ |
| if (mbim_utils_get_traces_enabled ()) { |
| gchar *printable; |
| |
| printable = mbim_message_get_printable (tr->fragments, ">>>>>> ", FALSE); |
| g_debug ("[%s] Received message (translated)...\n%s", |
| self->priv->path_display, |
| printable); |
| g_free (printable); |
| } |
| |
| transaction_complete_and_free (tr, NULL); |
| return; |
| } |
| |
| /* Need more fragments, store transaction */ |
| g_assert (device_store_transaction (self, |
| TRANSACTION_TYPE_HOST, |
| tr, |
| MAX_TIME_BETWEEN_FRAGMENTS_MS, |
| NULL)); |
| return; |
| } |
| |
| case MBIM_MESSAGE_TYPE_FUNCTION_ERROR: { |
| Transaction *tr; |
| GError *error_indication; |
| |
| /* Try to match this transaction just per transaction ID */ |
| tr = device_release_transaction (self, |
| TRANSACTION_TYPE_HOST, |
| MBIM_MESSAGE_TYPE_INVALID, |
| mbim_message_get_transaction_id (message)); |
| |
| if (!tr) |
| g_debug ("[%s] No transaction matched in received function error message", |
| self->priv->path_display); |
| |
| if (mbim_utils_get_traces_enabled ()) { |
| gchar *printable; |
| |
| printable = mbim_message_get_printable (message, ">>>>>> ", FALSE); |
| g_debug ("[%s] Received message (translated)...\n%s", |
| self->priv->path_display, |
| printable); |
| g_free (printable); |
| } |
| |
| if (tr) { |
| if (tr->fragments) |
| mbim_message_unref (tr->fragments); |
| tr->fragments = mbim_message_dup (message); |
| transaction_complete_and_free (tr, NULL); |
| } |
| |
| /* Signals are emitted regardless of whether the transaction matched or not */ |
| error_indication = mbim_message_error_get_error (message); |
| g_signal_emit (self, signals[SIGNAL_ERROR], 0, error_indication); |
| g_error_free (error_indication); |
| return; |
| } |
| |
| case MBIM_MESSAGE_TYPE_INVALID: |
| case MBIM_MESSAGE_TYPE_OPEN: |
| case MBIM_MESSAGE_TYPE_CLOSE: |
| case MBIM_MESSAGE_TYPE_COMMAND: |
| case MBIM_MESSAGE_TYPE_HOST_ERROR: |
| /* Shouldn't expect host-generated messages as replies */ |
| g_message ("[%s] Host-generated message received: ignoring", |
| self->priv->path_display); |
| return; |
| } |
| } |
| |
| static void |
| parse_response (MbimDevice *self) |
| { |
| do { |
| const MbimMessage *message; |
| guint32 in_length; |
| |
| /* If not even the MBIM header available, just return */ |
| if (self->priv->response->len < 12) |
| return; |
| |
| message = (const MbimMessage *)self->priv->response; |
| |
| /* No full message yet */ |
| in_length = mbim_message_get_message_length (message); |
| |
| if (self->priv->response->len < in_length) |
| return; |
| |
| /* Play with the received message */ |
| process_message (self, message); |
| |
| /* If we were force-closed during the processing of a message, we'd be |
| * losing the response array directly, so check just in case */ |
| if (!self->priv->response) |
| break; |
| |
| /* Remove message from buffer */ |
| g_byte_array_remove_range (self->priv->response, 0, in_length); |
| } while (self->priv->response->len > 0); |
| } |
| |
| static gboolean |
| data_available (GIOChannel *source, |
| GIOCondition condition, |
| MbimDevice *self) |
| { |
| gsize bytes_read; |
| GIOStatus status; |
| gchar buffer[MAX_CONTROL_TRANSFER + 1]; |
| |
| if (condition & G_IO_HUP) { |
| g_debug ("[%s] unexpected port hangup!", |
| self->priv->path_display); |
| |
| if (self->priv->response && |
| self->priv->response->len) |
| g_byte_array_remove_range (self->priv->response, 0, self->priv->response->len); |
| |
| mbim_device_close_force (self, NULL); |
| g_signal_emit (self, signals[SIGNAL_REMOVED], 0 ); |
| return FALSE; |
| } |
| |
| if (condition & G_IO_ERR) { |
| if (self->priv->response && |
| self->priv->response->len) |
| g_byte_array_remove_range (self->priv->response, 0, self->priv->response->len); |
| return TRUE; |
| } |
| |
| /* If not ready yet, prepare the response with default initial size. */ |
| if (G_UNLIKELY (!self->priv->response)) |
| self->priv->response = g_byte_array_sized_new (500); |
| |
| do { |
| GError *error = NULL; |
| |
| status = g_io_channel_read_chars (source, |
| buffer, |
| self->priv->max_control_transfer, |
| &bytes_read, |
| &error); |
| if (status == G_IO_STATUS_ERROR) { |
| if (error) { |
| g_warning ("[%s] error reading from the IOChannel: '%s'", |
| self->priv->path_display, |
| error->message); |
| g_error_free (error); |
| } |
| |
| /* Port is closed; we're done */ |
| if (!self->priv->iochannel_source) |
| break; |
| } |
| |
| /* If no bytes read, just let g_io_channel wait for more data */ |
| if (bytes_read == 0) |
| break; |
| |
| if (bytes_read > 0) |
| g_byte_array_append (self->priv->response, (const guint8 *)buffer, bytes_read); |
| |
| /* Try to parse what we already got */ |
| parse_response (self); |
| |
| /* And keep on if we were told to keep on */ |
| } while (bytes_read == self->priv->max_control_transfer || status == G_IO_STATUS_AGAIN); |
| |
| return TRUE; |
| } |
| |
| /* "MBIM Control Model Functional Descriptor" */ |
| struct usb_cdc_mbim_desc { |
| guint8 bLength; |
| guint8 bDescriptorType; |
| guint8 bDescriptorSubType; |
| guint16 bcdMBIMVersion; |
| guint16 wMaxControlMessage; |
| guint8 bNumberFilters; |
| guint8 bMaxFilterSize; |
| guint16 wMaxSegmentSize; |
| guint8 bmNetworkCapabilities; |
| } __attribute__ ((packed)); |
| |
| #if WITH_UDEV |
| |
| static gchar * |
| get_descriptors_filepath (MbimDevice *self) |
| { |
| GUdevClient *client; |
| GUdevDevice *device = NULL; |
| GUdevDevice *parent_device = NULL; |
| GUdevDevice *grandparent_device = NULL; |
| gchar *descriptors_path = NULL; |
| gchar *device_basename = NULL; |
| |
| client = g_udev_client_new (NULL); |
| if (!G_UDEV_IS_CLIENT (client)) { |
| g_warning ("[%s] Couldn't get udev client", |
| self->priv->path_display); |
| goto out; |
| } |
| |
| /* We need to get the sysfs of the cdc-wdm's grandfather: |
| * |
| * * Device's sysfs path is like: |
| * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.5/2-1.5:2.0/usbmisc/cdc-wdm0 |
| * * Parent's sysfs path is like: |
| * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.5/2-1.5:2.0 |
| * * Grandparent's sysfs path is like: |
| * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.5 |
| * |
| * Which is the one with the descriptors file. |
| */ |
| |
| device_basename = g_path_get_basename (self->priv->path); |
| device = g_udev_client_query_by_subsystem_and_name (client, "usb", device_basename); |
| if (!device) { |
| device = g_udev_client_query_by_subsystem_and_name (client, "usbmisc", device_basename); |
| if (!device) { |
| g_warning ("[%s] Couldn't find udev device", |
| self->priv->path_display); |
| goto out; |
| } |
| } |
| |
| parent_device = g_udev_device_get_parent (device); |
| if (!parent_device) { |
| g_warning ("[%s] Couldn't find parent udev device", |
| self->priv->path_display); |
| goto out; |
| } |
| |
| grandparent_device = g_udev_device_get_parent (parent_device); |
| if (!grandparent_device) { |
| g_warning ("[%s] Couldn't find grandparent udev device", |
| self->priv->path_display); |
| goto out; |
| } |
| |
| descriptors_path = g_build_path (G_DIR_SEPARATOR_S, |
| g_udev_device_get_sysfs_path (grandparent_device), |
| "descriptors", |
| NULL); |
| |
| out: |
| g_free (device_basename); |
| if (parent_device) |
| g_object_unref (parent_device); |
| if (grandparent_device) |
| g_object_unref (grandparent_device); |
| if (device) |
| g_object_unref (device); |
| if (client) |
| g_object_unref (client); |
| |
| return descriptors_path; |
| } |
| |
| #else |
| |
| static gchar * |
| get_descriptors_filepath (MbimDevice *self) |
| { |
| static const gchar *subsystems[] = { "usbmisc", "usb" }; |
| guint i; |
| gchar *device_basename; |
| gchar *descriptors_path = NULL; |
| |
| device_basename = g_path_get_basename (self->priv->path); |
| |
| for (i = 0; !descriptors_path && i < G_N_ELEMENTS (subsystems); i++) { |
| gchar *tmp; |
| gchar *path; |
| |
| /* parent sysfs can be built directly using subsystem and name; e.g. for subsystem |
| * usbmisc and name cdc-wdm0: |
| * $ realpath /sys/class/usbmisc/cdc-wdm0/device |
| * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.5/2-1.5:2.0 |
| */ |
| tmp = g_strdup_printf ("/sys/class/%s/%s/device", subsystems[i], device_basename); |
| path = canonicalize_file_name (tmp); |
| g_free (tmp); |
| |
| if (g_file_test (path, G_FILE_TEST_EXISTS)) { |
| /* Now look for the parent dir with descriptors file. */ |
| gchar *dirname; |
| |
| dirname = g_path_get_dirname (path); |
| descriptors_path = g_build_path (G_DIR_SEPARATOR_S, |
| dirname, |
| "descriptors", |
| NULL); |
| g_free (dirname); |
| } |
| g_free (path); |
| } |
| |
| g_free (device_basename); |
| |
| if (descriptors_path && !g_file_test (descriptors_path, G_FILE_TEST_EXISTS)) { |
| g_warning ("[%s] Descriptors file doesn't exist", |
| self->priv->path_display); |
| g_free (descriptors_path); |
| descriptors_path = NULL; |
| } |
| |
| return descriptors_path; |
| } |
| |
| #endif |
| |
| static guint16 |
| read_max_control_transfer (MbimDevice *self) |
| { |
| static const guint8 mbim_signature[4] = { 0x0c, 0x24, 0x1b, 0x00 }; |
| guint16 max = MAX_CONTROL_TRANSFER; |
| gchar *descriptors_path; |
| GError *error = NULL; |
| gchar *contents = NULL; |
| gsize length = 0; |
| guint i; |
| |
| /* Build descriptors filepath */ |
| descriptors_path = get_descriptors_filepath (self); |
| if (!descriptors_path) { |
| g_warning ("[%s] Couldn't get descriptors file path", |
| self->priv->path_display); |
| goto out; |
| } |
| |
| if (!g_file_get_contents (descriptors_path, |
| &contents, |
| &length, |
| &error)) { |
| g_warning ("[%s] Couldn't read descriptors file: %s", |
| self->priv->path_display, |
| error->message); |
| g_error_free (error); |
| goto out; |
| } |
| |
| i = 0; |
| while (i <= (length - sizeof (struct usb_cdc_mbim_desc))) { |
| /* Try to match the MBIM descriptor signature */ |
| if ((memcmp (&contents[i], mbim_signature, sizeof (mbim_signature)) == 0)) { |
| /* Found! */ |
| max = GUINT16_FROM_LE (((struct usb_cdc_mbim_desc *)&contents[i])->wMaxControlMessage); |
| g_debug ("[%s] Read max control message size from descriptors file: %" G_GUINT16_FORMAT, |
| self->priv->path_display, |
| max); |
| goto out; |
| } |
| |
| /* The first byte of the descriptor info is the length; so keep on |
| * skipping descriptors until we match the MBIM one */ |
| i += contents[i]; |
| } |
| |
| g_warning ("[%s] Couldn't find MBIM signature in descriptors file", |
| self->priv->path_display); |
| |
| out: |
| g_free (contents); |
| |
| return max; |
| } |
| |
| typedef struct { |
| MbimDevice *self; |
| GSimpleAsyncResult *result; |
| guint spawn_retries; |
| } CreateIoChannelContext; |
| |
| static void |
| create_iochannel_context_complete_and_free (CreateIoChannelContext *ctx) |
| { |
| g_simple_async_result_complete_in_idle (ctx->result); |
| g_object_unref (ctx->result); |
| g_object_unref (ctx->self); |
| g_slice_free (CreateIoChannelContext, ctx); |
| } |
| |
| static gboolean |
| create_iochannel_finish (MbimDevice *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); |
| } |
| |
| static void |
| setup_iochannel (CreateIoChannelContext *ctx) |
| { |
| GError *inner_error = NULL; |
| |
| /* We don't want UTF-8 encoding, we're playing with raw binary data */ |
| g_io_channel_set_encoding (ctx->self->priv->iochannel, NULL, NULL); |
| |
| /* We don't want to get the channel buffered */ |
| g_io_channel_set_buffered (ctx->self->priv->iochannel, FALSE); |
| |
| /* Let the GIOChannel own the FD */ |
| g_io_channel_set_close_on_unref (ctx->self->priv->iochannel, TRUE); |
| |
| /* We don't want to get blocked while writing stuff */ |
| if (!g_io_channel_set_flags (ctx->self->priv->iochannel, |
| G_IO_FLAG_NONBLOCK, |
| &inner_error)) { |
| g_simple_async_result_take_error (ctx->result, inner_error); |
| g_io_channel_shutdown (ctx->self->priv->iochannel, FALSE, NULL); |
| g_io_channel_unref (ctx->self->priv->iochannel); |
| ctx->self->priv->iochannel = NULL; |
| g_clear_object (&ctx->self->priv->socket_connection); |
| g_clear_object (&ctx->self->priv->socket_client); |
| create_iochannel_context_complete_and_free (ctx); |
| return; |
| } |
| |
| ctx->self->priv->iochannel_source = g_io_create_watch (ctx->self->priv->iochannel, |
| G_IO_IN | G_IO_ERR | G_IO_HUP); |
| g_source_set_callback (ctx->self->priv->iochannel_source, |
| (GSourceFunc)data_available, |
| ctx->self, |
| NULL); |
| g_source_attach (ctx->self->priv->iochannel_source, g_main_context_get_thread_default ()); |
| |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| create_iochannel_context_complete_and_free (ctx); |
| } |
| |
| static void |
| create_iochannel_with_fd (CreateIoChannelContext *ctx) |
| { |
| gint fd; |
| guint16 max; |
| |
| errno = 0; |
| fd = open (ctx->self->priv->path, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY); |
| if (fd < 0) { |
| g_simple_async_result_set_error ( |
| ctx->result, |
| MBIM_CORE_ERROR, |
| MBIM_CORE_ERROR_FAILED, |
| "Cannot open device file '%s': %s", |
| ctx->self->priv->path_display, |
| strerror (errno)); |
| create_iochannel_context_complete_and_free (ctx); |
| return; |
| } |
| |
| /* Query message size */ |
| if (ioctl (fd, IOCTL_WDM_MAX_COMMAND, &max) < 0) { |
| g_debug ("[%s] Couldn't query maximum message size: " |
| "IOCTL_WDM_MAX_COMMAND failed: %s", |
| ctx->self->priv->path_display, |
| strerror (errno)); |
| /* Fallback, try to read the descriptor file */ |
| max = read_max_control_transfer (ctx->self); |
| } else { |
| g_debug ("[%s] Queried max control message size: %" G_GUINT16_FORMAT, |
| ctx->self->priv->path_display, |
| max); |
| } |
| ctx->self->priv->max_control_transfer = max; |
| |
| /* Create new GIOChannel */ |
| ctx->self->priv->iochannel = g_io_channel_unix_new (fd); |
| |
| setup_iochannel (ctx); |
| } |
| |
| static void create_iochannel_with_socket (CreateIoChannelContext *ctx); |
| |
| static gboolean |
| wait_for_proxy_cb (CreateIoChannelContext *ctx) |
| { |
| create_iochannel_with_socket (ctx); |
| return FALSE; |
| } |
| |
| static void |
| create_iochannel_with_socket (CreateIoChannelContext *ctx) |
| { |
| GSocketAddress *socket_address; |
| GError *error = NULL; |
| |
| /* Create socket client */ |
| if (ctx->self->priv->socket_client) |
| g_object_unref (ctx->self->priv->socket_client); |
| ctx->self->priv->socket_client = g_socket_client_new (); |
| g_socket_client_set_family (ctx->self->priv->socket_client, G_SOCKET_FAMILY_UNIX); |
| g_socket_client_set_socket_type (ctx->self->priv->socket_client, G_SOCKET_TYPE_STREAM); |
| g_socket_client_set_protocol (ctx->self->priv->socket_client, G_SOCKET_PROTOCOL_DEFAULT); |
| |
| /* Setup socket address */ |
| socket_address = (g_unix_socket_address_new_with_type ( |
| MBIM_PROXY_SOCKET_PATH, |
| -1, |
| G_UNIX_SOCKET_ADDRESS_ABSTRACT)); |
| |
| /* Connect to address */ |
| if (ctx->self->priv->socket_connection) |
| g_object_unref (ctx->self->priv->socket_connection); |
| ctx->self->priv->socket_connection = (g_socket_client_connect ( |
| ctx->self->priv->socket_client, |
| G_SOCKET_CONNECTABLE (socket_address), |
| NULL, |
| &error)); |
| g_object_unref (socket_address); |
| |
| if (!ctx->self->priv->socket_connection) { |
| gchar **argc; |
| GSource *source; |
| |
| g_debug ("cannot connect to proxy: %s", error->message); |
| g_clear_error (&error); |
| g_clear_object (&ctx->self->priv->socket_client); |
| |
| /* Don't retry forever */ |
| ctx->spawn_retries++; |
| if (ctx->spawn_retries > MAX_SPAWN_RETRIES) { |
| g_simple_async_result_set_error (ctx->result, |
| MBIM_CORE_ERROR, |
| MBIM_CORE_ERROR_FAILED, |
| "Couldn't spawn the mbim-proxy"); |
| create_iochannel_context_complete_and_free (ctx); |
| return; |
| } |
| |
| g_debug ("spawning new mbim-proxy (try %u)...", ctx->spawn_retries); |
| |
| argc = g_new0 (gchar *, 2); |
| argc[0] = g_strdup (LIBEXEC_PATH "/mbim-proxy"); |
| if (!g_spawn_async (NULL, /* working directory */ |
| argc, |
| NULL, /* envp */ |
| G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, |
| NULL, /* child_setup */ |
| NULL, /* child_setup_user_data */ |
| NULL, |
| &error)) { |
| g_debug ("error spawning mbim-proxy: %s", error->message); |
| g_clear_error (&error); |
| } |
| g_strfreev (argc); |
| |
| /* Wait some ms and retry */ |
| source = g_timeout_source_new (100); |
| g_source_set_callback (source, (GSourceFunc)wait_for_proxy_cb, ctx, NULL); |
| g_source_attach (source, g_main_context_get_thread_default ()); |
| g_source_unref (source); |
| return; |
| } |
| |
| ctx->self->priv->iochannel = g_io_channel_unix_new ( |
| g_socket_get_fd ( |
| g_socket_connection_get_socket (ctx->self->priv->socket_connection))); |
| |
| /* try to read the descriptor file */ |
| ctx->self->priv->max_control_transfer = read_max_control_transfer (ctx->self); |
| |
| setup_iochannel (ctx); |
| } |
| |
| static void |
| create_iochannel (MbimDevice *self, |
| gboolean proxy, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| CreateIoChannelContext *ctx; |
| |
| ctx = g_slice_new (CreateIoChannelContext); |
| ctx->self = g_object_ref (self); |
| ctx->result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| create_iochannel); |
| ctx->spawn_retries = 0; |
| |
| if (self->priv->iochannel) { |
| g_simple_async_result_set_error (ctx->result, |
| MBIM_CORE_ERROR, |
| MBIM_CORE_ERROR_WRONG_STATE, |
| "Already open"); |
| create_iochannel_context_complete_and_free (ctx); |
| return; |
| } |
| |
| g_assert (self->priv->file); |
| g_assert (self->priv->path); |
| |
| if (proxy) |
| create_iochannel_with_socket (ctx); |
| else |
| create_iochannel_with_fd (ctx); |
| } |
| |
| typedef enum { |
| DEVICE_OPEN_CONTEXT_STEP_FIRST = 0, |
| DEVICE_OPEN_CONTEXT_STEP_CREATE_IOCHANNEL, |
| DEVICE_OPEN_CONTEXT_STEP_FLAGS_PROXY, |
| DEVICE_OPEN_CONTEXT_STEP_OPEN_MESSAGE, |
| DEVICE_OPEN_CONTEXT_STEP_LAST |
| } DeviceOpenContextStep; |
| |
| typedef struct { |
| MbimDevice *self; |
| GSimpleAsyncResult *result; |
| GCancellable *cancellable; |
| DeviceOpenContextStep step; |
| MbimDeviceOpenFlags flags; |
| gint timeout; |
| } DeviceOpenContext; |
| |
| static void |
| device_open_context_complete_and_free (DeviceOpenContext *ctx, |
| GError *error) |
| { |
| if (error) |
| g_simple_async_result_take_error (ctx->result, error); |
| else |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| |
| g_simple_async_result_complete_in_idle (ctx->result); |
| g_object_unref (ctx->result); |
| if (ctx->cancellable) |
| g_object_unref (ctx->cancellable); |
| g_object_unref (ctx->self); |
| g_slice_free (DeviceOpenContext, ctx); |
| } |
| |
| static void device_open_context_step (DeviceOpenContext *ctx); |
| |
| /** |
| * mbim_device_open_full_finish: |
| * @self: a #MbimDevice. |
| * @res: a #GAsyncResult. |
| * @error: Return location for error or %NULL. |
| * |
| * Finishes an asynchronous open operation started with mbim_device_open_full(). |
| * |
| * Returns: %TRUE if successful, %FALSE if @error is set. |
| */ |
| gboolean |
| mbim_device_open_full_finish (MbimDevice *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); |
| } |
| |
| /** |
| * mbim_device_open_finish: |
| * @self: a #MbimDevice. |
| * @res: a #GAsyncResult. |
| * @error: Return location for error or %NULL. |
| * |
| * Finishes an asynchronous open operation started with mbim_device_open(). |
| * |
| * Returns: %TRUE if successful, %FALSE if @error is set. |
| */ |
| gboolean |
| mbim_device_open_finish (MbimDevice *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return mbim_device_open_full_finish (self, res, error); |
| } |
| |
| static void open_message (DeviceOpenContext *ctx); |
| |
| static void |
| open_message_ready (MbimDevice *self, |
| GAsyncResult *res, |
| DeviceOpenContext *ctx) |
| { |
| MbimMessage *response; |
| GError *error = NULL; |
| |
| response = mbim_device_command_finish (self, res, &error); |
| if (!response) { |
| /* Check if we should be retrying */ |
| if (g_error_matches (error, MBIM_CORE_ERROR, MBIM_CORE_ERROR_TIMEOUT)) { |
| /* The timeout will tell us how many retries we should do */ |
| ctx->timeout -= RETRY_TIMEOUT_SECS; |
| if (ctx->timeout > 0) { |
| g_error_free (error); |
| open_message (ctx); |
| return; |
| } |
| |
| /* No more seconds left in the timeout... return error */ |
| } |
| |
| g_debug ("open operation timed out: closed"); |
| self->priv->open_status = OPEN_STATUS_CLOSED; |
| device_open_context_complete_and_free (ctx, error); |
| return; |
| } |
| |
| if (!mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_OPEN_DONE, &error)) { |
| g_debug ("getting open done result failed: closed"); |
| self->priv->open_status = OPEN_STATUS_CLOSED; |
| device_open_context_complete_and_free (ctx, error); |
| mbim_message_unref (response); |
| return; |
| } |
| |
| mbim_message_unref (response); |
| |
| /* go on */ |
| ctx->step++; |
| device_open_context_step (ctx); |
| } |
| |
| static void |
| open_message (DeviceOpenContext *ctx) |
| { |
| MbimMessage *request; |
| |
| /* Launch 'Open' command */ |
| request = mbim_message_open_new (mbim_device_get_next_transaction_id (ctx->self), |
| ctx->self->priv->max_control_transfer); |
| mbim_device_command (ctx->self, |
| request, |
| RETRY_TIMEOUT_SECS, |
| ctx->cancellable, |
| (GAsyncReadyCallback)open_message_ready, |
| ctx); |
| mbim_message_unref (request); |
| } |
| |
| static void |
| proxy_cfg_message_ready (MbimDevice *self, |
| GAsyncResult *res, |
| DeviceOpenContext *ctx) |
| { |
| MbimMessage *response; |
| GError *error = NULL; |
| |
| response = mbim_device_command_finish (self, res, &error); |
| if (!response) { |
| /* Hard error if proxy cfg command fails */ |
| g_debug ("proxy configuration failed: closed"); |
| self->priv->open_status = OPEN_STATUS_CLOSED; |
| device_open_context_complete_and_free (ctx, error); |
| return; |
| } |
| |
| mbim_message_unref (response); |
| |
| ctx->step++; |
| device_open_context_step (ctx); |
| } |
| |
| static void |
| proxy_cfg_message (DeviceOpenContext *ctx) |
| { |
| GError *error = NULL; |
| MbimMessage *request; |
| |
| request = mbim_message_proxy_control_configuration_set_new (ctx->self->priv->path, ctx->timeout, &error); |
| |
| /* This message is no longer a direct reply; as the proxy will also try to open the device |
| * directly. If it cannot open the device, it will return an error. */ |
| mbim_device_command (ctx->self, |
| request, |
| ctx->timeout, |
| ctx->cancellable, |
| (GAsyncReadyCallback)proxy_cfg_message_ready, |
| ctx); |
| mbim_message_unref (request); |
| } |
| |
| static void |
| create_iochannel_ready (MbimDevice *self, |
| GAsyncResult *res, |
| DeviceOpenContext *ctx) |
| { |
| GError *error = NULL; |
| |
| if (!create_iochannel_finish (self, res, &error)) { |
| g_debug ("creating iochannel failed: closed"); |
| self->priv->open_status = OPEN_STATUS_CLOSED; |
| device_open_context_complete_and_free (ctx, error); |
| return; |
| } |
| |
| /* Go on */ |
| ctx->step++; |
| device_open_context_step (ctx); |
| } |
| |
| static void |
| device_open_context_step (DeviceOpenContext *ctx) |
| { |
| switch (ctx->step) { |
| case DEVICE_OPEN_CONTEXT_STEP_FIRST: |
| if (ctx->self->priv->open_status == OPEN_STATUS_OPEN) { |
| GError *error; |
| |
| error = g_error_new (MBIM_CORE_ERROR, |
| MBIM_CORE_ERROR_WRONG_STATE, |
| "Already open"); |
| device_open_context_complete_and_free (ctx, error); |
| return; |
| } |
| |
| if (ctx->self->priv->open_status == OPEN_STATUS_OPENING) { |
| GError *error; |
| |
| error = g_error_new (MBIM_CORE_ERROR, |
| MBIM_CORE_ERROR_WRONG_STATE, |
| "Already opening"); |
| device_open_context_complete_and_free (ctx, error); |
| return; |
| } |
| |
| g_debug ("opening device..."); |
| g_assert (ctx->self->priv->open_status == OPEN_STATUS_CLOSED); |
| ctx->self->priv->open_status = OPEN_STATUS_OPENING; |
| |
| ctx->step++; |
| /* Fall down */ |
| |
| case DEVICE_OPEN_CONTEXT_STEP_CREATE_IOCHANNEL: |
| create_iochannel (ctx->self, |
| !!(ctx->flags & MBIM_DEVICE_OPEN_FLAGS_PROXY), |
| (GAsyncReadyCallback)create_iochannel_ready, |
| ctx); |
| return; |
| |
| case DEVICE_OPEN_CONTEXT_STEP_FLAGS_PROXY: |
| if (ctx->flags & MBIM_DEVICE_OPEN_FLAGS_PROXY) { |
| proxy_cfg_message (ctx); |
| return; |
| } |
| ctx->step++; |
| /* Fall down */ |
| |
| case DEVICE_OPEN_CONTEXT_STEP_OPEN_MESSAGE: |
| /* If the device is already in-session, avoid the open message */ |
| if (!ctx->self->priv->in_session) { |
| open_message (ctx); |
| return; |
| } |
| ctx->step++; |
| /* Fall down */ |
| |
| case DEVICE_OPEN_CONTEXT_STEP_LAST: |
| /* Nothing else to process, complete without error */ |
| ctx->self->priv->open_status = OPEN_STATUS_OPEN; |
| device_open_context_complete_and_free (ctx, NULL); |
| return; |
| |
| default: |
| break; |
| } |
| |
| g_assert_not_reached (); |
| } |
| |
| /** |
| * mbim_device_open_full: |
| * @self: a #MbimDevice. |
| * @flags: a set of #MbimDeviceOpenFlags. |
| * @timeout: maximum time, in seconds, to wait for the device to be opened. |
| * @cancellable: optional #GCancellable object, #NULL to ignore. |
| * @callback: a #GAsyncReadyCallback to call when the operation is finished. |
| * @user_data: the data to pass to callback function. |
| * |
| * Asynchronously opens a #MbimDevice for I/O. |
| * |
| * This method is an extension of the generic mbim_device_open(), which allows |
| * launching the #MbimDevice with proxy support. |
| * |
| * When the operation is finished @callback will be called. You can then call |
| * mbim_device_open_full_finish() to get the result of the operation. |
| */ |
| void |
| mbim_device_open_full (MbimDevice *self, |
| MbimDeviceOpenFlags flags, |
| guint timeout, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DeviceOpenContext *ctx; |
| |
| g_return_if_fail (MBIM_IS_DEVICE (self)); |
| g_return_if_fail (timeout > 0); |
| |
| ctx = g_slice_new (DeviceOpenContext); |
| ctx->self = g_object_ref (self); |
| ctx->result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| mbim_device_open_full); |
| ctx->step = DEVICE_OPEN_CONTEXT_STEP_FIRST; |
| ctx->flags = flags; |
| ctx->timeout = timeout; |
| ctx->cancellable = (cancellable ? g_object_ref (cancellable) : NULL); |
| |
| /* Start processing */ |
| device_open_context_step (ctx); |
| } |
| |
| /** |
| * mbim_device_open: |
| * @self: a #MbimDevice. |
| * @timeout: maximum time, in seconds, to wait for the device to be opened. |
| * @cancellable: optional #GCancellable object, #NULL to ignore. |
| * @callback: a #GAsyncReadyCallback to call when the operation is finished. |
| * @user_data: the data to pass to callback function. |
| * |
| * Asynchronously opens a #MbimDevice for I/O. |
| * |
| * When the operation is finished @callback will be called. You can then call |
| * mbim_device_open_finish() to get the result of the operation. |
| */ |
| void |
| mbim_device_open (MbimDevice *self, |
| guint timeout, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| mbim_device_open_full (self, |
| MBIM_DEVICE_OPEN_FLAGS_NONE, |
| timeout, |
| cancellable, |
| callback, |
| user_data); |
| } |
| |
| /*****************************************************************************/ |
| /* Close channel */ |
| |
| static gboolean |
| destroy_iochannel (MbimDevice *self, |
| GError **error) |
| { |
| GError *inner_error = NULL; |
| |
| self->priv->open_status = OPEN_STATUS_CLOSED; |
| |
| /* Already closed? */ |
| if (!self->priv->iochannel && !self->priv->socket_connection && !self->priv->socket_client) |
| return TRUE; |
| |
| if (self->priv->iochannel) { |
| g_io_channel_shutdown (self->priv->iochannel, TRUE, &inner_error); |
| g_io_channel_unref (self->priv->iochannel); |
| self->priv->iochannel = NULL; |
| } |
| |
| /* Failures when closing still make the device to get closed */ |
| g_clear_object (&self->priv->socket_connection); |
| g_clear_object (&self->priv->socket_client); |
| |
| if (self->priv->iochannel_source) { |
| g_source_destroy (self->priv->iochannel_source); |
| g_source_unref (self->priv->iochannel_source); |
| self->priv->iochannel_source = NULL; |
| } |
| |
| if (self->priv->response) { |
| g_byte_array_unref (self->priv->response); |
| self->priv->response = NULL; |
| } |
| |
| if (inner_error) { |
| g_propagate_error (error, inner_error); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| /** |
| * mbim_device_close_force: |
| * @self: a #MbimDevice. |
| * @error: Return location for error or %NULL. |
| * |
| * Forces the #MbimDevice to be closed. |
| * |
| * Returns: %TRUE if @self if no error happens, otherwise %FALSE and @error is set. |
| */ |
| gboolean |
| mbim_device_close_force (MbimDevice *self, |
| GError **error) |
| { |
| g_return_val_if_fail (MBIM_IS_DEVICE (self), FALSE); |
| |
| return destroy_iochannel (self, error); |
| } |
| |
| typedef struct { |
| MbimDevice *self; |
| GSimpleAsyncResult *result; |
| GCancellable *cancellable; |
| guint timeout; |
| } DeviceCloseContext; |
| |
| static void |
| device_close_context_complete_and_free (DeviceCloseContext *ctx) |
| { |
| g_simple_async_result_complete_in_idle (ctx->result); |
| g_object_unref (ctx->result); |
| if (ctx->cancellable) |
| g_object_unref (ctx->cancellable); |
| g_object_unref (ctx->self); |
| g_slice_free (DeviceCloseContext, ctx); |
| } |
| |
| /** |
| * mbim_device_close_finish: |
| * @self: a #MbimDevice. |
| * @res: a #GAsyncResult. |
| * @error: Return location for error or %NULL. |
| * |
| * Finishes an asynchronous close operation started with mbim_device_close(). |
| * |
| * Returns: %TRUE if successful, %FALSE if @error is set. |
| */ |
| gboolean |
| mbim_device_close_finish (MbimDevice *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); |
| } |
| |
| static void |
| close_message_ready (MbimDevice *self, |
| GAsyncResult *res, |
| DeviceCloseContext *ctx) |
| { |
| MbimMessage *response; |
| GError *error = NULL; |
| |
| response = mbim_device_command_finish (self, res, &error); |
| if (!response) |
| g_simple_async_result_take_error (ctx->result, error); |
| else if (!mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_CLOSE_DONE, &error)) |
| g_simple_async_result_take_error (ctx->result, error); |
| else |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| |
| if (response) |
| mbim_message_unref (response); |
| device_close_context_complete_and_free (ctx); |
| } |
| |
| /** |
| * mbim_device_close: |
| * @self: a #MbimDevice. |
| * @timeout: maximum time, in seconds, to wait for the device to be closed. |
| * @cancellable: optional #GCancellable object, #NULL to ignore. |
| * @callback: a #GAsyncReadyCallback to call when the operation is finished. |
| * @user_data: the data to pass to callback function. |
| * |
| * Asynchronously closes a #MbimDevice for I/O. |
| * |
| * When the operation is finished @callback will be called. You can then call |
| * mbim_device_close_finish() to get the result of the operation. |
| */ |
| void |
| mbim_device_close (MbimDevice *self, |
| guint timeout, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MbimMessage *request; |
| DeviceCloseContext *ctx; |
| |
| g_return_if_fail (MBIM_IS_DEVICE (self)); |
| |
| ctx = g_slice_new (DeviceCloseContext); |
| ctx->self = g_object_ref (self); |
| ctx->result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| mbim_device_close); |
| ctx->timeout = timeout; |
| ctx->cancellable = (cancellable ? g_object_ref (cancellable) : NULL); |
| |
| /* Already closed? */ |
| if (!self->priv->iochannel) { |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| device_close_context_complete_and_free (ctx); |
| return; |
| } |
| |
| /* If the device is in-session, avoid the close message */ |
| if (self->priv->in_session) { |
| GError *error = NULL; |
| |
| if (!destroy_iochannel (self, &error)) |
| g_simple_async_result_take_error (ctx->result, error); |
| else |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| device_close_context_complete_and_free (ctx); |
| return; |
| } |
| |
| /* Launch 'Close' command */ |
| request = mbim_message_close_new (mbim_device_get_next_transaction_id (self)); |
| mbim_device_command (self, |
| request, |
| 10, |
| ctx->cancellable, |
| (GAsyncReadyCallback) close_message_ready, |
| ctx); |
| mbim_message_unref (request); |
| } |
| |
| /*****************************************************************************/ |
| |
| /** |
| * mbim_device_get_next_transaction_id: |
| * @self: A #MbimDevice. |
| * |
| * Acquire the next transaction ID of this #MbimDevice. |
| * The internal transaction ID gets incremented. |
| * |
| * Returns: the next transaction ID. |
| */ |
| guint32 |
| mbim_device_get_next_transaction_id (MbimDevice *self) |
| { |
| guint32 next; |
| |
| g_return_val_if_fail (MBIM_IS_DEVICE (self), 0); |
| |
| next = self->priv->transaction_id; |
| |
| /* Don't go further than 8bits in the CTL service */ |
| if (self->priv->transaction_id == G_MAXUINT32) |
| /* Reset! */ |
| self->priv->transaction_id = 0x01; |
| else |
| self->priv->transaction_id++; |
| |
| return next; |
| } |
| |
| /*****************************************************************************/ |
| |
| static gboolean |
| device_write (MbimDevice *self, |
| const guint8 *data, |
| guint32 data_length, |
| GError **error) |
| { |
| gsize written; |
| GIOStatus write_status; |
| |
| written = 0; |
| write_status = G_IO_STATUS_AGAIN; |
| while (write_status == G_IO_STATUS_AGAIN) { |
| write_status = g_io_channel_write_chars (self->priv->iochannel, |
| (gconstpointer)data, |
| (gssize)data_length, |
| &written, |
| error); |
| switch (write_status) { |
| case G_IO_STATUS_ERROR: |
| g_prefix_error (error, "Cannot write message: "); |
| return FALSE; |
| |
| case G_IO_STATUS_EOF: |
| /* We shouldn't get EOF when writing */ |
| g_assert_not_reached (); |
| break; |
| |
| case G_IO_STATUS_NORMAL: |
| /* All good, we'll exit the loop now */ |
| break; |
| |
| case G_IO_STATUS_AGAIN: |
| /* We're in a non-blocking channel and therefore we're up to receive |
| * EAGAIN; just retry in this case. TODO: in an idle? */ |
| break; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| device_send (MbimDevice *self, |
| MbimMessage *message, |
| GError **error) |
| { |
| const guint8 *raw_message; |
| guint32 raw_message_len; |
| struct fragment_info *fragments; |
| guint n_fragments; |
| guint i; |
| |
| raw_message = mbim_message_get_raw (message, &raw_message_len, NULL); |
| g_assert (raw_message); |
| |
| if (mbim_utils_get_traces_enabled ()) { |
| gchar *printable; |
| |
| printable = __mbim_utils_str_hex (raw_message, raw_message_len, ':'); |
| g_debug ("[%s] Sent message...\n" |
| "<<<<<< RAW:\n" |
| "<<<<<< length = %u\n" |
| "<<<<<< data = %s\n", |
| self->priv->path_display, |
| ((GByteArray *)message)->len, |
| printable); |
| g_free (printable); |
| |
| printable = mbim_message_get_printable (message, "<<<<<< ", FALSE); |
| g_debug ("[%s] Sent message (translated)...\n%s", |
| self->priv->path_display, |
| printable); |
| g_free (printable); |
| } |
| |
| /* Single fragment? Send it! */ |
| if (raw_message_len <= MAX_CONTROL_TRANSFER) |
| return device_write (self, raw_message, raw_message_len, error); |
| |
| /* The message to send must be able to handle fragments */ |
| g_assert (_mbim_message_is_fragment (message)); |
| |
| fragments = _mbim_message_split_fragments (message, |
| MAX_CONTROL_TRANSFER, |
| &n_fragments); |
| for (i = 0; i < n_fragments; i++) { |
| if (mbim_utils_get_traces_enabled ()) { |
| GByteArray *bytearray; |
| gchar *printable; |
| gchar *printable_h; |
| gchar *printable_fh; |
| gchar *printable_d; |
| |
| printable_h = __mbim_utils_str_hex (&fragments[i].header, sizeof (fragments[i].header), ':'); |
| printable_fh = __mbim_utils_str_hex (&fragments[i].fragment_header, sizeof (fragments[i].fragment_header), ':'); |
| printable_d = __mbim_utils_str_hex (fragments[i].data, fragments[i].data_length, ':'); |
| g_debug ("[%s] Sent fragment (%u)...\n" |
| "<<<<<< RAW:\n" |
| "<<<<<< length = %u\n" |
| "<<<<<< data = %s%s%s\n", |
| self->priv->path_display, i, |
| (guint)(sizeof (fragments[i].header) + |
| sizeof (fragments[i].fragment_header) + |
| fragments[i].data_length), |
| printable_h, printable_fh, printable_d); |
| g_free (printable_h); |
| g_free (printable_fh); |
| g_free (printable_d); |
| |
| /* Dummy message for printable purposes only */ |
| bytearray = g_byte_array_new (); |
| g_byte_array_append (bytearray, (guint8 *)&fragments[i].header, sizeof (fragments[i].header)); |
| g_byte_array_append (bytearray, (guint8 *)&fragments[i].fragment_header, sizeof (fragments[i].fragment_header)); |
| printable = mbim_message_get_printable ((MbimMessage *)bytearray, "<<<<<< ", TRUE); |
| g_debug ("[%s] Sent fragment (translated)...\n%s", |
| self->priv->path_display, |
| printable); |
| g_free (printable); |
| g_byte_array_unref (bytearray); |
| } |
| |
| /* Write fragment headers */ |
| if (!device_write (self, |
| (guint8 *)&fragments[i].header, |
| sizeof (fragments[i].header), |
| error)) |
| return FALSE; |
| |
| if (!device_write (self, |
| (guint8 *)&fragments[i].fragment_header, |
| sizeof (fragments[i].fragment_header), |
| error)) |
| return FALSE; |
| |
| /* Write fragment data */ |
| if (!device_write (self, |
| fragments[i].data, |
| fragments[i].data_length, |
| error)) |
| return FALSE; |
| } |
| g_free (fragments); |
| |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| /* Report error */ |
| |
| typedef struct { |
| MbimDevice *self; |
| MbimMessage *message; |
| } ReportErrorContext; |
| |
| static void |
| device_report_error_context_free (ReportErrorContext *ctx) |
| { |
| mbim_message_unref (ctx->message); |
| g_object_unref (ctx->self); |
| g_slice_free (ReportErrorContext, ctx); |
| } |
| |
| static gboolean |
| device_report_error_in_idle (ReportErrorContext *ctx) |
| { |
| /* Device must be open */ |
| if (ctx->self->priv->iochannel) { |
| GError *error = NULL; |
| |
| if (!device_send (ctx->self, ctx->message, &error)) { |
| g_warning ("[%s] Couldn't send host error message: %s", |
| ctx->self->priv->path_display, |
| error->message); |
| g_error_free (error); |
| } |
| } |
| |
| device_report_error_context_free (ctx); |
| return FALSE; |
| } |
| |
| static void |
| device_report_error (MbimDevice *self, |
| guint32 transaction_id, |
| const GError *error) |
| { |
| ReportErrorContext *ctx; |
| GSource *source; |
| |
| /* Only protocol errors to be reported to the modem */ |
| if (error->domain != MBIM_PROTOCOL_ERROR) |
| return; |
| |
| ctx = g_slice_new (ReportErrorContext); |
| ctx->self = g_object_ref (self); |
| ctx->message = mbim_message_error_new (transaction_id, error->code); |
| |
| source = g_idle_source_new (); |
| g_source_set_callback (source, (GSourceFunc)device_report_error_in_idle, ctx, NULL); |
| g_source_attach (source, g_main_context_get_thread_default ()); |
| g_source_unref (source); |
| } |
| |
| /*****************************************************************************/ |
| /* Command */ |
| |
| /** |
| * mbim_device_command_finish: |
| * @self: a #MbimDevice. |
| * @res: a #GAsyncResult. |
| * @error: Return location for error or %NULL. |
| * |
| * Finishes an operation started with mbim_device_command(). |
| * |
| * Returns: a #MbimMessage response, or #NULL if @error is set. The returned value should be freed with mbim_message_unref(). |
| */ |
| MbimMessage * |
| mbim_device_command_finish (MbimDevice *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) |
| return NULL; |
| |
| return mbim_message_ref (g_simple_async_result_get_op_res_gpointer ( |
| G_SIMPLE_ASYNC_RESULT (res))); |
| } |
| |
| /** |
| * mbim_device_command: |
| * @self: a #MbimDevice. |
| * @message: the message to send. |
| * @timeout: maximum time, in seconds, to wait for the response. |
| * @cancellable: a #GCancellable, or %NULL. |
| * @callback: a #GAsyncReadyCallback to call when the operation is finished. |
| * @user_data: the data to pass to callback function. |
| * |
| * Asynchronously sends a #MbimMessage to the device. |
| * |
| * When the operation is finished @callback will be called. You can then call |
| * mbim_device_command_finish() to get the result of the operation. |
| */ |
| void |
| mbim_device_command (MbimDevice *self, |
| MbimMessage *message, |
| guint timeout, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GError *error = NULL; |
| Transaction *tr; |
| guint32 transaction_id; |
| |
| g_return_if_fail (MBIM_IS_DEVICE (self)); |
| g_return_if_fail (message != NULL); |
| |
| /* If the message comes without a explicit transaction ID, add one |
| * ourselves */ |
| transaction_id = mbim_message_get_transaction_id (message); |
| if (!transaction_id) { |
| transaction_id = mbim_device_get_next_transaction_id (self); |
| mbim_message_set_transaction_id (message, transaction_id); |
| } |
| |
| tr = transaction_new (self, |
| MBIM_MESSAGE_GET_MESSAGE_TYPE (message), |
| transaction_id, |
| cancellable, |
| callback, |
| user_data); |
| |
| /* Device must be open */ |
| if (!self->priv->iochannel) { |
| error = g_error_new (MBIM_CORE_ERROR, |
| MBIM_CORE_ERROR_WRONG_STATE, |
| "Device must be open to send commands"); |
| transaction_complete_and_free (tr, error); |
| g_error_free (error); |
| return; |
| } |
| |
| /* Setup context to match response */ |
| if (!device_store_transaction (self, TRANSACTION_TYPE_HOST, tr, timeout * 1000, &error)) { |
| g_prefix_error (&error, "Cannot store transaction: "); |
| transaction_complete_and_free (tr, error); |
| g_error_free (error); |
| return; |
| } |
| |
| if (!device_send (self, message, &error)) { |
| /* Match transaction so that we remove it from our tracking table */ |
| tr = device_release_transaction (self, |
| TRANSACTION_TYPE_HOST, |
| MBIM_MESSAGE_GET_MESSAGE_TYPE (message), |
| mbim_message_get_transaction_id (message)); |
| transaction_complete_and_free (tr, error); |
| g_error_free (error); |
| return; |
| } |
| |
| /* Just return, we'll get response asynchronously */ |
| } |
| |
| /*****************************************************************************/ |
| /* New MBIM device */ |
| |
| /** |
| * mbim_device_new_finish: |
| * @res: a #GAsyncResult. |
| * @error: Return location for error or %NULL. |
| * |
| * Finishes an operation started with mbim_device_new(). |
| * |
| * Returns: A newly created #MbimDevice, or #NULL if @error is set. |
| */ |
| MbimDevice * |
| mbim_device_new_finish (GAsyncResult *res, |
| GError **error) |
| { |
| GObject *ret; |
| GObject *source_object; |
| |
| source_object = g_async_result_get_source_object (res); |
| ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error); |
| g_object_unref (source_object); |
| |
| return (ret ? MBIM_DEVICE (ret) : NULL); |
| } |
| |
| /** |
| * mbim_device_new: |
| * @file: a #GFile. |
| * @cancellable: optional #GCancellable object, #NULL to ignore. |
| * @callback: a #GAsyncReadyCallback to call when the initialization is finished. |
| * @user_data: the data to pass to callback function. |
| * |
| * Asynchronously creates a #MbimDevice object to manage @file. |
| * When the operation is finished, @callback will be invoked. You can then call |
| * mbim_device_new_finish() to get the result of the operation. |
| */ |
| void |
| mbim_device_new (GFile *file, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| g_async_initable_new_async (MBIM_TYPE_DEVICE, |
| G_PRIORITY_DEFAULT, |
| cancellable, |
| callback, |
| user_data, |
| MBIM_DEVICE_FILE, file, |
| NULL); |
| } |
| |
| /*****************************************************************************/ |
| /* Async init */ |
| |
| typedef struct { |
| MbimDevice *self; |
| GSimpleAsyncResult *result; |
| GCancellable *cancellable; |
| } InitContext; |
| |
| static void |
| init_context_complete_and_free (InitContext *ctx) |
| { |
| g_simple_async_result_complete_in_idle (ctx->result); |
| if (ctx->cancellable) |
| g_object_unref (ctx->cancellable); |
| g_object_unref (ctx->result); |
| g_object_unref (ctx->self); |
| g_slice_free (InitContext, ctx); |
| } |
| |
| static gboolean |
| initable_init_finish (GAsyncInitable *initable, |
| GAsyncResult *result, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error); |
| } |
| |
| static void |
| query_info_async_ready (GFile *file, |
| GAsyncResult *res, |
| InitContext *ctx) |
| { |
| GError *error = NULL; |
| GFileInfo *info; |
| |
| info = g_file_query_info_finish (file, res, &error); |
| if (!info) { |
| g_prefix_error (&error, |
| "Couldn't query file info: "); |
| g_simple_async_result_take_error (ctx->result, error); |
| init_context_complete_and_free (ctx); |
| return; |
| } |
| |
| /* Our MBIM device must be of SPECIAL type */ |
| if (g_file_info_get_file_type (info) != G_FILE_TYPE_SPECIAL) { |
| g_simple_async_result_set_error (ctx->result, |
| MBIM_CORE_ERROR, |
| MBIM_CORE_ERROR_FAILED, |
| "Wrong file type"); |
| init_context_complete_and_free (ctx); |
| return; |
| } |
| g_object_unref (info); |
| |
| /* Done we are */ |
| g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); |
| init_context_complete_and_free (ctx); |
| } |
| |
| static void |
| initable_init_async (GAsyncInitable *initable, |
| int io_priority, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| InitContext *ctx; |
| |
| ctx = g_slice_new0 (InitContext); |
| ctx->self = g_object_ref (initable); |
| if (cancellable) |
| ctx->cancellable = g_object_ref (cancellable); |
| ctx->result = g_simple_async_result_new (G_OBJECT (initable), |
| callback, |
| user_data, |
| initable_init_async); |
| |
| /* We need a proper file to initialize */ |
| if (!ctx->self->priv->file) { |
| g_simple_async_result_set_error (ctx->result, |
| MBIM_CORE_ERROR, |
| MBIM_CORE_ERROR_INVALID_ARGS, |
| "Cannot initialize MBIM device: No file given"); |
| init_context_complete_and_free (ctx); |
| return; |
| } |
| |
| /* Check the file type. Note that this is just a quick check to avoid |
| * creating MbimDevices pointing to a location already known not to be a MBIM |
| * device. */ |
| g_file_query_info_async (ctx->self->priv->file, |
| G_FILE_ATTRIBUTE_STANDARD_TYPE, |
| G_FILE_QUERY_INFO_NONE, |
| G_PRIORITY_DEFAULT, |
| ctx->cancellable, |
| (GAsyncReadyCallback)query_info_async_ready, |
| ctx); |
| } |
| |
| /*****************************************************************************/ |
| |
| static void |
| set_property (GObject *object, |
| guint prop_id, |
| const GValue *value, |
| GParamSpec *pspec) |
| { |
| MbimDevice *self = MBIM_DEVICE (object); |
| |
| switch (prop_id) { |
| case PROP_FILE: |
| g_assert (self->priv->file == NULL); |
| self->priv->file = g_value_dup_object (value); |
| self->priv->path = g_file_get_path (self->priv->file); |
| self->priv->path_display = g_filename_display_name (self->priv->path); |
| break; |
| case PROP_TRANSACTION_ID: |
| self->priv->transaction_id = g_value_get_uint (value); |
| break; |
| case PROP_IN_SESSION: |
| self->priv->in_session = g_value_get_boolean (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| get_property (GObject *object, |
| guint prop_id, |
| GValue *value, |
| GParamSpec *pspec) |
| { |
| MbimDevice *self = MBIM_DEVICE (object); |
| |
| switch (prop_id) { |
| case PROP_FILE: |
| g_value_set_object (value, self->priv->file); |
| break; |
| case PROP_TRANSACTION_ID: |
| g_value_set_uint (value, self->priv->transaction_id); |
| break; |
| case PROP_IN_SESSION: |
| g_value_set_boolean (value, self->priv->in_session); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| mbim_device_init (MbimDevice *self) |
| { |
| self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), |
| MBIM_TYPE_DEVICE, |
| MbimDevicePrivate); |
| |
| /* Initialize transaction ID */ |
| self->priv->transaction_id = 0x01; |
| self->priv->open_status = OPEN_STATUS_CLOSED; |
| } |
| |
| static void |
| dispose (GObject *object) |
| { |
| MbimDevice *self = MBIM_DEVICE (object); |
| |
| g_clear_object (&self->priv->file); |
| |
| destroy_iochannel (self, NULL); |
| |
| G_OBJECT_CLASS (mbim_device_parent_class)->dispose (object); |
| } |
| |
| static void |
| finalize (GObject *object) |
| { |
| MbimDevice *self = MBIM_DEVICE (object); |
| guint i; |
| |
| /* Transactions keep refs to the device, so it's actually |
| * impossible to have any content in the HT */ |
| for (i = 0; i < TRANSACTION_TYPE_LAST; i++) { |
| if (self->priv->transactions[i]) { |
| g_assert (g_hash_table_size (self->priv->transactions[i]) == 0); |
| g_hash_table_unref (self->priv->transactions[i]); |
| self->priv->transactions[i] = NULL; |
| } |
| } |
| |
| g_free (self->priv->path); |
| g_free (self->priv->path_display); |
| |
| G_OBJECT_CLASS (mbim_device_parent_class)->finalize (object); |
| } |
| |
| static void |
| async_initable_iface_init (GAsyncInitableIface *iface) |
| { |
| iface->init_async = initable_init_async; |
| iface->init_finish = initable_init_finish; |
| } |
| |
| static void |
| mbim_device_class_init (MbimDeviceClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| |
| g_type_class_add_private (object_class, sizeof (MbimDevicePrivate)); |
| |
| object_class->get_property = get_property; |
| object_class->set_property = set_property; |
| object_class->finalize = finalize; |
| object_class->dispose = dispose; |
| |
| properties[PROP_FILE] = |
| g_param_spec_object (MBIM_DEVICE_FILE, |
| "Device file", |
| "File to the underlying MBIM device", |
| G_TYPE_FILE, |
| G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); |
| g_object_class_install_property (object_class, PROP_FILE, properties[PROP_FILE]); |
| |
| properties[PROP_TRANSACTION_ID] = |
| g_param_spec_uint (MBIM_DEVICE_TRANSACTION_ID, |
| "Transaction ID", |
| "Current transaction ID", |
| 0x01, |
| G_MAXUINT32, |
| 0x01, |
| G_PARAM_READWRITE); |
| g_object_class_install_property (object_class, PROP_TRANSACTION_ID, properties[PROP_TRANSACTION_ID]); |
| |
| properties[PROP_IN_SESSION] = |
| g_param_spec_boolean (MBIM_DEVICE_IN_SESSION, |
| "In session", |
| "Flag to specify if the device is within a session", |
| FALSE, |
| G_PARAM_READWRITE); |
| g_object_class_install_property (object_class, PROP_IN_SESSION, properties[PROP_IN_SESSION]); |
| |
| /** |
| * MbimDevice::device-indicate-status: |
| * @self: the #MbimDevice |
| * @message: the #MbimMessage indication |
| * |
| * The ::device-indication-status signal is emitted when a MBIM indication is received. |
| */ |
| signals[SIGNAL_INDICATE_STATUS] = |
| g_signal_new (MBIM_DEVICE_SIGNAL_INDICATE_STATUS, |
| G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)), |
| G_SIGNAL_RUN_LAST, |
| 0, |
| NULL, |
| NULL, |
| NULL, |
| G_TYPE_NONE, |
| 1, |
| MBIM_TYPE_MESSAGE); |
| |
| /** |
| * MbimDevice::device-error: |
| * @self: the #MbimDevice |
| * @message: the #MbimMessage error |
| * |
| * The ::device-error signal is emitted when a MBIM error is received. |
| */ |
| signals[SIGNAL_ERROR] = |
| g_signal_new (MBIM_DEVICE_SIGNAL_ERROR, |
| G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)), |
| G_SIGNAL_RUN_LAST, |
| 0, |
| NULL, |
| NULL, |
| NULL, |
| G_TYPE_NONE, |
| 1, |
| G_TYPE_ERROR); |
| |
| /** |
| * MbimDevice::device-removed: |
| * @self: the #MbimDevice |
| * @message: None |
| * |
| * The ::device-removed signal is emitted when an unexpected port hang-up is received. |
| */ |
| signals[SIGNAL_REMOVED] = |
| g_signal_new (MBIM_DEVICE_SIGNAL_REMOVED, |
| G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)), |
| G_SIGNAL_RUN_LAST, |
| 0, |
| NULL, |
| NULL, |
| NULL, |
| G_TYPE_NONE, |
| 0); |
| } |