root/sys/arch/arm64/stand/efiboot/efiboot.c
/*      $OpenBSD: efiboot.c,v 1.70 2026/03/11 16:21:20 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 "efidt.h"
#include "fdt.h"

#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI        1ULL

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;
static EFI_GUID          dt_fixup_guid = EFI_DT_FIXUP_PROTOCOL_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, pxsize;
        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;

        switch (gop->Mode->Info->PixelFormat) {
        case PixelRedGreenBlueReserved8BitPerColor:
                format = "x8b8g8r8";
                pxsize = 4;
                break;
        case PixelBlueGreenRedReserved8BitPerColor:
                format = "x8r8g8b8";
                pxsize = 4;
                break;
        case PixelBitMask: {
                EFI_PIXEL_BITMASK *bm = &gop->Mode->Info->PixelInformation;
                if (bm->RedMask == 0xf800 &&
                    bm->GreenMask == 0x07e0 &&
                    bm->BlueMask == 0x001f) {
                        format = "r5g6b5";
                        pxsize = 2;
                        break;
                }
                printf("Unsupported PixelInformation bitmasks\n");
                /* FALLTHROUGH */
        }
        default:
                printf("Unsupported PixelFormat %d, not adding "
                    "\"simple-framebuffer\" DT node\n",
                    gop->Mode->Info->PixelFormat);
                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 * pxsize);

        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,rk3528") ||
            fdt_node_is_compatible(node, "rockchip,rk3566") ||
            fdt_node_is_compatible(node, "rockchip,rk3568") ||
            fdt_node_is_compatible(node, "rockchip,rk3576") ||
            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, "qcom,sc8280xp") ||
            fdt_node_is_compatible(node, "qcom,x1e80100"))
                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/"

struct smbios_dtb {
        const char *vendor;
        const char *prod;
        const char *dtb;
} smbios_dtb[] = {
        /* Keep the list below sorted by vendor */
        { "ASUS", "ASUS Vivobook S 15 S5507QA",
          "qcom/x1e80100-asus-vivobook-s15.dtb" },
        { "ASUS", "ASUS Zenbook A14 UX3407QA",
          "qcom/x1p42100-asus-zenbook-a14.dtb" },
        { "ASUS", "ASUS Zenbook A14 UX3407RA",
          "qcom/x1e80100-asus-zenbook-a14.dtb" },
        { "Dell", "Inspiron 14 Plus 7441",
          "qcom/x1e80100-dell-inspiron-14-plus-7441.dtb" },
        { "Dell", "Latitude 7455",
          "qcom/x1e80100-dell-latitude-7455.dtb" },
        { "Dell", "XPS 13 9345",
          "qcom/x1e80100-dell-xps13-9345.dtb" },
        { "HONOR", "MRO-XXX",
          "qcom/x1e80100-honor-magicbook-art-14.dtb" },
        { "HP", "HP EliteBook Ultra G1q",
          "qcom/x1e80100-hp-elitebook-ultra-g1q.dtb" },
        { "HP", "HP OmniBook X Laptop 14-fe0xxx",
          "qcom/x1e80100-hp-omnibook-x14.dtb" },
        { "HP", "HP OmniBook X Laptop 14-fe1xxx",
          "qcom/x1p42100-hp-omnibook-x14.dtb" },
        { "LENOVO", "21BX",
          "qcom/sc8280xp-lenovo-thinkpad-x13s.dtb" },
        { "LENOVO", "21BY",
          "qcom/sc8280xp-lenovo-thinkpad-x13s.dtb" },
        { "LENOVO", "21N1",
          "qcom/x1e78100-lenovo-thinkpad-t14s.dtb" },
        { "LENOVO", "21N2",
          "qcom/x1e78100-lenovo-thinkpad-t14s.dtb" },
        { "LENOVO", "21NH",
          "qcom/x1p42100-lenovo-thinkbook-16.dtb" },
        { "LENOVO", "83ED",
          "qcom/x1e80100-lenovo-yoga-slim7x.dtb" },
        { "Microsoft Corporation", "Windows Dev Kit 2023",
          "qcom/sc8280xp-microsoft-blackrock.dtb" },
        { "Qualcomm", "CRD",
          "qcom/x1e80100-crd.dtb" },
        { "SAMSUNG", "Galaxy Book4 Edge",
          "qcom/x1e80100-samsung-galaxy-book4-edge.dtb" },
};

