/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuUsbDevice" #include "config.h" #include "fu-device-private.h" #include "fu-dump.h" #include "fu-mem.h" #include "fu-string.h" #include "fu-usb-device-fw-ds20.h" #include "fu-usb-device-ms-ds20.h" #include "fu-usb-device-private.h" /** * FuUsbDevice: * * A USB device. * * See also: [class@FuDevice], [class@FuHidDevice] */ typedef struct { GUsbDevice *usb_device; gint configuration; GPtrArray *interfaces; /* nullable, element-type FuUsbDeviceInterface */ FuDeviceLocker *usb_device_locker; } FuUsbDevicePrivate; typedef struct { guint8 number; gboolean claimed; } FuUsbDeviceInterface; G_DEFINE_TYPE_WITH_PRIVATE(FuUsbDevice, fu_usb_device, FU_TYPE_DEVICE) enum { PROP_0, PROP_USB_DEVICE, PROP_LAST }; #define GET_PRIVATE(o) (fu_usb_device_get_instance_private(o)) static void fu_usb_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuUsbDevice *device = FU_USB_DEVICE(object); FuUsbDevicePrivate *priv = GET_PRIVATE(device); switch (prop_id) { case PROP_USB_DEVICE: g_value_set_object(value, priv->usb_device); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_usb_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuUsbDevice *device = FU_USB_DEVICE(object); switch (prop_id) { case PROP_USB_DEVICE: fu_usb_device_set_dev(device, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_usb_device_finalize(GObject *object) { FuUsbDevice *device = FU_USB_DEVICE(object); FuUsbDevicePrivate *priv = GET_PRIVATE(device); if (priv->usb_device_locker != NULL) g_object_unref(priv->usb_device_locker); if (priv->usb_device != NULL) g_object_unref(priv->usb_device); if (priv->interfaces != NULL) g_ptr_array_unref(priv->interfaces); G_OBJECT_CLASS(fu_usb_device_parent_class)->finalize(object); } #if G_USB_CHECK_VERSION(0, 4, 5) static void fu_usb_device_flags_notify_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); if (usb_device == NULL) return; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) g_usb_device_add_tag(usb_device, FU_USB_DEVICE_EMULATION_TAG); } #endif static void fu_usb_device_init(FuUsbDevice *device) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); priv->configuration = -1; fu_device_set_acquiesce_delay(FU_DEVICE(device), 2500); #ifdef HAVE_GUSB fu_device_retry_add_recovery(FU_DEVICE(device), G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE, NULL); fu_device_retry_add_recovery(FU_DEVICE(device), G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_PERMISSION_DENIED, NULL); #endif } static void fu_usb_device_constructed(GObject *obj) { FuUsbDevice *self = FU_USB_DEVICE(obj); #if G_USB_CHECK_VERSION(0, 4, 5) /* copy this to the GUsbDevice */ g_signal_connect(FU_DEVICE(self), "notify::flags", G_CALLBACK(fu_usb_device_flags_notify_cb), NULL); #endif } /** * fu_usb_device_is_open: * @device: a #FuUsbDevice * * Finds out if a USB device is currently open. * * Returns: %TRUE if the device is open. * * Since: 1.0.3 **/ gboolean fu_usb_device_is_open(FuUsbDevice *device) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); g_return_val_if_fail(FU_IS_USB_DEVICE(device), FALSE); return priv->usb_device_locker != NULL; } /** * fu_usb_device_set_configuration: * @device: a #FuUsbDevice * @configuration: the configuration value to set * * Set the active bConfigurationValue for the device. * * Since: 1.7.4 **/ void fu_usb_device_set_configuration(FuUsbDevice *device, gint configuration) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); g_return_if_fail(FU_IS_USB_DEVICE(device)); priv->configuration = configuration; } /** * fu_usb_device_add_interface: * @device: a #FuUsbDevice * @number: bInterfaceNumber of the interface * * Adds an interface that will be claimed on `->open()` and released on `->close()`. * * Since: 1.7.4 **/ void fu_usb_device_add_interface(FuUsbDevice *device, guint8 number) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); FuUsbDeviceInterface *iface; g_return_if_fail(FU_IS_USB_DEVICE(device)); if (priv->interfaces == NULL) priv->interfaces = g_ptr_array_new_with_free_func(g_free); /* check for existing */ for (guint i = 0; i < priv->interfaces->len; i++) { iface = g_ptr_array_index(priv->interfaces, i); if (iface->number == number) return; } /* add new */ iface = g_new0(FuUsbDeviceInterface, 1); iface->number = number; g_ptr_array_add(priv->interfaces, iface); } #ifdef HAVE_GUSB static gboolean fu_usb_device_query_hub(FuUsbDevice *self, GError **error) { FuUsbDevicePrivate *priv = GET_PRIVATE(self); gsize sz = 0; guint16 value = 0x29; guint8 data[0x0c] = {0x0}; g_autoptr(GString) hub = g_string_new(NULL); /* longer descriptor for SuperSpeed */ if (fu_usb_device_get_spec(self) >= 0x0300) value = 0x2a; if (!g_usb_device_control_transfer(priv->usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_DEVICE, 0x06, /* LIBUSB_REQUEST_GET_DESCRIPTOR */ value << 8, 0x00, data, sizeof(data), &sz, 1000, NULL, error)) { g_prefix_error(error, "failed to get USB descriptor: "); return FALSE; } if (g_getenv("FU_USB_DEVICE_DEBUG") != NULL) fu_dump_raw(G_LOG_DOMAIN, "HUB_DT", data, sz); /* for USB 3: size is fixed as max ports is 15, * for USB 2: size is variable as max ports is 255 */ if (fu_usb_device_get_spec(self) >= 0x0300 && sz == 0x0C) { g_string_append_printf(hub, "%02X", data[0x0B]); g_string_append_printf(hub, "%02X", data[0x0A]); } else if (sz >= 9) { guint8 numbytes = fu_common_align_up(data[2] + 1, 0x03) / 8; for (guint i = 0; i < numbytes; i++) { guint8 tmp = 0x0; if (!fu_memread_uint8_safe(data, sz, 7 + i, &tmp, error)) return FALSE; g_string_append_printf(hub, "%02X", tmp); } } if (hub->len > 0) fu_device_add_instance_str(FU_DEVICE(self), "HUB", hub->str); return fu_device_build_instance_id(FU_DEVICE(self), error, "USB", "VID", "PID", "HUB", NULL); } #endif static gboolean fu_usb_device_open(FuDevice *device, GError **error) { #ifdef HAVE_GUSB FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(FuDeviceLocker) locker = NULL; g_return_val_if_fail(FU_IS_USB_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* already open */ if (priv->usb_device_locker != NULL) return TRUE; /* open */ locker = fu_device_locker_new(priv->usb_device, error); if (locker == NULL) return FALSE; /* success */ priv->usb_device_locker = g_steal_pointer(&locker); /* if set */ if (priv->configuration >= 0) { if (!g_usb_device_set_configuration(priv->usb_device, priv->configuration, error)) return FALSE; } /* claim interfaces */ for (guint i = 0; priv->interfaces != NULL && i < priv->interfaces->len; i++) { FuUsbDeviceInterface *iface = g_ptr_array_index(priv->interfaces, i); if (!g_usb_device_claim_interface(priv->usb_device, iface->number, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error(error, "failed to claim interface 0x%02x: ", iface->number); return FALSE; } iface->claimed = TRUE; } #endif return TRUE; } static gboolean fu_usb_device_setup(FuDevice *device, GError **error) { #ifdef HAVE_GUSB FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); guint idx; #if G_USB_CHECK_VERSION(0, 4, 0) g_autoptr(GPtrArray) bos_descriptors = NULL; #endif g_return_val_if_fail(FU_IS_USB_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* get vendor */ if (fu_device_get_vendor(device) == NULL) { idx = g_usb_device_get_manufacturer_index(priv->usb_device); if (idx != 0x00) { g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; tmp = g_usb_device_get_string_descriptor(priv->usb_device, idx, &error_local); if (tmp != NULL) fu_device_set_vendor(device, g_strchomp(tmp)); else g_debug( "failed to load manufacturer string for usb device %u:%u: %s", g_usb_device_get_bus(priv->usb_device), g_usb_device_get_address(priv->usb_device), error_local->message); } } /* get product */ if (fu_device_get_name(device) == NULL) { idx = g_usb_device_get_product_index(priv->usb_device); if (idx != 0x00) { g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; tmp = g_usb_device_get_string_descriptor(priv->usb_device, idx, &error_local); if (tmp != NULL) fu_device_set_name(device, g_strchomp(tmp)); else g_debug("failed to load product string for usb device %u:%u: %s", g_usb_device_get_bus(priv->usb_device), g_usb_device_get_address(priv->usb_device), error_local->message); } } /* get serial number */ if (!fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER) && fu_device_get_serial(device) == NULL) { idx = g_usb_device_get_serial_number_index(priv->usb_device); if (idx != 0x00) { g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; tmp = g_usb_device_get_string_descriptor(priv->usb_device, idx, &error_local); if (tmp != NULL) fu_device_set_serial(device, g_strchomp(tmp)); else g_debug( "failed to load serial number string for usb device %u:%u: %s", g_usb_device_get_bus(priv->usb_device), g_usb_device_get_address(priv->usb_device), error_local->message); } } /* get the hub descriptor if this is a hub */ if (g_usb_device_get_device_class(priv->usb_device) == G_USB_DEVICE_CLASS_HUB) { if (!fu_usb_device_query_hub(self, error)) return FALSE; } #if G_USB_CHECK_VERSION(0, 4, 2) /* get the platform capability BOS descriptors */ bos_descriptors = g_usb_device_get_bos_descriptors(priv->usb_device, NULL); for (guint i = 0; bos_descriptors != NULL && i < bos_descriptors->len; i++) { GUsbBosDescriptor *bos = g_ptr_array_index(bos_descriptors, i); GBytes *extra = g_usb_bos_descriptor_get_extra(bos); if (g_usb_bos_descriptor_get_capability(bos) == 0x5 && g_bytes_get_size(extra) > 0) { g_autoptr(FuFirmware) ds20 = NULL; g_autoptr(GError) error_ds20 = NULL; ds20 = fu_firmware_new_from_gtypes(extra, FWUPD_INSTALL_FLAG_NONE, &error_ds20, FU_TYPE_USB_DEVICE_FW_DS20, FU_TYPE_USB_DEVICE_MS_DS20, G_TYPE_INVALID); if (ds20 == NULL) { g_warning("failed to parse platform capability BOS descriptor: %s", error_ds20->message); continue; } if (!fu_usb_device_ds20_apply_to_device(FU_USB_DEVICE_DS20(ds20), self, &error_ds20)) { g_warning("failed to get DS20 data: %s", error_ds20->message); continue; } if (g_getenv("FU_USB_DEVICE_DEBUG") != NULL) { g_autofree gchar *str = fu_firmware_to_string(ds20); g_debug("DS20: %s", str); } } } #endif #endif /* success */ return TRUE; } static gboolean fu_usb_device_ready(FuDevice *device, GError **error) { #ifdef HAVE_GUSB FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GPtrArray) intfs = NULL; /* get the interface GUIDs */ intfs = g_usb_device_get_interfaces(priv->usb_device, error); if (intfs == NULL) return FALSE; /* add fallback icon if there is nothing added already */ if (fu_device_get_icons(device)->len == 0) { for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); /* Video: Video Control: i.e. a webcam */ if (g_usb_interface_get_class(intf) == G_USB_DEVICE_CLASS_VIDEO && g_usb_interface_get_subclass(intf) == 0x01) { fu_device_add_icon(device, "camera-web"); } /* Audio */ if (g_usb_interface_get_class(intf) == G_USB_DEVICE_CLASS_AUDIO) fu_device_add_icon(device, "audio-card"); /* Mass Storage */ if (g_usb_interface_get_class(intf) == G_USB_DEVICE_CLASS_MASS_STORAGE) fu_device_add_icon(device, "drive-harddisk"); /* Printer */ if (g_usb_interface_get_class(intf) == G_USB_DEVICE_CLASS_PRINTER) fu_device_add_icon(device, "printer"); } } #endif /* success */ return TRUE; } static gboolean fu_usb_device_close(FuDevice *device, GError **error) { FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* already closed */ if (priv->usb_device_locker == NULL) return TRUE; #ifdef HAVE_GUSB /* release interfaces, ignoring errors */ for (guint i = 0; priv->interfaces != NULL && i < priv->interfaces->len; i++) { FuUsbDeviceInterface *iface = g_ptr_array_index(priv->interfaces, i); g_autoptr(GError) error_local = NULL; if (!iface->claimed) continue; if (!g_usb_device_release_interface(priv->usb_device, iface->number, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_INTERNAL)) { g_debug("failed to release interface 0x%02x: %s", iface->number, error_local->message); } else { g_warning("failed to release interface 0x%02x: %s", iface->number, error_local->message); } } iface->claimed = FALSE; } #endif g_clear_object(&priv->usb_device_locker); return TRUE; } static gboolean fu_usb_device_probe(FuDevice *device, GError **error) { #ifdef HAVE_GUSB FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); guint16 release; g_autofree gchar *platform_id = NULL; g_autofree gchar *vendor_id = NULL; g_autoptr(GPtrArray) intfs = NULL; /* set vendor ID */ vendor_id = g_strdup_printf("USB:0x%04X", g_usb_device_get_vid(priv->usb_device)); fu_device_add_vendor_id(device, vendor_id); /* set the version if the release has been set */ release = g_usb_device_get_release(priv->usb_device); if (release != 0x0 && fu_device_get_version_format(device) == FWUPD_VERSION_FORMAT_UNKNOWN) { fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_BCD); fu_device_set_version_from_uint16(device, release); } /* add GUIDs in order of priority */ fu_device_add_instance_u16(device, "VID", g_usb_device_get_vid(priv->usb_device)); fu_device_add_instance_u16(device, "PID", g_usb_device_get_pid(priv->usb_device)); fu_device_add_instance_u16(device, "REV", release); fu_device_build_instance_id_quirk(device, NULL, "USB", "VID", NULL); fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", NULL); fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", "REV", NULL); /* add the interface GUIDs */ intfs = g_usb_device_get_interfaces(priv->usb_device, error); if (intfs == NULL) return FALSE; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); fu_device_add_instance_u8(device, "CLASS", g_usb_interface_get_class(intf)); fu_device_add_instance_u8(device, "SUBCLASS", g_usb_interface_get_subclass(intf)); fu_device_add_instance_u8(device, "PROT", g_usb_interface_get_protocol(intf)); fu_device_build_instance_id_quirk(device, NULL, "USB", "CLASS", NULL); fu_device_build_instance_id_quirk(device, NULL, "USB", "CLASS", "SUBCLASS", NULL); fu_device_build_instance_id_quirk(device, NULL, "USB", "CLASS", "SUBCLASS", "PROT", NULL); } /* add 2 levels of parent IDs */ platform_id = g_strdup(g_usb_device_get_platform_id(priv->usb_device)); for (guint i = 0; i < 2; i++) { gchar *tok = g_strrstr(platform_id, ":"); if (tok == NULL) break; *tok = '\0'; if (g_strcmp0(platform_id, "usb") == 0) break; fu_device_add_parent_physical_id(device, platform_id); } #endif /* success */ return TRUE; } /** * fu_usb_device_get_vid: * @self: a #FuUsbDevice * * Gets the device vendor code. * * Returns: integer, or 0x0 if unset or invalid * * Since: 1.1.2 **/ guint16 fu_usb_device_get_vid(FuUsbDevice *self) { #ifdef HAVE_GUSB FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), 0x0000); if (priv->usb_device == NULL) return 0x0; return g_usb_device_get_vid(priv->usb_device); #else return 0x0; #endif } /** * fu_usb_device_get_pid: * @self: a #FuUsbDevice * * Gets the device product code. * * Returns: integer, or 0x0 if unset or invalid * * Since: 1.1.2 **/ guint16 fu_usb_device_get_pid(FuUsbDevice *self) { #ifdef HAVE_GUSB FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), 0x0000); if (priv->usb_device == NULL) return 0x0; return g_usb_device_get_pid(priv->usb_device); #else return 0x0; #endif } /** * fu_usb_device_get_platform_id: * @self: a #FuUsbDevice * * Gets the device platform ID. * * Returns: string, or NULL if unset or invalid * * Since: 1.1.2 **/ const gchar * fu_usb_device_get_platform_id(FuUsbDevice *self) { #ifdef HAVE_GUSB FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), NULL); if (priv->usb_device == NULL) return NULL; return g_usb_device_get_platform_id(priv->usb_device); #else return NULL; #endif } /** * fu_usb_device_get_spec: * @self: a #FuUsbDevice * * Gets the string USB revision for the device. * * Returns: a specification revision in BCD format, or 0x0 if not supported * * Since: 1.3.4 **/ guint16 fu_usb_device_get_spec(FuUsbDevice *self) { #if G_USB_CHECK_VERSION(0, 3, 1) FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), 0x0); if (priv->usb_device == NULL) return 0x0; return g_usb_device_get_spec(priv->usb_device); #else return 0x0; #endif } /** * fu_usb_device_set_dev: * @device: a #FuUsbDevice * @usb_device: (nullable): optional #GUsbDevice * * Sets the #GUsbDevice to use. * * Since: 1.0.2 **/ void fu_usb_device_set_dev(FuUsbDevice *device, GUsbDevice *usb_device) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); g_return_if_fail(FU_IS_USB_DEVICE(device)); /* need to re-probe hardware */ if (!fu_device_has_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_EMULATED)) fu_device_probe_invalidate(FU_DEVICE(device)); /* allow replacement */ g_set_object(&priv->usb_device, usb_device); if (usb_device == NULL) { g_clear_object(&priv->usb_device_locker); return; } #ifdef HAVE_GUSB #if G_USB_CHECK_VERSION(0, 4, 5) /* propagate emulated flag */ if (usb_device != NULL && g_usb_device_is_emulated(usb_device)) fu_device_add_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_EMULATED); #endif /* set device ID automatically */ fu_device_set_physical_id(FU_DEVICE(device), g_usb_device_get_platform_id(usb_device)); #endif } /** * fu_usb_device_find_udev_device: * @device: a #FuUsbDevice * @error: (nullable): optional return location for an error * * Gets the matching #GUdevDevice for the #GUsbDevice. * * Returns: (transfer full): a #GUdevDevice, or NULL if unset or invalid * * Since: 1.3.2 **/ GUdevDevice * fu_usb_device_find_udev_device(FuUsbDevice *device, GError **error) { #if defined(HAVE_GUDEV) && defined(HAVE_GUSB) FuUsbDevicePrivate *priv = GET_PRIVATE(device); g_autoptr(GList) devices = NULL; g_autoptr(GUdevClient) gudev_client = g_udev_client_new(NULL); g_return_val_if_fail(FU_IS_USB_DEVICE(device), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find all tty devices */ devices = g_udev_client_query_by_subsystem(gudev_client, "usb"); for (GList *l = devices; l != NULL; l = l->next) { GUdevDevice *dev = G_UDEV_DEVICE(l->data); /* check correct device */ if (g_udev_device_get_sysfs_attr_as_int(dev, "busnum") != g_usb_device_get_bus(priv->usb_device)) continue; if (g_udev_device_get_sysfs_attr_as_int(dev, "devnum") != g_usb_device_get_address(priv->usb_device)) continue; /* success */ g_debug("USB device %u:%u is %s", g_usb_device_get_bus(priv->usb_device), g_usb_device_get_address(priv->usb_device), g_udev_device_get_sysfs_path(dev)); return g_object_ref(dev); } /* failure */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "could not find sysfs device for %u:%u", g_usb_device_get_bus(priv->usb_device), g_usb_device_get_address(priv->usb_device)); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); #endif return NULL; } /** * fu_usb_device_get_dev: * @device: a #FuUsbDevice * * Gets the #GUsbDevice. * * Returns: (transfer none): a USB device, or %NULL * * Since: 1.0.2 **/ GUsbDevice * fu_usb_device_get_dev(FuUsbDevice *device) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); g_return_val_if_fail(FU_IS_USB_DEVICE(device), NULL); return priv->usb_device; } static void fu_usb_device_incorporate(FuDevice *self, FuDevice *donor) { g_return_if_fail(FU_IS_USB_DEVICE(self)); g_return_if_fail(FU_IS_USB_DEVICE(donor)); fu_usb_device_set_dev(FU_USB_DEVICE(self), fu_usb_device_get_dev(FU_USB_DEVICE(donor))); } static gboolean fu_udev_device_bind_driver(FuDevice *device, const gchar *subsystem, const gchar *driver, GError **error) { FuUsbDevice *self = FU_USB_DEVICE(device); g_autoptr(GUdevDevice) dev = NULL; g_autoptr(FuUdevDevice) udev_device = NULL; /* use udev for this */ dev = fu_usb_device_find_udev_device(self, error); if (dev == NULL) return FALSE; udev_device = fu_udev_device_new(fu_device_get_context(device), dev); return fu_device_bind_driver(FU_DEVICE(udev_device), subsystem, driver, error); } static gboolean fu_udev_device_unbind_driver(FuDevice *device, GError **error) { FuUsbDevice *self = FU_USB_DEVICE(device); g_autoptr(GUdevDevice) dev = NULL; g_autoptr(FuUdevDevice) udev_device = NULL; /* use udev for this */ dev = fu_usb_device_find_udev_device(self, error); if (dev == NULL) return FALSE; udev_device = fu_udev_device_new(fu_device_get_context(device), dev); return fu_device_unbind_driver(FU_DEVICE(udev_device), error); } /** * fu_usb_device_new: * @ctx: (nullable): a #FuContext * @usb_device: a USB device * * Creates a new #FuUsbDevice. * * Returns: (transfer full): a #FuUsbDevice * * Since: 1.8.2 **/ FuUsbDevice * fu_usb_device_new(FuContext *ctx, GUsbDevice *usb_device) { #if G_USB_CHECK_VERSION(0, 4, 3) if (usb_device != NULL && g_usb_device_has_tag(usb_device, "is-transient")) { g_critical("cannot use a device built using fu_udev_device_find_usb_device() as " "the GUsbContext is different"); return NULL; } #endif return g_object_new(FU_TYPE_USB_DEVICE, "context", ctx, "usb-device", usb_device, NULL); } #ifdef HAVE_GUSB static const gchar * fu_usb_device_class_code_to_string(GUsbDeviceClassCode code) { if (code == G_USB_DEVICE_CLASS_INTERFACE_DESC) return "interface-desc"; if (code == G_USB_DEVICE_CLASS_AUDIO) return "audio"; if (code == G_USB_DEVICE_CLASS_COMMUNICATIONS) return "communications"; if (code == G_USB_DEVICE_CLASS_HID) return "hid"; if (code == G_USB_DEVICE_CLASS_PHYSICAL) return "physical"; if (code == G_USB_DEVICE_CLASS_IMAGE) return "image"; if (code == G_USB_DEVICE_CLASS_PRINTER) return "printer"; if (code == G_USB_DEVICE_CLASS_MASS_STORAGE) return "mass-storage"; if (code == G_USB_DEVICE_CLASS_HUB) return "hub"; if (code == G_USB_DEVICE_CLASS_CDC_DATA) return "cdc-data"; if (code == G_USB_DEVICE_CLASS_SMART_CARD) return "smart-card"; if (code == G_USB_DEVICE_CLASS_CONTENT_SECURITY) return "content-security"; if (code == G_USB_DEVICE_CLASS_VIDEO) return "video"; if (code == G_USB_DEVICE_CLASS_PERSONAL_HEALTHCARE) return "personal-healthcare"; if (code == G_USB_DEVICE_CLASS_AUDIO_VIDEO) return "audio-video"; if (code == G_USB_DEVICE_CLASS_BILLBOARD) return "billboard"; if (code == G_USB_DEVICE_CLASS_DIAGNOSTIC) return "diagnostic"; if (code == G_USB_DEVICE_CLASS_WIRELESS_CONTROLLER) return "wireless-controller"; if (code == G_USB_DEVICE_CLASS_MISCELLANEOUS) return "miscellaneous"; if (code == G_USB_DEVICE_CLASS_APPLICATION_SPECIFIC) return "application-specific"; if (code == G_USB_DEVICE_CLASS_VENDOR_SPECIFIC) return "vendor-specific"; return NULL; } #endif static void fu_usb_device_to_string(FuDevice *device, guint idt, GString *str) { FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); if (priv->configuration > 0) fu_string_append_kx(str, idt, "Configuration", priv->configuration); for (guint i = 0; priv->interfaces != NULL && i < priv->interfaces->len; i++) { FuUsbDeviceInterface *iface = g_ptr_array_index(priv->interfaces, i); g_autofree gchar *tmp = g_strdup_printf("InterfaceNumber#%02x", iface->number); fu_string_append(str, idt, tmp, iface->claimed ? "claimed" : "released"); } #ifdef HAVE_GUSB if (priv->usb_device != NULL) { GUsbDeviceClassCode code = g_usb_device_get_device_class(priv->usb_device); fu_string_append(str, idt, "UsbDeviceClass", fu_usb_device_class_code_to_string(code)); } #endif } static void fu_usb_device_class_init(FuUsbDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *device_class = FU_DEVICE_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_usb_device_finalize; object_class->get_property = fu_usb_device_get_property; object_class->set_property = fu_usb_device_set_property; object_class->constructed = fu_usb_device_constructed; device_class->open = fu_usb_device_open; device_class->setup = fu_usb_device_setup; device_class->ready = fu_usb_device_ready; device_class->close = fu_usb_device_close; device_class->probe = fu_usb_device_probe; device_class->to_string = fu_usb_device_to_string; device_class->incorporate = fu_usb_device_incorporate; device_class->bind_driver = fu_udev_device_bind_driver; device_class->unbind_driver = fu_udev_device_unbind_driver; /** * FuUsbDevice:usb-device: * * The low-level #GUsbDevice. * * Since: 1.0.2 */ pspec = g_param_spec_object("usb-device", NULL, NULL, #ifdef HAVE_GUSB G_USB_TYPE_DEVICE, #else G_TYPE_OBJECT, #endif G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_USB_DEVICE, pspec); }