Add initial ColorHug support

This may be useful; more people have ColorHug devices than UEFI firmware that
supports system capsule updates.
This commit is contained in:
Richard Hughes 2015-03-03 15:13:25 +00:00
parent 81e8d799f7
commit 72dff812c0
6 changed files with 586 additions and 0 deletions

View File

@ -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)

View File

@ -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

View File

@ -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) \

View File

@ -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 */

522
src/fu-provider-chug.c Normal file
View File

@ -0,0 +1,522 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
*
* 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 <colorhug.h>
#include <gio/gio.h>
#include <gio/gunixinputstream.h>
#include <glib-object.h>
#include <gusb.h>
#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, &micro);
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);
}

56
src/fu-provider-chug.h Normal file
View File

@ -0,0 +1,56 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
*
* 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 <glib-object.h>
#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 */