mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-31 12:08:19 +00:00 
			
		
		
		
	This has a typo which makes the method inoperable. Correct it so that 'bootflow read' works correctly for ChromeOS. Signed-off-by: Simon Glass <sjg@chromium.org>
		
			
				
	
	
		
			478 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			478 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_start(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,
 | |
| };
 |