mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-13 04:15:41 +00:00

This adds the logic for the VBE plugin. It supports reading information from the system to determine which VBE method (a device in fwupd) is in use, then creating those devices to handle the actual update. Verified Boot for Embedded (VBE) relies on FIT files to operate. FIT is a way of packaging multiple images along with information about them. Signed-off-by: Simon Glass <sjg@chromium.org>
497 lines
14 KiB
C
497 lines
14 KiB
C
/*
|
|
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
|
|
* Copyright (C) 2022 Google LLC
|
|
* Written by Simon Glass <sjg@chromium.org>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
|
|
#include "fu-vbe-simple-device.h"
|
|
|
|
/**
|
|
* @skip_offset: This allows an initial part of the image to be skipped when
|
|
* writing. This means that the first part of the image is ignored, with just
|
|
* the latter part being written. For example, if this is 0x200 then the first
|
|
* 512 bytes of the image (which must be present in the image) are skipped and
|
|
* the bytes after that are written to the store offset.
|
|
*/
|
|
struct _FuVbeSimpleDevice {
|
|
FuVbeDevice parent_instance;
|
|
gchar *storage; /* e.g. "mmc1" */
|
|
gchar *devname; /* e.g. /dev/mmcblk1 */
|
|
guint32 area_start;
|
|
guint32 area_size;
|
|
guint32 skip_offset;
|
|
gint fd;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuVbeSimpleDevice, fu_vbe_simple_device, FU_TYPE_VBE_DEVICE)
|
|
|
|
static void
|
|
fu_vbe_simple_device_to_string(FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device);
|
|
|
|
/* FuVbeDevice->to_string */
|
|
FU_DEVICE_CLASS(fu_vbe_simple_device_parent_class)->to_string(device, idt, str);
|
|
|
|
if (self->storage != NULL)
|
|
fu_string_append(str, idt, "Storage", self->storage);
|
|
if (self->devname != NULL)
|
|
fu_string_append(str, idt, "Devname", self->devname);
|
|
fu_string_append_kx(str, idt, "AreaStart", self->area_start);
|
|
fu_string_append_kx(str, idt, "AreaSize", self->area_size);
|
|
if (self->skip_offset != 0)
|
|
fu_string_append_kx(str, idt, "SkipOffset", self->skip_offset);
|
|
}
|
|
|
|
static gboolean
|
|
fu_vbe_simple_device_parse_devnum(const gchar *str, guint *value, GError **error)
|
|
{
|
|
guint64 val64 = 0;
|
|
|
|
/* skip non-numeric part */
|
|
while (*str != '\0' && g_ascii_isdigit(*str))
|
|
str++;
|
|
|
|
/* convert to uint */
|
|
if (!fu_strtoull(str, &val64, 0x0, G_MAXUINT, error))
|
|
return FALSE;
|
|
if (value != NULL)
|
|
*value = val64;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vbe_simple_device_probe(FuDevice *device, GError **error)
|
|
{
|
|
FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device);
|
|
FuFdtImage *fdt_node;
|
|
|
|
g_return_val_if_fail(FU_IS_VBE_DEVICE(self), FALSE);
|
|
|
|
/* FuVbeDevice->probe */
|
|
if (!FU_DEVICE_CLASS(fu_vbe_simple_device_parent_class)->probe(device, error))
|
|
return FALSE;
|
|
|
|
fdt_node = fu_vbe_device_get_fdt_node(FU_VBE_DEVICE(self));
|
|
if (!fu_fdt_image_get_attr_str(fdt_node, "storage", &self->storage, error))
|
|
return FALSE;
|
|
|
|
/* if this is an absolute path, use it */
|
|
if (g_str_has_prefix(self->storage, "/")) {
|
|
self->devname = g_strdup(self->storage);
|
|
} else {
|
|
guint devnum = 0;
|
|
|
|
/* obtain the 1 from "mmc1" */
|
|
if (!fu_vbe_simple_device_parse_devnum(self->storage, &devnum, error)) {
|
|
g_prefix_error(error, "cannot parse storage property %s: ", self->storage);
|
|
return FALSE;
|
|
}
|
|
if (g_str_has_prefix(self->storage, "mmc")) {
|
|
self->devname = g_strdup_printf("/dev/mmcblk%u", devnum);
|
|
} else {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"unsupported 'storage' media '%s'",
|
|
self->storage);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* get area */
|
|
if (!fu_fdt_image_get_attr_u32(fdt_node, "area-start", &self->area_start, error))
|
|
return FALSE;
|
|
if (!fu_fdt_image_get_attr_u32(fdt_node, "area-size", &self->area_size, error))
|
|
return FALSE;
|
|
|
|
/* an optional skip offset to skip everything, which could be useful for testing */
|
|
fu_fdt_image_get_attr_u32(fdt_node, "skip-offset", &self->skip_offset, NULL);
|
|
if (self->skip_offset > self->area_size) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"store offset 0x%x is larger than size 0x%x",
|
|
(guint)self->skip_offset,
|
|
(guint)self->area_size);
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vbe_simple_device_open(FuDevice *device, GError **error)
|
|
{
|
|
FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device);
|
|
|
|
/* open device */
|
|
self->fd = open(self->devname, O_RDWR);
|
|
if (self->fd == -1) {
|
|
#ifdef HAVE_ERRNO_H
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"cannot open %s [%s]",
|
|
self->devname,
|
|
strerror(errno));
|
|
#else
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"cannot open %s",
|
|
self->devname);
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vbe_simple_device_close(FuDevice *device, GError **error)
|
|
{
|
|
FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device);
|
|
|
|
/* close device */
|
|
close(self->fd);
|
|
self->fd = -1;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static FuFdtImage *
|
|
fu_vbe_simple_device_get_cfg_compatible(FuVbeSimpleDevice *self,
|
|
FuFirmware *firmware,
|
|
GError **error)
|
|
{
|
|
gchar **device_compatible;
|
|
g_autoptr(FuFdtImage) fdt_configurations = NULL;
|
|
g_autoptr(GPtrArray) img_configurations = NULL;
|
|
g_autofree gchar *str = NULL;
|
|
|
|
/* get all configurations */
|
|
fdt_configurations =
|
|
fu_fdt_firmware_get_image_by_path(FU_FDT_FIRMWARE(firmware),
|
|
"/" FU_FIT_FIRMWARE_ID_CONFIGURATIONS,
|
|
error);
|
|
if (fdt_configurations == NULL)
|
|
return NULL;
|
|
img_configurations = fu_firmware_get_images(firmware);
|
|
|
|
/* look for a configuration with the device compatible strings in priority order */
|
|
device_compatible = fu_vbe_device_get_compatible(FU_VBE_DEVICE(self));
|
|
for (guint j = 0; device_compatible[j] != NULL; j++) {
|
|
for (guint i = 0; i < img_configurations->len; i++) {
|
|
FuFdtImage *img = g_ptr_array_index(img_configurations, i);
|
|
g_auto(GStrv) img_compatible = NULL;
|
|
if (!fu_fdt_image_get_attr_strlist(img,
|
|
FU_FIT_FIRMWARE_ATTR_COMPATIBLE,
|
|
&img_compatible,
|
|
error))
|
|
return NULL;
|
|
if (g_strv_contains((const gchar *const *)img_compatible,
|
|
device_compatible[j]))
|
|
return g_object_ref(img);
|
|
}
|
|
}
|
|
|
|
/* failure */
|
|
str = g_strjoinv(", ", device_compatible);
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no images found that match %s", str);
|
|
return NULL;
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_vbe_simple_device_prepare_firmware(FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device);
|
|
g_autofree gchar *version = NULL;
|
|
g_auto(GStrv) firmware_ids = NULL;
|
|
g_autoptr(FuFdtImage) img_cfg = NULL;
|
|
g_autoptr(FuFirmware) firmware = fu_fit_firmware_new();
|
|
g_autoptr(FuFirmware) firmware_container = fu_firmware_new();
|
|
|
|
/* parse all images */
|
|
if (!fu_firmware_parse(firmware, fw, flags, error))
|
|
return NULL;
|
|
|
|
/* look for a compatible configuration */
|
|
img_cfg = fu_vbe_simple_device_get_cfg_compatible(self, firmware, error);
|
|
if (img_cfg == NULL)
|
|
return NULL;
|
|
if (!fu_fdt_image_get_attr_str(img_cfg, FU_FIT_FIRMWARE_ATTR_VERSION, &version, error))
|
|
return NULL;
|
|
|
|
/* check the firmware images exists */
|
|
if (!fu_fdt_image_get_attr_strlist(img_cfg, "firmware", &firmware_ids, error))
|
|
return NULL;
|
|
for (guint i = 0; firmware_ids[i] != NULL; i++) {
|
|
g_autofree gchar *path = NULL;
|
|
g_autoptr(FuFdtImage) img_firmware = NULL;
|
|
path = g_strdup_printf("/%s/%s", FU_FIT_FIRMWARE_ID_IMAGES, firmware_ids[i]);
|
|
img_firmware =
|
|
fu_fdt_firmware_get_image_by_path(FU_FDT_FIRMWARE(firmware), path, error);
|
|
if (img_firmware == NULL)
|
|
return NULL;
|
|
fu_firmware_add_image(firmware_container, FU_FIRMWARE(img_firmware));
|
|
}
|
|
|
|
/* success: return the container */
|
|
return g_steal_pointer(&firmware_container);
|
|
}
|
|
|
|
static gboolean
|
|
fu_vbe_simple_device_write_firmware_img(FuVbeSimpleDevice *self,
|
|
FuFdtImage *img,
|
|
FuProgress *progress,
|
|
GError **error)
|
|
{
|
|
const guint8 *buf;
|
|
gssize rc;
|
|
gsize bufsz = 0;
|
|
gsize seek_to;
|
|
guint32 store_offset = 0;
|
|
g_autoptr(GBytes) blob = NULL;
|
|
|
|
/* get data */
|
|
blob = fu_fdt_image_get_attr(img, FU_FIT_FIRMWARE_ATTR_DATA, error);
|
|
if (blob == NULL)
|
|
return FALSE;
|
|
buf = g_bytes_get_data(blob, &bufsz);
|
|
fu_fdt_image_get_attr_u32(img, "store-offset", &store_offset, NULL);
|
|
|
|
/* sanity check */
|
|
if (store_offset + bufsz > self->area_size) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"image '%s' store_offset=0x%x, bufsz=0x%x, area_size=0x%x",
|
|
fu_firmware_get_id(FU_FIRMWARE(img)),
|
|
(guint)store_offset,
|
|
(guint)bufsz,
|
|
(guint)self->area_size);
|
|
return FALSE;
|
|
}
|
|
if (self->skip_offset >= bufsz) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"image '%s' skip_offset=0x%x, bufsz=0x%x, area_size=0x%x",
|
|
fu_firmware_get_id(FU_FIRMWARE(img)),
|
|
(guint)store_offset,
|
|
(guint)bufsz,
|
|
(guint)self->area_size);
|
|
return FALSE;
|
|
}
|
|
|
|
/* seek to correct address */
|
|
seek_to = self->area_start + store_offset + self->skip_offset;
|
|
g_debug("writing image '%s' bufsz 0x%x (skipping 0x%x) to store_offset 0x%x, seek 0x%x\n",
|
|
fu_firmware_get_id(FU_FIRMWARE(img)),
|
|
(guint)bufsz,
|
|
(guint)self->skip_offset,
|
|
(guint)store_offset,
|
|
(guint)seek_to);
|
|
rc = lseek(self->fd, seek_to, SEEK_SET);
|
|
if (rc < 0) {
|
|
#ifdef HAVE_ERRNO_H
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"cannot seek file '%s' to 0x%x [%s]",
|
|
self->devname,
|
|
(guint)seek_to,
|
|
strerror(errno));
|
|
#else
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"cannot seek file '%s' to 0x%x",
|
|
self->devname,
|
|
(guint)seek_to);
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
/* write buffer */
|
|
rc = write(self->fd, buf + self->skip_offset, bufsz - self->skip_offset);
|
|
if (rc < 0) {
|
|
#ifdef HAVE_ERRNO_H
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"cannot write file '%s' [%s]",
|
|
self->devname,
|
|
strerror(errno));
|
|
#else
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"cannot write file '%s'",
|
|
self->devname);
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vbe_simple_device_write_firmware(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device);
|
|
g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware);
|
|
|
|
g_return_val_if_fail(FU_IS_VBE_DEVICE(self), FALSE);
|
|
|
|
/* write each firmware image */
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_set_steps(progress, imgs->len);
|
|
for (guint i = 0; i < imgs->len; i++) {
|
|
FuFdtImage *img = g_ptr_array_index(imgs, i);
|
|
if (!fu_vbe_simple_device_write_firmware_img(self,
|
|
img,
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static GBytes *
|
|
fu_vbe_simple_device_upload(FuDevice *device, FuProgress *progress, GError **error)
|
|
{
|
|
FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device);
|
|
gssize rc;
|
|
g_autoptr(GByteArray) buf = NULL;
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
/* notify UI */
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ);
|
|
|
|
/* seek to start */
|
|
rc = lseek(self->fd, self->area_start, SEEK_SET);
|
|
if (rc < 0) {
|
|
#ifdef HAVE_ERRNO_H
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"cannot seek file %s to 0x%x [%s]",
|
|
self->devname,
|
|
(guint)self->area_start,
|
|
strerror(errno));
|
|
#else
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"cannot seek file %s to 0x%x",
|
|
self->devname,
|
|
(guint)self->area_start);
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
/* process in chunks */
|
|
chunks = fu_chunk_array_new(NULL, self->area_size - self->area_start, 0x0, 0x0, 0x100000);
|
|
fu_progress_set_steps(progress, chunks->len);
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index(chunks, i);
|
|
g_autofree guint8 *tmpbuf = g_malloc0(fu_chunk_get_data_sz(chk));
|
|
|
|
rc = read(self->fd, tmpbuf, fu_chunk_get_data_sz(chk));
|
|
if (rc != (gssize)fu_chunk_get_data_sz(chk)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"incomplete read of %s @0x%x",
|
|
self->devname,
|
|
fu_chunk_get_address(chk));
|
|
return NULL;
|
|
}
|
|
g_byte_array_append(buf, tmpbuf, fu_chunk_get_data_sz(chk));
|
|
fu_progress_step_done(progress);
|
|
}
|
|
|
|
/* success */
|
|
return g_byte_array_free_to_bytes(g_steal_pointer(&buf));
|
|
}
|
|
|
|
static void
|
|
fu_vbe_simple_device_set_progress(FuDevice *self, FuProgress *progress)
|
|
{
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload");
|
|
}
|
|
|
|
static void
|
|
fu_vbe_simple_device_init(FuVbeSimpleDevice *self)
|
|
{
|
|
fu_device_set_name(FU_DEVICE(self), "simple");
|
|
fu_device_set_vendor(FU_DEVICE(self), "U-Boot");
|
|
fu_device_add_vendor_id(FU_DEVICE(self), "VBE:U-Boot");
|
|
fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET);
|
|
fu_device_set_version_lowest(FU_DEVICE(self), "0.0.1");
|
|
}
|
|
|
|
static void
|
|
fu_vbe_simple_device_constructed(GObject *obj)
|
|
{
|
|
FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(obj);
|
|
fu_device_add_guid(FU_DEVICE(self), "bb3b05a8-ebef-11ec-be98-d3a15278be95");
|
|
}
|
|
|
|
static void
|
|
fu_vbe_simple_device_finalize(GObject *obj)
|
|
{
|
|
FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(obj);
|
|
g_free(self->devname);
|
|
g_free(self->storage);
|
|
G_OBJECT_CLASS(fu_vbe_simple_device_parent_class)->finalize(obj);
|
|
}
|
|
|
|
static void
|
|
fu_vbe_simple_device_class_init(FuVbeSimpleDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
object_class->constructed = fu_vbe_simple_device_constructed;
|
|
object_class->finalize = fu_vbe_simple_device_finalize;
|
|
klass_device->to_string = fu_vbe_simple_device_to_string;
|
|
klass_device->probe = fu_vbe_simple_device_probe;
|
|
klass_device->open = fu_vbe_simple_device_open;
|
|
klass_device->close = fu_vbe_simple_device_close;
|
|
klass_device->set_progress = fu_vbe_simple_device_set_progress;
|
|
klass_device->prepare_firmware = fu_vbe_simple_device_prepare_firmware;
|
|
klass_device->write_firmware = fu_vbe_simple_device_write_firmware;
|
|
klass_device->dump_firmware = fu_vbe_simple_device_upload;
|
|
}
|