blob: 17773cb254f0273a8f2bfe8fd2033f2ee0a54543 [file] [log] [blame]
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* libqmi-glib -- GLib/GIO based library to control QMI devices
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
* Copyright (C) 2012 Lanedo GmbH
* Copyright (C) 2012-2017 Aleksander Morgado <aleksander@aleksander.es>
* Copyright (C) 2019 Eric Caruso <ejcaruso@chromium.org>
*/
#include <string.h>
#include <libmbim-glib.h>
#include "qmi-endpoint-mbim.h"
#include "qmi-errors.h"
#include "qmi-error-types.h"
#include "qmi-file.h"
G_DEFINE_TYPE (QmiEndpointMbim, qmi_endpoint_mbim, QMI_TYPE_ENDPOINT)
struct _QmiEndpointMbimPrivate {
MbimDevice *mbimdev;
guint mbim_notification_id;
guint mbim_removed_id;
};
/*
* Number of extra seconds to give the MBIM timeout delay.
* Needed so the QMI timeout triggers first and we can be sure
* that timeouts on the QMI side are not because of libmbim timeouts.
*/
#define MBIM_TIMEOUT_DELAY_SECS 1
/*****************************************************************************/
static void
mbim_device_removed_cb (MbimDevice *device,
QmiEndpointMbim *self)
{
g_signal_emit_by_name (QMI_ENDPOINT (self), QMI_ENDPOINT_SIGNAL_HANGUP);
}
/*****************************************************************************/
static void
mbim_device_command_ready (MbimDevice *dev,
GAsyncResult *res,
QmiEndpointMbim *self)
{
MbimMessage *response;
const guint8 *buf;
guint32 len;
GError *error = NULL;
response = mbim_device_command_finish (dev, res, &error);
if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
g_warning ("[%s] MBIM error: %s", qmi_endpoint_get_name (QMI_ENDPOINT (self)), error->message);
if (response)
mbim_message_unref (response);
g_object_unref (self);
g_error_free (error);
return;
}
/* Store the raw information buffer in the internal reception buffer,
* as if we had read from a iochannel. */
buf = mbim_message_command_done_get_raw_information_buffer (response, &len);
qmi_endpoint_add_message (QMI_ENDPOINT (self), buf, len);
mbim_message_unref (response);
g_object_unref (self);
}
static void
mbim_qmi_notification_cb (MbimDevice *device,
MbimMessage *notification,
QmiEndpointMbim *self)
{
MbimService service;
const guint8 *buf;
guint32 len;
service = mbim_message_indicate_status_get_service (notification);
if (service != MBIM_SERVICE_QMI)
return;
buf = mbim_message_indicate_status_get_raw_information_buffer (notification, &len);
qmi_endpoint_add_message (QMI_ENDPOINT (self), buf, len);
}
static void
mbim_subscribe_list_set_ready_cb (MbimDevice *device,
GAsyncResult *res,
GTask *task)
{
QmiEndpointMbim *self;
MbimMessage *response;
GError *error = NULL;
self = g_task_get_source_object (task);
response = mbim_device_command_finish (device, res, &error);
if (response) {
mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);
mbim_message_unref (response);
}
if (error) {
g_warning ("[%s] couldn't enable QMI indications via MBIM: %s",
qmi_endpoint_get_name (QMI_ENDPOINT (self)), error->message);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_debug ("[%s] enabled QMI indications via MBIM", qmi_endpoint_get_name (QMI_ENDPOINT (self)));
self->priv->mbim_notification_id = g_signal_connect (device,
MBIM_DEVICE_SIGNAL_INDICATE_STATUS,
G_CALLBACK (mbim_qmi_notification_cb),
self);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
/*****************************************************************************/
typedef struct {
gboolean use_proxy;
guint timeout;
} MbimDeviceOpenContext;
static void
mbim_device_open_context_free (MbimDeviceOpenContext *ctx)
{
g_slice_free (MbimDeviceOpenContext, ctx);
}
static gboolean
endpoint_open_finish (QmiEndpoint *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
mbim_device_open_ready (MbimDevice *dev,
GAsyncResult *res,
GTask *task)
{
QmiEndpointMbim *self;
GError *error = NULL;
if (!mbim_device_open_finish (dev, res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
self = g_task_get_source_object (task);
g_debug ("[%s] MBIM device open", qmi_endpoint_get_name (QMI_ENDPOINT (self)));
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
mbim_device_new_ready (GObject *source,
GAsyncResult *res,
GTask *task)
{
QmiEndpointMbim *self;
MbimDeviceOpenContext *ctx;
MbimDeviceOpenFlags open_flags = MBIM_DEVICE_OPEN_FLAGS_NONE;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
self->priv->mbimdev = mbim_device_new_finish (res, &error);
if (!self->priv->mbimdev) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_debug ("[%s] MBIM device created", qmi_endpoint_get_name (QMI_ENDPOINT (self)));
self->priv->mbim_removed_id =
g_signal_connect (self->priv->mbimdev,
MBIM_DEVICE_SIGNAL_REMOVED,
G_CALLBACK (mbim_device_removed_cb),
self);
/* If QMI proxy was requested, use MBIM proxy instead */
if (ctx->use_proxy)
open_flags |= MBIM_DEVICE_OPEN_FLAGS_PROXY;
/* We pass the original timeout of the request to the open operation */
g_debug ("[%s] opening MBIM device...", qmi_endpoint_get_name (QMI_ENDPOINT (self)));
mbim_device_open_full (self->priv->mbimdev,
open_flags,
ctx->timeout,
g_task_get_cancellable (task),
(GAsyncReadyCallback) mbim_device_open_ready,
task);
}
static void
endpoint_open (QmiEndpoint *endpoint,
gboolean use_proxy,
guint timeout,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
QmiEndpointMbim *self;
MbimDeviceOpenContext *ctx;
QmiFile *qfile;
GFile *file;
GTask *task;
self = QMI_ENDPOINT_MBIM (endpoint);
task = g_task_new (self, cancellable, callback, user_data);
ctx = g_slice_new0 (MbimDeviceOpenContext);
ctx->use_proxy = use_proxy;
ctx->timeout = timeout;
g_task_set_task_data (task, ctx, (GDestroyNotify)mbim_device_open_context_free);
if (self->priv->mbimdev) {
g_task_return_new_error (task,
QMI_CORE_ERROR,
QMI_CORE_ERROR_WRONG_STATE,
"Already open");
g_object_unref (task);
return;
}
g_object_get (self, QMI_ENDPOINT_FILE, &qfile, NULL);
g_debug ("[%s] creating MBIM device...", qmi_endpoint_get_name (QMI_ENDPOINT (self)));
file = g_file_new_for_path (qmi_file_get_path (qfile));
g_object_unref (qfile);
mbim_device_new (file,
g_task_get_cancellable (task),
(GAsyncReadyCallback) mbim_device_new_ready,
task);
g_object_unref (file);
}
static gboolean
endpoint_is_open (QmiEndpoint *self)
{
return !!(QMI_ENDPOINT_MBIM (self)->priv->mbimdev);
}
static gboolean
endpoint_setup_indications_finish (QmiEndpoint *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
endpoint_setup_indications (QmiEndpoint *self,
guint timeout,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
MbimEventEntry **entries;
guint n_entries = 0;
MbimMessage *request;
task = g_task_new (self, cancellable, callback, user_data);
g_debug ("[%s] Enabling QMI indications via MBIM...", qmi_endpoint_get_name (QMI_ENDPOINT (self)));
entries = g_new0 (MbimEventEntry *, 2);
entries[n_entries] = g_new (MbimEventEntry, 1);
memcpy (&(entries[n_entries]->device_service_id), MBIM_UUID_QMI, sizeof (MbimUuid));
entries[n_entries]->cids_count = 1;
entries[n_entries]->cids = g_new0 (guint32, 1);
entries[n_entries]->cids[0] = MBIM_CID_QMI_MSG;
n_entries++;
request = mbim_message_device_service_subscribe_list_set_new (
n_entries,
(const MbimEventEntry *const *)entries,
NULL);
mbim_device_command (QMI_ENDPOINT_MBIM (self)->priv->mbimdev,
request,
10,
NULL,
(GAsyncReadyCallback)mbim_subscribe_list_set_ready_cb,
task);
mbim_message_unref (request);
mbim_event_entry_array_free (entries);
}
static gboolean
endpoint_send (QmiEndpoint *self,
QmiMessage *message,
guint timeout,
GCancellable *cancellable,
GError **error)
{
g_autoptr(MbimMessage) mbim_message = NULL;
gconstpointer raw_message;
gsize raw_message_len;
GError *inner_error = NULL;
/* MBIM endpoint allows only QMUX messages */
if (qmi_message_get_marker (message) != QMI_MESSAGE_QMUX_MARKER) {
g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED,
"MBIM endpoint expects only QMUX messages");
return FALSE;
}
/* Disallow private CTL operations */
if ((qmi_message_get_service (message) == QMI_SERVICE_CTL) &&
(qmi_message_get_message_id (message) == QMI_MESSAGE_CTL_INTERNAL_ALLOCATE_CID_QRTR ||
qmi_message_get_message_id (message) == QMI_MESSAGE_CTL_INTERNAL_RELEASE_CID_QRTR)) {
g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED,
"MBIM endpoint expects only 8bit QMI services");
return FALSE;
}
/* Get raw message */
raw_message = qmi_message_get_raw (message, &raw_message_len, &inner_error);
if (!raw_message) {
g_propagate_prefixed_error (error, inner_error, "Cannot get raw message: ");
return FALSE;
}
mbim_message = mbim_message_qmi_msg_set_new (raw_message_len, raw_message, &inner_error);
if (!mbim_message) {
g_propagate_error (error, inner_error);
return FALSE;
}
/* Note:
*
* Pass a full reference to the QMI endpoint to the MBIM command
* operation, so that we make sure the parent object is valid regardless
* of when the underlying device is fully disposed. This is required
* because device close is async().
*/
mbim_device_command (QMI_ENDPOINT_MBIM (self)->priv->mbimdev,
mbim_message,
timeout + MBIM_TIMEOUT_DELAY_SECS,
cancellable,
(GAsyncReadyCallback) mbim_device_command_ready,
g_object_ref (self));
return TRUE;
}
/*****************************************************************************/
static gboolean
endpoint_close_finish (QmiEndpoint *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
mbim_device_close_ready (MbimDevice *dev,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!mbim_device_close_finish (dev, res, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
endpoint_close (QmiEndpoint *endpoint,
guint timeout,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
QmiEndpointMbim *self;
self = QMI_ENDPOINT_MBIM (endpoint);
task = g_task_new (self, cancellable, callback, user_data);
if (!self->priv->mbimdev) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
/* Schedule in new main context */
mbim_device_close (self->priv->mbimdev,
timeout,
NULL,
(GAsyncReadyCallback) mbim_device_close_ready,
task);
/* Cleanup right away, we don't want multiple close attempts on the
* device */
if (self->priv->mbim_notification_id) {
g_signal_handler_disconnect (self->priv->mbimdev, self->priv->mbim_notification_id);
self->priv->mbim_notification_id = 0;
}
if (self->priv->mbim_removed_id) {
g_signal_handler_disconnect (self->priv->mbimdev, self->priv->mbim_removed_id);
self->priv->mbim_removed_id = 0;
}
g_clear_object (&self->priv->mbimdev);
}
/*****************************************************************************/
QmiEndpointMbim *
qmi_endpoint_mbim_new (QmiFile *file)
{
if (!file)
return NULL;
return g_object_new (QMI_TYPE_ENDPOINT_MBIM,
QMI_ENDPOINT_FILE, file,
NULL);
}
/*****************************************************************************/
static void
qmi_endpoint_mbim_init (QmiEndpointMbim *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
QMI_TYPE_ENDPOINT_MBIM,
QmiEndpointMbimPrivate);
}
static void
dispose (GObject *object)
{
QmiEndpointMbim *self = QMI_ENDPOINT_MBIM (object);
if (self->priv->mbimdev) {
g_warning ("[%s] MBIM device wasn't explicitly closed",
qmi_endpoint_get_name (QMI_ENDPOINT (self)));
if (self->priv->mbim_notification_id) {
g_signal_handler_disconnect (self->priv->mbimdev, self->priv->mbim_notification_id);
self->priv->mbim_notification_id = 0;
}
if (self->priv->mbim_removed_id) {
g_signal_handler_disconnect (self->priv->mbimdev, self->priv->mbim_removed_id);
self->priv->mbim_removed_id = 0;
}
g_clear_object (&self->priv->mbimdev);
}
G_OBJECT_CLASS (qmi_endpoint_mbim_parent_class)->dispose (object);
}
static void
qmi_endpoint_mbim_class_init (QmiEndpointMbimClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
QmiEndpointClass *endpoint_class = QMI_ENDPOINT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (QmiEndpointMbimPrivate));
object_class->dispose = dispose;
endpoint_class->open = endpoint_open;
endpoint_class->open_finish = endpoint_open_finish;
endpoint_class->is_open = endpoint_is_open;
endpoint_class->setup_indications = endpoint_setup_indications;
endpoint_class->setup_indications_finish = endpoint_setup_indications_finish;
endpoint_class->send = endpoint_send;
endpoint_class->close = endpoint_close;
endpoint_class->close_finish = endpoint_close_finish;
}