mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-06 14:32:56 +00:00
Add support for platform capability descriptors so devices can set quirks
This feature adds support for platform capability BOS descriptors which allows the device itself to ship quirk data. Use `sudo fwupdtool get-devices --save-backends=FILENAME` to save fake backend devices to a file. This allows easy creation of self tests that do not require physical hardware.
This commit is contained in:
parent
a8017e0a4f
commit
bfebede490
@ -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] = []
|
||||
|
75
contrib/generate-ds20.py
Executable file
75
contrib/generate-ds20.py
Executable file
@ -0,0 +1,75 @@
|
||||
#!/usr/bin/python3
|
||||
# pylint: disable=invalid-name,missing-docstring
|
||||
#
|
||||
# Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
|
||||
#
|
||||
# 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())
|
@ -62,6 +62,7 @@ _fwupdtool_opts=(
|
||||
'--no-safety-check'
|
||||
'--ignore-checksum'
|
||||
'--ignore-vid-pid'
|
||||
'--save-backends'
|
||||
)
|
||||
|
||||
_show_filters()
|
||||
|
135
docs/ds20.md
Normal file
135
docs/ds20.md
Normal file
@ -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`:
|
||||
|
||||
<firmware gtype="FuUsbDeviceFwDs20">
|
||||
<idx>42</idx> <!-- bVendorCode -->
|
||||
<size>32</size> <!-- wLength -->
|
||||
</firmware>
|
||||
|
||||
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.
|
@ -45,6 +45,7 @@ content_files = [
|
||||
"env.md",
|
||||
"tutorial.md",
|
||||
"hsi.md",
|
||||
"ds20.md",
|
||||
"bios-settings.md",
|
||||
]
|
||||
content_images = [
|
||||
|
@ -66,6 +66,15 @@
|
||||
<ul>
|
||||
<li><a href="libfwupdplugin/bios-settings.html">User documentation</a></li>
|
||||
</ul>
|
||||
<header>
|
||||
<h1>BOS DS20 Specification</h1>
|
||||
<div class="description">
|
||||
<p>The fwupd Binary Object Store descriptor specification</p>
|
||||
</div>
|
||||
</header>
|
||||
<ul>
|
||||
<li><a href="libfwupdplugin/ds20.html">Documentation</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
|
22
libfwupdplugin/fu-backend-private.h
Normal file
22
libfwupdplugin/fu-backend-private.h
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* 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);
|
@ -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
|
||||
|
@ -6,12 +6,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <json-glib/json-glib.h>
|
||||
|
||||
#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 *
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
317
libfwupdplugin/fu-usb-device-ds20.c
Normal file
317
libfwupdplugin/fu-usb-device-ds20.c
Normal file
@ -0,0 +1,317 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* 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;
|
||||
}
|
26
libfwupdplugin/fu-usb-device-ds20.h
Normal file
26
libfwupdplugin/fu-usb-device-ds20.h
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* 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);
|
113
libfwupdplugin/fu-usb-device-fw-ds20.c
Normal file
113
libfwupdplugin/fu-usb-device-fw-ds20.c
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* 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);
|
||||
}
|
19
libfwupdplugin/fu-usb-device-fw-ds20.h
Normal file
19
libfwupdplugin/fu-usb-device-fw-ds20.h
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* 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);
|
119
libfwupdplugin/fu-usb-device-ms-ds20.c
Normal file
119
libfwupdplugin/fu-usb-device-ms-ds20.c
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* 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);
|
||||
}
|
19
libfwupdplugin/fu-usb-device-ms-ds20.h
Normal file
19
libfwupdplugin/fu-usb-device-ms-ds20.h
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* 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);
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
]
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
*
|
||||
* <firmware gtype="FuUsbDeviceFwDs20">
|
||||
* <idx>42</idx> <!-- bVendorCode -->
|
||||
* <size>32</size> <!-- wLength -->
|
||||
* </firmware>
|
||||
*
|
||||
* 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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
49
src/tests/usb-devices-invalid.json
Normal file
49
src/tests/usb-devices-invalid.json
Normal file
@ -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="
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
110
src/tests/usb-devices.json
Normal file
110
src/tests/usb-devices.json
Normal file
@ -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="
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
[wrap-git]
|
||||
directory = gusb
|
||||
url = https://github.com/hughsie/libgusb.git
|
||||
revision = 0.3.10
|
||||
revision = 0.4.0
|
||||
|
Loading…
Reference in New Issue
Block a user