From f58d3ef93fef8ae7e9d14f37fa2940d91d49a408 Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Tue, 23 Feb 2021 22:43:09 +0100 Subject: port-qmi: new methods to setup/cleanup net links The logic to setup/cleanup net links is based on the QmiDevice net link addition/removal operations. When the qmi_wwan add_mux/del_mux based logic is in use, we default to precreate 4 net links and we limit the amount of bearers that may be connected to that maximum, because it is not guaranteed that the qmi_wwan driver is able to create new links once the master interface is up; and the master interface needs to be up for a proper data connection. For all other drivers, or when qmi_wwan uses qmap-pass-through, we allow adding/deleting new links at any moment, without needing to rely on the precreated ones. --- src/mm-port-qmi.c | 467 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/mm-port-qmi.h | 19 +++ 2 files changed, 486 insertions(+) (limited to 'src') diff --git a/src/mm-port-qmi.c b/src/mm-port-qmi.c index 50c7076b..8cbe924c 100644 --- a/src/mm-port-qmi.c +++ b/src/mm-port-qmi.c @@ -26,6 +26,8 @@ #include "mm-modem-helpers-qmi.h" #include "mm-log-object.h" +#define DEFAULT_LINK_PREALLOCATED_AMOUNT 4 + G_DEFINE_TYPE (MMPortQmi, mm_port_qmi, MM_TYPE_PORT) typedef struct { @@ -48,6 +50,10 @@ struct _MMPortQmiPrivate { gboolean wda_unsupported; QmiWdaLinkLayerProtocol llp; QmiWdaDataAggregationProtocol dap; + /* preallocated links */ + MMPort *preallocated_links_master; + GArray *preallocated_links; + GList *preallocated_links_setup_pending; }; /*****************************************************************************/ @@ -276,6 +282,454 @@ mm_port_qmi_allocate_client (MMPortQmi *self, /*****************************************************************************/ +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 master 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 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 *master, + 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_master) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No preallocated links available"); + return FALSE; + } + + if ((master != self->priv->preallocated_links_master) && + (g_strcmp0 (mm_port_get_device (master), mm_port_get_device (self->priv->preallocated_links_master)) != 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_master), + mm_port_get_device (master)); + 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_master); + 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 *master; + gchar *link_name; + guint mux_id; +} SetupLinkContext; + +static void +setup_link_context_free (SetupLinkContext *ctx) +{ + g_free (ctx->link_name); + g_clear_object (&ctx->master); + 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_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->master, &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 master, because we're not really initialized */ + g_clear_object (&self->priv->preallocated_links_master); + 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->dap != QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAPV5) && + (self->priv->dap != QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAP)) { + 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->master = g_object_ref (data); + ctx->mux_id = QMI_DEVICE_MUX_ID_UNBOUND; + g_task_set_task_data (task, ctx, (GDestroyNotify) setup_link_context_free); + + /* For all drivers except for qmi_wwan, or when qmi_wwan is setup with + * qmap-pass-through, just try to add link in the QmiDevice */ + if ((mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_USBMISC) || + self->priv->kernel_data_format == QMI_DEVICE_EXPECTED_DATA_FORMAT_QMAP_PASS_THROUGH) { + qmi_device_add_link (self->priv->qmi_device, + QMI_DEVICE_MUX_ID_AUTOMATIC, + mm_kernel_device_get_name (mm_port_peek_kernel_device (data)), + link_prefix_hint, + NULL, + (GAsyncReadyCallback) device_add_link_ready, + task); + return; + } + + /* For qmi_wwan, use preallocated links */ + 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 (master is set) but the array didn't exist, + * queue our task for completion once we're fully initialized */ + if (self->priv->preallocated_links_master) { + self->priv->preallocated_links_setup_pending = g_list_append (self->priv->preallocated_links_setup_pending, task); + return; + } + + /* Store master to flag that we're initializing preallocated links */ + self->priv->preallocated_links_master = g_object_ref (data); + initialize_preallocated_links (self, + (GAsyncReadyCallback) initialize_preallocated_links_ready, + task); +} + +/*****************************************************************************/ + +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->dap != QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAPV5) && + (self->priv->dap != QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAP)) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Aggregation not enabled"); + g_object_unref (task); + return; + } + + /* For all drivers except for qmi_wwan, or when qmi_wwan is setup with + * qmap-pass-through, just try to delete link in the QmiDevice */ + if ((mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_USBMISC) || + self->priv->kernel_data_format == QMI_DEVICE_EXPECTED_DATA_FORMAT_QMAP_PASS_THROUGH) { + 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 (!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); +} + +/*****************************************************************************/ + typedef struct { QmiDevice *device; MMPort *data; @@ -1724,6 +2178,13 @@ mm_port_qmi_close (MMPortQmi *self, 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_master); + qmi_device_close_async (ctx->qmi_device, 5, NULL, @@ -1781,6 +2242,12 @@ dispose (GObject *object) 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_master); + /* Clear device object */ g_clear_object (&self->priv->qmi_device); diff --git a/src/mm-port-qmi.h b/src/mm-port-qmi.h index 50f984e7..e03c262f 100644 --- a/src/mm-port-qmi.h +++ b/src/mm-port-qmi.h @@ -115,6 +115,25 @@ gboolean mm_port_qmi_setup_data_format_finish (MMPortQmi *s GAsyncResult *res, GError **error); +void mm_port_qmi_setup_link (MMPortQmi *self, + MMPort *data, + const gchar *link_prefix_hint, + GAsyncReadyCallback callback, + gpointer user_data); +gchar *mm_port_qmi_setup_link_finish (MMPortQmi *self, + GAsyncResult *res, + guint *mux_id, + GError **error); + +void mm_port_qmi_cleanup_link (MMPortQmi *self, + const gchar *link_name, + guint mux_id, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_port_qmi_cleanup_link_finish (MMPortQmi *self, + GAsyncResult *res, + GError **error); + void mm_port_qmi_reset (MMPortQmi *self, MMPort *data, GAsyncReadyCallback callback, -- cgit v1.2.3-70-g09d2