diff options
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | src/mm-port-qmi.c | 351 |
2 files changed, 295 insertions, 58 deletions
diff --git a/configure.ac b/configure.ac index 8e8bda4e..26711ae5 100644 --- a/configure.ac +++ b/configure.ac @@ -289,7 +289,7 @@ dnl----------------------------------------------------------------------------- dnl QMI support (enabled by default) dnl -LIBQMI_VERSION=1.13.5 +LIBQMI_VERSION=1.13.6 AC_ARG_WITH(qmi, AS_HELP_STRING([--without-qmi], [Build without QMI support]), [], [with_qmi=yes]) AM_CONDITIONAL(WITH_QMI, test "x$with_qmi" = "xyes") diff --git a/src/mm-port-qmi.c b/src/mm-port-qmi.c index cca71b00..fa723dbb 100644 --- a/src/mm-port-qmi.c +++ b/src/mm-port-qmi.c @@ -163,22 +163,53 @@ mm_port_qmi_allocate_client (MMPortQmi *self, /*****************************************************************************/ +typedef enum { + PORT_OPEN_STEP_FIRST, + PORT_OPEN_STEP_CHECK_OPENING, + PORT_OPEN_STEP_CHECK_ALREADY_OPEN, + PORT_OPEN_STEP_DEVICE_NEW, + PORT_OPEN_STEP_OPEN_WITHOUT_DATA_FORMAT, + PORT_OPEN_STEP_GET_KERNEL_DATA_FORMAT, + PORT_OPEN_STEP_ALLOCATE_WDA_CLIENT, + PORT_OPEN_STEP_GET_WDA_DATA_FORMAT, + PORT_OPEN_STEP_CHECK_DATA_FORMAT, + PORT_OPEN_STEP_SET_KERNEL_DATA_FORMAT, + PORT_OPEN_STEP_OPEN_WITH_DATA_FORMAT, + PORT_OPEN_STEP_LAST +} PortOpenStep; + typedef struct { MMPortQmi *self; - gboolean set_data_format; GSimpleAsyncResult *result; GCancellable *cancellable; + QmiDevice *device; + QmiClient *wda; + GError *error; + PortOpenStep step; + gboolean set_data_format; + QmiDeviceExpectedDataFormat kernel_data_format; + QmiWdaLinkLayerProtocol llp; } PortOpenContext; static void port_open_context_complete_and_free (PortOpenContext *ctx) { g_simple_async_result_complete_in_idle (ctx->result); + if (ctx->wda) { + g_assert (ctx->device); + qmi_device_release_client (ctx->device, + ctx->wda, + QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID, + 3, NULL, NULL, NULL); + g_object_unref (ctx->wda); + } + if (ctx->device) + g_object_unref (ctx->device); if (ctx->cancellable) g_object_unref (ctx->cancellable); g_object_unref (ctx->result); g_object_unref (ctx->self); - g_free (ctx); + g_slice_free (PortOpenContext, ctx); } gboolean @@ -189,23 +220,77 @@ mm_port_qmi_open_finish (MMPortQmi *self, return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); } +static void port_open_context_step (PortOpenContext *ctx); + static void -qmi_device_open_ready (QmiDevice *qmi_device, +qmi_device_open_second_ready (QmiDevice *qmi_device, + GAsyncResult *res, + PortOpenContext *ctx) +{ + qmi_device_open_finish (qmi_device, res, &ctx->error); + + /* In both error and success, we go to last step */ + ctx->step = PORT_OPEN_STEP_LAST; + port_open_context_step (ctx); +} + +static void +get_data_format_ready (QmiClientWda *client, GAsyncResult *res, PortOpenContext *ctx) { - GError *error = NULL; + QmiMessageWdaGetDataFormatOutput *output; + + output = qmi_client_wda_get_data_format_finish (client, res, NULL); + if (!output || + !qmi_message_wda_get_data_format_output_get_result (output, NULL) || + !qmi_message_wda_get_data_format_output_get_link_layer_protocol (output, &ctx->llp, NULL)) + /* If loading WDA data format fails, fallback to 802.3 requested via CTL */ + ctx->step = PORT_OPEN_STEP_OPEN_WITH_DATA_FORMAT; + else + /* Go on to next step */ + ctx->step++; + + if (output) + qmi_message_wda_get_data_format_output_unref (output); + + port_open_context_step (ctx); +} - /* Reset the opening flag */ - ctx->self->priv->opening = FALSE; +static void +allocate_client_wda_ready (QmiDevice *device, + GAsyncResult *res, + PortOpenContext *ctx) +{ + ctx->wda = qmi_device_allocate_client_finish (device, res, NULL); + if (!ctx->wda) { + /* If no WDA supported, then we just fallback to reopening explicitly + * requesting 802.3 in the CTL service. */ + ctx->step = PORT_OPEN_STEP_OPEN_WITH_DATA_FORMAT; + port_open_context_step (ctx); + return; + } - if (!qmi_device_open_finish (qmi_device, res, &error)) { - g_clear_object (&ctx->self->priv->qmi_device); - g_simple_async_result_take_error (ctx->result, error); - } else - g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + /* Go on to next step */ + ctx->step++; + port_open_context_step (ctx); +} - port_open_context_complete_and_free (ctx); +static void +qmi_device_open_first_ready (QmiDevice *qmi_device, + GAsyncResult *res, + PortOpenContext *ctx) +{ + 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_context_step (ctx); } static void @@ -213,26 +298,203 @@ qmi_device_new_ready (GObject *unused, GAsyncResult *res, PortOpenContext *ctx) { - GError *error = NULL; - QmiDeviceOpenFlags flags = (QMI_DEVICE_OPEN_FLAGS_VERSION_INFO | QMI_DEVICE_OPEN_FLAGS_PROXY); + /* 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_context_step (ctx); +} - ctx->self->priv->qmi_device = qmi_device_new_finish (res, &error); - if (!ctx->self->priv->qmi_device) { - g_simple_async_result_take_error (ctx->result, error); - port_open_context_complete_and_free (ctx); +static void +port_open_context_step (PortOpenContext *ctx) +{ + switch (ctx->step) { + case PORT_OPEN_STEP_FIRST: + mm_dbg ("Opening QMI device..."); + ctx->step++; + /* Fall down to next step */ + + case PORT_OPEN_STEP_CHECK_OPENING: + mm_dbg ("Checking if QMI device already opening..."); + if (ctx->self->priv->opening) { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_IN_PROGRESS, + "QMI device already being opened"); + port_open_context_complete_and_free (ctx); + return; + } + ctx->step++; + /* Fall down to next step */ + + case PORT_OPEN_STEP_CHECK_ALREADY_OPEN: + mm_dbg ("Checking if QMI device already open..."); + if (ctx->self->priv->qmi_device) { + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + port_open_context_complete_and_free (ctx); + return; + } + ctx->step++; + /* Fall down to next step */ + + case PORT_OPEN_STEP_DEVICE_NEW: { + GFile *file; + gchar *fullpath; + + fullpath = g_strdup_printf ("/dev/%s", mm_port_get_device (MM_PORT (ctx->self))); + file = g_file_new_for_path (fullpath); + + /* We flag in this point that we're opening. From now on, if we stop + * for whatever reason, we should clear this flag. We do this by ensuring + * that all callbacks go through the LAST step for completing. */ + ctx->self->priv->opening = TRUE; + + mm_dbg ("Creating QMI device..."); + qmi_device_new (file, + ctx->cancellable, + (GAsyncReadyCallback) qmi_device_new_ready, + ctx); + + g_free (fullpath); + g_object_unref (file); return; } - if (ctx->set_data_format) - flags |= (QMI_DEVICE_OPEN_FLAGS_NET_802_3 | QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER); + case PORT_OPEN_STEP_OPEN_WITHOUT_DATA_FORMAT: + /* Now open the QMI device without any data format CTL flag */ + mm_dbg ("Opening device without data format update..."); + qmi_device_open (ctx->device, + (QMI_DEVICE_OPEN_FLAGS_VERSION_INFO | + QMI_DEVICE_OPEN_FLAGS_PROXY), + 10, + ctx->cancellable, + (GAsyncReadyCallback) qmi_device_open_first_ready, + ctx); + return; + + case PORT_OPEN_STEP_GET_KERNEL_DATA_FORMAT: + mm_dbg ("Querying kernel data format..."); + /* Try to gather expected data format from the sysfs file */ + ctx->kernel_data_format = qmi_device_get_expected_data_format (ctx->device, NULL); + /* If data format cannot be retrieved, we fallback to 802.3 via CTL */ + if (ctx->kernel_data_format == QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN) { + ctx->step = PORT_OPEN_STEP_OPEN_WITH_DATA_FORMAT; + port_open_context_step (ctx); + return; + } + ctx->step++; + /* Fall down to next step */ + + case PORT_OPEN_STEP_ALLOCATE_WDA_CLIENT: + /* Allocate WDA client */ + mm_dbg ("Allocating WDA client..."); + qmi_device_allocate_client (ctx->device, + QMI_SERVICE_WDA, + QMI_CID_NONE, + 10, + ctx->cancellable, + (GAsyncReadyCallback) allocate_client_wda_ready, + ctx); + return; + + case PORT_OPEN_STEP_GET_WDA_DATA_FORMAT: + /* If we have WDA client, query current data format */ + g_assert (ctx->wda); + mm_dbg ("Querying device data format..."); + qmi_client_wda_get_data_format (QMI_CLIENT_WDA (ctx->wda), + NULL, + 10, + ctx->cancellable, + (GAsyncReadyCallback) get_data_format_ready, + ctx); + return; + + case PORT_OPEN_STEP_CHECK_DATA_FORMAT: + /* We now have the WDA data format and the kernel data format, if they're + * equal, we're done */ + mm_dbg ("Checking data format: kernel %s, device %s", + qmi_device_expected_data_format_get_string (ctx->kernel_data_format), + qmi_wda_link_layer_protocol_get_string (ctx->llp)); + if ((ctx->kernel_data_format == QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3 && + ctx->llp == QMI_WDA_LINK_LAYER_PROTOCOL_802_3) || + (ctx->kernel_data_format == QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP && + ctx->llp == QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP)) { + ctx->step = PORT_OPEN_STEP_LAST; + port_open_context_step (ctx); + return; + } + + ctx->step++; + /* Fall down to next step */ + + case PORT_OPEN_STEP_SET_KERNEL_DATA_FORMAT: + /* Update the data format to be expected by the kernel */ + mm_dbg ("Updating kernel data format: %s", qmi_wda_link_layer_protocol_get_string (ctx->llp)); + if (ctx->llp == QMI_WDA_LINK_LAYER_PROTOCOL_802_3) + ctx->kernel_data_format = QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3; + else if (ctx->llp == QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP) + ctx->kernel_data_format = QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP; + else + g_assert_not_reached (); + + /* Regardless of the output, we're done after this action */ + qmi_device_set_expected_data_format (ctx->device, + ctx->kernel_data_format, + &ctx->error); + ctx->step = PORT_OPEN_STEP_LAST; + port_open_context_step (ctx); + return; - /* Now open the QMI device */ - qmi_device_open (ctx->self->priv->qmi_device, - flags, - 10, - ctx->cancellable, - (GAsyncReadyCallback)qmi_device_open_ready, - ctx); + case PORT_OPEN_STEP_OPEN_WITH_DATA_FORMAT: + /* Need to reopen setting 802.3 using CTL */ + mm_dbg ("Closing device to reopen it right away..."); + if (!qmi_device_close (ctx->device, &ctx->error)) { + mm_warn ("Couldn't close QMI device to reopen it"); + ctx->step = PORT_OPEN_STEP_LAST; + port_open_context_step (ctx); + return; + } + + mm_dbg ("Reopening device with data format..."); + qmi_device_open (ctx->device, + (QMI_DEVICE_OPEN_FLAGS_VERSION_INFO | + QMI_DEVICE_OPEN_FLAGS_PROXY | + QMI_DEVICE_OPEN_FLAGS_NET_802_3 | + QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER), + 10, + ctx->cancellable, + (GAsyncReadyCallback) qmi_device_open_second_ready, + ctx); + return; + + case PORT_OPEN_STEP_LAST: + mm_dbg ("QMI port open operation finished"); + + /* Reset opening flag */ + ctx->self->priv->opening = FALSE; + + if (ctx->error) { + /* Propagate error */ + if (ctx->device) + qmi_device_close (ctx->device, NULL); + g_simple_async_result_take_error (ctx->result, ctx->error); + ctx->error = NULL; + } else { + /* Store device in private info */ + g_assert (ctx->device); + g_assert (!ctx->self->priv->qmi_device); + ctx->self->priv->qmi_device = g_object_ref (ctx->device); + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + } + port_open_context_complete_and_free (ctx); + return; + } } void @@ -242,48 +504,23 @@ mm_port_qmi_open (MMPortQmi *self, GAsyncReadyCallback callback, gpointer user_data) { - GFile *file; - gchar *fullpath; PortOpenContext *ctx; g_return_if_fail (MM_IS_PORT_QMI (self)); - ctx = g_new0 (PortOpenContext, 1); + ctx = g_slice_new0 (PortOpenContext); ctx->self = g_object_ref (self); + ctx->step = PORT_OPEN_STEP_FIRST; ctx->set_data_format = set_data_format; + ctx->kernel_data_format = QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN; + ctx->llp = QMI_WDA_LINK_LAYER_PROTOCOL_UNKNOWN; ctx->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, mm_port_qmi_open); ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL; - if (self->priv->opening) { - g_simple_async_result_set_error (ctx->result, - MM_CORE_ERROR, - MM_CORE_ERROR_IN_PROGRESS, - "QMI device already being opened"); - port_open_context_complete_and_free (ctx); - return; - } - - if (self->priv->qmi_device) { - g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); - port_open_context_complete_and_free (ctx); - return; - } - - fullpath = g_strdup_printf ("/dev/%s", - mm_port_get_device (MM_PORT (self))); - file = g_file_new_for_path (fullpath); - - self->priv->opening = TRUE; - qmi_device_new (file, - ctx->cancellable, - (GAsyncReadyCallback)qmi_device_new_ready, - ctx); - - g_free (fullpath); - g_object_unref (file); + port_open_context_step (ctx); } gboolean |