void *
efi_fdt(void)
{
        extern char *hw_vendor, *hw_prod;
        size_t vendorlen, prodlen;
        char dtb[256];
        int i;

        /* '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;

        for (i = 0; i < nitems(smbios_dtb); i++) {
                vendorlen = strlen(smbios_dtb[i].vendor);
                prodlen = strlen(smbios_dtb[i].prod);
                if (strncmp(hw_vendor, smbios_dtb[i].vendor, vendorlen) == 0 &&
                    strncmp(hw_prod, smbios_dtb[i].prod, prodlen) == 0) {
                        snprintf(dtb, sizeof(dtb), "%s%s", FW_PATH,
                            smbios_dtb[i].dtb);
                        fdt_load_override(dtb);
                        /* TODO: find a better mechanism */
                        cnset(ttydev("fb0"));
                }
        }

        return fdt_override ? fdt_override : fdt_sys;
}

int
fdt_load_override(char *file)
{
        EFI_DT_FIXUP_PROTOCOL *dt_fixup;
        EFI_PHYSICAL_ADDRESS addr;
        char path[MAXPATHLEN];
        EFI_STATUS status;
        struct stat sb;
        size_t dt_size;
        UINTN sz;
        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;
        }
        dt_size = sb.st_size;
retry:
        if (efi_memprobe_find(EFI_SIZE_TO_PAGES(dt_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;
        }

        status = BS->LocateProtocol(&dt_fixup_guid, NULL, (void **)&dt_fixup);
        if (status == EFI_SUCCESS) {
                sz = dt_size;
                status = dt_fixup->Fixup(dt_fixup, (void *)addr, &sz,
                    EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY);
                if (status == EFI_BUFFER_TOO_SMALL) {
                        BS->FreePages(addr, EFI_SIZE_TO_PAGES(dt_size));
                        lseek(fd, 0, SEEK_SET);
                        dt_size = sz;
                        goto retry;
                }
                if (status != EFI_SUCCESS)
                        panic("DT fixup failed: 0x%lx", status);
        }

        if (!fdt_init((void *)addr)) {
                printf("invalid device tree\n");
                BS->FreePages(addr, EFI_SIZE_TO_PAGES(dt_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 = dt_size;
        return 0;
}

/*
 * Commands
 */

int Xacpi_efi(void);
int Xdtb_efi(void);
int Xexit_efi(void);
int Xfwsetup_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 },
        { "fwsetup",    CMDT_CMD, Xfwsetup_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
Xfwsetup_efi(void)
{
        UINT64 osind;
        UINTN osind_size = sizeof(osind);
        UINT32 osind_attrs = 0x1 | 0x2 | 0x4;
        EFI_GUID global = EFI_GLOBAL_VARIABLE;
        EFI_STATUS status;

        status = RS->GetVariable(L"OsIndicationsSupported", &global, NULL,
            &osind_size, &osind);
        if (status == EFI_NOT_FOUND) {
                printf("not supported on this machine.\n");
                return (-1);
        } else if (status != EFI_SUCCESS) {
                printf("%s: %d\n", __func__, status);
                return (-1);
        }

        if ((osind & EFI_OS_INDICATIONS_BOOT_TO_FW_UI) == 0) {
                printf("not supported on this machine.\n");
                return (-1);
        }

        osind = EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
        status = RS->SetVariable(L"OsIndications", &global, osind_attrs,
            sizeof(osind), &osind);
        if (status != EFI_SUCCESS) {
                printf("%s: %d\n", __func__, status);
                return (-1);
        }

        RS->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
        for (;;)
                continue;
        return (0);
}

int
Xpoweroff_efi(void)
{
        RS->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
        return (0);
}