Add a plugin to set GPIOs for the duration of an update

This commit is contained in:
Richard Hughes 2022-02-13 21:11:39 +00:00
parent 5014ed0da5
commit c71ca76235
16 changed files with 485 additions and 2 deletions

View File

@ -24,7 +24,7 @@ extraction:
- "export LD_LIBRARY_PATH=$LGTM_WORKSPACE/installdir/usr/lib:$LD_LIBRARY_PATH"
configure:
command:
- "meson setup _lgtm_build_dir -Defi_binary=false -Dplugin_uefi_capsule_splash=false -Ddocs=none"
- "meson setup _lgtm_build_dir -Defi_binary=false -Dplugin_uefi_capsule_splash=false -Dplugin_gpio=false -Ddocs=none"
index:
build_command:
- "ninja -C _lgtm_build_dir"

View File

@ -44,6 +44,7 @@ meson .. \
-Doffline=false \
-Dplugin_emmc=false \
-Dplugin_amt=false \
-Dplugin_gpio=false \
-Dplugin_mtd=false \
-Dintrospection=false \
-Dplugin_thunderbolt=false \

View File

@ -87,6 +87,7 @@ def build_install(revision):
"--prefix=/usr",
"--libdir=lib",
"-Db_coverage=false",
"-Dplugin_gpio=false",
"-Dgtkdoc=false",
"-Ddocs=none",
"-Dgusb:docs=false",

View File

@ -44,6 +44,7 @@ MESON_ARGS= -Dgudev=false \
-Dplugin_amt=false \
-Dplugin_dell=false \
-Dplugin_emmc=false \
-Dplugin_gpio=false \
-Dplugin_nvme=false \
-Dplugin_parade_lspcon=false \
-Dplugin_redfish=false \

View File

@ -434,6 +434,7 @@ done
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ep963x.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_fastboot.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_fresco_pd.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_gpio.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_hailuck.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_iommu.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_jabra.so

View File

