src/sys/arch/arm64/stand/efiboot/efiboot.c

1241 lines
27 KiB
C

/* $OpenBSD: efiboot.c,v 1.50 2024/02/23 21:52:12 kettenis Exp $ */
/*
* Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net>
* Copyright (c) 2016 Mark Kettenis
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <dev/cons.h>
#include <sys/disklabel.h>
#include <efi.h>
#include <efiapi.h>
#include <efiprot.h>
#include <eficonsctl.h>
#include <dev/biovar.h>
#include <dev/softraidvar.h>
#include <lib/libkern/libkern.h>
#include <lib/libsa/softraid.h>
#include <stand/boot/cmd.h>
#include "libsa.h"
#include "disk.h"
#include "softraid_arm64.h"
#include "efidev.h"
#include "efiboot.h"
#include "fdt.h"
EFI_SYSTEM_TABLE *ST;
EFI_BOOT_SERVICES *BS;
EFI_RUNTIME_SERVICES *RS;
EFI_HANDLE IH, efi_bootdp;
void *fdt_sys = NULL;
void *fdt_override = NULL;
size_t fdt_override_size;
void *smbios = NULL;
EFI_PHYSICAL_ADDRESS heap;
UINTN heapsiz = 1 * 1024 * 1024;
EFI_MEMORY_DESCRIPTOR *mmap;
UINTN mmap_key;
UINTN mmap_ndesc;
UINTN mmap_descsiz;
UINT32 mmap_version;
static EFI_GUID imgp_guid = LOADED_IMAGE_PROTOCOL;
static EFI_GUID blkio_guid = BLOCK_IO_PROTOCOL;
static EFI_GUID devp_guid = DEVICE_PATH_PROTOCOL;
static EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
static EFI_GUID fdt_guid = FDT_TABLE_GUID;
static EFI_GUID smbios_guid = SMBIOS_TABLE_GUID;
static EFI_GUID smbios3_guid = SMBIOS3_TABLE_GUID;
#define efi_guidcmp(_a, _b) memcmp((_a), (_b), sizeof(EFI_GUID))
int efi_device_path_depth(EFI_DEVICE_PATH *dp, int);
int efi_device_path_ncmp(EFI_DEVICE_PATH *, EFI_DEVICE_PATH *, int);
static void efi_heap_init(void);
static void efi_memprobe_internal(void);
static void efi_timer_init(void);
static void efi_timer_cleanup(void);
static EFI_STATUS efi_memprobe_find(UINTN, UINTN, EFI_MEMORY_TYPE,
EFI_PHYSICAL_ADDRESS *);
void *efi_fdt(void);
int fdt_load_override(char *);
extern void smbios_init(void *);
EFI_STATUS
efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
extern char *progname;
EFI_LOADED_IMAGE *imgp;
EFI_DEVICE_PATH *dp = NULL;
EFI_STATUS status;
int i;
ST = systab;
BS = ST->BootServices;
RS = ST->RuntimeServices;
IH = image;
/* disable reset by watchdog after 5 minutes */
BS->SetWatchdogTimer(0, 0, 0, NULL);
status = BS->HandleProtocol(image, &imgp_guid,
(void **)&imgp);
if (status == EFI_SUCCESS)
status = BS->HandleProtocol(imgp->DeviceHandle, &devp_guid,
(void **)&dp);
if (status == EFI_SUCCESS)
efi_bootdp = dp;
for (i = 0; i < ST->NumberOfTableEntries; i++) {
if (efi_guidcmp(&fdt_guid,
&ST->ConfigurationTable[i].VendorGuid) == 0)
fdt_sys = ST->ConfigurationTable[i].VendorTable;
if (efi_guidcmp(&smbios_guid,
&ST->ConfigurationTable[i].VendorGuid) == 0)
smbios = ST->ConfigurationTable[i].VendorTable;
if (efi_guidcmp(&smbios3_guid,
&ST->ConfigurationTable[i].VendorGuid) == 0)
smbios = ST->ConfigurationTable[i].VendorTable;
}
fdt_init(fdt_sys);
progname = "BOOTAA64";
boot(0);
return (EFI_SUCCESS);
}
static SIMPLE_TEXT_OUTPUT_INTERFACE *conout;
static SIMPLE_INPUT_INTERFACE *conin;
/*
* The device majors for these don't match the ones used by the
* kernel. That's fine. They're just used as an index into the cdevs
* array and never passed on to the kernel.
*/
static dev_t serial = makedev(1, 0);
static dev_t framebuffer = makedev(2, 0);
static char framebuffer_path[128];
void
efi_cons_probe(struct consdev *cn)
{
cn->cn_pri = CN_MIDPRI;
cn->cn_dev = makedev(0, 0);
}
void
efi_cons_init(struct consdev *cp)
{
conin = ST->ConIn;
conout = ST->ConOut;
}
int
efi_cons_getc(dev_t dev)
{
EFI_INPUT_KEY key;
EFI_STATUS status;
#if 0
UINTN dummy;
#endif
static int lastchar = 0;
if (lastchar) {
int r = lastchar;
if ((dev & 0x80) == 0)
lastchar = 0;
return (r);
}
status = conin->ReadKeyStroke(conin, &key);
while (status == EFI_NOT_READY || key.UnicodeChar == 0) {
if (dev & 0x80)
return (0);
/*
* XXX The implementation of WaitForEvent() in U-boot
* is broken and neverreturns.
*/
#if 0
BS->WaitForEvent(1, &conin->WaitForKey, &dummy);
#endif
status = conin->ReadKeyStroke(conin, &key);
}
if (dev & 0x80)
lastchar = key.UnicodeChar;
return (key.UnicodeChar);
}
void
efi_cons_putc(dev_t dev, int c)
{
CHAR16 buf[2];
if (c == '\n')
efi_cons_putc(dev, '\r');
buf[0] = c;
buf[1] = 0;
conout->OutputString(conout, buf);
}
void
efi_com_probe(struct consdev *cn)
{
cn->cn_pri = CN_LOWPRI;
cn->cn_dev = serial;
}
void
efi_com_init(struct consdev *cn)
{
conin = ST->ConIn;
conout = ST->ConOut;
}
int
efi_com_getc(dev_t dev)
{
return efi_cons_getc(dev);
}
void
efi_com_putc(dev_t dev, int c)
{
efi_cons_putc(dev, c);
}
void
efi_fb_probe(struct consdev *cn)
{
cn->cn_pri = CN_LOWPRI;
cn->cn_dev = framebuffer;
}
void
efi_fb_init(struct consdev *cn)
{
conin = ST->ConIn;
conout = ST->ConOut;
}
int
efi_fb_getc(dev_t dev)
{
return efi_cons_getc(dev);
}
void
efi_fb_putc(dev_t dev, int c)
{
efi_cons_putc(dev, c);
}
static void
efi_heap_init(void)
{
EFI_STATUS status;
status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
EFI_SIZE_TO_PAGES(heapsiz), &heap);
if (status != EFI_SUCCESS)
panic("BS->AllocatePages()");
}
struct disklist_lh disklist;
struct diskinfo *bootdev_dip;
void
efi_diskprobe(void)
{
int i, bootdev = 0, depth = -1;
UINTN sz;
EFI_STATUS status;
EFI_HANDLE *handles = NULL;
EFI_BLOCK_IO *blkio;
EFI_BLOCK_IO_MEDIA *media;
struct diskinfo *di;
EFI_DEVICE_PATH *dp;
TAILQ_INIT(&disklist);
sz = 0;
status = BS->LocateHandle(ByProtocol, &blkio_guid, 0, &sz, 0);
if (status == EFI_BUFFER_TOO_SMALL) {
handles = alloc(sz);
status = BS->LocateHandle(ByProtocol, &blkio_guid, 0, &sz,
handles);
}
if (handles == NULL || EFI_ERROR(status))
return;
if (efi_bootdp != NULL)
depth = efi_device_path_depth(efi_bootdp, MEDIA_DEVICE_PATH);
/*
* U-Boot incorrectly represents devices with a single
* MEDIA_DEVICE_PATH component. In that case include that
* component into the matching, otherwise we'll blindly select
* the first device.
*/
if (depth == 0)
depth = 1;
for (i = 0; i < sz / sizeof(EFI_HANDLE); i++) {
status = BS->HandleProtocol(handles[i], &blkio_guid,
(void **)&blkio);
if (EFI_ERROR(status))
panic("BS->HandleProtocol() returns %d", status);
media = blkio->Media;
if (media->LogicalPartition || !media->MediaPresent)
continue;
di = alloc(sizeof(struct diskinfo));
efid_init(di, blkio);
if (efi_bootdp == NULL || depth == -1 || bootdev != 0)
goto next;
status = BS->HandleProtocol(handles[i], &devp_guid,
(void **)&dp);
if (EFI_ERROR(status))
goto next;
if (efi_device_path_ncmp(efi_bootdp, dp, depth) == 0) {
TAILQ_INSERT_HEAD(&disklist, di, list);
bootdev_dip = di;
bootdev = 1;
continue;
}
next:
TAILQ_INSERT_TAIL(&disklist, di, list);
}
free(handles, sz);
/* Print available disks and probe for softraid. */
i = 0;
printf("disks:");
TAILQ_FOREACH(di, &disklist, list) {
printf(" sd%d%s", i, di == bootdev_dip ? "*" : "");
i++;
}
srprobe();
printf("\n");
}
/*
* Determine the number of nodes up to, but not including, the first
* node of the specified type.
*/
int
efi_device_path_depth(EFI_DEVICE_PATH *dp, int dptype)
{
int i;
for (i = 0; !IsDevicePathEnd(dp); dp = NextDevicePathNode(dp), i++) {
if (DevicePathType(dp) == dptype)
return (i);
}
return (i);
}
int
efi_device_path_ncmp(EFI_DEVICE_PATH *dpa, EFI_DEVICE_PATH *dpb, int deptn)
{
int i, cmp;
for (i = 0; i < deptn; i++) {
if (IsDevicePathEnd(dpa) || IsDevicePathEnd(dpb))
return ((IsDevicePathEnd(dpa) && IsDevicePathEnd(dpb))
? 0 : (IsDevicePathEnd(dpa))? -1 : 1);
cmp = DevicePathNodeLength(dpa) - DevicePathNodeLength(dpb);
if (cmp)
return (cmp);
cmp = memcmp(dpa, dpb, DevicePathNodeLength(dpa));
if (cmp)
return (cmp);
dpa = NextDevicePathNode(dpa);
dpb = NextDevicePathNode(dpb);
}
return (0);
}
void
efi_framebuffer(void)
{
EFI_GRAPHICS_OUTPUT *gop;
EFI_STATUS status;
void *node, *child;
uint32_t acells, scells;
uint64_t base, size;
uint32_t reg[4];
uint32_t width, height, stride;
char *format;
char *prop;
/*
* Don't create a "simple-framebuffer" node if we already have
* one. Besides "/chosen", we also check under "/" since that
* is where the Raspberry Pi firmware puts it.
*/
node = fdt_find_node("/chosen");
for (child = fdt_child_node(node); child;
child = fdt_next_node(child)) {
if (!fdt_node_is_compatible(child, "simple-framebuffer"))
continue;
if (!fdt_node_property(child, "status", &prop) ||
strcmp(prop, "okay") == 0) {
strlcpy(framebuffer_path, "/chosen/",
sizeof(framebuffer_path));
strlcat(framebuffer_path, fdt_node_name(child),
sizeof(framebuffer_path));
return;
}
}
node = fdt_find_node("/");
for (child = fdt_child_node(node); child;
child = fdt_next_node(child)) {
if (!fdt_node_is_compatible(child, "simple-framebuffer"))
continue;
if (!fdt_node_property(child, "status", &prop) ||
strcmp(prop, "okay") == 0) {
strlcpy(framebuffer_path, "/",
sizeof(framebuffer_path));
strlcat(framebuffer_path, fdt_node_name(child),
sizeof(framebuffer_path));
return;
}
}
status = BS->LocateProtocol(&gop_guid, NULL, (void **)&gop);
if (status != EFI_SUCCESS)
return;
/* Paranoia! */
if (gop == NULL || gop->Mode == NULL || gop->Mode->Info == NULL)
return;
/* We only support 32-bit pixel modes for now. */
switch (gop->Mode->Info->PixelFormat) {
case PixelRedGreenBlueReserved8BitPerColor:
format = "x8b8g8r8";
break;
case PixelBlueGreenRedReserved8BitPerColor:
format = "x8r8g8b8";
break;
default:
return;
}
base = gop->Mode->FrameBufferBase;
size = gop->Mode->FrameBufferSize;
width = htobe32(gop->Mode->Info->HorizontalResolution);
height = htobe32(gop->Mode->Info->VerticalResolution);
stride = htobe32(gop->Mode->Info->PixelsPerScanLine * 4);
node = fdt_find_node("/");
if (fdt_node_property_int(node, "#address-cells", &acells) != 1)
acells = 1;
if (fdt_node_property_int(node, "#size-cells", &scells) != 1)
scells = 1;
if (acells > 2 || scells > 2)
return;
if (acells >= 1)
reg[0] = htobe32(base);
if (acells == 2) {
reg[1] = reg[0];
reg[0] = htobe32(base >> 32);
}
if (scells >= 1)
reg[acells] = htobe32(size);
if (scells == 2) {
reg[acells + 1] = reg[acells];
reg[acells] = htobe32(size >> 32);
}
node = fdt_find_node("/chosen");
fdt_node_add_node(node, "framebuffer", &child);
fdt_node_add_property(child, "status", "okay", strlen("okay") + 1);
fdt_node_add_property(child, "format", format, strlen(format) + 1);
fdt_node_add_property(child, "stride", &stride, 4);
fdt_node_add_property(child, "height", &height, 4);
fdt_node_add_property(child, "width", &width, 4);
fdt_node_add_property(child, "reg", reg, (acells + scells) * 4);
fdt_node_add_property(child, "compatible",
"simple-framebuffer", strlen("simple-framebuffer") + 1);
strlcpy(framebuffer_path, "/chosen/framebuffer",
sizeof(framebuffer_path));
}
void
efi_console(void)
{
void *node;
if (major(cn_tab->cn_dev) == major(serial)) {
char *serial_path;
char alias[16];
int len;
/* Construct alias and resolve it. */
snprintf(alias, sizeof(alias), "serial%d",
minor(cn_tab->cn_dev));
node = fdt_find_node("/aliases");
len = fdt_node_property(node, alias, &serial_path);
if (len <= 0)
return;
/* Point stdout-path at the serial node. */
node = fdt_find_node("/chosen");
fdt_node_add_property(node, "stdout-path",
serial_path, strlen(serial_path) + 1);
} else if (major(cn_tab->cn_dev) == major(framebuffer)) {
if (strlen(framebuffer_path) == 0)
return;
/* Point stdout-path at the framebuffer node. */
node = fdt_find_node("/chosen");
fdt_node_add_property(node, "stdout-path",
framebuffer_path, strlen(framebuffer_path) + 1);
}
}
uint64_t dma_constraint[2] = { 0, -1 };
void
efi_dma_constraint(void)
{
void *node;
char *prop;
uint32_t *propint;
uint64_t base, size;
uint32_t pacells, pscells;
uint32_t acells, scells;
int len;
node = fdt_find_node("/");
if (fdt_node_property_int(node, "#address-cells", &pacells) != 1)
pacells = 1;
if (fdt_node_property_int(node, "#size-cells", &pscells) != 1)
pscells = 1;
if (pacells > 2 || pscells > 2)
return;
node = fdt_find_node("/soc");
if (node != NULL) {
if (fdt_node_property_int(node, "#address-cells", &acells) != 1)
acells = pacells;
if (fdt_node_property_int(node, "#size-cells", &scells) != 1)
scells = pscells;
if (acells > 2 || scells > 2)
return;
len = fdt_node_property(node, "dma-ranges", &prop);
propint = (uint32_t *)prop;
if (len == (acells + pacells + scells) * sizeof(uint32_t)) {
base = betoh32(propint[acells]);
if (pacells == 2)
base = (base << 32) |
betoh32(propint[acells + 1]);
size = betoh32(propint[acells + pacells]);
if (scells == 2)
size = (size << 32) |
betoh32(propint[acells + pacells + 1]);
dma_constraint[0] = htobe64(base);
dma_constraint[1] = htobe64(base + size - 1);
}
}
/*
* Some SoC's have DMA constraints that aren't explicitly
* advertised.
*/
node = fdt_find_node("/");
if (fdt_node_is_compatible(node, "brcm,bcm2711"))
dma_constraint[1] = htobe64(0x3bffffff);
if (fdt_node_is_compatible(node, "rockchip,rk3566") ||
fdt_node_is_compatible(node, "rockchip,rk3568") ||
fdt_node_is_compatible(node, "rockchip,rk3588") ||
fdt_node_is_compatible(node, "rockchip,rk3588s"))
dma_constraint[1] = htobe64(0xffffffff);
if (fdt_node_is_compatible(node, "lenovo,thinkpad-x13s"))
dma_constraint[1] = htobe64(0xffffffff);
/* Pass DMA constraint. */
node = fdt_find_node("/chosen");
fdt_node_add_property(node, "openbsd,dma-constraint",
dma_constraint, sizeof(dma_constraint));
}
int acpi = 0;
char *bootmac = NULL;
void *
efi_makebootargs(char *bootargs, int howto)
{
struct sr_boot_volume *bv;
u_char bootduid[8];
u_char zero[8] = { 0 };
uint64_t uefi_system_table = htobe64((uintptr_t)ST);
uint32_t boothowto = htobe32(howto);
EFI_PHYSICAL_ADDRESS addr;
void *node, *fdt;
size_t len;
fdt = efi_fdt();
if (fdt == NULL || acpi)
fdt = efi_acpi();
if (!fdt_get_size(fdt))
return NULL;
len = roundup(fdt_get_size(fdt) + PAGE_SIZE, PAGE_SIZE);
if (BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
EFI_SIZE_TO_PAGES(len), &addr) == EFI_SUCCESS) {
memcpy((void *)addr, fdt, fdt_get_size(fdt));
((struct fdt_head *)addr)->fh_size = htobe32(len);
fdt = (void *)addr;
}
if (!fdt_init(fdt))
return NULL;
/* Create common nodes which might not exist when using mach dtb */
node = fdt_find_node("/aliases");
if (node == NULL)
fdt_node_add_node(fdt_find_node("/"), "aliases", &node);
node = fdt_find_node("/chosen");
if (node == NULL)
fdt_node_add_node(fdt_find_node("/"), "chosen", &node);
node = fdt_find_node("/chosen");
len = strlen(bootargs) + 1;
fdt_node_add_property(node, "bootargs", bootargs, len);
fdt_node_add_property(node, "openbsd,boothowto",
&boothowto, sizeof(boothowto));
/* Pass DUID of the boot disk. */
if (bootdev_dip) {
memcpy(&bootduid, bootdev_dip->disklabel.d_uid,
sizeof(bootduid));
if (memcmp(bootduid, zero, sizeof(bootduid)) != 0) {
fdt_node_add_property(node, "openbsd,bootduid",
bootduid, sizeof(bootduid));
}
if (bootdev_dip->sr_vol != NULL) {
bv = bootdev_dip->sr_vol;
fdt_node_add_property(node, "openbsd,sr-bootuuid",
&bv->sbv_uuid, sizeof(bv->sbv_uuid));
if (bv->sbv_maskkey != NULL)
fdt_node_add_property(node,
"openbsd,sr-bootkey", bv->sbv_maskkey,
SR_CRYPTO_MAXKEYBYTES);
}
}
sr_clear_keys();
/* Pass netboot interface address. */
if (bootmac)
fdt_node_add_property(node, "openbsd,bootmac", bootmac, 6);
/* Pass EFI system table. */
fdt_node_add_property(node, "openbsd,uefi-system-table",
&uefi_system_table, sizeof(uefi_system_table));
/* Placeholders for EFI memory map. */
fdt_node_add_property(node, "openbsd,uefi-mmap-start", zero, 8);
fdt_node_add_property(node, "openbsd,uefi-mmap-size", zero, 4);
fdt_node_add_property(node, "openbsd,uefi-mmap-desc-size", zero, 4);
fdt_node_add_property(node, "openbsd,uefi-mmap-desc-ver", zero, 4);
efi_framebuffer();
efi_console();
efi_dma_constraint();
fdt_finalize();
return fdt;
}
void
efi_updatefdt(void)
{
uint64_t uefi_mmap_start = htobe64((uintptr_t)mmap);
uint32_t uefi_mmap_size = htobe32(mmap_ndesc * mmap_descsiz);
uint32_t uefi_mmap_desc_size = htobe32(mmap_descsiz);
uint32_t uefi_mmap_desc_ver = htobe32(mmap_version);
void *node;
node = fdt_find_node("/chosen");
if (!node)
return;
/* Pass EFI memory map. */
fdt_node_set_property(node, "openbsd,uefi-mmap-start",
&uefi_mmap_start, sizeof(uefi_mmap_start));
fdt_node_set_property(node, "openbsd,uefi-mmap-size",
&uefi_mmap_size, sizeof(uefi_mmap_size));
fdt_node_set_property(node, "openbsd,uefi-mmap-desc-size",
&uefi_mmap_desc_size, sizeof(uefi_mmap_desc_size));
fdt_node_set_property(node, "openbsd,uefi-mmap-desc-ver",
&uefi_mmap_desc_ver, sizeof(uefi_mmap_desc_ver));
fdt_finalize();
}
u_long efi_loadaddr;
void
machdep(void)
{
EFI_PHYSICAL_ADDRESS addr;
cninit();
efi_heap_init();
smbios_init(smbios);
/*
* The kernel expects to be loaded into a block of memory aligned
* on a 2MB boundary. We allocate a block of 64MB of memory, which
* gives us plenty of room for growth.
*/
if (efi_memprobe_find(EFI_SIZE_TO_PAGES(64 * 1024 * 1024),
0x200000, EfiLoaderCode, &addr) != EFI_SUCCESS)
printf("Can't allocate memory\n");
efi_loadaddr = addr;
efi_timer_init();
efi_diskprobe();
efi_pxeprobe();
}
void
efi_cleanup(void)
{
int retry;
EFI_STATUS status;
efi_timer_cleanup();
/* retry once in case of failure */
for (retry = 1; retry >= 0; retry--) {
efi_memprobe_internal(); /* sync the current map */
efi_updatefdt();
status = BS->ExitBootServices(IH, mmap_key);
if (status == EFI_SUCCESS)
break;
if (retry == 0)
panic("ExitBootServices failed (%d)", status);
}
}
void
_rtt(void)
{
#ifdef EFI_DEBUG
printf("Hit any key to reboot\n");
efi_cons_getc(0);
#endif
RS->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
for (;;)
continue;
}
/*
* U-Boot only implements the GetTime() Runtime Service if it has been
* configured with CONFIG_DM_RTC. Most board configurations don't
* include that option, so we can't use it to implement our boot
* prompt timeout. Instead we use timer events to simulate a clock
* that ticks ever second.
*/
EFI_EVENT timer;
int ticks;
static VOID
efi_timer(EFI_EVENT event, VOID *context)
{
ticks++;
}
static void
efi_timer_init(void)
{
EFI_STATUS status;
status = BS->CreateEvent(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK,
efi_timer, NULL, &timer);
if (status == EFI_SUCCESS)
status = BS->SetTimer(timer, TimerPeriodic, 10000000);
if (EFI_ERROR(status))
printf("Can't create timer\n");
}
static void
efi_timer_cleanup(void)
{
BS->CloseEvent(timer);
}
time_t
getsecs(void)
{
return ticks;
}
/*
* Various device-related bits.
*/
void
devboot(dev_t dev, char *p)
{
struct sr_boot_volume *bv;
struct sr_boot_chunk *bc;
struct diskinfo *dip;
int sd_boot_vol = 0;
int sr_boot_vol = -1;
int part_type = FS_UNUSED;
if (bootdev_dip == NULL) {
strlcpy(p, "tftp0a", 7);
return;
}
/*
* If there is no BSD disklabel on the boot device, boot from
* the ESP instead.
*/
if ((bootdev_dip->flags & DISKINFO_FLAG_GOODLABEL) == 0) {
strlcpy(p, "esp0a", 6);
return;
}
TAILQ_FOREACH(dip, &disklist, list) {
if (bootdev_dip == dip)
break;
sd_boot_vol++;
}
/*
* Determine the partition type for the 'a' partition of the
* boot device.
*/
part_type = bootdev_dip->disklabel.d_partitions[0].p_fstype;
/*
* See if we booted from a disk that is a member of a bootable
* softraid volume.
*/
SLIST_FOREACH(bv, &sr_volumes, sbv_link) {
SLIST_FOREACH(bc, &bv->sbv_chunks, sbc_link)
if (bc->sbc_diskinfo == bootdev_dip)
sr_boot_vol = bv->sbv_unit;
if (sr_boot_vol != -1)
break;
}
if (sr_boot_vol != -1 && part_type != FS_BSDFFS) {
strlcpy(p, "sr0a", 5);
p[2] = '0' + sr_boot_vol;
return;
}
strlcpy(p, "sd0a", 5);
p[2] = '0' + sd_boot_vol;
}
const char cdevs[][4] = { "cons", "com", "fb" };
const int ncdevs = nitems(cdevs);
int
cnspeed(dev_t dev, int sp)
{
return 115200;
}
char ttyname_buf[8];
char *
ttyname(int fd)
{
snprintf(ttyname_buf, sizeof ttyname_buf, "%s%d",
cdevs[major(cn_tab->cn_dev)], minor(cn_tab->cn_dev));
return ttyname_buf;
}
dev_t
ttydev(char *name)
{
int i, unit = -1;
char *no = name + strlen(name) - 1;
while (no >= name && *no >= '0' && *no <= '9')
unit = (unit < 0 ? 0 : (unit * 10)) + *no-- - '0';
if (no < name || unit < 0)
return NODEV;
for (i = 0; i < ncdevs; i++)
if (strncmp(name, cdevs[i], no - name + 1) == 0)
return makedev(i, unit);
return NODEV;
}
#define MAXDEVNAME 16
/*
* Parse a device spec.
*
* [A-Za-z]*[0-9]*[A-Za-z]:file
* dev uint part
*/
int
devparse(const char *fname, int *dev, int *unit, int *part, const char **file)
{
const char *s;
*unit = 0; /* default to wd0a */
*part = 0;
*dev = 0;
s = strchr(fname, ':');
if (s != NULL) {
int devlen;
int i, u, p = 0;
struct devsw *dp;
char devname[MAXDEVNAME];
devlen = s - fname;
if (devlen > MAXDEVNAME)
return (EINVAL);
/* extract device name */
for (i = 0; isalpha(fname[i]) && (i < devlen); i++)
devname[i] = fname[i];
devname[i] = 0;
if (!isdigit(fname[i]))
return (EUNIT);
/* device number */
for (u = 0; isdigit(fname[i]) && (i < devlen); i++)
u = u * 10 + (fname[i] - '0');
if (!isalpha(fname[i]))
return (EPART);
/* partition number */
if (i < devlen)
p = fname[i++] - 'a';
if (i != devlen)
return (ENXIO);
/* check device name */
for (dp = devsw, i = 0; i < ndevs; dp++, i++) {
if (dp->dv_name && !strcmp(devname, dp->dv_name))
break;
}
if (i >= ndevs)
return (ENXIO);
*unit = u;
*part = p;
*dev = i;
fname = ++s;
}
*file = fname;
return (0);
}
int
devopen(struct open_file *f, const char *fname, char **file)
{
struct devsw *dp;
int dev, unit, part, error;
error = devparse(fname, &dev, &unit, &part, (const char **)file);
if (error)
return (error);
dp = &devsw[dev];
f->f_dev = dp;
if (strcmp("tftp", dp->dv_name) != 0) {
/*
* Clear bootmac, to signal that we loaded this file from a
* non-network device.
*/
bootmac = NULL;
}
return (*dp->dv_open)(f, unit, part);
}
static void
efi_memprobe_internal(void)
{
EFI_STATUS status;
UINTN mapkey, mmsiz, siz;
UINT32 mmver;
EFI_MEMORY_DESCRIPTOR *mm;
int n;
free(mmap, mmap_ndesc * mmap_descsiz);
siz = 0;
status = BS->GetMemoryMap(&siz, NULL, &mapkey, &mmsiz, &mmver);
if (status != EFI_BUFFER_TOO_SMALL)
panic("cannot get the size of memory map");
mm = alloc(siz);
status = BS->GetMemoryMap(&siz, mm, &mapkey, &mmsiz, &mmver);
if (status != EFI_SUCCESS)
panic("cannot get the memory map");
n = siz / mmsiz;
mmap = mm;
mmap_key = mapkey;
mmap_ndesc = n;
mmap_descsiz = mmsiz;
mmap_version = mmver;
}
/*
* 64-bit ARMs can have a much wider memory mapping, as in somewhere
* after the 32-bit region. To cope with our alignment requirement,
* use the memory table to find a place where we can fit.
*/
static EFI_STATUS
efi_memprobe_find(UINTN pages, UINTN align, EFI_MEMORY_TYPE type,
EFI_PHYSICAL_ADDRESS *addr)
{
EFI_MEMORY_DESCRIPTOR *mm;
int i, j;
if (align < EFI_PAGE_SIZE)
return EFI_INVALID_PARAMETER;
efi_memprobe_internal(); /* sync the current map */
for (i = 0, mm = mmap; i < mmap_ndesc;
i++, mm = NextMemoryDescriptor(mm, mmap_descsiz)) {
if (mm->Type != EfiConventionalMemory)
continue;
if (mm->NumberOfPages < pages)
continue;
for (j = 0; j < mm->NumberOfPages; j++) {
EFI_PHYSICAL_ADDRESS paddr;
if (mm->NumberOfPages - j < pages)
break;
paddr = mm->PhysicalStart + (j * EFI_PAGE_SIZE);
if (paddr & (align - 1))
continue;
if (BS->AllocatePages(AllocateAddress, type,
pages, &paddr) == EFI_SUCCESS) {
*addr = paddr;
return EFI_SUCCESS;
}
}
}
return EFI_OUT_OF_RESOURCES;
}
int
mdrandom(char *buf, size_t buflen)
{
char *random;
void *node;
int i, len, ret = -1;
node = fdt_find_node("/chosen");
if (!node)
return -1;
len = fdt_node_property(node, "rng-seed", &random);
if (len > 0) {
for (i = 0; i < buflen; i++)
buf[i] ^= random[i % len];
ret = 0;
}
len = fdt_node_property(node, "kaslr-seed", &random);
if (len > 0) {
for (i = 0; i < buflen; i++)
buf[i] ^= random[i % len];
ret = 0;
}
return ret;
}
#define FW_PATH "/etc/firmware/dtb/"
void *
efi_fdt(void)
{
extern char *hw_vendor, *hw_prod;
/* 'mach dtb' has precedence */
if (fdt_override != NULL)
return fdt_override;
/* Return system provided one */
if (hw_vendor == NULL || hw_prod == NULL)
return fdt_sys;
if (strcmp(hw_vendor, "LENOVO") == 0) {
if (strncmp(hw_prod, "21BX", 4) == 0 ||
strncmp(hw_prod, "21BY", 4) == 0) {
fdt_load_override(FW_PATH
"qcom/sc8280xp-lenovo-thinkpad-x13s.dtb");
/* TODO: find a better mechanism */
cnset(ttydev("fb0"));
}
}
return fdt_override ? fdt_override : fdt_sys;
}
int
fdt_load_override(char *file)
{
EFI_PHYSICAL_ADDRESS addr;
char path[MAXPATHLEN];
struct stat sb;
int fd;
if (file == NULL && fdt_override) {
BS->FreePages((uint64_t)fdt_override,
EFI_SIZE_TO_PAGES(fdt_override_size));
fdt_override = NULL;
fdt_init(fdt_sys);
return 0;
}
snprintf(path, sizeof(path), "%s:%s", cmd.bootdev, file);
fd = open(path, O_RDONLY);
if (fd < 0 || fstat(fd, &sb) == -1) {
printf("cannot open %s\n", path);
return 0;
}
if (efi_memprobe_find(EFI_SIZE_TO_PAGES(sb.st_size),
PAGE_SIZE, EfiLoaderData, &addr) != EFI_SUCCESS) {
printf("cannot allocate memory for %s\n", path);
return 0;
}
if (read(fd, (void *)addr, sb.st_size) != sb.st_size) {
printf("cannot read from %s\n", path);
return 0;
}
if (!fdt_init((void *)addr)) {
printf("invalid device tree\n");
BS->FreePages(addr, EFI_SIZE_TO_PAGES(sb.st_size));
return 0;
}
if (fdt_override) {
BS->FreePages((uint64_t)fdt_override,
EFI_SIZE_TO_PAGES(fdt_override_size));
fdt_override = NULL;
}
fdt_override = (void *)addr;
fdt_override_size = sb.st_size;
return 0;
}
/*
* Commands
*/
int Xacpi_efi(void);
int Xdtb_efi(void);
int Xexit_efi(void);
int Xpoweroff_efi(void);
const struct cmd_table cmd_machine[] = {
{ "acpi", CMDT_CMD, Xacpi_efi },
{ "dtb", CMDT_CMD, Xdtb_efi },
{ "exit", CMDT_CMD, Xexit_efi },
{ "poweroff", CMDT_CMD, Xpoweroff_efi },
{ NULL, 0 }
};
int
Xacpi_efi(void)
{
acpi = 1;
return (0);
}
int
Xdtb_efi(void)
{
if (cmd.argc == 1) {
fdt_load_override(NULL);
return (0);
}
if (cmd.argc != 2) {
printf("dtb file\n");
return (0);
}
return fdt_load_override(cmd.argv[1]);
}
int
Xexit_efi(void)
{
BS->Exit(IH, 0, 0, NULL);
for (;;)
continue;
return (0);
}
int
Xpoweroff_efi(void)
{
RS->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
return (0);
}