Files
ModemManager/src/mm-port-qmi.c
T
Aleksander Morgado a20f2428ee port-qmi: implement the new generic 'removed' signal
Letting the MMBroadbandModemQmi listen for the notifications of the
QmiDevice was not a good idea, especially since no explicit reference
to the device object was hold in the modem object. This leads to race
conditions in which the QmiDevice outlives the MMBroadbandModemQmi and
the MMPortQmi, and when the "device-removed" signal is triggered, the
program crashes.

The MMPortQmi will now emit its own 'removed' signals when the
underlying QmiDevice emits "device-removed', ensuring that the signal
handler is properly cleared up during the MMPortQmi disposal.
2022-12-18 21:52:51 +00:00

2847 lines
102 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details:
*
* Copyright (C) 2012-2021 Google, Inc.
* Copyright (C) 2021 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <libqmi-glib.h>
#include <ModemManager.h>
#include <mm-errors-types.h>
#include "mm-port-qmi.h"
#include "mm-port-net.h"
#include "mm-port-enums-types.h"
#include "mm-modem-helpers-qmi.h"
#include "mm-log-object.h"
#define DEFAULT_LINK_PREALLOCATED_AMOUNT 4
/* as internally defined in the kernel */
#define RMNET_MAX_PACKET_SIZE 16384
G_DEFINE_TYPE (MMPortQmi, mm_port_qmi, MM_TYPE_PORT)
#if defined WITH_QRTR
enum {
PROP_0,
PROP_NODE,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
#endif
typedef struct {
QmiService service;
QmiClient *client;
guint flag;
} ServiceInfo;
struct _MMPortQmiPrivate {
gboolean in_progress;
QmiDevice *qmi_device;
GList *services;
gchar *net_driver;
gchar *net_sysfs_path;
#if defined WITH_QRTR
QrtrNode *node;
#endif
/* port monitoring */
gulong timeout_monitoring_id;
gulong removed_monitoring_id;
/* endpoint info */
QmiDataEndpointType endpoint_type;
gint endpoint_interface_number;
/* kernel data mode */
MMPortQmiKernelDataMode kernel_data_modes;
/* wda settings */
gboolean wda_unsupported;
QmiWdaLinkLayerProtocol llp;
QmiWdaDataAggregationProtocol dap;
guint max_multiplexed_links;
/* preallocated links */
MMPort *preallocated_links_main;
GArray *preallocated_links;
GList *preallocated_links_setup_pending;
};
/*****************************************************************************/
static QmiClient *
lookup_client (MMPortQmi *self,
QmiService service,
guint flag,
gboolean steal)
{
GList *l;
for (l = self->priv->services; l; l = g_list_next (l)) {
ServiceInfo *info = l->data;
if (info->service == service && info->flag == flag) {
QmiClient *found;
found = info->client;
if (steal) {
self->priv->services = g_list_delete_link (self->priv->services, l);
g_free (info);
}
return found;
}
}
return NULL;
}
QmiClient *
mm_port_qmi_peek_client (MMPortQmi *self,
QmiService service,
guint flag)
{
return lookup_client (self, service, flag, FALSE);
}
QmiClient *
mm_port_qmi_get_client (MMPortQmi *self,
QmiService service,
guint flag)
{
QmiClient *client;
client = mm_port_qmi_peek_client (self, service, flag);
return (client ? g_object_ref (client) : NULL);
}
/*****************************************************************************/
static void
initialize_endpoint_info (MMPortQmi *self)
{
MMKernelDevice *kernel_device;
kernel_device = mm_port_peek_kernel_device (MM_PORT (self));
self->priv->endpoint_type = mm_port_net_driver_to_qmi_endpoint_type (self->priv->net_driver);
switch (self->priv->endpoint_type) {
case QMI_DATA_ENDPOINT_TYPE_HSUSB:
g_assert (kernel_device);
self->priv->endpoint_interface_number = mm_kernel_device_get_interface_number (kernel_device);
break;
case QMI_DATA_ENDPOINT_TYPE_EMBEDDED:
self->priv->endpoint_interface_number = 1;
break;
case QMI_DATA_ENDPOINT_TYPE_PCIE:
case QMI_DATA_ENDPOINT_TYPE_UNDEFINED:
case QMI_DATA_ENDPOINT_TYPE_HSIC:
case QMI_DATA_ENDPOINT_TYPE_BAM_DMUX:
case QMI_DATA_ENDPOINT_TYPE_UNKNOWN:
default:
self->priv->endpoint_interface_number = 0;
break;
}
mm_obj_dbg (self, "endpoint info updated: type '%s', interface number '%u'",
qmi_data_endpoint_type_get_string (self->priv->endpoint_type),
self->priv->endpoint_interface_number);
}
QmiDataEndpointType
mm_port_qmi_get_endpoint_type (MMPortQmi *self)
{
return self->priv->endpoint_type;
}
guint
mm_port_qmi_get_endpoint_interface_number (MMPortQmi *self)
{
return self->priv->endpoint_interface_number;
}
void
mm_port_qmi_get_endpoint_info (MMPortQmi *self, MMQmiDataEndpoint *out_endpoint)
{
out_endpoint->type = self->priv->endpoint_type;
out_endpoint->interface_number = self->priv->endpoint_interface_number;
out_endpoint->sio_port = QMI_SIO_PORT_NONE;
}
/*****************************************************************************/
static void
reset_monitoring (MMPortQmi *self,
QmiDevice *qmi_device)
{
if (self->priv->timeout_monitoring_id && qmi_device) {
g_signal_handler_disconnect (qmi_device, self->priv->timeout_monitoring_id);
self->priv->timeout_monitoring_id = 0;
}
if (self->priv->removed_monitoring_id && qmi_device) {
g_signal_handler_disconnect (qmi_device, self->priv->removed_monitoring_id);
self->priv->removed_monitoring_id = 0;
}
}
static void
consecutive_timeouts_updated_cb (MMPortQmi *self,
GParamSpec *pspec,
QmiDevice *qmi_device)
{
g_signal_emit_by_name (self, MM_PORT_SIGNAL_TIMED_OUT, qmi_device_get_consecutive_timeouts (qmi_device));
}
static void
device_removed_cb (MMPortQmi *self)
{
g_signal_emit_by_name (self, MM_PORT_SIGNAL_REMOVED);
}
static void
setup_monitoring (MMPortQmi *self,
QmiDevice *qmi_device)
{
g_assert (qmi_device);
reset_monitoring (self, qmi_device);
g_assert (!self->priv->timeout_monitoring_id);
self->priv->timeout_monitoring_id = g_signal_connect_swapped (qmi_device,
"notify::" QMI_DEVICE_CONSECUTIVE_TIMEOUTS,
G_CALLBACK (consecutive_timeouts_updated_cb),
self);
g_assert (!self->priv->removed_monitoring_id);
self->priv->removed_monitoring_id = g_signal_connect_swapped (qmi_device,
QMI_DEVICE_SIGNAL_REMOVED,
G_CALLBACK (device_removed_cb),
self);
}
/*****************************************************************************/
void
mm_port_qmi_release_client (MMPortQmi *self,
QmiService service,
MMPortQmiFlag flag)
{
QmiClient *client;
if (!self->priv->qmi_device)
return;
client = lookup_client (self, service, flag, TRUE);
if (!client)
return;
mm_obj_dbg (self, "explicitly releasing client for service '%s'...", qmi_service_get_string (service));
qmi_device_release_client (self->priv->qmi_device,
client,
QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID,
3, NULL, NULL, NULL);
g_object_unref (client);
}
/*****************************************************************************/
typedef struct {
ServiceInfo *info;
} AllocateClientContext;
static void
allocate_client_context_free (AllocateClientContext *ctx)
{
if (ctx->info) {
g_assert (ctx->info->client == NULL);
g_free (ctx->info);
}
g_free (ctx);
}
gboolean
mm_port_qmi_allocate_client_finish (MMPortQmi *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
allocate_client_ready (QmiDevice *qmi_device,
GAsyncResult *res,
GTask *task)
{
MMPortQmi *self;
AllocateClientContext *ctx;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
ctx->info->client = qmi_device_allocate_client_finish (qmi_device, res, &error);
if (!ctx->info->client) {
g_prefix_error (&error,
"Couldn't create client for service '%s': ",
qmi_service_get_string (ctx->info->service));
g_task_return_error (task, error);
} else {
/* Move the service info to our internal list */
self->priv->services = g_list_prepend (self->priv->services, ctx->info);
ctx->info = NULL;
g_task_return_boolean (task, TRUE);
}
g_object_unref (task);
}
void
mm_port_qmi_allocate_client (MMPortQmi *self,
QmiService service,
guint flag,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
AllocateClientContext *ctx;
GTask *task;
task = g_task_new (self, cancellable, callback, user_data);
if (!mm_port_qmi_is_open (self)) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
"Port is closed");
g_object_unref (task);
return;
}
if (!!mm_port_qmi_peek_client (self, service, flag)) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_EXISTS,
"Client for service '%s' already allocated",
qmi_service_get_string (service));
g_object_unref (task);
return;
}
ctx = g_new0 (AllocateClientContext, 1);
ctx->info = g_new0 (ServiceInfo, 1);
ctx->info->service = service;
ctx->info->flag = flag;
g_task_set_task_data (task, ctx, (GDestroyNotify)allocate_client_context_free);
qmi_device_allocate_client (self->priv->qmi_device,
service,
QMI_CID_NONE,
10,
cancellable,
(GAsyncReadyCallback)allocate_client_ready,
task);
}
/*****************************************************************************/
typedef struct {
gchar *link_name;
guint mux_id;
gboolean setup;
} PreallocatedLinkInfo;
static void
preallocated_link_info_clear (PreallocatedLinkInfo *info)
{
g_free (info->link_name);
}
static void
delete_preallocated_links (QmiDevice *qmi_device,
GArray *preallocated_links)
{
guint i;
/* This link deletion cleanup may fail if the main interface is up
* (a limitation of qmi_wwan in some kernel versions). It's just a minor
* inconvenience really, if MM restarts they'll be all removed during
* initialization anyway */
for (i = 0; i < preallocated_links->len; i++) {
PreallocatedLinkInfo *info;
info = &g_array_index (preallocated_links, PreallocatedLinkInfo, i);
qmi_device_delete_link (qmi_device, info->link_name, info->mux_id,
NULL, NULL, NULL);
}
}
static guint
count_preallocated_links_setup (MMPortQmi *self)
{
guint i;
guint count = 0;
for (i = 0; self->priv->preallocated_links && (i < self->priv->preallocated_links->len); i++) {
PreallocatedLinkInfo *info;
info = &g_array_index (self->priv->preallocated_links, PreallocatedLinkInfo, i);
if (info->setup)
count++;
}
return count;
}
static gboolean
release_preallocated_link (MMPortQmi *self,
const gchar *link_name,
guint mux_id,
GError **error)
{
guint i;
for (i = 0; self->priv->preallocated_links && (i < self->priv->preallocated_links->len); i++) {
PreallocatedLinkInfo *info;
info = &g_array_index (self->priv->preallocated_links, PreallocatedLinkInfo, i);
if (!info->setup || (g_strcmp0 (info->link_name, link_name) != 0) || (info->mux_id != mux_id))
continue;
info->setup = FALSE;
return TRUE;
}
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"No preallocated link found to release");
return FALSE;
}
static gboolean
acquire_preallocated_link (MMPortQmi *self,
MMPort *main,
gchar **link_name,
guint *mux_id,
GError **error)
{
guint i;
if (!self->priv->qmi_device) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
"port is closed");
return FALSE;
}
if (!self->priv->preallocated_links || !self->priv->preallocated_links_main) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"No preallocated links available");
return FALSE;
}
if ((main != self->priv->preallocated_links_main) &&
(g_strcmp0 (mm_port_get_device (main), mm_port_get_device (self->priv->preallocated_links_main)) != 0)) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Preallocated links available in 'net/%s', not in 'net/%s'",
mm_port_get_device (self->priv->preallocated_links_main),
mm_port_get_device (main));
return FALSE;
}
for (i = 0; i < self->priv->preallocated_links->len; i++) {
PreallocatedLinkInfo *info;
info = &g_array_index (self->priv->preallocated_links, PreallocatedLinkInfo, i);
if (info->setup)
continue;
info->setup = TRUE;
*link_name = g_strdup (info->link_name);
*mux_id = info->mux_id;
return TRUE;
}
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"No more preallocated links available");
return FALSE;
}
/*****************************************************************************/
typedef struct {
QmiDevice *qmi_device;
MMPort *data;
GArray *preallocated_links;
} InitializePreallocatedLinksContext;
static void
initialize_preallocated_links_context_free (InitializePreallocatedLinksContext *ctx)
{
if (ctx->preallocated_links) {
delete_preallocated_links (ctx->qmi_device, ctx->preallocated_links);
g_array_unref (ctx->preallocated_links);
}
g_object_unref (ctx->qmi_device);
g_object_unref (ctx->data);
g_slice_free (InitializePreallocatedLinksContext, ctx);
}
static GArray *
initialize_preallocated_links_finish (MMPortQmi *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void initialize_preallocated_links_next (GTask *task);
static void
device_add_link_preallocated_ready (QmiDevice *device,
GAsyncResult *res,
GTask *task)
{
InitializePreallocatedLinksContext *ctx;
GError *error = NULL;
PreallocatedLinkInfo info = { NULL, 0, FALSE };
ctx = g_task_get_task_data (task);
info.link_name = qmi_device_add_link_finish (device, res, &info.mux_id, &error);
if (!info.link_name) {
g_prefix_error (&error, "failed to add preallocated link (%u/%u) for device: ",
ctx->preallocated_links->len + 1, DEFAULT_LINK_PREALLOCATED_AMOUNT);
g_task_return_error (task, error);
return;
}
g_array_append_val (ctx->preallocated_links, info);
initialize_preallocated_links_next (task);
}
static void
initialize_preallocated_links_next (GTask *task)
{
MMPortQmi *self;
InitializePreallocatedLinksContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
/* if we were closed while allocating, bad thing, abort */
if (!self->priv->qmi_device) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, "port is closed");
g_object_unref (task);
return;
}
if (ctx->preallocated_links->len == DEFAULT_LINK_PREALLOCATED_AMOUNT) {
g_task_return_pointer (task, g_steal_pointer (&ctx->preallocated_links), (GDestroyNotify)g_array_unref);
g_object_unref (task);
return;
}
qmi_device_add_link (self->priv->qmi_device,
ctx->preallocated_links->len + 1,
mm_kernel_device_get_name (mm_port_peek_kernel_device (ctx->data)),
"ignored", /* n/a in qmi_wwan add_mux */
NULL,
(GAsyncReadyCallback) device_add_link_preallocated_ready,
task);
}
static void
initialize_preallocated_links (MMPortQmi *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
InitializePreallocatedLinksContext *ctx;
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (InitializePreallocatedLinksContext);
ctx->qmi_device = g_object_ref (self->priv->qmi_device);
ctx->data = g_object_ref (self->priv->preallocated_links_main);
ctx->preallocated_links = g_array_sized_new (FALSE, FALSE, sizeof (PreallocatedLinkInfo), DEFAULT_LINK_PREALLOCATED_AMOUNT);
g_array_set_clear_func (ctx->preallocated_links, (GDestroyNotify)preallocated_link_info_clear);
g_task_set_task_data (task, ctx, (GDestroyNotify)initialize_preallocated_links_context_free);
initialize_preallocated_links_next (task);
}
/*****************************************************************************/
typedef struct {
MMPort *main;
gchar *link_name;
guint mux_id;
} SetupLinkContext;
static void
setup_link_context_free (SetupLinkContext *ctx)
{
g_free (ctx->link_name);
g_clear_object (&ctx->main);
g_slice_free (SetupLinkContext, ctx);
}
gchar *
mm_port_qmi_setup_link_finish (MMPortQmi *self,
GAsyncResult *res,
guint *mux_id,
GError **error)
{
SetupLinkContext *ctx;
if (!g_task_propagate_boolean (G_TASK (res), error))
return NULL;
ctx = g_task_get_task_data (G_TASK (res));
if (mux_id)
*mux_id = ctx->mux_id;
return g_steal_pointer (&ctx->link_name);
}
static void
device_add_link_ready (QmiDevice *device,
GAsyncResult *res,
GTask *task)
{
SetupLinkContext *ctx;
GError *error = NULL;
ctx = g_task_get_task_data (task);
ctx->link_name = qmi_device_add_link_with_flags_finish (device, res, &ctx->mux_id, &error);
if (!ctx->link_name) {
g_prefix_error (&error, "failed to add link for device: ");
g_task_return_error (task, error);
} else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
setup_preallocated_link (GTask *task)
{
MMPortQmi *self;
SetupLinkContext *ctx;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (!acquire_preallocated_link (self, ctx->main, &ctx->link_name, &ctx->mux_id, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
initialize_preallocated_links_ready (MMPortQmi *self,
GAsyncResult *res,
GTask *task)
{
g_autoptr(GError) error = NULL;
g_assert (!self->priv->preallocated_links);
self->priv->preallocated_links = initialize_preallocated_links_finish (self, res, &error);
if (!self->priv->preallocated_links) {
/* We need to fail this task and all the additional tasks also pending */
g_task_return_error (task, g_error_copy (error));
g_object_unref (task);
while (self->priv->preallocated_links_setup_pending) {
g_task_return_error (self->priv->preallocated_links_setup_pending->data, g_error_copy (error));
g_object_unref (self->priv->preallocated_links_setup_pending->data);
self->priv->preallocated_links_setup_pending = g_list_delete_link (self->priv->preallocated_links_setup_pending,
self->priv->preallocated_links_setup_pending);
}
/* and reset back the main, because we're not really initialized */
g_clear_object (&self->priv->preallocated_links_main);
return;
}
/* Now we know preallocated links are available, complete our task and all the pending ones */
setup_preallocated_link (task);
while (self->priv->preallocated_links_setup_pending) {
setup_preallocated_link (self->priv->preallocated_links_setup_pending->data);
self->priv->preallocated_links_setup_pending = g_list_delete_link (self->priv->preallocated_links_setup_pending,
self->priv->preallocated_links_setup_pending);
}
}
void
mm_port_qmi_setup_link (MMPortQmi *self,
MMPort *data,
const gchar *link_prefix_hint,
GAsyncReadyCallback callback,
gpointer user_data)
{
SetupLinkContext *ctx;
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
if (!self->priv->qmi_device) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Port is not open");
g_object_unref (task);
return;
}
if (!(self->priv->kernel_data_modes & (MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET | MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN))) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Multiplex support not available in kernel");
g_object_unref (task);
return;
}
if (!MM_PORT_QMI_DAP_IS_SUPPORTED_QMAP (self->priv->dap)) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Aggregation not enabled");
g_object_unref (task);
return;
}
ctx = g_slice_new0 (SetupLinkContext);
ctx->main = g_object_ref (data);
ctx->mux_id = QMI_DEVICE_MUX_ID_UNBOUND;
g_task_set_task_data (task, ctx, (GDestroyNotify) setup_link_context_free);
/* When using rmnet, just try to add link in the QmiDevice */
if (self->priv->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET) {
QmiDeviceAddLinkFlags flags = QMI_DEVICE_ADD_LINK_FLAGS_NONE;
/* This may not be fully right, but it's the only way forward we know
* right now for the Qualcomm SoCs based on QRTR+IPA, where QMAPV4 is
* used and the device has checksum offload enabled by default, so we
* should create the link with special flags. Ideally, we would have a
* way to know in advance whether the checksum offload flags are needed
* or not.
*/
if (self->priv->dap == QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAPV4)
flags = (QMI_DEVICE_ADD_LINK_FLAGS_INGRESS_MAP_CKSUMV4 | QMI_DEVICE_ADD_LINK_FLAGS_EGRESS_MAP_CKSUMV4);
qmi_device_add_link_with_flags (self->priv->qmi_device,
QMI_DEVICE_MUX_ID_AUTOMATIC,
mm_kernel_device_get_name (mm_port_peek_kernel_device (data)),
link_prefix_hint,
flags,
NULL,
(GAsyncReadyCallback) device_add_link_ready,
task);
return;
}
/* For qmi_wwan, use preallocated links */
if (self->priv->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN) {
if (self->priv->preallocated_links) {
setup_preallocated_link (task);
return;
}
/* We must make sure we don't run this procedure in parallel (e.g. if multiple
* connection attempts reach at the same time), so if we're told the preallocated
* links are already being initialized (main is set) but the array didn't exist,
* queue our task for completion once we're fully initialized */
if (self->priv->preallocated_links_main) {
self->priv->preallocated_links_setup_pending = g_list_append (self->priv->preallocated_links_setup_pending, task);
return;
}
/* Store main to flag that we're initializing preallocated links */
self->priv->preallocated_links_main = g_object_ref (data);
initialize_preallocated_links (self,
(GAsyncReadyCallback) initialize_preallocated_links_ready,
task);
return;
}
g_assert_not_reached ();
}
/*****************************************************************************/
gboolean
mm_port_qmi_cleanup_link_finish (MMPortQmi *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
device_delete_link_ready (QmiDevice *device,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!qmi_device_delete_link_finish (device, res, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
void
mm_port_qmi_cleanup_link (MMPortQmi *self,
const gchar *link_name,
guint mux_id,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GError *error = NULL;
task = g_task_new (self, NULL, callback, user_data);
if (!self->priv->qmi_device) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Port is not open");
g_object_unref (task);
return;
}
if (!(self->priv->kernel_data_modes & (MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET | MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN))) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Multiplex support not available in kernel");
g_object_unref (task);
return;
}
if (!MM_PORT_QMI_DAP_IS_SUPPORTED_QMAP (self->priv->dap)) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Aggregation not enabled");
g_object_unref (task);
return;
}
/* When using rmnet, just try to add link in the QmiDevice */
if (self->priv->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET) {
qmi_device_delete_link (self->priv->qmi_device,
link_name,
mux_id,
NULL,
(GAsyncReadyCallback) device_delete_link_ready,
task);
return;
}
/* For qmi_wwan, use preallocated links */
if (self->priv->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN) {
if (!release_preallocated_link (self, link_name, mux_id, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
g_assert_not_reached ();
}
/*****************************************************************************/
typedef struct {
QmiDevice *device;
MMPort *data;
} InternalResetContext;
static void
internal_reset_context_free (InternalResetContext *ctx)
{
g_clear_object (&ctx->device);
g_clear_object (&ctx->data);
g_slice_free (InternalResetContext, ctx);
}
static gboolean
internal_reset_finish (MMPortQmi *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
delete_all_links_ready (QmiDevice *device,
GAsyncResult *res,
GTask *task)
{
MMPortQmi *self;
InternalResetContext *ctx;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
/* link deletion not fatal, it may happen if in 802.3 already */
if (!qmi_device_delete_all_links_finish (device, res, &error)) {
mm_obj_dbg (self, "couldn't delete all links: %s", error->message);
g_clear_error (&error);
}
/* expected data format only applicable to qmi_wwan */
if (g_strcmp0 (self->priv->net_driver, "qmi_wwan") == 0) {
mm_obj_dbg (self, "reseting expected kernel data format to 802.3 in data interface '%s'",
mm_port_get_device (MM_PORT (ctx->data)));
if (!qmi_device_set_expected_data_format (ctx->device, QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
net_link_down_ready (MMPortNet *data,
GAsyncResult *res,
GTask *task)
{
MMPortQmi *self;
InternalResetContext *ctx;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (!mm_port_net_link_setup_finish (data, res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* first, delete all links found, if any */
mm_obj_dbg (self, "deleting all links in data interface '%s'",
mm_port_get_device (ctx->data));
qmi_device_delete_all_links (ctx->device,
mm_port_get_device (ctx->data),
NULL,
(GAsyncReadyCallback)delete_all_links_ready,
task);
}
static void
internal_reset (MMPortQmi *self,
MMPort *data,
QmiDevice *device,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
InternalResetContext *ctx;
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (InternalResetContext);
ctx->data = g_object_ref (data);
ctx->device = g_object_ref (device);
g_task_set_task_data (task, ctx, (GDestroyNotify) internal_reset_context_free);
/* first, bring down main interface */
mm_obj_dbg (self, "bringing down data interface '%s'",
mm_port_get_device (ctx->data));
mm_port_net_link_setup (MM_PORT_NET (ctx->data),
FALSE,
MM_PORT_NET_MTU_DEFAULT,
NULL,
(GAsyncReadyCallback) net_link_down_ready,
task);
}
/*****************************************************************************/
typedef struct {
QmiDevice *device;
MMPort *data;
} ResetContext;
static void
reset_context_free (ResetContext *ctx)
{
g_clear_object (&ctx->device);
g_clear_object (&ctx->data);
g_slice_free (ResetContext, ctx);
}
gboolean
mm_port_qmi_reset_finish (MMPortQmi *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
internal_reset_ready (MMPortQmi *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!internal_reset_finish (self, res, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
reset_device_new_ready (GObject *source,
GAsyncResult *res,
GTask *task)
{
MMPortQmi *self;
ResetContext *ctx;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
ctx->device = qmi_device_new_finish (res, &error);
if (!ctx->device) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
internal_reset (self,
ctx->data,
ctx->device,
(GAsyncReadyCallback) internal_reset_ready,
task);
}
void
mm_port_qmi_reset (MMPortQmi *self,
MMPort *data,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
ResetContext *ctx;
g_autoptr(GFile) file = NULL;
g_autofree gchar *fullpath = NULL;
task = g_task_new (self, NULL, callback, user_data);
if (self->priv->qmi_device) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Port is already open");
g_object_unref (task);
return;
}
ctx = g_slice_new0 (ResetContext);
ctx->data = g_object_ref (data);
g_task_set_task_data (task, ctx, (GDestroyNotify) reset_context_free);
fullpath = g_strdup_printf ("/dev/%s", mm_port_get_device (MM_PORT (self)));
file = g_file_new_for_path (fullpath);
qmi_device_new (file, NULL,
(GAsyncReadyCallback) reset_device_new_ready,
task);
}
/*****************************************************************************/
MMPortQmiKernelDataMode
mm_port_qmi_get_kernel_data_modes (MMPortQmi *self)
{
return self->priv->kernel_data_modes;
}
QmiWdaLinkLayerProtocol
mm_port_qmi_get_link_layer_protocol (MMPortQmi *self)
{
return self->priv->llp;
}
QmiWdaDataAggregationProtocol
mm_port_qmi_get_data_aggregation_protocol (MMPortQmi *self)
{
return self->priv->dap;
}
guint
mm_port_qmi_get_max_multiplexed_links (MMPortQmi *self)
{
return self->priv->max_multiplexed_links;
}
/*****************************************************************************/
static MMPortQmiKernelDataMode
load_current_kernel_data_modes (MMPortQmi *self,
QmiDevice *device)
{
/* For BAM-DMUX based setups, raw-ip only and no multiplexing */
if (g_strcmp0 (self->priv->net_driver, "bam-dmux") == 0)
return MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP;
/* For IPA based setups, always rmnet multiplexing */
if (g_strcmp0 (self->priv->net_driver, "ipa") == 0)
return MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET;
/* For USB based setups, query kernel */
if (g_strcmp0 (self->priv->net_driver, "qmi_wwan") == 0) {
switch (qmi_device_get_expected_data_format (device, NULL)) {
case QMI_DEVICE_EXPECTED_DATA_FORMAT_QMAP_PASS_THROUGH:
return MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET;
case QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP:
if (qmi_device_check_link_supported (device, NULL))
return (MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP | MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN);
return MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP;
case QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN:
/* If the expected data format is unknown, it means the kernel in use
* doesn't have support for querying it; therefore it's 802.3 */
case QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3:
return MM_PORT_QMI_KERNEL_DATA_MODE_802_3;
default:
g_assert_not_reached ();
return MM_PORT_QMI_KERNEL_DATA_MODE_NONE;
}
}
/* For any driver, assume raw-ip only */
return MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP;
}
static MMPortQmiKernelDataMode
load_supported_kernel_data_modes (MMPortQmi *self,
QmiDevice *device)
{
/* For BAM-DMUX based setups, raw-ip only and no multiplexing */
if (g_strcmp0 (self->priv->net_driver, "bam-dmux") == 0)
return MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP;
/* For IPA based setups, always rmnet multiplexing */
if (g_strcmp0 (self->priv->net_driver, "ipa") == 0)
return MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET;
/* For USB based setups, we may have all supported */
if (g_strcmp0 (self->priv->net_driver, "qmi_wwan") == 0) {
MMPortQmiKernelDataMode supported = MM_PORT_QMI_KERNEL_DATA_MODE_802_3;
/* If raw-ip is not supported, muxing is also not supported */
if (qmi_device_check_expected_data_format_supported (device, QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP, NULL)) {
supported |= MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP;
/* We switch to raw-ip to see if we can do link management with qmi_wwan.
* This switch would not truly be required, but the logic afterwards is robust
* enough to support this, nothing to worry about */
if (qmi_device_set_expected_data_format (device, QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP, NULL) &&
qmi_device_check_link_supported (device, NULL))
supported |= MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN;
if (qmi_device_check_expected_data_format_supported (device, QMI_DEVICE_EXPECTED_DATA_FORMAT_QMAP_PASS_THROUGH, NULL))
supported |= MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET;
}
return supported;
}
/* For any driver, assume raw-ip only */
return MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP;
}
/*****************************************************************************/
#define DEFAULT_DOWNLINK_DATA_AGGREGATION_MAX_SIZE 32768
#define DEFAULT_DOWNLINK_DATA_AGGREGATION_MAX_DATAGRAMS 32
typedef struct {
MMPortQmiKernelDataMode kernel_data_mode;
QmiWdaLinkLayerProtocol wda_llp;
QmiWdaDataAggregationProtocol wda_dap;
} DataFormatCombination;
static const DataFormatCombination data_format_combinations[] = {
{ MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET, QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP, QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAPV5 },
{ MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET, QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP, QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAPV4 },
{ MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET, QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP, QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAP },
{ MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN, QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP, QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAPV5 },
{ MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN, QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP, QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAP },
{ MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP, QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP, QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED },
{ MM_PORT_QMI_KERNEL_DATA_MODE_802_3, QMI_WDA_LINK_LAYER_PROTOCOL_802_3, QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED },
};
typedef enum {
INTERNAL_SETUP_DATA_FORMAT_STEP_FIRST,
INTERNAL_SETUP_DATA_FORMAT_STEP_ALLOCATE_WDA_CLIENT,
INTERNAL_SETUP_DATA_FORMAT_STEP_SUPPORTED_KERNEL_DATA_MODES,
INTERNAL_SETUP_DATA_FORMAT_STEP_RETRY,
INTERNAL_SETUP_DATA_FORMAT_STEP_CURRENT_KERNEL_DATA_MODES,
INTERNAL_SETUP_DATA_FORMAT_STEP_ALLOCATE_DPM_CLIENT,
INTERNAL_SETUP_DATA_FORMAT_STEP_DPM_OPEN,
INTERNAL_SETUP_DATA_FORMAT_STEP_GET_WDA_DATA_FORMAT,
INTERNAL_SETUP_DATA_FORMAT_STEP_QUERY_DONE,
INTERNAL_SETUP_DATA_FORMAT_STEP_CHECK_DATA_FORMAT_COMBINATION,
INTERNAL_SETUP_DATA_FORMAT_STEP_SYNC_WDA_DATA_FORMAT,
INTERNAL_SETUP_DATA_FORMAT_STEP_SETUP_MAIN_MTU,
INTERNAL_SETUP_DATA_FORMAT_STEP_SYNC_KERNEL_DATA_MODE,
INTERNAL_SETUP_DATA_FORMAT_STEP_LAST,
} InternalSetupDataFormatStep;
typedef struct {
QmiDevice *device;
MMPort *data;
MMPortQmiSetupDataFormatAction action;
InternalSetupDataFormatStep step;
gboolean use_endpoint;
gint data_format_combination_i;
/* kernel data modes */
MMPortQmiKernelDataMode kernel_data_modes_current;
MMPortQmiKernelDataMode kernel_data_modes_requested;
MMPortQmiKernelDataMode kernel_data_modes_supported;
/* configured device data format */
QmiClient *wda;
QmiClient *dpm;
QmiWdaLinkLayerProtocol wda_llp_current;
QmiWdaLinkLayerProtocol wda_llp_requested;
QmiWdaDataAggregationProtocol wda_ul_dap_current;
QmiWdaDataAggregationProtocol wda_ul_dap_requested;
QmiWdaDataAggregationProtocol wda_dl_dap_current;
QmiWdaDataAggregationProtocol wda_dl_dap_requested;
guint32 wda_dl_dap_max_datagrams_current;
guint32 wda_dl_dap_max_size_current;
gboolean wda_dap_supported;
} InternalSetupDataFormatContext;
static void
internal_setup_data_format_context_free (InternalSetupDataFormatContext *ctx)
{
if (ctx->wda && ctx->device)
qmi_device_release_client (ctx->device,
ctx->wda,
QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID,
3, NULL, NULL, NULL);
if (ctx->dpm && ctx->device)
qmi_device_release_client (ctx->device,
ctx->dpm,
QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID,
3, NULL, NULL, NULL);
g_clear_object (&ctx->wda);
g_clear_object (&ctx->dpm);
g_clear_object (&ctx->data);
g_clear_object (&ctx->device);
g_slice_free (InternalSetupDataFormatContext, ctx);
}
static gboolean
internal_setup_data_format_finish (MMPortQmi *self,
GAsyncResult *res,
MMPortQmiKernelDataMode *out_kernel_data_modes,
QmiWdaLinkLayerProtocol *out_llp,
QmiWdaDataAggregationProtocol *out_dap,
guint *out_max_multiplexed_links,
GError **error)
{
InternalSetupDataFormatContext *ctx;
if (!g_task_propagate_boolean (G_TASK (res), error))
return FALSE;
ctx = g_task_get_task_data (G_TASK (res));
*out_kernel_data_modes = ctx->kernel_data_modes_current;
*out_llp = ctx->wda_llp_current;
g_assert (ctx->wda_dl_dap_current == ctx->wda_ul_dap_current);
*out_dap = ctx->wda_dl_dap_current;
if (out_max_multiplexed_links) {
if (!ctx->wda_dap_supported) {
*out_max_multiplexed_links = 0;
mm_obj_dbg (self, "wda data aggregation protocol unsupported: no multiplexed bearers allowed");
} else {
/* if multiplex backend may be rmnet, MAX-MIN */
if (ctx->kernel_data_modes_supported & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET) {
*out_max_multiplexed_links = 1 + (QMI_DEVICE_MUX_ID_MAX - QMI_DEVICE_MUX_ID_MIN);
mm_obj_dbg (self, "rmnet link management supported: %u multiplexed bearers allowed",
*out_max_multiplexed_links);
}
/* if multiplex backend may be qmi_wwan, the max preallocated amount :/ */
else if (ctx->kernel_data_modes_supported & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN) {
*out_max_multiplexed_links = DEFAULT_LINK_PREALLOCATED_AMOUNT;
mm_obj_dbg (self, "qmi_wwan link management supported: %u multiplexed bearers allowed",
*out_max_multiplexed_links);
} else {
*out_max_multiplexed_links = 0;
mm_obj_dbg (self, "link management unsupported: no multiplexed bearers allowed");
}
}
}
return TRUE;
}
static void internal_setup_data_format_context_step (GTask *task);
static void
sync_kernel_data_mode (GTask *task)
{
MMPortQmi *self;
InternalSetupDataFormatContext *ctx;
GError *error = NULL;
g_autofree gchar *kernel_data_modes_current_str = NULL;
g_autofree gchar *kernel_data_modes_requested_str = NULL;
QmiDeviceExpectedDataFormat expected_data_format_requested = QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
kernel_data_modes_current_str = mm_port_qmi_kernel_data_mode_build_string_from_mask (ctx->kernel_data_modes_current);
kernel_data_modes_requested_str = mm_port_qmi_kernel_data_mode_build_string_from_mask (ctx->kernel_data_modes_requested);
mm_obj_dbg (self, "Updating kernel expected data format: %s -> %s",
kernel_data_modes_current_str, kernel_data_modes_requested_str);
if (ctx->kernel_data_modes_requested & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET)
expected_data_format_requested = QMI_DEVICE_EXPECTED_DATA_FORMAT_QMAP_PASS_THROUGH;
else if (ctx->kernel_data_modes_requested & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN)
expected_data_format_requested = QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP;
else if (ctx->kernel_data_modes_requested & MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP)
expected_data_format_requested = QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP;
else if (ctx->kernel_data_modes_requested & MM_PORT_QMI_KERNEL_DATA_MODE_802_3)
expected_data_format_requested = QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3;
else
g_assert_not_reached ();
if (!qmi_device_set_expected_data_format (ctx->device, expected_data_format_requested, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* request reload */
ctx->kernel_data_modes_current = MM_PORT_QMI_KERNEL_DATA_MODE_NONE;
/* Go on to next step */
ctx->step++;
internal_setup_data_format_context_step (task);
}
static void
main_mtu_ready (MMPortNet *data,
GAsyncResult *res,
GTask *task)
{
MMPortQmi *self;
InternalSetupDataFormatContext *ctx;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (!mm_port_net_link_setup_finish (data, res, &error)) {
mm_obj_dbg (self, "failed to setup main MTU: %s", error->message);
g_clear_error (&error);
}
/* Go on to next step */
ctx->step++;
internal_setup_data_format_context_step (task);
}
static void
setup_main_mtu (GTask *task)
{
MMPortQmi *self;
InternalSetupDataFormatContext *ctx;
guint mtu = MM_PORT_NET_MTU_DEFAULT;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
/* qmi_wwan multiplexing logic requires main mtu set to the maximum data
* aggregation size */
if (ctx->kernel_data_modes_requested & (MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET | MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN)) {
/* Load current max datagram size supported */
if (MM_PORT_QMI_DAP_IS_SUPPORTED_QMAP (ctx->wda_dl_dap_requested)) {
mtu = ctx->wda_dl_dap_max_size_current;
if ((ctx->kernel_data_modes_requested & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET) && (mtu > RMNET_MAX_PACKET_SIZE)) {
mm_obj_dbg (self, "mtu limited to maximum rmnet packet size");
mtu = RMNET_MAX_PACKET_SIZE;
}
}
/* If no max aggregation size was specified by the modem (e.g. if we requested QMAP
* aggregation protocol but the modem doesn't support it), skip */
if (!mtu) {
mm_obj_dbg (self, "ignoring main mtu setup");
ctx->step++;
internal_setup_data_format_context_step (task);
return;
}
}
/* Main MTU change can only be changed while in 802-3 */
if (!(ctx->kernel_data_modes_current & MM_PORT_QMI_KERNEL_DATA_MODE_802_3)) {
mm_obj_dbg (self, "Updating kernel expected data format to 802-3 temporarily for main mtu setup");
if (!qmi_device_set_expected_data_format (ctx->device, QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3, &error)) {
g_prefix_error (&error, "Failed setting up 802.3 kernel data format before main mtu change: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* the sync kernel data mode step will fix this appropriately */
ctx->kernel_data_modes_current = MM_PORT_QMI_KERNEL_DATA_MODE_802_3;
}
mm_obj_dbg (self, "setting up main mtu: %u bytes", mtu);
mm_port_net_link_setup (MM_PORT_NET (ctx->data),
FALSE,
mtu,
NULL,
(GAsyncReadyCallback) main_mtu_ready,
task);
}
static void
set_data_format_ready (QmiClientWda *client,
GAsyncResult *res,
GTask *task)
{
InternalSetupDataFormatContext *ctx;
g_autoptr(QmiMessageWdaSetDataFormatOutput) output = NULL;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
output = qmi_client_wda_set_data_format_finish (client, res, &error);
if (!output || !qmi_message_wda_set_data_format_output_get_result (output, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* store max aggregation size so that the main MTU logic works */
qmi_message_wda_set_data_format_output_get_downlink_data_aggregation_max_size (output, &ctx->wda_dl_dap_max_size_current, NULL);
/* request reload */
ctx->wda_llp_current = QMI_WDA_LINK_LAYER_PROTOCOL_UNKNOWN;
ctx->wda_ul_dap_current = QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED;
ctx->wda_dl_dap_current = QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED;
/* Go on to next step */
ctx->step++;
internal_setup_data_format_context_step (task);
}
static void
sync_wda_data_format (GTask *task)
{
MMPortQmi *self;
InternalSetupDataFormatContext *ctx;
g_autoptr(QmiMessageWdaSetDataFormatInput) input = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (ctx->wda_llp_current != ctx->wda_llp_requested)
mm_obj_dbg (self, "Updating device link layer protocol: %s -> %s",
qmi_wda_link_layer_protocol_get_string (ctx->wda_llp_current),
qmi_wda_link_layer_protocol_get_string (ctx->wda_llp_requested));
if (ctx->wda_ul_dap_current != ctx->wda_ul_dap_requested)
mm_obj_dbg (self, "Updating device uplink data aggregation protocol: %s -> %s",
qmi_wda_data_aggregation_protocol_get_string (ctx->wda_ul_dap_current),
qmi_wda_data_aggregation_protocol_get_string (ctx->wda_ul_dap_requested));
if (ctx->wda_dl_dap_current != ctx->wda_dl_dap_requested)
mm_obj_dbg (self, "Updating device downlink data aggregation protocol: %s -> %s",
qmi_wda_data_aggregation_protocol_get_string (ctx->wda_dl_dap_current),
qmi_wda_data_aggregation_protocol_get_string (ctx->wda_dl_dap_requested));
input = qmi_message_wda_set_data_format_input_new ();
qmi_message_wda_set_data_format_input_set_link_layer_protocol (input, ctx->wda_llp_requested, NULL);
qmi_message_wda_set_data_format_input_set_uplink_data_aggregation_protocol (input, ctx->wda_ul_dap_requested, NULL);
qmi_message_wda_set_data_format_input_set_downlink_data_aggregation_protocol (input, ctx->wda_dl_dap_requested, NULL);
if (ctx->wda_dl_dap_requested != QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED) {
qmi_message_wda_set_data_format_input_set_downlink_data_aggregation_max_size (input, DEFAULT_DOWNLINK_DATA_AGGREGATION_MAX_SIZE, NULL);
qmi_message_wda_set_data_format_input_set_downlink_data_aggregation_max_datagrams (input, DEFAULT_DOWNLINK_DATA_AGGREGATION_MAX_DATAGRAMS, NULL);
}
if (ctx->use_endpoint)
qmi_message_wda_set_data_format_input_set_endpoint_info (input, self->priv->endpoint_type, self->priv->endpoint_interface_number, NULL);
qmi_client_wda_set_data_format (QMI_CLIENT_WDA (ctx->wda),
input,
10,
g_task_get_cancellable (task),
(GAsyncReadyCallback) set_data_format_ready,
task);
}
static gboolean
setup_data_format_completed (GTask *task)
{
MMPortQmi *self;
InternalSetupDataFormatContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
/* if aggregation enabled we require link management supported; this covers the
* case of old qmi_wwan drivers where add_mux/del_mux wasn't available yet */
if ((MM_PORT_QMI_DAP_IS_SUPPORTED_QMAP (ctx->wda_dl_dap_requested)) &&
(!(ctx->kernel_data_modes_current & (MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET | MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN)))) {
mm_obj_dbg (self, "cannot enable data aggregation: link management unsupported");
return FALSE;
}
/* check whether the current and requested ones are the same */
if ((ctx->kernel_data_modes_current & ctx->kernel_data_modes_requested) &&
(ctx->wda_llp_current == ctx->wda_llp_requested) &&
(ctx->wda_ul_dap_current == ctx->wda_ul_dap_requested) &&
(ctx->wda_dl_dap_current == ctx->wda_dl_dap_requested)) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return TRUE;
}
return FALSE;
}
static void
check_data_format_combination (GTask *task)
{
MMPortQmi *self;
InternalSetupDataFormatContext *ctx;
gboolean first_iteration;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
first_iteration = (ctx->data_format_combination_i < 0);
if (!first_iteration && setup_data_format_completed (task))
return;
/* go on to the next supported combination */
for (++ctx->data_format_combination_i;
ctx->data_format_combination_i <= (gint)G_N_ELEMENTS (data_format_combinations);
ctx->data_format_combination_i++) {
const DataFormatCombination *combination;
g_autofree gchar *kernel_data_mode_str = NULL;
combination = &data_format_combinations[ctx->data_format_combination_i];
if (!(ctx->kernel_data_modes_supported & combination->kernel_data_mode))
continue;
if ((MM_PORT_QMI_DAP_IS_SUPPORTED_QMAP (combination->wda_dap)) &&
((!ctx->wda_dap_supported) ||
(ctx->action != MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_SET_MULTIPLEX)))
continue;
kernel_data_mode_str = mm_port_qmi_kernel_data_mode_build_string_from_mask (combination->kernel_data_mode);
mm_obj_dbg (self, "selected data format setup:");
mm_obj_dbg (self, " kernel data mode: %s", kernel_data_mode_str);
mm_obj_dbg (self, " link layer protocol: %s", qmi_wda_link_layer_protocol_get_string (combination->wda_llp));
mm_obj_dbg (self, " aggregation protocol: %s", qmi_wda_data_aggregation_protocol_get_string (combination->wda_dap));
ctx->kernel_data_modes_requested = combination->kernel_data_mode;
ctx->wda_llp_requested = combination->wda_llp;
ctx->wda_ul_dap_requested = combination->wda_dap;
ctx->wda_dl_dap_requested = combination->wda_dap;
if (first_iteration && setup_data_format_completed (task))
return;
/* Go on to next step */
ctx->step++;
internal_setup_data_format_context_step (task);
return;
}
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"No more data format combinations supported");
g_object_unref (task);
}
static gboolean
process_data_format_output (MMPortQmi *self,
QmiMessageWdaGetDataFormatOutput *output,
InternalSetupDataFormatContext *ctx,
GError **error)
{
/* Let's consider the lack o the LLP TLV a hard error; it really would be strange
* a module supporting WDA Get Data Format but not containing the LLP info */
if (!qmi_message_wda_get_data_format_output_get_link_layer_protocol (output, &ctx->wda_llp_current, error))
return FALSE;
/* QMAP assumed supported if both uplink and downlink TLVs are given */
ctx->wda_dap_supported = TRUE;
if (!qmi_message_wda_get_data_format_output_get_uplink_data_aggregation_protocol (output, &ctx->wda_ul_dap_current, NULL))
ctx->wda_dap_supported = FALSE;
if (!qmi_message_wda_get_data_format_output_get_downlink_data_aggregation_protocol (output, &ctx->wda_dl_dap_current, NULL))
ctx->wda_dap_supported = FALSE;
ctx->wda_dl_dap_max_size_current = 0;
ctx->wda_dl_dap_max_datagrams_current = 0;
if (ctx->wda_dl_dap_current != QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED) {
qmi_message_wda_get_data_format_output_get_downlink_data_aggregation_max_size (output, &ctx->wda_dl_dap_max_size_current, NULL);
qmi_message_wda_get_data_format_output_get_downlink_data_aggregation_max_datagrams (output, &ctx->wda_dl_dap_max_datagrams_current, NULL);
}
return TRUE;
}
static void
get_data_format_ready (QmiClientWda *client,
GAsyncResult *res,
GTask *task)
{
MMPortQmi *self;
InternalSetupDataFormatContext *ctx;
g_autoptr(QmiMessageWdaGetDataFormatOutput) output = NULL;
g_autoptr(GError) error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
output = qmi_client_wda_get_data_format_finish (client, res, &error);
if (!output ||
!qmi_message_wda_get_data_format_output_get_result (output, &error) ||
!process_data_format_output (self, output, ctx, &error)) {
/* A 'missing argument' error when querying data format is seen in new
* devices like the Quectel RM500Q, requiring the 'endpoint info' TLV.
* When this happens, retry the step with the missing TLV.
*
* Note that this is not an additional step, we're still in the
* GET_WDA_DATA_FORMAT step.
*/
if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_MISSING_ARGUMENT) &&
(self->priv->endpoint_type != QMI_DATA_ENDPOINT_TYPE_UNDEFINED)) {
/* retry same step with endpoint info */
ctx->use_endpoint = TRUE;
internal_setup_data_format_context_step (task);
return;
}
/* otherwise, fatal */
g_task_return_error (task, g_steal_pointer (&error));
g_object_unref (task);
return;
}
/* Go on to next step */
ctx->step++;
internal_setup_data_format_context_step (task);
}
static void
dpm_open_port_ready (QmiClientDpm *client,
GAsyncResult *res,
GTask *task)
{
g_autoptr(QmiMessageDpmOpenPortOutput) output = NULL;
InternalSetupDataFormatContext *ctx;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
output = qmi_client_dpm_open_port_finish (client, res, &error);
if (!output ||
!qmi_message_dpm_open_port_output_get_result (output, &error)) {
g_task_return_error (task, g_steal_pointer (&error));
g_object_unref (task);
return;
}
/* Go on to next step */
ctx->step++;
internal_setup_data_format_context_step (task);
}
static void
dpm_open_port (GTask *task)
{
MMPortQmi *self;
InternalSetupDataFormatContext *ctx;
QmiMessageDpmOpenPortInputHardwareDataPortsElement hw_port;
g_autoptr(GArray) hw_data_ports = NULL;
g_autoptr(QmiMessageDpmOpenPortInput) input = NULL;
g_autofree gchar *tx_sysfs_path = NULL;
g_autofree gchar *rx_sysfs_path = NULL;
g_autofree gchar *tx_sysfs_str = NULL;
g_autofree gchar *rx_sysfs_str = NULL;
guint tx_id = 0;
guint rx_id = 0;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
tx_sysfs_path = g_build_filename (self->priv->net_sysfs_path, "device", "modem", "tx_endpoint_id", NULL);
rx_sysfs_path = g_build_filename (self->priv->net_sysfs_path, "device", "modem", "rx_endpoint_id", NULL);
if (g_file_get_contents (rx_sysfs_path, &rx_sysfs_str, NULL, NULL) &&
g_file_get_contents (tx_sysfs_path, &tx_sysfs_str, NULL, NULL)) {
if (rx_sysfs_str && tx_sysfs_str) {
mm_get_uint_from_str (rx_sysfs_str, &rx_id);
mm_get_uint_from_str (tx_sysfs_str, &tx_id);
}
}
if (tx_id == 0 || rx_id == 0) {
mm_obj_warn (self, "Unable to read TX and RX endpoint IDs from sysfs. skipping automatic DPM port opening.");
/* Go on to next step */
ctx->step++;
internal_setup_data_format_context_step (task);
return;
}
mm_obj_dbg (self, "Opening DPM port with TX ID: %u and RX ID: %u", tx_id, rx_id);
/* The modem TX endpoint connects with the IPA's RX port and the modem RX endpoint connects with the IPA's TX port. */
hw_port.rx_endpoint_number = tx_id;
hw_port.tx_endpoint_number = rx_id;
hw_port.endpoint_type = self->priv->endpoint_type;
hw_port.interface_number = self->priv->endpoint_interface_number;
hw_data_ports = g_array_new (FALSE, FALSE, sizeof (QmiMessageDpmOpenPortInputHardwareDataPortsElement));
g_array_append_val (hw_data_ports, hw_port);
input = qmi_message_dpm_open_port_input_new ();
qmi_message_dpm_open_port_input_set_hardware_data_ports (input,
hw_data_ports,
NULL);
qmi_client_dpm_open_port (QMI_CLIENT_DPM (ctx->dpm),
input,
10,
g_task_get_cancellable (task),
(GAsyncReadyCallback) dpm_open_port_ready,
task);
}
static void
allocate_client_dpm_ready (QmiDevice *device,
GAsyncResult *res,
GTask *task)
{
InternalSetupDataFormatContext *ctx;
GError *error = NULL;
ctx = g_task_get_task_data (task);
ctx->dpm = qmi_device_allocate_client_finish (device, res, &error);
if (!ctx->dpm) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Go on to next step */
ctx->step++;
internal_setup_data_format_context_step (task);
}
static void
allocate_client_wda_ready (QmiDevice *device,
GAsyncResult *res,
GTask *task)
{
InternalSetupDataFormatContext *ctx;
GError *error = NULL;
ctx = g_task_get_task_data (task);
ctx->wda = qmi_device_allocate_client_finish (device, res, &error);
if (!ctx->wda) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Go on to next step */
ctx->step++;
internal_setup_data_format_context_step (task);
}
static void
internal_setup_data_format_context_step (GTask *task)
{
MMPortQmi *self;
InternalSetupDataFormatContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case INTERNAL_SETUP_DATA_FORMAT_STEP_FIRST:
ctx->step++;
/* Fall through */
case INTERNAL_SETUP_DATA_FORMAT_STEP_ALLOCATE_WDA_CLIENT:
/* Allocate new WDA client, only on first loop iteration */
g_assert (!ctx->wda);
qmi_device_allocate_client (ctx->device,
QMI_SERVICE_WDA,
QMI_CID_NONE,
10,
g_task_get_cancellable (task),
(GAsyncReadyCallback) allocate_client_wda_ready,
task);
return;
case INTERNAL_SETUP_DATA_FORMAT_STEP_SUPPORTED_KERNEL_DATA_MODES:
/* Load kernel data format capabilities, only on first loop iteration */
ctx->kernel_data_modes_supported = load_supported_kernel_data_modes (self, ctx->device);
ctx->step++;
/* Fall through */
case INTERNAL_SETUP_DATA_FORMAT_STEP_RETRY:
ctx->step++;
/* Fall through */
case INTERNAL_SETUP_DATA_FORMAT_STEP_CURRENT_KERNEL_DATA_MODES:
/* Only reload kernel data modes if it was updated or on first loop */
if (ctx->kernel_data_modes_current == MM_PORT_QMI_KERNEL_DATA_MODE_NONE)
ctx->kernel_data_modes_current = load_current_kernel_data_modes (self, ctx->device);
ctx->step++;
/* Fall through */
case INTERNAL_SETUP_DATA_FORMAT_STEP_ALLOCATE_DPM_CLIENT:
/* Only allocate new DPM client on first loop */
if ((g_strcmp0 (self->priv->net_driver, "ipa") == 0) && (ctx->data_format_combination_i < 0)) {
g_assert (!ctx->dpm);
qmi_device_allocate_client (ctx->device,
QMI_SERVICE_DPM,
QMI_CID_NONE,
10,
g_task_get_cancellable (task),
(GAsyncReadyCallback) allocate_client_dpm_ready,
task);
return;
}
ctx->step++;
/* Fall through */
case INTERNAL_SETUP_DATA_FORMAT_STEP_DPM_OPEN:
/* Only for IPA based setups, open dpm port */
if (g_strcmp0 (self->priv->net_driver, "ipa") == 0) {
dpm_open_port (task);
return;
}
ctx->step++;
/* Fall through */
case INTERNAL_SETUP_DATA_FORMAT_STEP_GET_WDA_DATA_FORMAT:
/* Only reload WDA data format if it was updated or on first loop */
if (ctx->wda_llp_current == QMI_WDA_LINK_LAYER_PROTOCOL_UNKNOWN) {
g_autoptr(QmiMessageWdaGetDataFormatInput) input = NULL;
if (ctx->use_endpoint) {
input = qmi_message_wda_get_data_format_input_new ();
qmi_message_wda_get_data_format_input_set_endpoint_info (input,
self->priv->endpoint_type,
self->priv->endpoint_interface_number,
NULL);
}
qmi_client_wda_get_data_format (QMI_CLIENT_WDA (ctx->wda),
input,
10,
g_task_get_cancellable (task),
(GAsyncReadyCallback) get_data_format_ready,
task);
return;
}
ctx->step++;
/* Fall through */
case INTERNAL_SETUP_DATA_FORMAT_STEP_QUERY_DONE: {
g_autofree gchar *kernel_data_modes_str = NULL;
kernel_data_modes_str = mm_port_qmi_kernel_data_mode_build_string_from_mask (ctx->kernel_data_modes_current);
mm_obj_dbg (self, "current data format setup:");
mm_obj_dbg (self, " kernel data modes: %s", kernel_data_modes_str);
mm_obj_dbg (self, " link layer protocol: %s", qmi_wda_link_layer_protocol_get_string (ctx->wda_llp_current));
mm_obj_dbg (self, " aggregation protocol ul: %s", qmi_wda_data_aggregation_protocol_get_string (ctx->wda_ul_dap_current));
mm_obj_dbg (self, " aggregation protocol dl: %s", qmi_wda_data_aggregation_protocol_get_string (ctx->wda_dl_dap_current));
if (ctx->action == MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_QUERY) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
ctx->step++;
} /* Fall through */
case INTERNAL_SETUP_DATA_FORMAT_STEP_CHECK_DATA_FORMAT_COMBINATION:
/* This step is the one that may complete the async operation
* successfully */
check_data_format_combination (task);
return;
case INTERNAL_SETUP_DATA_FORMAT_STEP_SYNC_WDA_DATA_FORMAT:
if ((ctx->wda_llp_current != ctx->wda_llp_requested) ||
(ctx->wda_ul_dap_current != ctx->wda_ul_dap_requested) ||
(ctx->wda_dl_dap_current != ctx->wda_dl_dap_requested)) {
sync_wda_data_format (task);
return;
}
ctx->step++;
/* Fall through */
case INTERNAL_SETUP_DATA_FORMAT_STEP_SETUP_MAIN_MTU:
/* qmi_wwan add_mux/del_mux based logic requires main MTU set to the maximum
* data aggregation size reported by the modem. */
if (g_strcmp0 (self->priv->net_driver, "qmi_wwan") == 0) {
setup_main_mtu (task);
return;
}
ctx->step++;
/* Fall through */
case INTERNAL_SETUP_DATA_FORMAT_STEP_SYNC_KERNEL_DATA_MODE:
if (!(ctx->kernel_data_modes_current & ctx->kernel_data_modes_requested)) {
sync_kernel_data_mode (task);
return;
}
ctx->step++;
/* Fall through */
case INTERNAL_SETUP_DATA_FORMAT_STEP_LAST:
/* jump back to first step to reload current state after
* the updates have been done */
ctx->step = INTERNAL_SETUP_DATA_FORMAT_STEP_RETRY;
internal_setup_data_format_context_step (task);
return;
default:
g_assert_not_reached ();
}
}
static void
internal_setup_data_format (MMPortQmi *self,
QmiDevice *device,
MMPort *data, /* may be NULL in query */
MMPortQmiSetupDataFormatAction action,
GAsyncReadyCallback callback,
gpointer user_data)
{
InternalSetupDataFormatContext *ctx;
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
if (!device) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
"Port must be open to setup data format");
g_object_unref (task);
return;
}
if (self->priv->wda_unsupported) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
"Setting up data format is not supported");
g_object_unref (task);
return;
}
ctx = g_slice_new0 (InternalSetupDataFormatContext);
ctx->device = g_object_ref (device);
ctx->data = data ? g_object_ref (data) : NULL;
ctx->action = action;
ctx->step = INTERNAL_SETUP_DATA_FORMAT_STEP_FIRST;
ctx->data_format_combination_i = -1;
ctx->kernel_data_modes_current = MM_PORT_QMI_KERNEL_DATA_MODE_NONE;
ctx->kernel_data_modes_requested = MM_PORT_QMI_KERNEL_DATA_MODE_NONE;
ctx->kernel_data_modes_supported = MM_PORT_QMI_KERNEL_DATA_MODE_NONE;
ctx->wda_llp_current = QMI_WDA_LINK_LAYER_PROTOCOL_UNKNOWN;
ctx->wda_llp_requested = QMI_WDA_LINK_LAYER_PROTOCOL_UNKNOWN;
ctx->wda_ul_dap_current = QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED;
ctx->wda_ul_dap_requested = QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED;
ctx->wda_dl_dap_current = QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED;
ctx->wda_dl_dap_requested = QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED;
if (mm_port_get_subsys (MM_PORT (self)) == MM_PORT_SUBSYS_QRTR)
ctx->use_endpoint = TRUE;
g_task_set_task_data (task, ctx, (GDestroyNotify) internal_setup_data_format_context_free);
internal_setup_data_format_context_step (task);
}
/*****************************************************************************/
typedef struct {
MMPort *data;
QmiDevice *device;
MMPortQmiSetupDataFormatAction action;
} SetupDataFormatContext;
static void
setup_data_format_context_free (SetupDataFormatContext *ctx)
{
g_clear_object (&ctx->device);
g_clear_object (&ctx->data);
g_slice_free (SetupDataFormatContext, ctx);
}
gboolean
mm_port_qmi_setup_data_format_finish (MMPortQmi *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
internal_setup_data_format_ready (MMPortQmi *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!internal_setup_data_format_finish (self,
res,
&self->priv->kernel_data_modes,
&self->priv->llp,
&self->priv->dap,
NULL, /* not expected to update */
&error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
setup_data_format_internal_reset_ready (MMPortQmi *self,
GAsyncResult *res,
GTask *task)
{
SetupDataFormatContext *ctx;
GError *error = NULL;
ctx = g_task_get_task_data (task);
if (!internal_reset_finish (self, res, &error)) {
g_prefix_error (&error, "Couldn't reset interface before setting up data format: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* call internal method with the already open QmiDevice */
internal_setup_data_format (self,
ctx->device,
ctx->data,
ctx->action,
(GAsyncReadyCallback)internal_setup_data_format_ready,
task);
}
static guint
count_links_setup (MMPortQmi *self,
MMPort *data)
{
if (self->priv->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET) {
g_autoptr(GPtrArray) links = NULL;
g_autoptr(GError) error = NULL;
if (!qmi_device_list_links (self->priv->qmi_device,
mm_port_get_device (data),
&links,
&error)) {
mm_obj_warn (self, "couldn't list links in %s: %s",
mm_port_get_device (data),
error->message);
return 0;
}
return links->len;
}
if (self->priv->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN)
return count_preallocated_links_setup (self);
return 0;
}
void
mm_port_qmi_setup_data_format (MMPortQmi *self,
MMPort *data,
MMPortQmiSetupDataFormatAction action,
GAsyncReadyCallback callback,
gpointer user_data)
{
SetupDataFormatContext *ctx;
GTask *task;
/* External calls are never query */
g_assert (action != MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_QUERY);
g_assert (MM_IS_PORT (data));
task = g_task_new (self, NULL, callback, user_data);
if (!self->priv->qmi_device) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Port not open");
g_object_unref (task);
return;
}
if (self->priv->wda_unsupported) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Setting up data format is unsupported");
g_object_unref (task);
return;
}
if ((action == MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_SET_MULTIPLEX) &&
(self->priv->kernel_data_modes & (MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET | MM_PORT_QMI_KERNEL_DATA_MODE_MUX_QMIWWAN)) &&
MM_PORT_QMI_DAP_IS_SUPPORTED_QMAP (self->priv->dap)) {
mm_obj_dbg (self, "multiplex support already available when setting up data format");
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
if ((action == MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_SET_DEFAULT) &&
(((self->priv->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP) && (self->priv->llp == QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP)) ||
((self->priv->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_802_3) && (self->priv->llp == QMI_WDA_LINK_LAYER_PROTOCOL_802_3))) &&
!MM_PORT_QMI_DAP_IS_SUPPORTED_QMAP (self->priv->dap)) {
mm_obj_dbg (self, "multiplex support already disabled when setting up data format");
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
/* support switching from multiplex to non-multiplex, but only if there are no active
* links allocated */
if ((action == MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_SET_DEFAULT) &&
MM_PORT_QMI_DAP_IS_SUPPORTED_QMAP (self->priv->dap)) {
guint n_links_setup;
n_links_setup = count_links_setup (self, data);
if (n_links_setup > 0) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
"Cannot switch to non-multiplex setup: %u links already setup exist",
n_links_setup);
g_object_unref (task);
return;
}
}
ctx = g_slice_new0 (SetupDataFormatContext);
ctx->data = g_object_ref (data);
ctx->device = g_object_ref (self->priv->qmi_device);
ctx->action = action;
g_task_set_task_data (task, ctx, (GDestroyNotify)setup_data_format_context_free);
internal_reset (self,
data,
ctx->device,
(GAsyncReadyCallback)setup_data_format_internal_reset_ready,
task);
}
/*****************************************************************************/
typedef enum {
PORT_OPEN_STEP_FIRST,
PORT_OPEN_STEP_CHECK_OPENING,
PORT_OPEN_STEP_CHECK_ALREADY_OPEN,
PORT_OPEN_STEP_DEVICE_NEW,
PORT_OPEN_STEP_OPEN_WITHOUT_DATA_FORMAT,
PORT_OPEN_STEP_SETUP_DATA_FORMAT,
PORT_OPEN_STEP_CLOSE_BEFORE_OPEN_WITH_DATA_FORMAT,
PORT_OPEN_STEP_OPEN_WITH_DATA_FORMAT,
PORT_OPEN_STEP_LAST
} PortOpenStep;
typedef struct {
QmiDevice *device;
GError *error;
PortOpenStep step;
gboolean set_data_format;
MMPortQmiKernelDataMode kernel_data_modes;
gboolean ctl_raw_ip_unsupported;
} PortOpenContext;
static void
port_open_context_free (PortOpenContext *ctx)
{
g_assert (!ctx->error);
g_clear_object (&ctx->device);
g_slice_free (PortOpenContext, ctx);
}
gboolean
mm_port_qmi_open_finish (MMPortQmi *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void port_open_step (GTask *task);
static void
port_open_complete_with_error (GTask *task)
{
MMPortQmi *self;
PortOpenContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
g_assert (ctx->error);
self->priv->in_progress = FALSE;
g_task_return_error (task, g_steal_pointer (&ctx->error));
g_object_unref (task);
}
static void
qmi_device_close_on_error_ready (QmiDevice *qmi_device,
GAsyncResult *res,
GTask *task)
{
MMPortQmi *self;
g_autoptr(GError) error = NULL;
self = g_task_get_source_object (task);
if (!qmi_device_close_finish (qmi_device, res, &error))
mm_obj_warn (self, "Couldn't close QMI device after failed open sequence: %s", error->message);
port_open_complete_with_error (task);
}
static void
qmi_device_open_second_ready (QmiDevice *qmi_device,
GAsyncResult *res,
GTask *task)
{
MMPortQmi *self;
PortOpenContext *ctx;
g_autoptr(GError) error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (!qmi_device_open_finish (qmi_device, res, &error)) {
/* Not all devices support raw-ip, which is the first thing we try
* by default. Detect this case, and retry with 802.3 if so. */
if ((g_strcmp0 (self->priv->net_driver, "qmi_wwan") == 0) &&
g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_DATA_FORMAT) &&
(ctx->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP)) {
/* switch to 802.3 right away, so that the logic can successfully go on after that */
qmi_device_set_expected_data_format (qmi_device, QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3, NULL);
ctx->ctl_raw_ip_unsupported = TRUE;
port_open_step (task);
return;
}
/* Otherwise, fatal */
ctx->error = g_steal_pointer (&error);
} else {
/* If the open with CTL data format is sucessful, update all settings
* that we would have received with the internal setup data format
* process */
self->priv->kernel_data_modes = ctx->kernel_data_modes;
if (ctx->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP)
self->priv->llp = QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP;
else if (ctx->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_802_3)
self->priv->llp = QMI_WDA_LINK_LAYER_PROTOCOL_802_3;
else
g_assert_not_reached ();
self->priv->dap = QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED;
self->priv->max_multiplexed_links = 0;
}
/* In both error and success, we go to last step */
ctx->step++;
port_open_step (task);
}
static void
qmi_device_close_to_reopen_ready (QmiDevice *qmi_device,
GAsyncResult *res,
GTask *task)
{
MMPortQmi *self;
PortOpenContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (!qmi_device_close_finish (qmi_device, res, &ctx->error)) {
mm_obj_warn (self, "Couldn't close QMI device to reopen it");
ctx->step = PORT_OPEN_STEP_LAST;
} else
ctx->step++;
port_open_step (task);
}
static void
open_internal_setup_data_format_ready (MMPortQmi *self,
GAsyncResult *res,
GTask *task)
{
PortOpenContext *ctx;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
if (!internal_setup_data_format_finish (self,
res,
&self->priv->kernel_data_modes,
&self->priv->llp,
&self->priv->dap,
&self->priv->max_multiplexed_links,
&error)) {
/* Continue with fallback to LLP requested via CTL */
mm_obj_warn (self, "Couldn't setup data format: %s", error->message);
self->priv->wda_unsupported = TRUE;
ctx->step++;
} else {
/* on success, we're done */
ctx->step = PORT_OPEN_STEP_LAST;
}
port_open_step (task);
}
static void
qmi_device_open_first_ready (QmiDevice *qmi_device,
GAsyncResult *res,
GTask *task)
{
PortOpenContext *ctx;
ctx = g_task_get_task_data (task);
if (!qmi_device_open_finish (qmi_device, res, &ctx->error))
/* Error opening the device */
ctx->step = PORT_OPEN_STEP_LAST;
else if (!ctx->set_data_format)
/* If not setting data format, we're done */
ctx->step = PORT_OPEN_STEP_LAST;
else
/* Go on to next step */
ctx->step++;
port_open_step (task);
}
static void
qmi_device_new_ready (GObject *unused,
GAsyncResult *res,
GTask *task)
{
PortOpenContext *ctx;
ctx = g_task_get_task_data (task);
/* Store the device in the context until the operation is fully done,
* so that we return IN_PROGRESS errors until we finish this async
* operation. */
ctx->device = qmi_device_new_finish (res, &ctx->error);
if (!ctx->device)
/* Error creating the device */
ctx->step = PORT_OPEN_STEP_LAST;
else
/* Go on to next step */
ctx->step++;
port_open_step (task);
}
static void
port_open_step (GTask *task)
{
MMPortQmi *self;
PortOpenContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case PORT_OPEN_STEP_FIRST:
mm_obj_dbg (self, "Opening QMI device...");
ctx->step++;
/* Fall through */
case PORT_OPEN_STEP_CHECK_OPENING:
mm_obj_dbg (self, "Checking if QMI device already opening...");
if (self->priv->in_progress) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_IN_PROGRESS,
"QMI device open/close operation in progress");
g_object_unref (task);
return;
}
ctx->step++;
/* Fall through */
case PORT_OPEN_STEP_CHECK_ALREADY_OPEN:
mm_obj_dbg (self, "Checking if QMI device already open...");
if (self->priv->qmi_device) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
ctx->step++;
/* Fall through */
case PORT_OPEN_STEP_DEVICE_NEW:
/* We flag in this point that we're opening. From now on, if we stop
* for whatever reason, we should clear this flag. We do this by ensuring
* that all callbacks go through the LAST step for completing. */
self->priv->in_progress = TRUE;
#if defined WITH_QRTR
if (self->priv->node) {
mm_obj_dbg (self, "Creating QMI device from QRTR node...");
qmi_device_new_from_node (self->priv->node,
g_task_get_cancellable (task),
(GAsyncReadyCallback) qmi_device_new_ready,
task);
return;
}
#endif
{
g_autoptr(GFile) file = NULL;
g_autofree gchar *fullpath = NULL;
fullpath = g_strdup_printf ("/dev/%s", mm_port_get_device (MM_PORT (self)));
file = g_file_new_for_path (fullpath);
mm_obj_dbg (self, "Creating QMI device...");
qmi_device_new (file,
g_task_get_cancellable (task),
(GAsyncReadyCallback) qmi_device_new_ready,
task);
return;
}
case PORT_OPEN_STEP_OPEN_WITHOUT_DATA_FORMAT:
if (!self->priv->wda_unsupported || !ctx->set_data_format) {
QmiDeviceOpenFlags open_flags;
g_autofree gchar *open_flags_str = NULL;
/* Now open the QMI device without any data format CTL flag */
open_flags = (QMI_DEVICE_OPEN_FLAGS_VERSION_INFO | QMI_DEVICE_OPEN_FLAGS_PROXY);
open_flags_str = qmi_device_open_flags_build_string_from_mask (open_flags);
mm_obj_dbg (self, "Opening device with flags: %s...", open_flags_str);
qmi_device_open (ctx->device,
open_flags,
45,
g_task_get_cancellable (task),
(GAsyncReadyCallback) qmi_device_open_first_ready,
task);
return;
}
ctx->step++;
/* Fall through */
case PORT_OPEN_STEP_SETUP_DATA_FORMAT:
if (qmi_device_is_open (ctx->device)) {
internal_setup_data_format (self,
ctx->device,
NULL,
MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_QUERY,
(GAsyncReadyCallback) open_internal_setup_data_format_ready,
task);
return;
}
ctx->step++;
/* Fall through */
case PORT_OPEN_STEP_CLOSE_BEFORE_OPEN_WITH_DATA_FORMAT:
/* This fallback only applies when WDA unsupported */
if (qmi_device_is_open (ctx->device)) {
mm_obj_dbg (self, "Closing device to reopen it right away...");
qmi_device_close_async (ctx->device,
5,
g_task_get_cancellable (task),
(GAsyncReadyCallback) qmi_device_close_to_reopen_ready,
task);
return;
}
ctx->step++;
/* Fall through */
case PORT_OPEN_STEP_OPEN_WITH_DATA_FORMAT: {
QmiDeviceOpenFlags open_flags;
g_autofree gchar *open_flags_str = NULL;
/* Common open flags */
open_flags = (QMI_DEVICE_OPEN_FLAGS_VERSION_INFO |
QMI_DEVICE_OPEN_FLAGS_PROXY |
QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER);
ctx->kernel_data_modes = load_current_kernel_data_modes (self, ctx->device);
/* Skip trying raw-ip if we already tried and it failed */
if (ctx->ctl_raw_ip_unsupported)
ctx->kernel_data_modes &= ~MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP;
/* Need to reopen setting 802.3/raw-ip using CTL */
if (ctx->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP)
open_flags |= QMI_DEVICE_OPEN_FLAGS_NET_RAW_IP;
else if (ctx->kernel_data_modes & MM_PORT_QMI_KERNEL_DATA_MODE_802_3)
open_flags |= QMI_DEVICE_OPEN_FLAGS_NET_802_3;
else {
/* Set error and jump to last step, so that we cleanly close the device
* in case we need to reopen it right away */
ctx->error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Unexpected kernel data mode: cannot setup using CTL");
ctx->step = PORT_OPEN_STEP_LAST;
port_open_step (task);
return;
}
open_flags_str = qmi_device_open_flags_build_string_from_mask (open_flags);
mm_obj_dbg (self, "Reopening device with flags: %s...", open_flags_str);
qmi_device_open (ctx->device,
open_flags,
10,
g_task_get_cancellable (task),
(GAsyncReadyCallback) qmi_device_open_second_ready,
task);
return;
}
case PORT_OPEN_STEP_LAST:
if (ctx->error) {
mm_obj_dbg (self, "QMI port open operation failed: %s", ctx->error->message);
if (ctx->device) {
qmi_device_close_async (ctx->device,
5,
NULL,
(GAsyncReadyCallback) qmi_device_close_on_error_ready,
task);
return;
}
port_open_complete_with_error (task);
return;
}
mm_obj_dbg (self, "QMI port open operation finished successfully");
/* Store device in private info */
g_assert (ctx->device);
g_assert (!self->priv->qmi_device);
self->priv->qmi_device = g_object_ref (ctx->device);
setup_monitoring (self, ctx->device);
self->priv->in_progress = FALSE;
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
default:
g_assert_not_reached ();
}
}
void
mm_port_qmi_open (MMPortQmi *self,
gboolean set_data_format,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
PortOpenContext *ctx;
GTask *task;
ctx = g_slice_new0 (PortOpenContext);
ctx->step = PORT_OPEN_STEP_FIRST;
ctx->set_data_format = set_data_format;
ctx->kernel_data_modes = MM_PORT_QMI_KERNEL_DATA_MODE_NONE;
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify)port_open_context_free);
port_open_step (task);
}
/*****************************************************************************/
gboolean
mm_port_qmi_is_open (MMPortQmi *self)
{
g_return_val_if_fail (MM_IS_PORT_QMI (self), FALSE);
return !!self->priv->qmi_device;
}
/*****************************************************************************/
void
mm_port_qmi_set_net_driver (MMPortQmi *self,
const gchar *net_driver)
{
g_assert (MM_IS_PORT_QMI (self));
g_assert (!self->priv->net_driver);
self->priv->net_driver = g_strdup (net_driver);
initialize_endpoint_info (self);
}
/*****************************************************************************/
void
mm_port_qmi_set_net_sysfs_path (MMPortQmi *self,
const gchar *net_sysfs_path)
{
g_assert (MM_IS_PORT_QMI (self));
g_assert (!self->priv->net_sysfs_path);
self->priv->net_sysfs_path = g_strdup (net_sysfs_path);
}
/*****************************************************************************/
typedef struct {
QmiDevice *qmi_device;
} PortQmiCloseContext;
static void
port_qmi_close_context_free (PortQmiCloseContext *ctx)
{
g_clear_object (&ctx->qmi_device);
g_slice_free (PortQmiCloseContext, ctx);
}
gboolean
mm_port_qmi_close_finish (MMPortQmi *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
qmi_device_close_ready (QmiDevice *qmi_device,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
MMPortQmi *self;
self = g_task_get_source_object (task);
g_assert (!self->priv->qmi_device);
self->priv->in_progress = FALSE;
if (!qmi_device_close_finish (qmi_device, res, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
void
mm_port_qmi_close (MMPortQmi *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
PortQmiCloseContext *ctx;
GTask *task;
GList *l;
g_return_if_fail (MM_IS_PORT_QMI (self));
task = g_task_new (self, NULL, callback, user_data);
if (self->priv->in_progress) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_IN_PROGRESS,
"QMI device open/close operation in progress");
g_object_unref (task);
return;
}
if (!self->priv->qmi_device) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
self->priv->in_progress = TRUE;
/* Store device to close in the context */
ctx = g_slice_new0 (PortQmiCloseContext);
ctx->qmi_device = g_steal_pointer (&self->priv->qmi_device);
g_task_set_task_data (task, ctx, (GDestroyNotify)port_qmi_close_context_free);
/* Reset monitoring logic */
reset_monitoring (self, ctx->qmi_device);
/* Release all allocated clients */
for (l = self->priv->services; l; l = g_list_next (l)) {
ServiceInfo *info = l->data;
mm_obj_dbg (self, "Releasing client for service '%s'...", qmi_service_get_string (info->service));
qmi_device_release_client (ctx->qmi_device,
info->client,
QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID,
3, NULL, NULL, NULL);
g_clear_object (&info->client);
}
g_list_free_full (self->priv->services, g_free);
self->priv->services = NULL;
/* Cleanup preallocated links, if any */
if (self->priv->preallocated_links) {
delete_preallocated_links (ctx->qmi_device, self->priv->preallocated_links);
g_clear_pointer (&self->priv->preallocated_links, g_array_unref);
}
g_clear_object (&self->priv->preallocated_links_main);
qmi_device_close_async (ctx->qmi_device,
5,
NULL,
(GAsyncReadyCallback)qmi_device_close_ready,
task);
}
/*****************************************************************************/
MMPortQmi *
mm_port_qmi_new (const gchar *name,
MMPortSubsys subsys)
{
return MM_PORT_QMI (g_object_new (MM_TYPE_PORT_QMI,
MM_PORT_DEVICE, name,
MM_PORT_SUBSYS, subsys,
MM_PORT_TYPE, MM_PORT_TYPE_QMI,
NULL));
}
#if defined WITH_QRTR
MMPortQmi *
mm_port_qmi_new_from_node (const gchar *name,
QrtrNode *node)
{
return MM_PORT_QMI (g_object_new (MM_TYPE_PORT_QMI,
"node", node,
MM_PORT_DEVICE, name,
MM_PORT_SUBSYS, MM_PORT_SUBSYS_QRTR,
MM_PORT_TYPE, MM_PORT_TYPE_QMI,
NULL));
}
#endif
static void
mm_port_qmi_init (MMPortQmi *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_PORT_QMI, MMPortQmiPrivate);
}
#if defined WITH_QRTR
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MMPortQmi *self = MM_PORT_QMI (object);
switch (prop_id) {
case PROP_NODE:
g_clear_object (&self->priv->node);
self->priv->node = g_value_dup_object (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)
{
MMPortQmi *self = MM_PORT_QMI (object);
switch (prop_id) {
case PROP_NODE:
g_value_set_object (value, self->priv->node);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
#endif /* defined WITH_QRTR */
static void
dispose (GObject *object)
{
MMPortQmi *self = MM_PORT_QMI (object);
GList *l;
/* Deallocate all clients */
for (l = self->priv->services; l; l = g_list_next (l)) {
ServiceInfo *info = l->data;
if (info->client)
g_object_unref (info->client);
}
g_list_free_full (self->priv->services, g_free);
self->priv->services = NULL;
/* Cleanup preallocated links, if any */
if (self->priv->preallocated_links && self->priv->qmi_device)
delete_preallocated_links (self->priv->qmi_device, self->priv->preallocated_links);
g_clear_pointer (&self->priv->preallocated_links, g_array_unref);
g_clear_object (&self->priv->preallocated_links_main);
/* Clear node object */
#if defined WITH_QRTR
g_clear_object (&self->priv->node);
#endif
/* Clear device object */
reset_monitoring (self, self->priv->qmi_device);
g_clear_object (&self->priv->qmi_device);
g_clear_pointer (&self->priv->net_driver, g_free);
g_clear_pointer (&self->priv->net_sysfs_path, g_free);
G_OBJECT_CLASS (mm_port_qmi_parent_class)->dispose (object);
}
static void
mm_port_qmi_class_init (MMPortQmiClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMPortQmiPrivate));
/* Virtual methods */
object_class->dispose = dispose;
#if defined WITH_QRTR
object_class->get_property = get_property;
object_class->set_property = set_property;
properties[PROP_NODE] =
g_param_spec_object ("node",
"Qrtr Node",
"Qrtr node to be probed",
QRTR_TYPE_NODE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_properties (object_class, PROP_LAST, properties);
#endif
}