fwupd/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-unifying.c
Richard Hughes 99df74f0c2 Add API to wait for a device
This allows us to ignore all the delays when the device is emulated, with the
idea being to do dozens of device emulations in the CI tests.

Also, do not call fu_progress_sleep() when the device is emulated.
2023-02-01 09:42:08 +00:00

180 lines
5.8 KiB
C

/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-logitech-hidpp-common.h"
#include "fu-logitech-hidpp-hidpp.h"
#include "fu-logitech-hidpp-runtime-unifying.h"
struct _FuLogitechHidPpRuntimeUnifying {
FuLogitechHidPpRuntime parent_instance;
};
G_DEFINE_TYPE(FuLogitechHidPpRuntimeUnifying,
fu_logitech_hidpp_runtime_unifying,
FU_TYPE_HIDPP_RUNTIME)
#define GET_PRIVATE(o) (fu_logitech_hidpp_runtime_unifying_get_instance_private(o))
static gboolean
fu_logitech_hidpp_runtime_unifying_detach(FuDevice *device, FuProgress *progress, GError **error)
{
FuLogitechHidPpRuntime *self = FU_HIDPP_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_IDX_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(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);
return TRUE;
}
static gboolean
fu_logitech_hidpp_runtime_unifying_setup_internal(FuDevice *device, GError **error)
{
FuLogitechHidPpRuntime *self = FU_HIDPP_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_IDX_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(fu_logitech_hidpp_runtime_get_io_channel(self),
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 (fu_logitech_hidpp_runtime_get_version_bl_major(self) > 0) {
g_autofree gchar *version_bl = NULL;
version_bl = fu_logitech_hidpp_format_version(
"BOT",
fu_logitech_hidpp_runtime_get_version_bl_major(self),
config[8],
config[9]);
fu_device_set_version_bootloader(FU_DEVICE(device), version_bl);
/* is the USB receiver expecting signed firmware */
if ((fu_logitech_hidpp_runtime_get_version_bl_major(self) == 0x01 &&
config[8] >= 0x04) ||
(fu_logitech_hidpp_runtime_get_version_bl_major(self) == 0x03 &&
config[8] >= 0x02)) {
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD);
fu_device_add_protocol(device, "com.logitech.unifyingsigned");
}
}
if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD)) {
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
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_unifying_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 */
fu_device_sleep(device, 200); /* ms */
if (fu_logitech_hidpp_runtime_unifying_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_unifying_set_progress(FuDevice *self, FuProgress *progress)
{
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 70, "write");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 4, "attach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 27, "reload");
}
static void
fu_logitech_hidpp_runtime_unifying_class_init(FuLogitechHidPpRuntimeUnifyingClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->detach = fu_logitech_hidpp_runtime_unifying_detach;
klass_device->setup = fu_logitech_hidpp_runtime_unifying_setup;
klass_device->set_progress = fu_logitech_hidpp_runtime_unifying_set_progress;
}
static void
fu_logitech_hidpp_runtime_unifying_init(FuLogitechHidPpRuntimeUnifying *self)
{
}