/* * 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-usb-device-private.h" /** * FuUsbDevice: * * A USB device. * * See also: [class@FuDevice], [class@FuHidDevice] */ typedef struct { GUsbDevice *usb_device; FuDeviceLocker *usb_device_locker; } FuUsbDevicePrivate; 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); G_OBJECT_CLASS(fu_usb_device_parent_class)->finalize(object); } static void fu_usb_device_init(FuUsbDevice *device) { #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 } /** * 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; } #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_autofree gchar *devid = 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_common_dump_raw(G_LOG_DOMAIN, "HUB_DT", data, sz); /* see http://www.usblyzer.com/usb-hub-class-decoder.htm */ if (sz == 0x09) { devid = g_strdup_printf("USB\\VID_%04X&PID_%04X&HUB_%02X", g_usb_device_get_vid(priv->usb_device), g_usb_device_get_pid(priv->usb_device), data[7]); fu_device_add_instance_id(FU_DEVICE(self), devid); } else if (sz == 0x0c) { devid = g_strdup_printf("USB\\VID_%04X&PID_%04X&HUB_%02X%02X", g_usb_device_get_vid(priv->usb_device), g_usb_device_get_pid(priv->usb_device), data[11], data[10]); fu_device_add_instance_id(FU_DEVICE(self), devid); } return TRUE; } #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); #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; 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; } #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 open */ if (priv->usb_device_locker == NULL) return TRUE; 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 *devid0 = NULL; g_autofree gchar *devid1 = NULL; g_autofree gchar *devid2 = NULL; 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) { g_autofree gchar *version = NULL; version = fu_common_version_from_uint16(release, FWUPD_VERSION_FORMAT_BCD); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_BCD); fu_device_set_version(device, version); } /* add GUIDs in order of priority */ devid2 = g_strdup_printf("USB\\VID_%04X&PID_%04X&REV_%04X", g_usb_device_get_vid(priv->usb_device), g_usb_device_get_pid(priv->usb_device), release); fu_device_add_instance_id(device, devid2); devid1 = g_strdup_printf("USB\\VID_%04X&PID_%04X", g_usb_device_get_vid(priv->usb_device), g_usb_device_get_pid(priv->usb_device)); fu_device_add_instance_id(device, devid1); devid0 = g_strdup_printf("USB\\VID_%04X", g_usb_device_get_vid(priv->usb_device)); fu_device_add_instance_id_full(device, devid0, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); /* 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); g_autofree gchar *intid1 = NULL; g_autofree gchar *intid2 = NULL; g_autofree gchar *intid3 = NULL; intid1 = g_strdup_printf("USB\\CLASS_%02X&SUBCLASS_%02X&PROT_%02X", g_usb_interface_get_class(intf), g_usb_interface_get_subclass(intf), g_usb_interface_get_protocol(intf)); fu_device_add_instance_id_full(device, intid1, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); intid2 = g_strdup_printf("USB\\CLASS_%02X&SUBCLASS_%02X", g_usb_interface_get_class(intf), g_usb_interface_get_subclass(intf)); fu_device_add_instance_id_full(device, intid2, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); intid3 = g_strdup_printf("USB\\CLASS_%02X", g_usb_interface_get_class(intf)); fu_device_add_instance_id_full(device, intid3, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* 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 */ 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 /* 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(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(dev); return fu_device_unbind_driver(FU_DEVICE(udev_device), error); } /** * fu_usb_device_new: * @usb_device: a USB device * * Creates a new #FuUsbDevice. * * Returns: (transfer full): a #FuUsbDevice * * Since: 1.0.2 **/ FuUsbDevice * fu_usb_device_new(GUsbDevice *usb_device) { FuUsbDevice *device = g_object_new(FU_TYPE_USB_DEVICE, NULL); fu_usb_device_set_dev(device, usb_device); return FU_USB_DEVICE(device); } #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) { #ifdef HAVE_GUSB FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); if (priv->usb_device != NULL) { GUsbDeviceClassCode code = g_usb_device_get_device_class(priv->usb_device); fu_common_string_append_kv(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; device_class->open = fu_usb_device_open; device_class->setup = fu_usb_device_setup; 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; 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); }