cfu: Add an initial outline CFU plugin

There's no actual hardware to test this against yet, but this is how I
would lay out a plugin if there was.

We still need to work out a generic encapsulation for the offer and
payload (for each component and bank) so this can work with LVFS and
fwupd.
This commit is contained in:
Richard Hughes 2021-09-19 18:43:03 +01:00
parent d1dff82803
commit 8e24fa77cf
10 changed files with 621 additions and 0 deletions

View File

@ -415,6 +415,7 @@ done
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ata.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_bcm57xx.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ccgx.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cfu.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_colorhug.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cros_ec.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cpu.so

38
plugins/cfu/README.md Normal file
View File

@ -0,0 +1,38 @@
# Component Firmware update
## Introduction
CFU is a protocol from Microsoft to make it easy to install firmware on HID devices.
This protocol is unique in that it requires has a pre-download phase before sending the firmware to
the microcontroller. This is so the device can check if the firmware is required and compatible.
CFU also requires devices to be able to transfer the entire new transfer mode in runtime mode.
See <https://docs.microsoft.com/en-us/windows-hardware/drivers/cfu/cfu-specification> for more
details.
This plugin supports the following protocol ID:
* com.microsoft.cfu
## GUID Generation
These devices use standard USB DeviceInstanceId values, as well as two extra for the component ID
and the bank, e.g.
* `HIDRAW\VEN_17EF&DEV_7226&CID_01&BANK_1`
* `HIDRAW\VEN_17EF&DEV_7226&CID_01`
* `HIDRAW\VEN_17EF&DEV_7226`
## Update Behavior
The device has to support runtime updates and does not have a detach-into-bootloader mode -- but
after the install has completed the device still has to reboot into the new firmware.
## Vendor ID Security
The vendor ID is set from the USB vendor, in this instance set to `HIDRAW:0x17EF`
## External Interface Access
This plugin requires read/write access to `/dev/bus/usb`.

2
plugins/cfu/cfu.quirk Normal file
View File

@ -0,0 +1,2 @@
[USB\VID_273F&PID_100A]
Plugin = cfu

293
plugins/cfu/fu-cfu-device.c Normal file
View File

