From d3afaaab9fa3d3b9b40c78b4445c364665aaa230 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Mon, 23 Nov 2015 11:14:14 +0000 Subject: [PATCH] libdfu: Add a context object to handle device hotplug Also, add a 'watch' command to dfu-tool to test this. --- docs/libdfu/libdfu.types | 2 + libdfu/Makefile.am | 3 + libdfu/dfu-context.c | 552 ++++++++++++++++++++++++++++++++++++ libdfu/dfu-context.h | 73 +++++ libdfu/dfu-device-private.h | 3 + libdfu/dfu-device.c | 6 +- libdfu/dfu-device.h | 1 + libdfu/dfu-tool.c | 197 ++++++++----- libdfu/dfu.h | 1 + 9 files changed, 764 insertions(+), 74 deletions(-) create mode 100644 libdfu/dfu-context.c create mode 100644 libdfu/dfu-context.h diff --git a/docs/libdfu/libdfu.types b/docs/libdfu/libdfu.types index c073ed14a..1dbb2b858 100644 --- a/docs/libdfu/libdfu.types +++ b/docs/libdfu/libdfu.types @@ -1,5 +1,7 @@ +dfu_context_get_type dfu_device_get_type dfu_element_get_type dfu_firmware_get_type dfu_image_get_type dfu_target_get_type +dfu_sector_get_type diff --git a/libdfu/Makefile.am b/libdfu/Makefile.am index 6a01a0b03..b011fccb7 100644 --- a/libdfu/Makefile.am +++ b/libdfu/Makefile.am @@ -30,6 +30,7 @@ libdfu_include_HEADERS = \ libdfubase_includedir = $(libdfu_includedir)/libdfu libdfubase_include_HEADERS = \ dfu-common.h \ + dfu-context.h \ dfu-device.h \ dfu-element.h \ dfu-error.h \ @@ -42,6 +43,8 @@ libdfu_la_SOURCES = \ dfu.h \ dfu-common.c \ dfu-common.h \ + dfu-context.c \ + dfu-context.h \ dfu-device.c \ dfu-device.h \ dfu-device-private.h \ diff --git a/libdfu/dfu-context.c b/libdfu/dfu-context.c new file mode 100644 index 000000000..d8d460f01 --- /dev/null +++ b/libdfu/dfu-context.c @@ -0,0 +1,552 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:dfu-context + * @short_description: A system context for managing DFU-capable devices + * + * This object allows discovering and monitoring hotpluggable DFU devices. + * + * When using #DfuContext the device is given some time to re-enumerate after a + * detach or reset. This allows client programs to continue using the #DfuDevice + * without dealing with the device hotplug and the #GUsbDevice changing. + * Using this object may be easier than using GUsbContext directly. + * + * Please be aware that after device detach or reset the number of #DfuTarget + * objects may be different and so need to be re-requested. + * + * See also: #DfuDevice, #DfuTarget + */ + +#include "config.h" + +#include + +#include "dfu-device-private.h" +#include "dfu-error.h" +#include "dfu-context.h" + +static void dfu_context_finalize (GObject *object); + +/** + * DfuContextPrivate: + * + * Private #DfuContext data + **/ +typedef struct { + GUsbContext *usb_ctx; + GPtrArray *devices; /* of DfuContextItem */ + guint timeout; /* in ms */ +} DfuContextPrivate; + +typedef struct { + DfuContext *context; /* not refcounted */ + DfuDevice *device; + guint timeout_id; + guint state_change_id; +} DfuContextItem; + +enum { + SIGNAL_DEVICE_ADDED, + SIGNAL_DEVICE_REMOVED, + SIGNAL_DEVICE_CHANGED, + SIGNAL_LAST +}; + +static guint signals [SIGNAL_LAST] = { 0 }; + +G_DEFINE_TYPE_WITH_PRIVATE (DfuContext, dfu_context, G_TYPE_OBJECT) +#define GET_PRIVATE(o) (dfu_context_get_instance_private (o)) + +/** + * dfu_context_device_free: + **/ +static void +dfu_context_device_free (DfuContextItem *item) +{ + if (item->timeout_id > 0) + g_source_remove (item->timeout_id); + if (item->timeout_id > 0) { + g_signal_handler_disconnect (item->device, + item->state_change_id); + } + g_object_unref (item->device); + g_free (item); +} + +/** + * dfu_context_class_init: + **/ +static void +dfu_context_class_init (DfuContextClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + /** + * DfuContext::device-added: + * @context: the #DfuContext instance that emitted the signal + * @device: the #DfuDevice + * + * The ::device-added signal is emitted when a new DFU device is connected. + * + * Since: 0.5.4 + **/ + signals [SIGNAL_DEVICE_ADDED] = + g_signal_new ("device-added", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (DfuContextClass, device_added), + NULL, NULL, g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, DFU_TYPE_DEVICE); + + /** + * DfuContext::device-removed: + * @context: the #DfuContext instance that emitted the signal + * @device: the #DfuDevice + * + * The ::device-removed signal is emitted when a DFU device is removed. + * + * Since: 0.5.4 + **/ + signals [SIGNAL_DEVICE_REMOVED] = + g_signal_new ("device-removed", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (DfuContextClass, device_removed), + NULL, NULL, g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, DFU_TYPE_DEVICE); + + /** + * DfuContext::device-changed: + * @context: the #DfuContext instance that emitted the signal + * @device: the #DfuDevice + * + * The ::device-changed signal is emitted when a DFU device is changed, + * typically when it has detached or been reset. + * + * Since: 0.5.4 + **/ + signals [SIGNAL_DEVICE_CHANGED] = + g_signal_new ("device-changed", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (DfuContextClass, device_changed), + NULL, NULL, g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, DFU_TYPE_DEVICE); + + object_class->finalize = dfu_context_finalize; +} + +/** + * dfu_context_get_device_id: + **/ +static gchar * +dfu_context_get_device_id (DfuDevice *device) +{ + GUsbDevice *dev; + dev = dfu_device_get_usb_dev (device); + return g_strdup_printf ("%04x:%04x [%s]", + g_usb_device_get_vid (dev), + g_usb_device_get_pid (dev), + g_usb_device_get_platform_id (dev)); +} + +/** + * dfu_context_find_item_by_platform_id: + **/ +static DfuContextItem * +dfu_context_find_item_by_platform_id (DfuContext *context, const gchar *platform_id) +{ + DfuContextPrivate *priv = GET_PRIVATE (context); + DfuContextItem *item; + GUsbDevice *dev; + guint i; + + /* do we have this device */ + for (i = 0; i < priv->devices->len; i++) { + item = g_ptr_array_index (priv->devices, i); + dev = dfu_device_get_usb_dev (item->device); + if (g_strcmp0 (g_usb_device_get_platform_id (dev), platform_id) == 0) + return item; + } + return NULL; +} + +/** + * dfu_context_remove_item: + **/ +static void +dfu_context_remove_item (DfuContextItem *item) +{ + DfuContextPrivate *priv = GET_PRIVATE (item->context); + g_autofree gchar *device_id = NULL; + + g_signal_emit (item->context, signals[SIGNAL_DEVICE_REMOVED], 0, item->device); + g_ptr_array_remove (priv->devices, item); + + /* log something */ + device_id = dfu_context_get_device_id (item->device); + g_debug ("%s was removed", device_id); +} + +/** + * dfu_context_device_timeout_cb: + **/ +static gboolean +dfu_context_device_timeout_cb (gpointer user_data) +{ + DfuContextItem *item = (DfuContextItem *) user_data; + g_autofree gchar *device_id = NULL; + + /* bad firmware? */ + device_id = dfu_context_get_device_id (item->device); + g_debug ("%s did not come back as a DFU capable device", device_id); + dfu_context_remove_item (item); + return FALSE; +} + +/** + * dfu_context_device_state_cb: + **/ +static void +dfu_context_device_state_cb (DfuDevice *device, DfuState state, DfuContext *context) +{ + g_autofree gchar *device_id = NULL; + device_id = dfu_context_get_device_id (device); + g_debug ("%s state now: %s", device_id, dfu_state_to_string (state)); + g_signal_emit (context, signals[SIGNAL_DEVICE_CHANGED], 0, device); +} + +/** + * dfu_context_device_added_cb: + **/ +static void +dfu_context_device_added_cb (GUsbContext *usb_context, + GUsbDevice *usb_device, + DfuContext *context) +{ + DfuContextPrivate *priv = GET_PRIVATE (context); + DfuDevice *device; + DfuContextItem *item; + const gchar *platform_id; + g_autofree gchar *device_id = NULL; + g_autoptr(GError) error = NULL; + + /* are we waiting for this device to come back? */ + platform_id = g_usb_device_get_platform_id (usb_device); + item = dfu_context_find_item_by_platform_id (context, platform_id); + if (item != NULL) { + device_id = dfu_context_get_device_id (item->device); + if (item->timeout_id > 0) + g_source_remove (item->timeout_id); + item->timeout_id = 0; + + /* try and be helpful; we may be a daemon like fwupd watching a + * DFU device after dfu-tool or dfu-util has detached the + * device on th command line */ + if (!dfu_device_set_new_usb_dev (item->device, usb_device, NULL, &error)) + g_warning ("Failed to set new device: %s", error->message); + + /* inform the UI */ + g_signal_emit (context, signals[SIGNAL_DEVICE_CHANGED], 0, item->device); + g_debug ("device %s came back", device_id); + return; + } + + /* is this a DFU-capable device */ + device = dfu_device_new (usb_device); + if (device == NULL) { + g_debug ("device was not DFU capable"); + return; + } + + /* add */ + item = g_new0 (DfuContextItem, 1); + item->context = context; + item->device = device; + item->state_change_id = + g_signal_connect (item->device, "state-changed", + G_CALLBACK (dfu_context_device_state_cb), context); + g_ptr_array_add (priv->devices, item); + g_signal_emit (context, signals[SIGNAL_DEVICE_ADDED], 0, device); + device_id = dfu_context_get_device_id (item->device); + g_debug ("device %s was added", device_id); +} + +/** + * dfu_context_device_removed_cb: + **/ +static void +dfu_context_device_removed_cb (GUsbContext *usb_context, + GUsbDevice *usb_device, + DfuContext *context) +{ + DfuContextPrivate *priv = GET_PRIVATE (context); + DfuContextItem *item; + const gchar *platform_id; + + platform_id = g_usb_device_get_platform_id (usb_device); + item = dfu_context_find_item_by_platform_id (context, platform_id); + if (item == NULL) + return; + + /* this item has just detached */ + if (item->timeout_id > 0) + g_source_remove (item->timeout_id); + item->timeout_id = + g_timeout_add (priv->timeout, dfu_context_device_timeout_cb, item); +} + +/** + * dfu_context_init: + **/ +static void +dfu_context_init (DfuContext *context) +{ + DfuContextPrivate *priv = GET_PRIVATE (context); + priv->timeout = 5000; + priv->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) dfu_context_device_free); + priv->usb_ctx = g_usb_context_new (NULL); + g_signal_connect (priv->usb_ctx, "device-added", + G_CALLBACK (dfu_context_device_added_cb), context); + g_signal_connect (priv->usb_ctx, "device-removed", + G_CALLBACK (dfu_context_device_removed_cb), context); +} + +/** + * dfu_context_finalize: + **/ +static void +dfu_context_finalize (GObject *object) +{ + DfuContext *context = DFU_CONTEXT (object); + DfuContextPrivate *priv = GET_PRIVATE (context); + + g_ptr_array_unref (priv->devices); + g_object_unref (priv->usb_ctx); + + G_OBJECT_CLASS (dfu_context_parent_class)->finalize (object); +} + +/** + * dfu_context_new: + * + * Creates a new DFU context object. + * + * Return value: a new #DfuContext + * + * Since: 0.5.4 + **/ +DfuContext * +dfu_context_new (void) +{ + DfuContext *context; + context = g_object_new (DFU_TYPE_CONTEXT, NULL); + return context; +} + +/** + * dfu_context_get_timeout: + * @context: a #DfuContext + * + * Gets the wait-for-replug timeout. + * + * Return value: value in milliseconds + * + * Since: 0.5.4 + **/ +guint +dfu_context_get_timeout (DfuContext *context) +{ + DfuContextPrivate *priv = GET_PRIVATE (context); + g_return_val_if_fail (DFU_IS_CONTEXT (context), 0); + return priv->timeout; +} + + +/** + * dfu_context_set_timeout: + * @context: a #DfuContext + * @timeout: a timeout in milliseconds + * + * Sets the wait-for-replug timeout. + * This is the longest we will wait for a device to re-enumerate after + * disconnecting. Using longer values will result in any UI not updating in a + * good time, but using too short values will result in devices being removed + * and re-added as different #DfuDevice's. + * + * Since: 0.5.4 + **/ +void +dfu_context_set_timeout (DfuContext *context, guint timeout) +{ + DfuContextPrivate *priv = GET_PRIVATE (context); + g_return_if_fail (DFU_IS_CONTEXT (context)); + priv->timeout = timeout; +} + + +/** + * dfu_context_enumerate: + * @context: a #DfuContext + * @error: a #GError, or %NULL + * + * Opens a DFU-capable context. + * + * Return value: %TRUE for success + * + * Since: 0.5.4 + **/ +gboolean +dfu_context_enumerate (DfuContext *context, GError **error) +{ + DfuContextPrivate *priv = GET_PRIVATE (context); + g_return_val_if_fail (DFU_IS_CONTEXT (context), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_usb_context_enumerate (priv->usb_ctx); + return TRUE; +} + +/** + * dfu_context_get_devices: + * @context: a #DfuContext + * + * Gets all the DFU-capable devices on the system. + * + * Return value: (element-type DfuDevice) (transfer container): array of devices + * + * Since: 0.5.4 + **/ +GPtrArray * +dfu_context_get_devices (DfuContext *context) +{ + DfuContextPrivate *priv = GET_PRIVATE (context); + DfuContextItem *item; + GPtrArray *devices; + guint i; + + g_return_val_if_fail (DFU_IS_CONTEXT (context), NULL); + + devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + for (i = 0; i < priv->devices->len; i++) { + item = g_ptr_array_index (priv->devices, i); + g_ptr_array_add (devices, g_object_ref (item->device)); + } + return devices; +} + +/** + * dfu_context_get_device_by_vid_pid: + * @context: a #DfuContext + * @vid: a vendor ID + * @pid: a product ID + * @error: a #GError, or %NULL + * + * Finds a device in the context with a specific vendor:product ID. + * An error is returned if more than one device matches. + * + * Return value: (transfer full): a #DfuDevice for success, or %NULL for an error + * + * Since: 0.5.4 + **/ +DfuDevice * +dfu_context_get_device_by_vid_pid (DfuContext *context, + guint16 vid, guint16 pid, + GError **error) +{ + DfuContextPrivate *priv = GET_PRIVATE (context); + DfuContextItem *item; + DfuDevice *device = NULL; + GUsbDevice *dev; + guint i; + + g_return_val_if_fail (DFU_IS_CONTEXT (context), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* search all devices */ + for (i = 0; i < priv->devices->len; i++) { + + /* match */ + item = g_ptr_array_index (priv->devices, i); + dev = dfu_device_get_usb_dev (item->device); + if (g_usb_device_get_vid (dev) == vid && + g_usb_device_get_pid (dev) == pid) { + if (device != NULL) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INVALID_DEVICE, + "multiple device matches for %04x:%04x", + vid, pid); + return NULL; + } + device = item->device; + continue; + } + } + if (device == NULL) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_FOUND, + "no device matches for %04x:%04x", + vid, pid); + return NULL; + } + return g_object_ref (device); +} + +/** + * dfu_context_get_device_default: + * @context: a #DfuContext + * @error: a #GError, or %NULL + * + * Gets the default device in the context. + * An error is returned if more than one device exists. + * + * Return value: (transfer full): a #DfuDevice for success, or %NULL for an error + * + * Since: 0.5.4 + **/ +DfuDevice * +dfu_context_get_device_default (DfuContext *context, GError **error) +{ + DfuContextPrivate *priv = GET_PRIVATE (context); + DfuContextItem *item; + + g_return_val_if_fail (DFU_IS_CONTEXT (context), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* none */ + if (priv->devices->len == 0) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_NOT_FOUND, + "no attached DFU device"); + return NULL; + } + + /* multiple */ + if (priv->devices->len > 1) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_INVALID_DEVICE, + "more than one attached DFU device"); + return NULL; + } + item = g_ptr_array_index (priv->devices, 0); + return g_object_ref (item->device); +} diff --git a/libdfu/dfu-context.h b/libdfu/dfu-context.h new file mode 100644 index 000000000..69b6fc878 --- /dev/null +++ b/libdfu/dfu-context.h @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __DFU_CONTEXT_H +#define __DFU_CONTEXT_H + +#include +#include + +#include "dfu-device.h" + +G_BEGIN_DECLS + +#define DFU_TYPE_CONTEXT (dfu_context_get_type ()) +G_DECLARE_DERIVABLE_TYPE (DfuContext, dfu_context, DFU, CONTEXT, GObject) + +struct _DfuContextClass +{ + GObjectClass parent_class; + void (*device_added) (DfuContext *context, + DfuDevice *device); + void (*device_removed) (DfuContext *context, + DfuDevice *device); + void (*device_changed) (DfuContext *context, + DfuDevice *device); + /*< private >*/ + /* Padding for future expansion */ + void (*_dfu_context_reserved1) (void); + void (*_dfu_context_reserved2) (void); + void (*_dfu_context_reserved3) (void); + void (*_dfu_context_reserved4) (void); + void (*_dfu_context_reserved5) (void); + void (*_dfu_context_reserved6) (void); + void (*_dfu_context_reserved7) (void); + void (*_dfu_context_reserved8) (void); + void (*_dfu_context_reserved9) (void); +}; + +DfuContext *dfu_context_new (void); +gboolean dfu_context_enumerate (DfuContext *context, + GError **error); +GPtrArray *dfu_context_get_devices (DfuContext *context); +guint dfu_context_get_timeout (DfuContext *context); +void dfu_context_set_timeout (DfuContext *context, + guint timeout); +DfuDevice *dfu_context_get_device_by_vid_pid (DfuContext *context, + guint16 vid, + guint16 pid, + GError **error); +DfuDevice *dfu_context_get_device_default (DfuContext *context, + GError **error); + +G_END_DECLS + +#endif /* __DFU_CONTEXT_H */ diff --git a/libdfu/dfu-device-private.h b/libdfu/dfu-device-private.h index b2cfebb97..894f92cd9 100644 --- a/libdfu/dfu-device-private.h +++ b/libdfu/dfu-device-private.h @@ -88,6 +88,9 @@ void dfu_device_error_fixup (DfuDevice *device, GCancellable *cancellable, GError **error); guint dfu_device_get_download_timeout (DfuDevice *device); +gboolean +dfu_device_set_new_usb_dev (DfuDevice *device, GUsbDevice *dev, + GCancellable *cancellable, GError **error); G_END_DECLS diff --git a/libdfu/dfu-device.c b/libdfu/dfu-device.c index c1229a74e..d79d9d033 100644 --- a/libdfu/dfu-device.c +++ b/libdfu/dfu-device.c @@ -1152,13 +1152,17 @@ dfu_device_close (DfuDevice *device, GError **error) /** * dfu_device_set_new_usb_dev: **/ -static gboolean +gboolean dfu_device_set_new_usb_dev (DfuDevice *device, GUsbDevice *dev, GCancellable *cancellable, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); gboolean reopen_device = FALSE; + /* same */ + if (priv->dev == dev) + return TRUE; + /* close */ if (priv->device_open) { if (!dfu_device_close (device, error)) diff --git a/libdfu/dfu-device.h b/libdfu/dfu-device.h index 28ca08c55..2b220c3fa 100644 --- a/libdfu/dfu-device.h +++ b/libdfu/dfu-device.h @@ -26,6 +26,7 @@ #include #include +#include "dfu-common.h" #include "dfu-target.h" #include "dfu-firmware.h" diff --git a/libdfu/dfu-tool.c b/libdfu/dfu-tool.c index 2aafc3575..2ff860155 100644 --- a/libdfu/dfu-tool.c +++ b/libdfu/dfu-tool.c @@ -28,7 +28,10 @@ #include #include +#include "dfu-device-private.h" + typedef struct { + DfuContext *dfu_context; GPtrArray *cmd_array; gboolean force; gboolean reset; @@ -37,6 +40,21 @@ typedef struct { guint8 alt_setting; } DfuToolPrivate; +/** + * dfu_tool_print_indent: + **/ +static void +dfu_tool_print_indent (const gchar *title, const gchar *message, guint indent) +{ + guint i; + for (i = 0; i < indent; i++) + g_print (" "); + g_print ("%s:", title); + for (i = strlen (title) + indent; i < 15; i++) + g_print (" "); + g_print ("%s\n", message); +} + /** * dfu_tool_private_free: **/ @@ -45,6 +63,8 @@ dfu_tool_private_free (DfuToolPrivate *priv) { if (priv == NULL) return; + if (priv->dfu_context != NULL) + g_object_unref (priv->dfu_context); g_free (priv->device_vid_pid); if (priv->cmd_array != NULL) g_ptr_array_unref (priv->cmd_array); @@ -197,21 +217,11 @@ dfu_tool_run (DfuToolPrivate *priv, const gchar *command, gchar **values, GError static DfuDevice * dfu_tool_get_defalt_device (DfuToolPrivate *priv, GError **error) { - guint i; - g_autoptr(GUsbContext) usb_ctx = NULL; - g_autoptr(GPtrArray) devices = NULL; - - /* get USB context */ - usb_ctx = g_usb_context_new (NULL); - g_usb_context_enumerate (usb_ctx); - /* we specified it manually */ if (priv->device_vid_pid != NULL) { - DfuDevice *device; gchar *tmp; guint64 pid; guint64 vid; - g_autoptr(GUsbDevice) usb_device = NULL; /* parse */ vid = g_ascii_strtoull (priv->device_vid_pid, &tmp, 16); @@ -239,39 +249,13 @@ dfu_tool_get_defalt_device (DfuToolPrivate *priv, GError **error) } /* find device */ - usb_device = g_usb_context_find_by_vid_pid (usb_ctx, - vid, pid, - error); - if (usb_device == NULL) - return NULL; - - /* get DFU device */ - device = dfu_device_new (usb_device); - if (device == NULL) { - g_set_error_literal (error, - DFU_ERROR, - DFU_ERROR_INVALID_DEVICE, - "Not a DFU device"); - return NULL; - } - return device; + return dfu_context_get_device_by_vid_pid (priv->dfu_context, + vid, pid, + error); } /* auto-detect first device */ - devices = g_usb_context_get_devices (usb_ctx); - for (i = 0; i < devices->len; i++) { - GUsbDevice *usb_device = g_ptr_array_index (devices, i); - DfuDevice *device = dfu_device_new (usb_device); - if (device != NULL) - return device; - } - - /* boo-hoo*/ - g_set_error_literal (error, - DFU_ERROR, - DFU_ERROR_INVALID_DEVICE, - "No DFU-capable devices detected"); - return NULL; + return dfu_context_get_device_default (priv->dfu_context, error); } /** @@ -973,6 +957,87 @@ dfu_tool_upload (DfuToolPrivate *priv, gchar **values, GError **error) return TRUE; } +/** + * dfu_tool_get_device_string: + **/ +static gchar * +dfu_tool_get_device_string (DfuDevice *device) +{ + gchar *dstr; + GUsbDevice *dev; + g_autoptr(GError) error = NULL; + + /* open, and get status */ + dev = dfu_device_get_usb_dev (device); + if (!dfu_device_open (device, DFU_DEVICE_OPEN_FLAG_NONE, NULL, &error)) { + return g_strdup_printf ("%04x:%04x [%s]", + g_usb_device_get_vid (dev), + g_usb_device_get_pid (dev), + error->message); + } + dstr = g_strdup_printf ("%04x:%04x [%s:%s]", + g_usb_device_get_vid (dev), + g_usb_device_get_pid (dev), + dfu_state_to_string (dfu_device_get_state (device)), + dfu_status_to_string (dfu_device_get_status (device))); + dfu_device_close (device, NULL); + return dstr; +} + +/** + * dfu_tool_device_added_cb: + **/ +static void +dfu_tool_device_added_cb (DfuContext *context, DfuDevice *device, gpointer user_data) +{ + g_autofree gchar *tmp; + tmp = dfu_tool_get_device_string (device); + /* TRANSLATORS: this is when a device is hotplugged */ + dfu_tool_print_indent (_("Added"), tmp, 0); +} + +/** + * dfu_tool_device_removed_cb: + **/ +static void +dfu_tool_device_removed_cb (DfuContext *context, DfuDevice *device, gpointer user_data) +{ + g_autofree gchar *tmp; + tmp = dfu_tool_get_device_string (device); + /* TRANSLATORS: this is when a device is hotplugged */ + dfu_tool_print_indent (_("Removed"), tmp, 0); +} + +/** + * dfu_tool_device_changed_cb: + **/ +static void +dfu_tool_device_changed_cb (DfuContext *context, DfuDevice *device, gpointer user_data) +{ + g_autofree gchar *tmp; + tmp = dfu_tool_get_device_string (device); + /* TRANSLATORS: this is when a device is hotplugged */ + dfu_tool_print_indent (_("Changed"), tmp, 0); +} + +/** + * dfu_tool_watch: + **/ +static gboolean +dfu_tool_watch (DfuToolPrivate *priv, gchar **values, GError **error) +{ + g_autoptr(GMainLoop) loop = NULL; + g_signal_connect (priv->dfu_context, "device-added", + G_CALLBACK (dfu_tool_device_added_cb), NULL); + g_signal_connect (priv->dfu_context, "device-removed", + G_CALLBACK (dfu_tool_device_removed_cb), NULL); + g_signal_connect (priv->dfu_context, "device-changed", + G_CALLBACK (dfu_tool_device_changed_cb), NULL); + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + return TRUE; +} + /** * dfu_tool_dump: **/ @@ -1187,21 +1252,6 @@ dfu_tool_download (DfuToolPrivate *priv, gchar **values, GError **error) return TRUE; } -/** - * dfu_tool_print_indent: - **/ -static void -dfu_tool_print_indent (const gchar *title, const gchar *message, guint indent) -{ - guint i; - for (i = 0; i < indent; i++) - g_print (" "); - g_print ("%s:", title); - for (i = strlen (title) + indent; i < 15; i++) - g_print (" "); - g_print ("%s\n", message); -} - /** * dfu_tool_list_target: **/ @@ -1245,40 +1295,31 @@ dfu_tool_list_target (DfuTarget *target) static gboolean dfu_tool_list (DfuToolPrivate *priv, gchar **values, GError **error) { - GUsbDevice *usb_device; guint i; g_autoptr(GPtrArray) devices = NULL; - g_autoptr(GUsbContext) usb_ctx = NULL; /* get all the connected USB devices */ - usb_ctx = g_usb_context_new (NULL); - g_usb_context_enumerate (usb_ctx); - devices = g_usb_context_get_devices (usb_ctx); + devices = dfu_context_get_devices (priv->dfu_context); for (i = 0; i < devices->len; i++) { - GPtrArray *dfu_targets; + DfuDevice *device = NULL; DfuTarget *target; + GUsbDevice *dev; + GPtrArray *dfu_targets; const gchar *tmp; guint j; g_autofree gchar *version = NULL; - g_autoptr(DfuDevice) device = NULL; g_autoptr(GError) error_local = NULL; - usb_device = g_ptr_array_index (devices, i); - g_debug ("PROBING [%04x:%04x]", - g_usb_device_get_vid (usb_device), - g_usb_device_get_pid (usb_device)); - device = dfu_device_new (usb_device); - if (device == NULL) - continue; - /* device specific */ - version = as_utils_version_from_uint16 (g_usb_device_get_release (usb_device), + device = g_ptr_array_index (devices, i); + dev = dfu_device_get_usb_dev (device); + version = as_utils_version_from_uint16 (g_usb_device_get_release (dev), AS_VERSION_PARSE_FLAG_NONE); g_print ("%s %04x:%04x [v%s]:\n", /* TRANSLATORS: detected a DFU device */ _("Found"), - g_usb_device_get_vid (usb_device), - g_usb_device_get_pid (usb_device), + g_usb_device_get_vid (dev), + g_usb_device_get_pid (dev), version); /* open */ @@ -1471,6 +1512,12 @@ main (int argc, char *argv[]) /* TRANSLATORS: command description */ _("Dump details about a firmware file"), dfu_tool_dump); + dfu_tool_add (priv->cmd_array, + "watch", + NULL, + /* TRANSLATORS: command description */ + _("Watch DFU devices being hotplugged"), + dfu_tool_watch); /* sort by command name */ g_ptr_array_sort (priv->cmd_array, @@ -1501,6 +1548,10 @@ main (int argc, char *argv[]) return EXIT_SUCCESS; } + /* get all the DFU devices */ + priv->dfu_context = dfu_context_new (); + dfu_context_enumerate (priv->dfu_context, NULL); + /* run the specified command */ ret = dfu_tool_run (priv, argv[1], (gchar**) &argv[2], &error); if (!ret) { diff --git a/libdfu/dfu.h b/libdfu/dfu.h index 1d1a92377..ebbfb1634 100644 --- a/libdfu/dfu.h +++ b/libdfu/dfu.h @@ -30,6 +30,7 @@ #define __DFU_H_INSIDE__ #include +#include #include #include #include