logitech-hidpp: Add support for updating Bolt receivers

This commit is contained in:
Ricardo Cañuelo 2021-08-31 17:44:24 +01:00 committed by Richard Hughes
parent 9d2168e985
commit 35af30321a
12 changed files with 482 additions and 15 deletions

View File

@ -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"
}
]
}

View File

@ -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`.

View File

@ -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

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -0,0 +1,256 @@
/*
* Copyright (C) 2021 Ricardo Cañuelo <ricardo.canuelo@collabora.com>
*
* 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");
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2021 Ricardo Cañuelo <ricardo.canuelo@collabora.com>
*
* 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);

View File

@ -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;

View File

@ -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);
}

View File

@ -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

View File

@ -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,