| /* |
| * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * Alternatively, this software may be distributed under the terms of the |
| * GNU General Public License ("GPL") version 2 as published by the Free |
| * Software Foundation. |
| */ |
| |
| #include <common.h> |
| #include <malloc.h> |
| #include <mapmem.h> |
| #include <part.h> |
| #include <asm/io.h> |
| #include <linux/compiler.h> |
| #include <cros/boot_kernel.h> |
| #include <cros/common.h> |
| #include <cros/crossystem_data.h> |
| #include <cros/vboot.h> |
| #include <i8042.h> |
| #include <cros/cros_fdtdec.h> |
| #include <cros/fmap.h> |
| #ifdef CONFIG_X86 |
| #include <asm/zimage.h> |
| #endif |
| |
| #include <vboot_api.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| enum { CROS_32BIT_ENTRY_ADDR = 0x100000 }; |
| |
| /* |
| * We uses a static variable to communicate with ft_board_setup(). |
| * For more information, please see commit log. |
| */ |
| static crossystem_data_t *g_crossystem_data; |
| |
| /* defined in common/cmd_bootm.c */ |
| int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]); |
| |
| /* Maximum kernel command-line size */ |
| #define CROS_CONFIG_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 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. |
| * |
| * @param kernel_buffer Address of kernel buffer in memory |
| * @param 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 + CROS_CONFIG_SIZE); |
| } |
| |
| static uint32_t get_dev_num(const block_dev_desc_t *dev) |
| { |
| return dev->dev; |
| } |
| |
| /* assert(0 <= val && val < 99); sprintf(dst, "%u", val); */ |
| static char *itoa(char *dst, int val) |
| { |
| if (val > 9) |
| *dst++ = '0' + val / 10; |
| *dst++ = '0' + val % 10; |
| return dst; |
| } |
| |
| /* copied from x86 bootstub code; sprintf(dst, "%02x", val) */ |
| static void one_byte(char *dst, uint8_t val) |
| { |
| dst[0] = "0123456789abcdef"[(val >> 4) & 0x0F]; |
| dst[1] = "0123456789abcdef"[val & 0x0F]; |
| } |
| |
| /* copied from x86 bootstub code; display a GUID in canonical form */ |
| static char *emit_guid(char *dst, uint8_t *guid) |
| { |
| one_byte(dst, guid[3]); dst += 2; |
| one_byte(dst, guid[2]); dst += 2; |
| one_byte(dst, guid[1]); dst += 2; |
| one_byte(dst, guid[0]); dst += 2; |
| *dst++ = '-'; |
| one_byte(dst, guid[5]); dst += 2; |
| one_byte(dst, guid[4]); dst += 2; |
| *dst++ = '-'; |
| one_byte(dst, guid[7]); dst += 2; |
| one_byte(dst, guid[6]); dst += 2; |
| *dst++ = '-'; |
| one_byte(dst, guid[8]); dst += 2; |
| one_byte(dst, guid[9]); dst += 2; |
| *dst++ = '-'; |
| one_byte(dst, guid[10]); dst += 2; |
| one_byte(dst, guid[11]); dst += 2; |
| one_byte(dst, guid[12]); dst += 2; |
| one_byte(dst, guid[13]); dst += 2; |
| one_byte(dst, guid[14]); dst += 2; |
| one_byte(dst, guid[15]); dst += 2; |
| return dst; |
| } |
| |
| /** |
| * 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". |
| * |
| * @param src Input string |
| * @param devnum Device number of the storage device we will mount |
| * @param partnum Partition number of the root file system we will mount |
| * @param guid GUID of the kernel partition |
| * @param dst Output string |
| * @param 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, uint8_t *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) { |
| VBDEBUG("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)) { \ |
| VBDEBUG("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': |
| VBDEBUG("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); |
| dst = itoa(dst, devnum); |
| } else { |
| CHECK_SPACE(2); |
| *dst++ = 'a' + devnum; |
| } |
| break; |
| case 'P': |
| CHECK_SPACE(3); |
| dst = itoa(dst, partnum); |
| break; |
| case 'U': |
| /* GUID replacement needs 36 bytes */ |
| CHECK_SPACE(36 + 1); |
| dst = emit_guid(dst, guid); |
| break; |
| default: |
| CHECK_SPACE(3); |
| *dst++ = '%'; |
| *dst++ = c; |
| break; |
| } |
| } |
| |
| #undef CHECK_SPACE |
| |
| *dst = '\0'; |
| return 0; |
| } |
| |
| int boot_kernel(struct vboot_info *vboot, VbSelectAndLoadKernelParams *kparams, |
| crossystem_data_t *cdata) |
| { |
| /* sizeof(CHROMEOS_BOOTARGS) reserves extra 1 byte */ |
| char cmdline_buf[sizeof(CHROMEOS_BOOTARGS) + CROS_CONFIG_SIZE]; |
| /* Reserve EXTRA_BUFFER bytes for update_cmdline's string replacement */ |
| char cmdline_out[sizeof(CHROMEOS_BOOTARGS) + CROS_CONFIG_SIZE + |
| EXTRA_BUFFER]; |
| char *cmdline; |
| #ifdef CONFIG_X86 |
| struct boot_params *params; |
| #else |
| /* Chrome OS kernel has to be loaded at fixed location */ |
| char address[20]; |
| char *argv[] = { "bootm", address }; |
| |
| sprintf(address, "%#08lx", |
| (ulong)map_to_sysmem(kparams->kernel_buffer)); |
| #endif |
| |
| if (!vboot == !cdata) { |
| VBDEBUG("Must pass exactly one of vboot or cdata\n"); |
| return -1; |
| } |
| |
| strcpy(cmdline_buf, CHROMEOS_BOOTARGS); |
| |
| /* |
| * bootloader_address is the offset in kernel image plus kernel body |
| * load address; so subtrate 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, kernel buffer is not |
| * always allocated at that address (nor even recommended to be). |
| * |
| * Because this address does not effect 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 codes here. |
| */ |
| cmdline = get_kernel_config(kparams->kernel_buffer, |
| kparams->bootloader_address - CROS_32BIT_ENTRY_ADDR); |
| /* |
| * strncat could write CROS_CONFIG_SIZE + 1 bytes to cmdline_buf. This |
| * is okay because the extra 1 byte has been reserved in sizeof(). |
| */ |
| strncat(cmdline_buf, cmdline, CROS_CONFIG_SIZE); |
| |
| VBDEBUG("cmdline before update: "); |
| VBDEBUG_PUTS(cmdline_buf); |
| VBDEBUG_PUTS("\n"); |
| |
| if (update_cmdline(cmdline_buf, |
| get_dev_num(kparams->disk_handle), |
| kparams->partition_number + 1, |
| kparams->partition_guid, |
| cmdline_out, sizeof(cmdline_out))) { |
| VBDEBUG("failed replace %%[DUP] in command line\n"); |
| return 1; |
| } |
| |
| setenv("bootargs", cmdline_out); |
| VBDEBUG("cmdline after update: "); |
| VBDEBUG_PUTS(getenv("bootargs")); |
| VBDEBUG_PUTS("\n"); |
| |
| g_crossystem_data = cdata; |
| boot_kernel_vboot_ptr = vboot; |
| |
| /* Disable keyboard and flush buffer so that further key strokes |
| * won't interfere kernel driver init. */ |
| #ifdef CONFIG_I8042_KBD |
| if (i8042_disable()) |
| VBDEBUG("i8042_disable() failed. fine, continue.\n"); |
| i8042_flush(); |
| #endif |
| |
| #ifdef CONFIG_X86 |
| if (vboot) |
| vboot_update_acpi(vboot); |
| else |
| crossystem_data_update_acpi(cdata); |
| |
| params = (struct boot_params *)(uintptr_t) |
| (kparams->bootloader_address - CROS_PARAMS_SIZE); |
| if (!setup_zimage(params, cmdline, 0, 0, 0)) |
| boot_zimage(params, kparams->kernel_buffer); |
| #else |
| cmd_tbl_t cmdtp; |
| |
| cmdtp.name = "bootm"; |
| do_bootm(&cmdtp, 0, ARRAY_SIZE(argv), argv); |
| #endif |
| boot_kernel_vboot_ptr = NULL; |
| |
| VBDEBUG("failed to boot; is kernel broken?\n"); |
| return 1; |
| } |
| |
| #ifdef CONFIG_OF_BOARD_SETUP |
| /* Optional function */ |
| __weak int ft_system_setup(void *blob, bd_t *bd) |
| { |
| return 0; |
| } |
| |
| static int ft_board_add_elog(void *fdt) |
| { |
| #ifdef CONFIG_ELOG |
| int res, node, config, elog, len, new_len, i, j; |
| uint32_t panic_event[2]; |
| uint32_t elog_area[2]; |
| uint32_t panic_start, panic_size; |
| const uint32_t *reg; |
| uint32_t *new_reg; |
| struct twostop_fmap fmap; |
| |
| res = fdt_ensure_subnode(fdt, 0, "firmware"); |
| if (res < 0) { |
| VBDEBUG("Failed to add /firmware\n"); |
| return res; |
| } |
| res = fdt_ensure_subnode(fdt, res, "chromeos"); |
| if (res < 0) { |
| VBDEBUG("Failed to add /firmware/chromeos\n"); |
| return res; |
| } |
| node = res; |
| |
| res = fdt_setprop_string(fdt, node, "compatible", "chromeos-firmware"); |
| if (res < 0) { |
| VBDEBUG("Failed to set the compatible property\n"); |
| return res; |
| } |
| |
| config = cros_fdtdec_config_node(gd->fdt_blob); |
| if (config < 0) { |
| VBDEBUG("Couldn't find config node.\n"); |
| return config; |
| } |
| elog = fdtdec_lookup_phandle(gd->fdt_blob, config, "firmware-storage"); |
| if (elog < 0) { |
| VBDEBUG("Couldn't find elog info in firmware-storage\n"); |
| return elog; |
| } |
| |
| /* fdtdec_get_int_array already does endian conversion for us */ |
| res = fdtdec_get_int_array(gd->fdt_blob, elog, |
| "elog-panic-event-offset", |
| panic_event, 2); |
| if (res < 0) { |
| VBDEBUG("Can't find panic event\n"); |
| return res; |
| } |
| |
| /* Convert offset into a real DRAM address. */ |
| panic_start = panic_event[0] + CONFIG_SYS_SDRAM_BASE; |
| panic_size = panic_event[1]; |
| |
| panic_event[0] = cpu_to_fdt32(panic_start); |
| panic_event[1] = cpu_to_fdt32(panic_size); |
| |
| res = fdt_setprop(fdt, node, "elog-panic-event", panic_event, |
| sizeof(panic_event)); |
| if (res < 0) { |
| VBDEBUG("Failed to set up elog-panic-event property\n"); |
| return res; |
| } |
| |
| if (cros_fdtdec_flashmap(gd->fdt_blob, &fmap)) { |
| VBDEBUG("Unable to find RW_ELOG in FMAP\n"); |
| return -1; |
| } |
| |
| elog_area[0] = cpu_to_fdt32(fmap.elog.offset); |
| elog_area[1] = cpu_to_fdt32(fmap.elog.length); |
| res = fdt_setprop(fdt, node, "elog-area", elog_area, |
| sizeof(elog_area)); |
| if (res < 0) { |
| VBDEBUG("Failed to set elog-area property\n"); |
| return res; |
| } |
| |
| node = fdt_subnode_offset(fdt, 0, "memory"); |
| if (node < 0) { |
| VBDEBUG("Couldn't find /memory node\n"); |
| return node; |
| } |
| |
| reg = fdt_getprop(fdt, node, "reg", &len); |
| if (!reg) { |
| VBDEBUG("Failed to read the /memory/reg property\n"); |
| return -1; |
| } |
| |
| new_reg = malloc(3 * len); |
| if (!new_reg) { |
| VBDEBUG("Failed to allocate space for new_reg\n"); |
| return -1; |
| } |
| |
| /* Subtract the panic buffer from the memory regions */ |
| for (i = 0, j = 0, new_len = 0; i < len / sizeof(*reg); |
| i += 2, j += 2, new_len += 2 * sizeof(*reg)) { |
| uint32_t start = fdt32_to_cpu(reg[i]); |
| uint32_t size = fdt32_to_cpu(reg[i + 1]); |
| uint32_t end = start + size; |
| |
| uint32_t panic_end = panic_start + panic_size; |
| |
| if (panic_start >= end || panic_end <= start) { |
| /* No overlap, pass the range through */ |
| new_reg[j] = cpu_to_fdt32(start); |
| new_reg[j + 1] = cpu_to_fdt32(size); |
| } else if (panic_start <= start && panic_end >= end) { |
| /* Total overlap, drop the range entirely */ |
| j -= 2; |
| new_len -= 2 * sizeof(*reg); |
| } else if (panic_start > start && panic_end < end) { |
| /* Partial interior overlap, cut the range in two */ |
| new_reg[j] = cpu_to_fdt32(start); |
| new_reg[j + 1] = cpu_to_fdt32(panic_start - start); |
| j += 2; |
| new_len += 2 * sizeof(*reg); |
| new_reg[j] = cpu_to_fdt32(panic_end); |
| new_reg[j + 1] = cpu_to_fdt32(end - panic_end); |
| } else if (panic_start <= start) { |
| /* Overlap on the low end, trim the region */ |
| new_reg[j] = cpu_to_fdt32(panic_end); |
| new_reg[j + 1] = cpu_to_fdt32(end - panic_end); |
| } else if (panic_end >= end) { |
| /* Overlap on the high end, trim the region */ |
| new_reg[j] = cpu_to_fdt32(start); |
| new_reg[j + 1] = cpu_to_fdt32(panic_start - start); |
| } else { |
| free(new_reg); |
| return -1; |
| } |
| } |
| |
| res = fdt_setprop(fdt, node, "reg", new_reg, new_len); |
| if (res < 0) { |
| VBDEBUG("Failed to set trimmed /memory/reg property\n"); |
| free(new_reg); |
| return res; |
| } |
| |
| free(new_reg); |
| |
| #endif |
| return 0; |
| } |
| |
| /* |
| * This function does the last chance FDT update before booting to kernel. |
| * Currently we modify the FDT by embedding crossystem data. So before |
| * calling bootm(), g_crossystem_data should be set. |
| */ |
| int ft_board_setup(void *fdt, bd_t *bd) |
| { |
| struct vboot_info *vboot = boot_kernel_vboot_ptr; |
| crossystem_data_t *cdata = g_crossystem_data; |
| int err; |
| |
| /* This function should be provided by the board file */ |
| err = ft_system_setup(fdt, bd); |
| if (err) { |
| VBDEBUG("warning: fdt_system_setup() fails\n"); |
| return err; |
| } |
| |
| if (!vboot == !cdata) { |
| VBDEBUG("warning: Must pass exactly one of vboot or cdata\n"); |
| return 0; |
| } |
| |
| if (vboot) { |
| err = vboot_write_to_fdt(vboot, fdt); |
| if (err) { |
| VBDEBUG("cdata_write_to_fdt() failed\n"); |
| return err; |
| } |
| } else { |
| /* Legacy code crosbug.com/p/21810 */ |
| err = crossystem_data_embed_into_fdt(cdata, fdt); |
| if (err) { |
| VBDEBUG("crossystem_data_embed_into_fdt() failed\n"); |
| return err; |
| } |
| } |
| |
| err = ft_board_add_elog(fdt); |
| if (err) { |
| VBDEBUG("Failed to add elog information\n"); |
| return err; |
| } |
| |
| VBDEBUG("Completed setting up fdt information for kerrnel\n"); |
| |
| return 0; |
| } |
| #endif |