blob: c2f550d817ec3a8c8d629435117f7979342c243b [file] [log] [blame]
/*
* Copyright (C) 2017 Google Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but without any warranty; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <libpayload.h>
#include <stdint.h>
#include <stdlib.h>
#include "base/cleanup_funcs.h"
#include "boot/bootdata.h"
#include "boot/multiboot.h"
#include "config.h"
#include "base/timestamp.h"
#include "vboot/boot_policy.h"
#include "vboot/boot.h"
static const int debug = 0;
// Mini allocator for multiboot parameters
static void *_mb_alloc_end, *_mb_alloc_ptr;
static void mb_alloc_init(void *addr, size_t size)
{
memset(addr, 0, size);
_mb_alloc_ptr = addr;
_mb_alloc_end = addr + size;
}
static void *mb_alloc(size_t length)
{
void *ptr;
size_t len = ALIGN_UP(length, sizeof(uint32_t));
if (_mb_alloc_ptr + len > _mb_alloc_end)
return NULL;
ptr = _mb_alloc_ptr;
_mb_alloc_ptr += len;
return ptr;
}
static void multiboot_start(struct multiboot_header *header,
struct multiboot_info *info)
{
printf("Starting multiboot kernel @ %p (info @ %p)\n\n",
(void *)header->entry_addr, info);
timestamp_add_now(TS_START_KERNEL);
__asm__ __volatile__ (
"cli\n"
"jmp *%[entry]\n"
:
:[entry] "c"(header->entry_addr),
"a"(MULTIBOOT_BOOTLOADER_MAGIC),
"b"((uintptr_t)info));
}
static struct multiboot_header *multiboot_find(void *start)
{
struct multiboot_header *header;
int i;
if (!start)
return NULL;
for (i = 0; i < MULTIBOOT_SEARCH; i += MULTIBOOT_HEADER_ALIGN) {
header = (struct multiboot_header *)(start + i);
if (header && header->magic == MULTIBOOT_HEADER_MAGIC &&
!(header->magic + header->flags + header->checksum))
return header;
}
printf("%s: unable to find multiboot magic\n", __func__);
return NULL;
}
static void dump_multiboot_header(struct multiboot_header *header)
{
if (debug) {
printf("multiboot header @ %p\n", header);
printf(" magic : 0x%08x\n", header->magic);
printf(" flags : 0x%08x\n", header->flags);
printf(" checksum : 0x%08x\n", header->checksum);
printf(" header_addr : 0x%08x\n", header->header_addr);
printf(" load_addr : 0x%08x\n", header->load_addr);
printf(" load_end_addr : 0x%08x\n", header->load_end_addr);
printf(" bss_end_addr : 0x%08x\n", header->bss_end_addr);
printf(" entry_addr : 0x%08x\n", header->entry_addr);
}
}
static void dump_boot_info(struct boot_info *bi)
{
if (debug) {
printf("boot_info @ %p\n", bi);
printf(" bi->kernel : %p\n", bi->kernel);
printf(" bi->cmd_line : %p\n", bi->cmd_line);
printf(" bi->params : %p\n", bi->params);
printf(" bi->ramdisk_addr : %p\n", bi->ramdisk_addr);
printf(" bi->ramdisk_size : %zu\n", bi->ramdisk_size);
printf(" bi->loader : %p\n", bi->loader);
}
}
int multiboot_fill_boot_info(struct boot_info *bi)
{
struct multiboot_header *header;
// Multiboot header should be within the first 8K of kernel image
header = multiboot_find(bi->kparams->kernel_buffer);
if (!header)
return -1;
// Validate header fields
if (header->load_addr > header->header_addr || !header->load_end_addr)
return -1;
// Require address fields in the header
if (!(header->flags & MULTIBOOT_AOUT_KLUDGE)) {
printf("%s: multiboot header does not report addresses\n",
__func__);
return -1;
}
dump_multiboot_header(header);
// Kernel should be loaded at expected offset already by vboot
if ((uintptr_t)bi->kparams->kernel_buffer !=
(uintptr_t)header->load_addr) {
printf("%s: multiboot kernel not loaded to address %p\n",
__func__, (void *)header->load_addr);
return -1;
}
// Point to multiboot header so it can be found again easily
bi->kernel = header;
// Use bootloader as ramdisk
bi->ramdisk_addr = (void *)(uintptr_t)bi->kparams->bootloader_address;
bi->ramdisk_size = bi->kparams->bootloader_size;
// Boot parameters come before ramdisk
bi->params = bi->ramdisk_addr - CrosParamSize;
// Kernel command line comes before boot parameters
bi->cmd_line = bi->params - CmdLineSize;
// Indicate end of BSS as locaion for bootloader to put data
if (header->bss_end_addr)
bi->loader = (void *)ALIGN_UP(header->bss_end_addr, 4096);
else
bi->loader = (void *)ALIGN_UP(header->load_end_addr, 4096);
dump_boot_info(bi);
return 0;
}
/*
* Example image loaded by vboot and processed by multiboot_fill_boot_info():
* 00100000:001c3000 kparams->kernel_buffer
* 00100050:001c3000 bi->kernel (multiboot header)
* 001c3000:001c4000 bi->cmd_line
* 001c4000:001c5000 bi->params
* 001c5000:00503000 bi->ramdisk_addr
*
* Multiboot kernel BSS is at end of kernel image so adjust the layout to:
* 00100000:001c3000 bi->kparams->kernel_buffer
* 00100050:001c3000 bi->kernel
* 001c3000:0023c000 bss (zeroed)
* 0023c000:0023d000 bi->cmd_line
* 0023d000:0023e000 bi->params
* 0023e000:0057c000 bi->ramdisk_addr
*/
static int multiboot_load(struct boot_info *bi)
{
struct multiboot_header *header = bi->kernel;
// Move params+ramdisk out of kernel BSS region
memmove(bi->loader + CmdLineSize, bi->params,
bi->ramdisk_size + CrosParamSize);
bi->params = bi->loader + CmdLineSize;
bi->ramdisk_addr = bi->params + CrosParamSize;
// bi->cmd_line is re-allocated during processing so restore it
memset(bi->loader, 0, CmdLineSize);
if (bi->cmd_line)
strncpy(bi->loader, bi->cmd_line,
strlen(bi->cmd_line) < CmdLineSize ?
strlen(bi->cmd_line) : CmdLineSize);
bi->cmd_line = bi->loader;
// Clear kernel BSS region
if (header->bss_end_addr)
memset((void *)header->load_end_addr, 0,
header->bss_end_addr - header->load_end_addr);
dump_boot_info(bi);
return 0;
}
static struct multiboot_mmap_entry *multiboot_mmap_create(size_t *count)
{
struct multiboot_mmap_entry *start, *entry;
size_t i, c;
if (lib_sysinfo.n_memranges == 0)
return NULL;
start = mb_alloc(sizeof(*entry) * lib_sysinfo.n_memranges);
if (!start)
return NULL;
for (i = c = 0, entry = start; i < lib_sysinfo.n_memranges; i++) {
struct memrange *range = &lib_sysinfo.memrange[i];
if (range->size == 0)
continue;
entry->size = sizeof(*entry) - sizeof(entry->size);
entry->addr = range->base;
entry->len = range->size;
switch (range->type) {
case CB_MEM_RAM:
entry->type = MULTIBOOT_MEMORY_AVAILABLE;
break;
case CB_MEM_NVS:
entry->type = MULTIBOOT_MEMORY_NVS;
break;
case CB_MEM_ACPI:
entry->type = MULTIBOOT_MEMORY_ACPI_RECLAIMABLE;
break;
default:
entry->type = MULTIBOOT_MEMORY_RESERVED;
break;
}
entry++;
c++;
}
*count = c;
return start;
}
static struct multiboot_info *multiboot_info(struct boot_info *bi)
{
struct multiboot_info *info;
struct multiboot_mmap_entry *mmap;
size_t mmap_count;
// Initialize the multiboot info allocator
mb_alloc_init(bi->params, CrosParamSize);
info = mb_alloc(sizeof(*info));
if (!info)
return NULL;
// Report command line
if (bi->cmd_line) {
info->cmdline = (uint32_t)bi->cmd_line;
info->flags |= MULTIBOOT_INFO_CMDLINE;
}
// Report ramdisk start and size
if (bi->ramdisk_addr && bi->ramdisk_size) {
struct multiboot_mod_list *list = mb_alloc(sizeof(*list));
if (!list)
return NULL;
list->mod_start = (uint32_t)bi->ramdisk_addr;
list->mod_end = (uint32_t)(bi->ramdisk_addr + bi->ramdisk_size);
info->mods_addr = (uint32_t)list;
info->mods_count = 1;
info->flags |= MULTIBOOT_INFO_MODS;
}
// Generate multiboot memory map
mmap = multiboot_mmap_create(&mmap_count);
if (mmap && mmap_count > 0) {
info->mmap_addr = (uint32_t)mmap;
info->mmap_length = mmap_count * sizeof(*mmap);
info->flags |= MULTIBOOT_INFO_MEM_MAP;
}
return info;
}
int multiboot_boot(struct boot_info *bi)
{
struct multiboot_header *header = bi->kernel;
struct multiboot_info *info;
if (!header || header->magic != MULTIBOOT_HEADER_MAGIC) {
printf("%s: Multiboot header not found at %p\n",
__func__, header);
return -1;
}
// Add bootdata info structures if a bootdata kernel is found.
// This can change bi->ramdisk_size, so do it before setting up
// the Multiboot structures that describe the ramdisk.
if (CONFIG(KERNEL_MULTIBOOT_BOOTDATA))
bootdata_prepare(bi);
// Load multiboot data into proper locations for boot
if (multiboot_load(bi) < 0)
return -1;
// Prepare the multiboot info structure
info = multiboot_info(bi);
if (!info)
return -1;
run_cleanup_funcs(CleanupOnHandoff);
// Start the kernel
multiboot_start(header, info);
return 0;
}