mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-19 01:56:44 +00:00

Devices may want to support more than one protocol, and for some devices (e.g. Unifying peripherals stuck in bootloader mode) you might not even be able to query for the correct protocol anyway.
337 lines
11 KiB
C
337 lines
11 KiB
C
/*
|
|
* Copyright (C) 2016 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "fu-logitech-hidpp-common.h"
|
|
#include "fu-logitech-hidpp-runtime.h"
|
|
#include "fu-logitech-hidpp-hidpp.h"
|
|
|
|
struct _FuLogitechHidPpRuntime
|
|
{
|
|
FuUdevDevice parent_instance;
|
|
guint8 version_bl_major;
|
|
gboolean signed_firmware;
|
|
FuIOChannel *io_channel;
|
|
};
|
|
|
|
G_DEFINE_TYPE (FuLogitechHidPpRuntime, fu_logitech_hidpp_runtime, FU_TYPE_UDEV_DEVICE)
|
|
|
|
static void
|
|
fu_logitech_hidpp_runtime_to_string (FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuLogitechHidPpRuntime *self = FU_UNIFYING_RUNTIME (device);
|
|
FU_DEVICE_CLASS (fu_logitech_hidpp_runtime_parent_class)->to_string (device, idt, str);
|
|
fu_common_string_append_kb (str, idt, "SignedFirmware", self->signed_firmware);
|
|
}
|
|
|
|
static gboolean
|
|
fu_logitech_hidpp_runtime_enable_notifications (FuLogitechHidPpRuntime *self, GError **error)
|
|
{
|
|
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_SET_REGISTER;
|
|
msg->function_id = HIDPP_REGISTER_HIDPP_NOTIFICATIONS;
|
|
msg->data[0] = 0x00;
|
|
msg->data[1] = 0x05; /* Wireless + SoftwarePresent */
|
|
msg->data[2] = 0x00;
|
|
msg->hidpp_version = 1;
|
|
return fu_logitech_hidpp_transfer (self->io_channel, msg, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_logitech_hidpp_runtime_close (FuDevice *device, GError **error)
|
|
{
|
|
FuLogitechHidPpRuntime *self = FU_UNIFYING_RUNTIME (device);
|
|
if (!fu_io_channel_shutdown (self->io_channel, error))
|
|
return FALSE;
|
|
g_clear_object (&self->io_channel);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_logitech_hidpp_runtime_poll (FuDevice *device, GError **error)
|
|
{
|
|
FuLogitechHidPpRuntime *self = FU_UNIFYING_RUNTIME (device);
|
|
const guint timeout = 1; /* ms */
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new ();
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
|
|
/* open */
|
|
locker = fu_device_locker_new (self, error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
|
|
/* is there any pending data to read */
|
|
msg->hidpp_version = 1;
|
|
if (!fu_logitech_hidpp_receive (self->io_channel, msg, timeout, &error_local)) {
|
|
if (g_error_matches (error_local,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_TIMED_OUT)) {
|
|
return TRUE;
|
|
}
|
|
g_warning ("failed to get pending read: %s", error_local->message);
|
|
return TRUE;
|
|
}
|
|
|
|
/* 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 ("device connection event, do something");
|
|
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_open (FuDevice *device, GError **error)
|
|
{
|
|
FuLogitechHidPpRuntime *self = FU_UNIFYING_RUNTIME (device);
|
|
GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (device));
|
|
const gchar *devpath = g_udev_device_get_device_file (udev_device);
|
|
|
|
/* open, but don't block */
|
|
self->io_channel = fu_io_channel_new_file (devpath, error);
|
|
if (self->io_channel == NULL)
|
|
return FALSE;
|
|
|
|
/* poll for notifications */
|
|
fu_device_set_poll_interval (device, 5000);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_logitech_hidpp_runtime_probe (FuDevice *device, GError **error)
|
|
{
|
|
FuLogitechHidPpRuntime *self = FU_UNIFYING_RUNTIME (device);
|
|
GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (device));
|
|
guint16 release = 0xffff;
|
|
g_autoptr(GUdevDevice) udev_parent = NULL;
|
|
|
|
/* FuUdevDevice->probe */
|
|
if (!FU_DEVICE_CLASS (fu_logitech_hidpp_runtime_parent_class)->probe (device, error))
|
|
return FALSE;
|
|
|
|
/* set the physical ID */
|
|
if (!fu_udev_device_set_physical_id (FU_UDEV_DEVICE (device), "usb", error))
|
|
return FALSE;
|
|
|
|
/* generate bootloader-specific GUID */
|
|
udev_parent = g_udev_device_get_parent_with_subsystem (udev_device,
|
|
"usb", "usb_device");
|
|
if (udev_parent != NULL) {
|
|
const gchar *release_str;
|
|
release_str = g_udev_device_get_property (udev_parent, "ID_REVISION");
|
|
if (release_str != NULL)
|
|
release = g_ascii_strtoull (release_str, NULL, 16);
|
|
}
|
|
if (release != 0xffff) {
|
|
g_autofree gchar *devid2 = NULL;
|
|
switch (release &= 0xff00) {
|
|
case 0x1200:
|
|
/* Nordic */
|
|
devid2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
|
|
(guint) FU_UNIFYING_DEVICE_VID,
|
|
(guint) FU_UNIFYING_DEVICE_PID_BOOTLOADER_NORDIC);
|
|
fu_device_add_counterpart_guid (device, devid2);
|
|
self->version_bl_major = 0x01;
|
|
break;
|
|
case 0x2400:
|
|
/* Texas */
|
|
devid2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
|
|
(guint) FU_UNIFYING_DEVICE_VID,
|
|
(guint) FU_UNIFYING_DEVICE_PID_BOOTLOADER_TEXAS);
|
|
fu_device_add_counterpart_guid (device, devid2);
|
|
self->version_bl_major = 0x03;
|
|
break;
|
|
default:
|
|
g_warning ("bootloader release %04x invalid", release);
|
|
break;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_logitech_hidpp_runtime_setup_internal (FuDevice *device, GError **error)
|
|
{
|
|
FuLogitechHidPpRuntime *self = FU_UNIFYING_RUNTIME (device);
|
|
guint8 config[10];
|
|
g_autofree gchar *version_fw = NULL;
|
|
|
|
/* read all 10 bytes of the version register */
|
|
memset (config, 0x00, sizeof (config));
|
|
for (guint i = 0x01; i < 0x05; i++) {
|
|
g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new ();
|
|
|
|
/* workaround a bug in the 12.01 firmware, which fails with
|
|
* INVALID_VALUE when reading MCU1_HW_VERSION */
|
|
if (i == 0x03)
|
|
continue;
|
|
|
|
msg->report_id = HIDPP_REPORT_ID_SHORT;
|
|
msg->device_id = HIDPP_DEVICE_ID_RECEIVER;
|
|
msg->sub_id = HIDPP_SUBID_GET_REGISTER;
|
|
msg->function_id = HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION;
|
|
msg->data[0] = i;
|
|
msg->hidpp_version = 1;
|
|
if (!fu_logitech_hidpp_transfer (self->io_channel, msg, error)) {
|
|
g_prefix_error (error, "failed to read device config: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_memcpy_safe (config, sizeof(config), i * 2, /* dst */
|
|
msg->data, sizeof(msg->data), 0x1, /* src */
|
|
2, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* get firmware version */
|
|
version_fw = fu_logitech_hidpp_format_version ("RQR",
|
|
config[2],
|
|
config[3],
|
|
(guint16) config[4] << 8 |
|
|
config[5]);
|
|
fu_device_set_version (device, version_fw);
|
|
|
|
/* get bootloader version */
|
|
if (self->version_bl_major > 0) {
|
|
g_autofree gchar *version_bl = NULL;
|
|
version_bl = fu_logitech_hidpp_format_version ("BOT",
|
|
self->version_bl_major,
|
|
config[8],
|
|
config[9]);
|
|
fu_device_set_version_bootloader (FU_DEVICE (device), version_bl);
|
|
|
|
/* is the dongle expecting signed firmware */
|
|
if ((self->version_bl_major == 0x01 && config[8] >= 0x04) ||
|
|
(self->version_bl_major == 0x03 && config[8] >= 0x02)) {
|
|
self->signed_firmware = TRUE;
|
|
fu_device_add_protocol (device, "com.logitech.unifyingsigned");
|
|
}
|
|
}
|
|
if (!self->signed_firmware)
|
|
fu_device_add_protocol (device, "com.logitech.unifying");
|
|
|
|
/* 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_setup (FuDevice *device, GError **error)
|
|
{
|
|
g_autoptr(GError) error_local = NULL;
|
|
for (guint i = 0; i < 5; i++) {
|
|
/* 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_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_clear_error (&error_local);
|
|
}
|
|
g_propagate_error (error, g_steal_pointer (&error_local));
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_logitech_hidpp_runtime_detach (FuDevice *device, GError **error)
|
|
{
|
|
FuLogitechHidPpRuntime *self = FU_UNIFYING_RUNTIME (device);
|
|
g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new ();
|
|
g_autoptr(GError) error_local = NULL;
|
|
msg->report_id = HIDPP_REPORT_ID_SHORT;
|
|
msg->device_id = HIDPP_DEVICE_ID_RECEIVER;
|
|
msg->sub_id = HIDPP_SUBID_SET_REGISTER;
|
|
msg->function_id = HIDPP_REGISTER_DEVICE_FIRMWARE_UPDATE_MODE;
|
|
msg->data[0] = 'I';
|
|
msg->data[1] = 'C';
|
|
msg->data[2] = 'P';
|
|
msg->hidpp_version = 1;
|
|
msg->flags = FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT;
|
|
if (!fu_logitech_hidpp_send (self->io_channel, 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);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_logitech_hidpp_runtime_finalize (GObject *object)
|
|
{
|
|
G_OBJECT_CLASS (fu_logitech_hidpp_runtime_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
fu_logitech_hidpp_runtime_class_init (FuLogitechHidPpRuntimeClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = fu_logitech_hidpp_runtime_finalize;
|
|
klass_device->open = fu_logitech_hidpp_runtime_open;
|
|
klass_device->probe = fu_logitech_hidpp_runtime_probe;
|
|
klass_device->setup = fu_logitech_hidpp_runtime_setup;
|
|
klass_device->close = fu_logitech_hidpp_runtime_close;
|
|
klass_device->detach = fu_logitech_hidpp_runtime_detach;
|
|
klass_device->poll = fu_logitech_hidpp_runtime_poll;
|
|
klass_device->to_string = fu_logitech_hidpp_runtime_to_string;
|
|
}
|
|
|
|
static void
|
|
fu_logitech_hidpp_runtime_init (FuLogitechHidPpRuntime *self)
|
|
{
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID);
|
|
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_PLAIN);
|
|
fu_device_add_icon (FU_DEVICE (self), "preferences-desktop-keyboard");
|
|
fu_device_set_name (FU_DEVICE (self), "Unifying Receiver");
|
|
fu_device_set_summary (FU_DEVICE (self), "A miniaturised USB wireless receiver");
|
|
fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
|
|
}
|