diff options
-rw-r--r-- | src/mm-bearer-qmi.c | 324 |
1 files changed, 285 insertions, 39 deletions
diff --git a/src/mm-bearer-qmi.c b/src/mm-bearer-qmi.c index 40307a51..ab3059fe 100644 --- a/src/mm-bearer-qmi.c +++ b/src/mm-bearer-qmi.c @@ -56,6 +56,8 @@ struct _MMBearerQmiPrivate { guint event_report_ipv6_indication_id; MMPort *data; + MMPort *link; + guint mux_id; guint32 packet_data_handle_ipv4; guint32 packet_data_handle_ipv6; }; @@ -313,10 +315,15 @@ connection_status_context_step (GTask *task) switch (ctx->step) { case CONNECTION_STATUS_CONTEXT_STEP_FIRST: /* Connection status polling is an optional feature that must be - * enabled explicitly via udev tags. If not set, out as unsupported */ - if (self->priv->data && - !mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (self->priv->data), - "ID_MM_QMI_CONNECTION_STATUS_POLLING_ENABLE")) { + * enabled explicitly via udev tags. If not set, out as unsupported. + * Note that when connected via a muxed link, the udev tag should be + * checked on the master interface (lower device) */ + if ((self->priv->data && + !mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (self->priv->data), + "ID_MM_QMI_CONNECTION_STATUS_POLLING_ENABLE")) || + (self->priv->link && + !mm_kernel_device_get_global_property_as_boolean (mm_kernel_device_peek_lower_device (mm_port_peek_kernel_device (self->priv->link)), + "ID_MM_QMI_CONNECTION_STATUS_POLLING_ENABLE"))) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Connection status polling not required"); g_object_unref (task); @@ -388,6 +395,8 @@ load_connection_status (MMBaseBearer *self, /*****************************************************************************/ /* Connect */ +#define WAIT_LINK_PORT_TIMEOUT_MS 2500 + static void common_setup_cleanup_packet_service_status_unsolicited_events (MMBearerQmi *self, QmiClientWds *client, gboolean enable, @@ -407,6 +416,7 @@ typedef enum { CONNECT_STEP_FIRST, CONNECT_STEP_OPEN_QMI_PORT, CONNECT_STEP_SETUP_DATA_FORMAT, + CONNECT_STEP_SETUP_LINK, CONNECT_STEP_IP_METHOD, CONNECT_STEP_IPV4, CONNECT_STEP_WDS_CLIENT_IPV4, @@ -431,6 +441,7 @@ typedef struct { MMPort *data; MMPortQmi *qmi; QmiSioPort sio_port; + MMBearerMultiplexSupport multiplex; gboolean explicit_qmi_open; gchar *user; gchar *password; @@ -438,6 +449,12 @@ typedef struct { QmiWdsAuthentication auth; gboolean no_ip_family_preference; + QmiWdaDataAggregationProtocol dap; + guint mux_id; + gchar *link_prefix_hint; + gchar *link_name; + MMPort *link; + MMBearerIpMethod ip_method; gboolean ipv4; @@ -510,6 +527,13 @@ connect_context_free (ConnectContext *ctx) g_clear_object (&ctx->client_ipv6); } + if (ctx->link_name) { + mm_port_qmi_cleanup_link (ctx->qmi, ctx->link_name, ctx->mux_id, NULL, NULL); + g_free (ctx->link_name); + } + g_clear_object (&ctx->link); + g_free (ctx->link_prefix_hint); + if (ctx->explicit_qmi_open) mm_port_qmi_close (ctx->qmi, NULL, NULL); @@ -517,7 +541,8 @@ connect_context_free (ConnectContext *ctx) g_clear_error (&ctx->error_ipv6); g_clear_object (&ctx->ipv4_config); g_clear_object (&ctx->ipv6_config); - g_object_unref (ctx->data); + + g_clear_object (&ctx->data); g_object_unref (ctx->qmi); g_object_unref (ctx->self); g_slice_free (ConnectContext, ctx); @@ -1019,6 +1044,32 @@ bind_data_port_ready (QmiClientWds *client, } static void +bind_mux_data_port_ready (QmiClientWds *client, + GAsyncResult *res, + GTask *task) +{ + ConnectContext *ctx; + GError *error = NULL; + g_autoptr(QmiMessageWdsBindMuxDataPortOutput) output = NULL; + + ctx = g_task_get_task_data (task); + + g_assert (ctx->running_ipv4 || ctx->running_ipv6); + g_assert (!(ctx->running_ipv4 && ctx->running_ipv6)); + + output = qmi_client_wds_bind_mux_data_port_finish (client, res, &error); + if (!output || !qmi_message_wds_bind_mux_data_port_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't bind mux data port: "); + complete_connect (task, NULL, error); + return; + } + + /* Keep on */ + ctx->step++; + connect_context_step (task); +} + +static void set_ip_family_ready (QmiClientWds *client, GAsyncResult *res, GTask *task) @@ -1285,6 +1336,64 @@ qmi_port_allocate_client_ready (MMPortQmi *qmi, } static void +wait_link_port_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + ConnectContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + ctx->link = mm_base_modem_wait_link_port_finish (modem, res, &error); + if (!ctx->link) { + complete_connect (task, NULL, error); + return; + } + + /* Keep on */ + ctx->step++; + connect_context_step (task); +} + +static void +setup_link_ready (MMPortQmi *qmi, + GAsyncResult *res, + GTask *task) +{ + ConnectContext *ctx; + GError *error = NULL; + g_autoptr(MMBaseModem) modem = NULL; + + ctx = g_task_get_task_data (task); + + ctx->link_name = mm_port_qmi_setup_link_finish (qmi, res, &ctx->mux_id, &error); + if (!ctx->link_name) { + g_prefix_error (&error, "failed to create net link for device: "); + complete_connect (task, NULL, error); + return; + } + + /* From now on link_name will be set, and we'll use that to know + * whether we should cleanup the link upon a connection failure */ + mm_obj_info (ctx->self, "net link %s created (mux id %u)", ctx->link_name, ctx->mux_id); + + /* Wait for the data port with the given interface name, which will be + * added asynchronously */ + g_object_get (ctx->self, + MM_BASE_BEARER_MODEM, &modem, + NULL); + g_assert (modem); + + mm_base_modem_wait_link_port (modem, + "net", + ctx->link_name, + WAIT_LINK_PORT_TIMEOUT_MS, + (GAsyncReadyCallback) wait_link_port_ready, + task); +} + +static void setup_data_format_ready (MMPortQmi *qmi, GAsyncResult *res, GTask *task) @@ -1296,11 +1405,30 @@ setup_data_format_ready (MMPortQmi *qmi, if (!mm_port_qmi_setup_data_format_finish (qmi, res, &error)) { /* a failure here could indicate no support for WDA Set Data Format, - * if so, just go on with the plain CTL based support */ + * if so, just go on with the plain CTL based support (and data aggregation + * protocol disabled) */ if (!g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) { complete_connect (task, NULL, g_steal_pointer (&error)); return; } + ctx->dap = QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED; + } else + ctx->dap = mm_port_qmi_get_data_aggregation_protocol (ctx->qmi); + + if ((ctx->multiplex == MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED) && + (ctx->dap == QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED)) { + complete_connect (task, NULL, + g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Cannot enable multiplex support")); + return; + } + + if ((ctx->multiplex == MM_BEARER_MULTIPLEX_SUPPORT_NONE) && + (ctx->dap != QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED)) { + complete_connect (task, NULL, + g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Cannot disable multiplex support")); + return; } /* Keep on */ @@ -1384,13 +1512,44 @@ connect_context_step (GTask *task) ctx->step++; /* fall through */ - case CONNECT_STEP_SETUP_DATA_FORMAT: + case CONNECT_STEP_SETUP_DATA_FORMAT: { + MMPortQmiSetupDataFormatAction action; + + switch (ctx->multiplex) { + case MM_BEARER_MULTIPLEX_SUPPORT_NONE: + action = MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_SET_DEFAULT; + break; + case MM_BEARER_MULTIPLEX_SUPPORT_REQUESTED: + case MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED: + action = MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_SET_MULTIPLEX; + break; + case MM_BEARER_MULTIPLEX_SUPPORT_UNKNOWN: + default: + g_assert_not_reached (); + } mm_port_qmi_setup_data_format (ctx->qmi, ctx->data, - MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_SET_DEFAULT, + action, (GAsyncReadyCallback) setup_data_format_ready, task); return; + } + + case CONNECT_STEP_SETUP_LINK: + + /* if muxing has been enabled in the port, we need to create a new link + * interface. */ + if (ctx->dap == QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAPV5 || + ctx->dap == QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAP) { + mm_port_qmi_setup_link (ctx->qmi, + ctx->data, + ctx->link_prefix_hint, + (GAsyncReadyCallback) setup_link_ready, + task); + return; + } + ctx->step++; + /* fall through */ case CONNECT_STEP_IP_METHOD: /* Once the QMI port is open, we decide the IP method we're going @@ -1480,6 +1639,28 @@ connect_context_step (GTask *task) return; } + /* If mux id given, bind mux data port */ + if (ctx->mux_id != QMI_DEVICE_MUX_ID_UNBOUND) { + g_autoptr(QmiMessageWdsBindMuxDataPortInput) input = NULL; + + mm_obj_dbg (self, "binding to mux id %d", ctx->mux_id); + input = qmi_message_wds_bind_mux_data_port_input_new (); + qmi_message_wds_bind_mux_data_port_input_set_endpoint_info ( + input, + mm_port_qmi_get_endpoint_type (ctx->qmi), + mm_port_qmi_get_endpoint_interface_number (ctx->qmi), + NULL); + qmi_message_wds_bind_mux_data_port_input_set_mux_id (input, ctx->mux_id, NULL); + + qmi_client_wds_bind_mux_data_port (ctx->client_ipv4, + input, + 10, + g_task_get_cancellable (task), + (GAsyncReadyCallback)bind_mux_data_port_ready, + task); + return; + } + ctx->step++; /* fall through */ @@ -1591,6 +1772,28 @@ connect_context_step (GTask *task) return; } + /* If mux id given, bind mux data port */ + if (ctx->mux_id != QMI_DEVICE_MUX_ID_UNBOUND) { + g_autoptr(QmiMessageWdsBindMuxDataPortInput) input = NULL; + + mm_obj_dbg (self, "binding to mux id %d", ctx->mux_id); + input = qmi_message_wds_bind_mux_data_port_input_new (); + qmi_message_wds_bind_mux_data_port_input_set_endpoint_info ( + input, + mm_port_qmi_get_endpoint_type (ctx->qmi), + mm_port_qmi_get_endpoint_interface_number (ctx->qmi), + NULL); + qmi_message_wds_bind_mux_data_port_input_set_mux_id (input, ctx->mux_id, NULL); + + qmi_client_wds_bind_mux_data_port (ctx->client_ipv6, + input, + 10, + g_task_get_cancellable (task), + (GAsyncReadyCallback)bind_mux_data_port_ready, + task); + return; + } + ctx->step++; /* fall through */ @@ -1631,7 +1834,9 @@ connect_context_step (GTask *task) ctx->step++; /* fall through */ - case CONNECT_STEP_LAST: + case CONNECT_STEP_LAST: { + MMBearerConnectResult *connect_result; + /* If one of IPv4 or IPv6 succeeds, we're connected */ if (!ctx->packet_data_handle_ipv4 && !ctx->packet_data_handle_ipv6) { GError *error; @@ -1650,7 +1855,7 @@ connect_context_step (GTask *task) } /* Port is connected; update the state */ - mm_port_set_connected (MM_PORT (ctx->data), TRUE); + mm_port_set_connected (ctx->link ? ctx->link : ctx->data, TRUE); /* Keep connection related data */ @@ -1662,7 +1867,14 @@ connect_context_step (GTask *task) } g_assert (ctx->self->priv->data == NULL); - ctx->self->priv->data = g_object_ref (ctx->data); + ctx->self->priv->data = ctx->data ? g_object_ref (ctx->data) : NULL; + g_assert (ctx->self->priv->link == NULL); + ctx->self->priv->link = ctx->link ? g_object_ref (ctx->link) : NULL; + g_assert (ctx->self->priv->mux_id == QMI_DEVICE_MUX_ID_UNBOUND); + ctx->self->priv->mux_id = ctx->mux_id; + + /* reset the link name to avoid cleaning up the link on context free */ + g_clear_pointer (&ctx->link_name, g_free); g_assert (ctx->self->priv->packet_data_handle_ipv4 == 0); g_assert (ctx->self->priv->client_ipv4 == NULL); @@ -1688,12 +1900,14 @@ connect_context_step (GTask *task) ctx->self->priv->client_ipv6 = g_object_ref (ctx->client_ipv6); } - complete_connect (task, - mm_bearer_connect_result_new (ctx->data, - ctx->ipv4_config, - ctx->ipv6_config), - NULL); + connect_result = mm_bearer_connect_result_new (ctx->link ? ctx->link : ctx->data, + ctx->ipv4_config, + ctx->ipv6_config); + mm_bearer_connect_result_set_multiplexed (connect_result, !!ctx->link); + + complete_connect (task, connect_result, NULL); return; + } default: g_assert_not_reached (); @@ -1725,14 +1939,6 @@ _connect (MMBaseBearer *_self, GTask *task; GCancellable *operation_cancellable = NULL; - if (mm_bearer_properties_get_multiplex (mm_base_bearer_peek_config (_self)) == MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED) { - g_task_report_new_error ( - self, callback, user_data, _connect, - MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, - "Multiplex support not available"); - goto out; - } - g_object_get (self, MM_BASE_BEARER_MODEM, &modem, NULL); @@ -1793,18 +1999,19 @@ _connect (MMBaseBearer *_self, goto out; } - mm_obj_dbg (self, "launching connection with QMI port (%s) and data port (%s)", - mm_port_get_device (MM_PORT (qmi)), - mm_port_get_device (data)); - ctx = g_slice_new0 (ConnectContext); ctx->self = g_object_ref (self); ctx->qmi = g_object_ref (qmi); ctx->sio_port = sio_port; + ctx->dap = mm_port_qmi_get_data_aggregation_protocol (ctx->qmi); + ctx->mux_id = QMI_DEVICE_MUX_ID_UNBOUND; ctx->data = g_object_ref (data); ctx->step = CONNECT_STEP_FIRST; ctx->ip_method = MM_BEARER_IP_METHOD_UNKNOWN; + /* If no multiplex setting given by the user, assume none */ + ctx->multiplex = MM_BEARER_MULTIPLEX_SUPPORT_NONE; + g_object_get (self, MM_BASE_BEARER_CONFIG, &properties, NULL); @@ -1815,8 +2022,9 @@ _connect (MMBaseBearer *_self, g_task_set_task_data (task, ctx, (GDestroyNotify)connect_context_free); if (properties) { - MMBearerAllowedAuth auth; - MMBearerIpFamily ip_family; + MMBearerAllowedAuth auth; + MMBearerIpFamily ip_family; + MMBearerMultiplexSupport multiplex; ctx->apn = g_strdup (mm_bearer_properties_get_apn (properties)); ctx->user = g_strdup (mm_bearer_properties_get_user (properties)); @@ -1883,6 +2091,16 @@ _connect (MMBaseBearer *_self, g_free (str); goto out; } + + multiplex = mm_bearer_properties_get_multiplex (properties); + if (multiplex != MM_BEARER_MULTIPLEX_SUPPORT_UNKNOWN) + ctx->multiplex = multiplex; + } + + if (ctx->multiplex == MM_BEARER_MULTIPLEX_SUPPORT_REQUESTED || + ctx->multiplex == MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED) { + /* the link prefix hint given must be modem-specific */ + ctx->link_prefix_hint = g_strdup_printf ("qmapmux%u.", mm_base_modem_get_dbus_id (MM_BASE_MODEM (modem))); } /* setup network cancellable */ @@ -1902,6 +2120,10 @@ _connect (MMBaseBearer *_self, g_object_unref); /* Run! */ + mm_obj_dbg (self, "launching connection with QMI port (%s) and data port (%s) (multiplex %s)", + mm_port_get_device (MM_PORT (qmi)), + mm_port_get_device (data), + mm_bearer_multiplex_support_get_string (ctx->multiplex)); connect_context_step (task); out: @@ -1958,6 +2180,18 @@ disconnect_finish (MMBaseBearer *self, } static void +cleanup_link_ready (MMPortQmi *qmi, + GAsyncResult *res, + MMBearerQmi *self) /* full reference */ +{ + g_autoptr(GError) error = NULL; + + if (!mm_port_qmi_cleanup_link_finish (qmi, res, &error)) + mm_obj_warn (self, "couldn't cleanup link: %s", error->message); + g_object_unref (self); +} + +static void reset_bearer_connection (MMBearerQmi *self, gboolean reset_ipv4, gboolean reset_ipv6) @@ -1995,6 +2229,24 @@ reset_bearer_connection (MMBearerQmi *self, } if (!self->priv->packet_data_handle_ipv4 && !self->priv->packet_data_handle_ipv6) { + if (self->priv->data) { + /* Port is disconnected; update the state */ + mm_port_set_connected (self->priv->data, FALSE); + g_clear_object (&self->priv->data); + } + if (self->priv->link) { + g_assert (self->priv->qmi); + /* Link is disconnected; update the state */ + mm_port_set_connected (self->priv->link, FALSE); + mm_port_qmi_cleanup_link (self->priv->qmi, + mm_port_get_device (self->priv->link), + self->priv->mux_id, + (GAsyncReadyCallback) cleanup_link_ready, + g_object_ref (self)); + g_clear_object (&self->priv->link); + } + self->priv->mux_id = QMI_DEVICE_MUX_ID_UNBOUND; + /* Close port if we had it explicitly open for this connection */ if (self->priv->qmi) { if (self->priv->explicit_qmi_open) { @@ -2003,11 +2255,6 @@ reset_bearer_connection (MMBearerQmi *self, } g_clear_object (&self->priv->qmi); } - if (self->priv->data) { - /* Port is disconnected; update the state */ - mm_port_set_connected (self->priv->data, FALSE); - g_clear_object (&self->priv->data); - } } } @@ -2173,7 +2420,7 @@ disconnect (MMBaseBearer *_self, if ((!self->priv->packet_data_handle_ipv4 && !self->priv->packet_data_handle_ipv6) || (!self->priv->client_ipv4 && !self->priv->client_ipv6) || - !self->priv->data || + (!self->priv->data && !self->priv->link) || !self->priv->qmi) { mm_obj_dbg (self, "no need to disconnect: QMI bearer is already disconnected"); g_task_return_boolean (task, TRUE); @@ -2239,9 +2486,8 @@ static void mm_bearer_qmi_init (MMBearerQmi *self) { /* Initialize private data */ - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, - MM_TYPE_BEARER_QMI, - MMBearerQmiPrivate); + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BEARER_QMI, MMBearerQmiPrivate); + self->priv->mux_id = QMI_DEVICE_MUX_ID_UNBOUND; } static void |