fwupd/libfwupdplugin/fu-hid-device.c
Richard Hughes 942802063e trivial: Remove the unused parent instance from the private struct
I assume at some point we forgot to remove it when converting an object
from FINAL to DERIVABLE and the anti-pattern just got copied around the
codebase...
2021-07-15 16:20:19 +01:00

492 lines
13 KiB
C

/*
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "FuHidDevice"
#include "config.h"
#include "fu-hid-device.h"
#define FU_HID_REPORT_GET 0x01
#define FU_HID_REPORT_SET 0x09
#define FU_HID_REPORT_TYPE_INPUT 0x01
#define FU_HID_REPORT_TYPE_OUTPUT 0x02
#define FU_HID_REPORT_TYPE_FEATURE 0x03
#define FU_HID_DEVICE_RETRIES 10
/**
* FuHidDevice:
*
* A Human Interface Device (HID) device.
*
* See also: [class@FuDevice], [class@FuUsbDevice]
*/
typedef struct
{
guint8 interface;
gboolean interface_autodetect;
FuHidDeviceFlags flags;
} FuHidDevicePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (FuHidDevice, fu_hid_device, FU_TYPE_USB_DEVICE)
enum {
PROP_0,
PROP_INTERFACE,
PROP_LAST
};
#define GET_PRIVATE(o) (fu_hid_device_get_instance_private (o))
static void
fu_hid_device_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
FuHidDevice *device = FU_HID_DEVICE (object);
FuHidDevicePrivate *priv = GET_PRIVATE (device);
switch (prop_id) {
case PROP_INTERFACE:
g_value_set_uint (value, priv->interface);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
fu_hid_device_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
FuHidDevice *device = FU_HID_DEVICE (object);
switch (prop_id) {
case PROP_INTERFACE:
fu_hid_device_set_interface (device, g_value_get_uint (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
fu_hid_device_open (FuDevice *device, GError **error)
{
#ifdef HAVE_GUSB
FuHidDevice *self = FU_HID_DEVICE (device);
FuHidDevicePrivate *priv = GET_PRIVATE (self);
GUsbDeviceClaimInterfaceFlags flags = 0;
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
/* FuUsbDevice->open */
if (!FU_DEVICE_CLASS (fu_hid_device_parent_class)->open (device, error))
return FALSE;
/* auto-detect */
if (priv->interface_autodetect) {
g_autoptr(GPtrArray) ifaces = NULL;
ifaces = g_usb_device_get_interfaces (usb_device, error);
if (ifaces == NULL)
return FALSE;
for (guint i = 0; i < ifaces->len; i++) {
GUsbInterface *iface = g_ptr_array_index (ifaces, i);
if (g_usb_interface_get_class (iface) == G_USB_DEVICE_CLASS_HID) {
priv->interface = g_usb_interface_get_number (iface);
priv->interface_autodetect = FALSE;
break;
}
}
if (priv->interface_autodetect) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"could not autodetect HID interface");
return FALSE;
}
g_debug ("autodetected HID interface of 0x%02x", priv->interface);
}
/* claim */
if ((priv->flags & FU_HID_DEVICE_FLAG_NO_KERNEL_UNBIND) == 0)
flags |= G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER;
if (!g_usb_device_claim_interface (usb_device, priv->interface, flags, error)) {
g_prefix_error (error, "failed to claim HID interface: ");
return FALSE;
}
#endif
/* success */
return TRUE;
}
static gboolean
fu_hid_device_close (FuDevice *device, GError **error)
{
#ifdef HAVE_GUSB
FuHidDevice *self = FU_HID_DEVICE (device);
FuHidDevicePrivate *priv = GET_PRIVATE (self);
GUsbDeviceClaimInterfaceFlags flags = 0;
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
g_autoptr(GError) error_local = NULL;
#endif
#ifdef HAVE_GUSB
/* release */
if ((priv->flags & FU_HID_DEVICE_FLAG_NO_KERNEL_REBIND) == 0)
flags |= G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER;
if (!g_usb_device_release_interface (usb_device, priv->interface, flags, &error_local)) {
if (g_error_matches (error_local,
G_USB_DEVICE_ERROR,
G_USB_DEVICE_ERROR_NO_DEVICE) ||
g_error_matches (error_local,
G_USB_DEVICE_ERROR,
G_USB_DEVICE_ERROR_INTERNAL)) {
g_debug ("ignoring: %s", error_local->message);
return TRUE;
}
g_propagate_prefixed_error (error,
g_steal_pointer (&error_local),
"failed to release HID interface: ");
return FALSE;
}
#endif
/* FuUsbDevice->close */
return FU_DEVICE_CLASS (fu_hid_device_parent_class)->close (device, error);
}
/**
* fu_hid_device_set_interface:
* @self: a #FuHidDevice
* @interface: an interface number, e.g. `0x03`
*
* Sets the HID USB interface number.
*
* In most cases the HID interface is auto-detected, but this function can be
* used where there are multiple HID interfaces or where the device USB
* interface descriptor is invalid.
*
* Since: 1.4.0
**/
void
fu_hid_device_set_interface (FuHidDevice *self, guint8 interface)
{
FuHidDevicePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_HID_DEVICE (self));
priv->interface = interface;
priv->interface_autodetect = FALSE;
}
/**
* fu_hid_device_get_interface:
* @self: a #FuHidDevice
*
* Gets the HID USB interface number.
*
* Returns: integer
*
* Since: 1.4.0
**/
guint8
fu_hid_device_get_interface (FuHidDevice *self)
{
FuHidDevicePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_HID_DEVICE (self), 0xff);
return priv->interface;
}
/**
* fu_hid_device_add_flag:
* @self: a #FuHidDevice
* @flag: HID device flags, e.g. %FU_HID_DEVICE_FLAG_RETRY_FAILURE
*
* Adds a flag to be used for all set and get report messages.
*
* Since: 1.5.2
**/
void
fu_hid_device_add_flag (FuHidDevice *self, FuHidDeviceFlags flag)
{
FuHidDevicePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_HID_DEVICE (self));
priv->flags |= flag;
}
typedef struct {
guint8 value;
guint8 *buf;
gsize bufsz;
guint timeout;
FuHidDeviceFlags flags;
} FuHidDeviceRetryHelper;
static gboolean
fu_hid_device_set_report_internal (FuHidDevice *self,
FuHidDeviceRetryHelper *helper,
GError **error)
{
#ifdef HAVE_GUSB
FuHidDevicePrivate *priv = GET_PRIVATE (self);
GUsbDevice *usb_device;
gsize actual_len = 0;
guint16 wvalue = (FU_HID_REPORT_TYPE_OUTPUT << 8) | helper->value;
/* special case */
if (helper->flags & FU_HID_DEVICE_FLAG_IS_FEATURE)
wvalue = (FU_HID_REPORT_TYPE_FEATURE << 8) | helper->value;
if (g_getenv ("FU_HID_DEVICE_VERBOSE") != NULL) {
g_autofree gchar *title = NULL;
title = g_strdup_printf ("HID::SetReport [wValue=0x%04x ,wIndex=%u]",
wvalue, priv->interface);
fu_common_dump_raw (G_LOG_DOMAIN, title,
helper->buf, helper->bufsz);
}
usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
if (!g_usb_device_control_transfer (usb_device,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_CLASS,
G_USB_DEVICE_RECIPIENT_INTERFACE,
FU_HID_REPORT_SET,
wvalue, priv->interface,
helper->buf, helper->bufsz,
&actual_len,
helper->timeout,
NULL, error)) {
g_prefix_error (error, "failed to SetReport: ");
return FALSE;
}
if ((helper->flags & FU_HID_DEVICE_FLAG_ALLOW_TRUNC) == 0 && actual_len != helper->bufsz) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
"wrote %" G_GSIZE_FORMAT ", requested %" G_GSIZE_FORMAT " bytes",
actual_len, helper->bufsz);
return FALSE;
}
#endif
return TRUE;
}
static gboolean
fu_hid_device_set_report_internal_cb (FuDevice *device, gpointer user_data, GError **error)
{
FuHidDevice *self = FU_HID_DEVICE (device);
FuHidDeviceRetryHelper *helper = (FuHidDeviceRetryHelper *) user_data;
return fu_hid_device_set_report_internal (self, helper, error);
}
/**
* fu_hid_device_set_report:
* @self: a #FuHidDevice
* @value: low byte of wValue
* @buf: (nullable): a mutable buffer of data to send
* @bufsz: size of @buf
* @timeout: timeout in ms
* @flags: HID device flags e.g. %FU_HID_DEVICE_FLAG_ALLOW_TRUNC
* @error: (nullable): optional return location for an error
*
* Calls SetReport on the hardware.
*
* Returns: %TRUE for success
*
* Since: 1.4.0
**/
gboolean
fu_hid_device_set_report (FuHidDevice *self,
guint8 value,
guint8 *buf,
gsize bufsz,
guint timeout,
FuHidDeviceFlags flags,
GError **error)
{
FuHidDeviceRetryHelper helper;
FuHidDevicePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_HID_DEVICE (self), FALSE);
g_return_val_if_fail (buf != NULL, FALSE);
g_return_val_if_fail (bufsz != 0, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* create helper */
helper.value = value;
helper.buf = buf;
helper.bufsz = bufsz;
helper.timeout = timeout;
helper.flags = priv->flags | flags;
/* special case */
if (flags & FU_HID_DEVICE_FLAG_RETRY_FAILURE) {
return fu_device_retry (FU_DEVICE (self),
fu_hid_device_set_report_internal_cb,
FU_HID_DEVICE_RETRIES,
&helper,
error);
}
/* just one */
return fu_hid_device_set_report_internal (self, &helper, error);
}
static gboolean
fu_hid_device_get_report_internal (FuHidDevice *self,
FuHidDeviceRetryHelper *helper,
GError **error)
{
#ifdef HAVE_GUSB
FuHidDevicePrivate *priv = GET_PRIVATE (self);
GUsbDevice *usb_device;
gsize actual_len = 0;
guint16 wvalue = (FU_HID_REPORT_TYPE_INPUT << 8) | helper->value;
/* special case */
if (helper->flags & FU_HID_DEVICE_FLAG_IS_FEATURE)
wvalue = (FU_HID_REPORT_TYPE_FEATURE << 8) | helper->value;
if (g_getenv ("FU_HID_DEVICE_VERBOSE") != NULL) {
g_autofree gchar *title = NULL;
title = g_strdup_printf ("HID::GetReport [wValue=0x%04x, wIndex=%u]",
wvalue, priv->interface);
fu_common_dump_raw (G_LOG_DOMAIN, title,
helper->buf, actual_len);
}
usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
if (!g_usb_device_control_transfer (usb_device,
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
G_USB_DEVICE_REQUEST_TYPE_CLASS,
G_USB_DEVICE_RECIPIENT_INTERFACE,
FU_HID_REPORT_GET,
wvalue, priv->interface,
helper->buf, helper->bufsz,
&actual_len, /* actual length */
helper->timeout,
NULL, error)) {
g_prefix_error (error, "failed to GetReport: ");
return FALSE;
}
if (g_getenv ("FU_HID_DEVICE_VERBOSE") != NULL) {
g_autofree gchar *title = NULL;
title = g_strdup_printf ("HID::GetReport [wValue=0x%04x, wIndex=%u]",
wvalue, priv->interface);
fu_common_dump_raw (G_LOG_DOMAIN, title, helper->buf, actual_len);
}
if ((helper->flags & FU_HID_DEVICE_FLAG_ALLOW_TRUNC) == 0 && actual_len != helper->bufsz) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
"read %" G_GSIZE_FORMAT ", requested %" G_GSIZE_FORMAT " bytes",
actual_len, helper->bufsz);
return FALSE;
}
#endif
return TRUE;
}
static gboolean
fu_hid_device_get_report_internal_cb (FuDevice *device, gpointer user_data, GError **error)
{
FuHidDevice *self = FU_HID_DEVICE (device);
FuHidDeviceRetryHelper *helper = (FuHidDeviceRetryHelper *) user_data;
return fu_hid_device_get_report_internal (self, helper, error);
}
/**
* fu_hid_device_get_report:
* @self: a #FuHidDevice
* @value: low byte of wValue
* @buf: (nullable): a mutable buffer of data to send
* @bufsz: size of @buf
* @timeout: timeout in ms
* @flags: HID device flags e.g. %FU_HID_DEVICE_FLAG_ALLOW_TRUNC
* @error: (nullable): optional return location for an error
*
* Calls GetReport on the hardware.
*
* Returns: %TRUE for success
*
* Since: 1.4.0
**/
gboolean
fu_hid_device_get_report (FuHidDevice *self,
guint8 value,
guint8 *buf,
gsize bufsz,
guint timeout,
FuHidDeviceFlags flags,
GError **error)
{
FuHidDeviceRetryHelper helper;
FuHidDevicePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_HID_DEVICE (self), FALSE);
g_return_val_if_fail (buf != NULL, FALSE);
g_return_val_if_fail (bufsz != 0, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* create helper */
helper.value = value;
helper.buf = buf;
helper.bufsz = bufsz;
helper.timeout = timeout;
helper.flags = priv->flags | flags;
/* special case */
if (flags & FU_HID_DEVICE_FLAG_RETRY_FAILURE) {
return fu_device_retry (FU_DEVICE (self),
fu_hid_device_get_report_internal_cb,
FU_HID_DEVICE_RETRIES,
&helper,
error);
}
/* just one */
return fu_hid_device_get_report_internal (self, &helper, error);
}
static void
fu_hid_device_init (FuHidDevice *self)
{
FuHidDevicePrivate *priv = GET_PRIVATE (self);
priv->interface_autodetect = TRUE;
}
/**
* fu_hid_device_new:
* @usb_device: a USB device
*
* Creates a new HID device.
*
* Returns: (transfer full): a #FuHidDevice
*
* Since: 1.4.0
**/
FuHidDevice *
fu_hid_device_new (GUsbDevice *usb_device)
{
FuHidDevice *device = g_object_new (FU_TYPE_HID_DEVICE, NULL);
fu_usb_device_set_dev (FU_USB_DEVICE (device), usb_device);
return FU_HID_DEVICE (device);
}
static void
fu_hid_device_class_init (FuHidDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
object_class->get_property = fu_hid_device_get_property;
object_class->set_property = fu_hid_device_set_property;
klass_device->open = fu_hid_device_open;
klass_device->close = fu_hid_device_close;
pspec = g_param_spec_uint ("interface", NULL, NULL,
0x00, 0xff, 0x00,
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_INTERFACE, pspec);
}