mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-06 16:43:15 +00:00
logitech-hidpp: Add support for updating Bolt receivers
This commit is contained in:
parent
9d2168e985
commit
35af30321a
18
data/device-tests/devices/logitech-bolt-receiver.json
Normal file
18
data/device-tests/devices/logitech-bolt-receiver.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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`.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
256
plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.c
Normal file
256
plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.c
Normal 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");
|
||||
}
|
19
plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.h
Normal file
19
plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.h
Normal 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);
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user