From 72dff812c00133e3ccca63cda11f6a4b69ddb6de Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Tue, 3 Mar 2015 15:13:25 +0000 Subject: [PATCH] Add initial ColorHug support This may be useful; more people have ColorHug devices than UEFI firmware that supports system capsule updates. --- configure.ac | 1 + contrib/fwupd.spec.in | 1 + src/Makefile.am | 4 + src/fu-main.c | 2 + src/fu-provider-chug.c | 522 +++++++++++++++++++++++++++++++++++++++++ src/fu-provider-chug.h | 56 +++++ 6 files changed, 586 insertions(+) create mode 100644 src/fu-provider-chug.c create mode 100644 src/fu-provider-chug.h diff --git a/configure.ac b/configure.ac index 16b9b52e6..53e787101 100644 --- a/configure.ac +++ b/configure.ac @@ -87,6 +87,7 @@ dnl - Check library dependencies dnl --------------------------------------------------------------------------- PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.36.0 gobject-2.0 gthread-2.0 gio-2.0 >= 2.25.9 gio-unix-2.0) PKG_CHECK_MODULES(GUDEV, gudev-1.0) +PKG_CHECK_MODULES(COLORHUG, colorhug >= 1.2.9 gusb >= 0.2.2) PKG_CHECK_MODULES(POLKIT, polkit-gobject-1 >= 0.103) PKG_CHECK_MODULES(GCAB, libgcab-1.0) PKG_CHECK_MODULES(APPSTREAM_GLIB, appstream-glib) diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index abcc6d8c4..7a6b07de6 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -13,6 +13,7 @@ BuildRequires: gettext BuildRequires: glib2-devel BuildRequires: intltool BuildRequires: libgudev1-devel +BuildRequires: colord-devel >= 1.0.0 BuildRequires: polkit-devel >= 0.103 BuildRequires: libgcab1-devel BuildRequires: libappstream-devel diff --git a/src/Makefile.am b/src/Makefile.am index 142dbb180..ae87b491d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,6 +6,7 @@ AM_CPPFLAGS = \ $(APPSTREAM_GLIB_CFLAGS) \ $(PIE_CFLAGS) \ $(GCAB_CFLAGS) \ + $(COLORHUG_CFLAGS) \ $(GLIB_CFLAGS) \ $(POLKIT_CFLAGS) \ $(GUDEV_CFLAGS) \ @@ -67,6 +68,8 @@ fwupd_SOURCES = \ fu-device.h \ fu-provider.c \ fu-provider.h \ + fu-provider-chug.c \ + fu-provider-chug.h \ fu-provider-uefi.c \ fu-provider-uefi.h \ fu-resources.c \ @@ -75,6 +78,7 @@ fwupd_SOURCES = \ fwupd_LDADD = \ $(APPSTREAM_GLIB_LIBS) \ + $(COLORHUG_LIBS) \ $(POLKIT_LIBS) \ $(GUDEV_LIBS) \ $(GCAB_LIBS) \ diff --git a/src/fu-main.c b/src/fu-main.c index f0e3d7978..3e6a53eaa 100644 --- a/src/fu-main.c +++ b/src/fu-main.c @@ -37,6 +37,7 @@ #include "fu-common.h" #include "fu-debug.h" #include "fu-device.h" +#include "fu-provider-colorhug.h" #include "fu-provider-uefi.h" #include "fu-resources.h" @@ -729,6 +730,7 @@ main (int argc, char *argv[]) /* add providers */ priv->providers = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + fu_main_add_provider (priv, fu_provider_chug_new ()); fu_main_add_provider (priv, fu_provider_uefi_new ()); /* load introspection from file */ diff --git a/src/fu-provider-chug.c b/src/fu-provider-chug.c new file mode 100644 index 000000000..2db6f81a6 --- /dev/null +++ b/src/fu-provider-chug.c @@ -0,0 +1,522 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes + * + * Licensed under the GNU General Public License Version 2 + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "fu-cleanup.h" +#include "fu-common.h" +#include "fu-device.h" +#include "fu-provider-colorhug.h" + +static void fu_provider_chug_finalize (GObject *object); + +#define FU_PROVIDER_CHUG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), FU_TYPE_PROVIDER_CHUG, FuProviderChugPrivate)) + +#define FU_PROVIDER_CHUG_POLL_REOPEN 5 /* seconds */ +#define FU_PROVIDER_CHUG_FIRMWARE_MAX (64 * 1024) /* bytes */ + +/** + * FuProviderChugPrivate: + **/ +struct _FuProviderChugPrivate +{ + GHashTable *devices; + GUsbContext *usb_ctx; + ChDeviceQueue *device_queue; +}; + +typedef struct { + ChDeviceMode mode; + FuDevice *device; + FuProviderChug *provider_chug; + GMainLoop *loop; + GUsbDevice *usb_device; + gboolean got_version; + gboolean is_bootloader; + guint timeout_open_id; + guint reconnect_id; + GBytes *fw_bin; +} FuProviderChugItem; + +G_DEFINE_TYPE (FuProviderChug, fu_provider_chug, FU_TYPE_PROVIDER) + +/** + * fu_provider_chug_device_free: + **/ +static void +fu_provider_chug_device_free (FuProviderChugItem *item) +{ + g_main_loop_unref (item->loop); + g_object_unref (item->device); + g_object_unref (item->provider_chug); + g_object_unref (item->usb_device); + if (item->fw_bin != NULL) + g_bytes_unref (item->fw_bin); + if (item->timeout_open_id != 0) + g_source_remove (item->timeout_open_id); + if (item->reconnect_id != 0) + g_source_remove (item->reconnect_id); +} + +/** + * fu_provider_chug_reconnect_timeout_cb: + **/ +static gboolean +fu_provider_chug_reconnect_timeout_cb (gpointer user_data) +{ + FuProviderChugItem *item = (FuProviderChugItem *) user_data; + item->reconnect_id = 0; + g_main_loop_quit (item->loop); + return FALSE; +} + +/** + * fu_provider_chug_wait_for_connect: + **/ +static gboolean +fu_provider_chug_wait_for_connect (FuProviderChugItem *item, GError **error) +{ + _cleanup_error_free_ GError *error_local = NULL; + item->reconnect_id = g_timeout_add (CH_DEVICE_USB_TIMEOUT, + fu_provider_chug_reconnect_timeout_cb, item); + g_main_loop_run (item->loop); + if (item->reconnect_id == 0) { + g_set_error_literal (error, + FU_ERROR, + FU_ERROR_INTERNAL, + "request timed out"); + return FALSE; + } + g_source_remove (item->reconnect_id); + item->reconnect_id = 0; + return TRUE; +} + + +/** + * fu_provider_chug_open: + **/ +static gboolean +fu_provider_chug_open (FuProviderChugItem *item, GError **error) +{ + _cleanup_error_free_ GError *error_local = NULL; + if (!ch_device_open (item->usb_device, &error_local)) { + g_set_error (error, + FU_ERROR, + FU_ERROR_INTERNAL, + "failed to open %s device: %s", + fu_device_get_id (item->device), + error_local->message); + return FALSE; + } + return TRUE; +} + +/** + * fu_provider_chug_get_id: + **/ +static gchar * +fu_provider_chug_get_id (GUsbDevice *device) +{ + /* this identifies the *port* the device is plugged into */ + return g_strdup_printf ("CHug-%s", g_usb_device_get_platform_id (device)); +} + +/** + * fu_provider_chug_get_firmware_version: + **/ +static void +fu_provider_chug_get_firmware_version (FuProviderChugItem *item) +{ + FuProviderChugPrivate *priv = item->provider_chug->priv; + guint16 major; + guint16 micro; + guint16 minor; + _cleanup_error_free_ GError *error = NULL; + _cleanup_free_ gchar *version = NULL; + + /* attempt to open the device and get the serial number */ + if (!ch_device_open (item->usb_device, &error)) { + g_debug ("Failed to open, polling: %s", error->message); + return; + } + ch_device_queue_get_firmware_ver (priv->device_queue, item->usb_device, + &major, &minor, µ); + if (!ch_device_queue_process (priv->device_queue, + CH_DEVICE_QUEUE_PROCESS_FLAGS_NONE, + NULL, &error)) { + g_warning ("Failed to get serial: %s", error->message); + } else { + item->got_version = TRUE; + version = g_strdup_printf ("%i.%i.%i", major, minor, micro); + fu_device_set_metadata (item->device, FU_DEVICE_KEY_VERSION, version); + } + + /* we're done here */ + if (!g_usb_device_close (item->usb_device, &error)) + g_debug ("Failed to close: %s", error->message); +} + +/** + * fu_provider_chug_update: + **/ +static gboolean +fu_provider_chug_update (FuProvider *provider, + FuDevice *device, + gint fd, + FuProviderFlags flags, + GError **error) +{ + FuProviderChug *provider_chug = FU_PROVIDER_CHUG (provider); + FuProviderChugPrivate *priv = provider_chug->priv; + FuProviderChugItem *item; + _cleanup_object_unref_ GInputStream *stream = NULL; + _cleanup_error_free_ GError *error_local = NULL; + + /* find item */ + item = g_hash_table_lookup (provider_chug->priv->devices, + fu_device_get_id (device)); + if (item == NULL) { + g_set_error (error, + FU_ERROR, + FU_ERROR_INTERNAL, + "cannot find: %s", + fu_device_get_id (device)); + return FALSE; + } + + /* this file is so small, just slurp it all in one go */ + stream = g_unix_input_stream_new (fd, TRUE); + item->fw_bin = g_input_stream_read_bytes (stream, + FU_PROVIDER_CHUG_FIRMWARE_MAX, + NULL, error); + if (item->fw_bin == NULL) + return FALSE; + + /* switch to bootloader mode */ + if (!item->is_bootloader) { + g_debug ("ColorHug: Switching to bootloader mode"); + if (!fu_provider_chug_open (item, error)) + return FALSE; + ch_device_queue_reset (priv->device_queue, item->usb_device); + if (!ch_device_queue_process (priv->device_queue, + CH_DEVICE_QUEUE_PROCESS_FLAGS_NONE, + NULL, &error_local)) { + g_set_error (error, + FU_ERROR, + FU_ERROR_INTERNAL, + "failed to reset device: %s", + error_local->message); + g_usb_device_close (item->usb_device, NULL); + return FALSE; + } + + /* this device has just gone away, no error possible */ + g_usb_device_close (item->usb_device, NULL); + + /* wait for reconnection */ + if (!fu_provider_chug_wait_for_connect (item, error)) + return FALSE; + } + + /* open the device, which is now in bootloader mode */ + if (!fu_provider_chug_open (item, error)) + return FALSE; + + /* write and verify firmware */ + g_debug ("ColorHug: Writing firmware"); + ch_device_queue_write_firmware (priv->device_queue, item->usb_device, + g_bytes_get_data (item->fw_bin, NULL), + g_bytes_get_size (item->fw_bin)); + ch_device_queue_verify_firmware (priv->device_queue, item->usb_device, + g_bytes_get_data (item->fw_bin, NULL), + g_bytes_get_size (item->fw_bin)); + if (!ch_device_queue_process (priv->device_queue, + CH_DEVICE_QUEUE_PROCESS_FLAGS_NONE, + NULL, &error_local)) { + g_set_error (error, + FU_ERROR, + FU_ERROR_INTERNAL, + "failed to write firmware: %s", + error_local->message); + g_usb_device_close (item->usb_device, NULL); + return FALSE; + } + + /* boot into the new firmware */ + g_debug ("ColorHug: Booting new firmware"); + ch_device_queue_boot_flash (priv->device_queue, item->usb_device); + if (!ch_device_queue_process (priv->device_queue, + CH_DEVICE_QUEUE_PROCESS_FLAGS_NONE, + NULL, &error_local)) { + g_set_error (error, + FU_ERROR, + FU_ERROR_INTERNAL, + "failed to boot flash: %s", + error_local->message); + g_usb_device_close (item->usb_device, NULL); + return FALSE; + } + + /* this device has just gone away, no error possible */ + g_usb_device_close (item->usb_device, NULL); + + /* wait for firmware mode */ + if (!fu_provider_chug_wait_for_connect (item, error)) + return FALSE; + if (!fu_provider_chug_open (item, error)) + return FALSE; + + /* set flash success */ + g_debug ("ColorHug: Setting flash success"); + ch_device_queue_set_flash_success (priv->device_queue, item->usb_device, 1); + if (!ch_device_queue_process (priv->device_queue, + CH_DEVICE_QUEUE_PROCESS_FLAGS_NONE, + NULL, &error_local)) { + g_set_error (error, + FU_ERROR, + FU_ERROR_INTERNAL, + "failed to set flash success: %s", + error_local->message); + g_usb_device_close (item->usb_device, NULL); + return FALSE; + } + + /* close, orderly */ + if (!g_usb_device_close (item->usb_device, &error_local)) { + g_set_error (error, + FU_ERROR, + FU_ERROR_INTERNAL, + "failed to close device: %s", + error_local->message); + g_usb_device_close (item->usb_device, NULL); + return FALSE; + } + + /* close stream */ + if (!g_input_stream_close (stream, NULL, error)) + return FALSE; + + /* get the new firmware version */ + g_debug ("ColorHug: Getting new firmware version"); + item->got_version = FALSE; + fu_provider_chug_get_firmware_version (item); + + if (item->got_version) + g_debug ("ColorHug: DONE!"); + + return TRUE; +} + +/** + * fu_provider_chug_open_cb: + **/ +static gboolean +fu_provider_chug_open_cb (gpointer user_data) +{ + FuProviderChugItem *item = (FuProviderChugItem *) user_data; + + g_debug ("attempt to open %s", + g_usb_device_get_platform_id (item->usb_device)); + fu_provider_chug_get_firmware_version (item); + + /* success! */ + if (item->got_version) { + item->timeout_open_id = 0; + return FALSE; + } + + /* keep trying */ + return TRUE; +} + +/** + * fu_provider_chug_device_added_cb: + **/ +static void +fu_provider_chug_device_added_cb (GUsbContext *ctx, + GUsbDevice *device, + FuProviderChug *provider_chug) +{ + FuProviderChugItem *item; + ChDeviceMode mode; + _cleanup_free_ gchar *id = NULL; + + /* ignore */ + mode = ch_device_get_mode (device); + if (mode == CH_DEVICE_MODE_UNKNOWN) + return; + + /* is already in database */ + id = fu_provider_chug_get_id (device); + item = g_hash_table_lookup (provider_chug->priv->devices, id); + if (item == NULL) { + item = g_new0 (FuProviderChugItem, 1); + item->loop = g_main_loop_new (NULL, FALSE); + item->provider_chug = g_object_ref (provider_chug); + item->usb_device = g_object_ref (device); + item->device = fu_device_new (); + item->mode = mode; + fu_device_set_id (item->device, id); + fu_device_set_metadata (item->device, FU_DEVICE_KEY_PROVIDER, + "ColorHug"); + fu_device_set_metadata (item->device, FU_DEVICE_KEY_GUID, + ch_device_get_guid (device)); + + /* try to get the serial number -- if opening failed then + * poll until the device is not busy */ + fu_provider_chug_get_firmware_version (item); + if (!item->got_version && item->timeout_open_id == 0) { + item->timeout_open_id = g_timeout_add_seconds (FU_PROVIDER_CHUG_POLL_REOPEN, + fu_provider_chug_open_cb, item); + } + + /* insert to hash */ + g_hash_table_insert (provider_chug->priv->devices, + g_strdup (id), item); + } else { + /* update the device */ + g_object_unref (item->usb_device); + item->usb_device = g_object_ref (device); + } + + /* is the device in bootloader mode */ + switch (mode) { + case CH_DEVICE_MODE_BOOTLOADER: + case CH_DEVICE_MODE_BOOTLOADER2: + case CH_DEVICE_MODE_BOOTLOADER_PLUS: + case CH_DEVICE_MODE_BOOTLOADER_ALS: + item->is_bootloader = TRUE; + break; + default: + item->is_bootloader = FALSE; + break; + } + fu_provider_emit_added (FU_PROVIDER (provider_chug), item->device); + + /* are we waiting for the device to show up */ + if (g_main_loop_is_running (item->loop)) + g_main_loop_quit (item->loop); +} + +/** + * fu_provider_chug_device_removed_cb: + **/ +static void +fu_provider_chug_device_removed_cb (GUsbContext *ctx, + GUsbDevice *device, + FuProviderChug *provider_chug) +{ + FuProviderChugItem *item; + _cleanup_free_ gchar *id = NULL; + + /* already in database */ + id = fu_provider_chug_get_id (device); + item = g_hash_table_lookup (provider_chug->priv->devices, id); + if (item == NULL) + return; + + /* no more polling for open */ + if (item->timeout_open_id != 0) { + g_source_remove (item->timeout_open_id); + item->timeout_open_id = 0; + } + fu_provider_emit_removed (FU_PROVIDER (provider_chug), item->device); +} + +/** + * fu_provider_chug_coldplug: + **/ +static gboolean +fu_provider_chug_coldplug (FuProvider *provider, GError **error) +{ + FuProviderChug *provider_chug = FU_PROVIDER_CHUG (provider); + g_usb_context_enumerate (provider_chug->priv->usb_ctx); + return TRUE; +} + +/** + * fu_provider_chug_class_init: + **/ +static void +fu_provider_chug_class_init (FuProviderChugClass *klass) +{ + FuProviderClass *provider_class = FU_PROVIDER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + provider_class->coldplug = fu_provider_chug_coldplug; + provider_class->update = fu_provider_chug_update; + object_class->finalize = fu_provider_chug_finalize; + + g_type_class_add_private (klass, sizeof (FuProviderChugPrivate)); +} + +/** + * fu_provider_chug_init: + **/ +static void +fu_provider_chug_init (FuProviderChug *provider_chug) +{ + provider_chug->priv = FU_PROVIDER_CHUG_GET_PRIVATE (provider_chug); + provider_chug->priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) fu_provider_chug_device_free); + provider_chug->priv->usb_ctx = g_usb_context_new (NULL); + provider_chug->priv->device_queue = ch_device_queue_new (); + g_signal_connect (provider_chug->priv->usb_ctx, "device-added", + G_CALLBACK (fu_provider_chug_device_added_cb), + provider_chug); + g_signal_connect (provider_chug->priv->usb_ctx, "device-removed", + G_CALLBACK (fu_provider_chug_device_removed_cb), + provider_chug); +} + +/** + * fu_provider_chug_finalize: + **/ +static void +fu_provider_chug_finalize (GObject *object) +{ + FuProviderChug *provider_chug = FU_PROVIDER_CHUG (object); + FuProviderChugPrivate *priv = provider_chug->priv; + + g_hash_table_unref (priv->devices); + g_object_unref (priv->usb_ctx); + g_object_unref (priv->device_queue); + + G_OBJECT_CLASS (fu_provider_chug_parent_class)->finalize (object); +} + +/** + * fu_provider_chug_new: + **/ +FuProvider * +fu_provider_chug_new (void) +{ + FuProviderChug *provider; + provider = g_object_new (FU_TYPE_PROVIDER_CHUG, NULL); + return FU_PROVIDER (provider); +} diff --git a/src/fu-provider-chug.h b/src/fu-provider-chug.h new file mode 100644 index 000000000..c1dd506fb --- /dev/null +++ b/src/fu-provider-chug.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes + * + * Licensed under the GNU General Public License Version 2 + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __FU_PROVIDER_CHUG_H +#define __FU_PROVIDER_CHUG_H + +#include + +#include "fu-device.h" +#include "fu-provider.h" + +G_BEGIN_DECLS + +#define FU_TYPE_PROVIDER_CHUG (fu_provider_chug_get_type ()) +#define FU_PROVIDER_CHUG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), FU_TYPE_PROVIDER_CHUG, FuProviderChug)) +#define FU_IS_PROVIDER_CHUG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), FU_TYPE_PROVIDER_CHUG)) + +typedef struct _FuProviderChugPrivate FuProviderChugPrivate; +typedef struct _FuProviderChug FuProviderChug; +typedef struct _FuProviderChugClass FuProviderChugClass; + +struct _FuProviderChug +{ + FuProvider parent; + FuProviderChugPrivate *priv; +}; + +struct _FuProviderChugClass +{ + FuProviderClass parent_class; +}; + +GType fu_provider_chug_get_type (void); +FuProvider *fu_provider_chug_new (void); + +G_END_DECLS + +#endif /* __FU_PROVIDER_CHUG_H */