fwupd/libfwupdplugin/fu-udev-device.c
Richard Hughes 2dea742004 Match the MEI UUID in quirk files
This allows us to be more specific when matching devices, and also means we get
more attributes 'for free' from the FuUdevDevice->probe().

This would allow us to have multiple device GTypes handling multiple MEI
interfaces in the same plugin., for instance, PTHI and MKHI.

The slight fly in the ointment is that the kernel does not set the 'dev' for
the mei_me devices, but it's always going to be just /dev/mei0, so hardcode it.
2022-10-12 13:17:42 +01:00

2287 lines
60 KiB
C

/*
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "FuUdevDevice"
#include "config.h"
#include <fcntl.h>
#include <string.h>
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_IOCTL_H
#include <sys/ioctl.h>
#endif
#include <glib/gstdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "fu-device-private.h"
#include "fu-i2c-device.h"
#include "fu-string.h"
#include "fu-udev-device-private.h"
/**
* FuUdevDevice:
*
* A UDev device, typically only available on Linux.
*
* See also: [class@FuDevice]
*/
typedef struct {
GUdevDevice *udev_device;
guint16 vendor;
guint16 model;
guint16 subsystem_vendor;
guint16 subsystem_model;
guint8 revision;
gchar *subsystem;
gchar *bind_id;
gchar *driver;
gchar *device_file;
gint fd;
FuUdevDeviceFlags flags;
} FuUdevDevicePrivate;
G_DEFINE_TYPE_WITH_PRIVATE(FuUdevDevice, fu_udev_device, FU_TYPE_DEVICE)
enum {
PROP_0,
PROP_UDEV_DEVICE,
PROP_SUBSYSTEM,
PROP_DRIVER,
PROP_DEVICE_FILE,
PROP_BIND_ID,
PROP_LAST
};
enum { SIGNAL_CHANGED, SIGNAL_LAST };
static guint signals[SIGNAL_LAST] = {0};
#define GET_PRIVATE(o) (fu_udev_device_get_instance_private(o))
/**
* fu_udev_device_emit_changed:
* @self: a #FuUdevDevice
*
* Emits the ::changed signal for the object.
*
* Since: 1.1.2
**/
void
fu_udev_device_emit_changed(FuUdevDevice *self)
{
g_autoptr(GError) error = NULL;
g_return_if_fail(FU_IS_UDEV_DEVICE(self));
g_debug("FuUdevDevice emit changed");
if (!fu_device_rescan(FU_DEVICE(self), &error))
g_debug("%s", error->message);
g_signal_emit(self, signals[SIGNAL_CHANGED], 0);
}
#ifdef HAVE_GUDEV
static guint16
fu_udev_device_get_sysfs_attr_as_uint16(GUdevDevice *udev_device, const gchar *name)
{
const gchar *tmp;
guint64 tmp64 = 0;
g_autoptr(GError) error_local = NULL;
tmp = g_udev_device_get_sysfs_attr(udev_device, name);
if (tmp == NULL)
return 0x0;
if (!fu_strtoull(tmp, &tmp64, 0, G_MAXUINT16 - 1, &error_local)) {
g_warning("reading %s for %s was invalid: %s", name, tmp, error_local->message);
return 0x0;
}
return tmp64;
}
static guint8
fu_udev_device_get_sysfs_attr_as_uint8(GUdevDevice *udev_device, const gchar *name)
{
const gchar *tmp;
guint64 tmp64 = 0;
g_autoptr(GError) error_local = NULL;
tmp = g_udev_device_get_sysfs_attr(udev_device, name);
if (tmp == NULL)
return 0x0;
if (!fu_strtoull(tmp, &tmp64, 0, G_MAXUINT8 - 1, &error_local)) {
g_warning("reading %s for %s was invalid: %s",
name,
g_udev_device_get_sysfs_path(udev_device),
error_local->message);
return 0x0;
}
return tmp64;
}
static void
fu_udev_device_to_string_raw(GUdevDevice *udev_device, guint idt, GString *str)
{
const gchar *const *keys;
if (udev_device == NULL)
return;
keys = g_udev_device_get_property_keys(udev_device);
for (guint i = 0; keys[i] != NULL; i++) {
fu_string_append(str,
idt,
keys[i],
g_udev_device_get_property(udev_device, keys[i]));
}
keys = g_udev_device_get_sysfs_attr_keys(udev_device);
for (guint i = 0; keys[i] != NULL; i++) {
fu_string_append(str,
idt,
keys[i],
g_udev_device_get_sysfs_attr(udev_device, keys[i]));
}
}
#endif
static void
fu_udev_device_to_string(FuDevice *device, guint idt, GString *str)
{
#ifdef HAVE_GUDEV
FuUdevDevice *self = FU_UDEV_DEVICE(device);
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
if (priv->vendor != 0x0)
fu_string_append_kx(str, idt, "Vendor", priv->vendor);
if (priv->model != 0x0)
fu_string_append_kx(str, idt, "Model", priv->model);
if (priv->subsystem_vendor != 0x0 || priv->subsystem_model != 0x0) {
fu_string_append_kx(str, idt, "SubsystemVendor", priv->subsystem_vendor);
fu_string_append_kx(str, idt, "SubsystemModel", priv->subsystem_model);
}
if (priv->revision != 0x0)
fu_string_append_kx(str, idt, "Revision", priv->revision);
if (priv->subsystem != NULL)
fu_string_append(str, idt, "Subsystem", priv->subsystem);
if (priv->driver != NULL)
fu_string_append(str, idt, "Driver", priv->driver);
if (priv->bind_id != NULL)
fu_string_append(str, idt, "BindId", priv->bind_id);
if (priv->device_file != NULL)
fu_string_append(str, idt, "DeviceFile", priv->device_file);
if (priv->udev_device != NULL) {
fu_string_append(str,
idt,
"SysfsPath",
g_udev_device_get_sysfs_path(priv->udev_device));
}
if (g_getenv("FU_UDEV_DEVICE_DEBUG") != NULL) {
g_autoptr(GUdevDevice) udev_parent = NULL;
fu_udev_device_to_string_raw(priv->udev_device, idt, str);
udev_parent = g_udev_device_get_parent(priv->udev_device);
if (udev_parent != NULL) {
fu_string_append(str, idt, "Parent", NULL);
fu_udev_device_to_string_raw(udev_parent, idt + 1, str);
}
}
#endif
}
static gboolean
fu_udev_device_ensure_bind_id(FuUdevDevice *self, GError **error)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
/* sanity check */
if (priv->bind_id != NULL)
return TRUE;
#ifdef HAVE_GUDEV
/* automatically set the bind ID from the subsystem */
if (g_strcmp0(priv->subsystem, "pci") == 0) {
priv->bind_id =
g_strdup(g_udev_device_get_property(priv->udev_device, "PCI_SLOT_NAME"));
return TRUE;
}
if (g_strcmp0(priv->subsystem, "hid") == 0) {
priv->bind_id = g_strdup(g_udev_device_get_property(priv->udev_device, "HID_PHYS"));
return TRUE;
}
if (g_strcmp0(priv->subsystem, "usb") == 0) {
priv->bind_id =
g_path_get_basename(g_udev_device_get_sysfs_path(priv->udev_device));
return TRUE;
}
#endif
/* nothing found automatically */
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"cannot derive bind-id from subsystem %s",
priv->subsystem);
return FALSE;
}
static void
fu_udev_device_set_subsystem(FuUdevDevice *self, const gchar *subsystem)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
/* not changed */
if (g_strcmp0(priv->subsystem, subsystem) == 0)
return;
g_free(priv->subsystem);
priv->subsystem = g_strdup(subsystem);
g_object_notify(G_OBJECT(self), "subsystem");
}
/**
* fu_udev_device_set_bind_id:
* @self: a #FuUdevDevice
* @bind_id: a bind-id string, e.g. `pci:0:0:1`
*
* Sets the device ID used for binding the device, e.g. `pci:1:2:3`
*
* Since: 1.7.2
**/
void
fu_udev_device_set_bind_id(FuUdevDevice *self, const gchar *bind_id)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
/* not changed */
if (g_strcmp0(priv->bind_id, bind_id) == 0)
return;
g_free(priv->bind_id);
priv->bind_id = g_strdup(bind_id);
g_object_notify(G_OBJECT(self), "bind-id");
}
static void
fu_udev_device_set_driver(FuUdevDevice *self, const gchar *driver)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
/* not changed */
if (g_strcmp0(priv->driver, driver) == 0)
return;
g_free(priv->driver);
priv->driver = g_strdup(driver);
g_object_notify(G_OBJECT(self), "driver");
}
/**
* fu_udev_device_set_device_file:
* @self: a #FuUdevDevice
* @device_file: (nullable): a device path
*
* Sets the device file to use for reading and writing.
*
* Since: 1.8.7
**/
void
fu_udev_device_set_device_file(FuUdevDevice *self, const gchar *device_file)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
/* not changed */
if (g_strcmp0(priv->device_file, device_file) == 0)
return;
g_free(priv->device_file);
priv->device_file = g_strdup(device_file);
g_object_notify(G_OBJECT(self), "device-file");
}
#ifdef HAVE_GUDEV
static const gchar *
fu_udev_device_get_vendor_fallback(GUdevDevice *udev_device)
{
const gchar *tmp;
tmp = g_udev_device_get_property(udev_device, "ID_VENDOR_FROM_DATABASE");
if (tmp != NULL)
return tmp;
tmp = g_udev_device_get_property(udev_device, "ID_VENDOR");
if (tmp != NULL)
return tmp;
return NULL;
}
#endif
#ifdef HAVE_GUDEV
static gboolean
fu_udev_device_probe_serio(FuUdevDevice *self, GError **error)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
const gchar *tmp;
/* firmware ID */
tmp = g_udev_device_get_property(priv->udev_device, "SERIO_FIRMWARE_ID");
if (tmp != NULL) {
/* this prefix is not useful */
if (g_str_has_prefix(tmp, "PNP: "))
tmp += 5;
fu_device_add_instance_strsafe(FU_DEVICE(self), "FWID", tmp);
if (!fu_device_build_instance_id(FU_DEVICE(self), error, "SERIO", "FWID", NULL))
return FALSE;
}
return TRUE;
}
static guint16
fu_udev_device_get_property_as_uint16(GUdevDevice *udev_device, const gchar *key)
{
const gchar *tmp = g_udev_device_get_property(udev_device, key);
guint64 value = 0;
g_autofree gchar *str = NULL;
if (tmp == NULL)
return 0x0;
str = g_strdup_printf("0x%s", tmp);
if (!fu_strtoull(str, &value, 0x0, G_MAXUINT16, NULL))
return 0x0;
return (guint16)value;
}
static void
fu_udev_device_set_vendor_from_udev_device(FuUdevDevice *self, GUdevDevice *udev_device)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
priv->vendor = fu_udev_device_get_sysfs_attr_as_uint16(udev_device, "vendor");
priv->model = fu_udev_device_get_sysfs_attr_as_uint16(udev_device, "device");
priv->revision = fu_udev_device_get_sysfs_attr_as_uint8(udev_device, "revision");
priv->subsystem_vendor =
fu_udev_device_get_sysfs_attr_as_uint16(udev_device, "subsystem_vendor");
priv->subsystem_model =
fu_udev_device_get_sysfs_attr_as_uint16(udev_device, "subsystem_device");
/* fallback to properties as udev might be using a subsystem-specific prober */
if (priv->vendor == 0x0)
priv->vendor = fu_udev_device_get_property_as_uint16(udev_device, "ID_VENDOR_ID");
if (priv->model == 0x0)
priv->model = fu_udev_device_get_property_as_uint16(udev_device, "ID_MODEL_ID");
if (priv->revision == 0x0)
priv->revision = fu_udev_device_get_property_as_uint16(udev_device, "ID_REVISION");
}
static void
fu_udev_device_set_vendor_from_parent(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GUdevDevice) udev_device = g_object_ref(priv->udev_device);
while (TRUE) {
g_autoptr(GUdevDevice) parent = g_udev_device_get_parent(udev_device);
if (parent == NULL)
break;
fu_udev_device_set_vendor_from_udev_device(self, parent);
if (priv->vendor != 0x0 || priv->model != 0x0 || priv->revision != 0x0)
break;
g_set_object(&udev_device, parent);
}
}
#endif
static gboolean
fu_udev_device_probe(FuDevice *device, GError **error)
{
FuUdevDevice *self = FU_UDEV_DEVICE(device);
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
#ifdef HAVE_GUDEV
const gchar *tmp;
g_autofree gchar *subsystem = NULL;
g_autoptr(GUdevDevice) udev_parent = NULL;
g_autoptr(GUdevDevice) parent_i2c = NULL;
#endif
/* nothing to do */
if (priv->udev_device == NULL)
return TRUE;
#ifdef HAVE_GUDEV
/* get IDs, but fallback to the parent, grandparent, great-grandparent, etc */
fu_udev_device_set_vendor_from_udev_device(self, priv->udev_device);
udev_parent = g_udev_device_get_parent(priv->udev_device);
if (udev_parent != NULL && priv->flags & FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT)
fu_udev_device_set_vendor_from_parent(self);
/* hidraw helpfully encodes the information in a different place */
if (udev_parent != NULL && priv->vendor == 0x0 && priv->model == 0x0 &&
priv->revision == 0x0 && g_strcmp0(priv->subsystem, "hidraw") == 0) {
tmp = g_udev_device_get_property(udev_parent, "HID_ID");
if (tmp != NULL) {
g_auto(GStrv) split = g_strsplit(tmp, ":", -1);
if (g_strv_length(split) == 3) {
guint64 val = g_ascii_strtoull(split[1], NULL, 16);
if (val > G_MAXUINT16) {
g_warning("reading %s for %s overflowed",
split[1],
g_udev_device_get_sysfs_path(priv->udev_device));
} else {
priv->vendor = val;
}
val = g_ascii_strtoull(split[2], NULL, 16);
if (val > G_MAXUINT32) {
g_warning("reading %s for %s overflowed",
split[2],
g_udev_device_get_sysfs_path(priv->udev_device));
} else {
priv->model = val;
}
}
}
tmp = g_udev_device_get_property(udev_parent, "HID_NAME");
if (tmp != NULL) {
if (fu_device_get_name(device) == NULL)
fu_device_set_name(device, tmp);
}
}
/* set the version if the revision has been set */
if (fu_device_get_version(device) == NULL &&
fu_device_get_version_format(device) == FWUPD_VERSION_FORMAT_UNKNOWN) {
if (priv->revision != 0x00 && priv->revision != 0xFF) {
g_autofree gchar *version = g_strdup_printf("%02x", priv->revision);
fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN);
fu_device_set_version(device, version);
}
}
/* set model */
if (fu_device_get_name(device) == NULL) {
tmp = g_udev_device_get_property(priv->udev_device, "ID_MODEL_FROM_DATABASE");
if (tmp == NULL)
tmp = g_udev_device_get_property(priv->udev_device, "ID_MODEL");
if (tmp == NULL)
tmp = g_udev_device_get_property(priv->udev_device,
"ID_PCI_CLASS_FROM_DATABASE");
if (tmp != NULL)
fu_device_set_name(device, tmp);
}
/* set vendor */
if (fu_device_get_vendor(device) == NULL) {
tmp = fu_udev_device_get_vendor_fallback(priv->udev_device);
if (tmp != NULL)
fu_device_set_vendor(device, tmp);
}
/* try harder to find a vendor name the user will recognize */
if (priv->flags & FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT && udev_parent != NULL &&
fu_device_get_vendor(device) == NULL) {
g_autoptr(GUdevDevice) device_tmp = g_object_ref(udev_parent);
for (guint i = 0; i < 0xff; i++) {
g_autoptr(GUdevDevice) parent = NULL;
tmp = fu_udev_device_get_vendor_fallback(device_tmp);
if (tmp != NULL) {
fu_device_set_vendor(device, tmp);
break;
}
parent = g_udev_device_get_parent(device_tmp);
if (parent == NULL)
break;
g_set_object(&device_tmp, parent);
}
}
/* set serial */
if (!fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER) &&
fu_device_get_serial(device) == NULL) {
tmp = g_udev_device_get_property(priv->udev_device, "ID_SERIAL_SHORT");
if (tmp == NULL)
tmp = g_udev_device_get_property(priv->udev_device, "ID_SERIAL");
if (tmp != NULL)
fu_device_set_serial(device, tmp);
}
/* set revision */
if (fu_device_get_version(device) == NULL &&
fu_device_get_version_format(device) == FWUPD_VERSION_FORMAT_UNKNOWN) {
tmp = g_udev_device_get_property(priv->udev_device, "ID_REVISION");
if (tmp != NULL)
fu_device_set_version(device, tmp);
}
/* set vendor ID */
subsystem = g_ascii_strup(g_udev_device_get_subsystem(priv->udev_device), -1);
if (subsystem != NULL && priv->vendor != 0x0000) {
g_autofree gchar *vendor_id = NULL;
vendor_id = g_strdup_printf("%s:0x%04X", subsystem, (guint)priv->vendor);
fu_device_add_vendor_id(device, vendor_id);
}
/* add GUIDs in order of priority */
if (priv->vendor != 0x0000)
fu_device_add_instance_u16(device, "VEN", priv->vendor);
if (priv->model != 0x0000)
fu_device_add_instance_u16(device, "DEV", priv->model);
if (priv->subsystem_vendor != 0x0000 || priv->subsystem_model != 0x0000) {
g_autofree gchar *subsys =
g_strdup_printf("%04X%04X", priv->subsystem_vendor, priv->subsystem_model);
fu_device_add_instance_str(device, "SUBSYS", subsys);
}
if (priv->revision != 0xFF)
fu_device_add_instance_u8(device, "REV", priv->revision);
fu_device_build_instance_id_quirk(device, NULL, subsystem, "VEN", NULL);
fu_device_build_instance_id(device, NULL, subsystem, "VEN", "DEV", NULL);
fu_device_build_instance_id(device, NULL, subsystem, "VEN", "DEV", "REV", NULL);
fu_device_build_instance_id(device, NULL, subsystem, "VEN", "DEV", "SUBSYS", NULL);
fu_device_build_instance_id(device, NULL, subsystem, "VEN", "DEV", "SUBSYS", "REV", NULL);
/* add device class */
tmp = g_udev_device_get_sysfs_attr(priv->udev_device, "class");
if (tmp != NULL && g_str_has_prefix(tmp, "0x"))
tmp += 2;
fu_device_add_instance_strup(device, "CLASS", tmp);
fu_device_build_instance_id_quirk(device, NULL, subsystem, "VEN", "CLASS", NULL);
/* add devtype */
fu_device_add_instance_strup(device, "TYPE", g_udev_device_get_devtype(priv->udev_device));
fu_device_build_instance_id_quirk(device, NULL, subsystem, "TYPE", NULL);
/* add the driver */
fu_device_add_instance_str(device, "DRIVER", priv->driver);
fu_device_build_instance_id_quirk(device, NULL, subsystem, "DRIVER", NULL);
/* add the modalias */
fu_device_add_instance_strsafe(device,
"MODALIAS",
g_udev_device_get_property(priv->udev_device, "MODALIAS"));
fu_device_build_instance_id_quirk(device, NULL, subsystem, "MODALIAS", NULL);
/* add subsystem to match in plugins */
if (subsystem != NULL) {
fu_device_add_instance_id_full(device,
subsystem,
FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS);
}
/* add firmware_id */
if (g_strcmp0(g_udev_device_get_subsystem(priv->udev_device), "serio") == 0) {
if (!fu_udev_device_probe_serio(self, error))
return FALSE;
}
/* determine if we're wired internally */
parent_i2c = g_udev_device_get_parent_with_subsystem(priv->udev_device, "i2c", NULL);
if (parent_i2c != NULL)
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL);
#endif
/* success */
return TRUE;
}
#ifdef HAVE_GUDEV
static gchar *
fu_udev_device_get_miscdev0(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
const gchar *fn;
g_autofree gchar *miscdir = NULL;
g_autoptr(GDir) dir = NULL;
miscdir = g_build_filename(g_udev_device_get_sysfs_path(priv->udev_device), "misc", NULL);
dir = g_dir_open(miscdir, 0, NULL);
if (dir == NULL)
return NULL;
fn = g_dir_read_name(dir);
if (fn == NULL)
return NULL;
return g_strdup_printf("/dev/%s", fn);
}
#endif
/**
* fu_udev_device_set_dev:
* @self: a #FuUdevDevice
* @udev_device: a #GUdevDevice
*
* Sets the #GUdevDevice. This may need to be used to replace the actual device
* used for reads and writes before the device is probed.
*
* Since: 1.6.2
**/
void
fu_udev_device_set_dev(FuUdevDevice *self, GUdevDevice *udev_device)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
#ifdef HAVE_GUDEV
const gchar *summary;
#endif
g_return_if_fail(FU_IS_UDEV_DEVICE(self));
#ifdef HAVE_GUDEV
/* the net subsystem is not a real hardware class */
if (udev_device != NULL &&
g_strcmp0(g_udev_device_get_subsystem(udev_device), "net") == 0) {
g_autoptr(GUdevDevice) udev_device_phys = NULL;
udev_device_phys = g_udev_device_get_parent(udev_device);
g_set_object(&priv->udev_device, udev_device_phys);
fu_device_set_metadata(FU_DEVICE(self),
"ParentSubsystem",
g_udev_device_get_subsystem(udev_device));
} else {
g_set_object(&priv->udev_device, udev_device);
}
#else
g_set_object(&priv->udev_device, udev_device);
#endif
/* set new device */
if (priv->udev_device == NULL)
return;
#ifdef HAVE_GUDEV
fu_udev_device_set_subsystem(self, g_udev_device_get_subsystem(priv->udev_device));
fu_udev_device_set_driver(self, g_udev_device_get_driver(priv->udev_device));
fu_udev_device_set_device_file(self, g_udev_device_get_device_file(priv->udev_device));
/* so we can display something sensible for unclaimed devices */
fu_device_set_backend_id(FU_DEVICE(self), g_udev_device_get_sysfs_path(priv->udev_device));
/* fall back to the first thing handled by misc drivers */
if (priv->device_file == NULL) {
/* perhaps we should unconditionally fall back? or perhaps
* require FU_UDEV_DEVICE_FLAG_FALLBACK_MISC... */
if (g_strcmp0(priv->subsystem, "serio") == 0)
priv->device_file = fu_udev_device_get_miscdev0(self);
if (priv->device_file != NULL)
g_debug("falling back to misc %s", priv->device_file);
}
/* try to get one line summary */
summary = g_udev_device_get_sysfs_attr(priv->udev_device, "description");
if (summary == NULL) {
g_autoptr(GUdevDevice) parent = NULL;
parent = g_udev_device_get_parent(priv->udev_device);
if (parent != NULL)
summary = g_udev_device_get_sysfs_attr(parent, "description");
}
if (summary != NULL)
fu_device_set_summary(FU_DEVICE(self), summary);
#endif
}
/**
* fu_udev_device_get_slot_depth:
* @self: a #FuUdevDevice
* @subsystem: a subsystem
*
* Determine how far up a chain a given device is
*
* Returns: unsigned integer
*
* Since: 1.2.4
**/
guint
fu_udev_device_get_slot_depth(FuUdevDevice *self, const gchar *subsystem)
{
#ifdef HAVE_GUDEV
GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(self));
g_autoptr(GUdevDevice) device_tmp = NULL;
device_tmp = g_udev_device_get_parent_with_subsystem(udev_device, subsystem, NULL);
if (device_tmp == NULL)
return 0;
for (guint i = 0; i < 0xff; i++) {
g_autoptr(GUdevDevice) parent = g_udev_device_get_parent(device_tmp);
if (parent == NULL)
return i;
g_set_object(&device_tmp, parent);
}
#endif
return 0;
}
static gboolean
fu_udev_device_unbind_driver(FuDevice *device, GError **error)
{
#ifdef HAVE_GUDEV
FuUdevDevice *self = FU_UDEV_DEVICE(device);
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_autofree gchar *fn = NULL;
g_autoptr(GFile) file = NULL;
g_autoptr(GOutputStream) stream = NULL;
/* is already unbound */
fn = g_build_filename(g_udev_device_get_sysfs_path(priv->udev_device),
"driver",
"unbind",
NULL);
if (!g_file_test(fn, G_FILE_TEST_EXISTS))
return TRUE;
/* write bus ID to file */
if (!fu_udev_device_ensure_bind_id(self, error))
return FALSE;
file = g_file_new_for_path(fn);
stream =
G_OUTPUT_STREAM(g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error));
if (stream == NULL)
return FALSE;
return g_output_stream_write_all(stream,
priv->bind_id,
strlen(priv->bind_id),
NULL,
NULL,
error);
#else
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"driver unbinding not supported");
return FALSE;
#endif
}
static gboolean
fu_udev_device_bind_driver(FuDevice *device,
const gchar *subsystem,
const gchar *driver,
GError **error)
{
#ifdef HAVE_GUDEV
FuUdevDevice *self = FU_UDEV_DEVICE(device);
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_autofree gchar *driver_safe = g_strdup(driver);
g_autofree gchar *fn = NULL;
g_autoptr(GFile) file = NULL;
g_autoptr(GOutputStream) stream = NULL;
/* copy the logic from modprobe */
g_strdelimit(driver_safe, "-", '_');
/* driver exists */
fn = g_strdup_printf("/sys/module/%s/drivers/%s:%s/bind",
driver_safe,
subsystem,
driver_safe);
if (!g_file_test(fn, G_FILE_TEST_EXISTS)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"cannot bind with %s:%s",
subsystem,
driver);
return FALSE;
}
/* write bus ID to file */
if (!fu_udev_device_ensure_bind_id(self, error))
return FALSE;
if (priv->bind_id == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"bind-id not set for subsystem %s",
priv->subsystem);
return FALSE;
}
file = g_file_new_for_path(fn);
stream =
G_OUTPUT_STREAM(g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error));
if (stream == NULL)
return FALSE;
return g_output_stream_write_all(stream,
priv->bind_id,
strlen(priv->bind_id),
NULL,
NULL,
error);
#else
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"driver binding not supported on Windows");
return FALSE;
#endif
}
static void
fu_udev_device_incorporate(FuDevice *self, FuDevice *donor)
{
FuUdevDevice *uself = FU_UDEV_DEVICE(self);
FuUdevDevice *udonor = FU_UDEV_DEVICE(donor);
FuUdevDevicePrivate *priv = GET_PRIVATE(uself);
FuUdevDevicePrivate *priv_donor = GET_PRIVATE(udonor);
g_return_if_fail(FU_IS_UDEV_DEVICE(self));
g_return_if_fail(FU_IS_UDEV_DEVICE(donor));
fu_udev_device_set_dev(uself, fu_udev_device_get_dev(udonor));
if (priv->device_file == NULL) {
fu_udev_device_set_subsystem(uself, fu_udev_device_get_subsystem(udonor));
fu_udev_device_set_bind_id(uself, fu_udev_device_get_bind_id(udonor));
fu_udev_device_set_device_file(uself, fu_udev_device_get_device_file(udonor));
fu_udev_device_set_driver(uself, fu_udev_device_get_driver(udonor));
}
if (priv->vendor == 0x0 && priv_donor->vendor != 0x0)
priv->vendor = priv_donor->vendor;
if (priv->model == 0x0 && priv_donor->model != 0x0)
priv->model = priv_donor->model;
if (priv->subsystem_vendor == 0x0 && priv_donor->subsystem_vendor != 0x0)
priv->subsystem_vendor = priv_donor->subsystem_vendor;
if (priv->subsystem_model == 0x0 && priv_donor->subsystem_model != 0x0)
priv->subsystem_model = priv_donor->subsystem_model;
if (priv->revision == 0x0 && priv_donor->revision != 0x0)
priv->revision = priv_donor->revision;
}
/**
* fu_udev_device_get_dev:
* @self: a #FuUdevDevice
*
* Gets the #GUdevDevice.
*
* Returns: (transfer none): a #GUdevDevice, or %NULL
*
* Since: 1.1.2
**/
GUdevDevice *
fu_udev_device_get_dev(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
return priv->udev_device;
}
/**
* fu_udev_device_get_subsystem:
* @self: a #FuUdevDevice
*
* Gets the device subsystem, e.g. `pci`
*
* Returns: a subsystem, or NULL if unset or invalid
*
* Since: 1.1.2
**/
const gchar *
fu_udev_device_get_subsystem(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
return priv->subsystem;
}
/**
* fu_udev_device_get_bind_id:
* @self: a #FuUdevDevice
*
* Gets the device ID used for binding the device, e.g. `pci:1:2:3`
*
* Returns: a bind_id, or NULL if unset or invalid
*
* Since: 1.7.2
**/
const gchar *
fu_udev_device_get_bind_id(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
fu_udev_device_ensure_bind_id(self, NULL);
return priv->bind_id;
}
/**
* fu_udev_device_get_driver:
* @self: a #FuUdevDevice
*
* Gets the device driver, e.g. `psmouse`.
*
* Returns: a subsystem, or NULL if unset or invalid
*
* Since: 1.5.3
**/
const gchar *
fu_udev_device_get_driver(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
return priv->driver;
}
/**
* fu_udev_device_get_device_file:
* @self: a #FuUdevDevice
*
* Gets the device node.
*
* Returns: a device file, or NULL if unset
*
* Since: 1.3.1
**/
const gchar *
fu_udev_device_get_device_file(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
return priv->device_file;
}
/**
* fu_udev_device_get_sysfs_path:
* @self: a #FuUdevDevice
*
* Gets the device sysfs path, e.g. `/sys/devices/pci0000:00/0000:00:14.0`.
*
* Returns: a local path, or NULL if unset or invalid
*
* Since: 1.1.2
**/
const gchar *
fu_udev_device_get_sysfs_path(FuUdevDevice *self)
{
#ifdef HAVE_GUDEV
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
if (priv->udev_device != NULL)
return g_udev_device_get_sysfs_path(priv->udev_device);
#endif
return NULL;
}
/**
* fu_udev_device_get_number:
* @self: a #FuUdevDevice
*
* Gets the device number, if any.
*
* Returns: integer, 0 if the data is unavailable, or %G_MAXUINT64 if the
* feature is not available
*
* Since: 1.5.0
**/
guint64
fu_udev_device_get_number(FuUdevDevice *self)
{
#ifdef HAVE_GUDEV
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0);
if (priv->udev_device != NULL) {
guint64 tmp = 0;
g_autoptr(GError) error_local = NULL;
if (!fu_strtoull(g_udev_device_get_number(priv->udev_device),
&tmp,
0x0,
G_MAXUINT64,
&error_local)) {
g_warning("failed to convert udev number: %s", error_local->message);
return G_MAXUINT64;
}
return tmp;
}
#endif
return G_MAXUINT64;
}
/**
* fu_udev_device_get_vendor:
* @self: a #FuUdevDevice
*
* Gets the device vendor code.
*
* Returns: a vendor code, or 0 if unset or invalid
*
* Since: 1.1.2
**/
guint16
fu_udev_device_get_vendor(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0x0000);
return priv->vendor;
}
/**
* fu_udev_device_get_model:
* @self: a #FuUdevDevice
*
* Gets the device device code.
*
* Returns: a vendor code, or 0 if unset or invalid
*
* Since: 1.1.2
**/
guint16
fu_udev_device_get_model(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0x0000);
return priv->model;
}
/**
* fu_udev_device_get_subsystem_vendor:
* @self: a #FuUdevDevice
*
* Gets the device subsystem vendor code.
*
* Returns: a vendor code, or 0 if unset or invalid
*
* Since: 1.5.0
**/
guint16
fu_udev_device_get_subsystem_vendor(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0x0000);
return priv->subsystem_vendor;
}
/**
* fu_udev_device_get_subsystem_model:
* @self: a #FuUdevDevice
*
* Gets the device subsystem model code.
*
* Returns: a vendor code, or 0 if unset or invalid
*
* Since: 1.5.0
**/
guint16
fu_udev_device_get_subsystem_model(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0x0000);
return priv->subsystem_model;
}
/**
* fu_udev_device_get_revision:
* @self: a #FuUdevDevice
*
* Gets the device revision.
*
* Returns: a vendor code, or 0 if unset or invalid
*
* Since: 1.1.2
**/
guint8
fu_udev_device_get_revision(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0x00);
return priv->revision;
}
#ifdef HAVE_GUDEV
static gchar *
fu_udev_device_get_parent_subsystems(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
GString *str = g_string_new(NULL);
g_autoptr(GUdevDevice) udev_device = g_object_ref(priv->udev_device);
/* find subsystems of self and all parent devices */
if (priv->subsystem != NULL)
g_string_append_printf(str, "%s,", priv->subsystem);
while (TRUE) {
g_autoptr(GUdevDevice) parent = g_udev_device_get_parent(udev_device);
if (parent == NULL)
break;
if (g_udev_device_get_subsystem(parent) != NULL) {
g_string_append_printf(str, "%s,", g_udev_device_get_subsystem(parent));
}
g_set_object(&udev_device, g_steal_pointer(&parent));
}
if (str->len > 0)
g_string_truncate(str, str->len - 1);
return g_string_free(str, FALSE);
}
static gboolean
fu_udev_device_match_subsystem_devtype(GUdevDevice *udev_device,
const gchar *subsystem,
const gchar *devtype)
{
if (subsystem != NULL) {
if (g_strcmp0(g_udev_device_get_subsystem(udev_device), subsystem) != 0)
return FALSE;
}
if (devtype != NULL) {
if (g_strcmp0(g_udev_device_get_devtype(udev_device), devtype) != 0)
return FALSE;
}
return TRUE;
}
static GUdevDevice *
fu_udev_device_get_parent_with_subsystem_devtype(GUdevDevice *udev_device,
const gchar *subsystem,
const gchar *devtype)
{
g_autoptr(GUdevDevice) udev_device_tmp = g_object_ref(udev_device);
while (udev_device_tmp != NULL) {
g_autoptr(GUdevDevice) parent = NULL;
if (fu_udev_device_match_subsystem_devtype(udev_device_tmp, subsystem, devtype))
return g_object_ref(udev_device_tmp);
parent = g_udev_device_get_parent(udev_device_tmp);
g_set_object(&udev_device_tmp, parent);
}
return NULL;
}
#endif
/**
* fu_udev_device_set_physical_id:
* @self: a #FuUdevDevice
* @subsystems: a subsystem string, e.g. `pci,usb,scsi:scsi_target`
* @error: (nullable): optional return location for an error
*
* Sets the physical ID from the device subsystem. Plugins should choose the
* subsystem that is "deepest" in the udev tree, for instance choosing `usb`
* over `pci` for a mouse device.
*
* The devtype can also be specified for a specific device, which is useful when the
* subsystem alone is not enough to identify the physical device. e.g. ignoring the
* specific LUNs for a SCSI device.
*
* Returns: %TRUE if the physical device was set.
*
* Since: 1.1.2
**/
gboolean
fu_udev_device_set_physical_id(FuUdevDevice *self, const gchar *subsystems, GError **error)
{
#ifdef HAVE_GUDEV
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
const gchar *tmp;
g_autofree gchar *physical_id = NULL;
g_autofree gchar *subsystem = NULL;
g_auto(GStrv) split = NULL;
g_autoptr(GUdevDevice) udev_device = NULL;
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
g_return_val_if_fail(subsystems != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* nothing to do */
if (priv->udev_device == NULL)
return TRUE;
/* look for each subsystem[:devtype] in turn */
split = g_strsplit(subsystems, ",", -1);
for (guint i = 0; split[i] != NULL; i++) {
g_auto(GStrv) subsys_devtype = g_strsplit(split[i], ":", 2);
/* matching on devtype is optional */
udev_device = fu_udev_device_get_parent_with_subsystem_devtype(priv->udev_device,
subsys_devtype[0],
subsys_devtype[1]);
if (udev_device != NULL) {
subsystem = g_strdup(subsys_devtype[0]);
break;
}
}
if (udev_device == NULL) {
g_autofree gchar *str = fu_udev_device_get_parent_subsystems(self);
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"failed to find device with subsystems %s, only got %s",
subsystems,
str);
return FALSE;
}
if (g_strcmp0(subsystem, "pci") == 0) {
tmp = g_udev_device_get_property(udev_device, "PCI_SLOT_NAME");
if (tmp == NULL) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"failed to find PCI_SLOT_NAME");
return FALSE;
}
physical_id = g_strdup_printf("PCI_SLOT_NAME=%s", tmp);
} 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, "gpio") == 0) {
tmp = g_udev_device_get_property(udev_device, "DEVPATH");
if (tmp == NULL) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"failed to find DEVPATH");
return FALSE;
}
physical_id = g_strdup_printf("DEVPATH=%s", tmp);
} else if (g_strcmp0(subsystem, "hid") == 0) {
tmp = g_udev_device_get_property(udev_device, "HID_PHYS");
if (tmp == NULL) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"failed to find HID_PHYS");
return FALSE;
}
physical_id = g_strdup_printf("HID_PHYS=%s", tmp);
} else if (g_strcmp0(subsystem, "tpm") == 0 ||
g_strcmp0(subsystem, "drm_dp_aux_dev") == 0) {
tmp = g_udev_device_get_property(udev_device, "DEVNAME");
if (tmp == NULL) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"failed to find DEVNAME");
return FALSE;
}
physical_id = g_strdup_printf("DEVNAME=%s", tmp);
} else {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"cannot handle subsystem %s",
subsystem);
return FALSE;
}
/* success */
fu_device_set_physical_id(FU_DEVICE(self), physical_id);
return TRUE;
#else
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Not supported as <gudev.h> is unavailable");
return FALSE;
#endif
}
/**
* fu_udev_device_set_logical_id:
* @self: a #FuUdevDevice
* @subsystem: a subsystem string, e.g. `pci,usb`
* @error: (nullable): optional return location for an error
*
* Sets the logical ID from the device subsystem. Plugins should choose the
* subsystem that most relevant in the udev tree, for instance choosing `hid`
* over `usb` for a mouse device.
*
* Returns: %TRUE if the logical device was set.
*
* Since: 1.5.8
**/
gboolean
fu_udev_device_set_logical_id(FuUdevDevice *self, const gchar *subsystem, GError **error)
{
#ifdef HAVE_GUDEV
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
const gchar *tmp;
g_autofree gchar *logical_id = NULL;
g_autoptr(GUdevDevice) udev_device = NULL;
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
g_return_val_if_fail(subsystem != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* nothing to do */
if (priv->udev_device == NULL)
return TRUE;
/* find correct device matching subsystem */
if (g_strcmp0(priv->subsystem, subsystem) == 0) {
udev_device = g_object_ref(priv->udev_device);
} else {
udev_device =
g_udev_device_get_parent_with_subsystem(priv->udev_device, subsystem, NULL);
}
if (udev_device == NULL) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"failed to find device with subsystem %s",
subsystem);
return FALSE;
}
/* query each subsystem */
if (g_strcmp0(subsystem, "hid") == 0) {
tmp = g_udev_device_get_property(udev_device, "HID_UNIQ");
if (tmp == NULL) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"failed to find HID_UNIQ");
return FALSE;
}
logical_id = g_strdup_printf("HID_UNIQ=%s", tmp);
} else {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"cannot handle subsystem %s",
subsystem);
return FALSE;
}
/* success */
fu_device_set_logical_id(FU_DEVICE(self), logical_id);
return TRUE;
#else
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Not supported as <gudev.h> is unavailable");
return FALSE;
#endif
}
/**
* fu_udev_device_get_fd:
* @self: a #FuUdevDevice
*
* Gets the file descriptor if the device is open.
*
* Returns: positive integer, or -1 if the device is not open
*
* Since: 1.3.3
**/
gint
fu_udev_device_get_fd(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), -1);
return priv->fd;
}
/**
* fu_udev_device_set_fd:
* @self: a #FuUdevDevice
* @fd: a valid file descriptor
*
* Replace the file descriptor to use when the device has already been opened.
* This object will automatically close() @fd when fu_device_close() is called.
*
* Since: 1.3.3
**/
void
fu_udev_device_set_fd(FuUdevDevice *self, gint fd)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_UDEV_DEVICE(self));
if (priv->fd > 0)
close(priv->fd);
priv->fd = fd;
}
/**
* fu_udev_device_set_flags:
* @self: a #FuUdevDevice
* @flags: udev device flags, e.g. %FU_UDEV_DEVICE_FLAG_OPEN_READ
*
* Sets the parameters to use when opening the device.
*
* For example %FU_UDEV_DEVICE_FLAG_OPEN_READ means that fu_device_open()
* would use `O_RDONLY` rather than `O_RDWR` which is the default.
*
* Since: 1.3.6
**/
void
fu_udev_device_set_flags(FuUdevDevice *self, FuUdevDeviceFlags flags)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_UDEV_DEVICE(self));
priv->flags = flags;
#ifdef HAVE_GUDEV
/* overwrite */
if (flags & FU_UDEV_DEVICE_FLAG_USE_CONFIG) {
g_free(priv->device_file);
priv->device_file =
g_build_filename(g_udev_device_get_sysfs_path(priv->udev_device),
"config",
NULL);
}
#endif
}
static gboolean
fu_udev_device_open(FuDevice *device, GError **error)
{
FuUdevDevice *self = FU_UDEV_DEVICE(device);
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
/* open device */
if (priv->device_file != NULL && priv->flags != FU_UDEV_DEVICE_FLAG_NONE) {
gint flags;
if (priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_READ &&
priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_WRITE) {
flags = O_RDWR;
} else if (priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_WRITE) {
flags = O_WRONLY;
} else {
flags = O_RDONLY;
}
#ifdef O_NONBLOCK
if (priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK)
flags |= O_NONBLOCK;
#endif
#ifdef O_SYNC
if (priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_SYNC)
flags |= O_SYNC;
#endif
priv->fd = g_open(priv->device_file, flags, 0);
if (priv->fd < 0) {
g_set_error(error,
G_IO_ERROR,
#ifdef HAVE_ERRNO_H
g_io_error_from_errno(errno),
#else
G_IO_ERROR_FAILED,
#endif
"failed to open %s: %s",
priv->device_file,
strerror(errno));
return FALSE;
}
}
/* success */
return TRUE;
}
static gboolean
fu_udev_device_rescan(FuDevice *device, GError **error)
{
#ifdef HAVE_GUDEV
FuUdevDevice *self = FU_UDEV_DEVICE(device);
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
const gchar *sysfs_path;
g_autoptr(GUdevClient) udev_client = g_udev_client_new(NULL);
g_autoptr(GUdevDevice) udev_device = NULL;
/* never set */
if (priv->udev_device == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"rescan with no previous device");
return FALSE;
}
sysfs_path = g_udev_device_get_sysfs_path(priv->udev_device);
udev_device = g_udev_client_query_by_sysfs_path(udev_client, sysfs_path);
if (udev_device == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"rescan could not find device %s",
sysfs_path);
return FALSE;
}
fu_udev_device_set_dev(self, udev_device);
fu_device_probe_invalidate(device);
#endif
return fu_device_probe(device, error);
}
static gboolean
fu_udev_device_close(FuDevice *device, GError **error)
{
FuUdevDevice *self = FU_UDEV_DEVICE(device);
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
/* close device */
if (priv->fd > 0) {
if (!g_close(priv->fd, error))
return FALSE;
priv->fd = 0;
}
/* success */
return TRUE;
}
/**
* fu_udev_device_ioctl:
* @self: a #FuUdevDevice
* @request: request number
* @buf: a buffer to use, which *must* be large enough for the request
* @rc: (out) (nullable): the raw return value from the ioctl
* @timeout: timeout in ms for the retry action, see %FU_UDEV_DEVICE_FLAG_IOCTL_RETRY
* @error: (nullable): optional return location for an error
*
* Control a device using a low-level request.
*
* Returns: %TRUE for success
*
* Since: 1.8.2
**/
gboolean
fu_udev_device_ioctl(FuUdevDevice *self,
gulong request,
guint8 *buf,
gint *rc,
guint timeout,
GError **error)
{
#ifdef HAVE_IOCTL_H
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
gint rc_tmp;
g_autoptr(GTimer) timer = g_timer_new();
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
g_return_val_if_fail(request != 0x0, FALSE);
g_return_val_if_fail(buf != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* not open! */
if (priv->fd == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"%s [%s] has not been opened",
fu_device_get_id(FU_DEVICE(self)),
fu_device_get_name(FU_DEVICE(self)));
return FALSE;
}
/* poll if required up to the timeout */
do {
rc_tmp = ioctl(priv->fd, request, buf);
if (rc_tmp >= 0)
break;
} while ((priv->flags & FU_UDEV_DEVICE_FLAG_IOCTL_RETRY) &&
(errno == EINTR || errno == EAGAIN) &&
g_timer_elapsed(timer, NULL) < timeout * 1000.f);
if (rc != NULL)
*rc = rc_tmp;
if (rc_tmp < 0) {
#ifdef HAVE_ERRNO_H
if (errno == EPERM) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_PERMISSION_DENIED,
"permission denied");
return FALSE;
}
if (errno == ENOTTY) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"permission denied");
return FALSE;
}
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"ioctl error: %s [%i]",
strerror(errno),
errno);
#else
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified ioctl error");
#endif
return FALSE;
}
return TRUE;
#else
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Not supported as <sys/ioctl.h> not found");
return FALSE;
#endif
}
/**
* fu_udev_device_pread:
* @self: a #FuUdevDevice
* @port: offset address
* @buf: (in): data
* @bufsz: size of @buf
* @error: (nullable): optional return location for an error
*
* Read a buffer from a file descriptor at a given offset.
*
* Returns: %TRUE for success
*
* Since: 1.8.2
**/
gboolean
fu_udev_device_pread(FuUdevDevice *self, goffset port, guint8 *buf, gsize bufsz, GError **error)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
g_return_val_if_fail(buf != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* not open! */
if (priv->fd == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"%s [%s] has not been opened",
fu_device_get_id(FU_DEVICE(self)),
fu_device_get_name(FU_DEVICE(self)));
return FALSE;
}
#ifdef HAVE_PWRITE
if (pread(priv->fd, buf, bufsz, port) != (gssize)bufsz) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"failed to read from port 0x%04x: %s",
(guint)port,
strerror(errno));
return FALSE;
}
return TRUE;
#else
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Not supported as pread() is unavailable");
return FALSE;
#endif
}
/**
* fu_udev_device_seek:
* @self: a #FuUdevDevice
* @offset: offset address
* @error: (nullable): optional return location for an error
*
* Seeks a file descriptor to a given offset.
*
* Returns: %TRUE for success
*
* Since: 1.7.2
**/
gboolean
fu_udev_device_seek(FuUdevDevice *self, goffset offset, GError **error)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* not open! */
if (priv->fd == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"%s [%s] has not been opened",
fu_device_get_id(FU_DEVICE(self)),
fu_device_get_name(FU_DEVICE(self)));
return FALSE;
}
#ifdef HAVE_PWRITE
if (lseek(priv->fd, offset, SEEK_SET) < 0) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"failed to seek to 0x%04x: %s",
(guint)offset,
strerror(errno));
return FALSE;
}
return TRUE;
#else
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Not supported as lseek() is unavailable");
return FALSE;
#endif
}
/**
* fu_udev_device_pwrite:
* @self: a #FuUdevDevice
* @port: offset address
* @buf: (out): data
* @bufsz: size of @data
* @error: (nullable): optional return location for an error
*
* Write a buffer to a file descriptor at a given offset.
*
* Returns: %TRUE for success
*
* Since: 1.8.2
**/
gboolean
fu_udev_device_pwrite(FuUdevDevice *self,
goffset port,
const guint8 *buf,
gsize bufsz,
GError **error)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* not open! */
if (priv->fd == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"%s [%s] has not been opened",
fu_device_get_id(FU_DEVICE(self)),
fu_device_get_name(FU_DEVICE(self)));
return FALSE;
}
#ifdef HAVE_PWRITE
if (pwrite(priv->fd, buf, bufsz, port) != (gssize)bufsz) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"failed to write to port %04x: %s",
(guint)port,
strerror(errno));
return FALSE;
}
return TRUE;
#else
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Not supported as pwrite() is unavailable");
return FALSE;
#endif
}
/**
* fu_udev_device_get_parent_name
* @self: a #FuUdevDevice
*
* Returns the name of the direct ancestor of this device
*
* Returns: string or NULL if unset or invalid
*
* Since: 1.4.5
**/
gchar *
fu_udev_device_get_parent_name(FuUdevDevice *self)
{
#ifdef HAVE_GUDEV
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GUdevDevice) parent = NULL;
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
parent = g_udev_device_get_parent(priv->udev_device);
return parent == NULL ? NULL : g_strdup(g_udev_device_get_name(parent));
#else
return NULL;
#endif
}
/**
* fu_udev_device_get_sysfs_attr:
* @self: a #FuUdevDevice
* @attr: name of attribute to get
* @error: (nullable): optional return location for an error
*
* Reads an arbitrary sysfs attribute 'attr' associated with UDEV device
*
* Returns: string or NULL
*
* Since: 1.4.5
**/
const gchar *
fu_udev_device_get_sysfs_attr(FuUdevDevice *self, const gchar *attr, GError **error)
{
#ifdef HAVE_GUDEV
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
const gchar *result;
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
g_return_val_if_fail(attr != NULL, NULL);
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
/* nothing to do */
if (priv->udev_device == NULL) {
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "not yet initialized");
return NULL;
}
result = g_udev_device_get_sysfs_attr(priv->udev_device, attr);
if (result == NULL) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"attribute %s returned no data",
attr);
return NULL;
}
return result;
#else
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"getting attributes is not supported as no GUdev support");
return NULL;
#endif
}
/**
* fu_udev_device_get_sysfs_attr_uint64:
* @self: a #FuUdevDevice
* @attr: name of attribute to get
* @value: (out) (optional): value to return
* @error: (nullable): optional return location for an error
*
* Reads an arbitrary sysfs attribute 'attr' associated with UDEV device as a uint64.
*
* Returns: %TRUE for success
*
* Since: 1.7.2
**/
gboolean
fu_udev_device_get_sysfs_attr_uint64(FuUdevDevice *self,
const gchar *attr,
guint64 *value,
GError **error)
{
const gchar *tmp;
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
g_return_val_if_fail(attr != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
tmp = fu_udev_device_get_sysfs_attr(self, attr, error);
if (tmp == NULL)
return FALSE;
return fu_strtoull(tmp, value, 0, G_MAXUINT64, error);
}
/**
* fu_udev_device_write_sysfs:
* @self: a #FuUdevDevice
* @attribute: sysfs attribute name
* @val: data to write into the attribute
* @error: (nullable): optional return location for an error
*
* Writes data into a sysfs attribute
*
* Returns: %TRUE for success
*
* Since: 1.4.5
**/
gboolean
fu_udev_device_write_sysfs(FuUdevDevice *self,
const gchar *attribute,
const gchar *val,
GError **error)
{
#ifdef __linux__
ssize_t n;
int r;
int fd;
g_autofree gchar *path = NULL;
g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
g_return_val_if_fail(attribute != NULL, FALSE);
g_return_val_if_fail(val != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
path = g_build_filename(fu_udev_device_get_sysfs_path(self), attribute, NULL);
fd = open(path, O_WRONLY | O_CLOEXEC);
if (fd < 0) {
g_set_error(error,
G_IO_ERROR,
g_io_error_from_errno(errno),
"could not open %s: %s",
path,
g_strerror(errno));
return FALSE;
}
do {
n = write(fd, val, strlen(val));
if (n < 1 && errno != EINTR) {
g_set_error(error,
G_IO_ERROR,
g_io_error_from_errno(errno),
"could not write to %s: %s",
path,
g_strerror(errno));
(void)close(fd);
return FALSE;
}
} while (n < 1);
r = close(fd);
if (r < 0 && errno != EINTR) {
g_set_error(error,
G_IO_ERROR,
g_io_error_from_errno(errno),
"could not close %s: %s",
path,
g_strerror(errno));
return FALSE;
}
return TRUE;
#else
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"sysfs attributes not supported on Windows");
return FALSE;
#endif
}
/**
* fu_udev_device_get_devtype
* @self: a #FuUdevDevice
*
* Returns the Udev device type
*
* Returns: device type specified in the uevent
*
* Since: 1.4.5
**/
const gchar *
fu_udev_device_get_devtype(FuUdevDevice *self)
{
#ifdef HAVE_GUDEV
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
return g_udev_device_get_devtype(priv->udev_device);
#else
return NULL;
#endif
}
/**
* fu_udev_device_get_siblings_with_subsystem
* @self: a #FuUdevDevice
* @subsystem: the name of a udev subsystem
*
* Get a list of devices that are siblings of self and have the
* provided subsystem.
*
* Returns: (element-type FuUdevDevice) (transfer full): devices
*
* Since: 1.6.0
*/
GPtrArray *
fu_udev_device_get_siblings_with_subsystem(FuUdevDevice *self, const gchar *const subsystem)
{
g_autoptr(GPtrArray) out = g_ptr_array_new_with_free_func(g_object_unref);
#ifdef HAVE_GUDEV
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GUdevDevice) udev_parent = g_udev_device_get_parent(priv->udev_device);
const gchar *udev_parent_path = g_udev_device_get_sysfs_path(udev_parent);
g_autoptr(GUdevClient) udev_client = g_udev_client_new(NULL);
g_autoptr(GList) enumerated = g_udev_client_query_by_subsystem(udev_client, subsystem);
for (GList *element = enumerated; element != NULL; element = element->next) {
g_autoptr(GUdevDevice) enumerated_device = element->data;
g_autoptr(GUdevDevice) enumerated_parent = NULL;
const gchar *enumerated_parent_path;
/* get parent, if it exists */
enumerated_parent = g_udev_device_get_parent(enumerated_device);
if (enumerated_parent == NULL)
break;
enumerated_parent_path = g_udev_device_get_sysfs_path(enumerated_parent);
/* if the sysfs path of self's parent is the same as that of the
* located device's parent, they are siblings */
if (g_strcmp0(udev_parent_path, enumerated_parent_path) == 0) {
FuUdevDevice *dev =
fu_udev_device_new(fu_device_get_context(FU_DEVICE(self)),
g_steal_pointer(&enumerated_device));
g_ptr_array_add(out, dev);
}
}
#endif
return g_steal_pointer(&out);
}
/**
* fu_udev_device_get_parent_with_subsystem
* @self: a #FuUdevDevice
* @subsystem: the name of a udev subsystem
*
* Get the device that is a parent of self and has the provided subsystem.
*
* Returns: (transfer full): device, or %NULL
*
* Since: 1.7.6
*/
FuUdevDevice *
fu_udev_device_get_parent_with_subsystem(FuUdevDevice *self, const gchar *subsystem)
{
#ifdef HAVE_GUDEV
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GUdevDevice) device_tmp = NULL;
device_tmp = g_udev_device_get_parent_with_subsystem(priv->udev_device, subsystem, NULL);
if (device_tmp == NULL)
return NULL;
return fu_udev_device_new(fu_device_get_context(FU_DEVICE(self)), device_tmp);
#else
return NULL;
#endif
}
/**
* fu_udev_device_get_children_with_subsystem
* @self: a #FuUdevDevice
* @subsystem: the name of a udev subsystem
*
* Get a list of devices that are children of self and have the
* provided subsystem.
*
* Returns: (element-type FuUdevDevice) (transfer full): devices
*
* Since: 1.6.2
*/
GPtrArray *
fu_udev_device_get_children_with_subsystem(FuUdevDevice *self, const gchar *const subsystem)
{
g_autoptr(GPtrArray) out = g_ptr_array_new_with_free_func(g_object_unref);
#ifdef HAVE_GUDEV
const gchar *self_path = fu_udev_device_get_sysfs_path(self);
g_autoptr(GUdevClient) udev_client = g_udev_client_new(NULL);
g_autoptr(GList) enumerated = g_udev_client_query_by_subsystem(udev_client, subsystem);
for (GList *element = enumerated; element != NULL; element = element->next) {
g_autoptr(GUdevDevice) enumerated_device = element->data;
g_autoptr(GUdevDevice) enumerated_parent = NULL;
const gchar *enumerated_parent_path;
/* get parent, if it exists */
enumerated_parent = g_udev_device_get_parent(enumerated_device);
if (enumerated_parent == NULL)
break;
enumerated_parent_path = g_udev_device_get_sysfs_path(enumerated_parent);
/* enumerated device is a child of self if its parent is the
* same as self */
if (g_strcmp0(self_path, enumerated_parent_path) == 0) {
FuUdevDevice *dev =
fu_udev_device_new(fu_device_get_context(FU_DEVICE(self)),
g_steal_pointer(&enumerated_device));
g_ptr_array_add(out, dev);
}
}
#endif
return g_steal_pointer(&out);
}
static void
fu_udev_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
FuUdevDevice *self = FU_UDEV_DEVICE(object);
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
switch (prop_id) {
case PROP_UDEV_DEVICE:
g_value_set_object(value, priv->udev_device);
break;
case PROP_SUBSYSTEM:
g_value_set_string(value, priv->subsystem);
break;
case PROP_BIND_ID:
g_value_set_string(value, priv->bind_id);
break;
case PROP_DRIVER:
g_value_set_string(value, priv->driver);
break;
case PROP_DEVICE_FILE:
g_value_set_string(value, priv->device_file);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
fu_udev_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
FuUdevDevice *self = FU_UDEV_DEVICE(object);
switch (prop_id) {
case PROP_UDEV_DEVICE:
fu_udev_device_set_dev(self, g_value_get_object(value));
break;
case PROP_SUBSYSTEM:
fu_udev_device_set_subsystem(self, g_value_get_string(value));
break;
case PROP_BIND_ID:
fu_udev_device_set_bind_id(self, g_value_get_string(value));
break;
case PROP_DRIVER:
fu_udev_device_set_driver(self, g_value_get_string(value));
break;
case PROP_DEVICE_FILE:
fu_udev_device_set_device_file(self, g_value_get_string(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
fu_udev_device_finalize(GObject *object)
{
FuUdevDevice *self = FU_UDEV_DEVICE(object);
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
g_free(priv->subsystem);
g_free(priv->bind_id);
g_free(priv->driver);
g_free(priv->device_file);
if (priv->udev_device != NULL)
g_object_unref(priv->udev_device);
if (priv->fd > 0)
g_close(priv->fd, NULL);
G_OBJECT_CLASS(fu_udev_device_parent_class)->finalize(object);
}
static void
fu_udev_device_init(FuUdevDevice *self)
{
FuUdevDevicePrivate *priv = GET_PRIVATE(self);
priv->flags = FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE;
fu_device_set_acquiesce_delay(FU_DEVICE(self), 2500);
}
static void
fu_udev_device_class_init(FuUdevDeviceClass *klass)
{
FuDeviceClass *device_class = FU_DEVICE_CLASS(klass);
GObjectClass *object_class = G_OBJECT_CLASS(klass);
GParamSpec *pspec;
object_class->finalize = fu_udev_device_finalize;
object_class->get_property = fu_udev_device_get_property;
object_class->set_property = fu_udev_device_set_property;
device_class->probe = fu_udev_device_probe;
device_class->rescan = fu_udev_device_rescan;
device_class->incorporate = fu_udev_device_incorporate;
device_class->open = fu_udev_device_open;
device_class->close = fu_udev_device_close;
device_class->to_string = fu_udev_device_to_string;
device_class->bind_driver = fu_udev_device_bind_driver;
device_class->unbind_driver = fu_udev_device_unbind_driver;
/**
* FuUdevDevice::changed:
* @self: the #FuUdevDevice instance that emitted the signal
*
* The ::changed signal is emitted when the low-level GUdevDevice has changed.
*
* Since: 1.1.2
**/
signals[SIGNAL_CHANGED] = g_signal_new("changed",
G_TYPE_FROM_CLASS(object_class),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* FuUdevDevice:udev-device:
*
* The low-level GUdevDevice.
*
* Since: 1.1.2
*/
pspec = g_param_spec_object("udev-device",
NULL,
NULL,
G_UDEV_TYPE_DEVICE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_UDEV_DEVICE, pspec);
/**
* FuUdevDevice:subsystem:
*
* The device subsystem.
*
* Since: 1.1.2
*/
pspec = g_param_spec_string("subsystem",
NULL,
NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_SUBSYSTEM, pspec);
/**
* FuUdevDevice:bind-id:
*
* The bind ID to use when binding a new driver.
*
* Since: 1.7.2
*/
pspec = g_param_spec_string("bind-id",
NULL,
NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_BIND_ID, pspec);
/**
* FuUdevDevice:driver:
*
* The driver being used for the device.
*
* Since: 1.5.3
*/
pspec = g_param_spec_string("driver",
NULL,
NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_DRIVER, pspec);
/**
* FuUdevDevice:device-file:
*
* The low level file to use for device access.
*
* Since: 1.3.1
*/
pspec = g_param_spec_string("device-file",
NULL,
NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_DEVICE_FILE, pspec);
}
/**
* fu_udev_device_new:
* @ctx: (nullable): a #FuContext
* @udev_device: a #GUdevDevice
*
* Creates a new #FuUdevDevice.
*
* Returns: (transfer full): a #FuUdevDevice
*
* Since: 1.8.2
**/
FuUdevDevice *
fu_udev_device_new(FuContext *ctx, GUdevDevice *udev_device)
{
return g_object_new(FU_TYPE_UDEV_DEVICE, "context", ctx, "udev-device", udev_device, NULL);
}