@ -1114,7 +1114,7 @@ fu_udev_device_set_physical_id(FuUdevDevice *self, const gchar *subsystems, GErr
} else if (g_strcmp0(subsystem, "usb") == 0 || g_strcmp0(subsystem, "mmc") == 0 ||
g_strcmp0(subsystem, "i2c") == 0 || g_strcmp0(subsystem, "platform") == 0 ||
g_strcmp0(subsystem, "scsi") == 0 || g_strcmp0(subsystem, "mtd") == 0 ||
g_strcmp0(subsystem, "block") == 0) {
g_strcmp0(subsystem, "block") == 0 || g_strcmp0(subsystem, "gpio") == 0) {
tmp = g_udev_device_get_property(udev_device, "DEVPATH");
if (tmp == NULL) {
g_set_error_literal(error,

View File

@ -383,6 +383,12 @@ if cc.has_header('linux/ipmi.h')
else
have_linux_ipmi = false
endif
if cc.has_header_symbol('linux/gpio.h', 'GPIO_V2_LINE_FLAG_OUTPUT')
have_linux_gpio = true
conf.set('HAVE_LINUX_GPIO_H', '1')
else
have_linux_gpio = false
endif
if cc.has_header_symbol('linux/ipmi_msgdefs.h', 'IPMI_DEVICE_IN_FW_UPDATE_ERR')
conf.set('HAVE_IPMI_DEVICE_IN_FW_UPDATE_ERR', '1')
endif

View File

@ -24,6 +24,7 @@ option('plugin_dummy', type : 'boolean', value : false, description : 'enable th
option('plugin_emmc', type : 'boolean', value : true, description : 'enable eMMC support')
option('plugin_ep963x', type : 'boolean', value : true, description : 'enable EP963x support')
option('plugin_fastboot', type : 'boolean', value : true, description : 'enable Fastboot support')
option('plugin_gpio', type : 'boolean', value : true, description : 'enable GPIO support')
option('plugin_logitech_bulkcontroller', type : 'boolean', value : true, description : 'enable Logitech bulk controller support')
option('plugin_parade_lspcon', type : 'boolean', value : true, description : 'enable Parade LSPCON support')
option('plugin_pixart_rf', type : 'boolean', value : true, description : 'enable PixartRF support')

34
plugins/gpio/README.md Normal file
View File

@ -0,0 +1,34 @@
# GPIO
## Introduction
This plugin sets GPIO outputs either high or low before and/or after an
update has been deployed.
## GUID Generation
These device use GPIO `gpiochip_info.label` values, e.g.
* `GPIO\ID_INT3450:00`
## Quirk Use
This plugin uses the following plugin-specific quirks:
### GpioForUpdate
The GPIO bit to set before the update is deployed e.g. `INT3450:00,SPI_MUX,high`.
After the update has finished, the bits are returned to the default state.
For example, to set GPIO pin 2 low for the duration of the ColorHug device update
this could be added to the quirk file:
[USB\VID_273F&PID_1001]
GpioForUpdate=fake-gpio-chip,2,low
Since: 1.7.6
## External Interface Access
This plugin requires ioctl `GPIO_GET_CHIPINFO_IOCTL` and `GPIO_V2_GET_LINE_IOCTL`
access on `/dev/gpiochip*` devices.

View File

@ -0,0 +1,211 @@
/*
* 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)
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_common_string_append_ku(str, idt, "NumLines", self->num_lines);
fu_common_string_append_kb(str, idt, "FdOpen", self->fd > 0);
}
static gboolean
fu_gpio_device_probe(FuDevice *device, GError **error)
{
/* FuUdevDevice->probe */
if (!FU_DEVICE_CLASS(fu_gpio_device_parent_class)->probe(device, error))
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,
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_common_strsafe(info.label, sizeof(info.label));
g_autofree gchar *instance_id = NULL;
fu_device_set_logical_id(device, logical_id);
instance_id = g_strdup_printf("GPIO\\ID_%s", logical_id);
fu_device_add_instance_id(device, instance_id);
}
/* 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,
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_common_strtoull_full(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,
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,
error)) {
g_prefix_error(error, "failed to get lineinfo: ");
return FALSE;
}
name = fu_common_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)
{
}
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;
}

View File

@ -0,0 +1,17 @@
/*
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#define FU_TYPE_GPIO_DEVICE (fu_gpio_device_get_type())
G_DECLARE_FINAL_TYPE(FuGpioDevice, fu_gpio_device, FU, GPIO_DEVICE, FuUdevDevice)
gboolean
fu_gpio_device_assign(FuGpioDevice *self, const gchar *id, gboolean value, GError **error);
gboolean
fu_gpio_device_unassign(FuGpioDevice *self, GError **error);

View File

@ -0,0 +1,164 @@
/*
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-gpio-device.h"
struct FuPluginData {
GPtrArray *current_logical_ids; /* element-type: utf-8 */
};
static void
fu_plugin_gpio_init(FuPlugin *plugin)
{
FuContext *ctx = fu_plugin_get_context(plugin);
FuPluginData *data = fu_plugin_alloc_data(plugin, sizeof(FuPluginData));
data->current_logical_ids = g_ptr_array_new_with_free_func(g_free);
fu_context_add_quirk_key(ctx, "GpioForUpdate");
fu_plugin_add_udev_subsystem(plugin, "gpio");
fu_plugin_add_device_gtype(plugin, FU_TYPE_GPIO_DEVICE);
}
static void
fu_plugin_gpio_destroy(FuPlugin *plugin)
{
FuPluginData *data = fu_plugin_get_data(plugin);
g_ptr_array_unref(data->current_logical_ids);
}
static gboolean
fu_plugin_gpio_parse_level(const gchar *str, gboolean *ret, GError **error)
{
if (g_strcmp0(str, "high") == 0) {
*ret = TRUE;
return TRUE;
}
if (g_strcmp0(str, "low") == 0) {
*ret = FALSE;
return TRUE;
}
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"cannot parse level, got %s and expected high|low",
str);
return FALSE;
}
static gboolean
fu_plugin_gpio_process_quirk(FuPlugin *self, const gchar *str, GError **error)
{
FuPluginData *data = fu_plugin_get_data(self);
gboolean value = FALSE;
FuDevice *device_tmp;
g_auto(GStrv) split = g_strsplit(str, ",", -1);
g_autoptr(FuDeviceLocker) locker = NULL;
/* sanity check */
if (g_strv_length(split) != 3) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid format, CHIP_NAME,PIN_NAME,LEVEL, got '%s'",
str);
return FALSE;
}
if (!fu_plugin_gpio_parse_level(split[2], &value, error))
return FALSE;
device_tmp = fu_plugin_cache_lookup(self, split[0]);
if (device_tmp == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"GPIO device %s not found",
split[0]);
return FALSE;
}
locker = fu_device_locker_new(device_tmp, error);
if (locker == NULL)
return FALSE;
if (!fu_gpio_device_assign(FU_GPIO_DEVICE(device_tmp), split[1], value, error)) {
g_prefix_error(error, "failed to assign %s: ", split[0]);
return FALSE;
}
/* success */
g_ptr_array_add(data->current_logical_ids, g_strdup(fu_device_get_logical_id(device_tmp)));
return TRUE;
}
static gboolean
fu_plugin_gpio_prepare(FuPlugin *self, FuDevice *device, FwupdInstallFlags flags, GError **error)
{
GPtrArray *guids = fu_device_get_guids(device);
for (guint i = 0; i < guids->len; i++) {
const gchar *guid = g_ptr_array_index(guids, i);
const gchar *str;
str = fu_context_lookup_quirk_by_id(fu_plugin_get_context(self),
guid,
"GpioForUpdate");
if (str == NULL)
continue;
if (!fu_plugin_gpio_process_quirk(self, str, error))
return FALSE;
}
return TRUE;
}
static gboolean
fu_plugin_gpio_cleanup(FuPlugin *self, FuDevice *device, FwupdInstallFlags flags, GError **error)
{
FuPluginData *data = fu_plugin_get_data(self);
g_autoptr(GPtrArray) current_logical_ids = NULL;
/* deep copy to local to clear transaction array */
current_logical_ids =
g_ptr_array_copy(data->current_logical_ids, (GCopyFunc)g_strdup, NULL);
g_ptr_array_set_size(data->current_logical_ids, 0);
/* close the fds we opened during ->prepare */
for (guint i = 0; i < current_logical_ids->len; i++) {
FuDevice *device_tmp;
const gchar *current_logical_id = g_ptr_array_index(current_logical_ids, i);
device_tmp = fu_plugin_cache_lookup(self, current_logical_id);
if (device_tmp == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"GPIO device %s no longer found",
current_logical_id);
return FALSE;
}
if (!fu_gpio_device_unassign(FU_GPIO_DEVICE(device_tmp), error)) {
g_prefix_error(error, "failed to unassign %s: ", current_logical_id);
return FALSE;
}
}
/* success */
return TRUE;
}
static void
fu_plugin_gpio_device_added(FuPlugin *self, FuDevice *device)
{
fu_plugin_cache_add(self, fu_device_get_logical_id(device), device);
}
void
fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs)
{
vfuncs->build_hash = FU_BUILD_HASH;
vfuncs->init = fu_plugin_gpio_init;
vfuncs->destroy = fu_plugin_gpio_destroy;
vfuncs->prepare = fu_plugin_gpio_prepare;
vfuncs->cleanup = fu_plugin_gpio_cleanup;
vfuncs->device_added = fu_plugin_gpio_device_added;
}

