fwupd/plugins/steelseries/fu-steelseries-device.c
Richard Hughes faa7871b08 Remove duplicate ->probe() calls to speed up startup by 150ms
Now incorporate is fixed to copy across the properties we need in the
superclass, we don't need to do the subclass ->probe().

Note, we still need to do the subclassed ->probe() when using
FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT or when looking at properties
on the parent device.

This also removes the spurious 'already set GType to FuVliUsbhubDevice,
ignoring FuVliUsbhubDevice' messages when running the daemon.
2022-06-14 21:01:57 +01:00

192 lines
5.0 KiB
C

/*
* Copyright (C) 2016 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-steelseries-device.h"
typedef struct {
gint iface_idx_offset;
guint8 iface_idx;
guint8 ep;
gsize ep_in_size;
} FuSteelseriesDevicePrivate;
G_DEFINE_TYPE_WITH_PRIVATE(FuSteelseriesDevice, fu_steelseries_device, FU_TYPE_USB_DEVICE)
#define GET_PRIVATE(o) (fu_steelseries_device_get_instance_private(o))
/* @iface_idx_offset can be negative to specify from the end */
void
fu_steelseries_device_set_iface_idx_offset(FuSteelseriesDevice *self, gint iface_idx_offset)
{
FuSteelseriesDevicePrivate *priv = GET_PRIVATE(self);
priv->iface_idx_offset = iface_idx_offset;
}
gboolean
fu_steelseries_device_cmd(FuSteelseriesDevice *self,
guint8 *data,
gsize datasz,
gboolean answer,
GError **error)
{
FuSteelseriesDevicePrivate *priv = GET_PRIVATE(self);
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
gsize actual_len = 0;
gboolean ret;
ret = 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,
0x09,
0x0200,
priv->iface_idx,
data,
datasz,
&actual_len,
STEELSERIES_TRANSACTION_TIMEOUT,
NULL,
error);
if (!ret) {
g_prefix_error(error, "failed to do control transfer: ");
return FALSE;
}
if (actual_len != datasz) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"only wrote %" G_GSIZE_FORMAT "bytes",
actual_len);
return FALSE;
}
/* cleanup the buffer before receiving any data */
memset(data, 0x00, datasz);
/* do not expect the answer from device */
if (answer != TRUE)
return TRUE;
ret = g_usb_device_interrupt_transfer(usb_device,
priv->ep,
data,
priv->ep_in_size,
&actual_len,
STEELSERIES_TRANSACTION_TIMEOUT,
NULL,
error);
if (!ret) {
g_prefix_error(error, "failed to do EP transfer: ");
return FALSE;
}
if (actual_len != priv->ep_in_size) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"only read %" G_GSIZE_FORMAT "bytes",
actual_len);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_steelseries_device_probe(FuDevice *device, GError **error)
{
#if G_USB_CHECK_VERSION(0, 3, 3)
FuSteelseriesDevice *self = FU_STEELSERIES_DEVICE(device);
FuSteelseriesDevicePrivate *priv = GET_PRIVATE(self);
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
GUsbInterface *iface = NULL;
GUsbEndpoint *ep = NULL;
guint8 ep_id;
guint16 packet_size;
g_autoptr(GPtrArray) ifaces = NULL;
g_autoptr(GPtrArray) endpoints = NULL;
ifaces = g_usb_device_get_interfaces(usb_device, error);
if (ifaces == NULL)
return FALSE;
/* use the correct interface for interrupt transfer, either specifying an absolute offset,
* or a negative offset value for the "last" one */
if (priv->iface_idx_offset >= 0) {
if ((guint)priv->iface_idx_offset > ifaces->len) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"update interface 0x%x not found",
(guint)priv->iface_idx_offset);
return FALSE;
}
priv->iface_idx = priv->iface_idx_offset;
} else {
priv->iface_idx = ifaces->len - 1;
}
iface = g_ptr_array_index(ifaces, priv->iface_idx);
endpoints = g_usb_interface_get_endpoints(iface);
/* expecting to have only one endpoint for communication */
if (endpoints == NULL || endpoints->len != 1) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"endpoint not found");
return FALSE;
}
ep = g_ptr_array_index(endpoints, 0);
ep_id = g_usb_endpoint_get_address(ep);
packet_size = g_usb_endpoint_get_maximum_packet_size(ep);
priv->ep = ep_id;
priv->ep_in_size = packet_size;
fu_usb_device_add_interface(FU_USB_DEVICE(self), priv->iface_idx);
/* success */
return TRUE;
#else
/* failed */
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"this version of GUsb is not supported");
return FALSE;
#endif
}
static void
fu_steelseries_device_to_string(FuDevice *device, guint idt, GString *str)
{
FuSteelseriesDevice *self = FU_STEELSERIES_DEVICE(device);
FuSteelseriesDevicePrivate *priv = GET_PRIVATE(self);
FU_DEVICE_CLASS(fu_steelseries_device_parent_class)->to_string(device, idt, str);
fu_string_append_kx(str, idt, "Interface", priv->iface_idx);
fu_string_append_kx(str, idt, "Endpoint", priv->ep);
}
static void
fu_steelseries_device_init(FuSteelseriesDevice *self)
{
fu_device_register_private_flag(FU_DEVICE(self),
FU_STEELSERIES_DEVICE_FLAG_IS_RECEIVER,
"is-receiver");
}
static void
fu_steelseries_device_class_init(FuSteelseriesDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->to_string = fu_steelseries_device_to_string;
klass_device->probe = fu_steelseries_device_probe;
}