diff options
-rw-r--r-- | src/qmi-firmware-update/qfu-udev-helpers.c | 157 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-udev-helpers.h | 8 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-updater.c | 47 |
3 files changed, 212 insertions, 0 deletions
diff --git a/src/qmi-firmware-update/qfu-udev-helpers.c b/src/qmi-firmware-update/qfu-udev-helpers.c index 32d8e48..90016bf 100644 --- a/src/qmi-firmware-update/qfu-udev-helpers.c +++ b/src/qmi-firmware-update/qfu-udev-helpers.c @@ -108,3 +108,160 @@ qfu_udev_helper_get_sysfs_path (GFile *file, g_object_unref (udev); return sysfs_path; } + +/******************************************************************************/ + +#define WAIT_FOR_TTY_TIMEOUT_SECS 15 + +typedef struct { + GUdevClient *udev; + gchar *sysfs_path; + guint timeout_id; + gulong uevent_id; + gulong cancellable_id; +} WaitForTtyContext; + +static void +wait_for_tty_context_free (WaitForTtyContext *ctx) +{ + g_assert (!ctx->timeout_id); + g_assert (!ctx->uevent_id); + g_assert (!ctx->cancellable_id); + + g_object_unref (ctx->udev); + g_free (ctx->sysfs_path); + g_slice_free (WaitForTtyContext, ctx); +} + +GFile * +qfu_udev_helper_wait_for_tty_finish (GAsyncResult *res, + GError **error) +{ + return G_FILE (g_task_propagate_pointer (G_TASK (res), error)); +} + +static void +handle_uevent (GUdevClient *client, + const char *action, + GUdevDevice *device, + GTask *task) +{ + gchar *sysfs_path; + WaitForTtyContext *ctx; + + ctx = (WaitForTtyContext *) g_task_get_task_data (task); + + if (!g_str_equal (action, "add") && !g_str_equal (action, "move") && !g_str_equal (action, "change")) + return; + + sysfs_path = qfu_udev_helper_get_udev_device_sysfs_path (device, NULL); + if (!sysfs_path) + return; + + /* If sysfs_path matches and driver is 'qcserial', we're done */ + if ((g_strcmp0 (sysfs_path, ctx->sysfs_path) == 0) && + (g_strcmp0 (g_udev_device_get_property (device, "ID_USB_DRIVER"), "qcserial") == 0)) { + gchar *path; + + /* Disconnect this handler */ + g_signal_handler_disconnect (ctx->udev, ctx->uevent_id); + ctx->uevent_id = 0; + + /* Disconnect the other handlers */ + g_cancellable_disconnect (g_task_get_cancellable (task), ctx->cancellable_id); + ctx->cancellable_id = 0; + g_source_remove (ctx->timeout_id); + ctx->timeout_id = 0; + + path = g_strdup_printf ("/dev/%s", g_udev_device_get_name (device)); + g_task_return_pointer (task, g_file_new_for_path (path), g_object_unref); + g_object_unref (task); + g_free (path); + } + + g_free (sysfs_path); +} + +static gboolean +wait_for_tty_timed_out (GTask *task) +{ + WaitForTtyContext *ctx; + + ctx = (WaitForTtyContext *) g_task_get_task_data (task); + + /* Disconnect this handler */ + ctx->timeout_id = 0; + + /* Disconnect the other handlers */ + g_cancellable_disconnect (g_task_get_cancellable (task), ctx->cancellable_id); + ctx->cancellable_id = 0; + g_signal_handler_disconnect (ctx->udev, ctx->uevent_id); + ctx->uevent_id = 0; + + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, + "waiting for TTY at '%s' timed out", + ctx->sysfs_path); + g_object_unref (task); + return FALSE; +} + +static void +wait_for_tty_cancelled (GCancellable *cancellable, + GTask *task) +{ + WaitForTtyContext *ctx; + + ctx = (WaitForTtyContext *) g_task_get_task_data (task); + + /* Disconnect this handler */ + g_cancellable_disconnect (g_task_get_cancellable (task), ctx->cancellable_id); + ctx->cancellable_id = 0; + + /* Disconnect the other handlers */ + g_source_remove (ctx->timeout_id); + ctx->timeout_id = 0; + g_signal_handler_disconnect (ctx->udev, ctx->uevent_id); + ctx->uevent_id = 0; + + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, + "waiting for TTY at '%s' cancelled", + ctx->sysfs_path); + g_object_unref (task); +} + +void +qfu_udev_helper_wait_for_tty (const gchar *sysfs_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + static const gchar *subsys_monitor[] = { "tty", NULL }; + GTask *task; + WaitForTtyContext *ctx; + + ctx = g_slice_new0 (WaitForTtyContext); + ctx->sysfs_path = g_strdup (sysfs_path); + ctx->udev = g_udev_client_new (subsys_monitor); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify) wait_for_tty_context_free); + + /* Monitor for tty additions. */ + ctx->uevent_id = g_signal_connect (ctx->udev, + "uevent", + G_CALLBACK (handle_uevent), + task); + + /* Allow cancellation */ + ctx->cancellable_id = g_cancellable_connect (cancellable, + (GCallback) wait_for_tty_cancelled, + task, + NULL); + + /* And also, setup a timeout to avoid waiting forever. */ + ctx->timeout_id = g_timeout_add_seconds (WAIT_FOR_TTY_TIMEOUT_SECS, + (GSourceFunc) wait_for_tty_timed_out, + task); + + /* Note: task ownership is shared between the signals and the timeout */ +} diff --git a/src/qmi-firmware-update/qfu-udev-helpers.h b/src/qmi-firmware-update/qfu-udev-helpers.h index e12bd80..8eedb7b 100644 --- a/src/qmi-firmware-update/qfu-udev-helpers.h +++ b/src/qmi-firmware-update/qfu-udev-helpers.h @@ -23,6 +23,7 @@ #define QFU_UDEV_HELPERS_H #include <gio/gio.h> +#include <gudev/gudev.h> G_BEGIN_DECLS @@ -32,6 +33,13 @@ gchar *qfu_udev_helper_get_sysfs_path (GFile *file, const gchar *const *subsys, GError **error); +void qfu_udev_helper_wait_for_tty (const gchar *sysfs_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GFile *qfu_udev_helper_wait_for_tty_finish (GAsyncResult *res, + GError **error); + G_END_DECLS #endif /* QFU_UDEV_HELPERS_H */ diff --git a/src/qmi-firmware-update/qfu-updater.c b/src/qmi-firmware-update/qfu-updater.c index f507f18..64b6c67 100644 --- a/src/qmi-firmware-update/qfu-updater.c +++ b/src/qmi-firmware-update/qfu-updater.c @@ -58,6 +58,7 @@ typedef enum { RUN_CONTEXT_STEP_OFFLINE, RUN_CONTEXT_STEP_RESET, RUN_CONTEXT_STEP_CLEANUP_QMI_DEVICE, + RUN_CONTEXT_STEP_WAIT_FOR_TTY, RUN_CONTEXT_STEP_CLEANUP_IMAGE, RUN_CONTEXT_STEP_LAST } RunContextStep; @@ -75,11 +76,15 @@ typedef struct { /* QMI device and client */ QmiDevice *qmi_device; QmiClientDms *qmi_client; + /* TTY file */ + GFile *tty; } RunContext; static void run_context_free (RunContext *ctx) { + if (ctx->tty) + g_object_unref (ctx->tty); if (ctx->qmi_client) { g_assert (ctx->qmi_device); qmi_device_release_client (ctx->qmi_device, @@ -148,6 +153,47 @@ run_context_step_cleanup_image (GTask *task) } static void +wait_for_tty_ready (gpointer unused, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + RunContext *ctx; + gchar *tty_path; + + ctx = (RunContext *) g_task_get_task_data (task); + + ctx->tty = qfu_udev_helper_wait_for_tty_finish (res, &error); + if (!ctx->tty) { + g_prefix_error (&error, "error waiting for TTY: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + tty_path = g_file_get_path (ctx->tty); + g_debug ("[qfu-updater] TTY device found: %s", tty_path); + g_free (tty_path); + + /* Go on */ + run_context_step_next (task, ctx->step + 1); +} + +static void +run_context_step_wait_for_tty (GTask *task) +{ + RunContext *ctx; + + ctx = (RunContext *) g_task_get_task_data (task); + + g_debug ("[qfu-updater] reset requested, now waiting for TTY device..."); + qfu_udev_helper_wait_for_tty (ctx->sysfs_path, + g_task_get_cancellable (task), + (GAsyncReadyCallback) wait_for_tty_ready, + task); +} + +static void qmi_client_release_ready (QmiDevice *device, GAsyncResult *res, GTask *task) @@ -586,6 +632,7 @@ static const RunContextStepFunc run_context_step_func[] = { [RUN_CONTEXT_STEP_OFFLINE] = run_context_step_offline, [RUN_CONTEXT_STEP_RESET] = run_context_step_reset, [RUN_CONTEXT_STEP_CLEANUP_QMI_DEVICE] = run_context_step_cleanup_qmi_device, + [RUN_CONTEXT_STEP_WAIT_FOR_TTY] = run_context_step_wait_for_tty, [RUN_CONTEXT_STEP_CLEANUP_IMAGE] = run_context_step_cleanup_image, }; |