u-boot/boot/bootmeth_cros.c
Tom Rini d678a59d2d Revert "Merge patch series "arm: dts: am62-beagleplay: Fix Beagleplay Ethernet""
When bringing in the series 'arm: dts: am62-beagleplay: Fix Beagleplay
Ethernet"' I failed to notice that b4 noticed it was based on next and
so took that as the base commit and merged that part of next to master.

This reverts commit c8ffd1356d, reversing
changes made to 2ee6f3a5f7.

Reported-by: Jonas Karlman <jonas@kwiboo.se>
Signed-off-by: Tom Rini <trini@konsulko.com>
2024-05-19 08:16:36 -06:00

477 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Bootmethod for ChromiumOS
*
* Copyright 2023 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY UCLASS_BOOTSTD
#include <common.h>
#include <blk.h>
#include <bootdev.h>
#include <bootflow.h>
#include <bootm.h>
#include <bootmeth.h>
#include <display_options.h>
#include <dm.h>
#include <efi.h>
#include <malloc.h>
#include <mapmem.h>
#include <part.h>
#include <linux/sizes.h>
#include "bootmeth_cros.h"
static const efi_guid_t cros_kern_type = PARTITION_CROS_KERNEL;
/*
* Layout of the ChromeOS kernel
*
* Partitions 2 and 4 contain kernels with type GUID_CROS_KERNEL
*
* Contents are:
*
* Offset Contents
* 0 struct vb2_keyblock
* m struct vb2_kernel_preamble
* m + n kernel buffer
*
* m is keyblock->keyblock_size
* n is preamble->preamble_size
*
* The kernel buffer itself consists of various parts:
*
* Offset Contents
* m + n kernel image (Flat vmlinux binary or FIT)
* b - 8KB Command line text
* b - 4KB X86 setup block (struct boot_params, extends for about 16KB)
* b X86 bootloader (continuation of setup block)
* b + 16KB X86 setup block (copy, used for hold data pointed to)
*
* b is m + n + preamble->bootloader_address - preamble->body_load_address
*
* Useful metadata extends from b - 8KB through to b + 32 KB
*/
enum {
PROBE_SIZE = SZ_4K, /* initial bytes read from partition */
X86_SETUP_OFFSET = -0x1000, /* setup offset relative to base */
CMDLINE_OFFSET = -0x2000, /* cmdline offset relative to base */
X86_KERNEL_OFFSET = 0x4000, /* kernel offset relative to base */
};
/**
* struct cros_priv - Private data
*
* This is read from the disk and recorded for use when the full kernel must
* be loaded and booted
*
* @body_offset: Offset of kernel body from start of partition (in bytes)
* @body_size: Size of kernel body in bytes
* @part_start: Block offset of selected partition from the start of the disk
* @body_load_address: Nominal load address for kernel body
* @bootloader_address: Address of bootloader, after body is loaded at
* body_load_address
* @bootloader_size: Size of bootloader in bytes
* @info_buf: Buffer containing ChromiumOS info
*/
struct cros_priv {
ulong body_offset;
ulong body_size;
lbaint_t part_start;
ulong body_load_address;
ulong bootloader_address;
ulong bootloader_size;
void *info_buf;
};
static int cros_check(struct udevice *dev, struct bootflow_iter *iter)
{
/* This only works on block and network devices */
if (bootflow_iter_check_blk(iter))
return log_msg_ret("blk", -ENOTSUPP);
return 0;
}
static int copy_cmdline(const char *from, const char *uuid, char **bufp)
{
const int maxlen = 2048;
char buf[maxlen];
char *cmd, *to, *end;
int len;
/* Allow space for cmdline + UUID */
len = strnlen(from, sizeof(buf));
if (len >= maxlen)
return -E2BIG;
log_debug("uuid %d %s\n", uuid ? (int)strlen(uuid) : 0, uuid);
for (to = buf, end = buf + maxlen - UUID_STR_LEN - 1; *from; from++) {
if (to >= end)
return -E2BIG;
if (from[0] == '%' && from[1] == 'U' && uuid &&
strlen(uuid) == UUID_STR_LEN) {
strcpy(to, uuid);
to += UUID_STR_LEN;
from++;
} else {
*to++ = *from;
}
}
*to = '\0';
len = to - buf;
cmd = strdup(buf);
if (!cmd)
return -ENOMEM;
free(*bufp);
*bufp = cmd;
return 0;
}
/**
* scan_part() - Scan a kernel partition to see if has a ChromeOS header
*
* This reads the first PROBE_SIZE of a partition, loookng for
* VB2_KEYBLOCK_MAGIC
*
* @blk: Block device to scan
* @partnum: Partition number to scan
* @info: Please to put partition info
* @hdrp: Return allocated keyblock header on success
*/
static int scan_part(struct udevice *blk, int partnum,
struct disk_partition *info, struct vb2_keyblock **hdrp)
{
struct blk_desc *desc = dev_get_uclass_plat(blk);
struct vb2_keyblock *hdr;
struct uuid type;
ulong num_blks;
int ret;
if (!partnum)
return log_msg_ret("efi", -ENOENT);
ret = part_get_info(desc, partnum, info);
if (ret)
return log_msg_ret("part", ret);
/* Check for kernel partition type */
log_debug("part %x: type=%s\n", partnum, info->type_guid);
if (uuid_str_to_bin(info->type_guid, (u8 *)&type, UUID_STR_FORMAT_GUID))
return log_msg_ret("typ", -EINVAL);
if (memcmp(&cros_kern_type, &type, sizeof(type)))
return log_msg_ret("typ", -ENOEXEC);
/* Make a buffer for the header information */
num_blks = PROBE_SIZE >> desc->log2blksz;
log_debug("Reading header, blk=%s, start=%lx, blocks=%lx\n",
blk->name, (ulong)info->start, num_blks);
hdr = memalign(SZ_1K, PROBE_SIZE);
if (!hdr)
return log_msg_ret("hdr", -ENOMEM);
ret = blk_read(blk, info->start, num_blks, hdr);
if (ret != num_blks) {
free(hdr);
return log_msg_ret("inf", -EIO);
}
if (memcmp(VB2_KEYBLOCK_MAGIC, hdr->magic, VB2_KEYBLOCK_MAGIC_SIZE)) {
free(hdr);
log_debug("no magic\n");
return -ENOENT;
}
*hdrp = hdr;
return 0;
}
/**
* cros_read_buf() - Read information into a buf and parse it
*
* @bflow: Bootflow to update
* @buf: Buffer to use
* @size: Size of buffer and number of bytes to read thereinto
* @start: Start offset to read from on disk
* @before_base: Number of bytes to read before the bootloader base
* @uuid: UUID string if supported, else NULL
* Return: 0 if OK, -ENOMEM if out of memory, -EIO on read failure
*/
static int cros_read_buf(struct bootflow *bflow, void *buf, ulong size,
loff_t start, ulong before_base, const char *uuid)
{
struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
ulong base, setup, cmdline, kern_base;
ulong num_blks;
int ret;
num_blks = size >> desc->log2blksz;
log_debug("Reading info to %lx, blk=%s, size=%lx, blocks=%lx\n",
(ulong)map_to_sysmem(buf), bflow->blk->name, size, num_blks);
ret = blk_read(bflow->blk, start, num_blks, buf);
if (ret != num_blks)
return log_msg_ret("inf", -EIO);
base = map_to_sysmem(buf) + before_base;
setup = base + X86_SETUP_OFFSET;
cmdline = base + CMDLINE_OFFSET;
kern_base = base + X86_KERNEL_OFFSET;
log_debug("base %lx setup %lx cmdline %lx kern_base %lx\n", base,
setup, cmdline, kern_base);
#ifdef CONFIG_X86
const char *version;
version = zimage_get_kernel_version(map_sysmem(setup, 0),
map_sysmem(kern_base, 0));
log_debug("version %s\n", version);
if (version)
bflow->name = strdup(version);
#endif
if (!bflow->name)
bflow->name = strdup("ChromeOS");
if (!bflow->name)
return log_msg_ret("nam", -ENOMEM);
bflow->os_name = strdup("ChromeOS");
if (!bflow->os_name)
return log_msg_ret("os", -ENOMEM);
ret = copy_cmdline(map_sysmem(cmdline, 0), uuid, &bflow->cmdline);
if (ret)
return log_msg_ret("cmd", ret);
bflow->x86_setup = map_sysmem(setup, 0);
return 0;
}
/**
* cros_read_info() - Read information and fill out the bootflow
*
* @bflow: Bootflow to update
* @uuid: UUID string if supported, else NULL
* @preamble: Kernel preamble information
* Return: 0 if OK, -ENOMEM if out of memory, -EIO on read failure
*/
static int cros_read_info(struct bootflow *bflow, const char *uuid,
const struct vb2_kernel_preamble *preamble)
{
struct cros_priv *priv = bflow->bootmeth_priv;
struct udevice *blk = bflow->blk;
struct blk_desc *desc = dev_get_uclass_plat(blk);
ulong offset, size, before_base;
void *buf;
int ret;
log_debug("Kernel preamble at %lx, version major %x, minor %x\n",
(ulong)map_to_sysmem(preamble),
preamble->header_version_major,
preamble->header_version_minor);
log_debug(" - load_address %lx, bl_addr %lx, bl_size %lx\n",
(ulong)preamble->body_load_address,
(ulong)preamble->bootloader_address,
(ulong)preamble->bootloader_size);
priv->body_size = preamble->body_signature.data_size;
priv->body_load_address = preamble->body_load_address;
priv->bootloader_address = preamble->bootloader_address;
priv->bootloader_size = preamble->bootloader_size;
log_debug("Kernel body at %lx size %lx\n", priv->body_offset,
priv->body_size);
/* Work out how many bytes to read before the bootloader base */
before_base = -CMDLINE_OFFSET;
/* Read the cmdline through to the end of the bootloader */
size = priv->bootloader_size + before_base;
offset = priv->body_offset +
(priv->bootloader_address - priv->body_load_address) +
CMDLINE_OFFSET;
buf = malloc(size);
if (!buf)
return log_msg_ret("buf", -ENOMEM);
ret = cros_read_buf(bflow, buf, size,
priv->part_start + (offset >> desc->log2blksz),
before_base, uuid);
if (ret) {
/* Clear this since the buffer is invalid */
bflow->x86_setup = NULL;
free(buf);
return log_msg_ret("pro", ret);
}
priv->info_buf = buf;
return 0;
}
static int cros_read_kernel(struct bootflow *bflow)
{
struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
struct cros_priv *priv = bflow->bootmeth_priv;
ulong base, setup;
ulong num_blks;
void *buf;
int ret;
bflow->size = priv->body_size;
buf = memalign(SZ_1K, priv->body_size);
if (!buf)
return log_msg_ret("buf", -ENOMEM);
/* Check that the header is not smaller than permitted */
if (priv->body_offset < PROBE_SIZE)
return log_msg_ret("san", EFAULT);
/* Read kernel body */
num_blks = priv->body_size >> desc->log2blksz;
log_debug("Reading body to %lx, blk=%s, size=%lx, blocks=%lx\n",
(ulong)map_to_sysmem(buf), bflow->blk->name, priv->body_size,
num_blks);
ret = blk_read(bflow->blk,
priv->part_start + (priv->body_offset >> desc->log2blksz),
num_blks, buf);
if (ret != num_blks)
return log_msg_ret("inf", -EIO);
base = map_to_sysmem(buf) + priv->bootloader_address -
priv->body_load_address;
setup = base + X86_SETUP_OFFSET;
bflow->buf = buf;
bflow->x86_setup = map_sysmem(setup, 0);
return 0;
}
static int cros_read_bootflow(struct udevice *dev, struct bootflow *bflow)
{
const struct vb2_kernel_preamble *preamble;
struct disk_partition info;
struct vb2_keyblock *hdr;
const char *uuid = NULL;
struct cros_priv *priv;
int ret;
log_debug("starting, part=%x\n", bflow->part);
/* Check for kernel partitions */
ret = scan_part(bflow->blk, bflow->part, &info, &hdr);
if (ret) {
log_debug("- scan failed: err=%d\n", ret);
return log_msg_ret("scan", ret);
}
priv = malloc(sizeof(struct cros_priv));
if (!priv) {
free(hdr);
return log_msg_ret("buf", -ENOMEM);
}
bflow->bootmeth_priv = priv;
log_debug("Selected partition %d, header at %lx\n", bflow->part,
(ulong)map_to_sysmem(hdr));
/* Grab a few things from the preamble */
preamble = (void *)hdr + hdr->keyblock_size;
priv->body_offset = hdr->keyblock_size + preamble->preamble_size;
priv->part_start = info.start;
/* Now read everything we can learn about kernel */
#if CONFIG_IS_ENABLED(PARTITION_UUIDS)
uuid = info.uuid;
#endif
ret = cros_read_info(bflow, uuid, preamble);
preamble = NULL;
free(hdr);
if (ret) {
free(priv->info_buf);
free(priv);
return log_msg_ret("inf", ret);
}
bflow->size = priv->body_size;
bflow->state = BOOTFLOWST_READY;
return 0;
}
static int cros_read_file(struct udevice *dev, struct bootflow *bflow,
const char *file_path, ulong addr, ulong *sizep)
{
return -ENOSYS;
}
#if CONFIG_IS_ENABLED(BOOTSTD_FULL)
static int cros_read_all(struct udevice *dev, struct bootflow *bflow)
{
int ret;
if (bflow->buf)
return log_msg_ret("ld", -EALREADY);
ret = cros_read_kernel(bflow);
if (ret)
return log_msg_ret("rd", ret);
return 0;
}
#endif /* BOOTSTD_FULL */
static int cros_boot(struct udevice *dev, struct bootflow *bflow)
{
int ret;
if (!bflow->buf) {
ret = cros_read_kernel(bflow);
if (ret)
return log_msg_ret("rd", ret);
}
if (IS_ENABLED(CONFIG_X86)) {
ret = zboot_run(map_to_sysmem(bflow->buf), bflow->size, 0, 0,
map_to_sysmem(bflow->x86_setup),
bflow->cmdline);
} else {
ret = bootm_boot_start(map_to_sysmem(bflow->buf),
bflow->cmdline);
}
return log_msg_ret("go", ret);
}
static int cros_bootmeth_bind(struct udevice *dev)
{
struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
plat->desc = "ChromiumOS boot";
plat->flags = BOOTMETHF_ANY_PART;
return 0;
}
static struct bootmeth_ops cros_bootmeth_ops = {
.check = cros_check,
.read_bootflow = cros_read_bootflow,
.read_file = cros_read_file,
.boot = cros_boot,
#if CONFIG_IS_ENABLED(BOOTSTD_FULL)
.read_all = cros_read_all,
#endif /* BOOTSTD_FULL */
};
static const struct udevice_id cros_bootmeth_ids[] = {
{ .compatible = "u-boot,cros" },
{ }
};
U_BOOT_DRIVER(bootmeth_cros) = {
.name = "bootmeth_cros",
.id = UCLASS_BOOTMETH,
.of_match = cros_bootmeth_ids,
.ops = &cros_bootmeth_ops,
.bind = cros_bootmeth_bind,
};