From 6bbc4c787b7bc43db9dc4b1d8a7c65d9a8fe2a22 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Thu, 17 Oct 2019 11:20:39 +0100 Subject: [PATCH] jabra: Move the Jabra-specific detach out into its own plugin This also lets us remove the call to dfu_device_wait_for_replug() which was causing a deadlock due to unsafe main context usage. Splitting the code allows us to use the device list to watch for replug, without adding even more Jabra- specific plugin code to the DFU plugin. Looking at this with a 40,000ft view, the Jabra runtime really doesn't have much in common with DFU and the reason it was originally all lumped together was that the daemon couldn't "change" plugins between detach and update. It's unfortunate that we have to include a sleep() in the DFU code after the DFU probe, but this is specified by Jabra themselves. Attempting to open the device without waiting reboots the hub back into runtime firmware mode, so we can't even retry the failing setup action. --- contrib/fwupd.spec.in | 1 + plugins/dfu/README.md | 1 - plugins/dfu/dfu-device.c | 183 +------------------------------- plugins/dfu/dfu-device.h | 6 -- plugins/dfu/dfu-tool.c | 53 +++++++-- plugins/dfu/dfu.quirk | 40 ++----- plugins/jabra/README.md | 23 ++++ plugins/jabra/fu-jabra-device.c | 165 ++++++++++++++++++++++++++++ plugins/jabra/fu-jabra-device.h | 12 +++ plugins/jabra/fu-plugin-jabra.c | 55 ++++++++++ plugins/jabra/jabra.quirk | 23 ++++ plugins/jabra/meson.build | 27 +++++ plugins/meson.build | 1 + 13 files changed, 362 insertions(+), 228 deletions(-) create mode 100644 plugins/jabra/README.md create mode 100644 plugins/jabra/fu-jabra-device.c create mode 100644 plugins/jabra/fu-jabra-device.h create mode 100644 plugins/jabra/fu-plugin-jabra.c create mode 100644 plugins/jabra/jabra.quirk create mode 100644 plugins/jabra/meson.build diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index ebe5b67b5..4e2c5e73d 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -316,6 +316,7 @@ rm ${RPM_BUILD_ROOT}%{_sbindir}/flashrom %if 0%{?enable_flashrom} %{_libdir}/fwupd-plugins-3/libfu_plugin_flashrom.so %endif +%{_libdir}/fwupd-plugins-3/libfu_plugin_jabra.so %if 0%{?have_modem_manager} %{_libdir}/fwupd-plugins-3/libfu_plugin_modem_manager.so %endif diff --git a/plugins/dfu/README.md b/plugins/dfu/README.md index f251486ae..d2505d26b 100644 --- a/plugins/dfu/README.md +++ b/plugins/dfu/README.md @@ -35,4 +35,3 @@ This plugin uses the following plugin-specific quirks: |------------------------|---------------------------------------------|-----------------------| |`DfuFlags` | Optional quirks for a DFU device which doesn't follow the DFU 1.0 or 1.1 specification | 1.0.1| |`DfuForceVersion` | Forces a specific DFU version for the hardware device. This is required if the device does not set, or sets incorrectly, items in the DFU functional descriptor. |1.0.1| -|`DfuJabraDetach` | Assigns the two magic bytes sent to the Jabra hardware when the device is in runtime mode to make it switch into DFU mode.|1.0.1| diff --git a/plugins/dfu/dfu-device.c b/plugins/dfu/dfu-device.c index deb96006c..9a6edd102 100644 --- a/plugins/dfu/dfu-device.c +++ b/plugins/dfu/dfu-device.c @@ -63,18 +63,6 @@ */ #define FU_QUIRKS_DFU_FORCE_VERSION "DfuForceVersion" -/** - * FU_QUIRKS_DFU_JABRA_DETACH: - * @key: the USB device ID, e.g. `USB\VID_0763&PID_2806` - * @value: the two uint8_t unlock values, encoded in base 16, e.g. `0201` - * - * Assigns the two magic bytes sent to the Jabra hardware when the device is - * in runtime mode to make it switch into DFU mode. - * - * Since: 1.0.1 - */ -#define FU_QUIRKS_DFU_JABRA_DETACH "DfuJabraDetach" - #include "config.h" #include @@ -97,11 +85,9 @@ typedef struct { DfuState state; DfuStatus status; GPtrArray *targets; - GUsbContext *usb_context; gboolean done_upload_or_download; gboolean claimed_interface; gchar *chip_id; - gchar *jabra_detach; guint16 version; guint16 force_version; guint16 runtime_pid; @@ -545,20 +531,6 @@ dfu_device_remove_attribute (DfuDevice *device, DfuDeviceAttributes attribute) priv->attributes &= ~attribute; } -void -dfu_device_set_usb_context (DfuDevice *device, GUsbContext *quirks) -{ - DfuDevicePrivate *priv = GET_PRIVATE (device); - g_set_object (&priv->usb_context, quirks); -} - -GUsbContext * -dfu_device_get_usb_context (DfuDevice *device) -{ - DfuDevicePrivate *priv = GET_PRIVATE (device); - return priv->usb_context; -} - /** * dfu_device_new: * @@ -943,23 +915,6 @@ dfu_device_refresh (DfuDevice *device, GError **error) return TRUE; } -static guint8 -_g_usb_device_get_interface_for_class (GUsbDevice *dev, - guint8 intf_class, - GError **error) -{ - g_autoptr(GPtrArray) intfs = NULL; - intfs = g_usb_device_get_interfaces (dev, error); - if (intfs == NULL) - return 0xff; - for (guint i = 0; i < intfs->len; i++) { - GUsbInterface *intf = g_ptr_array_index (intfs, i); - if (g_usb_interface_get_class (intf) == intf_class) - return g_usb_interface_get_number (intf); - } - return 0xff; -} - static gboolean dfu_device_request_detach (DfuDevice *self, GError **error) { @@ -1007,7 +962,6 @@ dfu_device_detach (FuDevice *device, GError **error) DfuDevice *self = DFU_DEVICE (device); DfuDevicePrivate *priv = GET_PRIVATE (self); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); - g_autoptr(GError) error_local = NULL; g_return_val_if_fail (DFU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); @@ -1028,75 +982,6 @@ dfu_device_detach (FuDevice *device, GError **error) return FALSE; } - /* handle Jabra devices that need a magic HID packet */ - if (priv->jabra_detach != NULL) { - guint8 adr = 0x00; - guint8 rep = 0x00; - guint8 iface_hid; - g_autofree guint8 *buf = g_malloc0 (33); - g_autoptr(GError) error_jabra = NULL; - - /* parse string and create magic packet */ - rep = fu_firmware_strparse_uint8 (priv->jabra_detach + 0); - adr = fu_firmware_strparse_uint8 (priv->jabra_detach + 2); - buf[0] = rep; - buf[1] = adr; - buf[2] = 0x00; - buf[3] = 0x01; - buf[4] = 0x85; - buf[5] = 0x07; - - /* detach the HID interface from the kernel driver */ - iface_hid = _g_usb_device_get_interface_for_class (usb_device, - G_USB_DEVICE_CLASS_HID, - &error_local); - if (iface_hid == 0xff) { - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_NOT_SUPPORTED, - "cannot find HID interface: %s", - error_local->message); - return FALSE; - } - g_debug ("claiming interface 0x%02x", iface_hid); - if (!g_usb_device_claim_interface (usb_device, (gint) iface_hid, - G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, - &error_local)) { - g_set_error (error, - FWUPD_ERROR, - FWUPD_ERROR_NOT_SUPPORTED, - "cannot claim interface 0x%02x: %s", - iface_hid, error_local->message); - return FALSE; - } - - /* send magic to device */ - if (!g_usb_device_control_transfer (usb_device, - G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, - G_USB_DEVICE_REQUEST_TYPE_CLASS, - G_USB_DEVICE_RECIPIENT_INTERFACE, - 0x09, - 0x0200 | rep, - 0x0003, - buf, 33, NULL, - FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, - NULL, /* cancellable */ - &error_jabra)) { - g_debug ("whilst sending magic: %s, ignoring", - error_jabra->message); - } - - /* wait for device to re-appear */ - fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); - if (!dfu_device_wait_for_replug (self, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) - return FALSE; - - /* wait 10 seconds for DFU mode to settle */ - g_debug ("waiting for Jabra device to settle..."); - fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY); - g_usleep (10 * G_USEC_PER_SEC); - } - /* the device has no DFU runtime, so cheat */ if (priv->state == DFU_STATE_APP_IDLE && fu_device_has_custom_flag (FU_DEVICE (self), "no-dfu-runtime")) @@ -1357,46 +1242,10 @@ dfu_device_probe (FuUsbDevice *device, GError **error) g_usb_device_get_pid (usb_device)); } - /* success */ - return TRUE; -} - -/** - * dfu_device_wait_for_replug: - * @device: a #DfuDevice - * @timeout: the maximum amount of time to wait - * @error: a #GError, or %NULL - * - * Waits for a DFU device to disconnect and reconnect. - * This does rely on a #GUsbContext being set up before this is called. - * - * Return value: %TRUE for success - **/ -gboolean -dfu_device_wait_for_replug (DfuDevice *device, guint timeout, GError **error) -{ - DfuDevicePrivate *priv = GET_PRIVATE (device); - GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); - g_autoptr(GUsbDevice) usb_device2 = NULL; - - /* close */ - fu_device_close (FU_DEVICE (device), NULL); - - /* watch the device disappear and re-appear */ - usb_device2 = g_usb_context_wait_for_replug (priv->usb_context, - usb_device, - FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, - error); - if (usb_device2 == NULL) - return FALSE; - - /* re-open with new device set */ - fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_IDLE); - fu_usb_device_set_dev (FU_USB_DEVICE (device), usb_device2); - if (!fu_device_open (FU_DEVICE (device), error)) - return FALSE; - if (!dfu_device_refresh_and_clear (device, error)) - return FALSE; + /* hardware rom Jabra literally reboots if you try to retry a failed + * write -- there's no way to avoid blocking the daemon like this... */ + if (fu_device_has_custom_flag (FU_DEVICE (device), "attach-extra-reset")) + g_usleep (10 * G_USEC_PER_SEC); /* success */ return TRUE; @@ -1495,16 +1344,6 @@ dfu_device_attach (FuDevice *device, GError **error) if (!dfu_target_attach (target, error)) return FALSE; - /* some devices need yet another reset */ - if (fu_device_has_custom_flag (FU_DEVICE (self), "attach-extra-reset")) { - if (!dfu_device_wait_for_replug (self, - FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, - error)) - return FALSE; - if (!dfu_device_reset (self, error)) - return FALSE; - } - /* success */ priv->force_version = 0x0; fu_device_set_status (device, FWUPD_STATUS_IDLE); @@ -1874,17 +1713,6 @@ dfu_device_set_quirk_kv (FuDevice *device, DfuDevice *self = DFU_DEVICE (device); DfuDevicePrivate *priv = GET_PRIVATE (self); - if (g_strcmp0 (key, FU_QUIRKS_DFU_JABRA_DETACH) == 0) { - if (value != NULL && strlen (value) == 4) { - priv->jabra_detach = g_strdup (value); - return TRUE; - } - g_set_error_literal (error, - G_IO_ERROR, - G_IO_ERROR_INVALID_DATA, - "unsupported jabra quirk format"); - return FALSE; - } if (g_strcmp0 (key, FU_QUIRKS_DFU_FORCE_VERSION) == 0) { if (value != NULL && strlen (value) == 4) { priv->force_version = fu_firmware_strparse_uint16 (value); @@ -1943,10 +1771,7 @@ dfu_device_finalize (GObject *object) DfuDevice *device = DFU_DEVICE (object); DfuDevicePrivate *priv = GET_PRIVATE (device); - if (priv->usb_context != NULL) - g_object_unref (priv->usb_context); g_free (priv->chip_id); - g_free (priv->jabra_detach); g_ptr_array_unref (priv->targets); G_OBJECT_CLASS (dfu_device_parent_class)->finalize (object); diff --git a/plugins/dfu/dfu-device.h b/plugins/dfu/dfu-device.h index a092624e7..3b1d60152 100644 --- a/plugins/dfu/dfu-device.h +++ b/plugins/dfu/dfu-device.h @@ -63,9 +63,6 @@ guint16 dfu_device_get_runtime_pid (DfuDevice *device); guint16 dfu_device_get_runtime_release (DfuDevice *device); gboolean dfu_device_reset (DfuDevice *device, GError **error); -gboolean dfu_device_wait_for_replug (DfuDevice *device, - guint timeout, - GError **error); DfuFirmware *dfu_device_upload (DfuDevice *device, DfuTargetTransferFlags flags, GError **error); @@ -96,9 +93,6 @@ void dfu_device_set_transfer_size (DfuDevice *device, guint16 transfer_size); void dfu_device_set_timeout (DfuDevice *device, guint timeout_ms); -void dfu_device_set_usb_context (DfuDevice *device, - GUsbContext *quirks); -GUsbContext *dfu_device_get_usb_context (DfuDevice *device); void dfu_device_error_fixup (DfuDevice *device, GError **error); guint dfu_device_get_download_timeout (DfuDevice *device); diff --git a/plugins/dfu/dfu-tool.c b/plugins/dfu/dfu-tool.c index 7c0d9f9d3..91eb18410 100644 --- a/plugins/dfu/dfu-tool.c +++ b/plugins/dfu/dfu-tool.c @@ -239,7 +239,6 @@ dfu_tool_get_default_device (DfuToolPrivate *priv, GError **error) } device = dfu_device_new (usb_device); fu_device_set_quirks (FU_DEVICE (device), priv->quirks); - dfu_device_set_usb_context (device, usb_context); return device; } @@ -249,7 +248,6 @@ dfu_tool_get_default_device (DfuToolPrivate *priv, GError **error) GUsbDevice *usb_device = g_ptr_array_index (devices, i); g_autoptr(DfuDevice) device = dfu_device_new (usb_device); fu_device_set_quirks (FU_DEVICE (device), priv->quirks); - dfu_device_set_usb_context (device, usb_context); if (fu_device_probe (FU_DEVICE (device), NULL)) return g_steal_pointer (&device); } @@ -262,6 +260,41 @@ dfu_tool_get_default_device (DfuToolPrivate *priv, GError **error) return NULL; } +static gboolean +dfu_device_wait_for_replug (DfuToolPrivate *priv, DfuDevice *device, guint timeout, GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); + g_autoptr(GUsbDevice) usb_device2 = NULL; + g_autoptr(GUsbContext) usb_context = NULL; + + /* get all the DFU devices */ + usb_context = g_usb_context_new (error); + if (usb_context == NULL) + return FALSE; + + /* close */ + fu_device_close (FU_DEVICE (device), NULL); + + /* watch the device disappear and re-appear */ + usb_device2 = g_usb_context_wait_for_replug (usb_context, + usb_device, + FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, + error); + if (usb_device2 == NULL) + return FALSE; + + /* re-open with new device set */ + fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_IDLE); + fu_usb_device_set_dev (FU_USB_DEVICE (device), usb_device2); + if (!fu_device_open (FU_DEVICE (device), error)) + return FALSE; + if (!dfu_device_refresh_and_clear (device, error)) + return FALSE; + + /* success */ + return TRUE; +} + static gboolean dfu_tool_set_vendor (DfuToolPrivate *priv, gchar **values, GError **error) { @@ -671,7 +704,7 @@ dfu_tool_read_alt (DfuToolPrivate *priv, gchar **values, GError **error) g_debug ("detaching"); if (!fu_device_detach (FU_DEVICE (device), error)) return FALSE; - if (!dfu_device_wait_for_replug (device, + if (!dfu_device_wait_for_replug (priv, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; @@ -707,7 +740,7 @@ dfu_tool_read_alt (DfuToolPrivate *priv, gchar **values, GError **error) /* do host reset */ if (!fu_device_attach (FU_DEVICE (device), error)) return FALSE; - if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) + if (!dfu_device_wait_for_replug (priv, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* create new firmware object */ @@ -796,7 +829,7 @@ dfu_tool_read (DfuToolPrivate *priv, gchar **values, GError **error) if (!fu_device_has_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!fu_device_detach (FU_DEVICE (device), error)) return FALSE; - if (!dfu_device_wait_for_replug (device, + if (!dfu_device_wait_for_replug (priv, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) { return FALSE; @@ -815,7 +848,7 @@ dfu_tool_read (DfuToolPrivate *priv, gchar **values, GError **error) /* do host reset */ if (!fu_device_attach (FU_DEVICE (device), error)) return FALSE; - if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) + if (!dfu_device_wait_for_replug (priv, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* save file */ @@ -1034,7 +1067,7 @@ dfu_tool_write_alt (DfuToolPrivate *priv, gchar **values, GError **error) g_debug ("detaching"); if (!fu_device_detach (FU_DEVICE (device), error)) return FALSE; - if (!dfu_device_wait_for_replug (device, 5000, error)) + if (!dfu_device_wait_for_replug (priv, device, 5000, error)) return FALSE; } @@ -1096,7 +1129,7 @@ dfu_tool_write_alt (DfuToolPrivate *priv, gchar **values, GError **error) /* do host reset */ if (!fu_device_attach (FU_DEVICE (device), error)) return FALSE; - if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) + if (!dfu_device_wait_for_replug (priv, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* success */ @@ -1141,7 +1174,7 @@ dfu_tool_write (DfuToolPrivate *priv, gchar **values, GError **error) if (!fu_device_has_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!fu_device_detach (FU_DEVICE (device), error)) return FALSE; - if (!dfu_device_wait_for_replug (device, + if (!dfu_device_wait_for_replug (priv, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) { return FALSE; @@ -1163,7 +1196,7 @@ dfu_tool_write (DfuToolPrivate *priv, gchar **values, GError **error) /* do host reset */ if (!fu_device_attach (FU_DEVICE (device), error)) return FALSE; - if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) + if (!dfu_device_wait_for_replug (priv, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* success */ diff --git a/plugins/dfu/dfu.quirk b/plugins/dfu/dfu.quirk index 87d3a9c6a..b00dfd795 100644 --- a/plugins/dfu/dfu.quirk +++ b/plugins/dfu/dfu.quirk @@ -68,49 +68,25 @@ Flags = attach-upload-download Plugin = dfu Flags = no-dfu-runtime,needs-bootloader -# Jabra 410 -[DeviceInstanceId=USB\VID_0B0E&PID_0412] -Plugin = dfu -Flags = no-dfu-runtime -DfuJabraDetach = 0201 -CounterpartGuid = USB\VID_0B0E&PID_0411 - -# Jabra 510 -[DeviceInstanceId=USB\VID_0B0E&PID_0420] -Plugin = dfu -Flags = no-dfu-runtime -DfuJabraDetach = 0201 -CounterpartGuid = USB\VID_0B0E&PID_0421 - -# Jabra 710 -[DeviceInstanceId=USB\VID_0B0E&PID_2475] -Plugin = dfu -Flags = no-dfu-runtime -DfuJabraDetach = 0508 -CounterpartGuid = USB\VID_0B0E&PID_0982 - -# Jabra 810 -[DeviceInstanceId=USB\VID_0B0E&PID_2456] -Plugin = dfu -Flags = no-dfu-runtime -DfuJabraDetach = 0508 -CounterpartGuid = USB\VID_0B0E&PID_0971 - +# Jabra 410 [appIDLE & dfuIDLE] [DeviceInstanceId=USB\VID_0B0E&PID_0411] Plugin = dfu -Flags = no-pid-change,force-dfu-mode,ignore-upload,attach-extra-reset +Flags = no-pid-change,ignore-upload,attach-extra-reset +# Jabra 510 [appIDLE & dfuIDLE] [DeviceInstanceId=USB\VID_0B0E&PID_0421] Plugin = dfu -Flags = no-pid-change,force-dfu-mode,ignore-upload,attach-extra-reset +Flags = no-pid-change,ignore-upload,attach-extra-reset +# Jabra 710 [appIDLE & dfuIDLE] [DeviceInstanceId=USB\VID_0B0E&PID_0982] Plugin = dfu -Flags = no-pid-change,force-dfu-mode,ignore-upload,attach-extra-reset +Flags = no-pid-change,ignore-upload,attach-extra-reset +# Jabra 810 [appIDLE & dfuIDLE] [DeviceInstanceId=USB\VID_0B0E&PID_0971] Plugin = dfu -Flags = no-pid-change,force-dfu-mode,ignore-upload,attach-extra-reset +Flags = no-pid-change,ignore-upload,attach-extra-reset # Atmel AT90USB Bootloader [DeviceInstanceId=USB\VID_03EB&PID_2FF7] diff --git a/plugins/jabra/README.md b/plugins/jabra/README.md new file mode 100644 index 000000000..fc80bb2ab --- /dev/null +++ b/plugins/jabra/README.md @@ -0,0 +1,23 @@ +Jabra Support +============= + +Introduction +------------ + +This plugin is used to detach the Jabra device to DFU mode. + +GUID Generation +--------------- + +These devices use the standard USB DeviceInstanceId values, e.g. + + * `USB\VID_0B0E&PID_0412` + +Quirk use +--------- + +This plugin uses the following plugin-specific quirks: + +| Quirk | Description | fwupd version | +|---------------|----------------------------------------------|---------------| +|`JabraMagic` | Two magic bytes sent to detach into DFU mode.|1.3.3 | diff --git a/plugins/jabra/fu-jabra-device.c b/plugins/jabra/fu-jabra-device.c new file mode 100644 index 000000000..363438a0f --- /dev/null +++ b/plugins/jabra/fu-jabra-device.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2019 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-firmware-common.h" + +#include "fu-jabra-device.h" + +struct _FuJabraDevice { + FuUsbDevice parent_instance; + gchar *magic; +}; + +G_DEFINE_TYPE (FuJabraDevice, fu_jabra_device, FU_TYPE_USB_DEVICE) + +static void +fu_jabra_device_to_string (FuDevice *device, guint idt, GString *str) +{ + FuJabraDevice *self = FU_JABRA_DEVICE (device); + fu_common_string_append_kv (str, idt, "Magic", self->magic); +} + +static guint8 +_g_usb_device_get_interface_for_class (GUsbDevice *dev, + guint8 intf_class, + GError **error) +{ + g_autoptr(GPtrArray) intfs = NULL; + intfs = g_usb_device_get_interfaces (dev, error); + if (intfs == NULL) + return 0xff; + for (guint i = 0; i < intfs->len; i++) { + GUsbInterface *intf = g_ptr_array_index (intfs, i); + if (g_usb_interface_get_class (intf) == intf_class) + return g_usb_interface_get_number (intf); + } + return 0xff; +} + +/* slightly weirdly, this magic turns the device into appIDLE, so we + * need the DFU plugin to further detach us into dfuIDLE */ +static gboolean +fu_jabra_device_prepare (FuDevice *device, FwupdInstallFlags flags, GError **error) +{ + FuJabraDevice *self = FU_JABRA_DEVICE (device); + GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); + guint8 adr = 0x00; + guint8 rep = 0x00; + guint8 iface_hid; + guint8 buf[33] = { 0x00 }; + g_autoptr(GError) error_local = NULL; + + /* parse string and create magic packet */ + rep = fu_firmware_strparse_uint8 (self->magic + 0); + adr = fu_firmware_strparse_uint8 (self->magic + 2); + buf[0] = rep; + buf[1] = adr; + buf[2] = 0x00; + buf[3] = 0x01; + buf[4] = 0x85; + buf[5] = 0x07; + + /* detach the HID interface from the kernel driver */ + iface_hid = _g_usb_device_get_interface_for_class (usb_device, + G_USB_DEVICE_CLASS_HID, + &error_local); + if (iface_hid == 0xff) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "cannot find HID interface: %s", + error_local->message); + return FALSE; + } + g_debug ("claiming interface 0x%02x", iface_hid); + if (!g_usb_device_claim_interface (usb_device, (gint) iface_hid, + G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, + &error_local)) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "cannot claim interface 0x%02x: %s", + iface_hid, error_local->message); + return FALSE; + } + + /* send magic to device */ + if (!g_usb_device_control_transfer (usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_CLASS, + G_USB_DEVICE_RECIPIENT_INTERFACE, + 0x09, + 0x0200 | rep, + 0x0003, + buf, 33, NULL, + FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, + NULL, /* cancellable */ + &error_local)) { + g_debug ("whilst sending magic: %s, ignoring", + error_local->message); + } + + /* wait for device to re-appear and be added to the dfu plugin */ + fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); + fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); + return TRUE; +} + +static gboolean +fu_jabra_device_set_quirk_kv (FuDevice *device, + const gchar *key, + const gchar *value, + GError **error) +{ + FuJabraDevice *self = FU_JABRA_DEVICE (device); + + if (g_strcmp0 (key, "JabraMagic") == 0) { + if (value != NULL && strlen (value) == 4) { + self->magic = g_strdup (value); + return TRUE; + } + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "unsupported jabra quirk format"); + return FALSE; + } + + /* failed */ + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "quirk key not supported"); + return FALSE; +} + +static void +fu_jabra_device_init (FuJabraDevice *self) +{ + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); + fu_device_set_remove_delay (FU_DEVICE (self), 20000); /* 10+10s! */ +} + +static void +fu_jabra_device_finalize (GObject *object) +{ + FuJabraDevice *self = FU_JABRA_DEVICE (object); + g_free (self->magic); + G_OBJECT_CLASS (fu_jabra_device_parent_class)->finalize (object); +} + +static void +fu_jabra_device_class_init (FuJabraDeviceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); + object_class->finalize = fu_jabra_device_finalize; + klass_device->to_string = fu_jabra_device_to_string; + klass_device->prepare = fu_jabra_device_prepare; + klass_device->set_quirk_kv = fu_jabra_device_set_quirk_kv; +} diff --git a/plugins/jabra/fu-jabra-device.h b/plugins/jabra/fu-jabra-device.h new file mode 100644 index 000000000..8d203610b --- /dev/null +++ b/plugins/jabra/fu-jabra-device.h @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2019 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-plugin.h" + +#define FU_TYPE_JABRA_DEVICE (fu_jabra_device_get_type ()) +G_DECLARE_FINAL_TYPE (FuJabraDevice, fu_jabra_device, FU, JABRA_DEVICE, FuUsbDevice) diff --git a/plugins/jabra/fu-plugin-jabra.c b/plugins/jabra/fu-plugin-jabra.c new file mode 100644 index 000000000..8f5d8a9fa --- /dev/null +++ b/plugins/jabra/fu-plugin-jabra.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-plugin-vfuncs.h" + +#include "fu-jabra-device.h" + +void +fu_plugin_init (FuPlugin *plugin) +{ + fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); + fu_plugin_set_device_gtype (plugin, FU_TYPE_JABRA_DEVICE); +} + +/* slightly weirdly, this takes us from appIDLE back into the actual + * runtime mode where the device actually works */ +gboolean +fu_plugin_update_cleanup (FuPlugin *plugin, + FwupdInstallFlags flags, + FuDevice *device, + GError **error) +{ + GUsbDevice *usb_device; + g_autoptr(FuDeviceLocker) locker = NULL; + g_autoptr(GError) error_local = NULL; + + /* check for a property on the *dfu* FuDevice, which is also why we + * can't just rely on using FuDevice->cleanup() */ + if (!fu_device_has_custom_flag (device, "attach-extra-reset")) + return TRUE; + locker = fu_device_locker_new (device, error); + if (locker == NULL) + return FALSE; + g_debug ("performing extra reset into firmware mode"); + usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); + if (!g_usb_device_reset (usb_device, &error_local)) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "cannot reset USB device: %s [%i]", + error_local->message, + error_local->code); + return FALSE; + } + + /* wait for device to re-appear */ + fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); + fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); + return TRUE; +} diff --git a/plugins/jabra/jabra.quirk b/plugins/jabra/jabra.quirk new file mode 100644 index 000000000..fad4ea9a4 --- /dev/null +++ b/plugins/jabra/jabra.quirk @@ -0,0 +1,23 @@ +# Jabra 410 [runtime] +[DeviceInstanceId=USB\VID_0B0E&PID_0412] +Plugin = jabra +JabraMagic = 0201 +CounterpartGuid = USB\VID_0B0E&PID_0411 + +# Jabra 510 [runtime] +[DeviceInstanceId=USB\VID_0B0E&PID_0420] +Plugin = jabra +JabraMagic = 0201 +CounterpartGuid = USB\VID_0B0E&PID_0421 + +# Jabra 710 [runtime] +[DeviceInstanceId=USB\VID_0B0E&PID_2475] +Plugin = jabra +JabraMagic = 0508 +CounterpartGuid = USB\VID_0B0E&PID_0982 + +# Jabra 810 [runtime] +[DeviceInstanceId=USB\VID_0B0E&PID_2456] +Plugin = jabra +JabraMagic = 0508 +CounterpartGuid = USB\VID_0B0E&PID_0971 diff --git a/plugins/jabra/meson.build b/plugins/jabra/meson.build new file mode 100644 index 000000000..0b9c5bdf9 --- /dev/null +++ b/plugins/jabra/meson.build @@ -0,0 +1,27 @@ +cargs = ['-DG_LOG_DOMAIN="FuPluginJabra"'] + +install_data(['jabra.quirk'], + install_dir: join_paths(datadir, 'fwupd', 'quirks.d') +) + +shared_module('fu_plugin_jabra', + fu_hash, + sources : [ + 'fu-plugin-jabra.c', + 'fu-jabra-device.c', + ], + include_directories : [ + include_directories('../..'), + include_directories('../../src'), + include_directories('../../libfwupd'), + ], + install : true, + install_dir: plugin_dir, + link_with : [ + libfwupdprivate, + ], + c_args : cargs, + dependencies : [ + plugin_deps, + ], +) diff --git a/plugins/meson.build b/plugins/meson.build index 2913bf885..08b5da311 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -4,6 +4,7 @@ subdir('colorhug') subdir('coreboot') subdir('ebitdo') subdir('fastboot') +subdir('jabra') subdir('steelseries') subdir('dell-dock') subdir('nitrokey')