blob: 698278a929324742b1a9b4dfd448b28253b2309f [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY LOGC_VBOOT
#include <common.h>
#include <blk.h>
#include <bloblist.h>
#include <command.h>
#include <dm.h>
#include <env.h>
#include <log.h>
#include <mapmem.h>
#include <uuid.h>
#ifdef CONFIG_X86
#include <asm/bootm.h>
#include <asm/zimage.h>
#endif
#include <cros/crossystem.h>
#include <cros/cros_common.h>
#include <cros/vboot.h>
#include <dm/device-internal.h>
/*
* The Chrome OS kernel file has the following format:
*
* 0 Vboot header (used by the vboot library). At offset 4f0 is the
* bootloader address (BLO), assuming that the kernel (at 8000) is
* loaded at CROS_32BIT_ENTRY_ADDR
* This header is easy to recognise as the first bytes are
* "CHROMEOS"
* 8000 Kernel start
* BLO - 1000 Setup block (x86)
* BLO - 2000 Command line
*/
enum {
CROS_32BIT_ENTRY_ADDR = 0x100000
};
/* Maximum kernel command-line size */
#define CMDLINE_SIZE 4096
/* Size of the x86 zeropage table */
#define CROS_PARAMS_SIZE 4096
/* Extra buffer to string replacement */
#define EXTRA_BUFFER 4096
/* Pointer to the vboot information so we can handle ft_board_setup() */
struct vboot_info *boot_kernel_vboot_ptr;
/**
* This loads the kernel command line from the buffer that holds the loaded
* kernel * image. This function calculates the address of the command line from
* the * bootloader address.
*
* @kernel_buffer: Address of kernel buffer in memory
* @bootloader_offset: Offset of bootloader in kernel_buffer
* @return kernel config address
*/
static char *get_kernel_config(void *kernel_buffer, size_t bootloader_offset)
{
/* Use the bootloader address to find the kernel config location */
return kernel_buffer + bootloader_offset -
(CROS_PARAMS_SIZE + CMDLINE_SIZE);
}
/**
* get_dev_num() - Gets the block-device number from a device
*
* @dev: Block device to check
* @return device number
*/
static u32 get_dev_num(const struct udevice *dev)
{
const struct blk_desc *desc = dev_get_uclass_plat(dev);
return desc->devnum;
}
/**
* update_cmdline() - Set up the command line for booting
*
* This replaces:
* %D -> device number
* %P -> partition number
* %U -> GUID
* in kernel command line.
*
* For example:
* ("root=/dev/sd%D%P", 2, 3) -> "root=/dev/sdc3"
* ("root=/dev/mmcblk%Dp%P", 0, 5) -> "root=/dev/mmcblk0p5".
*
* TODO(sjg@chromium.org): Consider using U-Boot's command-substition feature
* instead
*
* @src: Input string
* @devnum: Device number of the storage device we will mount
* @partnum: Partition number of the root file system we will mount
* @guid: GUID of the kernel partition as a string
* @dst: Output string
* @dst_size: Size of output string
* @return zero if it succeeds, non-zero if it fails
*/
static int update_cmdline(char *src, int devnum, int partnum, char *guid,
char *dst, int dst_size)
{
char *dst_end = dst + dst_size;
int c;
/* sanity check on inputs */
if (devnum < 0 || devnum > 25 || partnum < 1 || partnum > 99 ||
dst_size < 0 || dst_size > 10000) {
log_err("insane input: %d, %d, %d\n", devnum, partnum,
dst_size);
return 1;
}
/*
* Condition "dst + X <= dst_end" checks if there is at least X bytes
* left in dst. We use X > 1 so that there is always 1 byte for '\0'
* after the loop.
*
* We constantly estimate how many bytes we are going to write to dst
* for copying characters from src or for the string replacements, and
* check if there is sufficient space.
*/
#define CHECK_SPACE(bytes) \
if (!(dst + (bytes) <= dst_end)) { \
log_debug("fail: need at least %d bytes\n", (bytes)); \
return 1; \
}
while ((c = *src++)) {
if (c != '%') {
CHECK_SPACE(2);
*dst++ = c;
continue;
}
switch ((c = *src++)) {
case '\0':
log_debug("mal-formed input: end in '%%'\n");
return 1;
case 'D':
/*
* TODO: Do we have any better way to know whether %D
* is replaced by a letter or digits? So far, this is
* done by a rule of thumb that if %D is followed by a
* 'p' character, then it is replaced by digits.
*/
if (*src == 'p') {
CHECK_SPACE(3);
strcpy(dst, simple_itoa(devnum));
} else {
CHECK_SPACE(2);
*dst++ = 'a' + devnum;
}
break;
case 'P':
CHECK_SPACE(3);
strcpy(dst, simple_itoa(partnum));
break;
case 'U':
/* GUID replacement needs 36 bytes */
CHECK_SPACE(UUID_STR_LEN + 1);
strncpy(dst, guid, UUID_STR_LEN);
dst += UUID_STR_LEN;
break;
default:
CHECK_SPACE(3);
*dst++ = '%';
*dst++ = c;
break;
}
}
#undef CHECK_SPACE
*dst = '\0';
return 0;
}
/**
* boot_kernel() - Boot a kernel using either bootm (ARM) or zimage (x86)
*
* @vboot: vboot_info pointer
* @kparams: Kernel parameters from vboot
* @return does not return on success as it jump to the kernel; 1 on error
*/
static int boot_kernel(struct vboot_info *vboot,
VbSelectAndLoadKernelParams *kparams)
{
/* sizeof(CHROMEOS_BOOTARGS) reserves extra 1 byte */
char cmdline_buf[sizeof(CHROMEOS_BOOTARGS) + CMDLINE_SIZE];
/* Reserve EXTRA_BUFFER bytes for update_cmdline's string replacement */
char *cmdline;
struct udevice *dev;
char guid[UUID_STR_LEN + 1];
#ifdef CONFIG_X86
struct boot_params *params;
int ret;
#endif
#ifndef CONFIG_X86
/* Chromium OS kernel has to be loaded at fixed location */
struct cmd_tbl cmdtp;
ulong addr = map_to_sysmem(kparams->kernel_buffer);
char address[20];
char *argv[] = { "bootm", address };
sprintf(address, "%08lx", addr);
#endif
strcpy(cmdline_buf, CHROMEOS_BOOTARGS);
/*
* bootloader_address is the offset in kernel image plus kernel body
* load address; so subtract this address from bootloader_address and
* you have the offset.
*
* Note that kernel body load address is kept in kernel preamble but
* actually serves no real purpose; for one, the kernel buffer is not
* always allocated at that address (nor even recommended to be).
*
* Because this address does not affect kernel-buffer location (or in
* fact anything else), the current consensus is not to adjust this
* address on a per-board basis.
*
* If for any unforeseeable reason this address is going to be not
* CROS_32BIT_ENTRY_ADDR=0x100000, please also update the code here.
*/
cmdline = get_kernel_config(kparams->kernel_buffer,
kparams->bootloader_address -
CROS_32BIT_ENTRY_ADDR);
/*
* strncat could write CMDLINE_SIZE + 1 bytes to cmdline_buf. This
* is okay because the extra 1 byte has been reserved in sizeof().
*/
strncat(cmdline_buf, cmdline, CMDLINE_SIZE);
#ifdef LOG_DEBUG
printf("cmdline before update: ptr=%p, len %dn", cmdline_buf,
strlen(cmdline_buf));
/* Print directly to avoid printf() buffer size limits */
puts(cmdline_buf);
printf("\n");
#endif
uuid_bin_to_str(kparams->partition_guid, guid, UUID_STR_FORMAT_GUID);
log_info("partition_number=%d, guid=%s\n", kparams->partition_number,
guid);
if (update_cmdline(cmdline_buf, get_dev_num(kparams->disk_handle),
kparams->partition_number + 1, guid, cmdline,
CMDLINE_SIZE)) {
log_err("failed replace %%[DUP] in command line\n");
return 1;
}
#ifdef LOG_DEBUG
printf("cmdline after update: ptr=%p, len %d\n", cmdline,
strlen(cmdline));
/* Print directly to avoid printf() buffer size limits */
puts(cmdline);
printf("\n");
#endif
env_set("bootargs", cmdline);
boot_kernel_vboot_ptr = vboot;
/*
* Disable keyboard and flush buffer so that further keystrokes
* won't interfere kernel driver init
*/
uclass_first_device(UCLASS_KEYBOARD, &dev);
if (dev)
device_remove(dev, DM_REMOVE_NORMAL);
log_info("Bloblist:\n");
bloblist_show_list();
#ifdef CONFIG_X86
ret = vboot_update_acpi(vboot, FIRMWARE_TYPE_AUTO_DETECT);
if (ret)
log_warning("Failed to write vboot to ACPI (err=%d)\n", ret);
params = (struct boot_params *)(cmdline + CMDLINE_SIZE);
log_debug("kernel_buffer=%p, size=%x, bootloader_address=%llx, size=%x, cmdline=%p, params=%p\n",
kparams->kernel_buffer, kparams->kernel_buffer_size,
kparams->bootloader_address, kparams->bootloader_size,
cmdline, params);
log_buffer(LOGC_VBOOT, LOGL_DEBUG, (ulong)params + 0x1f1,
(void *)params + 0x1f1, 1, 0xf, 0);
if (!setup_zimage(params, cmdline, 0, 0, 0, 0)) {
#ifdef LOG_DEBUG
zimage_dump(params);
#endif
log_buffer(LOGC_VBOOT, LOGL_DEBUG,
(ulong)kparams->kernel_buffer,
kparams->kernel_buffer, 1, 0x100, 0);
log_debug("go %p, %p\n", params, kparams->kernel_buffer);
boot_linux_kernel((ulong)params, (ulong)kparams->kernel_buffer,
false);
}
#else
cmdtp.name = "bootm";
do_bootm(&cmdtp, 0, ARRAY_SIZE(argv), argv);
#endif
boot_kernel_vboot_ptr = NULL;
log_debug("failed to boot; is kernel broken?\n");
return 1;
}
int vboot_rw_boot_kernel(struct vboot_info *vboot)
{
int ret;
bootstage_mark(BOOTSTAGE_VBOOT_DONE);
ret = boot_kernel(vboot, &vboot->kparams);
if (ret)
return log_msg_ret("boot", ret);
return 0;
}