mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-25 22:40:50 +00:00
236 lines
6.9 KiB
C
236 lines
6.9 KiB
C
/*
|
|
* Copyright (C) 2016 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include "fu-logitech-hidpp-common.h"
|
|
#include "fu-logitech-hidpp-hidpp.h"
|
|
|
|
static gchar *
|
|
fu_logitech_hidpp_msg_to_string(FuLogitechHidPpHidppMsg *msg)
|
|
{
|
|
GString *str = g_string_new(NULL);
|
|
const gchar *tmp;
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(GString) flags_str = g_string_new(NULL);
|
|
|
|
g_return_val_if_fail(msg != NULL, NULL);
|
|
|
|
if (msg->flags == FU_UNIFYING_HIDPP_MSG_FLAG_NONE) {
|
|
g_string_append(flags_str, "none");
|
|
} else {
|
|
if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT)
|
|
g_string_append(flags_str, "longer-timeout,");
|
|
if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID)
|
|
g_string_append(flags_str, "ignore-sub-id,");
|
|
if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_FNCT_ID)
|
|
g_string_append(flags_str, "ignore-fnct-id,");
|
|
if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SWID)
|
|
g_string_append(flags_str, "ignore-swid,");
|
|
if (str->len > 0)
|
|
g_string_truncate(str, str->len - 1);
|
|
}
|
|
g_string_append_printf(str, "flags: %02x [%s]\n", msg->flags, flags_str->str);
|
|
g_string_append_printf(str,
|
|
"report-id: %02x [%s]\n",
|
|
msg->report_id,
|
|
fu_logitech_hidpp_msg_rpt_id_to_string(msg));
|
|
tmp = fu_logitech_hidpp_msg_dev_id_to_string(msg);
|
|
g_string_append_printf(str, "device-id: %02x [%s]\n", msg->device_id, tmp);
|
|
g_string_append_printf(str,
|
|
"sub-id: %02x [%s]\n",
|
|
msg->sub_id,
|
|
fu_logitech_hidpp_msg_sub_id_to_string(msg));
|
|
g_string_append_printf(str,
|
|
"function-id: %02x [%s]\n",
|
|
msg->function_id,
|
|
fu_logitech_hidpp_msg_fcn_id_to_string(msg));
|
|
if (!fu_logitech_hidpp_msg_is_error(msg, &error)) {
|
|
g_string_append_printf(str, "error: %s\n", error->message);
|
|
}
|
|
return g_string_free(str, FALSE);
|
|
}
|
|
|
|
gboolean
|
|
fu_logitech_hidpp_send(FuIOChannel *io_channel,
|
|
FuLogitechHidPpHidppMsg *msg,
|
|
guint timeout,
|
|
GError **error)
|
|
{
|
|
gsize len = fu_logitech_hidpp_msg_get_payload_length(msg);
|
|
FuIOChannelFlags write_flags = FU_IO_CHANNEL_FLAG_FLUSH_INPUT;
|
|
|
|
/* only for HID++2.0 */
|
|
if (msg->hidpp_version >= 2.f)
|
|
msg->function_id |= FU_UNIFYING_HIDPP_MSG_SW_ID;
|
|
|
|
/* force long reports for BLE-direct devices */
|
|
if (msg->hidpp_version == FU_HIDPP_VERSION_BLE) {
|
|
msg->report_id = HIDPP_REPORT_ID_LONG;
|
|
len = 20;
|
|
}
|
|
|
|
/* detailed debugging */
|
|
if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL) {
|
|
g_autofree gchar *str = fu_logitech_hidpp_msg_to_string(msg);
|
|
fu_common_dump_raw(G_LOG_DOMAIN, "host->device", (guint8 *)msg, len);
|
|
g_print("%s", str);
|
|
}
|
|
|
|
/* only use blocking IO when it will be a short timeout for reboot */
|
|
if ((msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT) == 0)
|
|
write_flags |= FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO;
|
|
|
|
/* HID */
|
|
if (!fu_io_channel_write_raw(io_channel, (guint8 *)msg, len, timeout, write_flags, error)) {
|
|
g_prefix_error(error, "failed to send: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_logitech_hidpp_receive(FuIOChannel *io_channel,
|
|
FuLogitechHidPpHidppMsg *msg,
|
|
guint timeout,
|
|
GError **error)
|
|
{
|
|
gsize read_size = 0;
|
|
|
|
if (!fu_io_channel_read_raw(io_channel,
|
|
(guint8 *)msg,
|
|
sizeof(FuLogitechHidPpHidppMsg),
|
|
&read_size,
|
|
timeout,
|
|
FU_IO_CHANNEL_FLAG_SINGLE_SHOT,
|
|
error)) {
|
|
g_prefix_error(error, "failed to receive: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check long enough, but allow returning oversize packets */
|
|
if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL)
|
|
fu_common_dump_raw(G_LOG_DOMAIN, "device->host", (guint8 *)msg, read_size);
|
|
if (read_size < fu_logitech_hidpp_msg_get_payload_length(msg)) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"message length too small, "
|
|
"got %" G_GSIZE_FORMAT " expected %" G_GSIZE_FORMAT,
|
|
read_size,
|
|
fu_logitech_hidpp_msg_get_payload_length(msg));
|
|
return FALSE;
|
|
}
|
|
|
|
/* detailed debugging */
|
|
if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL) {
|
|
g_autofree gchar *str = fu_logitech_hidpp_msg_to_string(msg);
|
|
g_print("%s", str);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_logitech_hidpp_transfer(FuIOChannel *io_channel, FuLogitechHidPpHidppMsg *msg, GError **error)
|
|
{
|
|
guint timeout = FU_UNIFYING_DEVICE_TIMEOUT_MS;
|
|
guint ignore_cnt = 0;
|
|
g_autoptr(FuLogitechHidPpHidppMsg) msg_tmp = fu_logitech_hidpp_msg_new();
|
|
|
|
/* increase timeout for some operations */
|
|
if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT)
|
|
timeout *= 10;
|
|
|
|
/* send request */
|
|
if (!fu_logitech_hidpp_send(io_channel, msg, timeout, error))
|
|
return FALSE;
|
|
|
|
/* keep trying to receive until we get a valid reply */
|
|
while (1) {
|
|
msg_tmp->hidpp_version = msg->hidpp_version;
|
|
|
|
if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_RETRY_STUCK) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
/* retry the send once case the device is "stuck" */
|
|
if (!fu_logitech_hidpp_receive(io_channel, msg_tmp, 1000, &error_local)) {
|
|
if (!fu_logitech_hidpp_send(io_channel, msg, timeout, error)) {
|
|
return FALSE;
|
|
}
|
|
if (!fu_logitech_hidpp_receive(io_channel,
|
|
msg_tmp,
|
|
timeout,
|
|
error)) {
|
|
g_prefix_error(error, "failed to receive: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
if (!fu_logitech_hidpp_receive(io_channel, msg_tmp, timeout, error)) {
|
|
g_prefix_error(error, "failed to receive: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* we don't know how to handle this report packet */
|
|
if (fu_logitech_hidpp_msg_get_payload_length(msg_tmp) == 0x0) {
|
|
g_debug("HID++1.0 report 0x%02x has unknown length, ignoring",
|
|
msg_tmp->report_id);
|
|
continue;
|
|
}
|
|
|
|
/* maybe something is also writing to the device? --
|
|
* we can't use the SwID as this is a HID++2.0 feature */
|
|
if (!fu_logitech_hidpp_msg_is_error(msg_tmp, error))
|
|
return FALSE;
|
|
|
|
/* is valid reply */
|
|
if (fu_logitech_hidpp_msg_is_reply(msg, msg_tmp))
|
|
break;
|
|
|
|
/* to ensure compatibility when an HID++ 2.0 device is
|
|
* connected to an HID++ 1.0 receiver, any feature index
|
|
* corresponding to an HID++ 1.0 sub-identifier which could be
|
|
* sent by the receiver, must be assigned to a dummy feature */
|
|
if (msg->hidpp_version >= 2.f) {
|
|
if (fu_logitech_hidpp_msg_is_hidpp10_compat(msg_tmp)) {
|
|
g_debug("ignoring HID++1.0 reply");
|
|
continue;
|
|
}
|
|
|
|
/* not us */
|
|
if ((msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SWID) == 0) {
|
|
if (!fu_logitech_hidpp_msg_verify_swid(msg_tmp)) {
|
|
g_debug("ignoring reply with SwId 0x%02i, expected 0x%02i",
|
|
msg_tmp->function_id & 0x0f,
|
|
FU_UNIFYING_HIDPP_MSG_SW_ID);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* hardware not responding */
|
|
if (ignore_cnt++ > 10) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"too many messages to ignore");
|
|
return FALSE;
|
|
}
|
|
|
|
g_debug("ignoring message %u", ignore_cnt);
|
|
};
|
|
|
|
/* copy over data */
|
|
fu_logitech_hidpp_msg_copy(msg, msg_tmp);
|
|
return TRUE;
|
|
}
|