fwupd/plugins/unifying/fu-unifying-hidpp.c
Richard Hughes 9d3f791727 unifying: Use the new daemon-provided functionality
Until now, the unifying plugin was a 'special snowflake' and did things in very
different ways to the other plugins. Refactor each object to derive from either
FuUsbDevice or FuUdevDevice, and then remove LuDevice and the LuContext layers.
2018-09-07 16:22:38 +01:00

210 lines
5.8 KiB
C

/*
* Copyright (C) 2016-2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-unifying-common.h"
#include "fu-unifying-hidpp.h"
static gchar *
fu_unifying_hidpp_msg_to_string (FuUnifyingHidppMsg *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_unifying_hidpp_msg_rpt_id_to_string (msg));
tmp = fu_unifying_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_unifying_hidpp_msg_sub_id_to_string (msg));
g_string_append_printf (str, "function-id: %02x [%s]\n",
msg->function_id,
fu_unifying_hidpp_msg_fcn_id_to_string (msg));
if (!fu_unifying_hidpp_msg_is_error (msg, &error)) {
g_string_append_printf (str, "error: %s\n",
error->message);
}
return g_string_free (str, FALSE);
}
gboolean
fu_unifying_hidpp_send (gint fd,
FuUnifyingHidppMsg *msg,
guint timeout,
GError **error)
{
gsize len = fu_unifying_hidpp_msg_get_payload_length (msg);
g_return_val_if_fail (fd > 0, FALSE);
/* only for HID++2.0 */
if (msg->hidpp_version >= 2.f)
msg->function_id |= FU_UNIFYING_HIDPP_MSG_SW_ID;
fu_unifying_dump_raw ("host->device", (guint8 *) msg, len);
/* detailed debugging */
if (g_getenv ("FWUPD_UNIFYING_VERBOSE") != NULL) {
g_autofree gchar *str = fu_unifying_hidpp_msg_to_string (msg);
g_print ("%s", str);
}
/* HID */
if (!fu_unifying_nonblock_write (fd, (guint8 *) msg, len, error)) {
g_prefix_error (error, "failed to send: ");
return FALSE;
}
/* success */
return TRUE;
}
gboolean
fu_unifying_hidpp_receive (gint fd,
FuUnifyingHidppMsg *msg,
guint timeout,
GError **error)
{
gsize read_size = 0;
g_return_val_if_fail (fd > 0, FALSE);
if (!fu_unifying_nonblock_read (fd,
(guint8 *) msg,
sizeof(FuUnifyingHidppMsg),
&read_size,
timeout,
error)) {
g_prefix_error (error, "failed to receive: ");
return FALSE;
}
/* check long enough, but allow returning oversize packets */
fu_unifying_dump_raw ("device->host", (guint8 *) msg, read_size);
if (read_size < fu_unifying_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_unifying_hidpp_msg_get_payload_length (msg));
return FALSE;
}
/* detailed debugging */
if (g_getenv ("FWUPD_UNIFYING_VERBOSE") != NULL) {
g_autofree gchar *str = fu_unifying_hidpp_msg_to_string (msg);
g_print ("%s", str);
}
/* success */
return TRUE;
}
gboolean
fu_unifying_hidpp_transfer (gint fd, FuUnifyingHidppMsg *msg, GError **error)
{
guint timeout = FU_UNIFYING_DEVICE_TIMEOUT_MS;
guint ignore_cnt = 0;
g_autoptr(FuUnifyingHidppMsg) msg_tmp = fu_unifying_hidpp_msg_new ();
g_return_val_if_fail (fd > 0, FALSE);
/* increase timeout for some operations */
if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT)
timeout *= 10;
/* send request */
if (!fu_unifying_hidpp_send (fd, 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 (!fu_unifying_hidpp_receive (fd, 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_unifying_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_unifying_hidpp_msg_is_error (msg_tmp, error))
return FALSE;
/* is valid reply */
if (fu_unifying_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_unifying_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_unifying_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_error ("ignoring message %u", ignore_cnt);
};
/* copy over data */
fu_unifying_hidpp_msg_copy (msg, msg_tmp);
return TRUE;
}