blob: 5db10879b50b5dec171cd99079e3226026d171cf [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-2017 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <config.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <gio/gio.h>
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#include <gio/gunixsocketaddress.h>
#if defined MBIM_QMUX_ENABLED
#include <libmbim-glib.h>
#endif
#include "qmi-device.h"
#include "qmi-message.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-oma.h"
#include "qmi-wda.h"
#include "qmi-voice.h"
#include "qmi-loc.h"
#include "qmi-qos.h"
#include "qmi-utils.h"
#include "qmi-error-types.h"
#include "qmi-enum-types.h"
#include "qmi-proxy.h"
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))
#define MAX_SPAWN_RETRIES 10
enum {
PROP_0,
PROP_FILE,
PROP_NO_FILE_CHECK,
PROP_PROXY_PATH,
PROP_WWAN_IFACE,
PROP_LAST
};
enum {
SIGNAL_INDICATION,
SIGNAL_REMOVED,
SIGNAL_LAST
};
static GParamSpec *properties[PROP_LAST];
static guint signals [SIGNAL_LAST] = { 0 };
struct _QmiDevicePrivate {
/* File */
GFile *file;
gchar *path;
gchar *path_display;
gboolean no_file_check;
gchar *proxy_path;
#if defined MBIM_QMUX_ENABLED
MbimDevice *mbimdev;
guint mbim_notification_id;
#endif
/* WWAN interface */
gboolean no_wwan_check;
gchar *wwan_iface;
/* Implicit CTL client */
QmiClientCtl *client_ctl;
guint sync_indication_id;
/* Supported services */
GArray *supported_services;
/* I/O stream, set when the file is open */
gint fd;
GInputStream *istream;
GOutputStream *ostream;
GSource *input_source;
GByteArray *buffer;
/* Support for qmi-proxy */
GSocketClient *socket_client;
GSocketConnection *socket_connection;
/* HT to keep track of ongoing transactions */
GHashTable *transactions;
/* HT of clients that want to get indications */
GHashTable *registered_clients;
};
#define BUFFER_SIZE 2048
#if defined MBIM_QMUX_ENABLED
/**
* Number of extra seconds to give the MBIM timeout delay.
* Needed so the QMI timeout triggers first and we can be sure
* that timeouts on the QMI side are not because of libmbim timeouts.
*/
#define MBIM_TIMEOUT_DELAY_SECS 1
#endif
static void destroy_iostream (QmiDevice *self);
/*****************************************************************************/
/* 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;
} 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);
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 (reply)
g_simple_async_result_set_op_res_gpointer (tr->result,
qmi_message_ref (reply),
(GDestroyNotify)qmi_message_unref);
else
g_simple_async_result_set_from_error (tr->result, error);
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_release_transaction (QmiDevice *self,
gconstpointer key)
{
Transaction *tr = NULL;
if (self->priv->transactions) {
tr = g_hash_table_lookup (self->priv->transactions, key);
if (tr)
/* If found, remove it from the HT */
g_hash_table_remove (self->priv->transactions, key);
}
return tr;
}
static gboolean
transaction_timed_out (TransactionWaitContext *ctx)
{
Transaction *tr;
GError *error = NULL;
tr = device_release_transaction (ctx->self, ctx->key);
tr->timeout_source = NULL;
/* Complete transaction with a timeout error */
error = g_error_new (QMI_CORE_ERROR,
QMI_CORE_ERROR_TIMEOUT,
"Transaction timed out");
transaction_complete_and_free (tr, NULL, error);
g_error_free (error);
return FALSE;
}
static void
transaction_cancelled (GCancellable *cancellable,
TransactionWaitContext *ctx)
{
Transaction *tr;
GError *error = NULL;
tr = device_release_transaction (ctx->self, ctx->key);
/* The transaction may have already been cancelled before we stored it in
* the tracking table */
if (!tr)
return;
tr->cancellable_id = 0;
/* Complete transaction with an abort error */
error = g_error_new (QMI_PROTOCOL_ERROR,
QMI_PROTOCOL_ERROR_ABORTED,
"Transaction aborted");
transaction_complete_and_free (tr, NULL, error);
g_error_free (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));
}
/*****************************************************************************/
/* 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...",
self->priv->path_display,
qmi_service_get_string (service));
return TRUE;
}
return !!find_service_version_info (self, service);
}
static gboolean
check_message_supported (QmiDevice *self,
QmiMessage *message,
GError **error)
{
const QmiMessageCtlGetVersionInfoOutputServiceListService *info;
guint message_major = 0;
guint message_minor = 0;
guint device_major = 0;
guint device_minor = 0;
/* If we didn't check supported services, just assume it is supported */
if (!self->priv->supported_services)
return TRUE;
/* For CTL, we assume all are supported */
if (qmi_message_get_service (message) == QMI_SERVICE_CTL)
return TRUE;
/* If we cannot get in which version this message was introduced, we'll just
* assume it's supported */
if (!qmi_message_get_version_introduced_full (message, NULL, &message_major, &message_minor))
return TRUE;
/* Get version info. It MUST exist because we allowed creating a client
* of this service type */
info = find_service_version_info (self, qmi_message_get_service (message));
g_assert (info != NULL);
g_assert (info->service == qmi_message_get_service (message));
device_major = info->major_version;
device_minor = info->minor_version;
/* Some device firmware versions (Quectel EC21) lie about their supported
* DMS version, so assume a reasonable DMS version if the WDS version is
* high enough */
if (info->service == QMI_SERVICE_DMS && device_major == 1 && device_minor == 0) {
const QmiMessageCtlGetVersionInfoOutputServiceListService *wds;
wds = find_service_version_info (self, QMI_SERVICE_WDS);
g_assert (wds != NULL);
if (wds->major_version >= 1 && wds->minor_version >= 9) {
device_major = 1;
device_minor = 3;
}
}
/* If the version of the message is greater than the version of the service,
* report unsupported */
if (message_major > device_major ||
(message_major == device_major &&
message_minor > device_minor)) {
g_set_error (error,
QMI_CORE_ERROR,
QMI_CORE_ERROR_UNSUPPORTED,
"QMI service '%s' version '%u.%u' required, got version '%u.%u'",
qmi_service_get_string (qmi_message_get_service (message)),
message_major, message_minor,
info->major_version,
info->minor_version);
return FALSE;
}
/* Supported! */
return TRUE;
}
/*****************************************************************************/
GFile *
qmi_device_get_file (QmiDevice *self)
{
GFile *file = NULL;
g_return_val_if_fail (QMI_IS_DEVICE (self), NULL);
g_object_get (G_OBJECT (self),
QMI_DEVICE_FILE, &file,
NULL);
return file;
}
GFile *
qmi_device_peek_file (QmiDevice *self)
{
g_return_val_if_fail (QMI_IS_DEVICE (self), NULL);
return self->priv->file;
}
const gchar *
qmi_device_get_path (QmiDevice *self)
{
g_return_val_if_fail (QMI_IS_DEVICE (self), NULL);
return self->priv->path;
}
const gchar *
qmi_device_get_path_display (QmiDevice *self)
{
g_return_val_if_fail (QMI_IS_DEVICE (self), NULL);
return self->priv->path_display;
}
gboolean
qmi_device_is_open (QmiDevice *self)
{
g_return_val_if_fail (QMI_IS_DEVICE (self), FALSE);
return !!(self->priv->istream && self->priv->ostream);
}
/*****************************************************************************/
/* WWAN iface name
* Always reload from scratch, to handle possible net interface renames */
static void
reload_wwan_iface_name (QmiDevice *self)
{
gchar *cdc_wdm_device_name;
static const gchar *driver_names[] = { "usbmisc", "usb" };
GError *error = NULL;
guint i;
/* Early cleanup */
g_free (self->priv->wwan_iface);
self->priv->wwan_iface = NULL;
cdc_wdm_device_name = __qmi_utils_get_devname (self->priv->path, &error);
if (!cdc_wdm_device_name) {
g_warning ("[%s] invalid path for cdc-wdm control port: %s",
self->priv->path_display,
error->message);
g_error_free (error);
return;
}
for (i = 0; i < G_N_ELEMENTS (driver_names) && !self->priv->wwan_iface; i++) {
gchar *sysfs_path;
GFile *sysfs_file;
GFileEnumerator *enumerator;
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,
&error);
if (!enumerator) {
g_debug ("[%s] cannot enumerate files at path '%s': %s",
self->priv->path_display,
sysfs_path,
error->message);
g_error_free (error);
} else {
GFileInfo *file_info;
/* 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",
self->priv->path_display, name);
else
self->priv->wwan_iface = g_strdup (name);
}
g_object_unref (file_info);
}
g_object_unref (enumerator);
}
g_free (sysfs_path);
g_object_unref (sysfs_file);
}
g_free (cdc_wdm_device_name);
if (!self->priv->wwan_iface)
g_warning ("[%s] wwan iface not found", self->priv->path_display);
}
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
get_expected_data_format (QmiDevice *self,
const gchar *sysfs_path,
GError **error)
{
QmiDeviceExpectedDataFormat expected = QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN;
gchar value = '\0';
FILE *f;
g_debug ("[%s] Reading expected data format from: %s",
self->priv->path_display,
sysfs_path);
if (!(f = fopen (sysfs_path, "r"))) {
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
"Failed to open file '%s': %s",
sysfs_path, g_strerror (errno));
goto out;
}
if (fread (&value, 1, 1, f) != 1) {
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
"Failed to read from file '%s': %s",
sysfs_path, g_strerror (errno));
goto out;
}
if (value == 'Y')
expected = QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP;
else if (value == 'N')
expected = QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3;
else
g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED,
"Unexpected sysfs file contents");
out:
g_prefix_error (error, "Expected data format not retrieved properly: ");
if (f)
fclose (f);
return expected;
}
static gboolean
set_expected_data_format (QmiDevice *self,
const gchar *sysfs_path,
QmiDeviceExpectedDataFormat requested,
GError **error)
{
gboolean status = FALSE;
gchar value;
FILE *f;
g_debug ("[%s] Writing expected data format to: %s",
self->priv->path_display,
sysfs_path);
if (requested == QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP)
value = 'Y';
else if (requested == QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3)
value = 'N';
else
g_assert_not_reached ();
if (!(f = fopen (sysfs_path, "w"))) {
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
"Failed to open file '%s' for R/W: %s",
sysfs_path, g_strerror (errno));
goto out;
}
if (fwrite (&value, 1, 1, f) != 1) {
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
"Failed to write to file '%s': %s",
sysfs_path, g_strerror (errno));
goto out;
}
status = TRUE;
out:
g_prefix_error (error, "Expected data format not updated properly: ");
if (f)
fclose (f);
return status;
}
static QmiDeviceExpectedDataFormat
common_get_set_expected_data_format (QmiDevice *self,
QmiDeviceExpectedDataFormat requested,
GError **error)
{
gchar *sysfs_path = NULL;
QmiDeviceExpectedDataFormat expected = QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN;
gboolean readonly;
readonly = (requested == QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN);
/* Make sure we load the WWAN iface name */
reload_wwan_iface_name (self);
if (!self->priv->wwan_iface) {
g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED,
"Unknown wwan iface");
goto out;
}
/* Build sysfs file path and open it */
sysfs_path = g_strdup_printf ("/sys/class/net/%s/qmi/raw_ip", self->priv->wwan_iface);
/* Set operation? */
if (!readonly && !set_expected_data_format (self, sysfs_path, requested, error))
goto out;
/* Get/Set operations */
if ((expected = get_expected_data_format (self, sysfs_path, error)) == QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN)
goto out;
/* 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));
expected = QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN;
}
out:
g_free (sysfs_path);
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);
}
/*****************************************************************************/
/* 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);
/* 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'",
self->priv->path_display,
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;
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:
ctx->client_type = QMI_TYPE_CLIENT_DMS;
break;
case QMI_SERVICE_WDS:
ctx->client_type = QMI_TYPE_CLIENT_WDS;
break;
case QMI_SERVICE_NAS:
ctx->client_type = QMI_TYPE_CLIENT_NAS;
break;
case QMI_SERVICE_WMS:
ctx->client_type = QMI_TYPE_CLIENT_WMS;
break;
case QMI_SERVICE_PDS:
ctx->client_type = QMI_TYPE_CLIENT_PDS;
break;
case QMI_SERVICE_PDC:
ctx->client_type = QMI_TYPE_CLIENT_PDC;
break;
case QMI_SERVICE_PBM:
ctx->client_type = QMI_TYPE_CLIENT_PBM;
break;
case QMI_SERVICE_UIM:
ctx->client_type = QMI_TYPE_CLIENT_UIM;
break;
case QMI_SERVICE_OMA:
ctx->client_type = QMI_TYPE_CLIENT_OMA;
break;
case QMI_SERVICE_WDA:
ctx->client_type = QMI_TYPE_CLIENT_WDA;
break;
case QMI_SERVICE_VOICE:
ctx->client_type = QMI_TYPE_CLIENT_VOICE;
break;
case QMI_SERVICE_LOC:
ctx->client_type = QMI_TYPE_CLIENT_LOC;
break;
case QMI_SERVICE_QOS:
ctx->client_type = QMI_TYPE_CLIENT_QOS;
break;
default:
g_task_return_new_error (task,
QMI_CORE_ERROR,
QMI_CORE_ERROR_INVALID_ARGS,
"Clients for service '%s' not yet 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...",
self->priv->path_display);
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'...",
self->priv->path_display,
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'...",
self->priv->path_display,
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'",
self->priv->path_display,
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;
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 */
typedef struct {
QmiClient *client;
QmiMessage *message;
} IdleIndicationContext;
static gboolean
process_indication_idle (IdleIndicationContext *ctx)
{
g_assert (ctx->client != NULL);
g_assert (ctx->message != NULL);
__qmi_client_process_indication (ctx->client, ctx->message);
g_object_unref (ctx->client);
qmi_message_unref (ctx->message);
g_slice_free (IdleIndicationContext, ctx);
return FALSE;
}
static void
report_indication (QmiClient *client,
QmiMessage *message)
{
IdleIndicationContext *ctx;
GSource *source;
/* Setup an idle to Pass the indication down to the client */
ctx = g_slice_new (IdleIndicationContext);
ctx->client = g_object_ref (client);
ctx->message = qmi_message_ref (message);
source = g_idle_source_new ();
g_source_set_callback (source, (GSourceFunc)process_indication_idle, ctx, NULL);
g_source_attach (source, g_main_context_get_thread_default ());
g_source_unref (source);
}
static void
trace_message (QmiDevice *self,
QmiMessage *message,
gboolean sent_or_received,
const gchar *message_str,
QmiMessageContext *message_context)
{
gchar *printable;
const gchar *prefix_str;
const gchar *action_str;
gchar *vendor_str = NULL;
if (!qmi_utils_get_traces_enabled ())
return;
if (sent_or_received) {
prefix_str = "<<<<<< ";
action_str = "sent";
} else {
prefix_str = "<<<<<< ";
action_str = "received";
}
printable = __qmi_utils_str_hex (((GByteArray *)message)->data,
((GByteArray *)message)->len,
':');
g_debug ("[%s] %s message...\n"
"%sRAW:\n"
"%s length = %u\n"
"%s data = %s\n",
self->priv->path_display, 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",
self->priv->path_display,
action_str,
vendor_str ? vendor_str : "generic",
message_str,
printable);
g_free (printable);
g_free (vendor_str);
}
static void
process_message (QmiDevice *self,
QmiMessage *message)
{
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",
self->priv->path_display);
} else {
/* 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.",
self->priv->path_display);
}
static void
parse_response (QmiDevice *self)
{
do {
GError *error = NULL;
QmiMessage *message;
/* Every message received must start with the QMUX marker.
* If it doesn't, we broke framing :-/
* If we broke framing, an error should be reported and the device
* should get closed */
if (self->priv->buffer->len > 0 &&
self->priv->buffer->data[0] != QMI_MESSAGE_QMUX_MARKER) {
/* TODO: Report fatal error */
g_warning ("[%s] QMI framing error detected",
self->priv->path_display);
return;
}
message = qmi_message_new_from_raw (self->priv->buffer, &error);
if (!message) {
if (!error)
/* More data we need */
return;
/* Warn about the issue */
g_warning ("[%s] Invalid QMI message received: '%s'",
self->priv->path_display,
error->message);
g_error_free (error);
if (qmi_utils_get_traces_enabled ()) {
gchar *printable;
guint len = MIN (self->priv->buffer->len, 2048);
printable = __qmi_utils_str_hex (self->priv->buffer->data, len, ':');
g_debug ("<<<<<< RAW INVALID MESSAGE:\n"
"<<<<<< length = %u\n"
"<<<<<< data = %s\n",
self->priv->buffer->len, /* show full buffer len */
printable);
g_free (printable);
}
} else {
/* Play with the received message */
process_message (self, message);
qmi_message_unref (message);
}
} while (self->priv->buffer->len > 0);
}
static gboolean
input_ready_cb (GInputStream *istream,
QmiDevice *self)
{
guint8 buffer[BUFFER_SIZE];
GError *error = NULL;
gssize r;
r = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (istream),
buffer,
BUFFER_SIZE,
NULL,
&error);
if (r < 0) {
g_warning ("Error reading from istream: %s", error ? error->message : "unknown");
if (error)
g_error_free (error);
/* Close the device */
qmi_device_close_async (self, 0, NULL, NULL, NULL);
return G_SOURCE_REMOVE;
}
if (r == 0) {
/* HUP! */
g_warning ("Cannot read from istream: connection broken");
g_signal_emit (self, signals[SIGNAL_REMOVED], 0);
return G_SOURCE_REMOVE;
}
/* else, r > 0 */
if (!G_UNLIKELY (self->priv->buffer))
self->priv->buffer = g_byte_array_sized_new (r);
g_byte_array_append (self->priv->buffer, buffer, r);
parse_response (self);
return G_SOURCE_CONTINUE;
}
typedef struct {
guint spawn_retries;
} CreateIostreamContext;
static void
create_iostream_context_free (CreateIostreamContext *ctx)
{
g_slice_free (CreateIostreamContext, ctx);
}
static gboolean
create_iostream_finish (QmiDevice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
setup_iostream (GTask *task)
{
QmiDevice *self;
self = g_task_get_source_object (task);
/* Check in/out streams */
if (!self->priv->istream || !self->priv->ostream) {
destroy_iostream (self);
g_task_return_new_error (task,
QMI_CORE_ERROR,
QMI_CORE_ERROR_FAILED,
"Cannot get input/output streams");
g_object_unref (task);
return;
}
/* Setup input events */
self->priv->input_source = g_pollable_input_stream_create_source (
G_POLLABLE_INPUT_STREAM (self->priv->istream),
NULL);
g_source_set_callback (self->priv->input_source,
(GSourceFunc)input_ready_cb,
self,
NULL);
g_source_attach (self->priv->input_source, g_main_context_get_thread_default ());
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
create_iostream_with_fd (GTask *task)
{
QmiDevice *self;
gint fd;
self = g_task_get_source_object (task);
fd = open (self->priv->path, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY);
if (fd < 0) {
g_task_return_new_error (task,
QMI_CORE_ERROR,
QMI_CORE_ERROR_FAILED,
"Cannot open device file '%s': %s",
self->priv->path_display,
strerror (errno));
g_object_unref (task);
return;
}
g_assert (self->priv->fd < 0);
self->priv->fd = fd;
self->priv->istream = g_unix_input_stream_new (fd, FALSE);
self->priv->ostream = g_unix_output_stream_new (fd, FALSE);
setup_iostream (task);
}
static void create_iostream_with_socket (GTask *task);
static gboolean
wait_for_proxy_cb (GTask *task)
{
create_iostream_with_socket (task);
return FALSE;
}
static void
spawn_child_setup (void)
{
if (setpgid (0, 0) < 0)
g_warning ("couldn't setup proxy specific process group");
}
static void
create_iostream_with_socket (GTask *task)
{
QmiDevice *self;
CreateIostreamContext *ctx;
GSocketAddress *socket_address;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
/* Create socket client */
self->priv->socket_client = g_socket_client_new ();
g_socket_client_set_family (self->priv->socket_client, G_SOCKET_FAMILY_UNIX);
g_socket_client_set_socket_type (self->priv->socket_client, G_SOCKET_TYPE_STREAM);
g_socket_client_set_protocol (self->priv->socket_client, G_SOCKET_PROTOCOL_DEFAULT);
/* Setup socket address */
socket_address = g_unix_socket_address_new_with_type (
self->priv->proxy_path,
-1,
G_UNIX_SOCKET_ADDRESS_ABSTRACT);
/* Connect to address */
self->priv->socket_connection = g_socket_client_connect (
self->priv->socket_client,
G_SOCKET_CONNECTABLE (socket_address),
NULL,
&error);
g_object_unref (socket_address);
if (!self->priv->socket_connection) {
gchar **argc;
GSource *source;
g_debug ("cannot connect to proxy: %s", error->message);
g_clear_error (&error);
g_clear_object (&self->priv->socket_client);
/* Don't retry forever */
ctx->spawn_retries++;
if (ctx->spawn_retries > MAX_SPAWN_RETRIES) {
g_task_return_new_error (task,
QMI_CORE_ERROR,
QMI_CORE_ERROR_FAILED,
"Couldn't spawn the qmi-proxy");
g_object_unref (task);
return;
}
g_debug ("spawning new qmi-proxy (try %u)...", ctx->spawn_retries);
argc = g_new0 (gchar *, 2);
argc[0] = g_strdup (LIBEXEC_PATH "/qmi-proxy");
if (!g_spawn_async (NULL, /* working directory */
argc,
NULL, /* envp */
G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
(GSpawnChildSetupFunc) spawn_child_setup,
NULL, /* child_setup_user_data */
NULL,
&error)) {
g_debug ("error spawning qmi-proxy: %s", error->message);
g_clear_error (&error);
}
g_strfreev (argc);
/* Wait some ms and retry */
source = g_timeout_source_new (100);
g_source_set_callback (source, (GSourceFunc)wait_for_proxy_cb, task, NULL);
g_source_attach (source, g_main_context_get_thread_default ());
g_source_unref (source);
return;
}
self->priv->istream = g_io_stream_get_input_stream (G_IO_STREAM (self->priv->socket_connection));
if (self->priv->istream)
g_object_ref (self->priv->istream);
self->priv->ostream = g_io_stream_get_output_stream (G_IO_STREAM (self->priv->socket_connection));
if (self->priv->ostream)
g_object_ref (self->priv->ostream);
setup_iostream (task);
}
static void
create_iostream (QmiDevice *self,
gboolean proxy,
GAsyncReadyCallback callback,
gpointer user_data)
{
CreateIostreamContext *ctx;
GTask *task;
ctx = g_slice_new (CreateIostreamContext);
ctx->spawn_retries = 0;
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task,
ctx,
(GDestroyNotify)create_iostream_context_free);
if (self->priv->istream || self->priv->ostream) {
g_task_return_new_error (task,
QMI_CORE_ERROR,
QMI_CORE_ERROR_WRONG_STATE,
"Already open");
g_object_unref (task);
return;
}
g_assert (self->priv->file);
g_assert (self->priv->path);
if (proxy)
create_iostream_with_socket (task);
else
create_iostream_with_fd (task);
}
/*****************************************************************************/
/* Open device */
typedef enum {
DEVICE_OPEN_CONTEXT_STEP_FIRST = 0,
DEVICE_OPEN_CONTEXT_STEP_DRIVER,
#if defined MBIM_QMUX_ENABLED
DEVICE_OPEN_CONTEXT_STEP_DEVICE_MBIM,
DEVICE_OPEN_CONTEXT_STEP_OPEN_DEVICE_MBIM,
#endif
DEVICE_OPEN_CONTEXT_STEP_CREATE_IOSTREAM,
DEVICE_OPEN_CONTEXT_STEP_FLAGS_PROXY,
DEVICE_OPEN_CONTEXT_STEP_FLAGS_VERSION_INFO,
DEVICE_OPEN_CONTEXT_STEP_FLAGS_SYNC,
DEVICE_OPEN_CONTEXT_STEP_FLAGS_NETPORT,
#if defined MBIM_QMUX_ENABLED
DEVICE_OPEN_CONTEXT_STEP_FLAGS_EXPECT_INDICATIONS,
#endif
DEVICE_OPEN_CONTEXT_STEP_LAST
} DeviceOpenContextStep;
typedef struct {
DeviceOpenContextStep step;
QmiDeviceOpenFlags flags;
guint timeout;
guint version_check_retries;
gchar *driver;
} DeviceOpenContext;
static void
device_open_context_free (DeviceOpenContext *ctx)
{
g_free (ctx->driver);
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);
#if defined MBIM_QMUX_ENABLED
static void
mbim_qmi_notification_cb (MbimDevice *device,
MbimMessage *notification,
QmiDevice *self)
{
GByteArray *bytearray;
QmiMessage *message;
MbimService service;
const guint8 *buf;
guint32 len;
GError *error = NULL;
service = mbim_message_indicate_status_get_service (notification);
if (service != MBIM_SERVICE_QMI)
return;
buf = mbim_message_indicate_status_get_raw_information_buffer (notification, &len);
bytearray = g_byte_array_append (g_byte_array_sized_new (len), buf, len);
message = qmi_message_new_from_raw (bytearray, &error);
if (!message) {
if (error) {
g_warning ("[%s] couldn't create QMI message: %s",
self->priv->path_display, error->message);
g_free (error);
} else
g_warning ("[%s] couldn't create QMI message: missing data",
self->priv->path_display);
if (qmi_utils_get_traces_enabled ()) {
gchar *printable;
printable = __qmi_utils_str_hex (buf, len, ':');
g_debug ("<<<<<< RAW INVALID MESSAGE:\n"
"<<<<<< length = %u\n"
"<<<<<< data = %s\n",
len,
printable);
g_free (printable);
}
goto out;
}
process_message (self, message);
qmi_message_unref (message);
out:
g_byte_array_unref (bytearray);
}
static void
mbim_subscribe_list_set_ready_cb (MbimDevice *device,
GAsyncResult *res,
GTask *task)
{
QmiDevice *self;
DeviceOpenContext *ctx;
MbimMessage *response;
GError *error = NULL;
self = g_task_get_source_object (task);
response = mbim_device_command_finish (device, res, &error);
if (response) {
mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);
mbim_message_unref (response);
}
if (error) {
g_warning ("[%s] couldn't enable QMI indications via MBIM: %s",
self->priv->path_display, error->message);
g_error_free (error);
} else {
g_debug ("[%s] enabled QMI indications via MBIM", self->priv->path_display);
self->priv->mbim_notification_id = g_signal_connect (device,
MBIM_DEVICE_SIGNAL_INDICATE_STATUS,
G_CALLBACK (mbim_qmi_notification_cb),
self);
}
/* Go on */
ctx = g_task_get_task_data (task);
ctx->step++;
device_open_step (task);
}
#endif
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",
self->priv->path_display);
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;
/* Check result of the async operation */
output = qmi_client_ctl_sync_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_sync_output_get_result (output, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
qmi_message_ctl_sync_output_unref (output);
return;
}
self = g_task_get_source_object (task);
g_debug ("[%s] Sync operation finished",
self->priv->path_display);
qmi_message_ctl_sync_output_unref (output);
/* Go on */
ctx = g_task_get_task_data (task);
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);
self->priv->supported_services = g_array_ref (service_list);
g_debug ("[%s] QMI Device supports %u services:",
self->priv->path_display,
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)",
self->priv->path_display,
service_str,
info->major_version,
info->minor_version);
else
g_debug ("[%s] unknown [0x%02x] (%u.%u)",
self->priv->path_display,
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);
}
static void
internal_proxy_open_ready (QmiClientCtl *client_ctl,
GAsyncResult *res,
GTask *task)
{
DeviceOpenContext *ctx;
QmiMessageCtlInternalProxyOpenOutput *output;
GError *error = NULL;
/* Check result of the async operation */
output = qmi_client_ctl_internal_proxy_open_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_internal_proxy_open_output_get_result (output, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
qmi_message_ctl_internal_proxy_open_output_unref (output);
return;
}
qmi_message_ctl_internal_proxy_open_output_unref (output);
/* Go on */
ctx = g_task_get_task_data (task);
ctx->step++;
device_open_step (task);
}
static void
create_iostream_ready (QmiDevice *self,
GAsyncResult *res,
GTask *task)
{
DeviceOpenContext *ctx;
GError *error = NULL;
if (!create_iostream_finish (self, res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Go on */
ctx = g_task_get_task_data (task);
ctx->step++;
device_open_step (task);
}
#if defined MBIM_QMUX_ENABLED
static void
mbim_device_open_ready (MbimDevice *dev,
GAsyncResult *res,
GTask *task)
{
QmiDevice *self;
DeviceOpenContext *ctx;
GError *error = NULL;
if (!mbim_device_open_finish (dev, res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
self = g_task_get_source_object (task);
g_debug ("[%s] MBIM device open", self->priv->path_display);
/* Go on */
ctx = g_task_get_task_data (task);
ctx->step++;
device_open_step (task);
}
static void
open_mbim_device (GTask *task)
{
QmiDevice *self;
DeviceOpenContext *ctx;
MbimDeviceOpenFlags open_flags = MBIM_DEVICE_OPEN_FLAGS_NONE;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
/* If QMI proxy was requested, use MBIM proxy instead */
if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_PROXY)
open_flags |= MBIM_DEVICE_OPEN_FLAGS_PROXY;
/* We pass the original timeout of the request to the open operation */
g_debug ("[%s] opening MBIM device...", self->priv->path_display);
mbim_device_open_full (self->priv->mbimdev,
open_flags,
ctx->timeout,
g_task_get_cancellable (task),
(GAsyncReadyCallback) mbim_device_open_ready,
task);
}
static void
mbim_device_new_ready (GObject *source,
GAsyncResult *res,
GTask *task)
{
QmiDevice *self;
DeviceOpenContext *ctx;
GError *error = NULL;
self = g_task_get_source_object (task);
self->priv->mbimdev = mbim_device_new_finish (res, &error);
if (!self->priv->mbimdev) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_debug ("[%s] MBIM device created", self->priv->path_display);
/* Go on */
ctx = g_task_get_task_data (task);
ctx->step++;
device_open_step (task);
}
static void
create_mbim_device (GTask *task)
{
QmiDevice *self;
GFile *file;
self = g_task_get_source_object (task);
if (self->priv->mbimdev) {
g_task_return_new_error (task,
QMI_CORE_ERROR,
QMI_CORE_ERROR_WRONG_STATE,
"Already open");
g_object_unref (task);
return;
}
g_debug ("[%s] creating MBIM device...", self->priv->path_display);
file = g_file_new_for_path (self->priv->path);
mbim_device_new (file,
g_task_get_cancellable (task),
(GAsyncReadyCallback) mbim_device_new_ready,
task);
g_object_unref (file);
}
#endif
#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_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 down */
case DEVICE_OPEN_CONTEXT_STEP_DRIVER:
ctx->driver = __qmi_utils_get_driver (self->priv->path, &error);
if (ctx->driver)
g_debug ("[%s] loaded driver of cdc-wdm port: %s", self->priv->path_display, ctx->driver);
else if (!self->priv->no_file_check) {
g_warning ("[%s] couldn't load driver of cdc-wdm port: %s", self->priv->path_display, error->message);
g_error_free(error);
}
#if defined MBIM_QMUX_ENABLED
/* Auto mode requested? */
if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_AUTO) {
if (!g_strcmp0 (ctx->driver, "cdc_mbim")) {
g_debug ("[%s] automatically selecting MBIM mode", self->priv->path_display);
ctx->flags |= QMI_DEVICE_OPEN_FLAGS_MBIM;
goto next_step;
}
if (!g_strcmp0 (ctx->driver, "qmi_wwan")) {
g_debug ("[%s] automatically selecting QMI mode", self->priv->path_display);
ctx->flags &= ~QMI_DEVICE_OPEN_FLAGS_MBIM;
goto next_step;
}
g_task_return_new_error (task,
QMI_CORE_ERROR,
QMI_CORE_ERROR_FAILED,
"Cannot automatically select QMI/MBIM mode: driver %s",
ctx->driver ? ctx->driver : "unknown");
g_object_unref (task);
return;
}
/* MBIM mode requested? */
if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_MBIM) {
if (g_strcmp0 (ctx->driver, "cdc_mbim") && !self->priv->no_file_check)
g_warning ("[%s] requested MBIM mode but unexpected driver found: %s", self->priv->path_display, ctx->driver);
goto next_step;
}
#else
if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_AUTO)
g_warning ("[%s] requested auto mode but no MBIM QMUX support available", self->priv->path_display);
if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_MBIM)
g_warning ("[%s] requested MBIM mode but no MBIM QMUX support available", self->priv->path_display);
#endif /* MBIM_QMUX_ENABLED */
/* QMI mode requested? */
if (g_strcmp0 (ctx->driver, "qmi_wwan") && !self->priv->no_file_check)
g_warning ("[%s] requested QMI mode but unexpected driver found: %s",
self->priv->path_display, ctx->driver ? ctx->driver : "unknown");
#if defined MBIM_QMUX_ENABLED
next_step:
#endif
ctx->step++;
/* Fall down */
#if defined MBIM_QMUX_ENABLED
case DEVICE_OPEN_CONTEXT_STEP_DEVICE_MBIM:
if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_MBIM) {
create_mbim_device (task);
return;
}
ctx->step++;
/* Fall down */
case DEVICE_OPEN_CONTEXT_STEP_OPEN_DEVICE_MBIM:
if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_MBIM) {
open_mbim_device (task);
return;
}
ctx->step++;
/* Fall down */
#endif
case DEVICE_OPEN_CONTEXT_STEP_CREATE_IOSTREAM:
if (!(ctx->flags & QMI_DEVICE_OPEN_FLAGS_MBIM)) {
create_iostream (self,
!!(ctx->flags & QMI_DEVICE_OPEN_FLAGS_PROXY),
(GAsyncReadyCallback)create_iostream_ready,
task);
return;
}
ctx->step++;
/* Fall down */
case DEVICE_OPEN_CONTEXT_STEP_FLAGS_PROXY:
/* Initialize communication with proxy? */
if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_PROXY && !(ctx->flags & QMI_DEVICE_OPEN_FLAGS_MBIM)) {
QmiMessageCtlInternalProxyOpenInput *input;
input = qmi_message_ctl_internal_proxy_open_input_new ();
qmi_message_ctl_internal_proxy_open_input_set_device_path (input, self->priv->path, NULL);
qmi_client_ctl_internal_proxy_open (self->priv->client_ctl,
input,
5,
g_task_get_cancellable (task),
(GAsyncReadyCallback)internal_proxy_open_ready,
task);
qmi_message_ctl_internal_proxy_open_input_unref (input);
return;
}
ctx->step++;
/* Fall down */
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)...",
self->priv->path_display,
ctx->version_check_retries);
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 down */
case DEVICE_OPEN_CONTEXT_STEP_FLAGS_SYNC:
/* Sync? */
if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_SYNC) {
g_debug ("[%s] Running sync...",
self->priv->path_display);
qmi_client_ctl_sync (self->priv->client_ctl,
NULL,
ctx->timeout,
g_task_get_cancellable (task),
(GAsyncReadyCallback)sync_ready,
task);
return;
}
ctx->step++;
/* Fall down */
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...",
self->priv->path_display);
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 down */
#if defined MBIM_QMUX_ENABLED
case DEVICE_OPEN_CONTEXT_STEP_FLAGS_EXPECT_INDICATIONS:
/* Enable MBIM indications explicitly ONLY after knowing this is
* a QMI-capable MBIM device. */
if (self->priv->mbimdev && ctx->flags & QMI_DEVICE_OPEN_FLAGS_EXPECT_INDICATIONS) {
MbimEventEntry **entries;
guint n_entries = 0;
MbimMessage *request;
g_debug ("[%s] Enabling QMI indications via MBIM...", self->priv->path_display);
entries = g_new0 (MbimEventEntry *, 2);
entries[n_entries] = g_new (MbimEventEntry, 1);
memcpy (&(entries[n_entries]->device_service_id), MBIM_UUID_QMI, sizeof (MbimUuid));
entries[n_entries]->cids_count = 1;
entries[n_entries]->cids = g_new0 (guint32, 1);
entries[n_entries]->cids[0] = MBIM_CID_QMI_MSG;
n_entries++;
request = mbim_message_device_service_subscribe_list_set_new (
n_entries,
(const MbimEventEntry *const *)entries,
NULL);
mbim_device_command (self->priv->mbimdev,
request,
10,
NULL,
(GAsyncReadyCallback)mbim_subscribe_list_set_ready_cb,
task);
mbim_message_unref (request);
mbim_event_entry_array_free (entries);
return;
}
ctx->step++;
/* Fall down */
#endif
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'...",
self->priv->path_display,
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 */
static void
destroy_iostream (QmiDevice *self)
{
if (self->priv->input_source) {
g_source_destroy (self->priv->input_source);
g_clear_pointer (&self->priv->input_source, g_source_unref);
}
g_clear_pointer (&self->priv->buffer, g_byte_array_unref);
g_clear_object (&self->priv->istream);
g_clear_object (&self->priv->ostream);
g_clear_object (&self->priv->socket_connection);
g_clear_object (&self->priv->socket_client);
if (self->priv->fd >= 0) {
close (self->priv->fd);
self->priv->fd = -1;
}
}
#if defined MBIM_QMUX_ENABLED
static void
mbim_device_close_ready (MbimDevice *dev,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!mbim_device_close_finish (dev, res, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
#endif
gboolean
qmi_device_close_finish (QmiDevice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
void
qmi_device_close_async (QmiDevice *self,
guint timeout,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, cancellable, callback, user_data);
#if defined MBIM_QMUX_ENABLED
if (self->priv->mbimdev) {
/* Schedule in new main context */
mbim_device_close (self->priv->mbimdev,
timeout,
NULL,
(GAsyncReadyCallback) mbim_device_close_ready,
task);
/* Cleanup right away, we don't want multiple close attempts on the
* device */
if (self->priv->mbim_notification_id) {
g_signal_handler_disconnect (self->priv->mbimdev, self->priv->mbim_notification_id);
self->priv->mbim_notification_id = 0;
}
g_clear_object (&self->priv->mbimdev);
return;
}
#endif
destroy_iostream (self);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
#if defined MBIM_QMUX_ENABLED
static void
mbim_device_command_ready (MbimDevice *dev,
GAsyncResult *res,
QmiDevice *self)
{
MbimMessage *response;
GError *error = NULL;
const guint8 *buf;
guint32 len;
response = mbim_device_command_finish (dev, res, &error);
if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
g_prefix_error (&error, "MBIM error: ");
if (response)
mbim_message_unref (response);
g_object_unref (self);
return;
}
/* Store the raw information buffer in the internal reception buffer,
* as if we had read from a iochannel. */
buf = mbim_message_command_done_get_raw_information_buffer (response, &len);
if (!G_UNLIKELY (self->priv->buffer))
self->priv->buffer = g_byte_array_sized_new (len);
g_byte_array_append (self->priv->buffer, buf, len);
/* And parse it as QMI; it should remove and cleanup the transaction */
parse_response (self);
mbim_message_unref (response);
g_object_unref (self);
}
static gboolean
mbim_command (QmiDevice *self,
gconstpointer raw_message,
gsize raw_message_len,
guint timeout,
GCancellable *cancellable,
GError **error)
{
MbimMessage *mbim_message;
mbim_message = mbim_message_qmi_msg_set_new (raw_message_len, raw_message, error);
if (!mbim_message)
return FALSE;
/* Note:
*
* Pass a full reference to the original QMI device to the MBIM command
* operation, so that we make sure the parent object is valid regardless
* of when the underlying device is fully disposed. This is required
* because device close is async().
*/
mbim_device_command (self->priv->mbimdev,
mbim_message,
timeout,
cancellable,
(GAsyncReadyCallback) mbim_device_command_ready,
g_object_ref (self));
mbim_message_unref (mbim_message);
return TRUE;
}
#endif
/*****************************************************************************/
/* Command */
QmiMessage *
qmi_device_command_full_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_full (QmiDevice *self,
QmiMessage *message,
QmiMessageContext *message_context,
guint timeout,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GError *error = NULL;
Transaction *tr;
gconstpointer raw_message;
gsize raw_message_len;
g_return_if_fail (QMI_IS_DEVICE (self));
g_return_if_fail (message != NULL);
g_return_if_fail (timeout > 0);
/* 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 (!self->priv->istream || !self->priv->ostream) {
#if defined MBIM_QMUX_ENABLED
if (!self->priv->mbimdev)
#endif
{
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;
}
/* Check if the message to be sent is supported by the device
* (only applicable if we did version info check when opening) */
if (!check_message_supported (self, message, &error)) {
g_prefix_error (&error, "Cannot send message: ");
transaction_early_error (self, tr, FALSE, error);
return;
}
/* Get raw message */
raw_message = qmi_message_get_raw (message, &raw_message_len, &error);
if (!raw_message) {
g_prefix_error (&error, "Cannot get raw message: ");
transaction_early_error (self, tr, FALSE, error);
return;
}
/* 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 defined MBIM_QMUX_ENABLED
if (self->priv->mbimdev) {
if (!mbim_command (self,
raw_message,
raw_message_len,
timeout + MBIM_TIMEOUT_DELAY_SECS,
NULL, /* cancellable */
&error)) {
g_prefix_error (&error, "Cannot create MBIM command: ");
transaction_early_error (self, tr, TRUE, error);
}
return;
}
#endif
if (!g_output_stream_write_all (self->priv->ostream,
raw_message,
raw_message_len,
NULL, /* bytes_written */
NULL, /* cancellable */
&error)) {
g_prefix_error (&error, "Cannot write message: ");
transaction_early_error (self, tr, TRUE, error);
return;
}
/* Flush explicitly if correctly written */
g_output_stream_flush (self->priv->ostream, NULL, NULL);
}
/*****************************************************************************/
/* Generic command */
QmiMessage *
qmi_device_command_finish (QmiDevice *self,
GAsyncResult *res,
GError **error)
{
return qmi_device_command_full_finish (self, res, error);
}
void
qmi_device_command (QmiDevice *self,
QmiMessage *message,
guint timeout,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
qmi_device_command_full (self, message, NULL, timeout, cancellable, callback, user_data);
}
/*****************************************************************************/
/* New QMI device */
QmiDevice *
qmi_device_new_finish (GAsyncResult *res,
GError **error)
{
GObject *ret;
GObject *source_object;
source_object = g_async_result_get_source_object (res);
ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error);
g_object_unref (source_object);
return (ret ? QMI_DEVICE (ret) : NULL);
}
void
qmi_device_new (GFile *file,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
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",
self->priv->path_display);
}
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
query_info_async_ready (GFile *file,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
GFileInfo *info;
info = g_file_query_info_finish (file, res, &error);
if (!info) {
g_prefix_error (&error,
"Couldn't query file info: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Our QMI device must be of SPECIAL type */
if (g_file_info_get_file_type (info) != G_FILE_TYPE_SPECIAL) {
g_task_return_new_error (task,
QMI_CORE_ERROR,
QMI_CORE_ERROR_FAILED,
"Wrong file type");
g_object_unref (task);
return;
}
g_object_unref (info);
/* 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 */
if (!self->priv->file) {
g_task_return_new_error (task,
QMI_CORE_ERROR,
QMI_CORE_ERROR_INVALID_ARGS,
"Cannot initialize QMI device: No file given");
g_object_unref (task);
return;
}
/* 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. */
g_file_query_info_async (self->priv->file,
G_FILE_ATTRIBUTE_STANDARD_TYPE,
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT,
cancellable,
(GAsyncReadyCallback)query_info_async_ready,
task);
}
/*****************************************************************************/
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:
g_assert (self->priv->file == NULL);
self->priv->file = g_value_dup_object (value);
if (self->priv->file) {
self->priv->path = g_file_get_path (self->priv->file);
self->priv->path_display = g_filename_display_name (self->priv->path);
}
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;
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_value_set_object (value, self->priv->file);
break;
case PROP_WWAN_IFACE:
reload_wwan_iface_name (self);
g_value_set_string (value, self->priv->wwan_iface);
break;
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);
self->priv->fd = -1;
}
static gboolean
foreach_warning (gpointer key,
QmiClient *client,
QmiDevice *self)
{
g_warning ("[%s] QMI client for service '%s' with CID '%u' wasn't released",
self->priv->path_display,
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);
g_clear_object (&self->priv->file);
/* 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 defined MBIM_QMUX_ENABLED
if (self->priv->mbimdev) {
g_warning ("[%s] MBIM device wasn't explicitly closed",
self->priv->path_display);
if (self->priv->mbim_notification_id) {
g_signal_handler_disconnect (self->priv->mbimdev, self->priv->mbim_notification_id);
self->priv->mbim_notification_id = 0;
}
g_clear_object (&self->priv->mbimdev);
}
#endif
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);
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->path);
g_free (self->priv->path_display);
g_free (self->priv->proxy_path);
g_free (self->priv->wwan_iface);
destroy_iostream (self);
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::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);
}