| // 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 <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; | |
| efi_guid_t 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, type.b, UUID_STR_FORMAT_GUID)) | |
| return log_msg_ret("typ", -EINVAL); | |
| if (guidcmp(&cros_kern_type, &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); | |
| if (!bootflow_img_add(bflow, "setup", | |
| (enum bootflow_img_t)IH_TYPE_X86_SETUP, | |
| setup, 0x3000)) | |
| return log_msg_ret("cri", -ENOMEM); | |
| bflow->x86_setup = map_sysmem(setup, 0); | |
| if (!bootflow_img_add(bflow, "cmdline", BFI_CMDLINE, cmdline, 0x1000)) | |
| return log_msg_ret("crc", -ENOMEM); | |
| 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; | |
| if (!bootflow_img_add(bflow, "kernel", | |
| (enum bootflow_img_t)IH_TYPE_KERNEL, 0, | |
| priv->body_size)) | |
| return log_msg_ret("crk", -ENOMEM); | |
| 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, | |
| enum bootflow_img_t type, 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, | |
| }; |