| /* -*- 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" |
| |
| /* as internally defined in the kernel */ |
| #define RMNET_MAX_PACKET_SIZE 16384 |
| #define MHI_NET_MTU_DEFAULT 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; |
| guint net_preallocated_links_requested; |
| #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 */ |
| guint preallocated_links_needed; |
| MMPort *preallocated_links_main; |
| GArray *preallocated_links; |
| GList *preallocated_links_setup_pending; |
| /* first multiplex setup */ |
| gboolean first_multiplex_setup; |
| }; |
| |
| /*****************************************************************************/ |
| |
| 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: |
| /* Qualcomm magic number */ |
| self->priv->endpoint_interface_number = 4; |
| break; |
| 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; |
| gchar *link_prefix_hint; |
| QmiDeviceAddLinkFlags flags; |
| 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_clear_pointer (&ctx->link_prefix_hint, g_free); |
| 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) |
| { |
| MMPortQmi *self; |
| InitializePreallocatedLinksContext *ctx; |
| GError *error = NULL; |
| PreallocatedLinkInfo info = { NULL, 0, FALSE }; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| info.link_name = qmi_device_add_link_with_flags_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, self->priv->preallocated_links_needed); |
| 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; |
| } |
| |
| g_assert (self->priv->preallocated_links_needed > 0); |
| if (ctx->preallocated_links->len == (guint) self->priv->preallocated_links_needed) { |
| g_task_return_pointer (task, g_steal_pointer (&ctx->preallocated_links), (GDestroyNotify)g_array_unref); |
| g_object_unref (task); |
| return; |
| } |
| |
| qmi_device_add_link_with_flags (self->priv->qmi_device, |
| ctx->preallocated_links->len + 1, |
| mm_kernel_device_get_name (mm_port_peek_kernel_device (ctx->data)), |
| ctx->link_prefix_hint, |
| ctx->flags, |
| NULL, |
| (GAsyncReadyCallback) device_add_link_preallocated_ready, |
| task); |
| } |
| |
| static void |
| initialize_preallocated_links (MMPortQmi *self, |
| const gchar *link_prefix_hint, |
| QmiDeviceAddLinkFlags flags, |
| 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->link_prefix_hint = g_strdup (link_prefix_hint); |
| ctx->flags = flags; |
| ctx->data = g_object_ref (self->priv->preallocated_links_main); |
| ctx->preallocated_links = g_array_sized_new (FALSE, FALSE, sizeof (PreallocatedLinkInfo), self->priv->preallocated_links_needed); |
| 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); |
| } |
| } |
| |
| static QmiDeviceAddLinkFlags |
| get_rmnet_device_add_link_flags (MMPortQmi *self) |
| { |
| QmiDeviceAddLinkFlags flags = QMI_DEVICE_ADD_LINK_FLAGS_NONE; |
| g_autofree gchar *flags_str = NULL; |
| |
| if (g_strcmp0 (self->priv->net_driver, "ipa") == 0) { |
| 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; |
| |
| tx_sysfs_path = g_build_filename (self->priv->net_sysfs_path, "device", "feature", "tx_offload", NULL); |
| rx_sysfs_path = g_build_filename (self->priv->net_sysfs_path, "device", "feature", "rx_offload", NULL); |
| |
| if (g_file_get_contents (rx_sysfs_path, &rx_sysfs_str, NULL, NULL) && rx_sysfs_str) { |
| if (g_str_has_prefix (rx_sysfs_str, "MAPv4")) |
| flags |= QMI_DEVICE_ADD_LINK_FLAGS_INGRESS_MAP_CKSUMV4; |
| else if (g_str_has_prefix (rx_sysfs_str, "MAPv5")) |
| flags |= QMI_DEVICE_ADD_LINK_FLAGS_INGRESS_MAP_CKSUMV5; |
| } |
| |
| if (g_file_get_contents (tx_sysfs_path, &tx_sysfs_str, NULL, NULL) && tx_sysfs_str) { |
| if (g_str_has_prefix (tx_sysfs_str, "MAPv4")) |
| flags |= QMI_DEVICE_ADD_LINK_FLAGS_EGRESS_MAP_CKSUMV4; |
| else if (g_str_has_prefix (tx_sysfs_str, "MAPv5")) |
| flags |= QMI_DEVICE_ADD_LINK_FLAGS_EGRESS_MAP_CKSUMV5; |
| } |
| } |
| |
| if (g_strcmp0 (self->priv->net_driver, "qmi_wwan") == 0 || |
| g_strcmp0 (self->priv->net_driver, "mhi_net") == 0) { |
| QmiWdaDataAggregationProtocol dap; |
| |
| dap = mm_port_qmi_get_data_aggregation_protocol (self); |
| if (dap == QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAPV5) |
| flags |= (QMI_DEVICE_ADD_LINK_FLAGS_INGRESS_MAP_CKSUMV5 | |
| QMI_DEVICE_ADD_LINK_FLAGS_EGRESS_MAP_CKSUMV5); |
| else if (dap == QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAPV4) |
| flags |= (QMI_DEVICE_ADD_LINK_FLAGS_INGRESS_MAP_CKSUMV4 | |
| QMI_DEVICE_ADD_LINK_FLAGS_EGRESS_MAP_CKSUMV4); |
| } |
| |
| flags_str = qmi_device_add_link_flags_build_string_from_mask (flags); |
| mm_obj_dbg (self, "Creating RMNET link with flags: %s", flags_str); |
| return flags; |
| } |
| |
| 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); |
| |
| /* If we're requested to use preallocated links, do it right away */ |
| if (self->priv->preallocated_links_needed > 0) { |
| 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, |
| link_prefix_hint, |
| get_rmnet_device_add_link_flags (self), |
| (GAsyncReadyCallback) initialize_preallocated_links_ready, |
| task); |
| return; |
| } |
| |
| /* No preallocated links required and 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_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, |
| get_rmnet_device_add_link_flags (self), |
| NULL, |
| (GAsyncReadyCallback) device_add_link_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; |
| } |
| |
| /* If using preallocated links, just release one */ |
| if (self->priv->preallocated_links_needed > 0) { |
| 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; |
| } |
| |
| /* When using rmnet, just try to delete the link from 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; |
| } |
| |
| 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, "resetting 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; |
| guint mtu; |
| |
| 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); |
| |
| /* mhi_net has a custom default MTU set by the kernel driver */ |
| if (g_strcmp0 (self->priv->net_driver, "mhi_net") == 0) |
| mtu = MHI_NET_MTU_DEFAULT; |
| else |
| mtu = MM_PORT_NET_MTU_DEFAULT; |
| |
| /* 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, |
| mtu, |
| 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; |
| } |
| } |
| |
| if (g_strcmp0 (self->priv->net_driver, "mhi_net") == 0) |
| return (MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP | MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET); |
| |
| /* 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; |
| } |
| |
| /* PCIe based setups support both raw ip and QMAP through rmnet */ |
| if (g_strcmp0 (self->priv->net_driver, "mhi_net") == 0) |
| return (MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP | MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET); |
| |
| /* For any driver, assume raw-ip only */ |
| return MM_PORT_QMI_KERNEL_DATA_MODE_RAW_IP; |
| } |
| |
| /*****************************************************************************/ |
| |
| #define DEFAULT_QMI_WWAN_PREALLOCATED_LINKS 4 |
| #define DEFAULT_DOWNLINK_DATA_AGGREGATION_MAX_SIZE 32768 |
| #define DEFAULT_DOWNLINK_DATA_AGGREGATION_MAX_SIZE_QMI_WWAN_RMNET 16384 |
| #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 void |
| internal_setup_data_format_propagate_link_setup (GTask *task, |
| guint *out_max_multiplexed_links, |
| guint *out_preallocated_links) |
| { |
| MMPortQmi *self; |
| InternalSetupDataFormatContext *ctx; |
| guint max_multiplexed_links; |
| guint preallocated_links; |
| |
| if (!out_max_multiplexed_links && !out_preallocated_links) |
| return; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| if (!ctx->wda_dap_supported) { |
| max_multiplexed_links = 0; |
| preallocated_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) { |
| max_multiplexed_links = 1 + (QMI_DEVICE_MUX_ID_MAX - QMI_DEVICE_MUX_ID_MIN); |
| preallocated_links = (self->priv->net_preallocated_links_requested > max_multiplexed_links) ? |
| max_multiplexed_links : |
| self->priv->net_preallocated_links_requested; |
| mm_obj_dbg (self, "rmnet link management supported: %u multiplexed bearers allowed, %u links preallocated", |
| max_multiplexed_links, preallocated_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) { |
| preallocated_links = (self->priv->net_preallocated_links_requested > 0) ? |
| self->priv->net_preallocated_links_requested : |
| DEFAULT_QMI_WWAN_PREALLOCATED_LINKS; |
| max_multiplexed_links = preallocated_links; |
| mm_obj_dbg (self, "qmi_wwan link management supported: %u multiplexed bearers allowed, %u links preallocated", |
| max_multiplexed_links, preallocated_links); |
| } else { |
| max_multiplexed_links = 0; |
| preallocated_links = 0; |
| mm_obj_dbg (self, "link management unsupported: no multiplexed bearers allowed"); |
| } |
| } |
| |
| if (out_max_multiplexed_links) |
| *out_max_multiplexed_links = max_multiplexed_links; |
| if (out_preallocated_links) |
| *out_preallocated_links = preallocated_links; |
| } |
| |
| 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, |
| guint *out_preallocated_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; |
| |
| internal_setup_data_format_propagate_link_setup (G_TASK (res), |
| out_max_multiplexed_links, |
| out_preallocated_links); |
| |
| 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, g_steal_pointer (&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) { |
| if ((g_strcmp0 (self->priv->net_driver, "qmi_wwan") == 0) && |
| (ctx->kernel_data_modes_supported & MM_PORT_QMI_KERNEL_DATA_MODE_MUX_RMNET)) |
| qmi_message_wda_set_data_format_input_set_downlink_data_aggregation_max_size (input, DEFAULT_DOWNLINK_DATA_AGGREGATION_MAX_SIZE_QMI_WWAN_RMNET, NULL); |
| else |
| 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 */ |
| NULL, /* not expected to update */ |
| &error)) |
| g_task_return_error (task, error); |
| else { |
| self->priv->first_multiplex_setup = FALSE; |
| 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; |
| } |
| |
| if (links) |
| return links->len; |
| |
| /* No list of links returned, so there are none */ |
| return 0; |
| } |
| |
| 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"); |
| /* If this is the first time that multiplex is used, perform anyway the internal reset operation, so that the links are properly managed */ |
| if (!self->priv->first_multiplex_setup) { |
| 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 successful, 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, |
| &self->priv->preallocated_links_needed, |
| &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..."); |
| self->priv->first_multiplex_setup = TRUE; |
| 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; |
| } |
| |
| /*****************************************************************************/ |
| /* Sets network details that the QMI port should be aware of before even |
| * a data connection is started. */ |
| |
| void |
| mm_port_qmi_set_net_details (MMPortQmi *self, |
| MMPort *first_net) |
| { |
| MMKernelDevice *first_net_dev; |
| |
| first_net_dev = mm_port_peek_kernel_device (first_net); |
| |
| g_assert (MM_IS_PORT_QMI (self)); |
| |
| g_assert (!self->priv->net_driver); |
| self->priv->net_driver = g_strdup (mm_kernel_device_get_driver (first_net_dev)); |
| |
| g_assert (!self->priv->net_sysfs_path); |
| self->priv->net_sysfs_path = g_strdup (mm_kernel_device_get_sysfs_path (first_net_dev)); |
| |
| g_assert (!self->priv->net_preallocated_links_requested); |
| self->priv->net_preallocated_links_requested = mm_kernel_device_get_global_property_as_int (first_net_dev, "ID_MM_QMI_PREALLOCATED_LINKS"); |
| |
| initialize_endpoint_info (self); |
| } |
| |
| /*****************************************************************************/ |
| |
| 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_GROUP, MM_PORT_GROUP_USED, |
| 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_GROUP, MM_PORT_GROUP_USED, |
| 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 |
| } |