diff --git a/src/Makefile.am b/src/Makefile.am index cf5c9f96c..a471a79f4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -95,6 +95,8 @@ fwupd_SOURCES = \ fu-pending.h \ fu-provider.c \ fu-provider.h \ + fu-provider-dfu.c \ + fu-provider-dfu.h \ fu-provider-rpi.c \ fu-provider-rpi.h \ fu-provider-udev.c \ diff --git a/src/fu-main.c b/src/fu-main.c index 68e4deb82..6ec12d0be 100644 --- a/src/fu-main.c +++ b/src/fu-main.c @@ -38,6 +38,7 @@ #include "fu-keyring.h" #include "fu-pending.h" #include "fu-provider.h" +#include "fu-provider-dfu.h" #include "fu-provider-rpi.h" #include "fu-provider-udev.h" #include "fu-provider-usb.h" @@ -1640,8 +1641,13 @@ cd_main_provider_device_added_cb (FuProvider *provider, /* remove any fake device */ item = fu_main_get_item_by_id (priv, fu_device_get_id (device)); - if (item != NULL) - g_ptr_array_remove (priv->devices, item); + if (item != NULL) { + g_debug ("already added %s by %s, ignoring same device from %s", + fu_device_get_id (item->device), + fu_device_get_metadata (item->device, FU_DEVICE_KEY_PROVIDER), + fu_provider_get_name (provider)); + return; + } /* create new device */ item = g_new0 (FuDeviceItem, 1); @@ -1664,9 +1670,18 @@ cd_main_provider_device_removed_cb (FuProvider *provider, item = fu_main_get_item_by_id (priv, fu_device_get_id (device)); if (item == NULL) { - g_warning ("can't remove device %s", fu_device_get_id (device)); + g_debug ("no device to remove %s", fu_device_get_id (device)); return; } + + /* check this came from the same provider */ + if (g_strcmp0 (fu_provider_get_name (provider), + fu_provider_get_name (item->provider)) != 0) { + g_debug ("ignoring duplicate removal from %s", + fu_provider_get_name (provider)); + return; + } + g_ptr_array_remove (priv->devices, item); fu_main_emit_changed (priv); } @@ -1786,7 +1801,7 @@ main (int argc, char *argv[]) priv->providers = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); if (g_key_file_get_boolean (config, "fwupd", "EnableOptionROM", NULL)) fu_main_add_provider (priv, fu_provider_udev_new ()); - fu_main_add_provider (priv, fu_provider_usb_new ()); + fu_main_add_provider (priv, fu_provider_dfu_new ()); fu_main_add_provider (priv, fu_provider_rpi_new ()); #ifdef HAVE_COLORHUG fu_main_add_provider (priv, fu_provider_chug_new ()); @@ -1795,6 +1810,9 @@ main (int argc, char *argv[]) fu_main_add_provider (priv, fu_provider_uefi_new ()); #endif + /* last as least priority */ + fu_main_add_provider (priv, fu_provider_usb_new ()); + /* load introspection from file */ priv->introspection_daemon = fu_main_load_introspection (FWUPD_DBUS_INTERFACE ".xml", &error); diff --git a/src/fu-provider-chug.c b/src/fu-provider-chug.c index 5523d3540..5232c822b 100644 --- a/src/fu-provider-chug.c +++ b/src/fu-provider-chug.c @@ -142,19 +142,6 @@ fu_provider_chug_open (FuProviderChugItem *item, GError **error) 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 and - * the kind of device */ - return g_strdup_printf ("CHug-%s-%s", - g_usb_device_get_platform_id (device), - ch_device_get_guid (device)); -} - /** * fu_provider_chug_get_firmware_version: **/ @@ -523,7 +510,7 @@ fu_provider_chug_device_added_cb (GUsbContext *ctx, FuProviderChugPrivate *priv = GET_PRIVATE (provider_chug); FuProviderChugItem *item; ChDeviceMode mode; - g_autofree gchar *id = NULL; + const gchar *platform_id = NULL; /* ignore */ mode = ch_device_get_mode (device); @@ -536,15 +523,15 @@ fu_provider_chug_device_added_cb (GUsbContext *ctx, return; /* is already in database */ - id = fu_provider_chug_get_id (device); - item = g_hash_table_lookup (priv->devices, id); + platform_id = g_usb_device_get_platform_id (device); + item = g_hash_table_lookup (priv->devices, platform_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 (); - fu_device_set_id (item->device, id); + fu_device_set_id (item->device, platform_id); fu_device_set_guid (item->device, ch_device_get_guid (device)); fu_device_add_flag (item->device, FU_DEVICE_FLAG_ALLOW_OFFLINE); fu_device_add_flag (item->device, FU_DEVICE_FLAG_ALLOW_ONLINE); @@ -559,7 +546,7 @@ fu_provider_chug_device_added_cb (GUsbContext *ctx, /* insert to hash */ g_hash_table_insert (priv->devices, - g_strdup (id), item); + g_strdup (platform_id), item); } else { /* update the device */ g_object_unref (item->usb_device); @@ -619,11 +606,11 @@ fu_provider_chug_device_removed_cb (GUsbContext *ctx, { FuProviderChugPrivate *priv = GET_PRIVATE (provider_chug); FuProviderChugItem *item; - g_autofree gchar *id = NULL; + const gchar *platform_id = NULL; /* already in database */ - id = fu_provider_chug_get_id (device); - item = g_hash_table_lookup (priv->devices, id); + platform_id = g_usb_device_get_platform_id (device); + item = g_hash_table_lookup (priv->devices, platform_id); if (item == NULL) return; @@ -638,7 +625,7 @@ fu_provider_chug_device_removed_cb (GUsbContext *ctx, * rescan each time so we don't get confused when different * kinds of ColorHug device are plugged in... */ if (!item->persist_after_unplug) - g_hash_table_remove (priv->devices, id); + g_hash_table_remove (priv->devices, platform_id); } /** diff --git a/src/fu-provider-dfu.c b/src/fu-provider-dfu.c new file mode 100644 index 000000000..2d3484450 --- /dev/null +++ b/src/fu-provider-dfu.c @@ -0,0 +1,385 @@ +/* -*- 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 "fu-device.h" +#include "fu-provider-dfu.h" + +static void fu_provider_dfu_finalize (GObject *object); + +/** + * FuProviderDfuPrivate: + **/ +typedef struct { + DfuContext *context; + GHashTable *devices; /* platform_id:DfuDevice */ +} FuProviderDfuPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (FuProviderDfu, fu_provider_dfu, FU_TYPE_PROVIDER) +#define GET_PRIVATE(o) (fu_provider_dfu_get_instance_private (o)) + +/** + * fu_provider_dfu_get_name: + **/ +static const gchar * +fu_provider_dfu_get_name (FuProvider *provider) +{ + return "DFU"; +} + +/** + * fu_provider_dfu_device_added_cb: + **/ +static void +fu_provider_dfu_device_added_cb (DfuContext *ctx, + DfuDevice *device, + FuProviderDfu *provider_dfu) +{ + FuProviderDfuPrivate *priv = GET_PRIVATE (provider_dfu); + const gchar *platform_id; + const gchar *display_name; + guint16 release; + g_autofree gchar *guid = NULL; + g_autofree gchar *id = NULL; + g_autofree gchar *version = NULL; + g_autofree gchar *vid_pid = NULL; + g_autoptr(AsProfile) profile = as_profile_new (); + g_autoptr(AsProfileTask) ptask = NULL; + g_autoptr(FuDevice) dev = NULL; + g_autoptr(GError) error = NULL; + + platform_id = dfu_device_get_platform_id (device); + ptask = as_profile_start (profile, "FuProviderDfu:added{%s} [%04x:%04x]", + platform_id, + dfu_device_get_runtime_vid (device), + dfu_device_get_runtime_pid (device)); + + /* create new device */ + dev = fu_device_new (); + fu_device_set_id (dev, platform_id); + + /* check capabilities */ + if (dfu_device_can_download (device)) { + fu_device_add_flag (dev, FU_DEVICE_FLAG_ALLOW_ONLINE); + fu_device_add_flag (dev, FU_DEVICE_FLAG_ALLOW_OFFLINE); + } + + /* get version number, falling back to the DFU device release */ + release = dfu_device_get_runtime_release (device); + if (release != 0xffff) { + version = as_utils_version_from_uint16 (release, + AS_VERSION_PARSE_FLAG_NONE); + fu_device_set_metadata (dev, FU_DEVICE_KEY_VERSION, version); + } + + /* get DFU VID:PID hash */ + if (dfu_device_get_runtime_vid (device) == 0xffff) { + g_debug ("Ignoring DFU device not in runtime: %s", platform_id); + return; + } + vid_pid = g_strdup_printf ("DFU\\VID_%04X&PID_%04X", + dfu_device_get_runtime_vid (device), + dfu_device_get_runtime_pid (device)); + guid = as_utils_guid_from_string (vid_pid); + fu_device_set_guid (dev, guid); + + /* open device to get display name */ + if (!dfu_device_open (device, DFU_DEVICE_OPEN_FLAG_NONE, NULL, &error)) { + g_warning ("Failed to open DFU device: %s", error->message); + return; + } + display_name = dfu_device_get_display_name (device); + if (display_name != NULL) + fu_device_set_display_name (dev, display_name); + + /* we're done here */ + if (!dfu_device_close (device, &error)) + g_debug ("Failed to close %s: %s", platform_id, error->message); + + /* attempt to add */ + fu_provider_device_add (FU_PROVIDER (provider_dfu), dev); + g_hash_table_insert (priv->devices, g_strdup (platform_id), g_object_ref (dev)); +} + +/** + * fu_provider_dfu_device_removed_cb: + **/ +static void +fu_provider_dfu_device_removed_cb (DfuContext *ctx, + DfuDevice *device, + FuProviderDfu *provider_dfu) +{ + FuProviderDfuPrivate *priv = GET_PRIVATE (provider_dfu); + FuDevice *dev; + const gchar *platform_id; + + /* convert DfuDevice to FuDevice */ + platform_id = dfu_device_get_platform_id (device); + dev = g_hash_table_lookup (priv->devices, platform_id); + if (dev == NULL) { + g_warning ("cannot find device %s", platform_id); + return; + } + + fu_provider_device_remove (FU_PROVIDER (provider_dfu), dev); +} + +/** + * fu_provider_dfu_coldplug: + **/ +static gboolean +fu_provider_dfu_coldplug (FuProvider *provider, GError **error) +{ + FuProviderDfu *provider_dfu = FU_PROVIDER_DFU (provider); + FuProviderDfuPrivate *priv = GET_PRIVATE (provider_dfu); + dfu_context_enumerate (priv->context, NULL); + return TRUE; +} + +/** + * fu_provider_dfu_state_changed_cb: + **/ +static void +fu_provider_dfu_state_changed_cb (DfuDevice *device, + DfuState state, + FuProvider *provider) +{ + switch (state) { + case DFU_STATE_DFU_UPLOAD_IDLE: + fu_provider_set_status (provider, FWUPD_STATUS_DEVICE_VERIFY); + break; + case DFU_STATE_DFU_DNLOAD_IDLE: + fu_provider_set_status (provider, FWUPD_STATUS_DEVICE_WRITE); + break; + default: + break; + } +} + +/** + * fu_provider_dfu_update: + * + * This updates using DFU. + **/ +static gboolean +fu_provider_dfu_update (FuProvider *provider, + FuDevice *dev, + GBytes *blob_fw, + FuProviderFlags flags, + GError **error) +{ + FuProviderDfu *provider_dfu = FU_PROVIDER_DFU (provider); + FuProviderDfuPrivate *priv = GET_PRIVATE (provider_dfu); + DfuDevice *device; + const gchar *platform_id; + g_autoptr(DfuDevice) dfu_device = NULL; + g_autoptr(DfuFirmware) dfu_firmware = NULL; + g_autoptr(GError) error_local = NULL; + + /* get device */ + platform_id = fu_device_get_id (dev) + 4; + device = dfu_context_get_device_by_platform_id (priv->context, platform_id, &error_local); + if (device == NULL) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "cannot find device %s: %s", + platform_id, error_local->message); + return FALSE; + } + + /* open it */ + if (!dfu_device_open (device, DFU_DEVICE_OPEN_FLAG_NONE, + NULL, &error_local)) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to open DFU device %s: %s", + platform_id, error_local->message); + return FALSE; + } + g_signal_connect (device, "state-changed", + G_CALLBACK (fu_provider_dfu_state_changed_cb), provider); + + /* hit hardware */ + dfu_firmware = dfu_firmware_new (); + if (!dfu_firmware_parse_data (dfu_firmware, blob_fw, + DFU_FIRMWARE_PARSE_FLAG_NONE, error)) + return FALSE; + if (!dfu_device_download (device, dfu_firmware, + DFU_TARGET_TRANSFER_FLAG_DETACH | + DFU_TARGET_TRANSFER_FLAG_VERIFY | + DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME, + NULL, + error)) + return FALSE; + + /* we're done */ + if (!dfu_device_close (device, &error_local)) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + error_local->message); + return FALSE; + } + fu_provider_set_status (provider, FWUPD_STATUS_IDLE); + return TRUE; +} + +/** + * fu_provider_dfu_verify: + **/ +static gboolean +fu_provider_dfu_verify (FuProvider *provider, + FuDevice *dev, + FuProviderVerifyFlags flags, + GError **error) +{ + FuProviderDfu *provider_dfu = FU_PROVIDER_DFU (provider); + FuProviderDfuPrivate *priv = GET_PRIVATE (provider_dfu); + GBytes *blob_fw; + DfuDevice *device; + const gchar *platform_id; + g_autofree gchar *hash = NULL; + g_autoptr(DfuDevice) dfu_device = NULL; + g_autoptr(DfuFirmware) dfu_firmware = NULL; + g_autoptr(GError) error_local = NULL; + + /* get device */ + platform_id = fu_device_get_id (dev); + device = dfu_context_get_device_by_platform_id (priv->context, + platform_id, + &error_local); + if (device == NULL) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "cannot find device %s: %s", + platform_id, error_local->message); + return FALSE; + } + + /* open it */ + if (!dfu_device_open (device, DFU_DEVICE_OPEN_FLAG_NONE, + NULL, &error_local)) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to open DFU device %s: %s", + platform_id, error_local->message); + return FALSE; + } + g_signal_connect (device, "state-changed", + G_CALLBACK (fu_provider_dfu_state_changed_cb), provider); + + /* get data from hardware */ + dfu_firmware = dfu_device_upload (device, + DFU_TARGET_TRANSFER_FLAG_DETACH | + DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME, + NULL, + error); + if (dfu_firmware == NULL) + return FALSE; + + /* we're done */ + if (!dfu_device_close (device, &error_local)) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + error_local->message); + return FALSE; + } + + /* get the SHA1 hash */ + blob_fw = dfu_firmware_write_data (dfu_firmware, error); + if (blob_fw == NULL) + return FALSE; + hash = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, blob_fw); + fu_device_set_metadata (dev, FU_DEVICE_KEY_FIRMWARE_HASH, hash); + fu_provider_set_status (provider, FWUPD_STATUS_IDLE); + return TRUE; +} + +/** + * fu_provider_dfu_class_init: + **/ +static void +fu_provider_dfu_class_init (FuProviderDfuClass *klass) +{ + FuProviderClass *provider_class = FU_PROVIDER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + provider_class->get_name = fu_provider_dfu_get_name; + provider_class->coldplug = fu_provider_dfu_coldplug; + provider_class->update_online = fu_provider_dfu_update; + provider_class->verify = fu_provider_dfu_verify; + object_class->finalize = fu_provider_dfu_finalize; +} + +/** + * fu_provider_dfu_init: + **/ +static void +fu_provider_dfu_init (FuProviderDfu *provider_dfu) +{ + FuProviderDfuPrivate *priv = GET_PRIVATE (provider_dfu); + priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_object_unref); + priv->context = dfu_context_new (); + g_signal_connect (priv->context, "device-added", + G_CALLBACK (fu_provider_dfu_device_added_cb), + provider_dfu); + g_signal_connect (priv->context, "device-removed", + G_CALLBACK (fu_provider_dfu_device_removed_cb), + provider_dfu); +} + +/** + * fu_provider_dfu_finalize: + **/ +static void +fu_provider_dfu_finalize (GObject *object) +{ + FuProviderDfu *provider_dfu = FU_PROVIDER_DFU (object); + FuProviderDfuPrivate *priv = GET_PRIVATE (provider_dfu); + + g_hash_table_unref (priv->devices); + g_object_unref (priv->context); + + G_OBJECT_CLASS (fu_provider_dfu_parent_class)->finalize (object); +} + +/** + * fu_provider_dfu_new: + **/ +FuProvider * +fu_provider_dfu_new (void) +{ + FuProviderDfu *provider; + provider = g_object_new (FU_TYPE_PROVIDER_DFU, NULL); + return FU_PROVIDER (provider); +} diff --git a/src/fu-provider-dfu.h b/src/fu-provider-dfu.h new file mode 100644 index 000000000..cf0a93dfc --- /dev/null +++ b/src/fu-provider-dfu.h @@ -0,0 +1,44 @@ +/* -*- 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_DFU_H +#define __FU_PROVIDER_DFU_H + +#include + +#include "fu-device.h" +#include "fu-provider.h" + +G_BEGIN_DECLS + +#define FU_TYPE_PROVIDER_DFU (fu_provider_dfu_get_type ()) +G_DECLARE_DERIVABLE_TYPE (FuProviderDfu, fu_provider_dfu, FU, PROVIDER_DFU, FuProvider) + +struct _FuProviderDfuClass +{ + FuProviderClass parent_class; +}; + +FuProvider *fu_provider_dfu_new (void); + +G_END_DECLS + +#endif /* __FU_PROVIDER_DFU_H */ diff --git a/src/fu-provider-usb.c b/src/fu-provider-usb.c index 4556a929e..5872ea9a8 100644 --- a/src/fu-provider-usb.c +++ b/src/fu-provider-usb.c @@ -25,7 +25,6 @@ #include #include #include -#include #include "fu-device.h" #include "fu-provider-usb.h" @@ -38,15 +37,12 @@ static void fu_provider_usb_finalize (GObject *object); typedef struct { GHashTable *devices; GUsbContext *usb_ctx; - GTimer *timer_dfu_replug; - gboolean done_coldplug; + gboolean done_enumerate; } FuProviderUsbPrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuProviderUsb, fu_provider_usb, FU_TYPE_PROVIDER) #define GET_PRIVATE(o) (fu_provider_usb_get_instance_private (o)) -#define _DFU_REPLUG_DELAY 3.f /* s */ - /** * fu_provider_usb_get_name: **/ @@ -57,33 +53,46 @@ fu_provider_usb_get_name (FuProvider *provider) } /** - * fu_provider_usb_get_id: + * fu_provider_usb_device_added: **/ -static gchar * -fu_provider_usb_get_id (GUsbDevice *device) -{ - /* this identifies the *port* the device is plugged into */ - return g_strdup_printf ("usb-%s", g_usb_device_get_platform_id (device)); -} - -/** - * fu_provider_usb_device_update: - * - * Important, the device must already be open! - **/ -static gboolean -fu_provider_usb_device_update (FuProviderUsb *provider_usb, - FuDevice *dev, - GUsbDevice *device, - GError **error) +static void +fu_provider_usb_device_added (FuProviderUsb *provider_usb, GUsbDevice *device) { + FuProviderUsbPrivate *priv = GET_PRIVATE (provider_usb); + const gchar *platform_id = NULL; guint8 idx = 0x00; g_autofree gchar *guid = NULL; g_autofree gchar *product = NULL; g_autofree gchar *version = NULL; g_autoptr(AsProfile) profile = as_profile_new (); g_autoptr(AsProfileTask) ptask = NULL; - g_autoptr(DfuDevice) dfu_device = NULL; + g_autoptr(FuDevice) dev = NULL; + g_autoptr(GError) error = NULL; + + /* ignore hubs */ + if (g_usb_device_get_device_class (device) == G_USB_DEVICE_CLASS_HUB) + return; + ptask = as_profile_start (profile, "FuProviderUsb:added{%04x:%04x}", + g_usb_device_get_vid (device), + g_usb_device_get_pid (device)); + + /* is already in database */ + platform_id = g_usb_device_get_platform_id (device); + dev = g_hash_table_lookup (priv->devices, platform_id); + if (dev != NULL) { + g_debug ("ignoring duplicate %s", platform_id); + return; + } + + /* try to get the version without claiming interface */ + if (!g_usb_device_open (device, &error)) { + g_debug ("Failed to open: %s", error->message); + return; + } + + /* insert to hash if valid */ + dev = fu_device_new (); + fu_device_set_id (dev, platform_id); /* get product */ idx = g_usb_device_get_product_index (device); @@ -93,15 +102,12 @@ fu_provider_usb_device_update (FuProviderUsb *provider_usb, product = g_usb_device_get_string_descriptor (device, idx, NULL); } if (product == NULL) { - g_set_error_literal (error, - FWUPD_ERROR, - FWUPD_ERROR_NOT_SUPPORTED, - "no product string descriptor"); - return FALSE; + g_debug ("no product string descriptor"); + return; } + fu_device_set_display_name (dev, product); /* get version number, falling back to the USB device release */ - ptask = as_profile_start_literal (profile, "FuProviderUsb:get-custom-index"); idx = g_usb_device_get_custom_index (device, G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, 'F', 'W', NULL); @@ -113,6 +119,7 @@ fu_provider_usb_device_update (FuProviderUsb *provider_usb, version = as_utils_version_from_uint16 (release, AS_VERSION_PARSE_FLAG_NONE); } + fu_device_set_metadata (dev, FU_DEVICE_KEY_VERSION, version); /* get GUID, falling back to the USB VID:PID hash */ idx = g_usb_device_get_custom_index (device, @@ -127,62 +134,33 @@ fu_provider_usb_device_update (FuProviderUsb *provider_usb, g_usb_device_get_pid (device)); guid = as_utils_guid_from_string (vid_pid); } + fu_device_set_guid (dev, guid); + + /* we're done here */ + if (!g_usb_device_close (device, &error)) + g_debug ("Failed to close: %s", error->message); /* insert to hash */ - fu_device_set_guid (dev, guid); - fu_device_set_display_name (dev, product); - fu_device_set_metadata (dev, FU_DEVICE_KEY_VERSION, version); - - /* is there a DFU interface */ - dfu_device = dfu_device_new (device); - if (dfu_device != NULL) { - if (dfu_device_can_download (dfu_device)) { - fu_device_add_flag (dev, FU_DEVICE_FLAG_ALLOW_ONLINE); - fu_device_add_flag (dev, FU_DEVICE_FLAG_ALLOW_OFFLINE); - } - } else { - g_debug ("not a DFU device"); - } - return TRUE; -} - -/** - * fu_provider_usb_device_add: - * - * Important, the device must already be open! - **/ -static void -fu_provider_usb_device_add (FuProviderUsb *provider_usb, const gchar *id, GUsbDevice *device) -{ - FuProviderUsbPrivate *priv = GET_PRIVATE (provider_usb); - g_autoptr(FuDevice) dev = NULL; - g_autoptr(GError) error = NULL; - - /* insert to hash if valid */ - dev = fu_device_new (); - fu_device_set_id (dev, id); - if (!fu_provider_usb_device_update (provider_usb, dev, device, &error)) { - g_debug ("Failed to add %s: %s", id, error->message); - return; - } - g_hash_table_insert (priv->devices, g_strdup (id), g_object_ref (dev)); fu_provider_device_add (FU_PROVIDER (provider_usb), dev); + g_hash_table_insert (priv->devices, g_strdup (platform_id), g_object_ref (dev)); } +typedef struct { + FuProviderUsb *provider_usb; + GUsbDevice *device; +} FuProviderUsbHelper; + /** - * fu_provider_usb_event_valid: + * fu_provider_usb_device_added_delay_cb: **/ static gboolean -fu_provider_usb_event_valid (FuProviderUsb *provider_usb) +fu_provider_usb_device_added_delay_cb (gpointer user_data) { - FuProviderUsbPrivate *priv = GET_PRIVATE (provider_usb); - - if (!priv->done_coldplug) - return TRUE; - if (g_timer_elapsed (priv->timer_dfu_replug, NULL) > _DFU_REPLUG_DELAY) - return TRUE; - - /* we probably are in the middle of a DFU reset */ + FuProviderUsbHelper *helper = (FuProviderUsbHelper *) user_data; + fu_provider_usb_device_added (helper->provider_usb, helper->device); + g_object_unref (helper->provider_usb); + g_object_unref (helper->device); + g_free (helper); return FALSE; } @@ -195,174 +173,19 @@ fu_provider_usb_device_added_cb (GUsbContext *ctx, FuProviderUsb *provider_usb) { FuProviderUsbPrivate *priv = GET_PRIVATE (provider_usb); - FuDevice *dev; - g_autoptr(GError) error = NULL; - g_autofree gchar *id = NULL; - g_autoptr(AsProfile) profile = as_profile_new (); - g_autoptr(AsProfileTask) ptask = NULL; - /* ignore hubs */ - if (g_usb_device_get_device_class (device) == G_USB_DEVICE_CLASS_HUB) - return; - ptask = as_profile_start (profile, "FuProviderUsb:added{%04x:%04x}", - g_usb_device_get_vid (device), - g_usb_device_get_pid (device)); - - /* handled by another provider */ - id = fu_provider_usb_get_id (device); - if (g_usb_device_get_vid (device) == 0x273f) { - switch (g_usb_device_get_pid (device)) { - case 0x1000: - case 0x1001: - case 0x1004: - case 0x1005: - case 0x1006: - case 0x1007: - case 0x1008: - g_debug ("handling %s in another provider", id); - return; - default: - break; - } - } - - /* doing DFU replug */ - if (!fu_provider_usb_event_valid (provider_usb)) { - g_debug ("ignoring device addition of %04x:%04x", - g_usb_device_get_vid (device), - g_usb_device_get_pid (device)); - dev = g_hash_table_lookup (priv->devices, id); - if (dev != NULL) { - g_debug ("Updating device after firmware flash"); - fu_provider_usb_device_update (provider_usb, dev, device, NULL); - } + /* use a small delay for hotplugging so that other, better, providers + * can claim this interface and add the FuDevice */ + if (priv->done_enumerate) { + FuProviderUsbHelper *helper; + g_debug ("waiting a small time for other providers"); + helper = g_new0 (FuProviderUsbHelper, 1); + helper->provider_usb = g_object_ref (provider_usb); + helper->device = g_object_ref (device); + g_timeout_add (500, fu_provider_usb_device_added_delay_cb, helper); return; } - - /* is already in database */ - dev = g_hash_table_lookup (priv->devices, id); - if (dev != NULL) { - g_debug ("ignoring duplicate %s", id); - return; - } - - /* try to get the version without claiming interface */ - if (!g_usb_device_open (device, &error)) { - g_debug ("Failed to open: %s", error->message); - return; - } - - /* try to add the device */ - fu_provider_usb_device_add (provider_usb, id, device); - - /* we're done here */ - if (!g_usb_device_close (device, &error)) - g_debug ("Failed to close: %s", error->message); -} - -/** - * fu_provider_usb_state_changed_cb: - **/ -static void -fu_provider_usb_state_changed_cb (DfuDevice *device, - DfuState state, - FuProvider *provider) -{ - FuProviderUsb *provider_usb = FU_PROVIDER_USB (provider); - FuProviderUsbPrivate *priv = GET_PRIVATE (provider_usb); - - switch (state) { - case DFU_STATE_DFU_UPLOAD_IDLE: - fu_provider_set_status (provider, FWUPD_STATUS_DEVICE_VERIFY); - break; - case DFU_STATE_DFU_DNLOAD_IDLE: - fu_provider_set_status (provider, FWUPD_STATUS_DEVICE_WRITE); - break; - default: - break; - } - - /* reset */ - g_timer_reset (priv->timer_dfu_replug); -} - -/** - * fu_provider_usb_update: - * - * This updates using DFU. - **/ -static gboolean -fu_provider_usb_update (FuProvider *provider, - FuDevice *device, - GBytes *blob_fw, - FuProviderFlags flags, - GError **error) -{ - FuProviderUsb *provider_usb = FU_PROVIDER_USB (provider); - FuProviderUsbPrivate *priv = GET_PRIVATE (provider_usb); - GUsbDevice *dev; - const gchar *platform_id; - g_autoptr(DfuDevice) dfu_device = NULL; - g_autoptr(DfuFirmware) dfu_firmware = NULL; - g_autoptr(GError) error_local = NULL; - - /* get device */ - platform_id = fu_device_get_id (device) + 4; - dev = g_usb_context_find_by_platform_id (priv->usb_ctx, platform_id, &error_local); - if (dev == NULL) { - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "cannot find device %s: %s", - platform_id, error_local->message); - return FALSE; - } - - /* open it */ - dfu_device = dfu_device_new (dev); - if (dfu_device == NULL) { - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "%s is not a DFU device", - platform_id); - return FALSE; - } - if (!dfu_device_open (dfu_device, DFU_DEVICE_OPEN_FLAG_NONE, - NULL, &error_local)) { - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "failed to open DFU device %s: %s", - platform_id, error_local->message); - return FALSE; - } - g_signal_connect (dfu_device, "state-changed", - G_CALLBACK (fu_provider_usb_state_changed_cb), provider); - - /* hit hardware */ - dfu_firmware = dfu_firmware_new (); - if (!dfu_firmware_parse_data (dfu_firmware, blob_fw, - DFU_FIRMWARE_PARSE_FLAG_NONE, error)) - return FALSE; - if (!dfu_device_download (dfu_device, dfu_firmware, - DFU_TARGET_TRANSFER_FLAG_DETACH | - DFU_TARGET_TRANSFER_FLAG_VERIFY | - DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME, - NULL, - error)) - return FALSE; - - /* we're done */ - if (!dfu_device_close (dfu_device, &error_local)) { - g_set_error_literal (error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - error_local->message); - return FALSE; - } - fu_provider_set_status (provider, FWUPD_STATUS_IDLE); - return TRUE; + fu_provider_usb_device_added (provider_usb, device); } /** @@ -375,23 +198,16 @@ fu_provider_usb_device_removed_cb (GUsbContext *ctx, { FuProviderUsbPrivate *priv = GET_PRIVATE (provider_usb); FuDevice *dev; - g_autofree gchar *id = NULL; - - /* doing DFU replug */ - if (!fu_provider_usb_event_valid (provider_usb)) { - g_debug ("ignoring device removal of %04x:%04x", - g_usb_device_get_vid (device), - g_usb_device_get_pid (device)); - return; - } + const gchar *platform_id = NULL; /* already in database */ - id = fu_provider_usb_get_id (device); - dev = g_hash_table_lookup (priv->devices, id); + platform_id = g_usb_device_get_platform_id (device); + dev = g_hash_table_lookup (priv->devices, platform_id); if (dev == NULL) return; fu_provider_device_remove (FU_PROVIDER (provider_usb), dev); + g_hash_table_remove (priv->devices, platform_id); } /** @@ -402,102 +218,8 @@ fu_provider_usb_coldplug (FuProvider *provider, GError **error) { FuProviderUsb *provider_usb = FU_PROVIDER_USB (provider); FuProviderUsbPrivate *priv = GET_PRIVATE (provider_usb); - g_usb_context_enumerate (priv->usb_ctx); - - /* start ignoring USB devices that just replug */ - priv->done_coldplug = TRUE; - return TRUE; -} - -/** - * fu_provider_usb_verify: - **/ -static gboolean -fu_provider_usb_verify (FuProvider *provider, - FuDevice *device, - FuProviderVerifyFlags flags, - GError **error) -{ - FuProviderUsb *provider_usb = FU_PROVIDER_USB (provider); - FuProviderUsbPrivate *priv = GET_PRIVATE (provider_usb); - GBytes *blob_fw; - GUsbDevice *dev; - const gchar *platform_id; - g_autofree gchar *hash = NULL; - g_autoptr(DfuDevice) dfu_device = NULL; - g_autoptr(DfuFirmware) dfu_firmware = NULL; - g_autoptr(GError) error_local = NULL; - - /* get device */ - platform_id = fu_device_get_id (device) + 4; - dev = g_usb_context_find_by_platform_id (priv->usb_ctx, platform_id, &error_local); - if (dev == NULL) { - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "cannot find device %s: %s", - platform_id, error_local->message); - return FALSE; - } - - /* open it */ - dfu_device = dfu_device_new (dev); - if (dfu_device == NULL) { - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "%s is not a DFU device", - platform_id); - return FALSE; - } - if (!dfu_device_open (dfu_device, DFU_DEVICE_OPEN_FLAG_NONE, - NULL, &error_local)) { - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "failed to open DFU device %s: %s", - platform_id, error_local->message); - return FALSE; - } - g_signal_connect (dfu_device, "state-changed", - G_CALLBACK (fu_provider_usb_state_changed_cb), provider); - - /* get data from hardware */ - dfu_firmware = dfu_device_upload (dfu_device, - DFU_TARGET_TRANSFER_FLAG_DETACH | - DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME, - NULL, - error); - if (dfu_firmware == NULL) - return FALSE; - - /* we're done */ - if (!dfu_device_close (dfu_device, &error_local)) { - g_set_error_literal (error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - error_local->message); - return FALSE; - } - - /* the device never came back! */ - if (!FU_IS_DEVICE (device)) { - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "%s never came back from DFU mode", - platform_id); - return FALSE; - } - - /* get the SHA1 hash */ - blob_fw = dfu_firmware_write_data (dfu_firmware, error); - if (blob_fw == NULL) - return FALSE; - hash = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, blob_fw); - fu_device_set_metadata (device, FU_DEVICE_KEY_FIRMWARE_HASH, hash); - fu_provider_set_status (provider, FWUPD_STATUS_IDLE); + priv->done_enumerate = TRUE; return TRUE; } @@ -512,8 +234,6 @@ fu_provider_usb_class_init (FuProviderUsbClass *klass) provider_class->get_name = fu_provider_usb_get_name; provider_class->coldplug = fu_provider_usb_coldplug; - provider_class->update_online = fu_provider_usb_update; - provider_class->verify = fu_provider_usb_verify; object_class->finalize = fu_provider_usb_finalize; } @@ -524,7 +244,6 @@ static void fu_provider_usb_init (FuProviderUsb *provider_usb) { FuProviderUsbPrivate *priv = GET_PRIVATE (provider_usb); - priv->timer_dfu_replug = g_timer_new (); priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref); priv->usb_ctx = g_usb_context_new (NULL); @@ -547,7 +266,6 @@ fu_provider_usb_finalize (GObject *object) g_hash_table_unref (priv->devices); g_object_unref (priv->usb_ctx); - g_timer_destroy (priv->timer_dfu_replug); G_OBJECT_CLASS (fu_provider_usb_parent_class)->finalize (object); } diff --git a/src/fu-provider.c b/src/fu-provider.c index e023c9d84..f5a8965ed 100644 --- a/src/fu-provider.c +++ b/src/fu-provider.c @@ -371,7 +371,9 @@ fu_provider_get_name (FuProvider *provider) void fu_provider_device_add (FuProvider *provider, FuDevice *device) { - g_debug ("emit added: %s", fu_device_get_id (device)); + g_debug ("emit added from %s: %s", + fu_provider_get_name (provider), + fu_device_get_id (device)); fu_device_set_metadata (device, FU_DEVICE_KEY_PROVIDER, fu_provider_get_name (provider)); g_signal_emit (provider, signals[SIGNAL_DEVICE_ADDED], 0, device); @@ -383,7 +385,9 @@ fu_provider_device_add (FuProvider *provider, FuDevice *device) void fu_provider_device_remove (FuProvider *provider, FuDevice *device) { - g_debug ("emit removed: %s", fu_device_get_id (device)); + g_debug ("emit removed from %s: %s", + fu_provider_get_name (provider), + fu_device_get_id (device)); g_signal_emit (provider, signals[SIGNAL_DEVICE_REMOVED], 0, device); }