diff options
author | Aleksander Morgado <aleksander@aleksander.es> | 2016-11-25 19:03:02 +0100 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2017-01-16 11:24:11 +0100 |
commit | 117775369f0b7e4e8151d4ebf0a249a9e50a5c1a (patch) | |
tree | 7fea8556966e0c2357d70cb1a1df899fa325da67 | |
parent | 42eca204b98155c6d7a34f36fc00c1f19a1455fb (diff) | |
download | external_libqmi-117775369f0b7e4e8151d4ebf0a249a9e50a5c1a.zip external_libqmi-117775369f0b7e4e8151d4ebf0a249a9e50a5c1a.tar.gz external_libqmi-117775369f0b7e4e8151d4ebf0a249a9e50a5c1a.tar.bz2 |
qmi-firmware-update: new QfuUpdater with the core upgrade logic
Initially, load sysfs path and setup state machine.
-rw-r--r-- | configure.ac | 47 | ||||
-rw-r--r-- | src/qmi-firmware-update/Makefile.am | 4 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-main.c | 60 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-udev-helpers.c | 110 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-udev-helpers.h | 37 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-updater.c | 206 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-updater.h | 64 |
7 files changed, 505 insertions, 23 deletions
diff --git a/configure.ac b/configure.ac index 1238945..311c10e 100644 --- a/configure.ac +++ b/configure.ac @@ -64,9 +64,13 @@ AC_SUBST(QMI_GLIB_LT_CURRENT) AC_SUBST(QMI_GLIB_LT_REVISION) AC_SUBST(QMI_GLIB_LT_AGE) -dnl Dependencies +dnl Required dependency versions +GLIB_REQUIRED=2.36 +GUDEV_REQUIRED=147 + +dnl GLib, GIO... PKG_CHECK_MODULES(GLIB, - glib-2.0 >= 2.36 + glib-2.0 >= $GLIB_REQUIRED gobject-2.0 gio-2.0 gio-unix-2.0) @@ -76,6 +80,26 @@ AC_SUBST(GLIB_LIBS) GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0` AC_SUBST(GLIB_MKENUMS) +dnl GUdev +PKG_CHECK_MODULES(GUDEV, + [gudev-1.0 >= $GUDEV_REQUIRED], + [have_gudev=yes],[have_gudev=no]) +AC_SUBST(GUDEV_CFLAGS) +AC_SUBST(GUDEV_LIBS) + +dnl qmi-firmware-update is optional, enabled by default +AC_ARG_ENABLE([firmware-update], + AS_HELP_STRING([--enable-firmware-update], + [enable compilation of `qmi-firmware-update' [default=yes]]), + [build_firmware_update=$enableval], + [build_firmware_update=yes]) +if test "x$build_firmware_update" = "xyes"; then + if test "x$have_gudev" = "xno"; then + AC_MSG_ERROR([Cannot build `qmi-firmware-update' if GUDev >= GUDEV_REQUIRED is not available. Install it, or otherwise configure using --disable-firmware-update to disable building `qmi-firmware-update'.]) + fi +fi +AM_CONDITIONAL([BUILD_FIRMWARE_UPDATE], [test "x$build_firmware_update" = "xyes"]) + dnl Documentation GTK_DOC_CHECK(1.0) @@ -166,11 +190,16 @@ echo " libqmi (libqmi-glib, qmicli) $VERSION ============================================== - compiler: ${CC} - cflags: ${CFLAGS} - Maintainer mode: ${USE_MAINTAINER_MODE} - udev base directory: ${UDEV_BASE_DIR} - Documentation: ${enable_gtk_doc} - QMI username: ${QMI_USERNAME_ENABLED} (${QMI_USERNAME}) - QMUX over MBIM: ${enable_mbim_qmux} + compiler: ${CC} + cflags: ${CFLAGS} + Maintainer mode: ${USE_MAINTAINER_MODE} + udev base directory: ${UDEV_BASE_DIR} + Documentation: ${enable_gtk_doc} + QMI username: ${QMI_USERNAME_ENABLED} (${QMI_USERNAME}) + QMUX over MBIM: ${enable_mbim_qmux} + + Built items: + libqmi-glib: yes + qmicli: yes + qmi-firmware-update: ${build_firmware_update} " diff --git a/src/qmi-firmware-update/Makefile.am b/src/qmi-firmware-update/Makefile.am index 7c804ca..91ea6b5 100644 --- a/src/qmi-firmware-update/Makefile.am +++ b/src/qmi-firmware-update/Makefile.am @@ -3,6 +3,7 @@ bin_PROGRAMS = qmi-firmware-update qmi_firmware_update_CPPFLAGS = \ $(GLIB_CFLAGS) \ + $(GUDEV_CFLAGS) \ -I$(top_srcdir) \ -I$(top_srcdir)/src/libqmi-glib \ -I$(top_srcdir)/src/libqmi-glib/generated \ @@ -12,9 +13,12 @@ qmi_firmware_update_CPPFLAGS = \ qmi_firmware_update_SOURCES = \ qfu-main.c \ + qfu-updater.h qfu-updater.c \ + qfu-udev-helpers.h qfu-udev-helpers.c \ $(NULL) qmi_firmware_update_LDADD = \ + $(GUDEV_LIBS) \ $(GLIB_LIBS) \ $(top_builddir)/src/libqmi-glib/libqmi-glib.la \ $(NULL) diff --git a/src/qmi-firmware-update/qfu-main.c b/src/qmi-firmware-update/qfu-main.c index c21e00d..e3556d0 100644 --- a/src/qmi-firmware-update/qfu-main.c +++ b/src/qmi-firmware-update/qfu-main.c @@ -33,6 +33,8 @@ #include <libqmi-glib.h> +#include "qfu-updater.h" + #define PROGRAM_NAME "qmi-firmware-update" #define PROGRAM_VERSION PACKAGE_VERSION @@ -79,7 +81,7 @@ static GOptionEntry main_entries[] = { GMainLoop *loop; GCancellable *cancellable; -gint exit_status; +gint exit_status = EXIT_FAILURE; /*****************************************************************************/ /* Signal handlers */ @@ -87,9 +89,6 @@ gint exit_status; static gboolean signals_handler (gpointer psignum) { - /* Flag failed exit */ - exit_status = EXIT_FAILURE; - /* Ignore consecutive requests of cancellation */ if (!g_cancellable_is_cancelled (cancellable)) { g_printerr ("cancelling the operation...\n"); @@ -184,10 +183,29 @@ print_version_and_exit (void) /*****************************************************************************/ +static void +run_ready (QfuUpdater *updater, + GAsyncResult *res) +{ + GError *error = NULL; + + if (!qfu_updater_run_finish (updater, res, &error)) { + g_printerr ("error: firmware update operation finished: %s\n", error->message); + g_error_free (error); + } else { + g_print ("firmware update operation finished successfully\n"); + exit_status = EXIT_SUCCESS; + } + + g_idle_add ((GSourceFunc) g_main_loop_quit, loop); +} + int main (int argc, char **argv) { GError *error = NULL; GOptionContext *context; + QfuUpdater *updater; + GFile *cdc_wdm_file; setlocale (LC_ALL, ""); @@ -197,11 +215,10 @@ int main (int argc, char **argv) context = g_option_context_new ("- Update firmware in QMI devices"); g_option_context_add_main_entries (context, main_entries, NULL); if (!g_option_context_parse (context, &argc, &argv, &error)) { - g_printerr ("error: %s\n", - error->message); - exit (EXIT_FAILURE); + g_printerr ("error: couldn't parse option context: %s\n", error->message); + g_error_free (error); + goto out; } - g_option_context_free (context); if (version_flag) print_version_and_exit (); @@ -214,7 +231,7 @@ int main (int argc, char **argv) /* No device path given? */ if (!device_str) { g_printerr ("error: no device path specified\n"); - exit (EXIT_FAILURE); + goto out; } /* Create runtime context */ @@ -223,15 +240,30 @@ int main (int argc, char **argv) exit_status = EXIT_SUCCESS; /* Setup signals */ - g_unix_signal_add (SIGINT, (GSourceFunc)signals_handler, GUINT_TO_POINTER (SIGINT)); - g_unix_signal_add (SIGHUP, (GSourceFunc)signals_handler, GUINT_TO_POINTER (SIGHUP)); - g_unix_signal_add (SIGTERM, (GSourceFunc)signals_handler, GUINT_TO_POINTER (SIGTERM)); + g_unix_signal_add (SIGINT, (GSourceFunc)signals_handler, GINT_TO_POINTER (SIGINT)); + g_unix_signal_add (SIGHUP, (GSourceFunc)signals_handler, GINT_TO_POINTER (SIGHUP)); + g_unix_signal_add (SIGTERM, (GSourceFunc)signals_handler, GINT_TO_POINTER (SIGTERM)); + + /* Create updater and run it */ + cdc_wdm_file = g_file_new_for_commandline_arg (device_str); + updater = qfu_updater_new (cdc_wdm_file, + device_open_proxy_flag, + device_open_mbim_flag); + qfu_updater_run (updater, cancellable, (GAsyncReadyCallback) run_ready, NULL); /* Run! */ g_main_loop_run (loop); - g_object_unref (cancellable); - g_main_loop_unref (loop); +out: + /* Clean exit for a clean memleak report */ + if (context) + g_option_context_free (context); + if (updater) + g_object_unref (updater); + if (cancellable) + g_object_unref (cancellable); + if (loop) + g_main_loop_unref (loop); return (exit_status); } diff --git a/src/qmi-firmware-update/qfu-udev-helpers.c b/src/qmi-firmware-update/qfu-udev-helpers.c new file mode 100644 index 0000000..32d8e48 --- /dev/null +++ b/src/qmi-firmware-update/qfu-udev-helpers.c @@ -0,0 +1,110 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmi-firmware-update -- Command line tool to update firmware in QMI devices + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2016 Zodiac Inflight Innovations + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <gio/gio.h> +#include <gudev/gudev.h> + +#include "qfu-udev-helpers.h" + +gchar * +qfu_udev_helper_get_udev_device_sysfs_path (GUdevDevice *device, + GError **error) +{ + GUdevDevice *parent; + gchar *sysfs_path; + + /* We need to look for the parent GUdevDevice which has a "usb_device" + * devtype. */ + + parent = g_udev_device_get_parent (device); + while (parent) { + GUdevDevice *next; + + if (g_strcmp0 (g_udev_device_get_devtype (parent), "usb_device") == 0) + break; + + /* Check next parent */ + next = g_udev_device_get_parent (parent); + g_object_unref (parent); + parent = next; + } + + if (!parent) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't find parent physical USB device"); + return NULL; + } + + sysfs_path = g_strdup (g_udev_device_get_sysfs_path (parent)); + g_object_unref (parent); + return sysfs_path; +} + +gchar * +qfu_udev_helper_get_sysfs_path (GFile *file, + const gchar *const *subsys, + GError **error) +{ + guint i; + GUdevClient *udev; + gchar *sysfs_path = NULL; + gchar *basename; + gboolean matched = FALSE; + + /* Get the filename */ + basename = g_file_get_basename (file); + if (!basename) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't get filename"); + return NULL; + } + + udev = g_udev_client_new (NULL); + + /* Note: we'll only get devices reported in either one subsystem or the + * other, never in both */ + for (i = 0; !matched && subsys[i]; i++) { + GList *devices, *iter; + + devices = g_udev_client_query_by_subsystem (udev, subsys[i]); + for (iter = devices; !matched && iter; iter = g_list_next (iter)) { + const gchar *name; + GUdevDevice *device; + + device = G_UDEV_DEVICE (iter->data); + name = g_udev_device_get_name (device); + if (g_strcmp0 (name, basename) != 0) + continue; + + /* We'll halt the search once this has been processed */ + matched = TRUE; + sysfs_path = qfu_udev_helper_get_udev_device_sysfs_path (device, error); + } + g_list_free_full (devices, (GDestroyNotify) g_object_unref); + } + + if (!matched) { + g_assert (!sysfs_path); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't find device"); + } + + g_free (basename); + g_object_unref (udev); + return sysfs_path; +} diff --git a/src/qmi-firmware-update/qfu-udev-helpers.h b/src/qmi-firmware-update/qfu-udev-helpers.h new file mode 100644 index 0000000..e12bd80 --- /dev/null +++ b/src/qmi-firmware-update/qfu-udev-helpers.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmi-firmware-update -- Command line tool to update firmware in QMI devices + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2016 Zodiac Inflight Innovations + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QFU_UDEV_HELPERS_H +#define QFU_UDEV_HELPERS_H + +#include <gio/gio.h> + +G_BEGIN_DECLS + +gchar *qfu_udev_helper_get_udev_device_sysfs_path (GUdevDevice *device, + GError **error); +gchar *qfu_udev_helper_get_sysfs_path (GFile *file, + const gchar *const *subsys, + 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 new file mode 100644 index 0000000..3f06a2e --- /dev/null +++ b/src/qmi-firmware-update/qfu-updater.c @@ -0,0 +1,206 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmi-firmware-update -- Command line tool to update firmware in QMI devices + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2016 Zodiac Inflight Innovations + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include <gio/gio.h> +#include <gudev/gudev.h> + +#include "qfu-updater.h" +#include "qfu-udev-helpers.h" + +G_DEFINE_TYPE (QfuUpdater, qfu_updater, G_TYPE_OBJECT) + +struct _QfuUpdaterPrivate { + /* Inputs */ + GFile *cdc_wdm_file; + gboolean device_open_proxy; + gboolean device_open_mbim; +}; + +static const gchar *cdc_wdm_subsys[] = { "usbmisc", "usb", NULL }; + +/******************************************************************************/ +/* Run */ + +typedef enum { + RUN_CONTEXT_STEP_USB_INFO = 0, + RUN_CONTEXT_STEP_LAST +} RunContextStep; + +typedef struct { + /* Context step */ + RunContextStep step; + /* USB info */ + gchar *sysfs_path; +} RunContext; + +static void +run_context_free (RunContext *ctx) +{ + g_free (ctx->sysfs_path); + g_slice_free (RunContext, ctx); +} + +gboolean +qfu_updater_run_finish (QfuUpdater *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void run_context_step (GTask *task); + +static gboolean +run_context_step_cb (GTask *task) +{ + run_context_step (task); + return FALSE; +} + +static void +run_context_step_next (GTask *task) +{ + RunContext *ctx; + + ctx = (RunContext *) g_task_get_task_data (task); + ctx->step++; + + /* Schedule next step in an idle */ + g_idle_add ((GSourceFunc) run_context_step_cb, task); +} + +static void +run_context_step_usb_info (GTask *task) +{ + QfuUpdater *self; + RunContext *ctx; + GError *error = NULL; + + ctx = (RunContext *) g_task_get_task_data (task); + self = g_task_get_source_object (task); + + g_debug ("[qfu-updater] looking for device sysfs path..."); + ctx->sysfs_path = qfu_udev_helper_get_sysfs_path (self->priv->cdc_wdm_file, cdc_wdm_subsys, &error); + if (!ctx->sysfs_path) { + g_prefix_error (&error, "couldn't get cdc-wdm device sysfs path: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + g_debug ("[qfu-updater] device sysfs path: %s", ctx->sysfs_path); + + run_context_step_next (task); +} + +typedef void (* RunContextStepFunc) (GTask *task); +static const RunContextStepFunc run_context_step_func[] = { + [RUN_CONTEXT_STEP_USB_INFO] = run_context_step_usb_info, +}; + +G_STATIC_ASSERT (G_N_ELEMENTS (run_context_step_func) == RUN_CONTEXT_STEP_LAST); + +static void +run_context_step (GTask *task) +{ + RunContext *ctx; + + ctx = (RunContext *) g_task_get_task_data (task); + + /* Early halt operation if cancelled */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + if (ctx->step < G_N_ELEMENTS (run_context_step_func)) { + g_debug ("[qfu-updater] running step %u/%lu...", ctx->step + 1, G_N_ELEMENTS (run_context_step_func)); + run_context_step_func [ctx->step] (task); + return; + } + + g_debug ("[qfu-updater] operation finished"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +qfu_updater_run (QfuUpdater *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + RunContext *ctx; + GTask *task; + + ctx = g_slice_new0 (RunContext); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify) run_context_free); + + run_context_step (task); +} + +/******************************************************************************/ + +QfuUpdater * +qfu_updater_new (GFile *cdc_wdm_file, + gboolean device_open_proxy, + gboolean device_open_mbim) +{ + QfuUpdater *self; + + g_assert (G_IS_FILE (cdc_wdm_file)); + + self = g_object_new (QFU_TYPE_UPDATER, NULL); + self->priv->cdc_wdm_file = g_object_ref (cdc_wdm_file); + self->priv->device_open_proxy = device_open_proxy; + self->priv->device_open_mbim = device_open_mbim; + + return self; +} + +static void +qfu_updater_init (QfuUpdater *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QFU_TYPE_UPDATER, QfuUpdaterPrivate); +} + +static void +dispose (GObject *object) +{ + QfuUpdater *self = QFU_UPDATER (object); + + g_clear_object (&self->priv->cdc_wdm_file); + + G_OBJECT_CLASS (qfu_updater_parent_class)->dispose (object); +} + +static void +qfu_updater_class_init (QfuUpdaterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (QfuUpdaterPrivate)); + + object_class->dispose = dispose; +} diff --git a/src/qmi-firmware-update/qfu-updater.h b/src/qmi-firmware-update/qfu-updater.h new file mode 100644 index 0000000..df36a00 --- /dev/null +++ b/src/qmi-firmware-update/qfu-updater.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmi-firmware-update -- Command line tool to update firmware in QMI devices + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2016 Zodiac Inflight Innovations + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QFU_UPDATER_H +#define QFU_UPDATER_H + +#include <glib-object.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define QFU_TYPE_UPDATER (qfu_updater_get_type ()) +#define QFU_UPDATER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), QFU_TYPE_UPDATER, QfuUpdater)) +#define QFU_UPDATER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), QFU_TYPE_UPDATER, QfuUpdaterClass)) +#define QFU_IS_UPDATER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), QFU_TYPE_UPDATER)) +#define QFU_IS_UPDATER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), QFU_TYPE_UPDATER)) +#define QFU_UPDATER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), QFU_TYPE_UPDATER, QfuUpdaterClass)) + +typedef struct _QfuUpdater QfuUpdater; +typedef struct _QfuUpdaterClass QfuUpdaterClass; +typedef struct _QfuUpdaterPrivate QfuUpdaterPrivate; + +struct _QfuUpdater { + GObject parent; + QfuUpdaterPrivate *priv; +}; + +struct _QfuUpdaterClass { + GObjectClass parent; +}; + +GType qfu_updater_get_type (void); +QfuUpdater *qfu_updater_new (GFile *cdc_wdm_file, + gboolean device_open_proxy, + gboolean device_open_mbim); +void qfu_updater_run (QfuUpdater *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean qfu_updater_run_finish (QfuUpdater *self, + GAsyncResult *res, + GError **error); + +G_END_DECLS + +#endif /* QFU_UPDATER_H */ |