mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-25 18:25:00 +00:00

It's actually quite hard to build a front-end for fwupd at the moment as you're never sure when the progress bar is going to zip back to 0% and start all over again. Some plugins go 0..100% for write, others go 0..100% for erase, then again for write, then *again* for verify. By creating a helper object we can easily split up the progress of the specific task, e.g. write_firmware(). We can encode at the plugin level "the erase takes 50% of the time, the write takes 40% and the read takes 10%". This means we can have a progressbar which goes up just once at a consistent speed.
773 lines
21 KiB
C
773 lines
21 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 <xmlb.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(FuDevice *device, GError **error)
|
|
{
|
|
FuFastbootDevice *self = FU_FASTBOOT_DEVICE(device);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
|
|
|
|
/* FuUsbDevice->open */
|
|
if (!FU_DEVICE_CLASS(fu_fastboot_device_parent_class)->open(device, error))
|
|
return FALSE;
|
|
|
|
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 = 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);
|
|
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);
|
|
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);
|
|
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_set_percentage_full(progress, (gsize)i + 1, (gsize)chunks->len);
|
|
}
|
|
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,
|
|
FuArchive *archive,
|
|
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_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, progress, error))
|
|
return FALSE;
|
|
return fu_fastboot_device_flash(device, partition, progress, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_fastboot_device_write_motorola_part(FuDevice *device,
|
|
FuArchive *archive,
|
|
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"}, {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, 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,
|
|
FuArchive *archive,
|
|
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_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;
|
|
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,
|
|
archive,
|
|
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,
|
|
FuArchive *archive,
|
|
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_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;
|
|
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,
|
|
archive,
|
|
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(FuArchive) archive = NULL;
|
|
g_autoptr(GBytes) fw = NULL;
|
|
|
|
/* get default image */
|
|
fw = fu_firmware_get_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, progress, error);
|
|
if (fu_archive_lookup_by_fn(archive, "flashfile.xml", NULL) != NULL) {
|
|
return fu_fastboot_device_write_motorola(device, archive, 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_close(FuDevice *device, GError **error)
|
|
{
|
|
FuFastbootDevice *self = FU_FASTBOOT_DEVICE(device);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(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;
|
|
}
|
|
|
|
/* FuUsbDevice->close */
|
|
return FU_DEVICE_CLASS(fu_fastboot_device_parent_class)->close(device, error);
|
|
}
|
|
|
|
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 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, 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;
|
|
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);
|
|
}
|
|
|
|
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->open = fu_fastboot_device_open;
|
|
klass_device->close = fu_fastboot_device_close;
|
|
klass_device->set_progress = fu_fastboot_device_set_progress;
|
|
}
|