252 lines
6.8 KiB
C
252 lines
6.8 KiB
C
/*
|
|
* Copyright (c) 2020 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include "efi.h"
|
|
#include "printf.h"
|
|
#include <zefi-segments.h>
|
|
#include <zephyr/arch/x86/efi.h>
|
|
|
|
#define PUTCHAR_BUFSZ 128
|
|
|
|
/* EFI GUID for RSDP
|
|
* See "Finding the RSDP on UEFI Enabled Systems" in ACPI specs.
|
|
*/
|
|
#define ACPI_1_0_RSDP_EFI_GUID \
|
|
{ \
|
|
.Data1 = 0xeb9d2d30, \
|
|
.Data2 = 0x2d88, \
|
|
.Data3 = 0x11d3, \
|
|
.Data4 = { 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, \
|
|
}
|
|
|
|
#define ACPI_2_0_RSDP_EFI_GUID \
|
|
{ \
|
|
.Data1 = 0x8868e871, \
|
|
.Data2 = 0xe4f1, \
|
|
.Data3 = 0x11d3, \
|
|
.Data4 = { 0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81 }, \
|
|
}
|
|
|
|
#define EFI_LOADED_IMAGE_PROTOCOL_GUID \
|
|
{ \
|
|
.Data1 = 0x5b1b31a1, \
|
|
.Data2 = 0x9562, \
|
|
.Data3 = 0x11d2, \
|
|
.Data4 = { 0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b } \
|
|
}
|
|
|
|
/* The linker places this dummy last in the data memory. We can't use
|
|
* traditional linker address symbols because we're relocatable; the
|
|
* linker doesn't know what the runtime address will be. The compiler
|
|
* has to emit code to find this thing's address at runtime via an
|
|
* offset from RIP. It's a qword so we can guarantee alignment of the
|
|
* stuff after.
|
|
*/
|
|
static __attribute__((section(".runtime_data_end")))
|
|
uint64_t runtime_data_end[1] = { 0x1111aa8888aa1111L };
|
|
|
|
#define EXT_DATA_START ((void *) &runtime_data_end[1])
|
|
|
|
static struct efi_system_table *efi;
|
|
static struct efi_boot_arg efi_arg;
|
|
|
|
static void efi_putchar(int c)
|
|
{
|
|
static uint16_t efibuf[PUTCHAR_BUFSZ + 1];
|
|
static int n;
|
|
|
|
if (c == '\n') {
|
|
efi_putchar('\r');
|
|
}
|
|
|
|
efibuf[n] = c;
|
|
++n;
|
|
|
|
if (c == '\n' || n == PUTCHAR_BUFSZ) {
|
|
efibuf[n] = 0U;
|
|
efi->ConOut->OutputString(efi->ConOut, efibuf);
|
|
n = 0;
|
|
}
|
|
}
|
|
|
|
static inline bool efi_guid_compare(efi_guid_t *s1, efi_guid_t *s2)
|
|
{
|
|
return ((s1->Part1 == s2->Part1) && (s1->Part2 == s2->Part2));
|
|
}
|
|
|
|
static void *efi_config_get_vendor_table_by_guid(efi_guid_t *guid)
|
|
{
|
|
struct efi_configuration_table *ect_tmp;
|
|
int n_ct;
|
|
|
|
if (efi == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
ect_tmp = efi->ConfigurationTable;
|
|
|
|
for (n_ct = 0; n_ct < efi->NumberOfTableEntries; n_ct++) {
|
|
if (efi_guid_compare(&ect_tmp->VendorGuid, guid)) {
|
|
return ect_tmp->VendorTable;
|
|
}
|
|
|
|
ect_tmp++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void efi_prepare_boot_arg(void)
|
|
{
|
|
efi_guid_t rsdp_guid_1 = ACPI_1_0_RSDP_EFI_GUID;
|
|
efi_guid_t rsdp_guid_2 = ACPI_2_0_RSDP_EFI_GUID;
|
|
|
|
/* Let's lookup for most recent ACPI table first */
|
|
efi_arg.acpi_rsdp = efi_config_get_vendor_table_by_guid(&rsdp_guid_2);
|
|
if (efi_arg.acpi_rsdp == NULL) {
|
|
efi_arg.acpi_rsdp =
|
|
efi_config_get_vendor_table_by_guid(&rsdp_guid_1);
|
|
}
|
|
|
|
if (efi_arg.acpi_rsdp != NULL) {
|
|
printf("RSDP found at %p\n", efi_arg.acpi_rsdp);
|
|
}
|
|
}
|
|
|
|
/* Existing x86_64 EFI environments have a bad habit of leaving the
|
|
* HPET timer running. This then fires later on, once the OS has
|
|
* started. If the timing isn't right, it can happen before the OS
|
|
* HPET driver gets a chance to disable it. And because we do the
|
|
* handoff (necessarily) with interrupts disabled, it's not actually
|
|
* possible for the OS to reliably disable it in time anyway.
|
|
*
|
|
* Basically: it's our job as the bootloader to ensure that no
|
|
* interrupt sources are live before entering the OS. Clear the
|
|
* interrupt enable bit of HPET timer zero.
|
|
*/
|
|
static void disable_hpet(void)
|
|
{
|
|
uint64_t *hpet = (uint64_t *)0xfed00000L;
|
|
|
|
hpet[32] &= ~4;
|
|
}
|
|
|
|
/* FIXME: if you check the generated code, "ms_abi" calls like this
|
|
* have to SPILL HALF OF THE SSE REGISTER SET TO THE STACK on entry
|
|
* because of the way the conventions collide. Is there a way to
|
|
* prevent/suppress that?
|
|
*/
|
|
uintptr_t __abi efi_entry(void *img_handle, struct efi_system_table *sys_tab)
|
|
{
|
|
#ifndef CONFIG_DYNAMIC_BOOTARGS
|
|
(void)img_handle;
|
|
#endif /* CONFIG_DYNAMIC_BOOTARGS */
|
|
|
|
efi = sys_tab;
|
|
z_putchar = efi_putchar;
|
|
printf("*** Zephyr EFI Loader ***\n");
|
|
|
|
efi_prepare_boot_arg();
|
|
|
|
for (int i = 0; i < sizeof(zefi_zsegs)/sizeof(zefi_zsegs[0]); i++) {
|
|
int bytes = zefi_zsegs[i].sz;
|
|
uint8_t *dst = (uint8_t *)zefi_zsegs[i].addr;
|
|
|
|
printf("Zeroing %d bytes of memory at %p\n", bytes, dst);
|
|
for (int j = 0; j < bytes; j++) {
|
|
dst[j] = 0U;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < sizeof(zefi_dsegs)/sizeof(zefi_dsegs[0]); i++) {
|
|
int bytes = zefi_dsegs[i].sz;
|
|
int off = zefi_dsegs[i].off;
|
|
uint8_t *dst = (uint8_t *)zefi_dsegs[i].addr;
|
|
uint8_t *src = &((uint8_t *)EXT_DATA_START)[off];
|
|
|
|
printf("Copying %d data bytes to %p from image offset %d\n",
|
|
bytes, dst, zefi_dsegs[i].off);
|
|
for (int j = 0; j < bytes; j++) {
|
|
dst[j] = src[j];
|
|
}
|
|
|
|
/* Page-aligned blocks below 1M are the .locore
|
|
* section, which has a jump in its first bytes for
|
|
* the benefit of 32 bit entry. Those have to be
|
|
* written over with NOP instructions. (See comment
|
|
* about OUTRAGEOUS HACK in locore.S) before Zephyr
|
|
* starts, because the very first thing it does is
|
|
* install its own page table that disallows writes.
|
|
*/
|
|
if (((long)dst & 0xfff) == 0 && dst < (uint8_t *)0x100000L) {
|
|
for (int i = 0; i < 8; i++) {
|
|
dst[i] = 0x90; /* 0x90 == 1-byte NOP */
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_DYNAMIC_BOOTARGS
|
|
char *dst_bootargs = (char *)zefi_bootargs;
|
|
struct efi_loaded_image_protocol *loaded_image;
|
|
efi_guid_t loaded_image_protocol = EFI_LOADED_IMAGE_PROTOCOL_GUID;
|
|
efi_status_t loaded_image_status = sys_tab->BootServices->HandleProtocol(
|
|
img_handle,
|
|
&loaded_image_protocol,
|
|
(void **)&loaded_image
|
|
);
|
|
|
|
if (loaded_image_status == EFI_SUCCESS) {
|
|
uint16_t *src_bootargs = (uint16_t *)loaded_image->LoadOptions;
|
|
|
|
while (*src_bootargs != '\0' &&
|
|
dst_bootargs + 1 <
|
|
(char *)zefi_bootargs + CONFIG_BOOTARGS_ARGS_BUFFER_SIZE) {
|
|
*dst_bootargs++ = *src_bootargs++ & 0x7f;
|
|
}
|
|
*dst_bootargs = '\0';
|
|
} else {
|
|
*dst_bootargs = '\0';
|
|
}
|
|
#endif /* CONFIG_DYNAMIC_BOOTARGS */
|
|
|
|
unsigned char *code = (void *)zefi_entry;
|
|
|
|
efi_arg.efi_systab = efi;
|
|
__asm__ volatile("movq %%cr3, %0" : "=r"(efi_arg.efi_cr3));
|
|
|
|
printf("Jumping to Entry Point: %p (%x %x %x %x %x %x %x)\n",
|
|
code, code[0], code[1], code[2], code[3],
|
|
code[4], code[5], code[6]);
|
|
|
|
disable_hpet();
|
|
|
|
/* The EFI console seems to be buffered, give it a little time
|
|
* to drain before we start banging on the same UART from the
|
|
* OS.
|
|
*/
|
|
for (volatile int i = 0; i < 50000000; i++) {
|
|
}
|
|
|
|
__asm__ volatile("cli; movq %0, %%rbx; jmp *%1"
|
|
:: "r"(&efi_arg), "r"(code) : "rbx");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Trick cribbed shamelessly from gnu-efi. We need to emit a ".reloc"
|
|
* section into the image with a single dummy entry for the EFI loader
|
|
* to think we're a valid PE file, gcc won't because it thinks we're
|
|
* ELF.
|
|
*/
|
|
uint32_t relocation_dummy;
|
|
__asm__(".section .reloc\n"
|
|
"base_relocation_block:\n"
|
|
".long relocation_dummy - base_relocation_block\n"
|
|
".long 0x0a\n"
|
|
".word 0\n");
|