diff --git a/contrib/ci/oss-fuzz.py b/contrib/ci/oss-fuzz.py index 506481b8b..42352c701 100755 --- a/contrib/ci/oss-fuzz.py +++ b/contrib/ci/oss-fuzz.py @@ -329,6 +329,14 @@ def _build(bld: Builder) -> None: "PACKAGE_VERSION": "0.0.0", }, ) + bld.write_header( + "libfwupdplugin/fu-version.h", + { + "FU_MAJOR_VERSION": 0, + "FU_MINOR_VERSION": 0, + "FU_MICRO_VERSION": 0, + }, + ) # libfwupd + libfwupdplugin built_objs: List[str] = [] diff --git a/contrib/generate-ds20.py b/contrib/generate-ds20.py new file mode 100755 index 000000000..d3bdc9d65 --- /dev/null +++ b/contrib/generate-ds20.py @@ -0,0 +1,75 @@ +#!/usr/bin/python3 +# pylint: disable=invalid-name,missing-docstring +# +# Copyright (C) 2022 Richard Hughes +# +# SPDX-License-Identifier: LGPL-2.1+ +# +# pylint: disable=consider-using-f-string + +import sys +import argparse +import configparser +import base64 +from typing import List + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + parser.add_argument( + "-b", + "--bufsz", + type=int, + help="Buffer size in bytes", + ) + parser.add_argument("-i", "--instance-id", type=str, help="Device instance ID") + parser.add_argument("filename", action="store", type=str, help="Quirk filename") + + args = parser.parse_args() + if not args.bufsz: + parser.print_help() + sys.exit(1) + + config = configparser.ConfigParser() + config.optionxform = str + try: + config.read(args.filename) + except configparser.MissingSectionHeaderError: + print("Not a quirk file") + sys.exit(1) + + # fall back to the default if there is only one device in the quirk file + if not args.instance_id: + sections = config.sections() + if len(sections) != 1: + print("Multiple devices found, use --instance-id to choose between:") + for section in sections: + print(" • {}".format(section)) + sys.exit(1) + args.instance_id = sections[0] + + # create the smallest kv store possible + lines: List[str] = [] + try: + for key in config[args.instance_id]: + if key in ["Inhibit", "Issue"]: + print("WARNING: skipping key {}".format(key)) + continue + value = config[args.instance_id][key] + lines.append("{}={}".format(key, value)) + except KeyError: + print("No {} section".format(args.instance_id)) + sys.exit(1) + + # pad to the buffer size + buf: bytes = "\n".join(lines).encode() + if len(buf) > args.bufsz: + print("Quirk data is larger than bufsz") + sys.exit(1) + buf = buf.ljust(args.bufsz, b"\0") + + # success + print("DS20 descriptor control transfer data:") + print(" ".join(["{:02x}".format(val) for val in list(buf)])) + print(base64.b64encode(buf).decode()) diff --git a/data/bash-completion/fwupdtool b/data/bash-completion/fwupdtool index 47fad1a05..071d7384c 100644 --- a/data/bash-completion/fwupdtool +++ b/data/bash-completion/fwupdtool @@ -62,6 +62,7 @@ _fwupdtool_opts=( '--no-safety-check' '--ignore-checksum' '--ignore-vid-pid' + '--save-backends' ) _show_filters() diff --git a/docs/ds20.md b/docs/ds20.md new file mode 100644 index 000000000..a010c478c --- /dev/null +++ b/docs/ds20.md @@ -0,0 +1,135 @@ +# fwupd BOS DS20 Specification + +## Introduction + +When `fwupd` starts it enumerates all PCI and USB hardware and generates *Instance IDs* based on the reported vendor and product so that it can match the device to a plugin. +The plugin knows how to communicate with the device (for instance using vendor-specific USB control transfers) and also knows how to parse the firmware and deliver it to the device. + +Before a vendor can ship a firmware on the LVFS to a machine using Linux (or ChromeOS) the fwupd package often must be updated so that it knows what plugin to use for that specific device. +At this point other overridden [quirk data](https://fwupd.github.io/libfwupdplugin/class.Quirks.html) can also be set. For instance, the icon, long summary or even per-plugin flags that change device behavior. +For example `colorhug.quirk`: + + [USB\VID_273F&PID_1001] + Plugin = colorhug + Flags = self-recovery + Icon = colorimeter-colorhug + +Updating the fwupd binary package might take anywhere from a few weeks to several years due to various Linux distribution policies. +Clearly this isn't good when the majority of firmware updates are distributed to address security issues. + +One suggestion would be to put this quirk information into the existing update metadata provided by the configured remote, but this has several problems: + +* The daemon needs to *enumerate* hardware that does not have updates on the main LVFS remote. +* A *lot* of machines using fwupd have never connected to the internet and we still want to enumerate hardware for audit and verification purposes. +* Re-enumerating all physical hardware (to get the latest quirks) when updating the metadata is not straightforward. +* The VID/PID is not necessarily the information that the plugin matches to assign the firmware stream, and so this would have to be a separate facet of metadata -- which may have to include quirks too. + +Matching devices to plugins should be thought of as *orthogonal* to matching firmware to devices. +To simplify deployment it should be possible to allow the device itself to specify the plugin to use and optionally additional quirk data. + +## Specification + +Devices that implement a fwupd BOS DS20 descriptor can be updated without always needing to update the runtime version of fwupd for updated quirk entries, assuming the device is updatable by the existing vendor plugin. + +USB devices can create a new platform device capability BOS *“Binary Object Store”* descriptor with `bDevCapabilityType=0x05` and a fwupd-specific UUID, specifically `010aec63-f574-52cd-9dda-2852550d94f0`. +This UUID is generated type-5 SHA-1 hash of the word `fwupd` using a DNS namespace. +Using this custom UUID will ensure that Microsoft Windows does not try to parse the capability descriptor as a *Microsoft OS 2.0 descriptor set*. + +## Implementation + +Create a BOS DS20 descriptor such as: + + 1C 10 05 00 63 ec 0a 01 74 f5 cd 52 9d da 28 52 55 0d 94 f0 05 08 01 00 20 00 2a 00 + ├┘ ├┘ ├┘ ├┘ └─────────────┬───────────────────────────────┘ └─┬───────┘ └─┬─┘ ├┘ ├┘ + │ │ │ │ └─PlatformCapabilityUUID │ wLength─┘ │ │ + │ │ │ └─bReserved dwVersion─┘ bVendorCode─┘ │ + │ │ └────bDevCapability bAltEnumCmd────┘ + │ └───────bDescriptorType + └──────────bLength + +The `dwVersion` encoded here is fwupd `1.8.5`, which is also the first version that supports BOS DS20 descriptors. + +The BOS descriptors are sorted by the requester and can appear in any order. +The descriptor with the highest `dwVersion` that is not newer than the current running daemon version is used. +This means that `dwVersion` is effectively the minimum version of fwupd that should read the descriptor. +This allows devices to set different quirks depending on the fwupd version, although in practice this should not be required. +A suitable bVendorCode should be chosen that is not used for existing device operation. + +* The `dwVersion` parameter **must** be larger than `0x00010805`, i.e. 1.8.5. +* The `bAltEnumCmd` parameter **must** be zero. +* The `PlatformCapabilityUUID` **must** be `010aec63-f574-52cd-9dda-2852550d94f0`. + +Then allow the device to reply to a USB control request with the following parameters: + + transfer-direction: device-to-host + request-type: vendor + recipient: device + bRequest: {value of bVendorCode in the BOS descriptor} + wValue: 0x00 + wIndex: 0x07 + wLength: {value of wLength in the BOS descriptor} + +The device should return something like: + + 50 6c 75 67 69 6e 3d 64 66 75 0a 49 63 6f 6e 3d 63 6f 6d 70 75 74 65 72 0a 00 00 00 00 00 00 00 + +...which is the UTF-8 quirk data, e.g. + + Plugin=dfu + Icon=computer + +The UTF-8 quirk data must **not** contain Windows *CRLF-style* line endings. + +## Workflow + +To generate the fwupd DS20 descriptor save a file such as `fw-ds20.builder.xml`: + + + 42 + 32 + + +Then run `fwupdtool firmware-build fw-ds20.builder.xml fw-ds20.bin`. + +To generate the control transfer response, save a file such as `fw-ds20.quirk`: + + [USB\VID_273F&PID_1004] + Plugin = dfu + Icon = computer + +Then run `contrib/generate-ds20.py fw-ds20.quirk --bufsz 32`. +The maximum buffer size is typically hardcoded for the device and may be specified in a microcontroller datasheet. + +## Prior Art + +### Hardcoded Class & Subclass + +The fwupd project already support two plugins that use the USB class code, rather than the exact instance ID with the VID/PID. +For example, this DFU entry means *“match any USB device with class 0xFE (application specific) and subclass 0x01”*: + + [USB\CLASS_FE&SUBCLASS_01] + Plugin = dfu + +These kind of devices do not need any device to plugin mapping (although, they still might need a quirk if they are non-complaint in some way, for example needing `Flags = detach-for-attach`) – but in the most cases they just work. + +The same can be done for Fastboot devices, matching class `0xFF` (vendor specific), subclass `0x42` and protocol `0x03`, although there is the same caveat for non-compliant devices that need quirk entries like `FastbootOperationDelay = 250`: + + [USB\CLASS_FF&SUBCLASS_42&PROT_03] + Plugin = fastboot + +#### Microsoft OS Descriptors 1.0 + +The [Microsoft OS Descriptors 1.0 Specification](https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-1-0-descriptors-specification) defines a device-specific variable-length metadata block of `CompatibleID`:`SubCompatibleID` on a string index of `0xEE` where the `CompatibleID` and `SubCompatibleID` are also both hardcoded at 8 bytes. + +Using `FWUPDPLU` or `FWUPDFLA` as the `CompatibleID` would be acceptable, but we could not fit the plugin name (e.g. `logitech-bulkcontroller`) or the GUID (16 bytes) in an 8 byte `SubCompatibleID`. +Some non-compliant devices also hang and stop working when probing this specific string index. + +#### Microsoft OS Descriptors 2.0 + +The [Microsoft OS Descriptors 2.0 Specification](https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-2-0-descriptors-specification) is more useful as it defines a new device capability that can return a variable length vendor-defined section of data, using a UUID as a key. +Using the `BOS` *“Binary Object Store”* descriptor is only available for devices using USB specification version 2.1 and newer. +The BOS descriptor is used for Wireless USB details, USB 2.0 extensions, SuperSpeed USB connection details and a Container ID. + +The BOS descriptor does give the OS the ability to parse the platform capability, which is `bDevCapabilityType=0x05`. For UUID `D8DD60DF-4589-4CC7-9CD2-659D9E648A9F` this is identified as a structured blob of data Microsoft Windows uses for the suspend mode of the device. + +Creating a new `bDevCapabilityType` would allow vendors to store a binary blob (e.g. `Plugin=foobarbaz\nFlags=QuirkValueHere\n`) but that would be out-of-specification and difficult to implement. diff --git a/docs/fwupdplugin.toml.in b/docs/fwupdplugin.toml.in index c4bb07ff3..32a5ef5c0 100644 --- a/docs/fwupdplugin.toml.in +++ b/docs/fwupdplugin.toml.in @@ -45,6 +45,7 @@ content_files = [ "env.md", "tutorial.md", "hsi.md", + "ds20.md", "bios-settings.md", ] content_images = [ diff --git a/docs/index.html b/docs/index.html index bd95e9eef..3e58e7a2e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -66,6 +66,15 @@ +
+

