blob: 193c5ee6f71ed415e52809ebcb00c47b7f5c072c [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <efi.h>
#include <efilib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cmdline.h>
#include <magenta.h>
#include <netboot.h>
#include <utils.h>
static EFI_GUID GraphicsOutputProtocol = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
#define DEFAULT_TIMEOUT 3
#define KBUFSIZE (32*1024*1024)
#define RBUFSIZE (256*1024*1024)
static char cmdextra[256];
static nbfile nbkernel;
static nbfile nbramdisk;
static nbfile nbcmdline;
nbfile* netboot_get_buffer(const char* name) {
// we know these are in a buffer large enough
// that this is safe (todo: implement strcmp)
if (!memcmp(name, "kernel.bin", 11)) {
return &nbkernel;
}
if (!memcmp(name, "ramdisk.bin", 11)) {
return &nbramdisk;
}
if (!memcmp(name, "cmdline", 7)) {
return &nbcmdline;
}
return NULL;
}
static char cmdline[4096];
enum {
BOOT_DEVICE_NONE,
BOOT_DEVICE_NETBOOT,
BOOT_DEVICE_LOCAL,
};
int boot_prompt(EFI_SYSTEM_TABLE* sys, int timeout_s) {
EFI_BOOT_SERVICES* bs = sys->BootServices;
EFI_EVENT TimerEvent;
EFI_EVENT WaitList[2];
EFI_STATUS status;
UINTN Index;
EFI_INPUT_KEY key;
memset(&key, 0, sizeof(key));
status = bs->CreateEvent(EVT_TIMER, 0, NULL, NULL, &TimerEvent);
if (status != EFI_SUCCESS) {
printf("could not create event timer: %s\n", efi_strerror(status));
return BOOT_DEVICE_NONE;
}
status = bs->SetTimer(TimerEvent, TimerPeriodic, 10000000);
if (status != EFI_SUCCESS) {
printf("could not set timer: %s\n", efi_strerror(status));
return BOOT_DEVICE_NONE;
}
int wait_idx = 0;
int key_idx = wait_idx;
WaitList[wait_idx++] = sys->ConIn->WaitForKey;
int timer_idx = wait_idx; // timer should always be last
WaitList[wait_idx++] = TimerEvent;
printf("Press (n) for netboot or (m) to boot the magenta.bin on the device\n");
// TODO: better event loop
do {
status = bs->WaitForEvent(wait_idx, WaitList, &Index);
// Check the timer
if (!EFI_ERROR(status)) {
if (Index == timer_idx) {
printf(".");
timeout_s--;
continue;
} else if (Index == key_idx) {
status = sys->ConIn->ReadKeyStroke(sys->ConIn, &key);
if (EFI_ERROR(status)) {
// clear the key and wait for another event
memset(&key, 0, sizeof(key));
}
}
} else {
printf("Error waiting for event: %s\n", efi_strerror(status));
return BOOT_DEVICE_NONE;
}
} while ((key.UnicodeChar != 'n' && key.UnicodeChar != 'm') && timeout_s);
printf("\n");
bs->CloseEvent(TimerEvent);
if (timeout_s > 0 || status == EFI_SUCCESS) {
if (key.UnicodeChar == 'n') {
return BOOT_DEVICE_NETBOOT;
} else if (key.UnicodeChar == 'm') {
return BOOT_DEVICE_LOCAL;
}
}
// Default to netboot
printf("Time out! Trying netboot...\n");
return BOOT_DEVICE_NETBOOT;
}
void set_graphics_mode(EFI_SYSTEM_TABLE* sys, EFI_GRAPHICS_OUTPUT_PROTOCOL* gop, char* cmdline) {
if (!gop || !cmdline) return;
char res[11];
if (cmdline_get(cmdline, "bootloader.fbres", res, sizeof(res)) < 0) return;
uint32_t hres = 0;
hres = atol(res);
char* x = strchr(res, 'x');
if (!x) return;
x++;
uint32_t vres = 0;
vres = atol(x);
if (!hres || !vres) return;
UINT32 max_mode = gop->Mode->MaxMode;
for (int i = 0; i < max_mode; i++) {
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION* mode_info;
UINTN info_size = 0;
EFI_STATUS status = gop->QueryMode(gop, i, &info_size, &mode_info);
if (EFI_ERROR(status)) {
printf("Could not retrieve mode %d: %s\n", i, efi_strerror(status));
continue;
}
if (mode_info->HorizontalResolution == hres &&
mode_info->VerticalResolution == vres) {
gop->SetMode(gop, i);
sys->BootServices->Stall(1000);
sys->ConOut->ClearScreen(sys->ConOut);
return;
}
}
printf("Could not find framebuffer mode %ux%u; using default mode = %ux%u\n",
hres, vres, gop->Mode->Info->HorizontalResolution, gop->Mode->Info->VerticalResolution);
sys->BootServices->Stall(5000000);
}
void draw_logo(EFI_GRAPHICS_OUTPUT_PROTOCOL* gop) {
if (!gop) return;
const uint32_t h_res = gop->Mode->Info->HorizontalResolution;
const uint32_t v_res = gop->Mode->Info->VerticalResolution;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL fuchsia = {
.Red = 0xFF,
.Green = 0x0,
.Blue = 0xFF,
};
EFI_GRAPHICS_OUTPUT_BLT_PIXEL black = {
.Red = 0x0,
.Green = 0x0,
.Blue = 0x0
};
// Blank the screen, removing vendor UEFI logos
gop->Blt(gop, &black, EfiBltVideoFill, 0, 0, 0, 0, h_res, v_res, 0);
// Draw the Fuchsia square in the top right corner
gop->Blt(gop, &fuchsia, EfiBltVideoFill, 0, 0, 0, 0, h_res, v_res/100, 0);
gop->Blt(gop, &fuchsia, EfiBltVideoFill, 0, 0, 0, v_res - (v_res/100), h_res, v_res/100, 0);
}
void do_netboot(EFI_HANDLE img, EFI_SYSTEM_TABLE* sys) {
EFI_BOOT_SERVICES* bs = sys->BootServices;
EFI_PHYSICAL_ADDRESS mem = 0xFFFFFFFF;
if (bs->AllocatePages(AllocateMaxAddress, EfiLoaderData, KBUFSIZE / 4096, &mem)) {
printf("Failed to allocate network io buffer\n");
return;
}
nbkernel.data = (void*) mem;
nbkernel.size = KBUFSIZE;
mem = 0xFFFFFFFF;
if (bs->AllocatePages(AllocateMaxAddress, EfiLoaderData, RBUFSIZE / 4096, &mem)) {
printf("Failed to allocate network io buffer\n");
return;
}
nbramdisk.data = (void*) mem;
nbramdisk.size = RBUFSIZE;
nbcmdline.data = (void*) cmdline;
nbcmdline.size = sizeof(cmdline) - 1;
nbcmdline.offset = 0;
cmdline[0] = 0;
printf("\nNetBoot Server Started...\n\n");
EFI_TPL prev_tpl = bs->RaiseTPL(TPL_NOTIFY);
for (;;) {
int n = netboot_poll();
if (n < 1) {
continue;
}
if (nbkernel.offset < 32768) {
// too small to be a kernel
continue;
}
uint8_t* x = nbkernel.data;
if ((x[0] == 'M') && (x[1] == 'Z') && (x[0x80] == 'P') && (x[0x81] == 'E')) {
UINTN exitdatasize;
EFI_STATUS r;
EFI_HANDLE h;
MEMMAP_DEVICE_PATH mempath[2] = {
{
.Header = {
.Type = HARDWARE_DEVICE_PATH,
.SubType = HW_MEMMAP_DP,
.Length = { (UINT8)(sizeof(MEMMAP_DEVICE_PATH) & 0xff),
(UINT8)((sizeof(MEMMAP_DEVICE_PATH) >> 8) & 0xff), },
},
.MemoryType = EfiLoaderData,
.StartingAddress = (EFI_PHYSICAL_ADDRESS)nbkernel.data,
.EndingAddress = (EFI_PHYSICAL_ADDRESS)(nbkernel.data + nbkernel.offset),
},
{
.Header = {
.Type = END_DEVICE_PATH_TYPE,
.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE,
.Length = { (UINT8)(sizeof(EFI_DEVICE_PATH) & 0xff),
(UINT8)((sizeof(EFI_DEVICE_PATH) >> 8) & 0xff), },
},
},
};
printf("Attempting to run EFI binary...\n");
r = bs->LoadImage(FALSE, img, (EFI_DEVICE_PATH*)mempath, (void*)nbkernel.data, nbkernel.offset, &h);
if (EFI_ERROR(r)) {
printf("LoadImage Failed (%s)\n", efi_strerror(r));
continue;
}
r = bs->StartImage(h, &exitdatasize, NULL);
if (EFI_ERROR(r)) {
printf("StartImage Failed %ld\n", r);
continue;
}
printf("\nNetBoot Server Resuming...\n");
continue;
}
// make sure network traffic is not in flight, etc
netboot_close();
// Restore the TPL before booting the kernel, or failing to netboot
bs->RestoreTPL(prev_tpl);
// ensure cmdline is null terminated
cmdline[nbcmdline.offset] = 0;
// maybe it's a kernel image?
EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
bs->LocateProtocol(&GraphicsOutputProtocol, NULL, (void**)&gop);
set_graphics_mode(sys, gop, cmdline);
boot_kernel(img, sys, (void*) nbkernel.data, nbkernel.offset,
(void*) nbramdisk.data, nbramdisk.offset,
cmdline, strlen(cmdline), cmdextra, strlen(cmdextra));
break;
}
}
EFI_STATUS efi_main(EFI_HANDLE img, EFI_SYSTEM_TABLE* sys) {
EFI_BOOT_SERVICES* bs = sys->BootServices;
InitializeLib(img, sys);
InitGoodies(img, sys);
uint64_t mmio;
if (FindPCIMMIO(bs, 0x0C, 0x03, 0x30, &mmio) == EFI_SUCCESS) {
sprintf(cmdextra, " xdc.mmio=0x%lx ", mmio);
} else {
cmdextra[0] = 0;
}
// Load the cmdline
UINTN csz = 0;
char* cmdline = LoadFile(L"cmdline", &csz);
if (cmdline) {
cmdline[csz] = '\0';
printf("cmdline: %s\n", cmdline);
}
EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
bs->LocateProtocol(&GraphicsOutputProtocol, NULL, (void**)&gop);
set_graphics_mode(sys, gop, cmdline);
draw_logo(gop);
printf("\nOSBOOT v0.2\n\n");
printf("Framebuffer base is at %lx\n\n", gop->Mode->FrameBufferBase);
// See if there's a network interface
bool have_network = netboot_init() == 0;
// Look for a kernel image on disk
// TODO: use the filesystem protocol
UINTN ksz = 0;
void* kernel = LoadFile(L"magenta.bin", &ksz);
if (!have_network && kernel == NULL) {
goto fail;
}
int boot_device = BOOT_DEVICE_NONE;
if (have_network) {
boot_device = BOOT_DEVICE_NETBOOT;
}
if (kernel != NULL) {
if (boot_device != BOOT_DEVICE_NONE) {
int timeout_s = cmdline_get_uint32(cmdline, "bootloader.timeout", DEFAULT_TIMEOUT);
boot_device = boot_prompt(sys, timeout_s);
} else {
boot_device = BOOT_DEVICE_LOCAL;
}
}
switch (boot_device) {
case BOOT_DEVICE_NETBOOT:
do_netboot(img, sys);
break;
case BOOT_DEVICE_LOCAL: {
UINTN rsz = 0;
void* ramdisk = LoadFile(L"ramdisk.bin", &rsz);
boot_kernel(img, sys, kernel, ksz, ramdisk, rsz,
cmdline, csz, cmdextra, strlen(cmdextra));
break;
}
default:
goto fail;
}
fail:
printf("\nBoot Failure\n");
WaitAnyKey();
return EFI_SUCCESS;
}