/* * 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 #include #include #include #include #ifdef CONFIG_X86 #include #endif #include #define PREFIX "boot_kernel: " #ifdef CONFIG_OF_UPDATE_FDT_BEFORE_BOOT /* * We uses a static variable to communicate with fit_update_fdt_before_boot(). * For more information, please see commit log. */ static crossystem_data_t *g_crossystem_data = NULL; #endif /* CONFIG_OF_UPDATE_FDT_BEFORE_BOOT */ /* 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 /** * 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 bootloader_address is the address of the bootloader in the buffer * @return kernel config address */ static char *get_kernel_config(char *bootloader_address) { /* Use the bootloader address to find the kernel config location. */ return bootloader_address - 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(PREFIX "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(PREFIX "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(PREFIX "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(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, "%p", kparams->kernel_buffer); #endif strcpy(cmdline_buf, CHROMEOS_BOOTARGS); /* * casting bootloader_address of uint64_t type to uintptr_t before * further casting it to char * to avoid compiler warning "cast to * pointer from integer of different size" on 32-bit address machine. */ cmdline = get_kernel_config((char *) (uintptr_t)kparams->bootloader_address); /* * 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(PREFIX "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(PREFIX "failed replace %%[DUP] in command line\n"); return 1; } setenv("bootargs", cmdline_out); VBDEBUG(PREFIX "cmdline after update: "); VBDEBUG_PUTS(getenv("bootargs")); VBDEBUG_PUTS("\n"); #ifdef CONFIG_OF_UPDATE_FDT_BEFORE_BOOT g_crossystem_data = cdata; #endif /* CONFIG_OF_UPDATE_FDT_BEFORE_BOOT */ #ifdef CONFIG_X86 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 do_bootm(NULL, 0, ARRAY_SIZE(argv), argv); #endif VBDEBUG(PREFIX "failed to boot; is kernel broken?\n"); return 1; } #ifdef CONFIG_OF_UPDATE_FDT_BEFORE_BOOT /* * 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 fit_update_fdt_before_boot(char *fdt, ulong *new_size) { uint32_t ns; if (!g_crossystem_data) { VBDEBUG(PREFIX "warning: g_crossystem_data is NULL\n"); return 0; } if (crossystem_data_embed_into_fdt(g_crossystem_data, fdt, &ns)) { VBDEBUG(PREFIX "crossystem_data_embed_into_fdt() failed\n"); return 0; } *new_size = ns; return 0; } #endif /* CONFIG_OF_UPDATE_FDT_BEFORE_BOOT */