/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libqmi-glib -- GLib/GIO based library to control QMI 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) 2012 Lanedo GmbH * Copyright (C) 2012-2021 Aleksander Morgado * Copyright (c) 2022 Qualcomm Innovation Center, Inc. */ #include #include #include #include #include #include #include #include "qmi-device.h" #include "qmi-message.h" #include "qmi-file.h" #include "qmi-endpoint.h" #include "qmi-endpoint-mbim.h" #include "qmi-endpoint-qmux.h" #include "qmi-ctl.h" #include "qmi-dms.h" #include "qmi-wds.h" #include "qmi-nas.h" #include "qmi-wms.h" #include "qmi-pdc.h" #include "qmi-pds.h" #include "qmi-pbm.h" #include "qmi-uim.h" #include "qmi-sar.h" #include "qmi-oma.h" #include "qmi-wda.h" #include "qmi-voice.h" #include "qmi-loc.h" #include "qmi-qos.h" #include "qmi-gas.h" #include "qmi-gms.h" #include "qmi-dsd.h" #include "qmi-dpm.h" #include "qmi-fox.h" #include "qmi-utils.h" #include "qmi-helpers.h" #include "qmi-error-types.h" #include "qmi-enum-types.h" #include "qmi-flag-types.h" #include "qmi-proxy.h" #include "qmi-net-port-manager-qmiwwan.h" #include "qmi-version.h" #if QMI_QRTR_SUPPORTED # include "qmi-endpoint-qrtr.h" # include #endif #if defined RMNET_SUPPORT_ENABLED # include "qmi-net-port-manager-rmnet.h" #endif /* maximum number of printed data bytes when personal info * should be hidden */ #define MAX_PRINTED_BYTES 12 static void async_initable_iface_init (GAsyncInitableIface *iface); G_DEFINE_TYPE_EXTENDED (QmiDevice, qmi_device, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)) enum { PROP_0, PROP_FILE, PROP_NO_FILE_CHECK, PROP_PROXY_PATH, PROP_WWAN_IFACE, PROP_CONSECUTIVE_TIMEOUTS, #if QMI_QRTR_SUPPORTED PROP_NODE, #endif PROP_LAST }; enum { SIGNAL_INDICATION, SIGNAL_REMOVED, SIGNAL_LAST }; static GParamSpec *properties[PROP_LAST]; static guint signals [SIGNAL_LAST] = { 0 }; struct _QmiDevicePrivate { /* File or node */ QmiFile *file; #if QMI_QRTR_SUPPORTED QrtrNode *node; #endif gboolean no_file_check; /* WWAN interface */ gboolean no_wwan_check; gchar *wwan_iface; /* Information necessary for data port */ QmiNetPortManager *net_port_manager; /* Implicit CTL client */ QmiClientCtl *client_ctl; guint sync_indication_id; /* Supported services */ GArray *supported_services; /* Lower-level transport */ QmiEndpoint *endpoint; guint endpoint_new_data_id; guint endpoint_hangup_id; /* Support for qmi-proxy */ gchar *proxy_path; /* HT to keep track of ongoing transactions */ GHashTable *transactions; /* HT of clients that want to get indications */ GHashTable *registered_clients; /* Number of consecutive timeouts detected */ guint consecutive_timeouts; }; #if QMI_QRTR_SUPPORTED # define QMI_CLIENT_VERSION_UNKNOWN 99 #endif /*****************************************************************************/ /* Message transactions (private) */ typedef struct { QmiDevice *self; gpointer key; } TransactionWaitContext; typedef struct { QmiMessage *message; QmiMessageContext *message_context; GSimpleAsyncResult *result; GSource *timeout_source; GCancellable *cancellable; gulong cancellable_id; TransactionWaitContext *wait_ctx; /* abortable support */ GError *abort_error; GCancellable *abort_cancellable; QmiDeviceCommandAbortableBuildRequestFn abort_build_request_fn; QmiDeviceCommandAbortableParseResponseFn abort_parse_response_fn; gpointer abort_user_data; GDestroyNotify abort_user_data_free; } Transaction; static Transaction * transaction_new (QmiDevice *self, QmiMessage *message, QmiMessageContext *message_context, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { Transaction *tr; tr = g_slice_new0 (Transaction); tr->message = qmi_message_ref (message); tr->message_context = (message_context ? qmi_message_context_ref (message_context) : NULL); tr->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, transaction_new); if (cancellable) tr->cancellable = g_object_ref (cancellable); return tr; } static void transaction_complete_and_free (Transaction *tr, QmiMessage *reply, const GError *error) { g_assert (reply != NULL || error != NULL); /* always set result first, as we may be using one of the GErrors * stored in the Transaction as result itself */ if (reply) { /* if we got a valid response, we can cancel any ongoing abort * operation for this request */ if (tr->abort_cancellable) g_cancellable_cancel (tr->abort_cancellable); g_simple_async_result_set_op_res_gpointer (tr->result, qmi_message_ref (reply), (GDestroyNotify)qmi_message_unref); } else if (error) g_simple_async_result_set_from_error (tr->result, error); else g_assert_not_reached (); 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 (tr->abort_error) g_error_free (tr->abort_error); if (tr->abort_cancellable) g_object_unref (tr->abort_cancellable); if (tr->abort_user_data && tr->abort_user_data_free) tr->abort_user_data_free (tr->abort_user_data); g_simple_async_result_complete_in_idle (tr->result); g_object_unref (tr->result); if (tr->message_context) qmi_message_context_unref (tr->message_context); qmi_message_unref (tr->message); g_slice_free (Transaction, tr); } static inline gpointer build_transaction_key (QmiMessage *message) { gpointer key; guint8 service; guint8 client_id; guint16 transaction_id; service = (guint8)qmi_message_get_service (message); client_id = qmi_message_get_client_id (message); transaction_id = qmi_message_get_transaction_id (message); /* We're putting a 32 bit value into a gpointer */ key = GUINT_TO_POINTER ((((service << 8) | client_id) << 16) | transaction_id); return key; } static Transaction * device_peek_transaction (QmiDevice *self, gconstpointer key) { return g_hash_table_lookup (self->priv->transactions, key); } static Transaction * device_release_transaction (QmiDevice *self, gconstpointer key) { Transaction *tr = NULL; /* If found, remove it from the HT */ tr = device_peek_transaction (self, key); if (tr) g_hash_table_remove (self->priv->transactions, key); return tr; } static void transaction_abort_ready (QmiDevice *self, GAsyncResult *res, gpointer key) { Transaction *tr; GError *error = NULL; QmiMessage *abort_response; /* Always try to remove from the HT at this point. Note that the transaction * pointer may be NULL already, e.g. if the abort was cancelled. In this case * we totally ignore the result of the abort operation. */ tr = device_release_transaction (self, key); if (!tr) { g_debug ("[%s] not processing abort response, operation has already been completed", qmi_file_get_path_display (self->priv->file)); return; } g_assert (tr->abort_parse_response_fn); abort_response = qmi_device_command_full_finish (self, res, &error); if (!abort_response || !tr->abort_parse_response_fn (self, abort_response, tr->abort_user_data, &error)) { GError *built_error; g_debug ("[%s] abort operation failed: %s", qmi_file_get_path_display (self->priv->file), error->message); /* We don't want to return any kind of error, because what failed here * is the abort operation for the user request, so always return * QMI_CORE_ERROR_FAILED and provide the description on the error * in the message. */ built_error = g_error_new (QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "operation failed and couldn't be aborted: %s", error->message); g_error_free (error); transaction_complete_and_free (tr, NULL, built_error); g_error_free (built_error); } else { /* aborted successfully */ g_debug ("operation aborted successfully"); g_assert (tr->abort_error); transaction_complete_and_free (tr, NULL, tr->abort_error); } if (abort_response) qmi_message_unref (abort_response); } static void transaction_abort (QmiDevice *self, Transaction *tr, GError *abort_error_take) { QmiMessage *abort_request; GError *error = NULL; guint16 transaction_id; transaction_id = qmi_message_get_transaction_id (tr->message); /* If the command is not abortable, we'll return the error right away * to the user. */ if (!__qmi_message_is_abortable (tr->message, tr->message_context)) { g_debug ("[%s] transaction 0x%x aborted, but message is not abortable", qmi_file_get_path_display (self->priv->file), transaction_id); device_release_transaction (self, tr->wait_ctx->key); transaction_complete_and_free (tr, NULL, abort_error_take); g_error_free (abort_error_take); return; } /* if the command is abortable but the user didn't use qmi_device_command_abortable(), * then return the error right away anyway */ if (!tr->abort_build_request_fn || !tr->abort_parse_response_fn) { g_debug ("[%s] transaction 0x%x aborted, but no way to build abort request", qmi_file_get_path_display (self->priv->file), transaction_id); device_release_transaction (self, tr->wait_ctx->key); transaction_complete_and_free (tr, NULL, abort_error_take); g_error_free (abort_error_take); return; } g_debug ("[%s] transaction 0x%x aborted, building abort request...", qmi_file_get_path_display (self->priv->file), transaction_id); /* Try to build abort request */ abort_request = tr->abort_build_request_fn (self, tr->message, tr->abort_user_data, &error); if (!abort_request) { /* complete the transaction with the error we got while building the * abort request */ g_debug ("[%s] transaction 0x%x aborted, but building abort request failed", qmi_file_get_path_display (self->priv->file), transaction_id); device_release_transaction (self, tr->wait_ctx->key); transaction_complete_and_free (tr, NULL, error); g_error_free (error); return; } /* If command is abortable, let's abort the operation in the * device. We'll store the specific abort error to use once the abort * operation has been acknowledged by the device. */ tr->abort_error = abort_error_take; tr->abort_cancellable = g_cancellable_new (); qmi_device_command_full (self, abort_request, NULL, 30, tr->abort_cancellable, (GAsyncReadyCallback) transaction_abort_ready, tr->wait_ctx->key); qmi_message_unref (abort_request); } static gboolean transaction_timed_out (TransactionWaitContext *ctx) { Transaction *tr; GError *error = NULL; /* A timed out transaction is always tracked */ tr = device_peek_transaction (ctx->self, ctx->key); g_assert (tr); tr->timeout_source = NULL; /* Increase number of consecutive timeouts */ ctx->self->priv->consecutive_timeouts++; g_object_notify_by_pspec (G_OBJECT (ctx->self), properties[PROP_CONSECUTIVE_TIMEOUTS]); g_debug ("[%s] number of consecutive timeouts: %u", qmi_file_get_path_display (ctx->self->priv->file), ctx->self->priv->consecutive_timeouts); error = g_error_new (QMI_CORE_ERROR, QMI_CORE_ERROR_TIMEOUT, "Transaction timed out"); transaction_abort (ctx->self, tr, error); return G_SOURCE_REMOVE; } static void transaction_cancelled (GCancellable *cancellable, TransactionWaitContext *ctx) { Transaction *tr; GError *error = NULL; tr = device_peek_transaction (ctx->self, ctx->key); /* The transaction may have already been cancelled before we stored it in * the tracking table, which means the command was NOT sent to the device * and we can safely ignore the cancellation request. */ if (!tr) return; tr->cancellable_id = 0; error = g_error_new (QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_ABORTED, "Transaction aborted"); transaction_abort (ctx->self, tr, error); } static gboolean device_store_transaction (QmiDevice *self, Transaction *tr, guint timeout, GError **error) { gpointer key; Transaction *existing; key = build_transaction_key (tr->message); /* Setup the timeout and cancellation */ tr->wait_ctx = g_slice_new (TransactionWaitContext); tr->wait_ctx->self = self; tr->wait_ctx->key = key; /* valid as long as the transaction is in the HT */ /* Timeout is optional (e.g. disabled when MBIM is used) */ if (timeout > 0) { tr->timeout_source = g_timeout_source_new_seconds (timeout); 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) { /* 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 (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_ABORTED, "Request is already cancelled"); return FALSE; } } /* If we have already a transaction with the same ID complete the existing * one with an error before the new one is added, or we'll end up with * dangling timeouts and cancellation handlers that may be fired off later * on. */ existing = device_release_transaction (self, key); if (existing) { GError *inner_error; /* Complete transaction with an abort error */ inner_error = g_error_new (QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_ABORTED, "Transaction overwritten"); transaction_complete_and_free (existing, NULL, inner_error); g_error_free (inner_error); } /* Keep in the HT */ g_hash_table_insert (self->priv->transactions, key, tr); return TRUE; } static Transaction * device_match_transaction (QmiDevice *self, QmiMessage *message) { /* msg can be either the original message or the response */ return device_release_transaction (self, build_transaction_key (message)); } /*****************************************************************************/ static void device_hangup_transactions (QmiDevice *self) { GHashTableIter iter; gpointer key, value; g_autoptr(GError) common_error = NULL; common_error = g_error_new (QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "endpoint hangup"); g_hash_table_iter_init (&iter, self->priv->transactions); while (g_hash_table_iter_next (&iter, &key, &value)) { Transaction *tr = value; transaction_complete_and_free (tr, NULL, common_error); g_hash_table_iter_remove (&iter); } } /*****************************************************************************/ guint qmi_device_get_consecutive_timeouts (QmiDevice *self) { g_return_val_if_fail (QMI_IS_DEVICE (self), 0); return self->priv->consecutive_timeouts; } /*****************************************************************************/ /* Version info request */ GArray * qmi_device_get_service_version_info_finish (QmiDevice *self, GAsyncResult *res, GError **error) { return g_task_propagate_pointer (G_TASK (res), error); } static void version_info_ready (QmiClientCtl *client_ctl, GAsyncResult *res, GTask *task) { GArray *service_list = NULL; GArray *out; QmiMessageCtlGetVersionInfoOutput *output; GError *error = NULL; guint i; /* Check result of the async operation */ output = qmi_client_ctl_get_version_info_finish (client_ctl, res, &error); if (!output) { g_task_return_error (task, error); g_object_unref (task); return; } /* Check result of the QMI operation */ if (!qmi_message_ctl_get_version_info_output_get_result (output, &error)) { qmi_message_ctl_get_version_info_output_unref (output); g_task_return_error (task, error); g_object_unref (task); return; } /* QMI operation succeeded, we can now get the outputs */ qmi_message_ctl_get_version_info_output_get_service_list (output, &service_list, NULL); out = g_array_sized_new (FALSE, FALSE, sizeof (QmiDeviceServiceVersionInfo), service_list->len); for (i = 0; i < service_list->len; i++) { QmiMessageCtlGetVersionInfoOutputServiceListService *info; QmiDeviceServiceVersionInfo outinfo; info = &g_array_index (service_list, QmiMessageCtlGetVersionInfoOutputServiceListService, i); outinfo.service = info->service; outinfo.major_version = info->major_version; outinfo.minor_version = info->minor_version; g_array_append_val (out, outinfo); } qmi_message_ctl_get_version_info_output_unref (output); g_task_return_pointer (task, out, (GDestroyNotify)g_array_unref); g_object_unref (task); } void qmi_device_get_service_version_info (QmiDevice *self, guint timeout, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { qmi_client_ctl_get_version_info ( self->priv->client_ctl, NULL, timeout, cancellable, (GAsyncReadyCallback)version_info_ready, g_task_new (self, cancellable, callback, user_data)); } /*****************************************************************************/ /* Version info checks (private) */ static const QmiMessageCtlGetVersionInfoOutputServiceListService * find_service_version_info (QmiDevice *self, QmiService service) { guint i; if (!self->priv->supported_services) return NULL; for (i = 0; i < self->priv->supported_services->len; i++) { const QmiMessageCtlGetVersionInfoOutputServiceListService *info; info = &g_array_index (self->priv->supported_services, QmiMessageCtlGetVersionInfoOutputServiceListService, i); if (service == info->service) return info; } return NULL; } static gboolean check_service_supported (QmiDevice *self, QmiService service) { /* If we didn't check supported services, just assume it is supported */ if (!self->priv->supported_services) { g_debug ("[%s] assuming service '%s' is supported...", qmi_file_get_path_display (self->priv->file), qmi_service_get_string (service)); return TRUE; } return !!find_service_version_info (self, service); } /*****************************************************************************/ GFile * qmi_device_get_file (QmiDevice *self) { g_return_val_if_fail (QMI_IS_DEVICE (self), NULL); return qmi_file_get_file (self->priv->file); } GFile * qmi_device_peek_file (QmiDevice *self) { g_return_val_if_fail (QMI_IS_DEVICE (self), NULL); return qmi_file_peek_file (self->priv->file); } const gchar * qmi_device_get_path (QmiDevice *self) { g_return_val_if_fail (QMI_IS_DEVICE (self), NULL); return qmi_file_get_path (self->priv->file); } const gchar * qmi_device_get_path_display (QmiDevice *self) { g_return_val_if_fail (QMI_IS_DEVICE (self), NULL); return qmi_file_get_path_display (self->priv->file); } gboolean qmi_device_is_open (QmiDevice *self) { g_return_val_if_fail (QMI_IS_DEVICE (self), FALSE); return !!self->priv->endpoint && qmi_endpoint_is_open (self->priv->endpoint); } /*****************************************************************************/ /* WWAN iface name * Always reload from scratch, to handle possible net interface renames */ static void reload_wwan_iface_name (QmiDevice *self) { g_autofree gchar *cdc_wdm_device_name = NULL; guint i; g_autoptr(GError) error = NULL; static const gchar *driver_names[] = { "usbmisc", /* kernel >= 3.6 */ "usb" }; /* kernel < 3.6 */ #if QMI_QRTR_SUPPORTED /* QRTR doesn't have a device file in sysfs, exit right away */ if (self->priv->node) return; #endif g_clear_pointer (&self->priv->wwan_iface, g_free); cdc_wdm_device_name = qmi_helpers_get_devname (qmi_file_get_path (self->priv->file), &error); if (!cdc_wdm_device_name) { g_warning ("[%s] invalid path for cdc-wdm control port: %s", qmi_file_get_path_display (self->priv->file), error->message); return; } for (i = 0; i < G_N_ELEMENTS (driver_names) && !self->priv->wwan_iface; i++) { g_autofree gchar *sysfs_path = NULL; g_autoptr(GFile) sysfs_file = NULL; g_autoptr(GFileEnumerator) enumerator = NULL; GFileInfo *file_info = NULL; /* WWAN iface name loading only applicable for qmi_wwan driver right now * (so QMI port exposed by the cdc-wdm driver in the usbmisc subsystem), * not for QRTR or any other subsystem or driver */ sysfs_path = g_strdup_printf ("/sys/class/%s/%s/device/net/", driver_names[i], cdc_wdm_device_name); sysfs_file = g_file_new_for_path (sysfs_path); enumerator = g_file_enumerate_children (sysfs_file, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (!enumerator) continue; /* Ignore errors when enumerating */ while ((file_info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) { const gchar *name; name = g_file_info_get_name (file_info); if (name) { /* We only expect ONE file in the sysfs directory corresponding * to this control port, if more found for any reason, warn about it */ if (self->priv->wwan_iface) g_warning ("[%s] invalid additional wwan iface found: %s", qmi_file_get_path_display (self->priv->file), name); else self->priv->wwan_iface = g_strdup (name); } g_object_unref (file_info); } if (!self->priv->wwan_iface) g_warning ("[%s] wwan iface not found", qmi_file_get_path_display (self->priv->file)); } /* wwan_iface won't be set at this point if the kernel driver in use isn't in * the usbmisc subsystem */ } const gchar * qmi_device_get_wwan_iface (QmiDevice *self) { g_return_val_if_fail (QMI_IS_DEVICE (self), NULL); reload_wwan_iface_name (self); return self->priv->wwan_iface; } /*****************************************************************************/ /* Expected data format */ static gboolean validate_yes_or_no (const gchar value, GError **error) { if (value != 'Y' && value != 'N') { g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "Unexpected sysfs file contents: %c", value); return FALSE; } return TRUE; } static gchar * build_raw_ip_sysfs_path (QmiDevice *self) { return g_strdup_printf ("/sys/class/net/%s/qmi/raw_ip", self->priv->wwan_iface); } static gchar * build_pass_through_sysfs_path (QmiDevice *self) { return g_strdup_printf ("/sys/class/net/%s/qmi/pass_through", self->priv->wwan_iface); } static gboolean get_expected_data_format (QmiDevice *self, const gchar *raw_ip_sysfs_path, const gchar *pass_through_sysfs_path, GError **error) { gchar raw_ip_value = '\0'; gchar pass_through_value = '\0'; if (!qmi_helpers_read_sysfs_file (raw_ip_sysfs_path, &raw_ip_value, 1, error) || !validate_yes_or_no (raw_ip_value, error)) return QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN; if (raw_ip_value == 'N') return QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3; if (qmi_helpers_read_sysfs_file (pass_through_sysfs_path, &pass_through_value, 1, NULL) && (pass_through_value == 'Y')) return QMI_DEVICE_EXPECTED_DATA_FORMAT_QMAP_PASS_THROUGH; return QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP; } static gboolean set_expected_data_format (QmiDevice *self, const gchar *raw_ip_sysfs_path, const gchar *pass_through_sysfs_path, QmiDeviceExpectedDataFormat requested, GError **error) { if (requested == QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3) { qmi_helpers_write_sysfs_file (pass_through_sysfs_path, "N", NULL); return qmi_helpers_write_sysfs_file (raw_ip_sysfs_path, "N", error); } if (requested == QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP) { qmi_helpers_write_sysfs_file (pass_through_sysfs_path, "N", NULL); return qmi_helpers_write_sysfs_file (raw_ip_sysfs_path, "Y", error); } if (requested == QMI_DEVICE_EXPECTED_DATA_FORMAT_QMAP_PASS_THROUGH) { return (qmi_helpers_write_sysfs_file (raw_ip_sysfs_path, "Y", error) && qmi_helpers_write_sysfs_file (pass_through_sysfs_path, "Y", error)); } g_assert_not_reached (); } static QmiDeviceExpectedDataFormat common_get_set_expected_data_format (QmiDevice *self, QmiDeviceExpectedDataFormat requested, GError **error) { g_autofree gchar *raw_ip = NULL; g_autofree gchar *pass_through = NULL; QmiDeviceExpectedDataFormat expected = QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN; gboolean readonly; /* Make sure we load the WWAN iface name */ reload_wwan_iface_name (self); /* Expected data format setting and getting is only supported in the qmi_wwan * driver, same as the WWAN iface name detection. Therefore, if we cannot load * the WWAN iface name we can safely assume that we're not using the qmi_wwan * driver. */ if (!self->priv->wwan_iface) { g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_UNSUPPORTED, "Setting expected data format management is unsupported by the driver"); return QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN; } readonly = (requested == QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN); /* Build sysfs file paths */ raw_ip = build_raw_ip_sysfs_path (self); pass_through = build_pass_through_sysfs_path (self); /* Set operation? */ if (!readonly && !set_expected_data_format (self, raw_ip, pass_through, requested, error)) return QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN; /* Get/Set operations */ if ((expected = get_expected_data_format (self, raw_ip, pass_through, error)) == QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN) return QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN; /* If we requested an update but we didn't read that value, report an error */ if (!readonly && (requested != expected)) { g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "Expected data format not updated properly to '%s': got '%s' instead", qmi_device_expected_data_format_get_string (requested), qmi_device_expected_data_format_get_string (expected)); return QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN; } /* If the set operation succeeds, we clear the net port manager, as we may need to use a * different one */ g_clear_object (&self->priv->net_port_manager); return expected; } QmiDeviceExpectedDataFormat qmi_device_get_expected_data_format (QmiDevice *self, GError **error) { g_return_val_if_fail (QMI_IS_DEVICE (self), QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN); return common_get_set_expected_data_format (self, QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN, error); } gboolean qmi_device_set_expected_data_format (QmiDevice *self, QmiDeviceExpectedDataFormat format, GError **error) { g_return_val_if_fail (QMI_IS_DEVICE (self), FALSE); return (common_get_set_expected_data_format (self, format, error) != QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN); } gboolean qmi_device_check_expected_data_format_supported (QmiDevice *self, QmiDeviceExpectedDataFormat format, GError **error) { g_autofree gchar *sysfs_path = NULL; gchar value = '\0'; g_return_val_if_fail (QMI_IS_DEVICE (self), FALSE); switch (format) { case QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3: return TRUE; case QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP: reload_wwan_iface_name (self); sysfs_path = build_raw_ip_sysfs_path (self); break; case QMI_DEVICE_EXPECTED_DATA_FORMAT_QMAP_PASS_THROUGH: reload_wwan_iface_name (self); sysfs_path = build_pass_through_sysfs_path (self); break; default: case QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN: g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "Unknown expected data format given: 0x%x", format); return FALSE; } g_assert (sysfs_path); return (qmi_helpers_read_sysfs_file (sysfs_path, &value, 1, error) && validate_yes_or_no (value, error)); } /*****************************************************************************/ /* Register/Unregister clients that want to receive indications */ static gpointer build_registered_client_key (guint8 cid, QmiService service) { return GUINT_TO_POINTER (((guint8)service << 8) | cid); } static gboolean register_client (QmiDevice *self, QmiClient *client, GError **error) { gpointer key; key = build_registered_client_key (qmi_client_get_cid (client), qmi_client_get_service (client)); /* Only add the new client if not already registered one with the same CID * for the same service */ if (g_hash_table_lookup (self->priv->registered_clients, key)) { g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "A client with CID '%u' and service '%s' is already registered", qmi_client_get_cid (client), qmi_service_get_string (qmi_client_get_service (client))); return FALSE; } g_hash_table_insert (self->priv->registered_clients, key, g_object_ref (client)); return TRUE; } static void unregister_client (QmiDevice *self, QmiClient *client) { g_hash_table_remove (self->priv->registered_clients, build_registered_client_key (qmi_client_get_cid (client), qmi_client_get_service (client))); } /*****************************************************************************/ /* Allocate new client */ typedef struct { QmiService service; GType client_type; guint8 cid; } AllocateClientContext; static void allocate_client_context_free (AllocateClientContext *ctx) { g_slice_free (AllocateClientContext, ctx); } QmiClient * qmi_device_allocate_client_finish (QmiDevice *self, GAsyncResult *res, GError **error) { return g_task_propagate_pointer (G_TASK (res), error); } static void build_client_object (GTask *task) { QmiDevice *self; AllocateClientContext *ctx; gchar *version_string = NULL; QmiClient *client; GError *error = NULL; const QmiMessageCtlGetVersionInfoOutputServiceListService *version_info; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); /* We now have a proper CID for the client, we should be able to create it * right away */ client = g_object_new (ctx->client_type, QMI_CLIENT_DEVICE, self, QMI_CLIENT_SERVICE, ctx->service, QMI_CLIENT_CID, ctx->cid, NULL); /* Add version info to the client if it was retrieved */ version_info = find_service_version_info (self, ctx->service); if (version_info) g_object_set (client, QMI_CLIENT_VERSION_MAJOR, version_info->major_version, QMI_CLIENT_VERSION_MINOR, version_info->minor_version, NULL); #if QMI_QRTR_SUPPORTED else if (self->priv->node) { /* QRTR does not have any way of fetching version information. Assume * all services can handle all message types and TLVs. */ g_debug ("[%s] client version cannot be retrieved when using QRTR", qmi_file_get_path_display (self->priv->file)); g_object_set (client, QMI_CLIENT_VERSION_MAJOR, QMI_CLIENT_VERSION_UNKNOWN, QMI_CLIENT_VERSION_MINOR, QMI_CLIENT_VERSION_UNKNOWN, NULL); } #endif /* Register the client to get indications */ if (!register_client (self, client, &error)) { g_prefix_error (&error, "Cannot register new client with CID '%u' and service '%s'", ctx->cid, qmi_service_get_string (ctx->service)); g_task_return_error (task, error); g_object_unref (task); g_object_unref (client); return; } /* Build version string for the logging */ if (self->priv->supported_services) { const QmiMessageCtlGetVersionInfoOutputServiceListService *info; info = find_service_version_info (self, ctx->service); if (info) version_string = g_strdup_printf ("%u.%u", info->major_version, info->minor_version); } g_debug ("[%s] registered '%s' (version %s) client with ID '%u'", qmi_file_get_path_display (self->priv->file), qmi_service_get_string (ctx->service), version_string ? version_string : "unknown", ctx->cid); g_free (version_string); /* Client created and registered, complete successfully */ g_task_return_pointer (task, client, g_object_unref); g_object_unref (task); } static void allocate_cid_ready (QmiClientCtl *client_ctl, GAsyncResult *res, GTask *task) { QmiMessageCtlAllocateCidOutput *output; QmiService service; guint8 cid; GError *error = NULL; AllocateClientContext *ctx; /* Check result of the async operation */ output = qmi_client_ctl_allocate_cid_finish (client_ctl, res, &error); if (!output) { g_prefix_error (&error, "CID allocation failed in the CTL client: "); g_task_return_error (task, error); g_object_unref (task); return; } /* Check result of the QMI operation */ if (!qmi_message_ctl_allocate_cid_output_get_result (output, &error)) { g_task_return_error (task, error); g_object_unref (task); qmi_message_ctl_allocate_cid_output_unref (output); return; } /* Allocation info is mandatory when result is success */ g_assert (qmi_message_ctl_allocate_cid_output_get_allocation_info (output, &service, &cid, NULL)); ctx = g_task_get_task_data (task); if (service != ctx->service) { g_task_return_new_error ( task, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "CID allocation failed in the CTL client: " "Service mismatch (requested '%s', got '%s')", qmi_service_get_string (ctx->service), qmi_service_get_string (service)); g_object_unref (task); qmi_message_ctl_allocate_cid_output_unref (output); return; } ctx->cid = cid; build_client_object (task); qmi_message_ctl_allocate_cid_output_unref (output); } void qmi_device_allocate_client (QmiDevice *self, QmiService service, guint8 cid, guint timeout, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { AllocateClientContext *ctx; GTask *task; g_return_if_fail (QMI_IS_DEVICE (self)); g_return_if_fail (service != QMI_SERVICE_UNKNOWN); ctx = g_slice_new0 (AllocateClientContext); ctx->service = service; ctx->client_type = G_TYPE_INVALID; task = g_task_new (self, cancellable, callback, user_data); g_task_set_task_data (task, ctx, (GDestroyNotify)allocate_client_context_free); /* Check if the requested service is supported by the device */ if (!check_service_supported (self, service)) { g_task_return_new_error (task, QMI_CORE_ERROR, QMI_CORE_ERROR_UNSUPPORTED, "Service '%s' not supported by the device", qmi_service_get_string (service)); g_object_unref (task); return; } switch (service) { case QMI_SERVICE_CTL: g_task_return_new_error (task, QMI_CORE_ERROR, QMI_CORE_ERROR_INVALID_ARGS, "Cannot create additional clients for the CTL service"); g_object_unref (task); return; case QMI_SERVICE_DMS: #if defined HAVE_QMI_SERVICE_DMS ctx->client_type = QMI_TYPE_CLIENT_DMS; #endif break; case QMI_SERVICE_WDS: #if defined HAVE_QMI_SERVICE_WDS ctx->client_type = QMI_TYPE_CLIENT_WDS; #endif break; case QMI_SERVICE_NAS: #if defined HAVE_QMI_SERVICE_NAS ctx->client_type = QMI_TYPE_CLIENT_NAS; #endif break; case QMI_SERVICE_WMS: #if defined HAVE_QMI_SERVICE_WMS ctx->client_type = QMI_TYPE_CLIENT_WMS; #endif break; case QMI_SERVICE_PDS: #if defined HAVE_QMI_SERVICE_PDS ctx->client_type = QMI_TYPE_CLIENT_PDS; #endif break; case QMI_SERVICE_PDC: #if defined HAVE_QMI_SERVICE_PDC ctx->client_type = QMI_TYPE_CLIENT_PDC; #endif break; case QMI_SERVICE_PBM: #if defined HAVE_QMI_SERVICE_PBM ctx->client_type = QMI_TYPE_CLIENT_PBM; #endif break; case QMI_SERVICE_UIM: #if defined HAVE_QMI_SERVICE_UIM ctx->client_type = QMI_TYPE_CLIENT_UIM; #endif break; case QMI_SERVICE_OMA: #if defined HAVE_QMI_SERVICE_OMA ctx->client_type = QMI_TYPE_CLIENT_OMA; #endif break; case QMI_SERVICE_GAS: #if defined HAVE_QMI_SERVICE_GAS ctx->client_type = QMI_TYPE_CLIENT_GAS; #endif break; case QMI_SERVICE_GMS: #if defined HAVE_QMI_SERVICE_GMS ctx->client_type = QMI_TYPE_CLIENT_GMS; #endif break; case QMI_SERVICE_WDA: #if defined HAVE_QMI_SERVICE_WDA ctx->client_type = QMI_TYPE_CLIENT_WDA; #endif break; case QMI_SERVICE_VOICE: #if defined HAVE_QMI_SERVICE_VOICE ctx->client_type = QMI_TYPE_CLIENT_VOICE; #endif break; case QMI_SERVICE_LOC: #if defined HAVE_QMI_SERVICE_LOC ctx->client_type = QMI_TYPE_CLIENT_LOC; #endif break; case QMI_SERVICE_QOS: #if defined HAVE_QMI_SERVICE_QOS ctx->client_type = QMI_TYPE_CLIENT_QOS; #endif break; case QMI_SERVICE_DSD: #if defined HAVE_QMI_SERVICE_DSD ctx->client_type = QMI_TYPE_CLIENT_DSD; #endif break; case QMI_SERVICE_SAR: #if defined HAVE_QMI_SERVICE_SAR ctx->client_type = QMI_TYPE_CLIENT_SAR; #endif break; case QMI_SERVICE_DPM: #if defined HAVE_QMI_SERVICE_DPM ctx->client_type = QMI_TYPE_CLIENT_DPM; #endif break; case QMI_SERVICE_FOX: #if defined HAVE_QMI_SERVICE_FOX ctx->client_type = QMI_TYPE_CLIENT_FOX; #endif break; case QMI_SERVICE_UNKNOWN: g_assert_not_reached (); case QMI_SERVICE_AUTH: case QMI_SERVICE_AT: case QMI_SERVICE_CAT2: case QMI_SERVICE_QCHAT: case QMI_SERVICE_RMTFS: case QMI_SERVICE_TEST: case QMI_SERVICE_IMS: case QMI_SERVICE_ADC: case QMI_SERVICE_CSD: case QMI_SERVICE_MFS: case QMI_SERVICE_TIME: case QMI_SERVICE_TS: case QMI_SERVICE_TMD: case QMI_SERVICE_SAP: case QMI_SERVICE_TSYNC: case QMI_SERVICE_RFSA: case QMI_SERVICE_CSVT: case QMI_SERVICE_QCMAP: case QMI_SERVICE_IMSP: case QMI_SERVICE_IMSVT: case QMI_SERVICE_IMSA: case QMI_SERVICE_COEX: case QMI_SERVICE_STX: case QMI_SERVICE_BIT: case QMI_SERVICE_IMSRTP: case QMI_SERVICE_RFRPE: case QMI_SERVICE_SSCTL: case QMI_SERVICE_CAT: case QMI_SERVICE_RMS: case QMI_SERVICE_FOTA: default: break; } if (ctx->client_type == G_TYPE_INVALID) { g_task_return_new_error (task, QMI_CORE_ERROR, QMI_CORE_ERROR_INVALID_ARGS, "Clients for service '%s' not supported", qmi_service_get_string (service)); g_object_unref (task); return; } /* Allocate a new CID for the client to be created */ if (cid == QMI_CID_NONE) { QmiMessageCtlAllocateCidInput *input; input = qmi_message_ctl_allocate_cid_input_new (); qmi_message_ctl_allocate_cid_input_set_service (input, ctx->service, NULL); g_debug ("[%s] allocating new client ID...", qmi_file_get_path_display (self->priv->file)); qmi_client_ctl_allocate_cid (self->priv->client_ctl, input, timeout, cancellable, (GAsyncReadyCallback)allocate_cid_ready, task); qmi_message_ctl_allocate_cid_input_unref (input); return; } /* Reuse the given CID */ g_debug ("[%s] reusing client CID '%u'...", qmi_file_get_path_display (self->priv->file), cid); ctx->cid = cid; build_client_object (task); } /*****************************************************************************/ /* Release client */ gboolean qmi_device_release_client_finish (QmiDevice *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void client_ctl_release_cid_ready (QmiClientCtl *client_ctl, GAsyncResult *res, GTask *task) { GError *error = NULL; QmiMessageCtlReleaseCidOutput *output; /* Note: even if we return an error, the client is to be considered * released! (so shouldn't be used) */ /* Check result of the async operation */ output = qmi_client_ctl_release_cid_finish (client_ctl, res, &error); if (!output) { g_task_return_error (task, error); g_object_unref (task); return; } /* Check result of the QMI operation */ if (!qmi_message_ctl_release_cid_output_get_result (output, &error)) { g_task_return_error (task, error); g_object_unref (task); qmi_message_ctl_release_cid_output_unref (output); return; } g_task_return_boolean (task, TRUE); g_object_unref (task); qmi_message_ctl_release_cid_output_unref (output); } void qmi_device_release_client (QmiDevice *self, QmiClient *client, QmiDeviceReleaseClientFlags flags, guint timeout, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; QmiService service; guint8 cid; gchar *flags_str; g_return_if_fail (QMI_IS_DEVICE (self)); g_return_if_fail (QMI_IS_CLIENT (client)); cid = qmi_client_get_cid (client); service = (guint8)qmi_client_get_service (client); /* The CTL client should not have been created out of the QmiDevice */ g_return_if_fail (service != QMI_SERVICE_CTL); flags_str = qmi_device_release_client_flags_build_string_from_mask (flags); g_debug ("[%s] releasing '%s' client with flags '%s'...", qmi_file_get_path_display (self->priv->file), qmi_service_get_string (service), flags_str); g_free (flags_str); task = g_task_new (self, cancellable, callback, user_data); /* Do not try to release an already released client */ if (cid == QMI_CID_NONE) { g_task_return_new_error (task, QMI_CORE_ERROR, QMI_CORE_ERROR_INVALID_ARGS, "Client is already released"); g_object_unref (task); return; } /* Keep the client object valid until after we reset its contents below */ g_object_ref (client); /* Unregister from device */ unregister_client (self, client); g_debug ("[%s] unregistered '%s' client with ID '%u'", qmi_file_get_path_display (self->priv->file), qmi_service_get_string (service), cid); /* Reset the contents of the client object, making it invalid */ g_object_set (client, QMI_CLIENT_CID, QMI_CID_NONE, QMI_CLIENT_SERVICE, QMI_SERVICE_UNKNOWN, QMI_CLIENT_DEVICE, NULL, NULL); g_object_unref (client); if (flags & QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID) { QmiMessageCtlReleaseCidInput *input; /* And now, really try to release the CID */ input = qmi_message_ctl_release_cid_input_new (); qmi_message_ctl_release_cid_input_set_release_info (input, service, cid, NULL); qmi_client_ctl_release_cid (self->priv->client_ctl, input, timeout, cancellable, (GAsyncReadyCallback)client_ctl_release_cid_ready, task); qmi_message_ctl_release_cid_input_unref (input); return; } /* No need to release the CID, so just done */ g_task_return_boolean (task, TRUE); g_object_unref (task); return; } /*****************************************************************************/ /* Set instance ID */ gboolean qmi_device_set_instance_id_finish (QmiDevice *self, GAsyncResult *res, guint16 *link_id, GError **error) { gssize value; value = g_task_propagate_int (G_TASK (res), error); if (value == -1) return FALSE; if (link_id) *link_id = (guint16)value; return TRUE; } static void set_instance_id_ready (QmiClientCtl *client_ctl, GAsyncResult *res, GTask *task) { QmiMessageCtlSetInstanceIdOutput *output; GError *error = NULL; /* Check result of the async operation */ output = qmi_client_ctl_set_instance_id_finish (client_ctl, res, &error); if (!output) g_task_return_error (task, error); else { /* Check result of the QMI operation */ if (!qmi_message_ctl_set_instance_id_output_get_result (output, &error)) g_task_return_error (task, error); else { guint16 link_id; qmi_message_ctl_set_instance_id_output_get_link_id (output, &link_id, NULL); g_task_return_int (task, link_id); } qmi_message_ctl_set_instance_id_output_unref (output); } g_object_unref (task); } void qmi_device_set_instance_id (QmiDevice *self, guint8 instance_id, guint timeout, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; QmiMessageCtlSetInstanceIdInput *input; g_return_if_fail (QMI_IS_DEVICE (self)); task = g_task_new (self, cancellable, callback, user_data); input = qmi_message_ctl_set_instance_id_input_new (); qmi_message_ctl_set_instance_id_input_set_id ( input, instance_id, NULL); qmi_client_ctl_set_instance_id (self->priv->client_ctl, input, timeout, cancellable, (GAsyncReadyCallback)set_instance_id_ready, task); qmi_message_ctl_set_instance_id_input_unref (input); } /*****************************************************************************/ /* Input channel processing */ static void process_message (QmiMessage *message, QmiDevice *self); static void endpoint_new_data_cb (QmiEndpoint *endpoint, QmiDevice *self) { GError *error = NULL; if (!qmi_endpoint_parse_buffer (endpoint, (QmiMessageHandler)process_message, self, &error)) { g_warning ("[%s] parsing error: %s", qmi_file_get_path_display (self->priv->file), error->message); g_error_free (error); } } static void endpoint_hangup_cb (QmiEndpoint *endpoint, QmiDevice *self) { g_debug ("[%s] endpoint hangup: removed", qmi_file_get_path_display (self->priv->file)); /* cancel all ongoing transactions as the endpoing hangup happened */ device_hangup_transactions (self); g_signal_emit (self, signals[SIGNAL_REMOVED], 0); } typedef struct { QmiClient *client; QmiMessage *message; } IdleIndicationContext; static gboolean process_indication_idle (IdleIndicationContext *ctx) { g_assert (ctx->client != NULL); g_assert (ctx->message != NULL); __qmi_client_process_indication (ctx->client, ctx->message); g_object_unref (ctx->client); qmi_message_unref (ctx->message); g_slice_free (IdleIndicationContext, ctx); return FALSE; } static void report_indication (QmiClient *client, QmiMessage *message) { IdleIndicationContext *ctx; GSource *source; /* Setup an idle to Pass the indication down to the client */ ctx = g_slice_new (IdleIndicationContext); ctx->client = g_object_ref (client); ctx->message = qmi_message_ref (message); source = g_idle_source_new (); g_source_set_priority (source, G_PRIORITY_DEFAULT); g_source_set_callback (source, (GSourceFunc)process_indication_idle, ctx, NULL); g_source_attach (source, g_main_context_get_thread_default ()); g_source_unref (source); } static void trace_message (QmiDevice *self, QmiMessage *message, gboolean sent_or_received, const gchar *message_str, QmiMessageContext *message_context) { gchar *printable; const gchar *prefix_str; const gchar *action_str; gchar *vendor_str = NULL; if (!qmi_utils_get_traces_enabled ()) return; if (sent_or_received) { prefix_str = "<<<<<< "; action_str = "sent"; } else { prefix_str = "<<<<<< "; action_str = "received"; } if (qmi_utils_get_show_personal_info () || (((GByteArray *)message)->len < MAX_PRINTED_BYTES)) { printable = qmi_helpers_str_hex (((GByteArray *)message)->data, ((GByteArray *)message)->len, ':'); } else { g_autofree gchar *tmp = NULL; tmp = qmi_helpers_str_hex (((GByteArray *)message)->data, MAX_PRINTED_BYTES, ':'); printable = g_strdup_printf ("%s...", tmp); } g_debug ("[%s] %s message...\n" "%sRAW:\n" "%s length = %u\n" "%s data = %s\n", qmi_file_get_path_display (self->priv->file), action_str, prefix_str, prefix_str, ((GByteArray *)message)->len, prefix_str, printable); g_free (printable); if (message_context) { guint16 vendor_id; vendor_id = qmi_message_context_get_vendor_id (message_context); if (vendor_id != QMI_MESSAGE_VENDOR_GENERIC) vendor_str = g_strdup_printf ("vendor-specific (0x%04x)", vendor_id); } printable = qmi_message_get_printable_full (message, message_context, prefix_str); g_debug ("[%s] %s %s %s (translated)...\n%s", qmi_file_get_path_display (self->priv->file), action_str, vendor_str ? vendor_str : "generic", message_str, printable); g_free (printable); g_free (vendor_str); } static void process_message (QmiMessage *message, QmiDevice *self) { if (qmi_message_is_indication (message)) { /* Indication traces translated without an explicit vendor */ trace_message (self, message, FALSE, "indication", NULL); /* Generic emission of the indication */ g_signal_emit (self, signals[SIGNAL_INDICATION], 0, message); if (qmi_message_get_client_id (message) == QMI_CID_BROADCAST) { GHashTableIter iter; gpointer key; QmiClient *client; g_hash_table_iter_init (&iter, self->priv->registered_clients); while (g_hash_table_iter_next (&iter, &key, (gpointer *)&client)) { /* For broadcast messages, report them just if the service matches */ if (qmi_message_get_service (message) == qmi_client_get_service (client)) report_indication (client, message); } } else { QmiClient *client; client = g_hash_table_lookup (self->priv->registered_clients, build_registered_client_key (qmi_message_get_client_id (message), qmi_message_get_service (message))); if (client) report_indication (client, message); } return; } if (qmi_message_is_response (message)) { Transaction *tr; tr = device_match_transaction (self, message); if (!tr) { /* Unmatched transactions translated without an explicit context */ trace_message (self, message, FALSE, "response", NULL); g_debug ("[%s] no transaction matched in received message", qmi_file_get_path_display (self->priv->file)); return; } /* Did the transaction sequence get out of sync? e.g. if the module reboots itself * while we're talking to it. If so, fail the transaction right away without setting the * message as response, or otherwise the parsers will complain */ if (qmi_message_get_message_id (tr->message) != qmi_message_get_message_id (message)) { g_autoptr(GError) error = NULL; error = g_error_new (QMI_CORE_ERROR, QMI_CORE_ERROR_UNEXPECTED_MESSAGE, "Unexpected response of type 0x%04x received matching transaction for request of type 0x%04x", qmi_message_get_message_id (message), qmi_message_get_message_id (tr->message)); /* Translate without an explicit context as this message has nothing to do with the * request. */ trace_message (self, message, FALSE, "response", NULL); g_debug ("[%s] mismatched message id in received message for transaction 0x%04x (expected 0x%04x, received 0x%04x)", qmi_file_get_path_display (self->priv->file), qmi_message_get_transaction_id (message), qmi_message_get_message_id (tr->message), qmi_message_get_message_id (message)); transaction_complete_and_free (tr, NULL, error); return; } /* Reset number of consecutive timeouts */ if (self->priv->consecutive_timeouts > 0) { g_debug ("[%s] reseted number of consecutive timeouts", qmi_file_get_path_display (self->priv->file)); self->priv->consecutive_timeouts = 0; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CONSECUTIVE_TIMEOUTS]); } /* Matched transactions translated with the same context as the request */ trace_message (self, message, FALSE, "response", tr->message_context); /* Report the reply message */ transaction_complete_and_free (tr, message, NULL); return; } /* Unexpected message types translated without an explicit context */ trace_message (self, message, FALSE, "unexpected message", NULL); g_debug ("[%s] message received but it is neither an indication nor a response. Skipping it.", qmi_file_get_path_display (self->priv->file)); } /*****************************************************************************/ static gboolean setup_net_port_manager (QmiDevice *self, GError **error) { QmiDeviceExpectedDataFormat expected_data_format; /* If we have a valid one already, use that one */ if (self->priv->net_port_manager) return TRUE; /* The qmi_wwan driver allows configuring the expected data format, * and depending on the configured one, we'll use one link management * api or another one. */ expected_data_format = qmi_device_get_expected_data_format (self, NULL); switch (expected_data_format) { case QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3: g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "Link management not supported with the expected data format configured as 802.3"); break; case QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP: self->priv->net_port_manager = QMI_NET_PORT_MANAGER (qmi_net_port_manager_qmiwwan_new (self->priv->wwan_iface, error)); break; case QMI_DEVICE_EXPECTED_DATA_FORMAT_QMAP_PASS_THROUGH: case QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN: default: #if defined RMNET_SUPPORT_ENABLED /* when the data format is unknown, it very likely is because this * is not the qmi_wwan driver; fallback to plain rmnet in that * case. */ self->priv->net_port_manager = QMI_NET_PORT_MANAGER (qmi_net_port_manager_rmnet_new (error)); #else g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "Link management not supported"); #endif break; } return !!self->priv->net_port_manager; } /*****************************************************************************/ /* Link management APIs */ typedef struct { guint mux_id; gchar *ifname; } AddLinkResult; static void add_link_result_free (AddLinkResult *ctx) { g_free (ctx->ifname); g_free (ctx); } gchar * qmi_device_add_link_with_flags_finish (QmiDevice *self, GAsyncResult *res, guint *mux_id, GError **error) { AddLinkResult *ctx; gchar *ifname; ctx = g_task_propagate_pointer (G_TASK (res), error); if (!ctx) return NULL; if (mux_id) *mux_id = ctx->mux_id; ifname = g_steal_pointer (&ctx->ifname); add_link_result_free (ctx); return ifname; } gchar * qmi_device_add_link_finish (QmiDevice *self, GAsyncResult *res, guint *mux_id, GError **error) { return qmi_device_add_link_with_flags_finish (self, res, mux_id, error); } static void device_add_link_ready (QmiNetPortManager *net_port_manager, GAsyncResult *res, GTask *task) { GError *error = NULL; AddLinkResult *ctx; ctx = g_new0 (AddLinkResult, 1); ctx->ifname = qmi_net_port_manager_add_link_finish (net_port_manager, &ctx->mux_id, res, &error); if (!ctx->ifname) { g_prefix_error (&error, "Could not allocate link: "); g_task_return_error (task, error); add_link_result_free (ctx); } else g_task_return_pointer (task, ctx, (GDestroyNotify) add_link_result_free); g_object_unref (task); } void qmi_device_add_link_with_flags (QmiDevice *self, guint mux_id, const gchar *base_ifname, const gchar *ifname_prefix, QmiDeviceAddLinkFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; GError *error = NULL; g_return_if_fail (QMI_IS_DEVICE (self)); g_return_if_fail (base_ifname); g_return_if_fail (mux_id >= QMI_DEVICE_MUX_ID_MIN); g_return_if_fail ((mux_id <= QMI_DEVICE_MUX_ID_MAX) || (mux_id == QMI_DEVICE_MUX_ID_AUTOMATIC)); task = g_task_new (self, cancellable, callback, user_data); if (!setup_net_port_manager (self, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } g_assert (self->priv->net_port_manager); qmi_net_port_manager_add_link (self->priv->net_port_manager, mux_id, base_ifname, ifname_prefix, flags, 5, cancellable, (GAsyncReadyCallback) device_add_link_ready, task); } void qmi_device_add_link (QmiDevice *self, guint mux_id, const gchar *base_ifname, const gchar *ifname_prefix, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { qmi_device_add_link_with_flags (self, mux_id, base_ifname, ifname_prefix, QMI_DEVICE_ADD_LINK_FLAGS_NONE, cancellable, callback, user_data); } gboolean qmi_device_delete_link_finish (QmiDevice *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void device_del_link_ready (QmiNetPortManager *net_port_manager, GAsyncResult *res, GTask *task) { GError *error = NULL; if (!qmi_net_port_manager_del_link_finish (net_port_manager, res, &error)) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); g_object_unref (task); } void qmi_device_delete_link (QmiDevice *self, const gchar *ifname, guint mux_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; GError *error = NULL; g_return_if_fail (QMI_IS_DEVICE (self)); g_return_if_fail (ifname); task = g_task_new (self, cancellable, callback, user_data); if (!setup_net_port_manager (self, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } g_assert (self->priv->net_port_manager); qmi_net_port_manager_del_link (self->priv->net_port_manager, ifname, mux_id, 5, /* timeout */ cancellable, (GAsyncReadyCallback) device_del_link_ready, task); } /*****************************************************************************/ gboolean qmi_device_delete_all_links_finish (QmiDevice *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void device_del_all_links_ready (QmiNetPortManager *net_port_manager, GAsyncResult *res, GTask *task) { GError *error = NULL; if (!qmi_net_port_manager_del_all_links_finish (net_port_manager, res, &error)) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); g_object_unref (task); } void qmi_device_delete_all_links (QmiDevice *self, const gchar *base_ifname, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; GError *error = NULL; g_return_if_fail (QMI_IS_DEVICE (self)); g_return_if_fail (base_ifname); task = g_task_new (self, cancellable, callback, user_data); if (!setup_net_port_manager (self, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } g_assert (self->priv->net_port_manager); qmi_net_port_manager_del_all_links (self->priv->net_port_manager, base_ifname, cancellable, (GAsyncReadyCallback) device_del_all_links_ready, task); } /*****************************************************************************/ gboolean qmi_device_list_links (QmiDevice *self, const gchar *base_ifname, GPtrArray **out_links, GError **error) { g_return_val_if_fail (QMI_IS_DEVICE (self), FALSE); g_return_val_if_fail (base_ifname, FALSE); if (!setup_net_port_manager (self, error)) return FALSE; g_assert (self->priv->net_port_manager); return qmi_net_port_manager_list_links (self->priv->net_port_manager, base_ifname, out_links, error); } /*****************************************************************************/ gboolean qmi_device_check_link_supported (QmiDevice *self, GError **error) { g_return_val_if_fail (QMI_IS_DEVICE (self), FALSE); /* if we can setup a net port manager, link management is supported */ return setup_net_port_manager (self, error); } /*****************************************************************************/ /* Open device */ #define SYNC_TIMEOUT_SECS 2 typedef enum { DEVICE_OPEN_CONTEXT_STEP_FIRST = 0, DEVICE_OPEN_CONTEXT_STEP_DRIVER, DEVICE_OPEN_CONTEXT_STEP_CREATE_ENDPOINT, DEVICE_OPEN_CONTEXT_STEP_OPEN_ENDPOINT, DEVICE_OPEN_CONTEXT_STEP_FLAGS_VERSION_INFO, DEVICE_OPEN_CONTEXT_STEP_FLAGS_SYNC, DEVICE_OPEN_CONTEXT_STEP_FLAGS_NETPORT, DEVICE_OPEN_CONTEXT_STEP_FLAGS_EXPECT_INDICATIONS, DEVICE_OPEN_CONTEXT_STEP_LAST } DeviceOpenContextStep; typedef struct { DeviceOpenContextStep step; QmiDeviceOpenFlags flags; guint timeout; guint version_check_retries; guint sync_retries; } DeviceOpenContext; static void device_open_context_free (DeviceOpenContext *ctx) { g_slice_free (DeviceOpenContext, ctx); } gboolean qmi_device_open_finish (QmiDevice *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void device_open_step (GTask *task); static void setup_indications_ready (QmiEndpoint *endpoint, GAsyncResult *res, GTask *task) { GError *error = NULL; DeviceOpenContext *ctx; ctx = g_task_get_task_data (task); if (!qmi_endpoint_setup_indications_finish (endpoint, res, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } ctx->step++; device_open_step (task); } static void ctl_set_data_format_ready (QmiClientCtl *client, GAsyncResult *res, GTask *task) { QmiDevice *self; DeviceOpenContext *ctx; QmiMessageCtlSetDataFormatOutput *output = NULL; GError *error = NULL; output = qmi_client_ctl_set_data_format_finish (client, res, &error); /* Check result of the async operation */ if (!output) { g_task_return_error (task, error); g_object_unref (task); return; } /* Check result of the QMI operation */ if (!qmi_message_ctl_set_data_format_output_get_result (output, &error)) { g_task_return_error (task, error); g_object_unref (task); qmi_message_ctl_set_data_format_output_unref (output); return; } self = g_task_get_source_object (task); g_debug ("[%s] network port data format operation finished", qmi_file_get_path_display (self->priv->file)); qmi_message_ctl_set_data_format_output_unref (output); /* Go on */ ctx = g_task_get_task_data (task); ctx->step++; device_open_step (task); } static void sync_ready (QmiClientCtl *client_ctl, GAsyncResult *res, GTask *task) { QmiDevice *self; DeviceOpenContext *ctx; GError *error = NULL; QmiMessageCtlSyncOutput *output; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); /* Check result of the async operation */ output = qmi_client_ctl_sync_finish (client_ctl, res, &error); if (!output) { if (g_error_matches (error, QMI_CORE_ERROR, QMI_CORE_ERROR_TIMEOUT)) { /* Update retries... */ ctx->sync_retries--; /* If retries left, retry */ if (ctx->sync_retries > 0) { g_error_free (error); qmi_client_ctl_sync (self->priv->client_ctl, NULL, SYNC_TIMEOUT_SECS, g_task_get_cancellable (task), (GAsyncReadyCallback)sync_ready, task); return; } /* Otherwise, propagate the error */ } g_task_return_error (task, error); g_object_unref (task); return; } /* Check result of the QMI operation */ if (!qmi_message_ctl_sync_output_get_result (output, &error)) { g_task_return_error (task, error); g_object_unref (task); qmi_message_ctl_sync_output_unref (output); return; } g_debug ("[%s] sync operation finished", qmi_file_get_path_display (self->priv->file)); qmi_message_ctl_sync_output_unref (output); /* Go on */ ctx->step++; device_open_step (task); } static void open_version_info_ready (QmiClientCtl *client_ctl, GAsyncResult *res, GTask *task) { QmiDevice *self; DeviceOpenContext *ctx; GArray *service_list; QmiMessageCtlGetVersionInfoOutput *output; GError *error = NULL; guint i; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); /* Check result of the async operation */ output = qmi_client_ctl_get_version_info_finish (client_ctl, res, &error); if (!output) { if (g_error_matches (error, QMI_CORE_ERROR, QMI_CORE_ERROR_TIMEOUT)) { /* Update retries... */ ctx->version_check_retries--; /* If retries left, retry */ if (ctx->version_check_retries > 0) { g_error_free (error); qmi_client_ctl_get_version_info (self->priv->client_ctl, NULL, 1, g_task_get_cancellable (task), (GAsyncReadyCallback)open_version_info_ready, task); return; } /* Otherwise, propagate the error */ } g_task_return_error (task, error); g_object_unref (task); return; } /* Check result of the QMI operation */ if (!qmi_message_ctl_get_version_info_output_get_result (output, &error)) { g_task_return_error (task, error); g_object_unref (task); qmi_message_ctl_get_version_info_output_unref (output); return; } /* QMI operation succeeded, we can now get the outputs */ service_list = NULL; qmi_message_ctl_get_version_info_output_get_service_list (output, &service_list, NULL); g_clear_pointer (&self->priv->supported_services, g_array_unref); self->priv->supported_services = g_array_ref (service_list); g_debug ("[%s] device supports %u services:", qmi_file_get_path_display (self->priv->file), self->priv->supported_services->len); for (i = 0; i < self->priv->supported_services->len; i++) { QmiMessageCtlGetVersionInfoOutputServiceListService *info; const gchar *service_str; info = &g_array_index (self->priv->supported_services, QmiMessageCtlGetVersionInfoOutputServiceListService, i); service_str = qmi_service_get_string (info->service); if (service_str) g_debug ("[%s] %s (%u.%u)", qmi_file_get_path_display (self->priv->file), service_str, info->major_version, info->minor_version); else g_debug ("[%s] unknown [0x%02x] (%u.%u)", qmi_file_get_path_display (self->priv->file), info->service, info->major_version, info->minor_version); } qmi_message_ctl_get_version_info_output_unref (output); /* Go on */ ctx->step++; device_open_step (task); } #if QMI_QRTR_SUPPORTED static void build_services_from_qrtr_node (GTask *task) { QmiDevice *self; DeviceOpenContext *ctx; GList *services; guint n_services; GList *elem; QrtrNodeServiceInfo *qrtr_serv_info; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); g_assert (self->priv->node); services = qrtr_node_peek_service_info_list (self->priv->node); n_services = g_list_length (services); g_clear_pointer (&self->priv->supported_services, g_array_unref); self->priv->supported_services = g_array_sized_new (FALSE, FALSE, sizeof (QmiMessageCtlGetVersionInfoOutputServiceListService), n_services); g_debug ("[%s] device supports %u services:", qmi_file_get_path_display (self->priv->file), n_services); for (elem = services; elem; elem = elem->next) { const gchar *service_str; QmiMessageCtlGetVersionInfoOutputServiceListService info; qrtr_serv_info = elem->data; info.service = qrtr_node_service_info_get_service (qrtr_serv_info); info.major_version = qrtr_node_service_info_get_version (qrtr_serv_info); g_array_append_val (self->priv->supported_services, info); service_str = qmi_service_get_string (info.service); if (service_str) g_debug ("[%s] %s (%u) ", qmi_file_get_path_display (self->priv->file), service_str, info.major_version); else g_debug ("[%s] unknown [0x%04x] (%u)", qmi_file_get_path_display (self->priv->file), info.service, info.major_version); } ctx->step++; device_open_step (task); } #endif static void endpoint_ready (QmiEndpoint *endpoint, GAsyncResult *res, GTask *task) { GError *error = NULL; DeviceOpenContext *ctx; ctx = g_task_get_task_data (task); if (!qmi_endpoint_open_finish (endpoint, res, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } ctx->step++; device_open_step (task); } #define NETPORT_FLAGS (QMI_DEVICE_OPEN_FLAGS_NET_802_3 | \ QMI_DEVICE_OPEN_FLAGS_NET_RAW_IP | \ QMI_DEVICE_OPEN_FLAGS_NET_QOS_HEADER | \ QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER) static void device_create_endpoint (QmiDevice *self, DeviceOpenContext *ctx) { if (!(ctx->flags & QMI_DEVICE_OPEN_FLAGS_MBIM)) { #if QMI_QRTR_SUPPORTED /* We talk to proxies over QMUX even if they are proxying a QRTR device. */ if (self->priv->node && !(ctx->flags & QMI_DEVICE_OPEN_FLAGS_PROXY)) { self->priv->endpoint = QMI_ENDPOINT (qmi_endpoint_qrtr_new (self->priv->node)); } else #endif { self->priv->endpoint = QMI_ENDPOINT (qmi_endpoint_qmux_new (self->priv->file, self->priv->proxy_path, self->priv->client_ctl)); } } #if defined MBIM_QMUX_ENABLED else { self->priv->endpoint = QMI_ENDPOINT (qmi_endpoint_mbim_new (self->priv->file)); } #endif /* MBIM_QMUX_ENABLED */ if (!self->priv->endpoint) return; self->priv->endpoint_new_data_id = g_signal_connect (self->priv->endpoint, QMI_ENDPOINT_SIGNAL_NEW_DATA, G_CALLBACK (endpoint_new_data_cb), self); self->priv->endpoint_hangup_id = g_signal_connect (self->priv->endpoint, QMI_ENDPOINT_SIGNAL_HANGUP, G_CALLBACK (endpoint_hangup_cb), self); g_debug ("[%s] created endpoint", qmi_file_get_path_display (self->priv->file)); } static gboolean device_setup_open_flags_by_transport (QmiDevice *self, DeviceOpenContext *ctx, GError **error) { QmiHelpersTransportType transport; GError *inner_error = NULL; transport = qmi_helpers_get_transport_type (qmi_file_get_path (self->priv->file), &inner_error); if ((transport == QMI_HELPERS_TRANSPORT_TYPE_UNKNOWN) && !self->priv->no_file_check) g_warning ("[%s] couldn't detect transport type of port: %s", qmi_file_get_path_display (self->priv->file), inner_error->message); g_clear_error (&inner_error); #if defined MBIM_QMUX_ENABLED /* Auto mode requested? */ if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_AUTO) { switch (transport) { case QMI_HELPERS_TRANSPORT_TYPE_MBIM: g_debug ("[%s] automatically selecting MBIM mode", qmi_file_get_path_display (self->priv->file)); ctx->flags |= QMI_DEVICE_OPEN_FLAGS_MBIM; break; case QMI_HELPERS_TRANSPORT_TYPE_QMUX: g_debug ("[%s] automatically selecting QMI mode", qmi_file_get_path_display (self->priv->file)); ctx->flags &= ~QMI_DEVICE_OPEN_FLAGS_MBIM; break; case QMI_HELPERS_TRANSPORT_TYPE_UNKNOWN: g_set_error (&inner_error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "Cannot automatically select QMI/MBIM mode"); break; default: g_assert_not_reached (); } goto out; } /* MBIM mode requested? */ if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_MBIM) { if ((transport != QMI_HELPERS_TRANSPORT_TYPE_MBIM) && !self->priv->no_file_check) g_warning ("[%s] requested MBIM mode but unexpected transport type found", qmi_file_get_path_display (self->priv->file)); goto out; } #else /* MBIM mode requested? */ if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_MBIM) { g_warning ("[%s] requested MBIM mode but no MBIM QMUX support available", qmi_file_get_path_display (self->priv->file)); goto out; } /* Treat AUTO as QMI mode, without warnings */ #endif /* MBIM_QMUX_ENABLED */ /* QMI mode requested? */ if ((transport != QMI_HELPERS_TRANSPORT_TYPE_QMUX) && !self->priv->no_file_check) g_warning ("[%s] requested QMI mode but unexpected transport type found", qmi_file_get_path_display (self->priv->file)); out: if (inner_error) { g_propagate_error (error, inner_error); return FALSE; } return TRUE; } static void device_open_step (GTask *task) { QmiDevice *self; DeviceOpenContext *ctx; GError *error = NULL; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); switch (ctx->step) { case DEVICE_OPEN_CONTEXT_STEP_FIRST: ctx->step++; /* Fall through */ case DEVICE_OPEN_CONTEXT_STEP_DRIVER: #if QMI_QRTR_SUPPORTED if (self->priv->node) { g_debug ("[%s] selecting QMI mode for QRTR endpoint", qmi_file_get_path_display (self->priv->file)); ctx->flags &= ~QMI_DEVICE_OPEN_FLAGS_MBIM; } else #endif if (!device_setup_open_flags_by_transport (self, ctx, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } ctx->step++; /* Fall through */ case DEVICE_OPEN_CONTEXT_STEP_CREATE_ENDPOINT: device_create_endpoint (self, ctx); if (!self->priv->endpoint) { g_task_return_new_error (task, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "Could not create endpoint"); g_object_unref (task); return; } ctx->step++; /* Fall through */ case DEVICE_OPEN_CONTEXT_STEP_OPEN_ENDPOINT: qmi_endpoint_open (self->priv->endpoint, !!(ctx->flags & QMI_DEVICE_OPEN_FLAGS_PROXY), 5, g_task_get_cancellable (task), (GAsyncReadyCallback)endpoint_ready, task); return; case DEVICE_OPEN_CONTEXT_STEP_FLAGS_VERSION_INFO: /* Query version info? */ if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_VERSION_INFO) { /* Setup how many times to retry... We'll retry once per second */ ctx->version_check_retries = ctx->timeout > 0 ? ctx->timeout : 1; g_debug ("[%s] checking version info (%u retries)...", qmi_file_get_path_display (self->priv->file), ctx->version_check_retries); #if QMI_QRTR_SUPPORTED if (self->priv->node) { g_debug ("[%s] QRTR does not support version info check: checking only for available services", qmi_file_get_path_display (self->priv->file)); build_services_from_qrtr_node (task); } else #endif qmi_client_ctl_get_version_info (self->priv->client_ctl, NULL, 1, g_task_get_cancellable (task), (GAsyncReadyCallback)open_version_info_ready, task); return; } ctx->step++; /* Fall through */ case DEVICE_OPEN_CONTEXT_STEP_FLAGS_SYNC: /* Sync? */ if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_SYNC) { /* Setup how many times to retry... We'll retry once per second */ ctx->sync_retries = ctx->timeout > SYNC_TIMEOUT_SECS ? (ctx->timeout / SYNC_TIMEOUT_SECS) : 1; g_debug ("[%s] running sync (%u retries)...", qmi_file_get_path_display (self->priv->file), ctx->sync_retries); qmi_client_ctl_sync (self->priv->client_ctl, NULL, SYNC_TIMEOUT_SECS, g_task_get_cancellable (task), (GAsyncReadyCallback)sync_ready, task); return; } ctx->step++; /* Fall through */ case DEVICE_OPEN_CONTEXT_STEP_FLAGS_NETPORT: /* Network port setup */ if (ctx->flags & NETPORT_FLAGS) { QmiMessageCtlSetDataFormatInput *input; QmiCtlDataFormat qos = QMI_CTL_DATA_FORMAT_QOS_FLOW_HEADER_ABSENT; QmiCtlDataLinkProtocol link_protocol = QMI_CTL_DATA_LINK_PROTOCOL_802_3; g_debug ("[%s] setting network port data format...", qmi_file_get_path_display (self->priv->file)); input = qmi_message_ctl_set_data_format_input_new (); if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_NET_QOS_HEADER) qos = QMI_CTL_DATA_FORMAT_QOS_FLOW_HEADER_PRESENT; qmi_message_ctl_set_data_format_input_set_format (input, qos, NULL); if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_NET_RAW_IP) link_protocol = QMI_CTL_DATA_LINK_PROTOCOL_RAW_IP; qmi_message_ctl_set_data_format_input_set_protocol (input, link_protocol, NULL); qmi_client_ctl_set_data_format (self->priv->client_ctl, input, 5, NULL, (GAsyncReadyCallback)ctl_set_data_format_ready, task); qmi_message_ctl_set_data_format_input_unref (input); return; } ctx->step++; /* Fall through */ case DEVICE_OPEN_CONTEXT_STEP_FLAGS_EXPECT_INDICATIONS: qmi_endpoint_setup_indications (self->priv->endpoint, 10, g_task_get_cancellable (task), (GAsyncReadyCallback)setup_indications_ready, task); return; /* Fall through */ case DEVICE_OPEN_CONTEXT_STEP_LAST: /* Nothing else to process, done we are */ g_task_return_boolean (task, TRUE); g_object_unref (task); return; default: break; } g_assert_not_reached (); } void qmi_device_open (QmiDevice *self, QmiDeviceOpenFlags flags, guint timeout, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { DeviceOpenContext *ctx; gchar *flags_str; GTask *task; /* Raw IP and 802.3 are mutually exclusive */ g_return_if_fail (!((flags & QMI_DEVICE_OPEN_FLAGS_NET_802_3) && (flags & QMI_DEVICE_OPEN_FLAGS_NET_RAW_IP))); /* QoS and no QoS are mutually exclusive */ g_return_if_fail (!((flags & QMI_DEVICE_OPEN_FLAGS_NET_QOS_HEADER) && (flags & QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER))); /* At least one of both link protocol and QoS must be specified */ if (flags & (QMI_DEVICE_OPEN_FLAGS_NET_802_3 | QMI_DEVICE_OPEN_FLAGS_NET_RAW_IP)) g_return_if_fail (flags & (QMI_DEVICE_OPEN_FLAGS_NET_QOS_HEADER | QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER)); if (flags & (QMI_DEVICE_OPEN_FLAGS_NET_QOS_HEADER | QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER)) g_return_if_fail (flags & (QMI_DEVICE_OPEN_FLAGS_NET_802_3 | QMI_DEVICE_OPEN_FLAGS_NET_RAW_IP)); g_return_if_fail (QMI_IS_DEVICE (self)); flags_str = qmi_device_open_flags_build_string_from_mask (flags); g_debug ("[%s] opening device with flags '%s'...", qmi_file_get_path_display (self->priv->file), flags_str); g_free (flags_str); ctx = g_slice_new (DeviceOpenContext); ctx->step = DEVICE_OPEN_CONTEXT_STEP_FIRST; ctx->flags = flags; ctx->timeout = timeout; task = g_task_new (self, cancellable, callback, user_data); g_task_set_task_data (task, ctx, (GDestroyNotify)device_open_context_free); /* Start processing */ device_open_step (task); } /*****************************************************************************/ /* Close stream */ typedef struct { QmiEndpoint *endpoint; guint endpoint_new_data_id; guint endpoint_hangup_id; } CloseContext; static void close_context_free (CloseContext *ctx) { if (ctx->endpoint_hangup_id) g_signal_handler_disconnect (ctx->endpoint, ctx->endpoint_hangup_id); if (ctx->endpoint_new_data_id) g_signal_handler_disconnect (ctx->endpoint, ctx->endpoint_new_data_id); g_object_unref (ctx->endpoint); g_slice_free (CloseContext, ctx); } gboolean qmi_device_close_finish (QmiDevice *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void endpoint_close_ready (QmiEndpoint *endpoint, GAsyncResult *res, GTask *task) { GError *error = NULL; if (!qmi_endpoint_close_finish (endpoint, res, &error)) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); g_object_unref (task); } void qmi_device_close_async (QmiDevice *self, guint timeout, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; CloseContext *ctx; task = g_task_new (self, cancellable, callback, user_data); /* if already closed, we're done */ if (!self->priv->endpoint) { g_task_return_boolean (task, TRUE); g_object_unref (task); return; } /* Steal endpoint setup from private info, it will be freed once * the task is completed and disposed */ ctx = g_slice_new0 (CloseContext); ctx->endpoint = g_steal_pointer (&self->priv->endpoint); ctx->endpoint_new_data_id = self->priv->endpoint_new_data_id; self->priv->endpoint_new_data_id = 0; ctx->endpoint_hangup_id = self->priv->endpoint_hangup_id; self->priv->endpoint_hangup_id = 0; g_task_set_task_data (task, ctx, (GDestroyNotify) close_context_free); qmi_endpoint_close (ctx->endpoint, timeout, cancellable, (GAsyncReadyCallback)endpoint_close_ready, task); } /*****************************************************************************/ /* Command */ QmiMessage * qmi_device_command_abortable_finish (QmiDevice *self, GAsyncResult *res, GError **error) { if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) return NULL; return qmi_message_ref (g_simple_async_result_get_op_res_gpointer ( G_SIMPLE_ASYNC_RESULT (res))); } static void transaction_early_error (QmiDevice *self, Transaction *tr, gboolean stored, GError *error) { g_assert (error); if (stored) { /* Match transaction so that we remove it from our tracking table */ tr = device_match_transaction (self, tr->message); g_assert (tr); } transaction_complete_and_free (tr, NULL, error); g_error_free (error); } void qmi_device_command_abortable (QmiDevice *self, QmiMessage *message, QmiMessageContext *message_context, guint timeout, QmiDeviceCommandAbortableBuildRequestFn abort_build_request_fn, QmiDeviceCommandAbortableParseResponseFn abort_parse_response_fn, gpointer abort_user_data, GDestroyNotify abort_user_data_free, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GError *error = NULL; Transaction *tr; g_return_if_fail (QMI_IS_DEVICE (self)); g_return_if_fail (message != NULL); g_return_if_fail (timeout > 0); /* either none or both set */ g_return_if_fail ((!abort_build_request_fn && !abort_parse_response_fn) || (abort_build_request_fn && abort_parse_response_fn)); /* Use a proper transaction id for CTL messages if they don't have one */ if (qmi_message_get_service (message) == QMI_SERVICE_CTL && qmi_message_get_transaction_id (message) == 0) { qmi_message_set_transaction_id ( message, qmi_client_get_next_transaction_id ( QMI_CLIENT ( self->priv->client_ctl))); } tr = transaction_new (self, message, message_context, cancellable, callback, user_data); /* Device must be open */ if (!qmi_device_is_open (self)) { error = g_error_new (QMI_CORE_ERROR, QMI_CORE_ERROR_WRONG_STATE, "Device must be open to send commands"); transaction_early_error (self, tr, FALSE, error); return; } /* Non-CTL services should use a proper CID */ if (qmi_message_get_service (message) != QMI_SERVICE_CTL && qmi_message_get_client_id (message) == 0) { error = g_error_new (QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "Cannot send message in service '%s' without a CID", qmi_service_get_string (qmi_message_get_service (message))); transaction_early_error (self, tr, FALSE, error); return; } /* If message is not abortable, we should not allow using the abortable() interface */ if (!__qmi_message_is_abortable (message, message_context)) { if (abort_build_request_fn || abort_parse_response_fn) { error = g_error_new (QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, "Message is not abortable"); transaction_early_error (self, tr, FALSE, error); return; } } else { /* Store abortable info, if any */ tr->abort_build_request_fn = abort_build_request_fn; tr->abort_parse_response_fn = abort_parse_response_fn; tr->abort_user_data = abort_user_data; tr->abort_user_data_free = abort_user_data_free; } /* Setup context to match response */ if (!device_store_transaction (self, tr, timeout, &error)) { g_prefix_error (&error, "Cannot store transaction: "); transaction_early_error (self, tr, FALSE, error); return; } /* From now on, if we want to complete the transaction with an early error, * it needs to be removed from the tracking table as well. */ trace_message (self, message, TRUE, "request", message_context); if (!qmi_endpoint_send (self->priv->endpoint, message, timeout, cancellable, &error)) { transaction_early_error (self, tr, TRUE, error); return; } } /*****************************************************************************/ /* Non-abortable standard command */ QmiMessage * qmi_device_command_full_finish (QmiDevice *self, GAsyncResult *res, GError **error) { return qmi_device_command_abortable_finish (self, res, error); } void qmi_device_command_full (QmiDevice *self, QmiMessage *message, QmiMessageContext *message_context, guint timeout, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { qmi_device_command_abortable (self, message, message_context, timeout, NULL, /* abort_build_request_fn */ NULL, /* abort_parse_response_fn */ NULL, /* abort_user_data */ NULL, /* abort_user_data_free */ cancellable, callback, user_data); } /*****************************************************************************/ /* New QMI device */ static QmiDevice * common_device_new_finish (GAsyncResult *res, GError **error) { g_autoptr(GObject) source_object = NULL; source_object = g_async_result_get_source_object (res); return QMI_DEVICE (g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error)); } #if QMI_QRTR_SUPPORTED QmiDevice * qmi_device_new_from_node_finish (GAsyncResult *res, GError **error) { return common_device_new_finish (res, error); } void qmi_device_new_from_node (QrtrNode *node, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail (QRTR_IS_NODE (node)); g_async_initable_new_async (QMI_TYPE_DEVICE, G_PRIORITY_DEFAULT, cancellable, callback, user_data, QMI_DEVICE_NODE, node, NULL); } QrtrNode * qmi_device_get_node (QmiDevice *self) { g_return_val_if_fail (QMI_IS_DEVICE (self), NULL); return self->priv->node ? g_object_ref (self->priv->node) : NULL; } QrtrNode * qmi_device_peek_node (QmiDevice *self) { g_return_val_if_fail (QMI_IS_DEVICE (self), NULL); return self->priv->node; } #endif QmiDevice * qmi_device_new_finish (GAsyncResult *res, GError **error) { return common_device_new_finish (res, error); } void qmi_device_new (GFile *file, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail (G_IS_FILE (file)); g_async_initable_new_async (QMI_TYPE_DEVICE, G_PRIORITY_DEFAULT, cancellable, callback, user_data, QMI_DEVICE_FILE, file, NULL); } /*****************************************************************************/ /* Async init */ static gboolean initable_init_finish (GAsyncInitable *initable, GAsyncResult *result, GError **error) { return g_task_propagate_boolean (G_TASK (result), error); } static void sync_indication_cb (QmiClientCtl *client_ctl, QmiDevice *self) { /* Just log about it */ g_debug ("[%s] sync indication received", qmi_file_get_path_display (self->priv->file)); } static void client_ctl_setup (GTask *task) { QmiDevice *self; GError *error = NULL; self = g_task_get_source_object (task); /* Create the implicit CTL client */ self->priv->client_ctl = g_object_new (QMI_TYPE_CLIENT_CTL, QMI_CLIENT_DEVICE, self, QMI_CLIENT_SERVICE, QMI_SERVICE_CTL, QMI_CLIENT_CID, QMI_CID_NONE, NULL); /* Register the CTL client to get indications */ register_client (self, QMI_CLIENT (self->priv->client_ctl), &error); g_assert_no_error (error); /* Connect to 'Sync' indications */ self->priv->sync_indication_id = g_signal_connect (self->priv->client_ctl, "sync", G_CALLBACK (sync_indication_cb), self); /* Done we are */ g_task_return_boolean (task, TRUE); g_object_unref (task); } static void check_type_async_ready (QmiFile *file, GAsyncResult *res, GTask *task) { GError *error = NULL; if (!qmi_file_check_type_finish (file, res, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } /* Go on with client CTL setup */ client_ctl_setup (task); } static void initable_init_async (GAsyncInitable *initable, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { QmiDevice *self; GTask *task; self = QMI_DEVICE (initable); task = g_task_new (self, cancellable, callback, user_data); /* We need a proper file to initialize */ g_assert (QMI_IS_FILE (self->priv->file)); #if QMI_QRTR_SUPPORTED /* If we have a node, just skip to setting up the control client */ if (self->priv->node) { client_ctl_setup (task); return; } #endif /* If no file check requested, don't do it */ if (self->priv->no_file_check) { client_ctl_setup (task); return; } /* Check the file type. Note that this is just a quick check to avoid * creating QmiDevices pointing to a location already known not to be a QMI * device. */ qmi_file_check_type_async (self->priv->file, cancellable, (GAsyncReadyCallback)check_type_async_ready, task); } /*****************************************************************************/ #if QMI_QRTR_SUPPORTED static QmiFile * get_file_for_node (QrtrNode *node) { g_autofree gchar *uri = NULL; uri = qrtr_get_uri_for_node (qrtr_node_get_id (node)); return qmi_file_new (g_file_new_for_uri (uri)); } #endif static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { QmiDevice *self = QMI_DEVICE (object); switch (prop_id) { case PROP_FILE: { GFile *file; file = g_value_get_object (value); g_assert (!self->priv->file); self->priv->file = file ? qmi_file_new (file) : NULL; break; } case PROP_NO_FILE_CHECK: self->priv->no_file_check = g_value_get_boolean (value); break; case PROP_PROXY_PATH: g_free (self->priv->proxy_path); self->priv->proxy_path = g_value_dup_string (value); break; case PROP_CONSECUTIVE_TIMEOUTS: g_assert_not_reached (); break; #if QMI_QRTR_SUPPORTED case PROP_NODE: g_assert (!self->priv->node); self->priv->node = g_value_dup_object (value); if (self->priv->node) { g_assert (!self->priv->file); self->priv->file = get_file_for_node (self->priv->node); } break; #endif 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) { QmiDevice *self = QMI_DEVICE (object); switch (prop_id) { case PROP_FILE: g_assert (QMI_IS_FILE (self->priv->file)); g_value_set_object (value, qmi_file_get_file (self->priv->file)); break; case PROP_WWAN_IFACE: reload_wwan_iface_name (self); g_value_set_string (value, self->priv->wwan_iface); break; case PROP_CONSECUTIVE_TIMEOUTS: g_value_set_uint (value, self->priv->consecutive_timeouts); break; #if QMI_QRTR_SUPPORTED case PROP_NODE: g_value_set_object (value, self->priv->node); break; #endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void qmi_device_init (QmiDevice *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), QMI_TYPE_DEVICE, QmiDevicePrivate); self->priv->transactions = g_hash_table_new (g_direct_hash, g_direct_equal); self->priv->registered_clients = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); self->priv->proxy_path = g_strdup (QMI_PROXY_SOCKET_PATH); } static gboolean foreach_warning (gpointer key, QmiClient *client, QmiDevice *self) { g_warning ("[%s] client for service '%s' with CID '%u' wasn't released", qmi_file_get_path_display (self->priv->file), qmi_service_get_string (qmi_client_get_service (client)), qmi_client_get_cid (client)); return TRUE; } static void dispose (GObject *object) { QmiDevice *self = QMI_DEVICE (object); /* unregister our CTL client */ if (self->priv->client_ctl) unregister_client (self, QMI_CLIENT (self->priv->client_ctl)); /* If clients were left unreleased, we'll just warn about it. * There is no point in trying to request CID releases, as the device * itself is being disposed. */ g_hash_table_foreach_remove (self->priv->registered_clients, (GHRFunc)foreach_warning, self); if (self->priv->sync_indication_id && self->priv->client_ctl) { g_signal_handler_disconnect (self->priv->client_ctl, self->priv->sync_indication_id); self->priv->sync_indication_id = 0; } g_clear_object (&self->priv->client_ctl); if (self->priv->endpoint) { if (self->priv->endpoint_hangup_id) { g_signal_handler_disconnect (self->priv->endpoint, self->priv->endpoint_hangup_id); self->priv->endpoint_hangup_id = 0; } if (self->priv->endpoint_new_data_id) { g_signal_handler_disconnect (self->priv->endpoint, self->priv->endpoint_new_data_id); self->priv->endpoint_new_data_id = 0; } g_clear_object (&self->priv->endpoint); } g_clear_object (&self->priv->net_port_manager); g_clear_object (&self->priv->file); #if QMI_QRTR_SUPPORTED g_clear_object (&self->priv->node); #endif G_OBJECT_CLASS (qmi_device_parent_class)->dispose (object); } static void finalize (GObject *object) { QmiDevice *self = QMI_DEVICE (object); /* Transactions keep refs to the device, so it's actually * impossible to have any content in the HT */ if (self->priv->transactions) { g_assert (g_hash_table_size (self->priv->transactions) == 0); g_hash_table_unref (self->priv->transactions); } g_hash_table_unref (self->priv->registered_clients); if (self->priv->supported_services) g_array_unref (self->priv->supported_services); g_free (self->priv->proxy_path); g_free (self->priv->wwan_iface); G_OBJECT_CLASS (qmi_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 qmi_device_class_init (QmiDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); g_type_class_add_private (object_class, sizeof (QmiDevicePrivate)); object_class->get_property = get_property; object_class->set_property = set_property; object_class->finalize = finalize; object_class->dispose = dispose; /** * QmiDevice:device-file: * * Since: 1.0 */ properties[PROP_FILE] = g_param_spec_object (QMI_DEVICE_FILE, "Device file", "File to the underlying QMI device", G_TYPE_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_FILE, properties[PROP_FILE]); /** * QmiDevice:device-no-file-check: * * Since: 1.12 */ properties[PROP_NO_FILE_CHECK] = g_param_spec_boolean (QMI_DEVICE_NO_FILE_CHECK, "No file check", "Don't check for file existence when creating the Qmi device.", FALSE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_NO_FILE_CHECK, properties[PROP_NO_FILE_CHECK]); /** * QmiDevice:device-proxy-path: * * Since: 1.12 */ properties[PROP_PROXY_PATH] = g_param_spec_string (QMI_DEVICE_PROXY_PATH, "Proxy path", "Path of the abstract socket where the proxy is available.", QMI_PROXY_SOCKET_PATH, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_PROXY_PATH, properties[PROP_PROXY_PATH]); /** * QmiDevice:device-wwan-iface: * * Since: 1.14 */ properties[PROP_WWAN_IFACE] = g_param_spec_string (QMI_DEVICE_WWAN_IFACE, "WWAN iface", "Name of the WWAN network interface associated with the control port.", NULL, G_PARAM_READABLE); g_object_class_install_property (object_class, PROP_WWAN_IFACE, properties[PROP_WWAN_IFACE]); /** * QmiDevice:device-consecutive-timeouts: * * Since: 1.32 */ properties[PROP_CONSECUTIVE_TIMEOUTS] = g_param_spec_uint (QMI_DEVICE_CONSECUTIVE_TIMEOUTS, "Consecutive timeouts", "Number of consecutive timeouts detected in requests sent to the device", 0, G_MAXUINT, 0, G_PARAM_READABLE); g_object_class_install_property (object_class, PROP_CONSECUTIVE_TIMEOUTS, properties[PROP_CONSECUTIVE_TIMEOUTS]); /** * QmiDevice:device-node: * * Since: 1.24 */ #if QMI_QRTR_SUPPORTED properties[PROP_NODE] = g_param_spec_object (QMI_DEVICE_NODE, "QRTR node", "Remote node on the QRTR bus", QRTR_TYPE_NODE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_NODE, properties[PROP_NODE]); #endif /** * QmiDevice::indication: * @object: A #QmiDevice. * @output: A #QmiMessage. * * The ::indication signal gets emitted when a QMI indication is received. * * Since: 1.8 */ signals[SIGNAL_INDICATION] = g_signal_new (QMI_DEVICE_SIGNAL_INDICATION, G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_BYTE_ARRAY); /** * QmiDevice::device-removed: * @object: A #QmiDevice. * @output: none * * The ::device-removed signal is emitted when an unexpected port hang-up is received. * * Since: 1.20 */ signals[SIGNAL_REMOVED] = g_signal_new (QMI_DEVICE_SIGNAL_REMOVED, G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); }