mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-06 04:12:25 +00:00

This allows us to ignore all the delays when the device is emulated, with the idea being to do dozens of device emulations in the CI tests. Also, do not call fu_progress_sleep() when the device is emulated.
735 lines
20 KiB
C
735 lines
20 KiB
C
/*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include <string.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;
|
|
guint operation_delay;
|
|
};
|
|
|
|
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_string_append_kx(str, idt, "BlockSize", self->blocksz);
|
|
fu_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;
|
|
fu_usb_device_add_interface(FU_USB_DEVICE(self), g_usb_interface_get_number(intf));
|
|
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)
|
|
{
|
|
FuFastbootDevice *self = FU_FASTBOOT_DEVICE(device);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
|
|
gboolean ret;
|
|
gsize actual_len = 0;
|
|
g_autofree guint8 *buf2 = NULL;
|
|
|
|
/* make mutable */
|
|
buf2 = fu_memdup_safe(buf, buflen, error);
|
|
if (buf2 == NULL)
|
|
return FALSE;
|
|
|
|
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);
|
|
|
|
/* give device some time to handle action */
|
|
fu_device_sleep(device, self->operation_delay);
|
|
|
|
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,
|
|
FuProgress *progress,
|
|
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);
|
|
/* give device some time to handle action */
|
|
fu_device_sleep(device, self->operation_delay);
|
|
|
|
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_progress_set_status(progress, FWUPD_STATUS_DEVICE_ERASE);
|
|
else if (g_strcmp0(tmp, "writing flash") == 0)
|
|
fu_progress_set_status(progress, 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, NULL, FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error)) {
|
|
g_prefix_error(error, "failed to getvar %s: ", key);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_fastboot_device_cmd(FuDevice *device,
|
|
const gchar *cmd,
|
|
FuProgress *progress,
|
|
FuFastbootDeviceReadFlags flags,
|
|
GError **error)
|
|
{
|
|
if (!fu_fastboot_device_writestr(device, cmd, error))
|
|
return FALSE;
|
|
if (!fu_fastboot_device_read(device, NULL, progress, flags, error))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_fastboot_device_flash(FuDevice *device,
|
|
const gchar *partition,
|
|
FuProgress *progress,
|
|
GError **error)
|
|
{
|
|
g_autofree gchar *tmp = g_strdup_printf("flash:%s", partition);
|
|
return fu_fastboot_device_cmd(device,
|
|
tmp,
|
|
progress,
|
|
FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_fastboot_device_download(FuDevice *device, GBytes *fw, FuProgress *progress, 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,
|
|
progress,
|
|
FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* send the data in chunks */
|
|
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE);
|
|
chunks = fu_chunk_array_new_from_bytes(fw,
|
|
0x00, /* start addr */
|
|
0x00, /* page_sz */
|
|
self->blocksz);
|
|
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);
|
|
if (!fu_fastboot_device_write(device,
|
|
fu_chunk_get_data(chk),
|
|
fu_chunk_get_data_sz(chk),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
}
|
|
if (!fu_fastboot_device_read(device,
|
|
NULL,
|
|
progress,
|
|
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;
|
|
|
|
/* FuUsbDevice->setup */
|
|
if (!FU_DEVICE_CLASS(fu_fastboot_device_parent_class)->setup(device, error))
|
|
return FALSE;
|
|
|
|
/* 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,
|
|
FuFirmware *firmware,
|
|
XbNode *part,
|
|
FuProgress *progress,
|
|
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_firmware_get_image_by_id_bytes(firmware, 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, progress, error))
|
|
return FALSE;
|
|
return fu_fastboot_device_flash(device, partition, progress, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_fastboot_device_write_motorola_part(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
XbNode *part,
|
|
FuProgress *progress,
|
|
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,
|
|
progress,
|
|
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"},
|
|
{G_CHECKSUM_SHA256, "SHA256"},
|
|
{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_firmware_get_image_by_id_bytes(firmware, 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, progress, error))
|
|
return FALSE;
|
|
return fu_fastboot_device_flash(device, partition, progress, 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,
|
|
progress,
|
|
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,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
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_firmware_get_image_by_id_bytes(firmware, "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;
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_set_steps(progress, parts->len);
|
|
for (guint i = 0; i < parts->len; i++) {
|
|
XbNode *part = g_ptr_array_index(parts, i);
|
|
if (!fu_fastboot_device_write_motorola_part(device,
|
|
firmware,
|
|
part,
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_fastboot_device_write_qfil(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
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_firmware_get_image_by_id_bytes(firmware, "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;
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_set_steps(progress, parts->len);
|
|
for (guint i = 0; i < parts->len; i++) {
|
|
XbNode *part = g_ptr_array_index(parts, i);
|
|
if (!fu_fastboot_device_write_qfil_part(device,
|
|
firmware,
|
|
part,
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_fastboot_device_write_firmware(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
g_autoptr(FuFirmware) manifest = NULL;
|
|
|
|
/* load the manifest of operations */
|
|
manifest = fu_firmware_get_image_by_id(firmware, "partition_nand.xml", NULL);
|
|
if (manifest != NULL)
|
|
return fu_fastboot_device_write_qfil(device, firmware, progress, error);
|
|
manifest = fu_firmware_get_image_by_id(firmware, "flashfile.xml", NULL);
|
|
if (manifest != NULL)
|
|
return fu_fastboot_device_write_motorola(device, firmware, progress, 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_set_quirk_kv(FuDevice *device,
|
|
const gchar *key,
|
|
const gchar *value,
|
|
GError **error)
|
|
{
|
|
FuFastbootDevice *self = FU_FASTBOOT_DEVICE(device);
|
|
guint64 tmp = 0;
|
|
|
|
/* load from quirks */
|
|
if (g_strcmp0(key, "FastbootBlockSize") == 0) {
|
|
if (!fu_strtoull(value, &tmp, 0x40, 0x100000, error))
|
|
return FALSE;
|
|
self->blocksz = tmp;
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0(key, "FastbootOperationDelay") == 0) {
|
|
if (!fu_strtoull(value, &tmp, 0, G_MAXSIZE, error))
|
|
return FALSE;
|
|
self->operation_delay = tmp;
|
|
return TRUE;
|
|
}
|
|
|
|
/* 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, FuProgress *progress, GError **error)
|
|
{
|
|
if (!fu_fastboot_device_cmd(device,
|
|
"reboot",
|
|
progress,
|
|
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_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, 94, "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_fastboot_device_init(FuFastbootDevice *self)
|
|
{
|
|
/* this is a safe default, even using USBv1 */
|
|
self->blocksz = 512;
|
|
/* no delay is applied by default after a read or write operation */
|
|
self->operation_delay = 0;
|
|
fu_device_add_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_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID);
|
|
fu_device_set_remove_delay(FU_DEVICE(self), FASTBOOT_REMOVE_DELAY_RE_ENUMERATE);
|
|
fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_ARCHIVE_FIRMWARE);
|
|
}
|
|
|
|
static void
|
|
fu_fastboot_device_class_init(FuFastbootDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_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_device->set_progress = fu_fastboot_device_set_progress;
|
|
}
|