fwupd/libfwupdplugin/fu-usb-device-ds20.c
Richard Hughes bfebede490 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.
2022-09-13 12:07:35 +01:00

318 lines
8.6 KiB
C

/*
* 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;
}