Files
ModemManager/src/mm-port-qmi.c
T
Aleksander Morgado 5b91e489eb port-qmi: expected kernel data format is qmi_wwan specific
The qmi_wwan driver is the only one that allows switching between
802.3 and raw-ip during runtime, and therefore we must not assume that
every QMI port managed allows to do so, we'll limit that feature only
to QMI ports in the 'usbmisc' subsystem.

For every other port, we assume the kernel expects raw-ip by default,
and so we include logic to switch the modem to raw-ip both using WDA
and CTL (WDA preferred over CTL). This assumption may not be perfect,
but it's probably a good guess, as raw-ip is the preferred and
sometimes the only format supported by new devices.
2020-11-09 18:41:57 +01:00

907 lines
30 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 Google, Inc.
*/
#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-log-object.h"
G_DEFINE_TYPE (MMPortQmi, mm_port_qmi, MM_TYPE_PORT)
typedef struct {
QmiService service;
QmiClient *client;
MMPortQmiFlag flag;
} ServiceInfo;
struct _MMPortQmiPrivate {
gboolean in_progress;
QmiDevice *qmi_device;
GList *services;
gboolean llp_is_raw_ip;
};
/*****************************************************************************/
static QmiClient *
lookup_client (MMPortQmi *self,
QmiService service,
MMPortQmiFlag 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,
MMPortQmiFlag flag)
{
return lookup_client (self, service, flag, FALSE);
}
QmiClient *
mm_port_qmi_get_client (MMPortQmi *self,
QmiService service,
MMPortQmiFlag flag)
{
QmiClient *client;
client = mm_port_qmi_peek_client (self, service, flag);
return (client ? g_object_ref (client) : NULL);
}
/*****************************************************************************/
QmiDevice *
mm_port_qmi_peek_device (MMPortQmi *self)
{
g_return_val_if_fail (MM_IS_PORT_QMI (self), NULL);
return self->priv->qmi_device;
}
/*****************************************************************************/
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,
MMPortQmiFlag 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);
}
/*****************************************************************************/
gboolean
mm_port_qmi_llp_is_raw_ip (MMPortQmi *self)
{
return self->priv->llp_is_raw_ip;
}
/*****************************************************************************/
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_GET_KERNEL_DATA_FORMAT,
PORT_OPEN_STEP_ALLOCATE_WDA_CLIENT,
PORT_OPEN_STEP_GET_WDA_DATA_FORMAT,
PORT_OPEN_STEP_CHECK_DATA_FORMAT,
PORT_OPEN_STEP_SYNC_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;
QmiClient *wda;
GError *error;
PortOpenStep step;
gboolean set_data_format;
QmiDeviceExpectedDataFormat kernel_data_format;
QmiWdaLinkLayerProtocol llp;
} PortOpenContext;
static void
port_open_context_free (PortOpenContext *ctx)
{
g_assert (!ctx->error);
if (ctx->wda && ctx->device)
qmi_device_release_client (ctx->device,
ctx->wda,
QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID,
3, NULL, NULL, NULL);
g_clear_object (&ctx->wda);
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;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (qmi_device_open_finish (qmi_device, res, &ctx->error)) {
/* If the open with CTL data format is sucessful, update */
if (ctx->kernel_data_format == QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP)
self->priv->llp_is_raw_ip = TRUE;
else
self->priv->llp_is_raw_ip = FALSE;
}
/* In both error and success, we go to last step */
ctx->step = PORT_OPEN_STEP_LAST;
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
set_data_format_ready (QmiClientWda *client,
GAsyncResult *res,
GTask *task)
{
MMPortQmi *self;
PortOpenContext *ctx;
g_autoptr(QmiMessageWdaSetDataFormatOutput) 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_set_data_format_finish (client, res, &error);
if (!output || !qmi_message_wda_set_data_format_output_get_result (output, &error)) {
mm_obj_warn (self, "Couldn't set data format: %s", error->message);
/* If setting WDA data format fails, fallback to LLP requested via CTL */
ctx->step = PORT_OPEN_STEP_CLOSE_BEFORE_OPEN_WITH_DATA_FORMAT;
} else {
self->priv->llp_is_raw_ip = TRUE;
ctx->step = PORT_OPEN_STEP_LAST;
}
port_open_step (task);
}
static void
get_data_format_ready (QmiClientWda *client,
GAsyncResult *res,
GTask *task)
{
MMPortQmi *self;
PortOpenContext *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, NULL);
if (!output ||
!qmi_message_wda_get_data_format_output_get_result (output, &error) ||
!qmi_message_wda_get_data_format_output_get_link_layer_protocol (output, &ctx->llp, NULL)) {
/* 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, assume the device supports only raw-ip and be done
* with it. */
if (error && g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_MISSING_ARGUMENT)) {
mm_obj_dbg (self, "Querying data format failed: '%s', assuming raw-ip is only supported", error->message);
ctx->llp = QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP;
ctx->step++;
} else {
/* If loading WDA data format fails, fallback to 802.3 requested via CTL */
ctx->step = PORT_OPEN_STEP_CLOSE_BEFORE_OPEN_WITH_DATA_FORMAT;
}
} else
/* Go on to next step */
ctx->step++;
port_open_step (task);
}
static void
allocate_client_wda_ready (QmiDevice *device,
GAsyncResult *res,
GTask *task)
{
PortOpenContext *ctx;
ctx = g_task_get_task_data (task);
ctx->wda = qmi_device_allocate_client_finish (device, res, NULL);
if (!ctx->wda) {
/* If no WDA supported, then we just fallback to reopening explicitly
* requesting 802.3 in the CTL service. */
ctx->step = PORT_OPEN_STEP_CLOSE_BEFORE_OPEN_WITH_DATA_FORMAT;
port_open_step (task);
return;
}
/* Go on to next step */
ctx->step++;
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: {
GFile *file;
gchar *fullpath;
fullpath = g_strdup_printf ("/dev/%s", mm_port_get_device (MM_PORT (self)));
file = g_file_new_for_path (fullpath);
/* 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;
mm_obj_dbg (self, "Creating QMI device...");
qmi_device_new (file,
g_task_get_cancellable (task),
(GAsyncReadyCallback) qmi_device_new_ready,
task);
g_free (fullpath);
g_object_unref (file);
return;
}
case PORT_OPEN_STEP_OPEN_WITHOUT_DATA_FORMAT:
/* Now open the QMI device without any data format CTL flag */
mm_obj_dbg (self, "Opening device without data format update...");
qmi_device_open (ctx->device,
(QMI_DEVICE_OPEN_FLAGS_VERSION_INFO |
QMI_DEVICE_OPEN_FLAGS_PROXY),
25,
g_task_get_cancellable (task),
(GAsyncReadyCallback) qmi_device_open_first_ready,
task);
return;
case PORT_OPEN_STEP_GET_KERNEL_DATA_FORMAT:
/* Querying kernel data format is only expected when using qmi_wwan */
if (mm_port_get_subsys (MM_PORT (self)) == MM_PORT_SUBSYS_USBMISC) {
mm_obj_dbg (self, "Querying kernel data format...");
/* Try to gather expected data format from the sysfs file */
ctx->kernel_data_format = qmi_device_get_expected_data_format (ctx->device, NULL);
/* If data format cannot be retrieved, we fallback to 802.3 via CTL */
if (ctx->kernel_data_format == QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN) {
ctx->step = PORT_OPEN_STEP_CLOSE_BEFORE_OPEN_WITH_DATA_FORMAT;
port_open_step (task);
return;
}
}
/* For any driver other than qmi_wwan, assume raw-ip */
else {
ctx->kernel_data_format = QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP;
mm_obj_dbg (self, "Assuming default kernel data format: %s",
qmi_device_expected_data_format_get_string (ctx->kernel_data_format));
}
ctx->step++;
/* Fall through */
case PORT_OPEN_STEP_ALLOCATE_WDA_CLIENT:
/* Allocate WDA client */
mm_obj_dbg (self, "Allocating WDA client...");
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 PORT_OPEN_STEP_GET_WDA_DATA_FORMAT:
/* If we have WDA client, query current data format */
mm_obj_dbg (self, "Querying device data format...");
qmi_client_wda_get_data_format (QMI_CLIENT_WDA (ctx->wda),
NULL,
10,
g_task_get_cancellable (task),
(GAsyncReadyCallback) get_data_format_ready,
task);
return;
case PORT_OPEN_STEP_CHECK_DATA_FORMAT:
/* We now have the WDA data format and the kernel data format, if they're
* equal, we're done */
mm_obj_dbg (self, "Checking data format: kernel %s, device %s",
qmi_device_expected_data_format_get_string (ctx->kernel_data_format),
qmi_wda_link_layer_protocol_get_string (ctx->llp));
if (ctx->kernel_data_format == QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3 &&
ctx->llp == QMI_WDA_LINK_LAYER_PROTOCOL_802_3) {
self->priv->llp_is_raw_ip = FALSE;
ctx->step = PORT_OPEN_STEP_LAST;
port_open_step (task);
return;
}
if (ctx->kernel_data_format == QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP &&
ctx->llp == QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP) {
self->priv->llp_is_raw_ip = TRUE;
ctx->step = PORT_OPEN_STEP_LAST;
port_open_step (task);
return;
}
ctx->step++;
/* Fall through */
case PORT_OPEN_STEP_SYNC_DATA_FORMAT:
/* For drivers other than qmi_wwan, the kernel data format was raw-ip
* by default, we need to ask the module to switch to it */
if (mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_USBMISC) {
g_autoptr(QmiMessageWdaSetDataFormatInput) input = NULL;
g_assert (ctx->kernel_data_format == QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP);
input = qmi_message_wda_set_data_format_input_new ();
qmi_message_wda_set_data_format_input_set_link_layer_protocol (input, QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP, 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);
return;
}
/* If using the qmi_wwan driver, we ask the kernel to sync with the
* data format requested by the module */
mm_obj_dbg (self, "Updating kernel data format: %s", qmi_wda_link_layer_protocol_get_string (ctx->llp));
if (ctx->llp == QMI_WDA_LINK_LAYER_PROTOCOL_802_3) {
ctx->kernel_data_format = QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3;
self->priv->llp_is_raw_ip = FALSE;
} else if (ctx->llp == QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP) {
ctx->kernel_data_format = QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP;
self->priv->llp_is_raw_ip = TRUE;
} else
g_assert_not_reached ();
/* Regardless of the output, we're done after this action */
qmi_device_set_expected_data_format (ctx->device,
ctx->kernel_data_format,
&ctx->error);
ctx->step = PORT_OPEN_STEP_LAST;
port_open_step (task);
return;
case PORT_OPEN_STEP_CLOSE_BEFORE_OPEN_WITH_DATA_FORMAT:
/* This fallback only applies when WDA unsupported */
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;
case PORT_OPEN_STEP_OPEN_WITH_DATA_FORMAT: {
QmiDeviceOpenFlags open_flags;
/* Common open flags */
open_flags = (QMI_DEVICE_OPEN_FLAGS_VERSION_INFO |
QMI_DEVICE_OPEN_FLAGS_PROXY |
QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER);
/* Need to reopen setting 802.3/raw-ip using CTL */
if (ctx->kernel_data_format == QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP)
open_flags |= QMI_DEVICE_OPEN_FLAGS_NET_RAW_IP;
else
open_flags |= QMI_DEVICE_OPEN_FLAGS_NET_802_3;
mm_obj_dbg (self, "Reopening device with data format: %s...",
qmi_device_expected_data_format_get_string (ctx->kernel_data_format));
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);
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->llp = QMI_WDA_LINK_LAYER_PROTOCOL_UNKNOWN;
ctx->kernel_data_format = QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN;
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;
}
/*****************************************************************************/
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);
/* 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;
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));
}
static void
mm_port_qmi_init (MMPortQmi *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_PORT_QMI, MMPortQmiPrivate);
}
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;
/* Clear device object */
g_clear_object (&self->priv->qmi_device);
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;
}