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