fwupd/plugins/gpio/fu-gpio-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

226 lines
5.7 KiB
C

/*
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <glib/gstdio.h>
#include <linux/gpio.h>
#include "fu-gpio-device.h"
struct _FuGpioDevice {
FuUdevDevice parent_instance;
guint num_lines;
gint fd; /* valid when the GPIO bit is assigned */
};
G_DEFINE_TYPE(FuGpioDevice, fu_gpio_device, FU_TYPE_UDEV_DEVICE)
#define FU_GPIO_DEVICE_IOCTL_TIMEOUT 5000 /* ms */
static void
fu_gpio_device_to_string(FuDevice *device, guint idt, GString *str)
{
FuGpioDevice *self = FU_GPIO_DEVICE(device);
FU_DEVICE_CLASS(fu_gpio_device_parent_class)->to_string(device, idt, str);
fu_string_append_ku(str, idt, "NumLines", self->num_lines);
fu_string_append_kb(str, idt, "FdOpen", self->fd > 0);
}
static gboolean
fu_gpio_device_probe(FuDevice *device, GError **error)
{
/* no device file */
if (fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)) == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no device file");
return FALSE;
}
/* set the physical ID */
return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "gpio", error);
}
static gboolean
fu_gpio_device_setup(FuDevice *device, GError **error)
{
FuGpioDevice *self = FU_GPIO_DEVICE(device);
struct gpiochip_info info = {0x0};
/* get info */
if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self),
GPIO_GET_CHIPINFO_IOCTL,
(guint8 *)&info,
NULL,
FU_GPIO_DEVICE_IOCTL_TIMEOUT,
error)) {
g_prefix_error(error, "failed to get chipinfo: ");
return FALSE;
}
/* sanity check */
self->num_lines = info.lines;
if (self->num_lines == 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"0 lines is not supported");
return FALSE;
}
/* label is optional, but name is always set */
if (info.label[0] != '\0') {
g_autofree gchar *logical_id = fu_strsafe(info.label, sizeof(info.label));
fu_device_set_logical_id(device, logical_id);
/* add instance ID */
fu_device_add_instance_strsafe(device, "ID", logical_id);
if (!fu_device_build_instance_id(device, error, "GPIO", "ID", NULL))
return FALSE;
}
/* success */
return TRUE;
}
gboolean
fu_gpio_device_unassign(FuGpioDevice *self, GError **error)
{
if (self->fd < 0)
return TRUE;
g_debug("unsetting %s", fu_device_get_logical_id(FU_DEVICE(self)));
if (!g_close(self->fd, error))
return FALSE;
self->fd = -1;
return TRUE;
}
static gboolean
fu_gpio_device_assign_full(FuGpioDevice *self, guint64 line, gboolean value, GError **error)
{
const gchar consumer[] = "fwupd";
struct gpio_v2_line_request req = {
.num_lines = 1,
req.offsets[0] = line,
.config.flags = GPIO_V2_LINE_FLAG_OUTPUT,
.config.num_attrs = 1,
.config.attrs[0].attr.values = value ? 0x1 : 0x0,
.config.attrs[0].mask = 0x1,
};
/* this is useful if we have contention with other tools */
if (!fu_memcpy_safe((guint8 *)req.consumer,
sizeof(req.consumer),
0x0, /* dst */
(const guint8 *)consumer,
sizeof(consumer),
0x0, /* src */
sizeof(consumer),
error))
return FALSE;
/* slightly weird API, but roll with it */
g_debug("setting %s:0x%02x → %i",
fu_device_get_logical_id(FU_DEVICE(self)),
(guint)line,
value);
if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self),
GPIO_V2_GET_LINE_IOCTL,
(guint8 *)&req,
NULL,
FU_GPIO_DEVICE_IOCTL_TIMEOUT,
error)) {
g_prefix_error(error, "failed to assign: ");
return FALSE;
}
/* success */
self->fd = req.fd;
return TRUE;
}
gboolean
fu_gpio_device_assign(FuGpioDevice *self, const gchar *id, gboolean value, GError **error)
{
guint64 line = G_MAXUINT64;
/* sanity check */
if (self->fd > 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"GPIO %s already in use",
id);
return FALSE;
}
/* specified as a number, or look for @id as named pin */
if (fu_strtoull(id, &line, 0, self->num_lines - 1, NULL)) {
struct gpio_v2_line_info info = {.offset = line};
if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self),
GPIO_V2_GET_LINEINFO_IOCTL,
(guint8 *)&info,
NULL,
FU_GPIO_DEVICE_IOCTL_TIMEOUT,
error)) {
g_prefix_error(error, "failed to get lineinfo: ");
return FALSE;
}
} else {
for (guint i = 0; i < self->num_lines; i++) {
struct gpio_v2_line_info info = {.offset = i};
g_autofree gchar *name = NULL;
if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self),
GPIO_V2_GET_LINEINFO_IOCTL,
(guint8 *)&info,
NULL,
FU_GPIO_DEVICE_IOCTL_TIMEOUT,
error)) {
g_prefix_error(error, "failed to get lineinfo: ");
return FALSE;
}
name = fu_strsafe(info.name, sizeof(info.name));
if (g_strcmp0(name, id) == 0) {
line = i;
break;
}
}
}
if (line == G_MAXUINT64) {
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to find %s", id);
return FALSE;
}
return fu_gpio_device_assign_full(self, line, value, error);
}
static void
fu_gpio_device_init(FuGpioDevice *self)
{
fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ);
}
static void
fu_gpio_device_finalize(GObject *object)
{
FuGpioDevice *self = FU_GPIO_DEVICE(object);
if (self->fd > 0)
g_close(self->fd, NULL);
G_OBJECT_CLASS(fu_gpio_device_parent_class)->finalize(object);
}
static void
fu_gpio_device_class_init(FuGpioDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
object_class->finalize = fu_gpio_device_finalize;
klass_device->to_string = fu_gpio_device_to_string;
klass_device->setup = fu_gpio_device_setup;
klass_device->probe = fu_gpio_device_probe;
}