BOS DS20 Specification

+
+

The fwupd Binary Object Store descriptor specification

+
+
+ diff --git a/libfwupdplugin/fu-backend-private.h b/libfwupdplugin/fu-backend-private.h new file mode 100644 index 000000000..497870fff --- /dev/null +++ b/libfwupdplugin/fu-backend-private.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-backend.h" + +gboolean +fu_backend_load(FuBackend *self, + JsonObject *json_object, + const gchar *tag, + FuBackendLoadFlags flags, + GError **error); +gboolean +fu_backend_save(FuBackend *self, + JsonBuilder *json_builder, + const gchar *tag, + FuBackendSaveFlags flags, + GError **error); diff --git a/libfwupdplugin/fu-backend.c b/libfwupdplugin/fu-backend.c index 377471a44..e8402228e 100644 --- a/libfwupdplugin/fu-backend.c +++ b/libfwupdplugin/fu-backend.c @@ -8,7 +8,7 @@ #include "config.h" -#include "fu-backend.h" +#include "fu-backend-private.h" #include "fu-string.h" /** @@ -220,6 +220,74 @@ fu_backend_setup(FuBackend *self, FuProgress *progress, GError **error) return TRUE; } +/** + * fu_backend_load: + * @self: a #FuBackend + * @json_object: a #JsonObject + * @tag: a string backend tag, or %NULL + * @flags: %FuBackendLoadFlags, typically `FU_BACKEND_LOAD_FLAG_NONE` + * @error: (nullable): optional return location for an error + * + * Loads the backend from a JSON object. + * + * Returns: %TRUE for success + * + * Since: 1.8.5 + **/ +gboolean +fu_backend_load(FuBackend *self, + JsonObject *json_object, + const gchar *tag, + FuBackendLoadFlags flags, + GError **error) +{ + FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); + + g_return_val_if_fail(FU_IS_BACKEND(self), FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + /* optional */ + if (klass->load != NULL) { + if (!klass->load(self, json_object, tag, flags, error)) + return FALSE; + } + return TRUE; +} + +/** + * fu_backend_save: + * @self: a #FuBackend + * @json_builder: a #JsonBuilder + * @tag: a string backend tag, or %NULL + * @flags: %FuBackendSaveFlags, typically `FU_BACKEND_SAVE_FLAG_NONE` + * @error: (nullable): optional return location for an error + * + * Saves the backend to a JSON builder. + * + * Returns: %TRUE for success + * + * Since: 1.8.5 + **/ +gboolean +fu_backend_save(FuBackend *self, + JsonBuilder *json_builder, + const gchar *tag, + FuBackendSaveFlags flags, + GError **error) +{ + FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); + + g_return_val_if_fail(FU_IS_BACKEND(self), FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + /* optional */ + if (klass->save != NULL) { + if (!klass->save(self, json_builder, tag, flags, error)) + return FALSE; + } + return TRUE; +} + /** * fu_backend_coldplug: * @self: a #FuBackend diff --git a/libfwupdplugin/fu-backend.h b/libfwupdplugin/fu-backend.h index b6c75425c..ccc75ffa6 100644 --- a/libfwupdplugin/fu-backend.h +++ b/libfwupdplugin/fu-backend.h @@ -6,12 +6,22 @@ #pragma once +#include + #include "fu-context.h" #include "fu-device.h" #define FU_TYPE_BACKEND (fu_backend_get_type()) G_DECLARE_DERIVABLE_TYPE(FuBackend, fu_backend, FU, BACKEND, GObject) +typedef enum { + FU_BACKEND_LOAD_FLAG_NONE, +} FuBackendLoadFlags; + +typedef enum { + FU_BACKEND_SAVE_FLAG_NONE, +} FuBackendSaveFlags; + struct _FuBackendClass { GObjectClass parent_class; /* signals */ @@ -24,8 +34,18 @@ struct _FuBackendClass { void (*registered)(FuBackend *self, FuDevice *device); void (*invalidate)(FuBackend *self); void (*to_string)(FuBackend *self, guint indent, GString *str); + gboolean (*load)(FuBackend *self, + JsonObject *json_object, + const gchar *tag, + FuBackendLoadFlags flags, + GError **error); + gboolean (*save)(FuBackend *self, + JsonBuilder *json_builder, + const gchar *tag, + FuBackendSaveFlags flags, + GError **error); /*< private >*/ - gpointer padding[26]; + gpointer padding[24]; }; const gchar * diff --git a/libfwupdplugin/fu-context.c b/libfwupdplugin/fu-context.c index b0aa42fe2..a3a17718f 100644 --- a/libfwupdplugin/fu-context.c +++ b/libfwupdplugin/fu-context.c @@ -21,6 +21,7 @@ */ typedef struct { + FuContextFlags flags; FuHwids *hwids; FuSmbios *smbios; FuQuirks *quirks; @@ -830,6 +831,42 @@ fu_context_set_battery_threshold(FuContext *self, guint battery_threshold) g_object_notify(G_OBJECT(self), "battery-threshold"); } +/** + * fu_context_add_flag: + * @context: a #FuContext + * @flag: the context flag + * + * Adds a specific flag to the context. + * + * Since: 1.8.5 + **/ +void +fu_context_add_flag(FuContext *context, FuContextFlags flag) +{ + FuContextPrivate *priv = GET_PRIVATE(context); + g_return_if_fail(FU_IS_CONTEXT(context)); + priv->flags |= flag; +} + +/** + * fu_context_has_flag: + * @context: a #FuContext + * @flag: the context flag + * + * Finds if the context has a specific flag. + * + * Returns: %TRUE if the flag is set + * + * Since: 1.8.5 + **/ +gboolean +fu_context_has_flag(FuContext *context, FuContextFlags flag) +{ + FuContextPrivate *priv = GET_PRIVATE(context); + g_return_val_if_fail(FU_IS_CONTEXT(context), FALSE); + return (priv->flags & flag) > 0; +} + static void fu_context_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { diff --git a/libfwupdplugin/fu-context.h b/libfwupdplugin/fu-context.h index 781795f25..53c4587f1 100644 --- a/libfwupdplugin/fu-context.h +++ b/libfwupdplugin/fu-context.h @@ -36,6 +36,36 @@ typedef void (*FuContextLookupIter)(FuContext *self, const gchar *value, gpointer user_data); +/** + * FU_CONTEXT_FLAG_NONE: + * + * No flags set. + * + * Since: 1.8.5 + **/ +#define FU_CONTEXT_FLAG_NONE (0u) + +/** + * FU_CONTEXT_FLAG_SAVE_EVENTS: + * + * Save events so that they can be replayed to emulate devices. + * + * Since: 1.8.5 + **/ +#define FU_CONTEXT_FLAG_SAVE_EVENTS (1u << 0) + +/** + * FuContextFlags: + * + * The context flags. + **/ +typedef guint64 FuContextFlags; + +void +fu_context_add_flag(FuContext *context, FuContextFlags flag); +gboolean +fu_context_has_flag(FuContext *context, FuContextFlags flag); + const gchar * fu_context_get_smbios_string(FuContext *self, guint8 structure_type, guint8 offset); guint diff --git a/libfwupdplugin/fu-usb-device-ds20.c b/libfwupdplugin/fu-usb-device-ds20.c new file mode 100644 index 000000000..eafc7630b --- /dev/null +++ b/libfwupdplugin/fu-usb-device-ds20.c @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#define G_LOG_DOMAIN "FuFirmware" + +#include "config.h" + +#include "fu-byte-array.h" +#include "fu-bytes.h" +#include "fu-dump.h" +#include "fu-mem.h" +#include "fu-usb-device-ds20.h" + +/** + * FuUsbDeviceDs20: + * + * A USB DS20 BOS descriptor. + * + * See also: [class@FuUsbDevice] + */ + +typedef struct { + guint32 version_lowest; +} FuUsbDeviceDs20Private; + +G_DEFINE_TYPE_WITH_PRIVATE(FuUsbDeviceDs20, fu_usb_device_ds20, FU_TYPE_FIRMWARE) +#define GET_PRIVATE(o) (fu_usb_device_ds20_get_instance_private(o)) + +typedef struct __attribute__((packed)) { + guint32 platform_ver; + guint16 total_length; + guint8 vendor_code; + guint8 alt_code; +} FuUsbDeviceDs20Item; + +/** + * fu_usb_device_ds20_set_version_lowest: + * @self: a #FuUsbDeviceDs20 + * @version_lowest: version number + * + * Sets the lowest possible `platform_ver` for a DS20 descriptor. + * + * Since: 1.8.5 + **/ +void +fu_usb_device_ds20_set_version_lowest(FuUsbDeviceDs20 *self, guint32 version_lowest) +{ + FuUsbDeviceDs20Private *priv = GET_PRIVATE(self); + g_return_if_fail(FU_IS_USB_DEVICE_DS20(self)); + priv->version_lowest = version_lowest; +} + +/** + * fu_usb_device_ds20_apply_to_device: + * @self: a #FuUsbDeviceDs20 + * @device: a #FuUsbDevice + * @error: (nullable): optional return location for an error + * + * Sets the DS20 descriptor onto @device. + * + * Returns: %TRUE for success + * + * Since: 1.8.5 + **/ +gboolean +fu_usb_device_ds20_apply_to_device(FuUsbDeviceDs20 *self, FuUsbDevice *device, GError **error) +{ +#ifdef HAVE_GUSB + FuUsbDeviceDs20Class *klass = FU_USB_DEVICE_DS20_GET_CLASS(self); + GUsbDevice *usb_device = fu_usb_device_get_dev(device); + gsize actual_length = 0; + gsize total_length = fu_firmware_get_size(FU_FIRMWARE(self)); + guint8 vendor_code = fu_firmware_get_idx(FU_FIRMWARE(self)); + g_autofree guint8 *buf = g_malloc0(total_length); + g_autoptr(GBytes) blob = NULL; + + g_return_val_if_fail(FU_IS_USB_DEVICE_DS20(self), FALSE); + g_return_val_if_fail(FU_IS_USB_DEVICE(device), FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + vendor_code, /* bRequest */ + 0x0, /* wValue */ + 0x07, /* wIndex */ + buf, + total_length, + &actual_length, + 500, + NULL, /* cancellable */ + error)) { + g_prefix_error(error, "requested vendor code 0x%02x: ", vendor_code); + return FALSE; + } + if (total_length != actual_length) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "expected 0x%x bytes from vendor code 0x%02x, but got 0x%x", + (guint)total_length, + vendor_code, + (guint)actual_length); + return FALSE; + } + + /* debug */ + if (g_getenv("FWUPD_VERBOSE") != NULL) + fu_dump_raw(G_LOG_DOMAIN, "PlatformCapabilityOs20", buf, actual_length); + + /* FuUsbDeviceDs20->parse */ + blob = g_bytes_new_take(g_steal_pointer(&buf), actual_length); + return klass->parse(self, blob, device, error); +#else + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "GUsb support is unavailable"); + return FALSE; +#endif +} + +static gboolean +fu_usb_device_ds20_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) +{ + fwupd_guid_t guid = {0x0}; + g_autofree gchar *guid_str = NULL; + + /* parse GUID */ + if (!fu_memcpy_safe((guint8 *)&guid, + sizeof(guid), + 0x0, /* dst */ + g_bytes_get_data(fw, NULL), + g_bytes_get_size(fw), + offset + 0x1, /* src */ + sizeof(guid), + error)) + return FALSE; + + /* matches the correct UUID */ + guid_str = fwupd_guid_to_string(&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN); + if (g_strcmp0(guid_str, fu_firmware_get_id(firmware)) != 0) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "invalid UUID for DS20, expected %s", + fu_firmware_get_id(firmware)); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gint +fu_usb_device_ds20_sort_by_platform_ver_cb(gconstpointer a, gconstpointer b) +{ + FuUsbDeviceDs20Item *ds1 = *((FuUsbDeviceDs20Item **)a); + FuUsbDeviceDs20Item *ds2 = *((FuUsbDeviceDs20Item **)b); + if (ds1->platform_ver < ds2->platform_ver) + return -1; + if (ds1->platform_ver > ds2->platform_ver) + return 1; + return 0; +} + +static gboolean +fu_usb_device_ds20_parse(FuFirmware *firmware, + GBytes *fw, + gsize offset, + FwupdInstallFlags flags, + GError **error) +{ + FuUsbDeviceDs20 *self = FU_USB_DEVICE_DS20(firmware); + FuUsbDeviceDs20Private *priv = GET_PRIVATE(self); + const guint8 *buf; + gsize bufsz = 0; + guint version_lowest = fu_firmware_get_version_raw(firmware); + g_autoptr(GBytes) blob = NULL; + g_autoptr(GPtrArray) dsinfos = g_ptr_array_new_with_free_func(g_free); + + /* cut out the data */ + blob = fu_bytes_new_offset(fw, + 1 + sizeof(fwupd_guid_t), + g_bytes_get_size(fw) - (1 + sizeof(fwupd_guid_t)), + error); + if (blob == NULL) + return FALSE; + buf = g_bytes_get_data(blob, &bufsz); + for (gsize off = 0; off < bufsz; off += sizeof(FuUsbDeviceDs20Item)) { + FuUsbDeviceDs20Item *dsinfo = g_new0(FuUsbDeviceDs20Item, 1); + guint16 total_length = 0; + guint32 platform_ver = 0; + + g_ptr_array_add(dsinfos, dsinfo); + if (!fu_memread_uint32_safe(buf, + bufsz, + off + + G_STRUCT_OFFSET(FuUsbDeviceDs20Item, platform_ver), + &platform_ver, + G_LITTLE_ENDIAN, + error)) + return FALSE; + if (!fu_memread_uint16_safe(buf, + bufsz, + off + + G_STRUCT_OFFSET(FuUsbDeviceDs20Item, total_length), + &total_length, + G_LITTLE_ENDIAN, + error)) + return FALSE; + if (!fu_memread_uint8_safe(buf, + bufsz, + off + G_STRUCT_OFFSET(FuUsbDeviceDs20Item, vendor_code), + &dsinfo->vendor_code, + error)) + return FALSE; + if (!fu_memread_uint8_safe(buf, + bufsz, + off + G_STRUCT_OFFSET(FuUsbDeviceDs20Item, alt_code), + &dsinfo->alt_code, + error)) + return FALSE; + dsinfo->platform_ver = platform_ver; + dsinfo->total_length = total_length; + g_debug("PlatformVersion=0x%08x, TotalLength=0x%04x, VendorCode=0x%02x, " + "AltCode=0x%02x", + dsinfo->platform_ver, + dsinfo->total_length, + dsinfo->vendor_code, + dsinfo->alt_code); + } + + /* sort by platform_ver, highest first */ + g_ptr_array_sort(dsinfos, fu_usb_device_ds20_sort_by_platform_ver_cb); + + /* find the newest info that's not newer than the lowest version */ + for (guint i = 0; i < dsinfos->len; i++) { + FuUsbDeviceDs20Item *dsinfo = g_ptr_array_index(dsinfos, i); + + /* not valid */ + if (dsinfo->platform_ver == 0x0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "invalid platform version 0x%08x", + dsinfo->platform_ver); + return FALSE; + } + if (dsinfo->platform_ver < priv->version_lowest) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "invalid platform version 0x%08x, expected >= 0x%08x", + dsinfo->platform_ver, + priv->version_lowest); + return FALSE; + } + + /* dwVersion is effectively the minimum version */ + if (dsinfo->platform_ver <= version_lowest) { + fu_firmware_set_size(firmware, dsinfo->total_length); + fu_firmware_set_idx(firmware, dsinfo->vendor_code); + return TRUE; + } + } + + /* failed */ + g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no supported platform version"); + return FALSE; +} + +static GBytes * +fu_usb_device_ds20_write(FuFirmware *firmware, GError **error) +{ + fwupd_guid_t guid = {0x0}; + g_autoptr(GByteArray) buf = g_byte_array_new(); + + /* bReserved */ + fu_byte_array_append_uint8(buf, 0x0); + + /* PlatformCapabilityUUID */ + if (!fwupd_guid_from_string(fu_firmware_get_id(firmware), + &guid, + FWUPD_GUID_FLAG_MIXED_ENDIAN, + error)) + return NULL; + g_byte_array_append(buf, (const guint8 *)&guid, sizeof(guid)); + + /* CapabilityData */ + fu_byte_array_append_uint32(buf, fu_firmware_get_version_raw(firmware), G_LITTLE_ENDIAN); + fu_byte_array_append_uint16(buf, fu_firmware_get_size(firmware), G_LITTLE_ENDIAN); + fu_byte_array_append_uint8(buf, fu_firmware_get_idx(firmware)); + fu_byte_array_append_uint8(buf, 0x0); /* AltCode */ + + /* success */ + return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); +} + +static void +fu_usb_device_ds20_init(FuUsbDeviceDs20 *self) +{ + fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); +} + +static void +fu_usb_device_ds20_class_init(FuUsbDeviceDs20Class *klass) +{ + FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); + klass_firmware->check_magic = fu_usb_device_ds20_check_magic; + klass_firmware->parse = fu_usb_device_ds20_parse; + klass_firmware->write = fu_usb_device_ds20_write; +} diff --git a/libfwupdplugin/fu-usb-device-ds20.h b/libfwupdplugin/fu-usb-device-ds20.h new file mode 100644 index 000000000..83e64b0bd --- /dev/null +++ b/libfwupdplugin/fu-usb-device-ds20.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-firmware.h" +#include "fu-usb-device.h" + +#define FU_TYPE_USB_DEVICE_DS20 (fu_usb_device_ds20_get_type()) +G_DECLARE_DERIVABLE_TYPE(FuUsbDeviceDs20, fu_usb_device_ds20, FU, USB_DEVICE_DS20, FuFirmware) + +struct _FuUsbDeviceDs20Class { + FuFirmwareClass parent_class; + gboolean (*parse)(FuUsbDeviceDs20 *self, GBytes *blob, FuUsbDevice *device, GError **error) + G_GNUC_WARN_UNUSED_RESULT; + /*< private >*/ + gpointer padding[30]; +}; + +void +fu_usb_device_ds20_set_version_lowest(FuUsbDeviceDs20 *self, guint32 version_lowest); +gboolean +fu_usb_device_ds20_apply_to_device(FuUsbDeviceDs20 *self, FuUsbDevice *device, GError **error); diff --git a/libfwupdplugin/fu-usb-device-fw-ds20.c b/libfwupdplugin/fu-usb-device-fw-ds20.c new file mode 100644 index 000000000..7f9b4061f --- /dev/null +++ b/libfwupdplugin/fu-usb-device-fw-ds20.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-device-private.h" +#include "fu-string.h" +#include "fu-usb-device-fw-ds20.h" +#include "fu-version.h" + +struct _FuUsbDeviceFwDs20 { + FuUsbDeviceDs20 parent_instance; +}; + +G_DEFINE_TYPE(FuUsbDeviceFwDs20, fu_usb_device_fw_ds20, FU_TYPE_USB_DEVICE_DS20) + +#define DS20_VERSION_LOWEST ((1u << 16) | (8u << 8) | 5u) +#define DS20_VERSION_CURRENT \ + ((((guint32)FU_MAJOR_VERSION) << 16) | (((guint32)FU_MINOR_VERSION) << 8) | \ + ((guint)FU_MICRO_VERSION)) + +static gboolean +fu_usb_device_fw_ds20_parse(FuUsbDeviceDs20 *self, + GBytes *blob, + FuUsbDevice *device, + GError **error) +{ + gsize bufsz = 0; + gsize bufsz_safe; + const guint8 *buf = g_bytes_get_data(blob, &bufsz); + g_auto(GStrv) lines = NULL; + + /* only accept Linux line-endings */ + if (g_strstr_len((const gchar *)buf, bufsz, "\r") != NULL) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "Windows line endings are not supported"); + return FALSE; + } + + /* find the first NUL, if one exists */ + for (bufsz_safe = 0; buf[bufsz_safe] != '\0' && bufsz_safe < bufsz; bufsz_safe++) + ; + if (!g_utf8_validate((const gchar *)buf, (gssize)bufsz_safe, NULL)) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "DS20 descriptor is not valid UTF-8"); + return FALSE; + } + + /* add payload for ->export() */ + fu_firmware_set_bytes(FU_FIRMWARE(self), blob); + + /* split into lines */ + lines = fu_strsplit((const gchar *)buf, bufsz_safe, "\n", -1); + for (guint i = 0; lines[i] != NULL; i++) { + g_auto(GStrv) kv = NULL; + if (lines[i][0] == '\0') + continue; + kv = g_strsplit(lines[i], "=", 2); + if (g_strv_length(kv) < 2) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "expected key=value for '%s'", + lines[i]); + return FALSE; + } + /* it's fine to be strict here, as we checked the fwupd version was new enough in + * FuUsbDeviceDs20Item */ + g_debug("setting ds20 device quirk '%s'='%s'", kv[0], kv[1]); + if (!fu_device_set_quirk_kv(FU_DEVICE(device), kv[0], kv[1], error)) + return FALSE; + } + + /* success */ + return TRUE; +} + +static void +fu_usb_device_fw_ds20_class_init(FuUsbDeviceFwDs20Class *klass) +{ + FuUsbDeviceDs20Class *usb_device_ds20_klass = FU_USB_DEVICE_DS20_CLASS(klass); + usb_device_ds20_klass->parse = fu_usb_device_fw_ds20_parse; +} + +static void +fu_usb_device_fw_ds20_init(FuUsbDeviceFwDs20 *self) +{ + fu_firmware_set_version_raw(FU_FIRMWARE(self), DS20_VERSION_CURRENT); + fu_usb_device_ds20_set_version_lowest(FU_USB_DEVICE_DS20(self), DS20_VERSION_LOWEST); + fu_firmware_set_id(FU_FIRMWARE(self), "010aec63-f574-52cd-9dda-2852550d94f0"); +} + +/** + * fu_usb_device_fw_ds20_new: + * + * Creates a new #FuUsbDeviceFwDs20. + * + * Returns: (transfer full): a #FuFirmware + * + * Since: 1.8.5 + **/ +FuFirmware * +fu_usb_device_fw_ds20_new(void) +{ + return g_object_new(FU_TYPE_USB_DEVICE_FW_DS20, NULL); +} diff --git a/libfwupdplugin/fu-usb-device-fw-ds20.h b/libfwupdplugin/fu-usb-device-fw-ds20.h new file mode 100644 index 000000000..3cab06c75 --- /dev/null +++ b/libfwupdplugin/fu-usb-device-fw-ds20.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-usb-device-ds20.h" + +#define FU_TYPE_USB_DEVICE_FW_DS20 (fu_usb_device_fw_ds20_get_type()) +G_DECLARE_FINAL_TYPE(FuUsbDeviceFwDs20, + fu_usb_device_fw_ds20, + FU, + USB_DEVICE_FW_DS20, + FuUsbDeviceDs20) + +FuFirmware * +fu_usb_device_fw_ds20_new(void); diff --git a/libfwupdplugin/fu-usb-device-ms-ds20.c b/libfwupdplugin/fu-usb-device-ms-ds20.c new file mode 100644 index 000000000..3ccae8f62 --- /dev/null +++ b/libfwupdplugin/fu-usb-device-ms-ds20.c @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-mem.h" +#include "fu-usb-device-ms-ds20.h" + +struct _FuUsbDeviceMsDs20 { + FuUsbDeviceDs20 parent_instance; +}; + +G_DEFINE_TYPE(FuUsbDeviceMsDs20, fu_usb_device_ms_ds20, FU_TYPE_USB_DEVICE_DS20) + +#define USB_OS_20_SET_HEADER_DESCRIPTOR 0x00 +#define USB_OS_20_SUBSET_HEADER_CONFIGURATION 0x01 +#define USB_OS_20_SUBSET_HEADER_FUNCTION 0x02 +#define USB_OS_20_FEATURE_COMPATBLE_ID 0x03 +#define USB_OS_20_FEATURE_REG_PROPERTY 0x04 +#define USB_OS_20_FEATURE_MIN_RESUME_TIME 0x05 +#define USB_OS_20_FEATURE_MODEL_ID 0x06 +#define USB_OS_20_FEATURE_CCGP_DEVICE 0x07 +#define USB_OS_20_FEATURE_VENDOR_REVISION 0x08 + +static const gchar * +fu_usb_device_os20_type_to_string(guint16 type) +{ + if (type == USB_OS_20_SET_HEADER_DESCRIPTOR) + return "set-header-descriptor"; + if (type == USB_OS_20_SUBSET_HEADER_CONFIGURATION) + return "subset-header-configuration"; + if (type == USB_OS_20_SUBSET_HEADER_FUNCTION) + return "subset-header-function"; + if (type == USB_OS_20_FEATURE_COMPATBLE_ID) + return "feature-compatble-id"; + if (type == USB_OS_20_FEATURE_REG_PROPERTY) + return "feature-reg-property"; + if (type == USB_OS_20_FEATURE_MIN_RESUME_TIME) + return "feature-min-resume-time"; + if (type == USB_OS_20_FEATURE_MODEL_ID) + return "feature-model-id"; + if (type == USB_OS_20_FEATURE_CCGP_DEVICE) + return "feature-ccgp-device"; + if (type == USB_OS_20_FEATURE_VENDOR_REVISION) + return "feature-vendor-revision"; + return NULL; +} + +static gboolean +fu_usb_device_ms_ds20_parse(FuUsbDeviceDs20 *self, + GBytes *blob, + FuUsbDevice *device, + GError **error) +{ + gsize bufsz = 0; + const guint8 *buf = g_bytes_get_data(blob, &bufsz); + + /* get length and type only */ + for (gsize offset = 0; offset < bufsz;) { + guint16 desc_sz = 0; + guint16 desc_type = 0; + if (!fu_memread_uint16_safe(buf, + bufsz, + offset + 0x0, + &desc_sz, + G_LITTLE_ENDIAN, + error)) + return FALSE; + if (desc_sz == 0) + break; + if (!fu_memread_uint16_safe(buf, + bufsz, + offset + 0x2, + &desc_type, + G_LITTLE_ENDIAN, + error)) + return FALSE; + g_debug("USB OS descriptor type 0x%04x [%s], length 0x%04x", + desc_type, + fu_usb_device_os20_type_to_string(desc_type), + desc_sz); + offset += desc_sz; + } + + /* success */ + return TRUE; +} + +static void +fu_usb_device_ms_ds20_class_init(FuUsbDeviceMsDs20Class *klass) +{ + FuUsbDeviceDs20Class *usb_device_ds20_klass = FU_USB_DEVICE_DS20_CLASS(klass); + usb_device_ds20_klass->parse = fu_usb_device_ms_ds20_parse; +} + +static void +fu_usb_device_ms_ds20_init(FuUsbDeviceMsDs20 *self) +{ + fu_firmware_set_version_raw(FU_FIRMWARE(self), 0x06030000); /* Windows 8.1 */ + fu_firmware_set_id(FU_FIRMWARE(self), "d8dd60df-4589-4cc7-9cd2-659d9e648a9f"); +} + +/** + * fu_usb_device_ms_ds20_new: + * + * Creates a new #FuUsbDeviceMsDs20. + * + * Returns: (transfer full): a #FuFirmware + * + * Since: 1.8.5 + **/ +FuFirmware * +fu_usb_device_ms_ds20_new(void) +{ + return g_object_new(FU_TYPE_USB_DEVICE_MS_DS20, NULL); +} diff --git a/libfwupdplugin/fu-usb-device-ms-ds20.h b/libfwupdplugin/fu-usb-device-ms-ds20.h new file mode 100644 index 000000000..9fe06055d --- /dev/null +++ b/libfwupdplugin/fu-usb-device-ms-ds20.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-usb-device-ds20.h" + +#define FU_TYPE_USB_DEVICE_MS_DS20 (fu_usb_device_ms_ds20_get_type()) +G_DECLARE_FINAL_TYPE(FuUsbDeviceMsDs20, + fu_usb_device_ms_ds20, + FU, + USB_DEVICE_MS_DS20, + FuUsbDeviceDs20) + +FuFirmware * +fu_usb_device_ms_ds20_new(void); diff --git a/libfwupdplugin/fu-usb-device.c b/libfwupdplugin/fu-usb-device.c index a1f93956f..c4752efa1 100644 --- a/libfwupdplugin/fu-usb-device.c +++ b/libfwupdplugin/fu-usb-device.c @@ -12,6 +12,8 @@ #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" /** @@ -274,6 +276,9 @@ fu_usb_device_setup(FuDevice *device, GError **error) 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); @@ -340,6 +345,41 @@ fu_usb_device_setup(FuDevice *device, GError **error) if (!fu_usb_device_query_hub(self, error)) return FALSE; } + +#if G_USB_CHECK_VERSION(0, 4, 0) + /* 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; + g_autofree gchar *str = 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; + } + str = fu_firmware_to_string(ds20); + g_debug("DS20: %s", str); + } + } +#endif #endif /* success */ diff --git a/libfwupdplugin/fwupdplugin.map b/libfwupdplugin/fwupdplugin.map index cd7481b46..6875d4c81 100644 --- a/libfwupdplugin/fwupdplugin.map +++ b/libfwupdplugin/fwupdplugin.map @@ -1098,6 +1098,10 @@ LIBFWUPDPLUGIN_1.8.4 { LIBFWUPDPLUGIN_1.8.5 { global: + fu_backend_load; + fu_backend_save; + fu_context_add_flag; + fu_context_has_flag; fu_device_set_quirk_kv; fu_intel_thunderbolt_firmware_get_type; fu_intel_thunderbolt_firmware_new; @@ -1111,5 +1115,12 @@ LIBFWUPDPLUGIN_1.8.5 { fu_intel_thunderbolt_nvm_is_native; fu_intel_thunderbolt_nvm_new; fu_kernel_get_cmdline; + fu_usb_device_ds20_apply_to_device; + fu_usb_device_ds20_get_type; + fu_usb_device_ds20_set_version_lowest; + fu_usb_device_fw_ds20_get_type; + fu_usb_device_fw_ds20_new; + fu_usb_device_ms_ds20_get_type; + fu_usb_device_ms_ds20_new; local: *; } LIBFWUPDPLUGIN_1.8.4; diff --git a/libfwupdplugin/meson.build b/libfwupdplugin/meson.build index a0b3b9fa6..9505104c5 100644 --- a/libfwupdplugin/meson.build +++ b/libfwupdplugin/meson.build @@ -78,6 +78,9 @@ fwupdplugin_src = [ 'fu-i2c-device.c', 'fu-mei-device.c', 'fu-usb-device.c', + 'fu-usb-device-ds20.c', # fuzzing + 'fu-usb-device-fw-ds20.c', # fuzzing + 'fu-usb-device-ms-ds20.c', # fuzzing 'fu-cfi-device.c', 'fu-hid-device.c', 'fu-linear-firmware.c', @@ -193,6 +196,7 @@ fu_hash = custom_target( fwupdplugin_headers_private = [ fu_hash, + 'fu-backend-private.h', 'fu-context-private.h', 'fu-device-private.h', 'fu-kenv.h', @@ -201,6 +205,9 @@ fwupdplugin_headers_private = [ 'fu-security-attrs-private.h', 'fu-smbios-private.h', 'fu-udev-device-private.h', + 'fu-usb-device-ds20.h', + 'fu-usb-device-fw-ds20.h', + 'fu-usb-device-ms-ds20.h', 'fu-usb-device-private.h', fwupdplugin_version_h, ] diff --git a/src/fu-engine.c b/src/fu-engine.c index 6a5231e55..e1cc1654f 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -37,6 +37,7 @@ #include "fwupd-resources.h" #include "fwupd-security-attr-private.h" +#include "fu-backend-private.h" #include "fu-bios-settings-private.h" #include "fu-cabinet.h" #include "fu-context-private.h" @@ -60,6 +61,8 @@ #include "fu-security-attr-common.h" #include "fu-security-attrs-private.h" #include "fu-udev-device-private.h" +#include "fu-usb-device-fw-ds20.h" +#include "fu-usb-device-ms-ds20.h" #include "fu-version.h" #ifdef HAVE_GUDEV @@ -7760,6 +7763,8 @@ fu_engine_load(FuEngine *self, FuEngineLoadFlags flags, FuProgress *progress, GE fu_context_add_firmware_gtype(self->ctx, "intel-thunderbolt-nvm", FU_TYPE_INTEL_THUNDERBOLT_NVM); + fu_context_add_firmware_gtype(self->ctx, "usb-device-fw-ds20", FU_TYPE_USB_DEVICE_FW_DS20); + fu_context_add_firmware_gtype(self->ctx, "usb-device-ms-ds20", FU_TYPE_USB_DEVICE_MS_DS20); /* we are emulating a different host */ if (host_emulate != NULL) { @@ -8033,6 +8038,22 @@ fu_engine_idle_status_notify_cb(FuIdle *idle, GParamSpec *pspec, FuEngine *self) fu_engine_set_status(self, status); } +gboolean +fu_engine_backends_save(FuEngine *self, JsonBuilder *json_builder, GError **error) +{ + json_builder_begin_object(json_builder); + json_builder_set_member_name(json_builder, "Backends"); + json_builder_begin_array(json_builder); + for (guint i = 0; i < self->backends->len; i++) { + FuBackend *backend = g_ptr_array_index(self->backends, i); + if (!fu_backend_save(backend, json_builder, NULL, FU_BACKEND_SAVE_FLAG_NONE, error)) + return FALSE; + } + json_builder_end_array(json_builder); + json_builder_end_object(json_builder); + return TRUE; +} + static void fu_engine_init(FuEngine *self) { diff --git a/src/fu-engine.h b/src/fu-engine.h index 8760a2d6b..133465a7e 100644 --- a/src/fu-engine.h +++ b/src/fu-engine.h @@ -248,3 +248,5 @@ fu_engine_modify_bios_settings(FuEngine *self, GHashTable *settings, gboolean force_ro, GError **error); +gboolean +fu_engine_backends_save(FuEngine *self, JsonBuilder *json_builder, GError **error); diff --git a/src/fu-self-test.c b/src/fu-self-test.c index 96c7efa1d..ac0face2e 100644 --- a/src/fu-self-test.c +++ b/src/fu-self-test.c @@ -16,6 +16,7 @@ #include "fwupd-bios-setting-private.h" #include "fwupd-security-attr-private.h" +#include "fu-backend-private.h" #include "fu-bios-settings-private.h" #include "fu-cabinet-common.h" #include "fu-config.h" @@ -32,6 +33,7 @@ #include "fu-security-attr-common.h" #include "fu-smbios-private.h" #include "fu-spawn.h" +#include "fu-usb-backend.h" typedef struct { FuPlugin *plugin; @@ -3176,6 +3178,157 @@ _plugin_device_register_cb(FuPlugin *plugin, FuDevice *device, gpointer user_dat fu_plugin_runner_device_register(plugin, device); } +/* + * To generate the fwupd DS20 descriptor in the usb-devices.json file save fw-ds20.builder.xml: + * + * + * 42 + * 32 + * + * + * Then run: + * + * fwupdtool firmware-build fw-ds20.builder.xml fw-ds20.bin + * base64 fw-ds20.bin + * + * To generate the fake control transfer response, save fw-ds20.quirk: + * + * [USB\VID_273F&PID_1004] + * Plugin = dfu + * Icon = computer + * + * Then run: + * + * contrib/generate-ds20.py fw-ds20.quirk --bufsz 32 + */ +static void +fu_backend_usb_func(gconstpointer user_data) +{ + FuTest *self = (FuTest *)user_data; + gboolean ret; + FuDevice *device_tmp; + g_autofree gchar *gusb_emulate_fn = NULL; + g_autofree gchar *devicestr = NULL; + g_autoptr(FuBackend) backend = fu_usb_backend_new(self->ctx); + g_autoptr(FuDeviceLocker) locker = NULL; + g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); + g_autoptr(GError) error = NULL; + g_autoptr(GPtrArray) devices = NULL; + g_autoptr(GPtrArray) possible_plugins = NULL; + g_autoptr(JsonParser) parser = json_parser_new(); + +#if !G_USB_CHECK_VERSION(0, 4, 0) + g_test_skip("GUsb version too old"); + return; +#endif + + /* load the JSON into the backend */ + gusb_emulate_fn = g_test_build_filename(G_TEST_DIST, "tests", "usb-devices.json", NULL); + ret = json_parser_load_from_file(parser, gusb_emulate_fn, &error); + g_assert_no_error(error); + g_assert_true(ret); + g_assert_cmpstr(fu_backend_get_name(backend), ==, "usb"); + g_assert_true(fu_backend_get_enabled(backend)); + ret = fu_backend_setup(backend, progress, &error); + g_assert_no_error(error); + g_assert_true(ret); + ret = fu_backend_load(backend, + json_node_get_object(json_parser_get_root(parser)), + NULL, + FU_BACKEND_LOAD_FLAG_NONE, + &error); + g_assert_no_error(error); + g_assert_true(ret); + ret = fu_backend_coldplug(backend, progress, &error); + g_assert_no_error(error); + g_assert_true(ret); + devices = fu_backend_get_devices(backend); + g_assert_cmpint(devices->len, ==, 1); + device_tmp = g_ptr_array_index(devices, 0); + fu_device_set_context(device_tmp, self->ctx); + locker = fu_device_locker_new(device_tmp, &error); + g_assert_no_error(error); + g_assert_nonnull(locker); + + /* for debugging */ + devicestr = fu_device_to_string(device_tmp); + g_debug("%s", devicestr); + + /* check the device was processed correctly by FuUsbDevice */ + g_assert_cmpstr(fu_device_get_name(device_tmp), ==, "ColorHug2"); + g_assert_true(fu_device_has_instance_id(device_tmp, "USB\\VID_273F&PID_1004&REV_0002")); + g_assert_true(fu_device_has_vendor_id(device_tmp, "USB:0x273F")); + + /* check the fwupd DS20 descriptor was parsed */ + g_assert_true(fu_device_has_icon(device_tmp, "computer")); + possible_plugins = fu_device_get_possible_plugins(device_tmp); + g_assert_cmpint(possible_plugins->len, ==, 1); + g_assert_cmpstr(g_ptr_array_index(possible_plugins, 0), ==, "dfu"); +} + +static void +fu_backend_usb_invalid_func(gconstpointer user_data) +{ + FuTest *self = (FuTest *)user_data; + gboolean ret; + FuDevice *device_tmp; + g_autofree gchar *gusb_emulate_fn = NULL; + g_autoptr(FuBackend) backend = fu_usb_backend_new(self->ctx); + g_autoptr(FuDeviceLocker) locker = NULL; + g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); + g_autoptr(GError) error = NULL; + g_autoptr(GPtrArray) devices = NULL; + g_autoptr(JsonParser) parser = json_parser_new(); + +#if !G_USB_CHECK_VERSION(0, 4, 0) + g_test_skip("GUsb version too old"); + return; +#endif + + /* load the JSON into the backend */ + gusb_emulate_fn = + g_test_build_filename(G_TEST_DIST, "tests", "usb-devices-invalid.json", NULL); + ret = json_parser_load_from_file(parser, gusb_emulate_fn, &error); + g_assert_no_error(error); + g_assert_true(ret); + ret = fu_backend_setup(backend, progress, &error); + g_assert_no_error(error); + g_assert_true(ret); + ret = fu_backend_load(backend, + json_node_get_object(json_parser_get_root(parser)), + NULL, + FU_BACKEND_LOAD_FLAG_NONE, + &error); + g_assert_no_error(error); + g_assert_true(ret); + ret = fu_backend_coldplug(backend, progress, &error); + g_assert_no_error(error); + g_assert_true(ret); + devices = fu_backend_get_devices(backend); + g_assert_cmpint(devices->len, ==, 1); + device_tmp = g_ptr_array_index(devices, 0); + fu_device_set_context(device_tmp, self->ctx); + + g_test_expect_message("FuUsbDevice", + G_LOG_LEVEL_WARNING, + "*invalid platform version 0x0000000a, expected >= 0x00010805*"); + g_test_expect_message("FuUsbDevice", + G_LOG_LEVEL_WARNING, + "failed to parse * BOS descriptor: did not find magic*"); + + locker = fu_device_locker_new(device_tmp, &error); + g_assert_no_error(error); + g_assert_nonnull(locker); + + /* check the device was processed correctly by FuUsbDevice */ + g_assert_cmpstr(fu_device_get_name(device_tmp), ==, "ColorHug2"); + g_assert_true(fu_device_has_instance_id(device_tmp, "USB\\VID_273F&PID_1004&REV_0002")); + g_assert_true(fu_device_has_vendor_id(device_tmp, "USB:0x273F")); + + /* check the fwupd DS20 descriptor was *not* parsed */ + g_assert_false(fu_device_has_icon(device_tmp, "computer")); +} + static void fu_plugin_module_func(gconstpointer user_data) { @@ -4689,6 +4842,8 @@ main(int argc, char **argv) g_test_add_data_func("/fwupd/progressbar", self, fu_progressbar_func); } g_test_add_data_func("/fwupd/plugin{build-hash}", self, fu_plugin_hash_func); + g_test_add_data_func("/fwupd/backend{usb}", self, fu_backend_usb_func); + g_test_add_data_func("/fwupd/backend{usb-invalid}", self, fu_backend_usb_invalid_func); g_test_add_data_func("/fwupd/plugin{module}", self, fu_plugin_module_func); g_test_add_data_func("/fwupd/memcpy", self, fu_memcpy_func); g_test_add_data_func("/fwupd/security-attr", self, fu_security_attr_func); diff --git a/src/fu-tool.c b/src/fu-tool.c index 62cecb288..992e64c45 100644 --- a/src/fu-tool.c +++ b/src/fu-tool.c @@ -3368,6 +3368,34 @@ fu_util_setup_interactive(FuUtilPrivate *priv, GError **error) return fu_util_setup_interactive_console(error); } +static gboolean +fu_util_backends_save(FuUtilPrivate *priv, const gchar *fn, GError **error) +{ + g_autofree gchar *data = NULL; + g_autoptr(JsonBuilder) json_builder = json_builder_new(); + g_autoptr(JsonGenerator) json_generator = NULL; + g_autoptr(JsonNode) json_root = NULL; + + /* export as a string */ + if (!fu_engine_backends_save(priv->engine, json_builder, error)) + return FALSE; + json_root = json_builder_get_root(json_builder); + json_generator = json_generator_new(); + json_generator_set_pretty(json_generator, TRUE); + json_generator_set_root(json_generator, json_root); + data = json_generator_to_data(json_generator, NULL); + if (data == NULL) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "Failed to convert to JSON string"); + return FALSE; + } + + /* save to file */ + return g_file_set_contents(fn, data, -1, error); +} + int main(int argc, char *argv[]) { @@ -3386,6 +3414,7 @@ main(int argc, char *argv[]) g_autoptr(GPtrArray) cmd_array = fu_util_cmd_array_new(); g_autofree gchar *cmd_descriptions = NULL; g_autofree gchar *filter = NULL; + g_autofree gchar *save_backends_fn = NULL; const GOptionEntry options[] = { {"version", '\0', @@ -3532,6 +3561,15 @@ main(int argc, char *argv[]) N_("Filter with a set of device flags using a ~ prefix to " "exclude, e.g. 'internal,~needs-reboot'"), NULL}, + {"save-backends", + '\0', + 0, + G_OPTION_ARG_STRING, + &save_backends_fn, + /* TRANSLATORS: command line option */ + N_("Specify a filename to use to save backend events"), + /* TRANSLATORS: filename argument with path */ + N_("FILENAME")}, {"json", '\0', 0, @@ -3968,6 +4006,10 @@ main(int argc, char *argv[]) /* load engine */ priv->engine = fu_engine_new(); + if (save_backends_fn != NULL) { + fu_context_add_flag(fu_engine_get_context(priv->engine), + FU_CONTEXT_FLAG_SAVE_EVENTS); + } g_signal_connect(FU_ENGINE(priv->engine), "device-request", G_CALLBACK(fu_util_update_device_request_cb), @@ -4026,6 +4068,12 @@ main(int argc, char *argv[]) return EXIT_FAILURE; } + /* dump devices */ + if (save_backends_fn != NULL && !fu_util_backends_save(priv, save_backends_fn, &error)) { + g_printerr("%s\n", error->message); + return EXIT_FAILURE; + } + /* a good place to do the traceback */ if (fu_progress_get_profile(priv->progress)) { g_autofree gchar *str = fu_progress_traceback(priv->progress); diff --git a/src/fu-usb-backend.c b/src/fu-usb-backend.c index ce3680d57..7c4a0692e 100644 --- a/src/fu-usb-backend.c +++ b/src/fu-usb-backend.c @@ -120,6 +120,9 @@ static gboolean fu_usb_backend_coldplug(FuBackend *backend, FuProgress *progress, GError **error) { FuUsbBackend *self = FU_USB_BACKEND(backend); +#if G_USB_CHECK_VERSION(0, 4, 0) + FuContext *ctx = fu_backend_get_context(backend); +#endif g_autoptr(GPtrArray) usb_devices = NULL; /* progress */ @@ -127,6 +130,12 @@ fu_usb_backend_coldplug(FuBackend *backend, FuProgress *progress, GError **error fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "enumerate"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 99, "add-devices"); +#if G_USB_CHECK_VERSION(0, 4, 0) + /* save events */ + if (fu_context_has_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS)) + g_usb_context_set_flags(self->usb_ctx, G_USB_CONTEXT_FLAGS_SAVE_EVENTS); +#endif + /* no insight */ g_usb_context_enumerate(self->usb_ctx); fu_progress_step_done(progress); @@ -148,6 +157,44 @@ fu_usb_backend_coldplug(FuBackend *backend, FuProgress *progress, GError **error return TRUE; } +static gboolean +fu_usb_backend_load(FuBackend *backend, + JsonObject *json_object, + const gchar *tag, + FuBackendLoadFlags flags, + GError **error) +{ +#if G_USB_CHECK_VERSION(0, 4, 0) + FuUsbBackend *self = FU_USB_BACKEND(backend); + return g_usb_context_load(self->usb_ctx, json_object, error); +#else + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "GUsb version too old to load backends"); + return FALSE; +#endif +} + +static gboolean +fu_usb_backend_save(FuBackend *backend, + JsonBuilder *json_builder, + const gchar *tag, + FuBackendSaveFlags flags, + GError **error) +{ +#if G_USB_CHECK_VERSION(0, 4, 0) + FuUsbBackend *self = FU_USB_BACKEND(backend); + return g_usb_context_save(self->usb_ctx, json_builder, error); +#else + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "GUsb version too old to save backends"); + return FALSE; +#endif +} + static void fu_usb_backend_registered(FuBackend *backend, FuDevice *device) { @@ -170,6 +217,7 @@ fu_usb_backend_finalize(GObject *object) FuUsbBackend *self = FU_USB_BACKEND(object); if (self->usb_ctx != NULL) { + g_signal_handlers_disconnect_by_data(G_USB_CONTEXT(self->usb_ctx), self); g_object_weak_unref(G_OBJECT(self->usb_ctx), fu_usb_backend_context_finalized_cb, self); @@ -191,6 +239,8 @@ fu_usb_backend_class_init(FuUsbBackendClass *klass) object_class->finalize = fu_usb_backend_finalize; klass_backend->setup = fu_usb_backend_setup; klass_backend->coldplug = fu_usb_backend_coldplug; + klass_backend->load = fu_usb_backend_load; + klass_backend->save = fu_usb_backend_save; klass_backend->registered = fu_usb_backend_registered; } diff --git a/src/tests/usb-devices-invalid.json b/src/tests/usb-devices-invalid.json new file mode 100644 index 000000000..23f524493 --- /dev/null +++ b/src/tests/usb-devices-invalid.json @@ -0,0 +1,49 @@ +{ + "UsbDevices": [ + { + "PlatformId": "usb:00", + "IdVendor": 10047, + "IdProduct": 4100, + "Device": 2, + "USB": 512, + "Manufacturer": 1, + "Product": 2, + "UsbBosDescriptors": [ + { + "Comment": "version invalid", + "DevCapabilityType": 5, + "ExtraData": "AGPsCgF09c1SndooUlUNlPAKAAAAIAAqAA==" + }, + { + "Comment": "UUID invalid", + "DevCapabilityType": 5, + "ExtraData": "AAAAAAAAAAAAAAAAAAAAAAAFCAEAIAAqAA==" + }, + { + "Comment": "plugin invalid", + "DevCapabilityType": 5, + "ExtraData": "AGPsCgF09c1SndooUlUNlPAFCAEAIAArAA==" + } + ], + "UsbEvents": [ + { + "Id": "GetStringDescriptor:DescIndex=0x02", + "Data": + "Q29sb3JIdWcyAEcAAAAAAACwA4pgfwAAAN+Vneb9GHkAAAAAAAAAAEA42QAAAAAAwHVdKPx/AAAAAAAAAAAAAJiCXSj8fwAAR9bLiWB/AADAdV0o/H8AAOrE/IlgfwAAgHW/AAAAAAAQw9UAAAAAAJiCXSj8fwAAytnLiQEAAAA=" + }, + { + "Comment": "Plugin=dfu\nIcon=computer\n", + "Id": + "ControlTransfer:Direction=0x00,RequestType=0x02,Recipient=0x00,Request=0x2a,Value=0x0000,Idx=0x0007,Data=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=,Length=0x20", + "Data": "UGx1Z2luPWRmdQpJY29uPWNvbXB1dGVyCgAAAAAAAAA=" + }, + { + "Comment": "Plugin=XXX", + "Id": + "ControlTransfer:Direction=0x00,RequestType=0x02,Recipient=0x00,Request=0x2b,Value=0x0000,Idx=0x0007,Data=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=,Length=0x20", + "Data": "UGx1Z2luPVhYWAoAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } + ] + } + ] +} diff --git a/src/tests/usb-devices.json b/src/tests/usb-devices.json new file mode 100644 index 000000000..671591f82 --- /dev/null +++ b/src/tests/usb-devices.json @@ -0,0 +1,110 @@ +{ + "UsbDevices": [ + { + "PlatformId": "usb:01:00:06", + "IdVendor": 10047, + "IdProduct": 4100, + "Device": 2, + "USB": 512, + "Manufacturer": 1, + "Product": 2, + "UsbBosDescriptors": [ + { + "DevCapabilityType": 5, + "ExtraData": "AN9g3diJRcdMnNJlnZ5kip8AAAMG4AQVAA==" + }, + { + "DevCapabilityType": 5, + "ExtraData": "AGPsCgF09c1SndooUlUNlPAFCAEAIAAqAA==" + }, + { + "DevCapabilityType": 17, + "ExtraData": "AQMAAAA=" + } + ], + "UsbInterfaces": [ + { + "Length": 9, + "DescriptorType": 4, + "InterfaceNumber": 1, + "InterfaceClass": 255, + "InterfaceSubClass": 70, + "InterfaceProtocol": 87, + "Interface": 3 + }, + { + "Length": 9, + "DescriptorType": 4, + "InterfaceNumber": 2, + "InterfaceClass": 255, + "InterfaceSubClass": 71, + "InterfaceProtocol": 85, + "Interface": 4 + }, + { + "Length": 9, + "DescriptorType": 4, + "InterfaceClass": 3, + "UsbEndpoints": [ + { + "DescriptorType": 5, + "EndpointAddress": 129, + "Interval": 1, + "MaxPacketSize": 64 + }, + { + "DescriptorType": 5, + "EndpointAddress": 1, + "Interval": 1, + "MaxPacketSize": 64 + } + ], + "ExtraData": "CSERAQABIh0A" + } + ], + "UsbEvents": [ + { + "Id": "GetStringDescriptor:DescIndex=0x01", + "Data": + "SHVnaHNraSBMdGQuAAAAAAAAAAAAAAAAIFjfAAAAAAAAAAAAAAAAAEA42QAAAAAAwHVdKPx/AAAAAAAAAAAAAJiCXSj8fwAAR9bLiWB/AADAdV0o/H8AAOrE/IlgfwAAgHW/AAAAAAAQw9UAAAAAAJiCXSj8fwAAytnLiQEAAAA=" + }, + { + "Id": "GetStringDescriptor:DescIndex=0x02", + "Data": + "Q29sb3JIdWcyAEcAAAAAAACwA4pgfwAAAN+Vneb9GHkAAAAAAAAAAEA42QAAAAAAwHVdKPx/AAAAAAAAAAAAAJiCXSj8fwAAR9bLiWB/AADAdV0o/H8AAOrE/IlgfwAAgHW/AAAAAAAQw9UAAAAAAJiCXSj8fwAAytnLiQEAAAA=" + }, + { + "Id": + "GetCustomIndex:ClassId=0xff,SubclassId=0x46,ProtocolId=0x57", + "Data": "Aw==" + }, + { + "Id": "GetStringDescriptor:DescIndex=0x03", + "Data": + "Mi4wLjcAAAAD0WmJYH8AAP8AAAAAAAAAA9FpiWB/AACQRNkAAAAAAGCj2wAAAAAAUHZdKPx/AACNC7qJYH8AAAMAAAAAAAAANougiWB/AACYgl0o/H8AAAAAAAAAAAAA/wAAAPx/V0Zgo9sAAAAAAEh5XSj8fwAAEMPVAAAAAAM=" + }, + { + "Id": + "GetCustomIndex:ClassId=0xff,SubclassId=0x47,ProtocolId=0x55", + "Data": "BA==" + }, + { + "Id": + "ControlTransfer:Direction=0x00,RequestType=0x02,Recipient=0x00,Request=0x15,Value=0x0000,Idx=0x0007,Data=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,Length=0x4e0", + "Data": + "CgAAAAAAAwbgBAgAAQAAANYECAACAAAAkgGAAAQAAQAoAFUAVgBDAC0ARgBTAFMAZQBuAHMAbwByAEcAcgBvAHUAcABJAEQAAABOAHsARgA2ADYARgBBADYANwA0AC0ARgAyAEIARQAtADQARAAxADkALQA5ADgAQwA1AC0ARAAxADMAMgAzADIAOQBDADYANAAyAEYAfQAAAF4ABAABACwAVQBWAEMALQBGAFMAUwBlAG4AcwBvAHIARwByAG8AdQBwAE4AYQBtAGUAAAAoAEwAZQBuAG8AdgBvACAAQwBhAG0AZQByAGEAIABHAHIAbwB1AHAAAAA8AAQABAAuAFUAVgBDAC0ARQBuAGEAYgBsAGUAUABsAGEAdABmAG8AcgBtAEQAbQBmAHQAAAAEAAEAAAA+AAQABAAwAEUAbgBhAGIAbABlAEQAcwBoAG8AdwBSAGUAZABpAHIAZQBjAHQAaQBvAG4AAAAAAAQAAQAAADIABAAEACQAVQBWAEMALQBDAFAAVgAyAEYAYQBjAGUAQQB1AHQAaAAAAAAABAD//wAACAACAAIAwAGAAAQAAQAoAFUAVgBDAC0ARgBTAFMAZQBuAHMAbwByAEcAcgBvAHUAcABJAEQAAABOAHsARgA2ADYARgBBADYANwA0AC0ARgAyAEIARQAtADQARAAxADkALQA5ADgAQwA1AC0ARAAxADMAMgAzADIAOQBDADYANAAyAEYAfQAAAF4ABAABACwAVQBWAEMALQBGAFMAUwBlAG4AcwBvAHIARwByAG8AdQBwAE4AYQBtAGUAAAAoAEwAZQBuAG8AdgBvACAAQwBhAG0AZQByAGEAIABHAHIAbwB1AHAAAAAwAAQABAAiAFMAZQBuAHMAbwByAEMAYQBtAGUAcgBhAE0AbwBkAGUAAAAEAAEAAAA6AAQABAAsAFMAawBpAHAAQwBhAG0AZQByAGEARQBuAHUAbQBlAHIAYQB0AGkAbwBuAAAABAABAAAAPgAEAAQAMABFAG4AYQBiAGwAZQBEAHMAaABvAHcAUgBlAGQAaQByAGUAYwB0AGkAbwBuAAAAAAAEAAEAAAAyAAQABAAkAFUAVgBDAC0AQwBQAFYAMgBGAGEAYwBlAEEAdQB0AGgAAAAAAAQAAAD//wgAAgAEAHwBMgAEAAQAJABEAGUAdgBpAGMAZQBJAGQAbABlAEUAbgBhAGIAbABlAGQAAAAEAAEAAAAyAAQABAAkAEQAZQBmAGEAdQBsAHQASQBkAGwAZQBTAHQAYQB0AGUAAAAAAAQAAQAAADYABAAEACgARABlAGYAYQB1AGwAdABJAGQAbABlAFQAaQBtAGUAbwB1AHQAAAAAAAQAiBMAAEYABAAEADgARABlAHYAaQBjAGUASQBkAGwAZQBJAGcAbgBvAHIAZQBXAGEAawBlAEUAbgBhAGIAbABlAAAAAAAEAAEAAAAUAAMAV0lOVVNCAAAAAAAAAAAAAIAABAABACgARABlAHYAaQBjAGUASQBuAHQAZQByAGYAYQBjAGUARwBVAEkARAAAAE4AewBlAGMAYwBlAGYAZgAzADUALQAxADQANgAzAC0ANABmAGYAMwAtAGEAYwBkADkALQA4AGYAOQA5ADIAZAAwADkAYQBjAGQAZAB9AAAA" + }, + { + "Id": + "ControlTransfer:Direction=0x00,RequestType=0x02,Recipient=0x00,Request=0x2a,Value=0x0000,Idx=0x0007,Data=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=,Length=0x20", + "Data": "UGx1Z2luPWRmdQpJY29uPWNvbXB1dGVyCgAAAAAAAAA=" + }, + { + "Id": "GetStringDescriptor:DescIndex=0x04", + "Data": + "MjA4MmI1ZTAtN2E2NC00NzhhLWIxYjItZTM0MDRmYWI2ZGFkAAAAAICg2QAAAAAAUHZdKPx/AACNC7qJYH8AAAQAAAAAAAAANougiWB/AAAAsAOKYH8AAAAAAAAAAAAA/wAAAAAAVUeAoNkAAAAAAFB2XSj8fwAAsKPbAAAAAAQ=" + } + ] + } + ] +} diff --git a/subprojects/gusb.wrap b/subprojects/gusb.wrap index f75aa5500..10558da8c 100644 --- a/subprojects/gusb.wrap +++ b/subprojects/gusb.wrap @@ -1,4 +1,4 @@ [wrap-git] directory = gusb url = https://github.com/hughsie/libgusb.git -revision = 0.3.10 +revision = 0.4.0