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