#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: efi_machdep.c,v 1.8 2026/07/01 00:59:57 riastradh Exp $");
#include "efi.h"
#include "opt_efi.h"
#include <sys/kmem.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/uuid.h>
#include <uvm/uvm_extern.h>
#include <machine/bootinfo.h>
#include <machine/pmap_private.h>
#include <x86/bus_defs.h>
#include <x86/bus_funcs.h>
#include <x86/efi.h>
#include <x86/fpu.h>
#include <dev/mm.h>
#if NPCI > 0
#include <dev/pci/pcivar.h>
#endif
const struct uuid EFI_UUID_ACPI20 = EFI_TABLE_ACPI20;
const struct uuid EFI_UUID_ACPI10 = EFI_TABLE_ACPI10;
const struct uuid EFI_UUID_SMBIOS = EFI_TABLE_SMBIOS;
const struct uuid EFI_UUID_SMBIOS3 = EFI_TABLE_SMBIOS3;
static vaddr_t efi_getva(paddr_t);
static void efi_relva(paddr_t, vaddr_t);
struct efi_cfgtbl *efi_getcfgtblhead(void);
void efi_aprintcfgtbl(void);
void efi_aprintuuid(const struct uuid *);
bool efi_uuideq(const struct uuid *, const struct uuid *);
static bool efi_is32x64 = false;
static paddr_t efi_systbl_pa;
static struct efi_systbl *efi_systbl_va = NULL;
static struct efi_cfgtbl *efi_cfgtblhead_va = NULL;
static struct efi_e820memmap {
struct btinfo_memmap bim;
struct bi_memmap_entry entry[VM_PHYSSEG_MAX - 1];
} efi_e820memmap;
#ifdef EFI_RUNTIME
#include <dev/efivar.h>
#include <uvm/uvm_extern.h>
#if !(NEFI > 0)
#error options EFI_RUNTIME makes no sense without pseudo-device efi.
#endif
struct pmap *efi_runtime_pmap __read_mostly;
static kmutex_t efi_runtime_lock __cacheline_aligned;
static struct efi_rt efi_rt __read_mostly;
static struct efi_ops efi_runtime_ops __read_mostly;
static void efi_runtime_init(void);
#endif
static vaddr_t
efi_getva(paddr_t pa)
{
vaddr_t va;
int rv;
rv = _x86_memio_map(x86_bus_space_mem, pa,
PAGE_SIZE, 0, (bus_space_handle_t *)&va);
if (rv != 0) {
aprint_debug("efi: unable to allocate va\n");
return 0;
}
return va;
}
static void
efi_relva(paddr_t pa, vaddr_t va)
{
(void)_x86_memio_unmap(x86_bus_space_mem, (bus_space_handle_t)va,
PAGE_SIZE, NULL);
}
bool
efi_uuideq(const struct uuid * a, const struct uuid * b)
{
return !memcmp(a, b, sizeof(struct uuid));
}
void
efi_aprintuuid(const struct uuid * uuid)
{
int i;
aprint_debug(" %08" PRIx32 "", uuid->time_low);
aprint_debug("-%04" PRIx16 "", uuid->time_mid);
aprint_debug("-%04" PRIx16 "", uuid->time_hi_and_version);
aprint_debug("-%02" PRIx8 "", uuid->clock_seq_hi_and_reserved);
aprint_debug("%02" PRIx8 "", uuid->clock_seq_low);
aprint_debug("-");
for (i = 0; i < _UUID_NODE_LEN; i++) {
aprint_debug("%02" PRIx8 "", uuid->node[i]);
}
if (efi_uuideq(uuid, &EFI_UUID_ACPI20)) {
aprint_debug(" ACPI 2.0");
} else if (efi_uuideq(uuid, &EFI_UUID_ACPI10)) {
aprint_debug(" ACPI 1.0");
} else if (efi_uuideq(uuid, &EFI_UUID_SMBIOS)) {
aprint_debug(" SMBIOS");
} else if (efi_uuideq(uuid, &EFI_UUID_SMBIOS3)) {
aprint_debug(" SMBIOS3");
}
}
struct efi_cfgtbl *
efi_getcfgtblhead(void)
{
paddr_t pa;
vaddr_t va;
if (efi_cfgtblhead_va != NULL)
return efi_cfgtblhead_va;
if (efi_is32x64) {
#if defined(__amd64__)
struct efi_systbl32 *systbl32 = (void *) efi_systbl_va;
pa = systbl32->st_cfgtbl;
#elif defined(__i386__)
struct efi_systbl64 *systbl64 = (void *) efi_systbl_va;
if (systbl64->st_cfgtbl & 0xffffffff00000000ULL)
return NULL;
pa = (paddr_t) systbl64->st_cfgtbl;
#endif
} else
pa = (paddr_t)(u_long) efi_systbl_va->st_cfgtbl;
aprint_debug("efi: cfgtbl at pa %" PRIxPADDR "\n", pa);
va = efi_getva(pa);
aprint_debug("efi: cfgtbl mapped at va %" PRIxVADDR "\n", va);
efi_cfgtblhead_va = (struct efi_cfgtbl *) va;
efi_aprintcfgtbl();
return efi_cfgtblhead_va;
}
void
efi_aprintcfgtbl(void)
{
struct efi_cfgtbl *ct;
unsigned long count;
if (efi_is32x64) {
#if defined(__amd64__)
struct efi_systbl32 *systbl32 = (void *) efi_systbl_va;
struct efi_cfgtbl32 *ct32 = (void *) efi_cfgtblhead_va;
count = systbl32->st_entries;
aprint_debug("efi: %lu cfgtbl entries:\n", count);
for (; count; count--, ct32++) {
aprint_debug("efi: %08" PRIx32, ct32->ct_data);
efi_aprintuuid(&ct32->ct_uuid);
aprint_debug("\n");
}
#elif defined(__i386__)
struct efi_systbl64 *systbl64 = (void *) efi_systbl_va;
struct efi_cfgtbl64 *ct64 = (void *) efi_cfgtblhead_va;
uint64_t count64 = systbl64->st_entries;
aprint_debug("efi: %" PRIu64 " cfgtbl entries:\n", count64);
for (; count64; count64--, ct64++) {
aprint_debug("efi: %016" PRIx64, ct64->ct_data);
efi_aprintuuid(&ct64->ct_uuid);
aprint_debug("\n");
}
#endif
return;
}
ct = efi_cfgtblhead_va;
count = efi_systbl_va->st_entries;
aprint_debug("efi: %lu cfgtbl entries:\n", count);
for (; count; count--, ct++) {
aprint_debug("efi: %p", ct->ct_data);
efi_aprintuuid(&ct->ct_uuid);
aprint_debug("\n");
}
}
void *
efi_getcfgtbl(const struct uuid * uuid)
{
paddr_t pa;
vaddr_t va;
pa = efi_getcfgtblpa(uuid);
if (pa == 0)
return NULL;
va = efi_getva(pa);
return (void *) va;
}
paddr_t
efi_getcfgtblpa(const struct uuid * uuid)
{
struct efi_cfgtbl *ct;
unsigned long count;
if (efi_is32x64) {
#if defined(__amd64__)
struct efi_systbl32 *systbl32 = (void *) efi_systbl_va;
struct efi_cfgtbl32 *ct32 = (void *) efi_cfgtblhead_va;
count = systbl32->st_entries;
for (; count; count--, ct32++)
if (efi_uuideq(&ct32->ct_uuid, uuid))
return ct32->ct_data;
#elif defined(__i386__)
struct efi_systbl64 *systbl64 = (void *) efi_systbl_va;
struct efi_cfgtbl64 *ct64 = (void *) efi_cfgtblhead_va;
uint64_t count64 = systbl64->st_entries;
for (; count64; count64--, ct64++)
if (efi_uuideq(&ct64->ct_uuid, uuid))
if (!(ct64->ct_data & 0xffffffff00000000ULL))
return ct64->ct_data;
#endif
return 0;
}
ct = efi_cfgtblhead_va;
count = efi_systbl_va->st_entries;
for (; count; count--, ct++)
if (efi_uuideq(&ct->ct_uuid, uuid))
return (paddr_t)(u_long) ct->ct_data;
return 0;
}
paddr_t
efi_getsystblpa(void)
{
struct btinfo_efi *bi;
paddr_t pa;
bi = lookup_bootinfo(BTINFO_EFI);
if (bi == NULL) {
return 0;
}
if (sizeof(paddr_t) == 4 &&
(bi->systblpa & 0xffffffff00000000ULL)) {
return 0;
}
if (bi->common.len > 16 && (bi->flags & BI_EFI_32BIT)) {
#if defined(__amd64__)
efi_is32x64 = true;
#endif
} else {
#if defined(__i386__)
efi_is32x64 = true;
#endif
}
pa = (paddr_t) bi->systblpa;
return pa;
}
struct efi_systbl *
efi_getsystbl(void)
{
paddr_t pa;
vaddr_t va;
struct efi_systbl *systbl;
if (efi_systbl_va)
return efi_systbl_va;
pa = efi_getsystblpa();
if (pa == 0)
return NULL;
aprint_normal("efi: systbl at pa %" PRIxPADDR "\n", pa);
efi_systbl_pa = pa;
va = efi_getva(pa);
aprint_debug("efi: systbl mapped at va %" PRIxVADDR "\n", va);
if (efi_is32x64) {
#if defined(__amd64__)
struct efi_systbl32 *systbl32 = (struct efi_systbl32 *) va;
aprint_debug("efi: signature %" PRIx64 " revision %" PRIx32
" crc32 %" PRIx32 "\n", systbl32->st_hdr.th_sig,
systbl32->st_hdr.th_rev, systbl32->st_hdr.th_crc32);
aprint_debug("efi: firmware revision %" PRIx32 "\n",
systbl32->st_fwrev);
aprint_debug("efi: runtime services at pa 0x%08" PRIx32 "\n",
systbl32->st_rt);
aprint_debug("efi: boot services at pa 0x%08" PRIx32 "\n",
systbl32->st_bs);
efi_systbl_va = (struct efi_systbl *) systbl32;
#elif defined(__i386__)
struct efi_systbl64 *systbl64 = (struct efi_systbl64 *) va;
aprint_debug("efi: signature %" PRIx64 " revision %" PRIx32
" crc32 %" PRIx32 "\n", systbl64->st_hdr.th_sig,
systbl64->st_hdr.th_rev, systbl64->st_hdr.th_crc32);
aprint_debug("efi: firmware revision %" PRIx32 "\n",
systbl64->st_fwrev);
aprint_debug("efi: runtime services at pa 0x%016" PRIx64 "\n",
systbl64->st_rt);
aprint_debug("efi: boot services at pa 0x%016" PRIx64 "\n",
systbl64->st_bs);
efi_systbl_va = (struct efi_systbl *) systbl64;
#endif
return efi_systbl_va;
}
systbl = (struct efi_systbl *) va;
aprint_debug("efi: signature %" PRIx64 " revision %" PRIx32
" crc32 %" PRIx32 "\n", systbl->st_hdr.th_sig,
systbl->st_hdr.th_rev, systbl->st_hdr.th_crc32);
aprint_debug("efi: firmware revision %" PRIx32 "\n", systbl->st_fwrev);
aprint_debug("efi: runtime services at pa %p\n", systbl->st_rt);
aprint_debug("efi: boot services at pa %p\n", systbl->st_bs);
efi_systbl_va = systbl;
return efi_systbl_va;
}
void
efi_init(void)
{
if (efi_getsystbl() == NULL) {
aprint_debug("efi: missing or invalid systbl\n");
bootmethod_efi = false;
return;
}
if (efi_getcfgtblhead() == NULL) {
aprint_debug("efi: missing or invalid cfgtbl\n");
efi_relva(efi_systbl_pa, (vaddr_t) efi_systbl_va);
bootmethod_efi = false;
return;
}
bootmethod_efi = true;
#if NPCI > 0
pci_mapreg_map_enable_decode = true;
#endif
#ifdef EFI_RUNTIME
efi_runtime_init();
#endif
}
bool
efi_probe(void)
{
return bootmethod_efi;
}
int
efi_getbiosmemtype(uint32_t type, uint64_t attr)
{
switch (type) {
case EFI_MD_TYPE_CODE:
case EFI_MD_TYPE_DATA:
case EFI_MD_TYPE_BS_CODE:
case EFI_MD_TYPE_BS_DATA:
case EFI_MD_TYPE_FREE:
return (attr & EFI_MD_ATTR_WB) ? BIM_Memory : BIM_Reserved;
case EFI_MD_TYPE_RECLAIM:
return BIM_ACPI;
case EFI_MD_TYPE_FIRMWARE:
return BIM_NVS;
case EFI_MD_TYPE_PMEM:
return BIM_PMEM;
case EFI_MD_TYPE_NULL:
case EFI_MD_TYPE_RT_CODE:
case EFI_MD_TYPE_RT_DATA:
case EFI_MD_TYPE_BAD:
case EFI_MD_TYPE_IOMEM:
case EFI_MD_TYPE_IOPORT:
case EFI_MD_TYPE_PALCODE:
default:
return BIM_Reserved;
}
}
const char *
efi_getmemtype_str(uint32_t type)
{
static const char *efimemtypes[] = {
"Reserved",
"LoaderCode",
"LoaderData",
"BootServicesCode",
"BootServicesData",
"RuntimeServicesCode",
"RuntimeServicesData",
"ConventionalMemory",
"UnusableMemory",
"ACPIReclaimMemory",
"ACPIMemoryNVS",
"MemoryMappedIO",
"MemoryMappedIOPortSpace",
"PalCode",
"PersistentMemory",
};
if (type < __arraycount(efimemtypes))
return efimemtypes[type];
return "unknown";
}
struct btinfo_memmap *
efi_get_e820memmap(void)
{
struct btinfo_efimemmap *efimm;
struct bi_memmap_entry *entry;
struct efi_md *md;
uint64_t addr, size;
uint64_t start_addr = 0;
uint64_t end_addr = 0;
uint32_t i;
int n, type, seg_type = -1;
if (efi_e820memmap.bim.common.type == BTINFO_MEMMAP)
return &efi_e820memmap.bim;
efimm = lookup_bootinfo(BTINFO_EFIMEMMAP);
if (efimm == NULL)
return NULL;
for (n = 0, i = 0; i < efimm->num; i++) {
md = (struct efi_md *)(efimm->memmap + efimm->size * i);
addr = md->md_phys;
size = md->md_pages * EFI_PAGE_SIZE;
type = efi_getbiosmemtype(md->md_type, md->md_attr);
#ifdef DEBUG_MEMLOAD
printf("MEMMAP: p0x%016" PRIx64 "-0x%016" PRIx64
", v0x%016" PRIx64 "-0x%016" PRIx64
", size=0x%016" PRIx64 ", attr=0x%016" PRIx64
", type=%d(%s)\n",
addr, addr + size - 1,
md->md_virt, md->md_virt + size - 1,
size, md->md_attr, md->md_type,
efi_getmemtype_str(md->md_type));
#endif
if (seg_type == -1) {
} else if (seg_type == type && end_addr == addr) {
end_addr = addr + size;
continue;
} else {
entry = &efi_e820memmap.bim.entry[n];
entry->addr = start_addr;
entry->size = end_addr - start_addr;
entry->type = seg_type;
if (++n == VM_PHYSSEG_MAX)
break;
}
start_addr = addr;
end_addr = addr + size;
seg_type = type;
}
if (i > 0 && n < VM_PHYSSEG_MAX) {
entry = &efi_e820memmap.bim.entry[n];
entry->addr = start_addr;
entry->size = end_addr - start_addr;
entry->type = seg_type;
++n;
} else if (n == VM_PHYSSEG_MAX) {
printf("WARNING: too many memory segments"
"(increase VM_PHYSSEG_MAX)\n");
}
efi_e820memmap.bim.num = n;
efi_e820memmap.bim.common.len =
(intptr_t)&efi_e820memmap.bim.entry[n] - (intptr_t)&efi_e820memmap;
efi_e820memmap.bim.common.type = BTINFO_MEMMAP;
return &efi_e820memmap.bim;
}
#ifdef EFI_RUNTIME
static void
efi_runtime_init(void)
{
struct efi_systbl *systbl;
struct btinfo_efimemmap *efimm;
uint32_t i;
int error;
if (efi_is32x64) {
aprint_debug("%s: 32x64 runtime services not supported\n",
__func__);
return;
}
systbl = efi_getsystbl();
if (systbl->st_rt == NULL) {
aprint_debug("%s: no runtime\n", __func__);
return;
}
if ((efimm = lookup_bootinfo(BTINFO_EFIMEMMAP)) == NULL) {
aprint_debug("%s: no efi memmap\n", __func__);
return;
}
efi_runtime_pmap = pmap_create();
void *const cookie = pmap_activate_sync(efi_runtime_pmap);
for (i = 0; i < efimm->num; i++) {
struct efi_md *md = (void *)(efimm->memmap + efimm->size * i);
uint64_t j;
vaddr_t va;
paddr_t pa;
int prot, flags;
if ((md->md_attr & EFI_MD_ATTR_RT) == 0)
continue;
aprint_debug("%s: map %zu pages at %#"PRIxVADDR
" to %#"PRIxPADDR" type %"PRIu32" attrs 0x%08"PRIx64"\n",
__func__, (size_t)md->md_pages, (vaddr_t)md->md_virt,
(paddr_t)md->md_phys, md->md_type, md->md_attr);
prot = VM_PROT_READ|VM_PROT_WRITE;
switch (md->md_type) {
case EFI_MD_TYPE_RT_CODE:
prot |= VM_PROT_EXECUTE;
break;
}
flags = 0;
if (md->md_attr & EFI_MD_ATTR_UC)
flags |= PMAP_NOCACHE;
else if (md->md_attr & EFI_MD_ATTR_WC)
flags |= PMAP_WRITE_COMBINE;
if (md->md_attr & EFI_MD_ATTR_RO)
prot &= ~VM_PROT_WRITE;
if (md->md_attr & EFI_MD_ATTR_XP)
prot &= ~VM_PROT_EXECUTE;
pa = md->md_phys;
va = md->md_virt;
if (va == 0)
va = pa;
if (VM_MIN_KERNEL_ADDRESS <= va &&
va < VM_MAX_KERNEL_ADDRESS) {
aprint_debug("%s: efi runtime overlaps kernel map"
" %"PRIxVADDR" in [%"PRIxVADDR", %"PRIxVADDR")\n",
__func__,
va,
(vaddr_t)VM_MIN_KERNEL_ADDRESS,
(vaddr_t)VM_MAX_KERNEL_ADDRESS);
goto fail;
}
#ifdef __HAVE_DIRECT_MAP
if (PMAP_DIRECT_BASE <= va && va < PMAP_DIRECT_END) {
aprint_debug("%s: efi runtime overlaps direct map"
" %"PRIxVADDR" in [%"PRIxVADDR", %"PRIxVADDR")\n",
__func__,
va,
(vaddr_t)PMAP_DIRECT_BASE,
(vaddr_t)PMAP_DIRECT_END);
goto fail;
}
#endif
for (j = 0; j < md->md_pages; j++) {
error = pmap_enter(efi_runtime_pmap,
va + j*PAGE_SIZE, pa + j*PAGE_SIZE, prot, flags);
KASSERTMSG(error == 0, "error=%d", error);
}
}
pmap_update(efi_runtime_pmap);
(memcpy)(&efi_rt, systbl->st_rt, sizeof(efi_rt));
pmap_deactivate_sync(efi_runtime_pmap, cookie);
mutex_init(&efi_runtime_lock, MUTEX_DEFAULT, IPL_VM);
efi_register_ops(&efi_runtime_ops);
return;
fail:
pmap_deactivate_sync(efi_runtime_pmap, cookie);
pmap_destroy(efi_runtime_pmap);
efi_runtime_pmap = NULL;
}
struct efi_runtime_cookie {
void *erc_pmap_cookie;
};
static void
efi_runtime_enter(struct efi_runtime_cookie *cookie)
{
KASSERT(efi_runtime_pmap != NULL);
mutex_enter(&efi_runtime_lock);
fpu_kern_enter();
cookie->erc_pmap_cookie = pmap_activate_sync(efi_runtime_pmap);
}
static void
efi_runtime_exit(struct efi_runtime_cookie *cookie)
{
pmap_deactivate_sync(efi_runtime_pmap, cookie->erc_pmap_cookie);
fpu_kern_leave();
mutex_exit(&efi_runtime_lock);
}
static efi_status
efi_runtime_gettime(struct efi_tm *tm, struct efi_tmcap *tmcap)
{
efi_status status;
struct efi_runtime_cookie cookie;
if (efi_rt.rt_gettime == NULL)
return EFI_UNSUPPORTED;
efi_runtime_enter(&cookie);
status = efi_rt.rt_gettime(tm, tmcap);
efi_runtime_exit(&cookie);
return status;
}
static efi_status
efi_runtime_settime(struct efi_tm *tm)
{
efi_status status;
struct efi_runtime_cookie cookie;
if (efi_rt.rt_settime == NULL)
return EFI_UNSUPPORTED;
efi_runtime_enter(&cookie);
status = efi_rt.rt_settime(tm);
efi_runtime_exit(&cookie);
return status;
}
static efi_status
efi_runtime_getvar(efi_char *name, struct uuid *vendor, uint32_t *attrib,
unsigned long *datasize, void *data)
{
efi_status status;
struct efi_runtime_cookie cookie;
if (efi_rt.rt_getvar == NULL)
return EFI_UNSUPPORTED;
efi_runtime_enter(&cookie);
status = efi_rt.rt_getvar(name, vendor, attrib, datasize, data);
efi_runtime_exit(&cookie);
return status;
}
static efi_status
efi_runtime_nextvar(unsigned long *namesize, efi_char *name,
struct uuid *vendor)
{
efi_status status;
struct efi_runtime_cookie cookie;
if (efi_rt.rt_scanvar == NULL)
return EFI_UNSUPPORTED;
efi_runtime_enter(&cookie);
status = efi_rt.rt_scanvar(namesize, name, vendor);
efi_runtime_exit(&cookie);
return status;
}
static efi_status
efi_runtime_setvar(efi_char *name, struct uuid *vendor, uint32_t attrib,
unsigned long datasize, void *data)
{
efi_status status;
struct efi_runtime_cookie cookie;
if (efi_rt.rt_setvar == NULL)
return EFI_UNSUPPORTED;
efi_runtime_enter(&cookie);
status = efi_rt.rt_setvar(name, vendor, attrib, datasize, data);
efi_runtime_exit(&cookie);
return status;
}
static efi_status
efi_runtime_gettab(const struct uuid *vendor, uint64_t *addrp)
{
struct efi_cfgtbl *cfgtbl = efi_getcfgtblhead();
paddr_t pa;
if (cfgtbl == NULL)
return EFI_UNSUPPORTED;
pa = efi_getcfgtblpa(vendor);
if (pa == 0)
return EFI_NOT_FOUND;
*addrp = pa;
return EFI_SUCCESS;
}
static struct efi_ops efi_runtime_ops = {
.efi_gettime = efi_runtime_gettime,
.efi_settime = efi_runtime_settime,
.efi_getvar = efi_runtime_getvar,
.efi_setvar = efi_runtime_setvar,
.efi_nextvar = efi_runtime_nextvar,
.efi_gettab = efi_runtime_gettab,
};
#endif