mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-03 13:41:12 +00:00

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.
226 lines
5.7 KiB
C
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;
|
|
}
|