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