/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuUdevDevice" #include "config.h" #include #include #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_IOCTL_H #include #endif #include #include #include #include #include "fu-device-private.h" #include "fu-i2c-device.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; guint32 vendor; guint32 model; guint32 subsystem_vendor; guint32 subsystem_model; guint8 revision; gchar *subsystem; 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_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); } static guint32 fu_udev_device_get_sysfs_attr_as_uint32(GUdevDevice *udev_device, const gchar *name) { #ifdef HAVE_GUDEV guint64 tmp = fu_common_strtoull(g_udev_device_get_sysfs_attr(udev_device, name)); if (tmp > G_MAXUINT32) { g_warning("reading %s for %s overflowed", name, g_udev_device_get_sysfs_path(udev_device)); return G_MAXUINT32; } return tmp; #else return G_MAXUINT32; #endif } static guint8 fu_udev_device_get_sysfs_attr_as_uint8(GUdevDevice *udev_device, const gchar *name) { #ifdef HAVE_GUDEV guint64 tmp = fu_common_strtoull(g_udev_device_get_sysfs_attr(udev_device, name)); if (tmp > G_MAXUINT8) { g_warning("reading %s for %s overflowed", name, g_udev_device_get_sysfs_path(udev_device)); return G_MAXUINT8; } return tmp; #else return G_MAXUINT8; #endif } #ifdef HAVE_GUDEV 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_common_string_append_kv(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_common_string_append_kv(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->udev_device != NULL) { fu_common_string_append_kv(str, idt, "SysfsPath", g_udev_device_get_sysfs_path(priv->udev_device)); fu_common_string_append_kv(str, idt, "Subsystem", priv->subsystem); if (priv->driver != NULL) fu_common_string_append_kv(str, idt, "Driver", priv->driver); if (priv->device_file != NULL) fu_common_string_append_kv(str, idt, "DeviceFile", priv->device_file); } 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_common_string_append_kv(str, idt, "Parent", NULL); fu_udev_device_to_string_raw(udev_parent, idt + 1, str); } } #endif } 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"); } 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"); } static 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) { g_autofree gchar *devid = NULL; g_autofree gchar *id_safe = NULL; /* this prefix is not useful */ if (g_str_has_prefix(tmp, "PNP: ")) tmp += 5; id_safe = g_utf8_strup(tmp, -1); g_strdelimit(id_safe, " /\\\"", '-'); devid = g_strdup_printf("SERIO\\FWID_%s", id_safe); fu_device_add_instance_id(FU_DEVICE(self), devid); } return TRUE; } #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; /* set ven:dev:rev */ priv->vendor = fu_udev_device_get_sysfs_attr_as_uint32(priv->udev_device, "vendor"); priv->model = fu_udev_device_get_sysfs_attr_as_uint32(priv->udev_device, "device"); priv->revision = fu_udev_device_get_sysfs_attr_as_uint8(priv->udev_device, "revision"); priv->subsystem_vendor = fu_udev_device_get_sysfs_attr_as_uint32(priv->udev_device, "subsystem_vendor"); priv->subsystem_model = fu_udev_device_get_sysfs_attr_as_uint32(priv->udev_device, "subsystem_device"); #ifdef HAVE_GUDEV /* fallback to the parent */ udev_parent = g_udev_device_get_parent(priv->udev_device); if (udev_parent != NULL && priv->flags & FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT && priv->vendor == 0x0 && priv->model == 0x0 && priv->revision == 0x0) { priv->vendor = fu_udev_device_get_sysfs_attr_as_uint32(udev_parent, "vendor"); priv->model = fu_udev_device_get_sysfs_attr_as_uint32(udev_parent, "device"); priv->revision = fu_udev_device_get_sysfs_attr_as_uint8(udev_parent, "revision"); priv->subsystem_vendor = fu_udev_device_get_sysfs_attr_as_uint32(udev_parent, "subsystem_vendor"); priv->subsystem_model = fu_udev_device_get_sysfs_attr_as_uint32(udev_parent, "subsystem_device"); } /* 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_MAXUINT32) { 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) { 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 && priv->model != 0x0000 && priv->subsystem_vendor != 0x0000 && priv->subsystem_model != 0x0000) { g_autofree gchar *devid1 = NULL; g_autofree gchar *devid2 = NULL; devid1 = g_strdup_printf("%s\\VEN_%04X&DEV_%04X&SUBSYS_%04X%04X&REV_%02X", subsystem, priv->vendor, priv->model, priv->subsystem_vendor, priv->subsystem_model, priv->revision); fu_device_add_instance_id(device, devid1); devid2 = g_strdup_printf("%s\\VEN_%04X&DEV_%04X&SUBSYS_%04X%04X", subsystem, priv->vendor, priv->model, priv->subsystem_vendor, priv->subsystem_model); fu_device_add_instance_id(device, devid2); } if (priv->vendor != 0x0000 && priv->model != 0x0000) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("%s\\VEN_%04X&DEV_%04X&REV_%02X", subsystem, priv->vendor, priv->model, priv->revision); fu_device_add_instance_id(device, devid); } if (priv->vendor != 0x0000 && priv->model != 0x0000) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("%s\\VEN_%04X&DEV_%04X", subsystem, priv->vendor, priv->model); fu_device_add_instance_id(device, devid); } if (priv->vendor != 0x0000) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("%s\\VEN_%04X", subsystem, priv->vendor); fu_device_add_instance_id_full(device, devid, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* add device class */ tmp = g_udev_device_get_sysfs_attr(priv->udev_device, "class"); if (tmp != NULL && g_str_has_prefix(tmp, "0x")) { g_autofree gchar *class_id = g_utf8_strup(tmp + 2, -1); g_autofree gchar *devid = NULL; devid = g_strdup_printf("%s\\VEN_%04X&CLASS_%s", subsystem, priv->vendor, class_id); fu_device_add_instance_id_full(device, devid, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* add the driver */ if (priv->driver != NULL) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("%s\\DRIVER_%s", subsystem, priv->driver); fu_device_add_instance_id_full(device, devid, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* 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; } #ifdef HAVE_GUDEV static gchar * fu_udev_device_get_bind_id(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); if (g_strcmp0(fu_udev_device_get_subsystem(self), "pci") == 0) return g_strdup(g_udev_device_get_property(priv->udev_device, "PCI_SLOT_NAME")); if (g_strcmp0(fu_udev_device_get_subsystem(self), "hid") == 0) return g_strdup(g_udev_device_get_property(priv->udev_device, "HID_PHYS")); if (g_strcmp0(fu_udev_device_get_subsystem(self), "usb") == 0) return g_path_get_basename(g_udev_device_get_sysfs_path(priv->udev_device)); return NULL; } #endif 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 *bind_id = NULL; 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 */ bind_id = fu_udev_device_get_bind_id(self); if (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, bind_id, strlen(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 *bind_id = NULL; 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 */ bind_id = fu_udev_device_get_bind_id(self); if (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, bind_id, strlen(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); 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_device_file(uself, fu_udev_device_get_device_file(udonor)); } } /** * 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_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) return fu_common_strtoull(g_udev_device_get_number(priv->udev_device)); #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 **/ guint32 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 **/ guint32 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 **/ guint32 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 **/ guint32 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); } #endif /** * fu_udev_device_set_physical_id: * @self: a #FuUdevDevice * @subsystems: a subsystem string, e.g. `pci,usb` * @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. * * 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 *subsystem = NULL; const gchar *tmp; g_autofree gchar *physical_id = 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 in turn */ split = g_strsplit(subsystems, ",", -1); for (guint i = 0; split[i] != NULL; i++) { subsystem = split[i]; if (g_strcmp0(priv->subsystem, subsystem) == 0) { udev_device = g_object_ref(priv->udev_device); break; } udev_device = g_udev_device_get_parent_with_subsystem(priv->udev_device, subsystem, NULL); if (udev_device != NULL) 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) { 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 DEVPATH"); 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 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 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 priv->fd = g_open(priv->device_file, flags, 0); if (priv->fd < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "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 * @error: (nullable): optional return location for an error * * Control a device using a low-level request. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_udev_device_ioctl(FuUdevDevice *self, gulong request, guint8 *buf, gint *rc, GError **error) { #ifdef HAVE_IOCTL_H FuUdevDevicePrivate *priv = GET_PRIVATE(self); gint rc_tmp; 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; } rc_tmp = ioctl(priv->fd, request, buf); 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; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "ioctl error: %s", strerror(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 not found"); return FALSE; #endif } /** * fu_udev_device_pread_full: * @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.4.5 **/ gboolean fu_udev_device_pread_full(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_pwrite_full: * @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.4.5 **/ gboolean fu_udev_device_pwrite_full(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_pwrite: * @self: a #FuUdevDevice * @port: offset address * @data: value * @error: (nullable): optional return location for an error * * Write to a file descriptor at a given offset. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_udev_device_pwrite(FuUdevDevice *self, goffset port, guint8 data, GError **error) { return fu_udev_device_pwrite_full(self, port, &data, 0x01, error); } /** * 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, "not supported"); return NULL; #endif } /** * fu_udev_device_pread: * @self: a #FuUdevDevice * @port: offset address * @data: (out): value * @error: (nullable): optional return location for an error * * Read from a file descriptor at a given offset. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_udev_device_pread(FuUdevDevice *self, goffset port, guint8 *data, GError **error) { return fu_udev_device_pread_full(self, port, data, 0x1, 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 = g_udev_device_get_parent(enumerated_device); const gchar *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) { g_ptr_array_add(out, fu_udev_device_new(g_steal_pointer(&enumerated_device))); } } #endif return g_steal_pointer(&out); } /** * 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 = g_udev_device_get_parent(enumerated_device); const gchar *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) { g_ptr_array_add(out, fu_udev_device_new(g_steal_pointer(&enumerated_device))); } } #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_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_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->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; } 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; 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); 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); 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); 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); 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: * @udev_device: a #GUdevDevice * * Creates a new #FuUdevDevice. * * Returns: (transfer full): a #FuUdevDevice * * Since: 1.1.2 **/ FuUdevDevice * fu_udev_device_new(GUdevDevice *udev_device) { #ifdef HAVE_GUDEV /* create the correct object depending on the subsystem */ if (g_strcmp0(g_udev_device_get_subsystem(udev_device), "i2c-dev") == 0) { return FU_UDEV_DEVICE( g_object_new(FU_TYPE_I2C_DEVICE, "udev-device", udev_device, NULL)); } #endif return FU_UDEV_DEVICE(g_object_new(FU_TYPE_UDEV_DEVICE, "udev-device", udev_device, NULL)); }