broadband-modem-qmi: keep power operation ongoing until fully finished
The GTask stored in the private modem struct indicates whether a power update operation is ongoing. We should keep this task stored always until just before completing the GTask. We also make sure that any additional step (e.g. disabling DMS event report indications) is included in the power update operation, so that they are correctly synchronized also in the modem. This fix should solve a crash triggered by a DMS event report indication being received while setting up the next power update operation. 0x0000007aa67de878 (libc.so.6 + 0x0007e878) pthread_key_delete 0x0000007aa6799d28 (libc.so.6 + 0x00039d28) gsignal 0x0000007aa6786e2c (libc.so.6 + 0x00026e2c) abort 0x0000007aa6feb188 (libglib-2.0.so.0 - gtestutils.c: 3256) g_assertion_message 0x0000007aa6feb1f8 (libglib-2.0.so.0 - gtestutils.c: 3282) g_assertion_message_expr 0x0000005a58199b7c (ModemManager - mm-broadband-modem-qmi.c: 1926) dms_set_event_report_operating_mode_activate_ready 0x0000007aa6e4b7a0 (libgio-2.0.so.0 - gtask.c: 1232) g_task_return_now 0x0000007aa6e4a6b0 (libgio-2.0.so.0 - gtask.c: 1301) g_task_return 0x0000007aa6b58400 (libqmi-glib.so.5 - qmi-dms.c: 32600) set_event_report_ready 0x0000007aa6e3642c (libgio-2.0.so.0 - gsimpleasyncresult.c: 804) g_simple_async_result_complete 0x0000007aa6e36538 (libgio-2.0.so.0 - gsimpleasyncresult.c: 816) complete_in_idle_cb 0x0000007aa6fc6390 (libglib-2.0.so.0 - gmain.c: 3444) g_main_context_dispatch 0x0000007aa6fc66c4 (libglib-2.0.so.0 - gmain.c: 4238) g_main_context_iterate 0x0000007aa6fc6a34 (libglib-2.0.so.0 - gmain.c: 4438) g_main_loop_run 0x0000005a58119274 (ModemManager - main.c: 221) main 0x0000007aa67870f8 (libc.so.6 + 0x000270f8) __libc_init_first 0x0000007aa67871c8 (libc.so.6 + 0x000271c8) __libc_start_main 0x0000005a58118aec (ModemManager + 0x000a8aec) _start
This commit is contained in:
+155
-109
@@ -1984,13 +1984,17 @@ get_cell_info (MMIfaceModem *self,
|
||||
typedef struct {
|
||||
QmiDmsOperatingMode mode;
|
||||
QmiClientDms *client;
|
||||
gboolean event_report_set;
|
||||
guint indication_id;
|
||||
guint timeout_id;
|
||||
gboolean reload;
|
||||
GError *saved_error;
|
||||
} SetOperatingModeContext;
|
||||
|
||||
static void
|
||||
set_operating_mode_context_free (SetOperatingModeContext *ctx)
|
||||
{
|
||||
g_assert (!ctx->saved_error);
|
||||
g_assert (ctx->indication_id == 0);
|
||||
g_assert (ctx->timeout_id == 0);
|
||||
g_clear_object (&ctx->client);
|
||||
@@ -2006,140 +2010,178 @@ modem_power_up_down_off_finish (MMIfaceModem *self,
|
||||
}
|
||||
|
||||
static void
|
||||
set_operating_mode_context_reset (SetOperatingModeContext *ctx)
|
||||
set_operating_mode_done (MMBroadbandModemQmi *self)
|
||||
{
|
||||
GTask *task;
|
||||
SetOperatingModeContext *ctx;
|
||||
|
||||
g_assert (self->priv->set_operating_mode_task);
|
||||
task = g_steal_pointer (&self->priv->set_operating_mode_task);
|
||||
ctx = g_task_get_task_data (task);
|
||||
|
||||
if (ctx->saved_error)
|
||||
g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
|
||||
else
|
||||
g_task_return_boolean (task, TRUE);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
static void
|
||||
dms_reload_current_operating_mode_ready (QmiClientDms *client,
|
||||
GAsyncResult *res,
|
||||
MMBroadbandModemQmi *self) /* full reference */
|
||||
{
|
||||
g_autoptr(QmiMessageDmsGetOperatingModeOutput) output = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
SetOperatingModeContext *ctx;
|
||||
|
||||
g_assert (self->priv->set_operating_mode_task);
|
||||
ctx = g_task_get_task_data (self->priv->set_operating_mode_task);
|
||||
|
||||
/* Reloading the current operating mode is only expected if the operation is
|
||||
* failed for some other reason */
|
||||
g_assert (ctx->saved_error);
|
||||
|
||||
output = qmi_client_dms_get_operating_mode_finish (client, res, &error);
|
||||
if (!output || !qmi_message_dms_get_operating_mode_output_get_result (output, &error)) {
|
||||
mm_obj_warn (self, "couldn't reload current operating mode: %s", error->message);
|
||||
} else {
|
||||
QmiDmsOperatingMode mode = QMI_DMS_OPERATING_MODE_UNKNOWN;
|
||||
|
||||
qmi_message_dms_get_operating_mode_output_get_mode (output, &mode, NULL);
|
||||
|
||||
if (mode != ctx->mode) {
|
||||
g_prefix_error (&ctx->saved_error,
|
||||
"Requested (%s) and reloaded (%s) modes did not match: ",
|
||||
qmi_dms_operating_mode_get_string (ctx->mode),
|
||||
qmi_dms_operating_mode_get_string (mode));
|
||||
} else {
|
||||
/* Reloaded mode is the one we requested! we can cleanup the saved error
|
||||
* as the operation did really not fail */
|
||||
mm_obj_info (self, "power update operation successful even after error");
|
||||
g_clear_error (&ctx->saved_error);
|
||||
}
|
||||
}
|
||||
|
||||
set_operating_mode_done (self);
|
||||
g_object_unref (self);
|
||||
}
|
||||
|
||||
static void
|
||||
set_operating_mode_reload (MMBroadbandModemQmi *self)
|
||||
{
|
||||
SetOperatingModeContext *ctx;
|
||||
|
||||
g_assert (self->priv->set_operating_mode_task);
|
||||
ctx = g_task_get_task_data (self->priv->set_operating_mode_task);
|
||||
if (ctx->reload) {
|
||||
mm_obj_dbg (self, "reload current device operating mode...");
|
||||
qmi_client_dms_get_operating_mode (ctx->client,
|
||||
NULL,
|
||||
5,
|
||||
NULL,
|
||||
(GAsyncReadyCallback)dms_reload_current_operating_mode_ready,
|
||||
g_object_ref (self));
|
||||
return;
|
||||
}
|
||||
|
||||
set_operating_mode_done (self);
|
||||
}
|
||||
|
||||
static void
|
||||
dms_set_event_report_operating_mode_deactivate_ready (QmiClientDms *client,
|
||||
GAsyncResult *res,
|
||||
MMBroadbandModemQmi *self) /* full reference */
|
||||
{
|
||||
g_autoptr(QmiMessageDmsSetEventReportOutput) output = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
g_assert (self->priv->set_operating_mode_task);
|
||||
|
||||
output = qmi_client_dms_set_event_report_finish (client, res, &error);
|
||||
if (!output || !qmi_message_dms_set_event_report_output_get_result (output, &error))
|
||||
mm_obj_dbg (self, "couldn't deregister for power indications: %s", error->message);
|
||||
|
||||
set_operating_mode_reload (self);
|
||||
g_object_unref (self);
|
||||
}
|
||||
|
||||
static void
|
||||
set_operating_mode_complete (MMBroadbandModemQmi *self)
|
||||
{
|
||||
SetOperatingModeContext *ctx;
|
||||
|
||||
g_assert (self->priv->set_operating_mode_task);
|
||||
ctx = g_task_get_task_data (self->priv->set_operating_mode_task);
|
||||
|
||||
if (ctx->timeout_id) {
|
||||
g_source_remove (ctx->timeout_id);
|
||||
ctx->timeout_id = 0;
|
||||
}
|
||||
|
||||
if (ctx->indication_id) {
|
||||
g_autoptr(QmiMessageDmsSetEventReportInput) input = NULL;
|
||||
|
||||
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
||||
ctx->indication_id = 0;
|
||||
}
|
||||
|
||||
if (ctx->event_report_set) {
|
||||
g_autoptr(QmiMessageDmsSetEventReportInput) input = NULL;
|
||||
|
||||
input = qmi_message_dms_set_event_report_input_new ();
|
||||
qmi_message_dms_set_event_report_input_set_operating_mode_reporting (input, FALSE, NULL);
|
||||
qmi_client_dms_set_event_report (ctx->client, input, 5, NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dms_check_current_operating_mode_ready (QmiClientDms *client,
|
||||
GAsyncResult *res,
|
||||
GTask *task)
|
||||
{
|
||||
QmiMessageDmsGetOperatingModeOutput *output = NULL;
|
||||
GError *error = NULL;
|
||||
SetOperatingModeContext *ctx;
|
||||
|
||||
ctx = g_task_get_task_data (task);
|
||||
|
||||
output = qmi_client_dms_get_operating_mode_finish (client, res, &error);
|
||||
if (!output) {
|
||||
g_prefix_error (&error, "QMI operation failed: ");
|
||||
g_task_return_error (task, error);
|
||||
} else if (!qmi_message_dms_get_operating_mode_output_get_result (output, &error)) {
|
||||
g_prefix_error (&error, "Couldn't get operating mode: ");
|
||||
g_task_return_error (task, error);
|
||||
} else {
|
||||
QmiDmsOperatingMode mode = QMI_DMS_OPERATING_MODE_UNKNOWN;
|
||||
|
||||
qmi_message_dms_get_operating_mode_output_get_mode (output, &mode, NULL);
|
||||
|
||||
if (mode == ctx->mode)
|
||||
g_task_return_boolean (task, TRUE);
|
||||
else
|
||||
g_task_return_new_error (task,
|
||||
MM_CORE_ERROR,
|
||||
MM_CORE_ERROR_FAILED,
|
||||
"Requested mode (%s) and mode received (%s) did not match",
|
||||
qmi_dms_operating_mode_get_string (ctx->mode),
|
||||
qmi_dms_operating_mode_get_string (mode));
|
||||
qmi_client_dms_set_event_report (ctx->client,
|
||||
input,
|
||||
5,
|
||||
NULL,
|
||||
(GAsyncReadyCallback)dms_set_event_report_operating_mode_deactivate_ready,
|
||||
g_object_ref (self));
|
||||
return;
|
||||
}
|
||||
|
||||
if (output)
|
||||
qmi_message_dms_get_operating_mode_output_unref (output);
|
||||
|
||||
g_object_unref (task);
|
||||
set_operating_mode_reload (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dms_set_operating_mode_timeout_cb (MMBroadbandModemQmi *self)
|
||||
{
|
||||
GTask *task;
|
||||
SetOperatingModeContext *ctx;
|
||||
|
||||
g_assert (self->priv->set_operating_mode_task);
|
||||
task = g_steal_pointer (&self->priv->set_operating_mode_task);
|
||||
ctx = g_task_get_task_data (task);
|
||||
ctx = g_task_get_task_data (self->priv->set_operating_mode_task);
|
||||
|
||||
mm_obj_warn (self, "Power update operation timed out");
|
||||
|
||||
set_operating_mode_context_reset (ctx);
|
||||
|
||||
mm_obj_dbg (self, "check current device operating mode...");
|
||||
qmi_client_dms_get_operating_mode (ctx->client,
|
||||
NULL,
|
||||
5,
|
||||
NULL,
|
||||
(GAsyncReadyCallback)dms_check_current_operating_mode_ready,
|
||||
task);
|
||||
/* Save a timeout error, but also request to reload, in case the modem
|
||||
* failed to send the indication even if it did update the power state */
|
||||
ctx->saved_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_TIMEOUT, "Power update operation timed out");
|
||||
ctx->reload = TRUE;
|
||||
|
||||
set_operating_mode_complete (self);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
set_operating_mode_complete (MMBroadbandModemQmi *self,
|
||||
GError *error)
|
||||
{
|
||||
GTask *task;
|
||||
SetOperatingModeContext *ctx;
|
||||
|
||||
g_assert (self->priv->set_operating_mode_task);
|
||||
task = g_steal_pointer (&self->priv->set_operating_mode_task);
|
||||
ctx = g_task_get_task_data (task);
|
||||
|
||||
set_operating_mode_context_reset (ctx);
|
||||
|
||||
if (error)
|
||||
g_task_return_error (task, error);
|
||||
else
|
||||
g_task_return_boolean (task, TRUE);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
static void
|
||||
power_event_report_indication_cb (QmiClientDms *client,
|
||||
QmiIndicationDmsEventReportOutput *output,
|
||||
MMBroadbandModemQmi *self)
|
||||
{
|
||||
QmiDmsOperatingMode state;
|
||||
GError *error = NULL;
|
||||
SetOperatingModeContext *ctx;
|
||||
|
||||
if (!qmi_indication_dms_event_report_output_get_operating_mode (output, &state, NULL)) {
|
||||
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid power indication received");
|
||||
set_operating_mode_complete (self, error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_assert (self->priv->set_operating_mode_task);
|
||||
ctx = g_task_get_task_data (self->priv->set_operating_mode_task);
|
||||
|
||||
if (ctx->mode == state) {
|
||||
g_assert (!ctx->saved_error);
|
||||
if (!qmi_indication_dms_event_report_output_get_operating_mode (output, &state, NULL))
|
||||
ctx->saved_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid power indication received");
|
||||
else if (ctx->mode != state)
|
||||
ctx->saved_error = g_error_new (MM_CORE_ERROR,
|
||||
MM_CORE_ERROR_FAILED,
|
||||
"Requested (%s) and notified (%s) modes did not match",
|
||||
qmi_dms_operating_mode_get_string (ctx->mode),
|
||||
qmi_dms_operating_mode_get_string (state));
|
||||
else
|
||||
mm_obj_dbg (self, "Power state successfully updated: '%s'", qmi_dms_operating_mode_get_string (state));
|
||||
set_operating_mode_complete (self, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
error = g_error_new (MM_CORE_ERROR,
|
||||
MM_CORE_ERROR_FAILED,
|
||||
"Requested mode (%s) and mode received (%s) did not match",
|
||||
qmi_dms_operating_mode_get_string (ctx->mode),
|
||||
qmi_dms_operating_mode_get_string (state));
|
||||
set_operating_mode_complete (self, error);
|
||||
set_operating_mode_complete (self);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -2185,20 +2227,23 @@ dms_set_operating_mode_ready (QmiClientDms *client,
|
||||
if (g_error_matches (error, QMI_CORE_ERROR, QMI_CORE_ERROR_UNSUPPORTED)) {
|
||||
mm_obj_dbg (self, "device doesn't support operating mode setting: ignoring power update");
|
||||
g_clear_error (&error);
|
||||
set_operating_mode_complete (self, NULL);
|
||||
} else if (error)
|
||||
set_operating_mode_complete (self, error);
|
||||
else if (ctx->timeout_id)
|
||||
set_operating_mode_complete (self);
|
||||
} else if (error) {
|
||||
ctx->saved_error = error;
|
||||
set_operating_mode_complete (self);
|
||||
} else if (ctx->event_report_set) {
|
||||
g_assert (ctx->timeout_id);
|
||||
mm_obj_dbg (self, "operating mode request sent, waiting for power update indication");
|
||||
else
|
||||
set_operating_mode_complete (self, NULL);
|
||||
} else {
|
||||
mm_obj_dbg (self, "operating mode request finished: no need to wait for indications");
|
||||
set_operating_mode_complete (self);
|
||||
}
|
||||
|
||||
g_object_unref (self);
|
||||
}
|
||||
|
||||
static void
|
||||
dms_set_operating_mode (MMBroadbandModemQmi *self,
|
||||
gboolean supports_power_indications)
|
||||
dms_set_operating_mode (MMBroadbandModemQmi *self)
|
||||
{
|
||||
g_autoptr (QmiMessageDmsSetOperatingModeInput) input = NULL;
|
||||
SetOperatingModeContext *ctx;
|
||||
@@ -2215,7 +2260,7 @@ dms_set_operating_mode (MMBroadbandModemQmi *self,
|
||||
(GAsyncReadyCallback)dms_set_operating_mode_ready,
|
||||
g_object_ref (self));
|
||||
|
||||
if (supports_power_indications) {
|
||||
if (ctx->event_report_set) {
|
||||
mm_obj_dbg (self, "Starting timeout for indication receiving for 10 seconds");
|
||||
ctx->timeout_id = g_timeout_add_seconds (10,
|
||||
(GSourceFunc) dms_set_operating_mode_timeout_cb,
|
||||
@@ -2231,7 +2276,6 @@ dms_set_event_report_operating_mode_activate_ready (QmiClientDms *client,
|
||||
g_autoptr(QmiMessageDmsSetEventReportOutput) output = NULL;
|
||||
GError *error = NULL;
|
||||
SetOperatingModeContext *ctx;
|
||||
gboolean supports_power_indications = TRUE;
|
||||
|
||||
g_assert (self->priv->set_operating_mode_task);
|
||||
ctx = g_task_get_task_data (self->priv->set_operating_mode_task);
|
||||
@@ -2241,17 +2285,20 @@ dms_set_event_report_operating_mode_activate_ready (QmiClientDms *client,
|
||||
if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_MISSING_ARGUMENT)) {
|
||||
mm_obj_dbg (self, "device doesn't support power indication registration: ignore it and continue");
|
||||
g_clear_error (&error);
|
||||
supports_power_indications = FALSE;
|
||||
} else {
|
||||
g_prefix_error (&error, "Couldn't register for power indications: ");
|
||||
set_operating_mode_complete (self, error);
|
||||
ctx->saved_error = error;
|
||||
set_operating_mode_complete (self);
|
||||
g_object_unref (self);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
mm_obj_dbg (self, "device supports power indications");
|
||||
ctx->event_report_set = TRUE;
|
||||
}
|
||||
|
||||
g_assert (ctx->indication_id == 0);
|
||||
if (supports_power_indications) {
|
||||
if (ctx->event_report_set) {
|
||||
ctx->indication_id = g_signal_connect (client,
|
||||
"event-report",
|
||||
G_CALLBACK (power_event_report_indication_cb),
|
||||
@@ -2259,8 +2306,7 @@ dms_set_event_report_operating_mode_activate_ready (QmiClientDms *client,
|
||||
mm_obj_dbg (self, "Power operation is pending");
|
||||
}
|
||||
|
||||
dms_set_operating_mode (self,
|
||||
supports_power_indications);
|
||||
dms_set_operating_mode (self);
|
||||
g_object_unref (self);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user