3
plugins/gpio/gpio.quirk Normal file
View File

@ -0,0 +1,3 @@
# match all devices with this udev subsystem
[GPIO]
Plugin = gpio

41
plugins/gpio/meson.build Normal file
View File

@ -0,0 +1,41 @@
if get_option('plugin_gpio')
if not get_option('gudev')
error('gudev is required for plugin_gpio')
endif
if not have_linux_gpio
error('linux/gpio.h (with API v2) is required for plugin_gpio')
endif
cargs = ['-DG_LOG_DOMAIN="FuPluginGpio"']
install_data([
'gpio.quirk',
],
install_dir: join_paths(datadir, 'fwupd', 'quirks.d')
)
shared_module('fu_plugin_gpio',
fu_hash,
sources : [
'fu-plugin-gpio.c',
'fu-gpio-device.c',
],
include_directories : [
root_incdir,
fwupd_incdir,
fwupdplugin_incdir,
],
install : true,
install_dir: plugin_dir,
c_args : [
cargs,
'-DLOCALSTATEDIR="' + localstatedir + '"',
],
link_with : [
fwupd,
fwupdplugin,
],
dependencies : [
plugin_deps,
],
)
endif

View File

@ -25,6 +25,7 @@ subdir('fastboot')
subdir('flashrom')
subdir('fresco-pd')
subdir('goodix-moc')
subdir('gpio')
subdir('hailuck')
subdir('intel-spi')
subdir('iommu')

View File

@ -191,6 +191,7 @@ parts:
-Dman=false,
-Dplugin_modem_manager=true,
-Dplugin_powerd=false,
-Dplugin_gpio=false,
-Dudevdir=$SNAPCRAFT_STAGE/lib/udev,
"-Dgusb:tests=false",
"-Dgusb:docs=false",