fwupd/plugins/logitech-hidpp/fu-logitech-hidpp-runtime.c
Ricardo Cañuelo 15a39b94ee logitech-hidpp: Add support for updating Bolt peripheral firmware
Also simplify the Instance IDs added by the Bolt hardware.
2021-09-13 11:28:55 +01:00

308 lines
9.3 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-hidpp.h"
#include "fu-logitech-hidpp-runtime.h"
typedef struct {
guint8 version_bl_major;
gboolean signed_firmware;
FuIOChannel *io_channel;
} FuLogitechHidPpRuntimePrivate;
G_DEFINE_TYPE_WITH_PRIVATE(FuLogitechHidPpRuntime, fu_logitech_hidpp_runtime, FU_TYPE_UDEV_DEVICE)
#define GET_PRIVATE(o) (fu_logitech_hidpp_runtime_get_instance_private(o))
gboolean
fu_logitech_hidpp_runtime_get_signed_firmware(FuLogitechHidPpRuntime *self)
{
FuLogitechHidPpRuntimePrivate *priv;
g_return_val_if_fail(FU_IS_HIDPP_RUNTIME(self), FALSE);
priv = GET_PRIVATE(self);
return priv->signed_firmware;
}
void
fu_logitech_hidpp_runtime_set_signed_firmware(FuLogitechHidPpRuntime *self,
gboolean signed_firmware)
{
FuLogitechHidPpRuntimePrivate *priv;
g_return_if_fail(FU_IS_HIDPP_RUNTIME(self));
priv = GET_PRIVATE(self);
priv->signed_firmware = signed_firmware;
}
FuIOChannel *
fu_logitech_hidpp_runtime_get_io_channel(FuLogitechHidPpRuntime *self)
{
FuLogitechHidPpRuntimePrivate *priv;
g_return_val_if_fail(FU_IS_HIDPP_RUNTIME(self), NULL);
priv = GET_PRIVATE(self);
return priv->io_channel;
}
void
fu_logitech_hidpp_runtime_set_io_channel(FuLogitechHidPpRuntime *self, FuIOChannel *io_channel)
{
FuLogitechHidPpRuntimePrivate *priv;
g_return_if_fail(FU_IS_HIDPP_RUNTIME(self));
priv = GET_PRIVATE(self);
priv->io_channel = io_channel;
}
guint8
fu_logitech_hidpp_runtime_get_version_bl_major(FuLogitechHidPpRuntime *self)
{
FuLogitechHidPpRuntimePrivate *priv;
g_return_val_if_fail(FU_IS_HIDPP_RUNTIME(self), 0);
priv = GET_PRIVATE(self);
return priv->version_bl_major;
}
void
fu_logitech_hidpp_runtime_set_version_bl_major(FuLogitechHidPpRuntime *self,
guint8 version_bl_major)
{
FuLogitechHidPpRuntimePrivate *priv;
g_return_if_fail(FU_IS_HIDPP_RUNTIME(self));
priv = GET_PRIVATE(self);
priv->version_bl_major = version_bl_major;
}
static void
fu_logitech_hidpp_runtime_to_string(FuDevice *device, guint idt, GString *str)
{
FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device);
FuLogitechHidPpRuntimePrivate *priv = GET_PRIVATE(self);
FU_DEVICE_CLASS(fu_logitech_hidpp_runtime_parent_class)->to_string(device, idt, str);
fu_common_string_append_kb(str, idt, "SignedFirmware", priv->signed_firmware);
}
gboolean
fu_logitech_hidpp_runtime_enable_notifications(FuLogitechHidPpRuntime *self, GError **error)
{
g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new();
FuLogitechHidPpRuntimePrivate *priv = GET_PRIVATE(self);
msg->report_id = HIDPP_REPORT_ID_SHORT;
msg->device_id = HIDPP_DEVICE_IDX_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(priv->io_channel, msg, error);
}
static gboolean
fu_logitech_hidpp_runtime_close(FuDevice *device, GError **error)
{
FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device);
FuLogitechHidPpRuntimePrivate *priv = GET_PRIVATE(self);
if (priv->io_channel != NULL) {
if (!fu_io_channel_shutdown(priv->io_channel, error))
return FALSE;
g_clear_object(&priv->io_channel);
}
return TRUE;
}
static gboolean
fu_logitech_hidpp_runtime_poll(FuDevice *device, GError **error)
{
FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device);
FuLogitechHidPpRuntimePrivate *priv = GET_PRIVATE(self);
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(priv->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_HIDPP_RUNTIME(device);
FuLogitechHidPpRuntimePrivate *priv = GET_PRIVATE(self);
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 */
priv->io_channel = fu_io_channel_new_file(devpath, error);
if (priv->io_channel == NULL)
return FALSE;
/* poll for notifications */
fu_device_set_poll_interval(device, FU_HIDPP_RECEIVER_RUNTIME_POLLING_INTERVAL);
/* success */
return TRUE;
}
static gboolean
fu_logitech_hidpp_runtime_probe(FuDevice *device, GError **error)
{
FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device);
FuLogitechHidPpRuntimePrivate *priv = GET_PRIVATE(self);
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))
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;
const gchar *interface_str;
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);
priv->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);
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;
}
}
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->close = fu_logitech_hidpp_runtime_close;
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), "Miniaturised USB wireless receiver");
fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
}