@ -0,0 +1,293 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-cfu-device.h"
#include "fu-cfu-module.h"
struct _FuCfuDevice {
FuHidDevice parent_instance;
guint8 protocol_version;
};
G_DEFINE_TYPE(FuCfuDevice, fu_cfu_device, FU_TYPE_HID_DEVICE)
#define FU_CFU_DEVICE_TIMEOUT 5000 /* ms */
#define FU_CFU_FEATURE_SIZE 60 /* bytes */
#define FU_CFU_CMD_GET_FIRMWARE_VERSION 0x00
#define FU_CFU_CMD_SEND_OFFER 0x00 // TODO
static void
fu_cfu_device_to_string(FuDevice *device, guint idt, GString *str)
{
FuCfuDevice *self = FU_CFU_DEVICE(device);
/* FuUdevDevice->to_string */
FU_DEVICE_CLASS(fu_cfu_device_parent_class)->to_string(device, idt, str);
fu_common_string_append_kx(str, idt, "ProtocolVersion", self->protocol_version);
}
static gboolean
fu_cfu_device_write_offer(FuCfuDevice *self,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
const guint8 *buf;
gsize bufsz = 0;
guint8 buf2[FU_CFU_FEATURE_SIZE] = {0};
g_autofree guint8 *buf_tmp = NULL;
g_autoptr(GBytes) blob = NULL;
/* generate a offer blob */
if (flags & FWUPD_INSTALL_FLAG_FORCE)
fu_cfu_offer_set_force_ignore_version(FU_CFU_OFFER(firmware), TRUE);
blob = fu_firmware_write(firmware, error);
if (blob == NULL)
return FALSE;
/* send it to the hardware */
buf = g_bytes_get_data(blob, &bufsz);
buf_tmp = fu_memdup_safe(buf, bufsz, error);
if (buf_tmp == NULL)
return FALSE;
if (!fu_hid_device_set_report(FU_HID_DEVICE(self),
FU_CFU_CMD_SEND_OFFER,
buf_tmp,
bufsz,
FU_CFU_DEVICE_TIMEOUT,
FU_HID_DEVICE_FLAG_IS_FEATURE,
error)) {
g_prefix_error(error, "failed to send offer: ");
return FALSE;
}
if (!fu_hid_device_get_report(FU_HID_DEVICE(self),
FU_CFU_CMD_SEND_OFFER,
buf2,
sizeof(buf2),
FU_CFU_DEVICE_TIMEOUT,
FU_HID_DEVICE_FLAG_IS_FEATURE,
error)) {
return FALSE;
}
g_debug("status:%s reject:%s",
fu_cfu_device_offer_to_string(buf2[13]),
fu_cfu_device_reject_to_string(buf2[9]));
if (buf2[13] != FU_CFU_DEVICE_OFFER_ACCEPT) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"not supported: %s",
fu_cfu_device_offer_to_string(buf2[13]));
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_cfu_device_write_payload(FuCfuDevice *self,
FuFirmware *firmware,
FuProgress *progress,
GError **error)
{
g_autoptr(GPtrArray) chunks = NULL;
/* write each chunk */
chunks = fu_firmware_get_chunks(firmware, error);
if (chunks == NULL)
return FALSE;
fu_progress_set_id(progress, G_STRLOC);
fu_progress_set_steps(progress, chunks->len);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index(chunks, i);
guint8 databuf[60] = {0};
guint8 buf2[60] = {0};
/* flags */
if (i == 0)
databuf[0] = FU_CFU_DEVICE_FLAG_FIRST_BLOCK;
else if (i == chunks->len - 1)
databuf[0] = FU_CFU_DEVICE_FLAG_LAST_BLOCK;
/* length */
databuf[1] = fu_chunk_get_data_sz(chk);
/* sequence number */
if (!fu_common_write_uint16_safe(databuf,
sizeof(databuf),
0x2,
i + 1,
G_LITTLE_ENDIAN,
error))
return FALSE;
/* address */
if (!fu_common_write_uint32_safe(databuf,
sizeof(databuf),
0x4,
fu_chunk_get_address(chk),
G_LITTLE_ENDIAN,
error))
return FALSE;
/* data */
if (!fu_memcpy_safe(databuf,
sizeof(databuf),
0x8, /* dst */
fu_chunk_get_data(chk),
fu_chunk_get_data_sz(chk),
0x0, /* src */
fu_chunk_get_data_sz(chk),
error)) {
g_prefix_error(error, "memory copy for payload fail: ");
return FALSE;
}
// send
// revc
if (buf2[5] != FU_CFU_DEVICE_STATUS_SUCCESS) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"failed to send chunk %u: %s",
i + 1,
fu_cfu_device_status_to_string(buf2[5]));
return FALSE;
}
fu_progress_step_done(progress);
}
/* success */
return TRUE;
}
static gboolean
fu_cfu_device_write_firmware(FuDevice *device,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
FuCfuDevice *self = FU_CFU_DEVICE(device);
g_autoptr(FuFirmware) fw_offer = NULL;
g_autoptr(FuFirmware) fw_payload = NULL;
/* progress */
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2); /* offer */
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98); /* payload */
/* send offer */
fw_offer = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_HEADER, error);
if (fw_offer == NULL)
return FALSE;
if (!fu_cfu_device_write_offer(self,
fw_offer,
fu_progress_get_child(progress),
flags,
error))
return FALSE;
fu_progress_step_done(progress);
/* send payload */
fw_payload = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_PAYLOAD, error);
if (fw_payload == NULL)
return FALSE;
if (!fu_cfu_device_write_payload(self, fw_payload, fu_progress_get_child(progress), error))
return FALSE;
fu_progress_step_done(progress);
/* success */
return TRUE;
}
static gboolean
fu_cfu_device_setup(FuDevice *device, GError **error)
{
FuCfuDevice *self = FU_CFU_DEVICE(device);
guint8 buf[FU_CFU_FEATURE_SIZE] = {0};
guint8 component_cnt = 0;
guint8 tmp = 0;
gsize offset = 0;
g_autoptr(GHashTable) modules_by_cid = NULL;
/* FuHidDevice->setup */
if (!FU_DEVICE_CLASS(fu_cfu_device_parent_class)->setup(device, error))
return FALSE;
/* get version */
if (!fu_hid_device_get_report(FU_HID_DEVICE(device),
FU_CFU_CMD_GET_FIRMWARE_VERSION,
buf,
sizeof(buf),
FU_CFU_DEVICE_TIMEOUT,
FU_HID_DEVICE_FLAG_IS_FEATURE,
error)) {
return FALSE;
}
if (!fu_common_read_uint8_safe(buf, sizeof(buf), 0x0, &component_cnt, error))
return FALSE;
if (!fu_common_read_uint8_safe(buf, sizeof(buf), 0x3, &tmp, error))
return FALSE;
self->protocol_version = tmp & 0b1111;
/* keep track of all modules so we can work out which are dual bank */
modules_by_cid = g_hash_table_new(g_int_hash, g_int_equal);
/* read each component module version */
offset += 4;
for (guint i = 0; i < component_cnt; i++) {
g_autoptr(FuCfuModule) module = fu_cfu_module_new(device);
FuCfuModule *module_tmp;
if (!fu_cfu_module_setup(module, buf, sizeof(buf), offset, error))
return FALSE;
fu_device_add_child(device, FU_DEVICE(module));
/* same module already exists, so mark both as being dual bank */
module_tmp =
g_hash_table_lookup(modules_by_cid,
GINT_TO_POINTER(fu_cfu_module_get_component_id(module)));
if (module_tmp != NULL) {
fu_device_add_flag(FU_DEVICE(module), FWUPD_DEVICE_FLAG_DUAL_IMAGE);
fu_device_add_flag(FU_DEVICE(module_tmp), FWUPD_DEVICE_FLAG_DUAL_IMAGE);
} else {
g_hash_table_insert(modules_by_cid,
GINT_TO_POINTER(fu_cfu_module_get_component_id(module)),
module);
}
/* done */
offset += 0x8;
}
/* success */
return TRUE;
}
static void
fu_cfu_device_init(FuCfuDevice *self)
{
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE);
}
static void
fu_cfu_device_class_init(FuCfuDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->setup = fu_cfu_device_setup;
klass_device->to_string = fu_cfu_device_to_string;
klass_device->write_firmware = fu_cfu_device_write_firmware;
}

