fwupd/plugins/fastboot/fu-fastboot-device.c
Richard Hughes cddf5b5b89 Only auto-add counterpart GUIDs when required
Doing this unconditionally means we accidentally 'bleed' one device mode into
another in a non-obvious way. For instance, a device might have two operating
modes with different GUIDs. If firmware is supplied for both modes in the same
cabinet archive then we might accidentally match the 'wrong' firmware when
the daemon has observed a mode switch and added the counterpart GUIDs.

We only really need the counterpart GUIDs when switching between Jabra, 8bitdo
and DFU devices where the DFU bootloader VID:PID is not manually tagged with
`CounterpartGuid` in a quirk file. In the general case lets keep it simple to
avoid difficult to find bugs.
2020-04-08 13:55:39 +01:00

720 lines
20 KiB
C

/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include <xmlb.h>
#include "fu-archive.h"
#include "fu-chunk.h"
#include "fu-fastboot-device.h"
#define FASTBOOT_REMOVE_DELAY_RE_ENUMERATE 60000 /* ms */
#define FASTBOOT_TRANSACTION_TIMEOUT 1000 /* ms */
#define FASTBOOT_TRANSACTION_RETRY_MAX 600
#define FASTBOOT_EP_IN 0x81
#define FASTBOOT_EP_OUT 0x01
#define FASTBOOT_CMD_BUFSZ 64 /* bytes */
struct _FuFastbootDevice {
FuUsbDevice parent_instance;
gboolean secure;
guint blocksz;
guint8 intf_nr;
};
G_DEFINE_TYPE (FuFastbootDevice, fu_fastboot_device, FU_TYPE_USB_DEVICE)
static void
fu_fastboot_device_to_string (FuDevice *device, guint idt, GString *str)
{
FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
fu_common_string_append_kx (str, idt, "InterfaceNumber", self->intf_nr);
fu_common_string_append_kx (str, idt, "BlockSize", self->blocksz);
fu_common_string_append_kb (str, idt, "Secure", self->secure);
}
static gboolean
fu_fastboot_device_probe (FuDevice *device, GError **error)
{
FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
g_autoptr(GUsbInterface) intf = NULL;
/* find the correct fastboot interface */
intf = g_usb_device_get_interface (usb_device, 0xff, 0x42, 0x03, error);
if (intf == NULL)
return FALSE;
self->intf_nr = g_usb_interface_get_number (intf);
return TRUE;
}
static gboolean
fu_fastboot_device_open (FuUsbDevice *device, GError **error)
{
FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
GUsbDevice *usb_device = fu_usb_device_get_dev (device);
if (!g_usb_device_claim_interface (usb_device, self->intf_nr,
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
error)) {
g_prefix_error (error, "failed to claim interface: ");
return FALSE;
}
/* success */
return TRUE;
}
static void
fu_fastboot_buffer_dump (const gchar *title, const guint8 *buf, gsize sz)
{
if (g_getenv ("FWUPD_FASTBOOT_VERBOSE") == NULL)
return;
g_print ("%s (%" G_GSIZE_FORMAT "):\n", title, sz);
for (gsize i = 0; i < sz; i++) {
g_print ("%02x[%c] ", buf[i], g_ascii_isprint (buf[i]) ? buf[i] : '?');
if (i > 0 && (i + 1) % 256 == 0)
g_print ("\n");
}
g_print ("\n");
}
static gboolean
fu_fastboot_device_write (FuDevice *device, const guint8 *buf, gsize buflen, GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
gboolean ret;
gsize actual_len = 0;
g_autofree guint8 *buf2 = g_memdup (buf, (guint) buflen);
fu_fastboot_buffer_dump ("writing", buf, buflen);
ret = g_usb_device_bulk_transfer (usb_device,
FASTBOOT_EP_OUT,
buf2,
buflen,
&actual_len,
FASTBOOT_TRANSACTION_TIMEOUT,
NULL, error);
if (!ret) {
g_prefix_error (error, "failed to do bulk transfer: ");
return FALSE;
}
if (actual_len != buflen) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"only wrote %" G_GSIZE_FORMAT "bytes", actual_len);
return FALSE;
}
return TRUE;
}
static gboolean
fu_fastboot_device_writestr (FuDevice *device, const gchar *str, GError **error)
{
gsize buflen = strlen (str);
if (buflen > FASTBOOT_CMD_BUFSZ - 4) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"fastboot limits writes to %i bytes",
FASTBOOT_CMD_BUFSZ - 4);
return FALSE;
}
return fu_fastboot_device_write (device, (const guint8 *) str, buflen, error);
}
typedef enum {
FU_FASTBOOT_DEVICE_READ_FLAG_NONE,
FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL,
} FuFastbootDeviceReadFlags;
static gboolean
fu_fastboot_device_read (FuDevice *device,
gchar **str,
FuFastbootDeviceReadFlags flags,
GError **error)
{
FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
guint retries = 1;
/* these commands may return INFO or take some time to complete */
if (flags & FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL)
retries = FASTBOOT_TRANSACTION_RETRY_MAX;
for (guint i = 0; i < retries; i++) {
gboolean ret;
gsize actual_len = 0;
guint8 buf[FASTBOOT_CMD_BUFSZ] = { 0x00 };
g_autofree gchar *tmp = NULL;
g_autoptr(GError) error_local = NULL;
ret = g_usb_device_bulk_transfer (usb_device,
FASTBOOT_EP_IN,
buf,
sizeof(buf),
&actual_len,
FASTBOOT_TRANSACTION_TIMEOUT,
NULL, &error_local);
if (!ret) {
if (g_error_matches (error_local,
G_USB_DEVICE_ERROR,
G_USB_DEVICE_ERROR_TIMED_OUT)) {
g_debug ("ignoring %s", error_local->message);
continue;
}
g_propagate_prefixed_error (error,
g_steal_pointer (&error_local),
"failed to do bulk transfer: ");
return FALSE;
}
fu_fastboot_buffer_dump ("read", buf, actual_len);
if (actual_len < 4) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"only read %" G_GSIZE_FORMAT "bytes", actual_len);
return FALSE;
}
/* info */
tmp = g_strndup ((const gchar *) buf + 4, self->blocksz - 4);
if (memcmp (buf, "INFO", 4) == 0) {
if (g_strcmp0 (tmp, "erasing flash") == 0)
fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE);
else if (g_strcmp0 (tmp, "writing flash") == 0)
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
else
g_debug ("INFO returned unknown: %s", tmp);
continue;
}
/* success */
if (memcmp (buf, "OKAY", 4) == 0 || memcmp (buf, "DATA", 4) == 0) {
if (str != NULL)
*str = g_steal_pointer (&tmp);
return TRUE;
}
/* failure */
if (memcmp (buf, "FAIL", 4) == 0) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"failed to read response: %s", tmp);
return FALSE;
}
/* unknown failure */
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"failed to read response");
return FALSE;
}
/* we timed out a *lot* */
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"no response to read");
return FALSE;
}
static gboolean
fu_fastboot_device_getvar (FuDevice *device, const gchar *key, gchar **str, GError **error)
{
g_autofree gchar *tmp = g_strdup_printf ("getvar:%s", key);
if (!fu_fastboot_device_writestr (device, tmp, error))
return FALSE;
if (!fu_fastboot_device_read (device, str,
FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error))
return FALSE;
return TRUE;
}
static gboolean
fu_fastboot_device_cmd (FuDevice *device, const gchar *cmd,
FuFastbootDeviceReadFlags flags, GError **error)
{
if (!fu_fastboot_device_writestr (device, cmd, error))
return FALSE;
if (!fu_fastboot_device_read (device, NULL, flags, error))
return FALSE;
return TRUE;
}
static gboolean
fu_fastboot_device_flash (FuDevice *device, const gchar *partition, GError **error)
{
g_autofree gchar *tmp = g_strdup_printf ("flash:%s", partition);
return fu_fastboot_device_cmd (device, tmp,
FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL,
error);
}
static gboolean
fu_fastboot_device_download (FuDevice *device, GBytes *fw, GError **error)
{
FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
gsize sz = g_bytes_get_size (fw);
g_autofree gchar *tmp = g_strdup_printf ("download:%08x", (guint) sz);
g_autoptr(GPtrArray) chunks = NULL;
/* tell the client the size of data to expect */
if (!fu_fastboot_device_cmd (device, tmp,
FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL,
error))
return FALSE;
/* send the data in chunks */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
chunks = fu_chunk_array_new_from_bytes (fw,
0x00, /* start addr */
0x00, /* page_sz */
self->blocksz);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index (chunks, i);
if (!fu_fastboot_device_write (device, chk->data, chk->data_sz, error))
return FALSE;
fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len * 2);
}
if (!fu_fastboot_device_read (device, NULL,
FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL, error))
return FALSE;
return TRUE;
}
static gboolean
fu_fastboot_device_setup (FuDevice *device, GError **error)
{
FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
g_autofree gchar *product = NULL;
g_autofree gchar *serialno = NULL;
g_autofree gchar *version = NULL;
g_autofree gchar *secure = NULL;
g_autofree gchar *version_bootloader = NULL;
/* product */
if (!fu_fastboot_device_getvar (device, "product", &product, error))
return FALSE;
if (product != NULL && product[0] != '\0') {
g_autofree gchar *tmp = g_strdup_printf ("Fastboot %s", product);
fu_device_set_name (device, tmp);
}
/* fastboot API version */
if (!fu_fastboot_device_getvar (device, "version", &version, error))
return FALSE;
if (version != NULL && version[0] != '\0')
g_debug ("fastboot version=%s", version);
/* bootloader version */
if (!fu_fastboot_device_getvar (device, "version-bootloader", &version_bootloader, error))
return FALSE;
if (version_bootloader != NULL && version_bootloader[0] != '\0') {
fu_device_set_version_format (device, FWUPD_VERSION_FORMAT_PAIR);
fu_device_set_version_bootloader (device, version_bootloader);
}
/* serialno */
if (!fu_fastboot_device_getvar (device, "serialno", &serialno, error))
return FALSE;
if (serialno != NULL && serialno[0] != '\0')
fu_device_set_serial (device, serialno);
/* secure */
if (!fu_fastboot_device_getvar (device, "secure", &secure, error))
return FALSE;
if (secure != NULL && secure[0] != '\0')
self->secure = TRUE;
/* success */
return TRUE;
}
static gboolean
fu_fastboot_device_write_qfil_part (FuDevice *device,
FuArchive *archive,
XbNode *part,
GError **error)
{
GBytes *data;
const gchar *fn;
const gchar *partition;
/* not all partitions have images */
fn = xb_node_query_text (part, "img_name", NULL);
if (fn == NULL)
return TRUE;
/* find filename */
data = fu_archive_lookup_by_fn (archive, fn, error);
if (data == NULL)
return FALSE;
/* get the partition name */
partition = xb_node_query_text (part, "name", error);
if (partition == NULL)
return FALSE;
if (g_str_has_prefix (partition, "0:"))
partition += 2;
/* flash the partition */
if (!fu_fastboot_device_download (device, data, error))
return FALSE;
return fu_fastboot_device_flash (device, partition, error);
}
static gboolean
fu_fastboot_device_write_motorola_part (FuDevice *device,
FuArchive *archive,
XbNode *part,
GError **error)
{
const gchar *op = xb_node_get_attr (part, "operation");
/* oem */
if (g_strcmp0 (op, "oem") == 0) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"OEM commands are not supported");
return FALSE;
}
/* getvar */
if (g_strcmp0 (op, "getvar") == 0) {
const gchar *var = xb_node_get_attr (part, "var");
g_autofree gchar *tmp = NULL;
/* check required args */
if (var == NULL) {
tmp = xb_node_export (part, XB_NODE_EXPORT_FLAG_NONE, NULL);
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"required var for part: %s", tmp);
return FALSE;
}
/* just has to be non-empty */
if (!fu_fastboot_device_getvar (device, var, &tmp, error))
return FALSE;
if (tmp == NULL || tmp[0] == '\0') {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"failed to getvar %s", var);
return FALSE;
}
return TRUE;
}
/* erase */
if (g_strcmp0 (op, "erase") == 0) {
const gchar *partition = xb_node_get_attr (part, "partition");
g_autofree gchar *cmd = g_strdup_printf ("erase:%s", partition);
/* check required args */
if (partition == NULL) {
g_autofree gchar *tmp = NULL;
tmp = xb_node_export (part, XB_NODE_EXPORT_FLAG_NONE, NULL);
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"required partition for part: %s", tmp);
return FALSE;
}
/* erase the partition */
return fu_fastboot_device_cmd (device, cmd,
FU_FASTBOOT_DEVICE_READ_FLAG_NONE,
error);
}
/* flash */
if (g_strcmp0 (op, "flash") == 0) {
GBytes *data;
const gchar *filename = xb_node_get_attr (part, "filename");
const gchar *partition = xb_node_get_attr (part, "partition");
struct {
GChecksumType kind;
const gchar *str;
} csum_kinds[] = {
{ G_CHECKSUM_MD5, "MD5" },
{ G_CHECKSUM_SHA1, "SHA1" },
{ 0, NULL }
};
/* check required args */
if (partition == NULL || filename == NULL) {
g_autofree gchar *tmp = NULL;
tmp = xb_node_export (part, XB_NODE_EXPORT_FLAG_NONE, NULL);
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"required partition and filename: %s", tmp);
return FALSE;
}
/* find filename */
data = fu_archive_lookup_by_fn (archive, filename, error);
if (data == NULL)
return FALSE;
/* checksum is optional */
for (guint i = 0; csum_kinds[i].str != NULL; i++) {
const gchar *csum;
g_autofree gchar *csum_actual = NULL;
/* not provided */
csum = xb_node_get_attr (part, csum_kinds[i].str);
if (csum == NULL)
continue;
/* check is valid */
csum_actual = g_compute_checksum_for_bytes (csum_kinds[i].kind, data);
if (g_strcmp0 (csum, csum_actual) != 0) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"%s invalid, expected %s, got %s",
filename, csum, csum_actual);
return FALSE;
}
}
/* flash the partition */
if (!fu_fastboot_device_download (device, data, error))
return FALSE;
return fu_fastboot_device_flash (device, partition, error);
}
/* dumb operation that doesn't expect a response */
if (g_strcmp0 (op, "boot") == 0 ||
g_strcmp0 (op, "continue") == 0 ||
g_strcmp0 (op, "reboot") == 0 ||
g_strcmp0 (op, "reboot-bootloader") == 0 ||
g_strcmp0 (op, "powerdown") == 0) {
return fu_fastboot_device_cmd (device, op,
FU_FASTBOOT_DEVICE_READ_FLAG_NONE,
error);
}
/* unknown */
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"unknown operation %s", op);
return FALSE;
}
static gboolean
fu_fastboot_device_write_motorola (FuDevice *device,
FuArchive* archive,
GError **error)
{
GBytes *data;
g_autoptr(GPtrArray) parts = NULL;
g_autoptr(XbBuilder) builder = xb_builder_new ();
g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
g_autoptr(XbSilo) silo = NULL;
/* load the manifest of operations */
data = fu_archive_lookup_by_fn (archive, "flashfile.xml", error);
if (data == NULL)
return FALSE;
if (!xb_builder_source_load_bytes (source, data,
XB_BUILDER_SOURCE_FLAG_NONE, error))
return FALSE;
xb_builder_import_source (builder, source);
silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error);
if (silo == NULL)
return FALSE;
/* get all the operation parts */
parts = xb_silo_query (silo, "parts/part", 0, error);
if (parts == NULL)
return FALSE;
for (guint i = 0; i < parts->len; i++) {
XbNode *part = g_ptr_array_index (parts, i);
if (!fu_fastboot_device_write_motorola_part (device,
archive,
part,
error))
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_fastboot_device_write_qfil (FuDevice *device, FuArchive* archive, GError **error)
{
GBytes *data;
g_autoptr(GPtrArray) parts = NULL;
g_autoptr(XbBuilder) builder = xb_builder_new ();
g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
g_autoptr(XbSilo) silo = NULL;
/* load the manifest of operations */
data = fu_archive_lookup_by_fn (archive, "partition_nand.xml", error);
if (data == NULL)
return FALSE;
if (!xb_builder_source_load_bytes (source, data,
XB_BUILDER_SOURCE_FLAG_NONE, error))
return FALSE;
xb_builder_import_source (builder, source);
silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error);
if (silo == NULL)
return FALSE;
/* get all the operation parts */
parts = xb_silo_query (silo, "nandboot/partitions/partition", 0, error);
if (parts == NULL)
return FALSE;
for (guint i = 0; i < parts->len; i++) {
XbNode *part = g_ptr_array_index (parts, i);
if (!fu_fastboot_device_write_qfil_part (device,
archive,
part,
error))
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_fastboot_device_write_firmware (FuDevice *device,
FuFirmware *firmware,
FwupdInstallFlags flags,
GError **error)
{
g_autoptr(FuArchive) archive = NULL;
g_autoptr(GBytes) fw = NULL;
/* get default image */
fw = fu_firmware_get_image_default_bytes (firmware, error);
if (fw == NULL)
return FALSE;
/* decompress entire archive ahead of time */
archive = fu_archive_new (fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error);
if (archive == NULL)
return FALSE;
/* load the manifest of operations */
if (fu_archive_lookup_by_fn (archive, "partition_nand.xml", NULL) != NULL)
return fu_fastboot_device_write_qfil (device, archive, error);
if (fu_archive_lookup_by_fn (archive, "flashfile.xml", NULL) != NULL) {
return fu_fastboot_device_write_motorola (device,
archive,
error);
}
/* not supported */
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"manifest not supported");
return FALSE;
}
static gboolean
fu_fastboot_device_close (FuUsbDevice *device, GError **error)
{
FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
GUsbDevice *usb_device = fu_usb_device_get_dev (device);
/* we're done here */
if (!g_usb_device_release_interface (usb_device, self->intf_nr,
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
error)) {
g_prefix_error (error, "failed to release interface: ");
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_fastboot_device_set_quirk_kv (FuDevice *device,
const gchar *key,
const gchar *value,
GError **error)
{
FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
/* load slave address from quirks */
if (g_strcmp0 (key, "FastbootBlockSize") == 0) {
guint64 tmp = fu_common_strtoull (value);
if (tmp >= 0x40 && tmp < 0x100000) {
self->blocksz = tmp;
return TRUE;
}
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid block size");
return FALSE;
}
/* failed */
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"quirk key not supported");
return FALSE;
}
static gboolean
fu_fastboot_device_attach (FuDevice *device, GError **error)
{
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
if (!fu_fastboot_device_cmd (device, "reboot",
FU_FASTBOOT_DEVICE_READ_FLAG_NONE,
error))
return FALSE;
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
return TRUE;
}
static void
fu_fastboot_device_init (FuFastbootDevice *self)
{
/* this is a safe default, even using USBv1 */
self->blocksz = 512;
fu_device_set_protocol (FU_DEVICE (self), "com.google.fastboot");
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS);
fu_device_set_remove_delay (FU_DEVICE (self), FASTBOOT_REMOVE_DELAY_RE_ENUMERATE);
}
static void
fu_fastboot_device_class_init (FuFastbootDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass);
klass_device->probe = fu_fastboot_device_probe;
klass_device->setup = fu_fastboot_device_setup;
klass_device->write_firmware = fu_fastboot_device_write_firmware;
klass_device->attach = fu_fastboot_device_attach;
klass_device->to_string = fu_fastboot_device_to_string;
klass_device->set_quirk_kv = fu_fastboot_device_set_quirk_kv;
klass_usb_device->open = fu_fastboot_device_open;
klass_usb_device->close = fu_fastboot_device_close;
}