summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2022-06-29 22:01:27 +0200
committerAleksander Morgado <aleksander@aleksander.es>2022-06-30 10:25:41 +0200
commitcc590d8dadfd67aae10e56cc4886df21a423d72a (patch)
tree16995735a9116ab6e38fa3bf203016e988de62e9
parent4f70d00c79220bf407b486abe6419cc073bb8586 (diff)
downloadlibqmi-cc590d8dadfd67aae10e56cc4886df21a423d72a.tar.gz
libqmi-glib,device: keep count of the timed out responses
So that we can easily identify a QMI device that is no longer usable if it's not replying in the control port.
-rw-r--r--docs/reference/libqmi-glib/libqmi-glib-common.sections2
-rw-r--r--src/libqmi-glib/#qmi-device.c#3440
l---------src/libqmi-glib/.#qmi-device.c1
-rw-r--r--src/libqmi-glib/qmi-device.c49
-rw-r--r--src/libqmi-glib/qmi-device.h21
5 files changed, 3513 insertions, 0 deletions
diff --git a/docs/reference/libqmi-glib/libqmi-glib-common.sections b/docs/reference/libqmi-glib/libqmi-glib-common.sections
index dcb973ee..a1bb41ca 100644
--- a/docs/reference/libqmi-glib/libqmi-glib-common.sections
+++ b/docs/reference/libqmi-glib/libqmi-glib-common.sections
@@ -73,6 +73,7 @@ QMI_DEVICE_FILE
QMI_DEVICE_NO_FILE_CHECK
QMI_DEVICE_PROXY_PATH
QMI_DEVICE_WWAN_IFACE
+QMI_DEVICE_CONSECUTIVE_TIMEOUTS
QMI_DEVICE_SIGNAL_INDICATION
QMI_DEVICE_SIGNAL_REMOVED
QmiDevice
@@ -83,6 +84,7 @@ qmi_device_peek_file
qmi_device_get_path
qmi_device_get_path_display
qmi_device_is_open
+qmi_device_get_consecutive_timeouts
QmiDeviceOpenFlags
qmi_device_open_flags_build_string_from_mask
qmi_device_open
diff --git a/src/libqmi-glib/#qmi-device.c# b/src/libqmi-glib/#qmi-device.c#
new file mode 100644
index 00000000..3c149154
--- /dev/null
+++ b/src/libqmi-glib/#qmi-device.c#
@@ -0,0 +1,3440 @@
+/* -*- 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 <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#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-proxy.h"
+#include "qmi-net-port-manager-qmiwwan.h"
+#include "qmi-version.h"
+
+#if QMI_QRTR_SUPPORTED
+# include "qmi-endpoint-qrtr.h"
+# include <libqrtr-glib.h>
+#endif
+
+#if defined RMNET_SUPPORT_ENABLED
+# include "qmi-net-port-manager-rmnet.h"
+#endif
+
+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,
+#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;
+};
+
+#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_debug ("transaction 0x%x completed with a response: cancelling the abort operation",
+ qmi_message_get_transaction_id (tr->message));
+ 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 ("not processing abort response, operation has already been completed");
+ 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 ("abort operation failed: %s", 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 ("transaction 0x%x aborted, but message is not abortable", 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 ("transaction 0x%x aborted, but no way to build abort request", 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 ("transaction 0x%x aborted, building abort request...", 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 ("transaction 0x%x aborted, but building abort request failed", 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;
+
+ 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);
+ }
+}
+
+/*****************************************************************************/
+/* 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] QMI 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] QMI 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_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";
+ }
+
+ printable = qmi_helpers_str_hex (((GByteArray *)message)->data,
+ ((GByteArray *)message)->len,
+ ':');
+ 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;
+ }
+
+ /* 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] QMI 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] QMI 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;
+#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;
+#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] QMI 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-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);
+}
diff --git a/src/libqmi-glib/.#qmi-device.c b/src/libqmi-glib/.#qmi-device.c
new file mode 120000
index 00000000..f601aaa6
--- /dev/null
+++ b/src/libqmi-glib/.#qmi-device.c
@@ -0,0 +1 @@
+aleksander@ares.4689:1656059922 \ No newline at end of file
diff --git a/src/libqmi-glib/qmi-device.c b/src/libqmi-glib/qmi-device.c
index 3c149154..b23f172b 100644
--- a/src/libqmi-glib/qmi-device.c
+++ b/src/libqmi-glib/qmi-device.c
@@ -84,6 +84,7 @@ enum {
PROP_NO_FILE_CHECK,
PROP_PROXY_PATH,
PROP_WWAN_IFACE,
+ PROP_CONSECUTIVE_TIMEOUTS,
#if QMI_QRTR_SUPPORTED
PROP_NODE,
#endif
@@ -134,6 +135,9 @@ struct _QmiDevicePrivate {
/* HT of clients that want to get indications */
GHashTable *registered_clients;
+
+ /* Number of consecutive timeouts detected */
+ guint consecutive_timeouts;
};
#if QMI_QRTR_SUPPORTED
@@ -411,6 +415,13 @@ transaction_timed_out (TransactionWaitContext *ctx)
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);
@@ -530,6 +541,16 @@ device_hangup_transactions (QmiDevice *self)
}
/*****************************************************************************/
+
+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 *
@@ -1762,6 +1783,14 @@ process_message (QmiMessage *message,
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 */
@@ -3168,6 +3197,9 @@ set_property (GObject *object,
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);
@@ -3201,6 +3233,9 @@ get_property (GObject *object,
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);
@@ -3382,6 +3417,20 @@ qmi_device_class_init (QmiDeviceClass *klass)
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:
*
diff --git a/src/libqmi-glib/qmi-device.h b/src/libqmi-glib/qmi-device.h
index 9a537e3e..408f60a6 100644
--- a/src/libqmi-glib/qmi-device.h
+++ b/src/libqmi-glib/qmi-device.h
@@ -101,6 +101,15 @@ typedef struct _QmiDevicePrivate QmiDevicePrivate;
#define QMI_DEVICE_WWAN_IFACE "device-wwan-iface"
/**
+ * QMI_DEVICE_CONSECUTIVE_TIMEOUTS:
+ *
+ * Symbol defining the #QmiDevice:device-consecutive-timeouts property.
+ *
+ * Since: 1.32
+ */
+#define QMI_DEVICE_CONSECUTIVE_TIMEOUTS "device-consecutive-timeouts"
+
+/**
* QMI_DEVICE_SIGNAL_INDICATION:
*
* Symbol defining the #QmiDevice::indication signal.
@@ -721,6 +730,18 @@ GArray *qmi_device_get_service_version_info_finish (QmiDevice *self,
GAsyncResult *res,
GError **error);
+/**
+ * qmi_device_get_consecutive_timeouts:
+ * @self: a #QmiDevice.
+ *
+ * Gets the number of consecutive transaction timeouts in the device.
+ *
+ * Returns: a #guint.
+ *
+ * Since: 1.32
+ */
+guint qmi_device_get_consecutive_timeouts (QmiDevice *self);
+
/******************************************************************************/
/* qmi_wwan specific APIs */