From 35af30321a155db92d75ec868cbf3da446db8bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Ca=C3=B1uelo?= Date: Tue, 31 Aug 2021 17:44:24 +0100 Subject: [PATCH] logitech-hidpp: Add support for updating Bolt receivers --- .../devices/logitech-bolt-receiver.json | 18 ++ plugins/logitech-hidpp/README.md | 68 ++++- .../logitech-hidpp/fu-logitech-hidpp-common.h | 1 + .../logitech-hidpp/fu-logitech-hidpp-device.c | 69 ++++- .../fu-logitech-hidpp-hidpp-msg.c | 2 + .../logitech-hidpp/fu-logitech-hidpp-hidpp.h | 15 + .../fu-logitech-hidpp-runtime-bolt.c | 256 ++++++++++++++++++ .../fu-logitech-hidpp-runtime-bolt.h | 19 ++ .../fu-logitech-hidpp-runtime.c | 30 ++ .../logitech-hidpp/fu-plugin-logitech-hidpp.c | 2 + plugins/logitech-hidpp/logitech-hidpp.quirk | 16 ++ plugins/logitech-hidpp/meson.build | 1 + 12 files changed, 482 insertions(+), 15 deletions(-) create mode 100644 data/device-tests/devices/logitech-bolt-receiver.json create mode 100644 plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.c create mode 100644 plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.h diff --git a/data/device-tests/devices/logitech-bolt-receiver.json b/data/device-tests/devices/logitech-bolt-receiver.json new file mode 100644 index 000000000..60660d835 --- /dev/null +++ b/data/device-tests/devices/logitech-bolt-receiver.json @@ -0,0 +1,18 @@ +{ + "name": "Logitech Bolt receiver", + "guids": [ + "af1404c4-f038-5b3e-92b0-09bf4aa84f1c" + ], + "interactive": true, + "protocol": "com.logitech.unifyingsigned", + "releases": [ + { + "version": "MPR05.00_B0008", + "file": "b061b6b7338ace5cd8d341d229a81004f78a7551b3e507de578dbab1bc686294-MPR05.00_B0008_full.cab" + }, + { + "version": "MPR05.00_B0009", + "file": "2a2b7197f0096a2c119e58cfdaa14531b2ca11bdad10553cd5069fb4d7dba106-MPR05.00_B0009_full.cab" + } + ] +} diff --git a/plugins/logitech-hidpp/README.md b/plugins/logitech-hidpp/README.md index 910d235e9..9f95ed56b 100644 --- a/plugins/logitech-hidpp/README.md +++ b/plugins/logitech-hidpp/README.md @@ -2,8 +2,12 @@ ## Introduction -This plugin can flash the firmware on Logitech Unifying dongles, both the -Nordic (U0007) device and the Texas Instruments (U0008) version. +This plugin can flash the firmware on: + +* Logitech Unifying dongles, both the Nordic (U0007) device and the + Texas Instruments (U0008) versions +* Logitech Bolt dongles +* Unifying peripherals through the Unifying receiver This plugin will not work with the different "Nano" dongle (U0010) as it does not use the Unifying protocol. @@ -26,7 +30,8 @@ This plugin supports the following protocol IDs: ## GUID Generation -These devices use the standard USB DeviceInstanceId values when in DFU mode: +The Unifying receivers and peripherals use the standard USB +DeviceInstanceId values when in DFU mode: * `USB\VID_046D&PID_AAAA&REV_0001` * `USB\VID_046D&PID_AAAA` @@ -38,6 +43,11 @@ When in runtime mode, the HID raw DeviceInstanceId values are used: * `HIDRAW\VEN_046D&DEV_C52B` * `HIDRAW\VEN_046D` +The Bolt dongle and peripherals use HID raw DeviceInstanceId values +regardless of their mode. This might change once these devices are +handled by the Logitech Linux driver instead of by the generic hid +driver. + ## Vendor ID Security The vendor ID is set from the vendor ID, in this instance set to `USB:0x046D` @@ -45,17 +55,25 @@ in bootloader and `HIDRAW:0x046D` in runtime mode. ## Update Behavior -The peripheral firmware is deployed when the device is in normal runtime mode, -and the device will reset when the new firmware has been written. +Due to the variety of devices supported and the differences in how +they're enumerated, the update behavior is slightly different between +them. -The receiver device presents in runtime mode, but on detach re-enumerates with a +In all cases, the devices have to be put in bootloader mode to run the +DFU process. While in bootloader mode, the user won't be able to use the +device. For receivers, that also means that while they're in bootloader +mode, the peripherals paired to them won't work during the update. + +A Unifying receiver presents in runtime mode, but on detach re-enumerates with a different USB PID in a bootloader mode. On attach the device again re-enumerates -back to the runtime mode. All unifying devices attached to the receiver will not -work for the duration of the update. +back to the runtime mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. +The Bolt receiver enumerates as a hidraw device both in runtime and +bootloader mode, but with different HIDRAW devIDs. + ## Design Notes When a dongle is detected in bootloader mode we detach the hidraw driver from @@ -64,6 +82,40 @@ corrupt the uploading firmware. For application firmware we use hidraw which means the hardware keeps working while probing, and also allows us to detect paired devices. +### How the code is organized + +Here's how the different devices are handled in the plugin: + +* Unifying receiver in runtime mode: FuLogitechHidPpRuntimeUnifying + (fu-logitech-hidpp-runtime-unifying.c) +* Unifying receiver in bootloader mode: + * Nordic chipset: FuLogitechHidPpBootloaderNordic + (fu-logitech-hidpp-bootloader-nordic.c) + * TI chipset: FuLogitechHidPpBootloaderTexas + (fu-logitech-hidpp-bootloader-texas.c) +* Bolt receiver in runtime mode: FuLogitechHidPpRuntimeBolt + (fu-logitech-hidpp-runtime-bolt.c) +* Bolt receiver in bootloader mode and all peripherals: + FuLogitechHidPpDevice (fu-logitech-hidpp-device.c) + +FuLogitechHidPpDevice effectively handles all devices that use the +HID++2.0 protocol. + +### Plugin-specific flags + +Even though the same code handles multiple different devices, there are +some inherent differences in them that makes it necessary to handle some +exceptional behaviors sometimes. + +In order to do that there are a few specific flags that can be used to +tweak the plugin code for certain device types: + +* rebind-attach: some devices will have their device file unbound and + re-bound after reset, so the device object can't be simply re-probed + using the same file descriptor. +* force-receiver-id: this flag is used to differentiate the receiver device in + FuLogitechHidPpDevice, since the receiver has a specific HID++ ID. + ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. diff --git a/plugins/logitech-hidpp/fu-logitech-hidpp-common.h b/plugins/logitech-hidpp/fu-logitech-hidpp-common.h index cdecdb7aa..bd130836e 100644 --- a/plugins/logitech-hidpp/fu-logitech-hidpp-common.h +++ b/plugins/logitech-hidpp/fu-logitech-hidpp-common.h @@ -15,6 +15,7 @@ #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_NORDIC_PICO 0xaaae #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_TEXAS 0xaaac #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_TEXAS_PICO 0xaaad +#define FU_UNIFYING_DEVICE_PID_BOOTLOADER_BOLT 0xab07 /* Signed firmware are very long to verify on the device */ #define FU_UNIFYING_DEVICE_TIMEOUT_MS 30000 diff --git a/plugins/logitech-hidpp/fu-logitech-hidpp-device.c b/plugins/logitech-hidpp/fu-logitech-hidpp-device.c index 816e85759..7cf48467f 100644 --- a/plugins/logitech-hidpp/fu-logitech-hidpp-device.c +++ b/plugins/logitech-hidpp/fu-logitech-hidpp-device.c @@ -11,6 +11,26 @@ #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-device.h" #include "fu-logitech-hidpp-hidpp.h" +#include "fu-logitech-hidpp-runtime-bolt.h" + +/** + * FU_LOGITECH_HIDPP_DEVICE_FLAG_FORCE_RECEIVER_ID: + * + * Device is a unifying or Bolt receiver. + * + * Since: 1.7.0 + */ +#define FU_LOGITECH_HIDPP_DEVICE_FLAG_FORCE_RECEIVER_ID (1 << 0) + +/** + * FU_LOGITECH_HIDPP_DEVICE_FLAG_REBIND_ATTACH: + * + * The device file is automatically unbound and re-bound after the + * device is attached. + * + * Since: 1.7.0 + */ +#define FU_LOGITECH_HIDPP_DEVICE_FLAG_REBIND_ATTACH (1 << 2) typedef struct { guint8 cached_fw_entity; @@ -139,6 +159,8 @@ fu_logitech_hidpp_feature_to_string(guint16 feature) return "DfuControl"; if (feature == HIDPP_FEATURE_DFU_CONTROL_SIGNED) return "DfuControlSigned"; + if (feature == HIDPP_FEATURE_DFU_CONTROL_BOLT) + return "DfuControlBolt"; if (feature == HIDPP_FEATURE_DFU) return "Dfu"; return NULL; @@ -637,9 +659,13 @@ fu_logitech_hidpp_device_setup(FuDevice *device, GError **error) HIDPP_FEATURE_UNIFIED_BATTERY, HIDPP_FEATURE_DFU_CONTROL, HIDPP_FEATURE_DFU_CONTROL_SIGNED, + HIDPP_FEATURE_DFU_CONTROL_BOLT, HIDPP_FEATURE_DFU, HIDPP_FEATURE_ROOT}; + if (fu_device_has_private_flag(device, FU_LOGITECH_HIDPP_DEVICE_FLAG_FORCE_RECEIVER_ID)) + priv->hidpp_id = HIDPP_DEVICE_ID_RECEIVER; + /* ping device to get HID++ version */ if (!fu_logitech_hidpp_device_ping(self, error)) return FALSE; @@ -713,7 +739,10 @@ fu_logitech_hidpp_device_setup(FuDevice *device, GError **error) fu_device_remove_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_add_protocol(FU_DEVICE(self), "com.logitech.unifying"); } - idx = fu_logitech_hidpp_device_feature_get_idx(self, HIDPP_FEATURE_DFU_CONTROL_SIGNED); + idx = fu_logitech_hidpp_device_feature_get_idx(self, HIDPP_FEATURE_DFU_CONTROL_BOLT); + if (idx == 0x00) + idx = fu_logitech_hidpp_device_feature_get_idx(self, + HIDPP_FEATURE_DFU_CONTROL_SIGNED); if (idx != 0x00) { /* check the feature is available */ g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new(); @@ -770,8 +799,10 @@ fu_logitech_hidpp_device_detach(FuDevice *device, GError **error) return TRUE; } - /* this requires user action */ - idx = fu_logitech_hidpp_device_feature_get_idx(self, HIDPP_FEATURE_DFU_CONTROL); + /* these require user action */ + idx = fu_logitech_hidpp_device_feature_get_idx(self, HIDPP_FEATURE_DFU_CONTROL_BOLT); + if (idx == 0x00) + idx = fu_logitech_hidpp_device_feature_get_idx(self, HIDPP_FEATURE_DFU_CONTROL); if (idx != 0x00) { g_autoptr(FwupdRequest) request = fwupd_request_new(); msg->report_id = HIDPP_REPORT_ID_LONG; @@ -1023,6 +1054,7 @@ fu_logitech_hidpp_device_write_firmware(FuDevice *device, GError **error) { FuLogitechHidPpDevice *self = FU_HIDPP_DEVICE(device); + FuLogitechHidPpDevicePrivate *priv = GET_PRIVATE(self); gsize sz = 0; const guint8 *data; guint8 cmd = 0x04; @@ -1041,8 +1073,12 @@ fu_logitech_hidpp_device_write_firmware(FuDevice *device, if (fw == NULL) return FALSE; - /* flash hardware */ + /* flash hardware -- the first data byte is the fw entity */ data = g_bytes_get_data(fw, &sz); + if (priv->cached_fw_entity != data[0]) { + g_warning("updating cached entity 0x%x with 0x%x", priv->cached_fw_entity, data[0]); + priv->cached_fw_entity = data[0]; + } fu_device_set_status(device, FWUPD_STATUS_DEVICE_WRITE); for (gsize i = 0; i < sz / 16; i++) { /* send packet and wait for reply */ @@ -1116,9 +1152,19 @@ fu_logitech_hidpp_device_attach(FuLogitechHidPpDevice *self, guint8 entity, GErr } } - /* reprobe */ - if (!fu_device_retry(device, fu_logitech_hidpp_device_reprobe_cb, 5, NULL, error)) - return FALSE; + if (fu_device_has_private_flag(device, FU_LOGITECH_HIDPP_DEVICE_FLAG_REBIND_ATTACH)) { + fu_device_set_poll_interval(device, 0); + /* + * Wait for device to become ready after flashing. + * Possible race condition: after the device is reset, Linux might enumerate it as + * a different hidraw device depending on timing. + */ + fu_device_sleep_with_progress(device, 1); /* second */ + } else { + /* device file hasn't been unbound/re-bound, just probe again */ + if (!fu_device_retry(device, fu_logitech_hidpp_device_reprobe_cb, 5, NULL, error)) + return FALSE; + } /* success */ return TRUE; @@ -1130,6 +1176,8 @@ fu_logitech_hidpp_device_attach_cached(FuDevice *device, GError **error) FuLogitechHidPpDevice *self = FU_HIDPP_DEVICE(device); FuLogitechHidPpDevicePrivate *priv = GET_PRIVATE(self); fu_device_set_status(device, FWUPD_STATUS_DEVICE_RESTART); + if (fu_device_has_private_flag(device, FU_LOGITECH_HIDPP_DEVICE_FLAG_REBIND_ATTACH)) + fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return fu_logitech_hidpp_device_attach(self, priv->cached_fw_entity, error); } @@ -1170,5 +1218,12 @@ fu_logitech_hidpp_device_init(FuLogitechHidPpDevice *self) fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_retry_set_delay(FU_DEVICE(self), 1000); + fu_device_register_private_flag(FU_DEVICE(self), + FU_LOGITECH_HIDPP_DEVICE_FLAG_FORCE_RECEIVER_ID, + "force-receiver-id"); + fu_device_register_private_flag(FU_DEVICE(self), + FU_LOGITECH_HIDPP_DEVICE_FLAG_REBIND_ATTACH, + "rebind-attach"); + fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG); fu_device_set_battery_threshold(FU_DEVICE(self), 20); } diff --git a/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp-msg.c b/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp-msg.c index 16f166bc5..589baaf7c 100644 --- a/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp-msg.c +++ b/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp-msg.c @@ -104,6 +104,8 @@ fu_logitech_hidpp_msg_fcn_id_to_string(FuLogitechHidPpHidppMsg *msg) return "device-firmware-update-mode"; if (msg->function_id == HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION) return "device-firmware-information"; + if (msg->function_id == BOLT_REGISTER_RECEIVER_FW_INFORMATION) + return "receiver-fw-information"; break; default: break; diff --git a/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp.h b/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp.h index c42700e40..930e9be71 100644 --- a/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp.h +++ b/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp.h @@ -99,6 +99,20 @@ #define HIDPP_REGISTER_DEVICE_FIRMWARE_UPDATE_MODE 0xF0 #define HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION 0xF1 +/* + * Bolt registers + */ +#define BOLT_REGISTER_HIDPP_REPORTING 0x00 +#define BOLT_REGISTER_CONNECTION_STATE 0x02 +#define BOLT_REGISTER_DEVICE_ACTIVITY 0xB3 +#define BOLT_REGISTER_PAIRING_INFORMATION 0xB5 +#define BOLT_REGISTER_PERFORM_DEVICE_DISCOVERY 0xC0 +#define BOLT_REGISTER_PERFORM_DEVICE_PAIRING 0xC1 +#define BOLT_REGISTER_RESET 0xF2 +#define BOLT_REGISTER_RECEIVER_FW_INFORMATION 0xF4 +#define BOLT_REGISTER_DFU_CONTROL 0xF5 +#define BOLT_REGISTER_UNIQUE_IDENTIFIER 0xFB + /* * HID++2.0 error codes */ @@ -122,6 +136,7 @@ #define HIDPP_FEATURE_GET_DEVICE_NAME_TYPE 0x0005 #define HIDPP_FEATURE_DFU_CONTROL 0x00c1 #define HIDPP_FEATURE_DFU_CONTROL_SIGNED 0x00c2 +#define HIDPP_FEATURE_DFU_CONTROL_BOLT 0x00c3 #define HIDPP_FEATURE_DFU 0x00d0 #define HIDPP_FEATURE_BATTERY_LEVEL_STATUS 0x1000 #define HIDPP_FEATURE_UNIFIED_BATTERY 0x1004 diff --git a/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.c b/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.c new file mode 100644 index 000000000..95e5ae2d9 --- /dev/null +++ b/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2021 Ricardo Cañuelo + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-logitech-hidpp-common.h" +#include "fu-logitech-hidpp-device.h" +#include "fu-logitech-hidpp-hidpp.h" +#include "fu-logitech-hidpp-runtime-bolt.h" + +struct _FuLogitechHidPpRuntimeBolt { + FuLogitechHidPpRuntime parent_instance; + guint8 pairing_slots; +}; + +G_DEFINE_TYPE(FuLogitechHidPpRuntimeBolt, fu_logitech_hidpp_runtime_bolt, FU_TYPE_HIDPP_RUNTIME) + +static gboolean +fu_logitech_hidpp_runtime_bolt_detach(FuDevice *device, GError **error) +{ + FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device); + g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new(); + g_autoptr(GError) error_local = NULL; + g_autoptr(FwupdRequest) request = fwupd_request_new(); + + msg->report_id = HIDPP_REPORT_ID_LONG; + msg->device_id = HIDPP_DEVICE_ID_RECEIVER; + msg->sub_id = HIDPP_SUBID_SET_LONG_REGISTER; + msg->function_id = BOLT_REGISTER_DFU_CONTROL; + msg->data[0] = 1; /* Enable DFU */ + msg->data[4] = 'P'; + msg->data[5] = 'R'; + msg->data[6] = 'E'; + msg->hidpp_version = 1; + msg->flags = FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT; + if (!fu_logitech_hidpp_send(fu_logitech_hidpp_runtime_get_io_channel(self), + msg, + FU_UNIFYING_DEVICE_TIMEOUT_MS, + &error_local)) { + if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_WRITE)) { + g_debug("failed to detach to bootloader: %s", error_local->message); + } else { + g_prefix_error(&error_local, "failed to detach to bootloader: "); + g_propagate_error(error, g_steal_pointer(&error_local)); + return FALSE; + } + } + + fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); + /* generate a message if not already set */ + if (fu_device_get_update_message(device) == NULL) { + g_autofree gchar *str = NULL; + str = g_strdup_printf("%s needs to be manually restarted to complete the update. " + "Please unplug it and plug it back again.", + fu_device_get_name(device)); + fu_device_set_update_message(device, str); + } + fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); + fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG); + fwupd_request_set_message(request, fu_device_get_update_message(device)); + fu_device_emit_request(device, request); + return TRUE; +} + +static void +fu_logitech_hidpp_runtime_bolt_to_string(FuDevice *device, guint idt, GString *str) +{ + FuLogitechHidPpRuntimeBolt *self = FU_HIDPP_RUNTIME_BOLT(device); + + FU_DEVICE_CLASS(fu_logitech_hidpp_runtime_bolt_parent_class)->to_string(device, idt, str); + fu_common_string_append_ku(str, idt, "PairingSlots", self->pairing_slots); +} + +static gboolean +fu_logitech_hidpp_runtime_bolt_process_notification(FuLogitechHidPpRuntimeBolt *self, + FuLogitechHidPpHidppMsg *msg) +{ + g_autoptr(GError) error_local = NULL; + + /* HID++1.0 error */ + if (!fu_logitech_hidpp_msg_is_error(msg, &error_local)) { + g_warning("failed to get pending read: %s", error_local->message); + return TRUE; + } + + /* unifying receiver notification */ + if (msg->report_id == HIDPP_REPORT_ID_SHORT) { + switch (msg->sub_id) { + case HIDPP_SUBID_DEVICE_CONNECTION: + case HIDPP_SUBID_DEVICE_DISCONNECTION: + case HIDPP_SUBID_DEVICE_LOCKING_CHANGED: + g_debug("ignoring device message"); + break; + case HIDPP_SUBID_LINK_QUALITY: + g_debug("ignoring link quality message"); + break; + case HIDPP_SUBID_ERROR_MSG: + g_debug("ignoring link quality message"); + break; + default: + g_debug("unknown SubID %02x", msg->sub_id); + break; + } + } + + return TRUE; +} + +static gboolean +fu_logitech_hidpp_runtime_bolt_poll(FuDevice *device, GError **error) +{ + FuLogitechHidPpRuntime *runtime = FU_HIDPP_RUNTIME(device); + FuLogitechHidPpRuntimeBolt *self = FU_HIDPP_RUNTIME_BOLT(device); + const guint timeout = 1; /* ms */ + g_autoptr(GError) error_local = NULL; + g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new(); + + if (!fu_device_open(device, error)) + return FALSE; + + /* is there any pending data to read */ + msg->hidpp_version = 1; + while (fu_logitech_hidpp_receive(fu_logitech_hidpp_runtime_get_io_channel(runtime), + msg, + timeout, + &error_local)) { + fu_logitech_hidpp_runtime_bolt_process_notification(self, msg); + } + if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) { + g_propagate_error(error, g_steal_pointer(&error_local)); + g_prefix_error(error, "Error polling Bolt receiver: "); + return FALSE; + } + + return TRUE; +} + +static gboolean +fu_logitech_hidpp_runtime_bolt_setup_internal(FuDevice *device, GError **error) +{ + FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device); + FuLogitechHidPpRuntimeBolt *bolt = FU_HIDPP_RUNTIME_BOLT(device); + g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new(); + + msg->report_id = HIDPP_REPORT_ID_SHORT; + msg->device_id = HIDPP_DEVICE_ID_RECEIVER; + msg->sub_id = HIDPP_SUBID_GET_LONG_REGISTER; + msg->function_id = BOLT_REGISTER_PAIRING_INFORMATION; + msg->data[0] = 0x02; /* FW Version (contains the number of pairing slots) */ + msg->hidpp_version = 1; + if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self), + msg, + error)) { + g_prefix_error(error, "failed to fetch the number of pairing slots: "); + return FALSE; + } + bolt->pairing_slots = msg->data[8]; + + /* + * TODO: Iterate only over the first three entity indexes for + * now. + */ + for (guint i = 0; i < 3; i++) { + g_autofree gchar *version = NULL; + + msg->report_id = HIDPP_REPORT_ID_SHORT; + msg->device_id = HIDPP_DEVICE_ID_RECEIVER; + msg->sub_id = HIDPP_SUBID_GET_LONG_REGISTER; + msg->function_id = BOLT_REGISTER_RECEIVER_FW_INFORMATION; + msg->data[0] = i; + msg->hidpp_version = 1; + if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self), + msg, + error)) { + g_prefix_error(error, "failed to read device config: "); + return FALSE; + } + switch (msg->data[0]) { + case 0: + /* main application */ + version = fu_logitech_hidpp_format_version("MPR", + msg->data[1], + msg->data[2], + (guint16)msg->data[3] << 8 | + msg->data[4]); + fu_device_set_version(device, version); + break; + case 1: + /* bootloader */ + version = fu_logitech_hidpp_format_version("BOT", + msg->data[1], + msg->data[2], + (guint16)msg->data[3] << 8 | + msg->data[4]); + fu_device_set_version_bootloader(device, version); + break; + case 5: + /* SoftDevice */ + break; + default: + break; + } + } + + /* enable HID++ notifications */ + if (!fu_logitech_hidpp_runtime_enable_notifications(self, error)) { + g_prefix_error(error, "failed to enable notifications: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_logitech_hidpp_runtime_bolt_setup(FuDevice *device, GError **error) +{ + g_autoptr(GError) error_local = NULL; + for (guint i = 0; i < 5; i++) { + g_clear_error(&error_local); + /* HID++1.0 devices have to sleep to allow Solaar to talk to + * the device first -- we can't use the SwID as this is a + * HID++2.0 feature */ + g_usleep(200 * 1000); + if (fu_logitech_hidpp_runtime_bolt_setup_internal(device, &error_local)) + return TRUE; + if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_DATA)) { + g_propagate_error(error, g_steal_pointer(&error_local)); + return FALSE; + } + } + g_propagate_error(error, g_steal_pointer(&error_local)); + return FALSE; +} + +static void +fu_logitech_hidpp_runtime_bolt_class_init(FuLogitechHidPpRuntimeBoltClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + + klass_device->detach = fu_logitech_hidpp_runtime_bolt_detach; + klass_device->setup = fu_logitech_hidpp_runtime_bolt_setup; + klass_device->poll = fu_logitech_hidpp_runtime_bolt_poll; + klass_device->to_string = fu_logitech_hidpp_runtime_bolt_to_string; +} + +static void +fu_logitech_hidpp_runtime_bolt_init(FuLogitechHidPpRuntimeBolt *self) +{ + fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG); + fu_device_set_name(FU_DEVICE(self), "Bolt Receiver"); + fu_device_add_protocol(FU_DEVICE(self), "com.logitech.unifyingsigned"); +} diff --git a/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.h b/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.h new file mode 100644 index 000000000..c941c0b75 --- /dev/null +++ b/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 Ricardo Cañuelo + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-logitech-hidpp-runtime.h" + +#define FU_TYPE_HIDPP_RUNTIME_BOLT (fu_logitech_hidpp_runtime_bolt_get_type()) +G_DECLARE_FINAL_TYPE(FuLogitechHidPpRuntimeBolt, + fu_logitech_hidpp_runtime_bolt, + FU, + HIDPP_RUNTIME_BOLT, + FuLogitechHidPpRuntime) + +void +fu_logitech_hidpp_runtime_bolt_poll_peripherals(FuDevice *device); diff --git a/plugins/logitech-hidpp/fu-logitech-hidpp-runtime.c b/plugins/logitech-hidpp/fu-logitech-hidpp-runtime.c index 64f447a84..27d422274 100644 --- a/plugins/logitech-hidpp/fu-logitech-hidpp-runtime.c +++ b/plugins/logitech-hidpp/fu-logitech-hidpp-runtime.c @@ -200,6 +200,7 @@ fu_logitech_hidpp_runtime_probe(FuDevice *device, GError **error) GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); guint16 release = 0xffff; g_autoptr(GUdevDevice) udev_parent = NULL; + g_autoptr(GUdevDevice) udev_parent_usb_interface = NULL; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_logitech_hidpp_runtime_parent_class)->probe(device, error)) @@ -219,6 +220,7 @@ fu_logitech_hidpp_runtime_probe(FuDevice *device, GError **error) } if (release != 0xffff) { g_autofree gchar *devid2 = NULL; + const gchar *interface_str; switch (release &= 0xff00) { case 0x1200: /* Nordic */ @@ -236,6 +238,34 @@ fu_logitech_hidpp_runtime_probe(FuDevice *device, GError **error) fu_device_add_counterpart_guid(device, devid2); priv->version_bl_major = 0x03; break; + case 0x0500: + /* Bolt */ + udev_parent_usb_interface = + g_udev_device_get_parent_with_subsystem(udev_device, + "usb", + "usb_interface"); + interface_str = + g_udev_device_get_property(udev_parent_usb_interface, "INTERFACE"); + if (interface_str == NULL) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + "INTERFACE property not found in parent device"); + return FALSE; + } + if (g_strcmp0(interface_str, "3/0/0") != 0) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "skipping hidraw device"); + return FALSE; + } + devid2 = g_strdup_printf("USB\\VID_%04X&PID_%04X", + (guint)FU_UNIFYING_DEVICE_VID, + (guint)FU_UNIFYING_DEVICE_PID_BOOTLOADER_BOLT); + fu_device_add_counterpart_guid(device, devid2); + priv->version_bl_major = 0x03; + break; default: g_warning("bootloader release %04x invalid", release); break; diff --git a/plugins/logitech-hidpp/fu-plugin-logitech-hidpp.c b/plugins/logitech-hidpp/fu-plugin-logitech-hidpp.c index 02ba32b0b..b22cfcd51 100644 --- a/plugins/logitech-hidpp/fu-plugin-logitech-hidpp.c +++ b/plugins/logitech-hidpp/fu-plugin-logitech-hidpp.c @@ -12,6 +12,7 @@ #include "fu-logitech-hidpp-bootloader-texas.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-device.h" +#include "fu-logitech-hidpp-runtime-bolt.h" #include "fu-logitech-hidpp-runtime-unifying.h" gboolean @@ -38,4 +39,5 @@ fu_plugin_init(FuPlugin *plugin) fu_plugin_add_device_gtype(plugin, FU_TYPE_UNIFYING_BOOTLOADER_TEXAS); fu_plugin_add_device_gtype(plugin, FU_TYPE_HIDPP_RUNTIME_UNIFYING); fu_plugin_add_device_gtype(plugin, FU_TYPE_HIDPP_DEVICE); + fu_plugin_add_device_gtype(plugin, FU_TYPE_HIDPP_RUNTIME_BOLT); } diff --git a/plugins/logitech-hidpp/logitech-hidpp.quirk b/plugins/logitech-hidpp/logitech-hidpp.quirk index 476bc3ff4..370e06480 100644 --- a/plugins/logitech-hidpp/logitech-hidpp.quirk +++ b/plugins/logitech-hidpp/logitech-hidpp.quirk @@ -5,6 +5,22 @@ GType = FuLogitechHidPpRuntimeUnifying VendorId = USB:0x046D InstallDuration = 30 +# Bolt Receiver (runtime) +[HIDRAW\VEN_046D&DEV_C548] +Plugin = logitech_hidpp +GType = FuLogitechHidPpRuntimeBolt +VendorId = USB:0x046D +InstallDuration = 30 + +# Bolt Receiver (bootloader) +[HIDRAW\VEN_046D&DEV_AB07] +Plugin = logitech_hidpp +Name = Bolt Receiver +GType = FuLogitechHidPpDevice +CounterpartGuid = HIDRAW\VEN_046D&DEV_C548 +InstallDuration = 30 +Flags = rebind-attach,force-receiver-id,replug-match-guid + # Nordic [USB\VID_046D&PID_AAAA] Plugin = logitech_hidpp diff --git a/plugins/logitech-hidpp/meson.build b/plugins/logitech-hidpp/meson.build index 3c8f7b4a6..d45516e65 100644 --- a/plugins/logitech-hidpp/meson.build +++ b/plugins/logitech-hidpp/meson.build @@ -21,6 +21,7 @@ shared_module('fu_plugin_logitech_hidpp', 'fu-logitech-hidpp-hidpp-msg.c', 'fu-logitech-hidpp-runtime.c', 'fu-logitech-hidpp-runtime-unifying.c', + 'fu-logitech-hidpp-runtime-bolt.c', ], include_directories : [ root_incdir,