| /* |
| * 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 <part.h> |
| #include <chromeos/common.h> |
| #include <chromeos/gpio.h> |
| #include <chromeos/kernel_shared_data.h> |
| #include <chromeos/load_kernel_helper.h> |
| #include <chromeos/os_storage.h> |
| #include <chromeos/vboot_nvstorage_helper.h> |
| |
| /* TODO For load fmap; remove when not used */ |
| #include <chromeos/firmware_storage.h> |
| |
| /* TODO For strcpy; remove when not used */ |
| #include <linux/string.h> |
| |
| /* TODO For GoogleBinaryBlockHeader; remove when not used */ |
| #include <gbb_header.h> |
| |
| /* TODO remove when not used */ |
| extern uint64_t get_nvcxt_lba(void); |
| |
| /* defined in common/cmd_bootm.c */ |
| int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]); |
| |
| #include <load_kernel_fw.h> |
| #include <vboot_nvstorage.h> |
| #include <vboot_struct.h> |
| |
| #undef PREFIX |
| #define PREFIX "load_kernel_wrapper: " |
| |
| int load_kernel_wrapper_core(LoadKernelParams *params, |
| void *gbb_data, uint64_t gbb_size, |
| uint64_t boot_flags, VbNvContext *nvcxt, |
| uint8_t *shared_data_blob, |
| int bypass_load_kernel) |
| { |
| /* |
| * TODO(clchiou): Hack for bringing up factory; preserve recovery |
| * reason before LoadKernel destroys it. Remove when not needed. |
| */ |
| uint32_t reason = 0; |
| VbNvGet(nvcxt, VBNV_RECOVERY_REQUEST, &reason); |
| |
| int status = LOAD_KERNEL_NOT_FOUND; |
| block_dev_desc_t *dev_desc; |
| |
| memset(params, '\0', sizeof(*params)); |
| |
| if (!bypass_load_kernel) { |
| dev_desc = get_bootdev(); |
| if (!dev_desc) { |
| VBDEBUG(PREFIX "get_bootdev fail\n"); |
| goto EXIT; |
| } |
| } |
| |
| params->gbb_data = gbb_data; |
| params->gbb_size = gbb_size; |
| |
| params->boot_flags = boot_flags; |
| params->shared_data_blob = shared_data_blob ? shared_data_blob : |
| (uint8_t *) CONFIG_VB_SHARED_DATA_BLOB; |
| params->shared_data_size = CONFIG_VB_SHARED_DATA_SIZE; |
| |
| params->bytes_per_lba = get_bytes_per_lba(); |
| params->ending_lba = get_ending_lba(); |
| |
| params->kernel_buffer = (uint8_t *) CONFIG_LOADADDR; |
| params->kernel_buffer_size = CONFIG_MAX_KERNEL_SIZE; |
| |
| params->nv_context = nvcxt; |
| |
| VBDEBUG(PREFIX "call LoadKernel() with parameters...\n"); |
| VBDEBUG(PREFIX "shared_data_blob: 0x%p\n", |
| params->shared_data_blob); |
| VBDEBUG(PREFIX "bytes_per_lba: %d\n", |
| (int) params->bytes_per_lba); |
| VBDEBUG(PREFIX "ending_lba: 0x%08x\n", |
| (int) params->ending_lba); |
| VBDEBUG(PREFIX "kernel_buffer: 0x%p\n", |
| params->kernel_buffer); |
| VBDEBUG(PREFIX "kernel_buffer_size: 0x%08x\n", |
| (int) params->kernel_buffer_size); |
| VBDEBUG(PREFIX "boot_flags: 0x%08x\n", |
| (int) params->boot_flags); |
| |
| if (!bypass_load_kernel) { |
| status = LoadKernel(params); |
| } else { |
| status = LOAD_KERNEL_SUCCESS; |
| params->partition_number = 2; |
| } |
| |
| EXIT: |
| VBDEBUG(PREFIX "LoadKernel status: %d\n", status); |
| if (status == LOAD_KERNEL_SUCCESS) { |
| VBDEBUG(PREFIX "partition_number: 0x%08x\n", |
| (int) params->partition_number); |
| VBDEBUG(PREFIX "bootloader_address: 0x%08x\n", |
| (int) params->bootloader_address); |
| VBDEBUG(PREFIX "bootloader_size: 0x%08x\n", |
| (int) params->bootloader_size); |
| |
| if (params->partition_number == 2) { |
| setenv("kernelpart", "2"); |
| setenv("rootpart", "3"); |
| } else if (params->partition_number == 4) { |
| setenv("kernelpart", "4"); |
| setenv("rootpart", "5"); |
| } else { |
| VBDEBUG(PREFIX "unknown kernel partition: %d\n", |
| (int) params->partition_number); |
| status = LOAD_KERNEL_NOT_FOUND; |
| } |
| } |
| |
| /* |
| * TODO(clchiou): This is an urgent hack for bringing up factory. We |
| * fill in data that will be used by kernel at last 1MB space. |
| * |
| * Rewrite this part after the protocol specification between |
| * Chrome OS firmware and kernel is finalized. |
| */ |
| if (status == LOAD_KERNEL_SUCCESS) { |
| KernelSharedDataType *sd = get_kernel_shared_data(); |
| |
| GoogleBinaryBlockHeader *gbbh = |
| (GoogleBinaryBlockHeader*) gbb_data; |
| const char* hwid; |
| int i; |
| |
| VBDEBUG(PREFIX "kernel shared data at %p\n", sd); |
| |
| /* |
| * chsw bit value |
| * bit 0x00000002 : recovery button pressed |
| * bit 0x00000020 : developer mode enabled |
| * bit 0x00000200 : firmware write protect disabled |
| */ |
| if (params->boot_flags & BOOT_FLAG_RECOVERY) |
| sd->chsw |= 0x002; |
| if (params->boot_flags & BOOT_FLAG_DEVELOPER) |
| sd->chsw |= 0x020; |
| if (!is_firmware_write_protect_gpio_asserted()) |
| sd->chsw |= 0x200; /* write protect is disabled */ |
| |
| /* |
| * "$GBB" is the GBB signaure. We can't use it as a whole |
| * or somethings that a compiler may think it is a whole. |
| * If the signature appears in the u-boot .constdata section |
| * gbb_utility will consider it a duplicated signature and |
| * will be failed. |
| */ |
| if (gbbh->signature[0] == '$' && |
| gbbh->signature[1] == 'G' && |
| gbbh->signature[2] == 'B' && |
| gbbh->signature[2] == gbbh->signature[3]) { |
| hwid = (const char*)(gbb_data + gbbh->hwid_offset); |
| } else { |
| /* Must be a debug workaround case */ |
| hwid = "Unknown"; |
| } |
| |
| strncpy((char*) sd->hwid, hwid, sizeof(sd->hwid)); |
| |
| /* boot reason; always 0 */ |
| sd->binf[0] = 0; |
| |
| /* active main firmware; TODO: rewritable B (=2) */ |
| if (params->boot_flags & BOOT_FLAG_RECOVERY) |
| sd->binf[1] = 0; |
| else |
| sd->binf[1] = 1; /* rewritable A */ |
| |
| /* active EC firmware; TODO: rewritable (=1) */ |
| sd->binf[2] = 0; |
| |
| /* active firmware type */ |
| if (params->boot_flags & BOOT_FLAG_RECOVERY) |
| sd->binf[3] = 0; |
| else if (params->boot_flags & BOOT_FLAG_DEVELOPER) |
| sd->binf[3] = 2; |
| else |
| sd->binf[3] = 1; |
| |
| /* recovery reason */ |
| sd->binf[4] = reason; |
| |
| memcpy(sd->shared_data_body, params->shared_data_blob, |
| params->shared_data_size); |
| sd->fmap_base = CONFIG_OFFSET_FMAP; |
| |
| sd->nvcxt_lba = get_nvcxt_lba(); |
| |
| memcpy(sd->nvcxt_cache, |
| params->nv_context->raw, VBNV_BLOCK_SIZE); |
| |
| VBDEBUG(PREFIX "version %08x\n", sd->version); |
| VBDEBUG(PREFIX "chsw %08x\n", sd->chsw); |
| for (i = 0; i < 5; i++) |
| VBDEBUG(PREFIX "binf[%2d] %08x\n", i, sd->binf[i]); |
| VBDEBUG(PREFIX "vbnv[ 0] %08x\n", sd->vbnv[0]); |
| VBDEBUG(PREFIX "vbnv[ 1] %08x\n", sd->vbnv[1]); |
| VBDEBUG(PREFIX "nvcxt %08llx\n", sd->nvcxt_lba); |
| VBDEBUG(PREFIX "nvcxt_c "); |
| for (i = 0; i < VBNV_BLOCK_SIZE; i++) |
| VBDEBUG("%02x", sd->nvcxt_cache[i]); |
| putc('\n'); |
| VBDEBUG(PREFIX "write_protect_sw %d\n", sd->write_protect_sw); |
| VBDEBUG(PREFIX "recovery_sw %d\n", sd->recovery_sw); |
| VBDEBUG(PREFIX "developer_sw %d\n", sd->developer_sw); |
| VBDEBUG(PREFIX "hwid \"%s\"\n", sd->hwid); |
| VBDEBUG(PREFIX "fwid \"%s\"\n", sd->fwid); |
| VBDEBUG(PREFIX "frid \"%s\"\n", sd->frid); |
| VBDEBUG(PREFIX "fmap %08x\n", sd->fmap_base); |
| } |
| |
| return status; |
| } |
| |
| int load_kernel_wrapper(LoadKernelParams *params, |
| void *gbb_data, uint64_t gbb_size, |
| uint64_t boot_flags, VbNvContext *nvcxt, |
| uint8_t *shared_data_blob) |
| { |
| return load_kernel_wrapper_core(params, gbb_data, gbb_size, boot_flags, |
| nvcxt, shared_data_blob, 0); |
| } |
| |
| /* Maximum kernel command-line size */ |
| #define CROS_CONFIG_SIZE 4096 |
| |
| /* Size of the x86 zeropage table */ |
| #define CROS_PARAMS_SIZE 4096 |
| |
| static int load_kernel_config(uint64_t bootloader_address) |
| { |
| char buf[80 + CROS_CONFIG_SIZE]; |
| |
| strcpy(buf, "setenv bootargs ${bootargs} "); |
| |
| /* Use the bootloader address to find the kernel config location. */ |
| strncat(buf, (char *)((uint32_t)bootloader_address - CROS_PARAMS_SIZE - |
| CROS_CONFIG_SIZE), CROS_CONFIG_SIZE); |
| |
| /* |
| * Use run_command instead of setenv because we need variable |
| * substitutions. |
| */ |
| if (run_command(buf, 0)) { |
| VBDEBUG(PREFIX "run_command(%s) fail\n", buf); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* 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; |
| } |
| |
| /* |
| * Replace: |
| * %D -> device number |
| * %P -> partition number |
| * %U -> GUID |
| * |
| * For example: |
| * ("root=/dev/sd%D%P", 2, 3) -> "root=/dev/sdc3" |
| * ("root=/dev/mmcblk%Dp%P", 0, 5) -> "root=/dev/mmcblk0p5". |
| * |
| * <cmdline> must have sufficient space for in-place update. |
| */ |
| static void update_cmdline(char *src, int devnum, int partnum, uint8_t *guid, |
| char *dst) |
| { |
| int c; |
| |
| // sanity check on inputs |
| if (devnum < 0 || devnum > 25 || partnum < 1 || partnum > 99) { |
| VBDEBUG(PREFIX "insane input: %d, %d\n", devnum, partnum); |
| devnum = 0; |
| partnum = 3; |
| } |
| |
| while ((c = *src++)) { |
| if (c != '%') { |
| *dst++ = c; |
| continue; |
| } |
| |
| switch ((c = *src++)) { |
| case '\0': |
| /* input ends in '%'; is it not well-formed? */ |
| src--; |
| break; |
| 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') |
| dst = itoa(dst, devnum); |
| else |
| *dst++ = 'a' + devnum; |
| break; |
| case 'P': |
| dst = itoa(dst, devnum); |
| break; |
| case 'U': |
| dst = emit_guid(dst, guid); |
| break; |
| default: |
| *dst++ = '%'; |
| *dst++ = c; |
| break; |
| } |
| } |
| |
| *dst = '\0'; |
| } |
| |
| static int boot_kernel(LoadKernelParams *params) |
| { |
| char *cmdline, cmdline_buf[4096]; |
| char load_address[32]; |
| char *argv[2] = { "bootm", load_address }; |
| |
| VBDEBUG(PREFIX "boot_kernel\n"); |
| VBDEBUG(PREFIX "kernel_buffer: 0x%p\n", |
| params->kernel_buffer); |
| VBDEBUG(PREFIX "bootloader_address: 0x%08x\n", |
| (int) params->bootloader_address); |
| |
| if (load_kernel_config(params->bootloader_address)) { |
| VBDEBUG(PREFIX "error: load kernel config failed\n"); |
| return LOAD_KERNEL_INVALID; |
| } |
| |
| if ((cmdline = getenv("bootargs"))) { |
| VBDEBUG(PREFIX "cmdline before update: %s\n", cmdline); |
| |
| update_cmdline(cmdline, get_device_number(), |
| params->partition_number + 1, |
| params->partition_guid, |
| cmdline_buf); |
| setenv("bootargs", cmdline_buf); |
| VBDEBUG(PREFIX "cmdline after update: %s\n", getenv("bootargs")); |
| } else { |
| VBDEBUG(PREFIX "bootargs == NULL\n"); |
| } |
| |
| /* |
| * FIXME: So far bootloader in kernel partition isn't really a |
| * bootloader; instead, it is merely a u-boot scripts that sets kernel |
| * parameters. And therefore we still have to boot kernel to here |
| * by calling do_bootm. |
| */ |
| sprintf(load_address, "0x%p", params->kernel_buffer); |
| VBDEBUG(PREFIX "run command: %s %s\n", argv[0], argv[1]); |
| do_bootm(NULL, 0, sizeof(argv)/sizeof(*argv), argv); |
| |
| /* should never reach here! */ |
| VBDEBUG(PREFIX "error: do_bootm() returned\n"); |
| return LOAD_KERNEL_INVALID; |
| } |
| |
| static void prepare_bootargs(void) |
| { |
| /* TODO move to u-boot-config */ |
| run_command("setenv console console=ttyS0,115200n8", 0); |
| run_command("setenv bootargs " |
| "${bootargs} ${console} ${platform_extras}", 0); |
| } |
| |
| int load_and_boot_kernel(void *gbb_data, uint64_t gbb_size, |
| uint64_t boot_flags) |
| { |
| LoadKernelParams params; |
| VbNvContext nvcxt; |
| int status; |
| |
| if (read_nvcontext(&nvcxt)) { |
| /* |
| * Even if we can't read nvcxt, we continue anyway because this |
| * is developer firmware |
| */ |
| VBDEBUG(PREFIX "fail to read nvcontext\n"); |
| } |
| |
| prepare_bootargs(); |
| |
| status = load_kernel_wrapper(¶ms, gbb_data, gbb_size, |
| boot_flags, &nvcxt, NULL); |
| |
| VBDEBUG(PREFIX "load_kernel_wrapper returns %d\n", status); |
| |
| if (VbNvTeardown(&nvcxt) || |
| (nvcxt.raw_changed && write_nvcontext(&nvcxt))) { |
| /* |
| * Even if we can't read nvcxt, we continue anyway because this |
| * is developer firmware |
| */ |
| VBDEBUG(PREFIX "fail to write nvcontext\n"); |
| } |
| |
| if (status == LOAD_KERNEL_SUCCESS) |
| return boot_kernel(¶ms); |
| |
| return status; |
| } |