mm-port-qmi.c:353:18: error: this statement may fall through [-Werror=implicit-fallthrough=]
353 | ctx->step++;
| ~~~~~~~~~^~
mm-port-qmi.c:356:5: note: here
356 | case PORT_OPEN_STEP_CHECK_OPENING:
| ^~~~
mm-port-qmi.c:366:18: error: this statement may fall through [-Werror=implicit-fallthrough=]
366 | ctx->step++;
| ~~~~~~~~~^~
mm-port-qmi.c:369:5: note: here
369 | case PORT_OPEN_STEP_CHECK_ALREADY_OPEN:
| ^~~~
mm-port-qmi.c:376:18: error: this statement may fall through [-Werror=implicit-fallthrough=]
376 | ctx->step++;
| ~~~~~~~~~^~
mm-port-qmi.c:379:5: note: here
379 | case PORT_OPEN_STEP_DEVICE_NEW: {
| ^~~~
mm-port-qmi.c:424:18: error: this statement may fall through [-Werror=implicit-fallthrough=]
424 | ctx->step++;
| ~~~~~~~~~^~
mm-port-qmi.c:427:5: note: here
427 | case PORT_OPEN_STEP_ALLOCATE_WDA_CLIENT:
| ^~~~
mm-port-qmi.c:474:18: error: this statement may fall through [-Werror=implicit-fallthrough=]
474 | ctx->step++;
| ~~~~~~~~~^~
mm-port-qmi.c:477:5: note: here
477 | case PORT_OPEN_STEP_SET_KERNEL_DATA_FORMAT:
| ^~~~
780 lines
24 KiB
C
780 lines
24 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.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;
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
|
|
QmiClient *
|
|
mm_port_qmi_peek_client (MMPortQmi *self,
|
|
QmiService service,
|
|
MMPortQmiFlag flag)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = self->priv->services; l; l = g_list_next (l)) {
|
|
ServiceInfo *info = l->data;
|
|
|
|
if (info->service == service &&
|
|
info->flag == flag)
|
|
return info->client;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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_SET_KERNEL_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)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!qmi_device_close_finish (qmi_device, res, &error)) {
|
|
mm_warn ("Couldn't close QMI device after failed open sequence: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
port_open_complete_with_error (task);
|
|
}
|
|
|
|
static void
|
|
qmi_device_open_second_ready (QmiDevice *qmi_device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
PortOpenContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
qmi_device_open_finish (qmi_device, res, &ctx->error);
|
|
|
|
/* 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)
|
|
{
|
|
PortOpenContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!qmi_device_close_finish (qmi_device, res, &ctx->error)) {
|
|
mm_warn ("Couldn't close QMI device to reopen it");
|
|
ctx->step = PORT_OPEN_STEP_LAST;
|
|
} else
|
|
ctx->step++;
|
|
port_open_step (task);
|
|
}
|
|
|
|
static void
|
|
get_data_format_ready (QmiClientWda *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
PortOpenContext *ctx;
|
|
QmiMessageWdaGetDataFormatOutput *output;
|
|
|
|
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, NULL) ||
|
|
!qmi_message_wda_get_data_format_output_get_link_layer_protocol (output, &ctx->llp, NULL))
|
|
/* 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++;
|
|
|
|
if (output)
|
|
qmi_message_wda_get_data_format_output_unref (output);
|
|
|
|
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_dbg ("Opening QMI device...");
|
|
ctx->step++;
|
|
/* Fall through */
|
|
|
|
case PORT_OPEN_STEP_CHECK_OPENING:
|
|
mm_dbg ("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_dbg ("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_dbg ("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_dbg ("Opening device without data format update...");
|
|
qmi_device_open (ctx->device,
|
|
(QMI_DEVICE_OPEN_FLAGS_VERSION_INFO |
|
|
QMI_DEVICE_OPEN_FLAGS_PROXY),
|
|
20,
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback) qmi_device_open_first_ready,
|
|
task);
|
|
return;
|
|
|
|
case PORT_OPEN_STEP_GET_KERNEL_DATA_FORMAT:
|
|
mm_dbg ("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;
|
|
}
|
|
ctx->step++;
|
|
/* Fall through */
|
|
|
|
case PORT_OPEN_STEP_ALLOCATE_WDA_CLIENT:
|
|
/* Allocate WDA client */
|
|
mm_dbg ("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 */
|
|
g_assert (ctx->wda);
|
|
mm_dbg ("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_dbg ("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_SET_KERNEL_DATA_FORMAT:
|
|
/* Update the data format to be expected by the kernel */
|
|
mm_dbg ("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:
|
|
mm_dbg ("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:
|
|
/* Need to reopen setting 802.3 using CTL */
|
|
mm_dbg ("Reopening device with data format...");
|
|
qmi_device_open (ctx->device,
|
|
(QMI_DEVICE_OPEN_FLAGS_VERSION_INFO |
|
|
QMI_DEVICE_OPEN_FLAGS_PROXY |
|
|
QMI_DEVICE_OPEN_FLAGS_NET_802_3 |
|
|
QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER),
|
|
10,
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback) qmi_device_open_second_ready,
|
|
task);
|
|
return;
|
|
|
|
case PORT_OPEN_STEP_LAST:
|
|
if (ctx->error) {
|
|
mm_dbg ("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_dbg ("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;
|
|
|
|
g_return_if_fail (MM_IS_PORT_QMI (self));
|
|
|
|
ctx = g_slice_new0 (PortOpenContext);
|
|
ctx->step = PORT_OPEN_STEP_FIRST;
|
|
ctx->set_data_format = set_data_format;
|
|
ctx->kernel_data_format = QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN;
|
|
ctx->llp = QMI_WDA_LINK_LAYER_PROTOCOL_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_dbg ("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)
|
|
{
|
|
return MM_PORT_QMI (g_object_new (MM_TYPE_PORT_QMI,
|
|
MM_PORT_DEVICE, name,
|
|
MM_PORT_SUBSYS, MM_PORT_SUBSYS_USB,
|
|
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;
|
|
}
|