View File

@ -0,0 +1,12 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#define FU_TYPE_CFU_DEVICE (fu_cfu_device_get_type())
G_DECLARE_FINAL_TYPE(FuCfuDevice, fu_cfu_device, FU, CFU_DEVICE, FuHidDevice)

200
plugins/cfu/fu-cfu-module.c Normal file
View File

@ -0,0 +1,200 @@
/*#
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-cfu-module.h"
struct _FuCfuModule {
FuDevice parent_instance;
guint8 component_id;
guint8 bank;
};
G_DEFINE_TYPE(FuCfuModule, fu_cfu_module, FU_TYPE_DEVICE)
static void
fu_cfu_module_to_string(FuDevice *device, guint idt, GString *str)
{
FuCfuModule *self = FU_CFU_MODULE(device);
fu_common_string_append_kx(str, idt, "ComponentId", self->component_id);
fu_common_string_append_kx(str, idt, "Bank", self->bank);
}
guint8
fu_cfu_module_get_component_id(FuCfuModule *self)
{
return self->component_id;
}
guint8
fu_cfu_module_get_bank(FuCfuModule *self)
{
return self->bank;
}
gboolean
fu_cfu_module_setup(FuCfuModule *self, const guint8 *buf, gsize bufsz, gsize offset, GError **error)
{
FuDevice *parent = fu_device_get_proxy(FU_DEVICE(self));
guint32 version_raw = 0;
guint8 tmp = 0;
g_autofree gchar *instance_id0 = NULL;
g_autofree gchar *instance_id1 = NULL;
g_autofree gchar *instance_id2 = NULL;
g_autofree gchar *logical_id = NULL;
g_autofree gchar *version = NULL;
/* component ID */
if (!fu_common_read_uint8_safe(buf, bufsz, offset + 0x5, &self->component_id, error))
return FALSE;
/* these GUIDs may cause the name or version-format to be overwritten */
instance_id0 = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X",
fu_udev_device_get_vendor(FU_UDEV_DEVICE(parent)),
fu_udev_device_get_model(FU_UDEV_DEVICE(parent)));
fu_device_add_instance_id(FU_DEVICE(self), instance_id0);
instance_id1 = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&CID_%02X",
fu_udev_device_get_vendor(FU_UDEV_DEVICE(parent)),
fu_udev_device_get_model(FU_UDEV_DEVICE(parent)),
self->component_id);
fu_device_add_instance_id(FU_DEVICE(self), instance_id1);
/* bank */
if (!fu_common_read_uint8_safe(buf, bufsz, offset + 0x4, &tmp, error))
return FALSE;
self->bank = tmp & 0b11;
instance_id2 = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&CID_%02X&BANK_%01X",
fu_udev_device_get_vendor(FU_UDEV_DEVICE(parent)),
fu_udev_device_get_model(FU_UDEV_DEVICE(parent)),
self->component_id,
self->bank);
fu_device_add_instance_id(FU_DEVICE(self), instance_id2);
/* set name, if not already set using a quirk */
if (fu_device_get_name(FU_DEVICE(self)) == NULL) {
g_autofree gchar *name = NULL;
name = g_strdup_printf("%s (0x%02X:0x%02x)",
fu_device_get_name(parent),
self->component_id,
self->bank);
fu_device_set_name(FU_DEVICE(self), name);
}
/* version */
if (!fu_common_read_uint32_safe(buf, bufsz, offset, &version_raw, G_LITTLE_ENDIAN, error))
return FALSE;
fu_device_set_version_raw(FU_DEVICE(self), version_raw);
version = fu_common_version_from_uint32(version_raw,
fu_device_get_version_format(FU_DEVICE(self)));
/* logical ID */
logical_id = g_strdup_printf("CID:0x%02x,BANK:0x%02x", self->component_id, self->bank);
fu_device_set_logical_id(FU_DEVICE(self), logical_id);
/* success */
return TRUE;
}
static FuFirmware *
fu_cfu_module_prepare_firmware(FuDevice *device,
GBytes *fw,
FwupdInstallFlags flags,
GError **error)
{
g_autoptr(FuFirmware) firmware = fu_firmware_new();
g_autoptr(FuFirmware) offer = fu_cfu_offer_new();
g_autoptr(FuFirmware) payload = fu_cfu_payload_new();
g_autoptr(GBytes) fw_offset = NULL;
/* offer */
if (!fu_firmware_parse(offer, fw, flags, error))
return NULL;
fu_firmware_set_id(offer, FU_FIRMWARE_ID_HEADER);
fu_firmware_add_image(firmware, offer);
/* payload */
fw_offset = fu_common_bytes_new_offset(fw, 0x10, g_bytes_get_size(fw) - 0x10, error);
if (fw_offset == NULL)
return NULL;
if (!fu_firmware_parse(payload, fw_offset, flags, error))
return NULL;
fu_firmware_set_id(payload, FU_FIRMWARE_ID_PAYLOAD);
fu_firmware_add_image(firmware, payload);
/* success */
return g_steal_pointer(&firmware);
}
static gboolean
fu_cfu_module_write_firmware(FuDevice *device,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
FuDevice *proxy;
g_autoptr(GBytes) fw = NULL;
/* get the whole image */
fw = fu_firmware_get_bytes(firmware, error);
if (fw == NULL)
return FALSE;
/* process by the parent */
proxy = fu_device_get_proxy(device);
if (proxy == NULL) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"no proxy device assigned");
return FALSE;
}
return fu_device_write_firmware(proxy, fw, progress, flags, error);
}
static void
fu_cfu_module_set_progress(FuDevice *self, FuProgress *progress)
{
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2); /* detach */
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 96); /* write */
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2); /* attach */
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2); /* reload */
}
static void
fu_cfu_module_init(FuCfuModule *self)
{
fu_device_add_protocol(FU_DEVICE(self), "com.microsoft.cfu");
fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD);
}
static void
fu_cfu_module_class_init(FuCfuModuleClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->to_string = fu_cfu_module_to_string;
klass_device->prepare_firmware = fu_cfu_module_prepare_firmware;
klass_device->write_firmware = fu_cfu_module_write_firmware;
klass_device->set_progress = fu_cfu_module_set_progress;
}
FuCfuModule *
fu_cfu_module_new(FuDevice *parent)
{
FuCfuModule *self;
self = g_object_new(FU_TYPE_CFU_MODULE,
"ctx",
fu_device_get_context(parent),
"proxy",
parent,
NULL);
return self;
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#define FU_TYPE_CFU_MODULE (fu_cfu_module_get_type())
G_DECLARE_FINAL_TYPE(FuCfuModule, fu_cfu_module, FU, CFU_MODULE, FuDevice)
guint8
fu_cfu_module_get_component_id(FuCfuModule *self);
guint8
fu_cfu_module_get_bank(FuCfuModule *self);
gboolean
fu_cfu_module_setup(FuCfuModule *self,
const guint8 *buf,
gsize bufsz,
gsize offset,
GError **error);
FuCfuModule *
fu_cfu_module_new(FuDevice *parent);

View File

@ -0,0 +1,18 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-cfu-device.h"
void
fu_plugin_init(FuPlugin *plugin)
{
fu_plugin_set_build_hash(plugin, FU_BUILD_HASH);
fu_plugin_add_device_gtype(plugin, FU_TYPE_CFU_DEVICE);
}

31
plugins/cfu/meson.build Normal file
View File

@ -0,0 +1,31 @@
cargs = ['-DG_LOG_DOMAIN="FuPluginCfu"']
install_data([
'cfu.quirk',
],
install_dir: join_paths(datadir, 'fwupd', 'quirks.d')
)
shared_module('fu_plugin_cfu',
fu_hash,
sources : [
'fu-cfu-device.c',
'fu-cfu-module.c',
'fu-plugin-cfu.c',
],
include_directories : [
root_incdir,
fwupd_incdir,
fwupdplugin_incdir,
],
install : true,
install_dir: plugin_dir,
link_with : [
fwupd,
fwupdplugin,
],
c_args : cargs,
dependencies : [
plugin_deps,
],
)

View File

@ -8,6 +8,7 @@ subdir('ata')
subdir('bcm57xx')
subdir('bios')
subdir('ccgx')
subdir('cfu')
subdir('colorhug')
subdir('cpu')
subdir('cros-ec')