1*f2f88681Skettenis /* $OpenBSD: efi_machdep.c,v 1.6 2023/01/14 12:11:11 kettenis Exp $ */
201ad66f9Skettenis
301ad66f9Skettenis /*
401ad66f9Skettenis * Copyright (c) 2017 Mark Kettenis <kettenis@openbsd.org>
501ad66f9Skettenis *
601ad66f9Skettenis * Permission to use, copy, modify, and distribute this software for any
701ad66f9Skettenis * purpose with or without fee is hereby granted, provided that the above
801ad66f9Skettenis * copyright notice and this permission notice appear in all copies.
901ad66f9Skettenis *
1001ad66f9Skettenis * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1101ad66f9Skettenis * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1201ad66f9Skettenis * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1301ad66f9Skettenis * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1401ad66f9Skettenis * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1501ad66f9Skettenis * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1601ad66f9Skettenis * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1701ad66f9Skettenis */
1801ad66f9Skettenis
1901ad66f9Skettenis #include <sys/param.h>
2001ad66f9Skettenis #include <sys/device.h>
2101ad66f9Skettenis #include <sys/systm.h>
2201ad66f9Skettenis
2301ad66f9Skettenis #include <uvm/uvm_extern.h>
2401ad66f9Skettenis
2501ad66f9Skettenis #include <machine/cpufunc.h>
2601ad66f9Skettenis #include <machine/bus.h>
2701ad66f9Skettenis #include <machine/fdt.h>
2801ad66f9Skettenis #include <machine/fpu.h>
2960f107aaSkettenis #include <machine/pcb.h>
3001ad66f9Skettenis
3101ad66f9Skettenis #include <dev/ofw/openfirm.h>
3201ad66f9Skettenis #include <dev/ofw/fdt.h>
3301ad66f9Skettenis
3401ad66f9Skettenis #include <dev/efi/efi.h>
35*f2f88681Skettenis #include <machine/efivar.h>
3601ad66f9Skettenis
3701ad66f9Skettenis /*
3801ad66f9Skettenis * We need a large address space to allow identity mapping of physical
3901ad66f9Skettenis * memory on some machines.
4001ad66f9Skettenis */
4101ad66f9Skettenis #define EFI_SPACE_BITS 48
4201ad66f9Skettenis
4301ad66f9Skettenis extern uint32_t mmap_size;
4401ad66f9Skettenis extern uint32_t mmap_desc_size;
4501ad66f9Skettenis extern uint32_t mmap_desc_ver;
4601ad66f9Skettenis
4701ad66f9Skettenis extern EFI_MEMORY_DESCRIPTOR *mmap;
4801ad66f9Skettenis
4901ad66f9Skettenis uint64_t efi_acpi_table;
5001ad66f9Skettenis uint64_t efi_smbios_table;
5101ad66f9Skettenis
5201ad66f9Skettenis int efi_match(struct device *, void *, void *);
5301ad66f9Skettenis void efi_attach(struct device *, struct device *, void *);
5401ad66f9Skettenis
5501ad66f9Skettenis const struct cfattach efi_ca = {
5601ad66f9Skettenis sizeof(struct efi_softc), efi_match, efi_attach
5701ad66f9Skettenis };
5801ad66f9Skettenis
5901ad66f9Skettenis void efi_map_runtime(struct efi_softc *);
6001ad66f9Skettenis int efi_gettime(struct todr_chip_handle *, struct timeval *);
6101ad66f9Skettenis int efi_settime(struct todr_chip_handle *, struct timeval *);
6201ad66f9Skettenis
6360f107aaSkettenis label_t efi_jmpbuf;
6460f107aaSkettenis
6501ad66f9Skettenis int
efi_match(struct device * parent,void * match,void * aux)6601ad66f9Skettenis efi_match(struct device *parent, void *match, void *aux)
6701ad66f9Skettenis {
6801ad66f9Skettenis struct fdt_attach_args *faa = aux;
6901ad66f9Skettenis
7001ad66f9Skettenis return (strcmp(faa->fa_name, "efi") == 0);
7101ad66f9Skettenis }
7201ad66f9Skettenis
7301ad66f9Skettenis void
efi_attach(struct device * parent,struct device * self,void * aux)7401ad66f9Skettenis efi_attach(struct device *parent, struct device *self, void *aux)
7501ad66f9Skettenis {
7601ad66f9Skettenis struct efi_softc *sc = (struct efi_softc *)self;
7701ad66f9Skettenis struct fdt_attach_args *faa = aux;
7801ad66f9Skettenis uint64_t system_table;
7901ad66f9Skettenis bus_space_handle_t ioh;
8001ad66f9Skettenis EFI_SYSTEM_TABLE *st;
8101ad66f9Skettenis EFI_TIME time;
8201ad66f9Skettenis EFI_STATUS status;
8301ad66f9Skettenis uint16_t major, minor;
8401ad66f9Skettenis int node, i;
8501ad66f9Skettenis
8601ad66f9Skettenis node = OF_finddevice("/chosen");
8701ad66f9Skettenis KASSERT(node != -1);
8801ad66f9Skettenis
8901ad66f9Skettenis system_table = OF_getpropint64(node, "openbsd,uefi-system-table", 0);
9001ad66f9Skettenis KASSERT(system_table);
9101ad66f9Skettenis
9201ad66f9Skettenis if (bus_space_map(faa->fa_iot, system_table, sizeof(EFI_SYSTEM_TABLE),
9301ad66f9Skettenis BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_CACHEABLE, &ioh)) {
9401ad66f9Skettenis printf(": can't map system table\n");
9501ad66f9Skettenis return;
9601ad66f9Skettenis }
9701ad66f9Skettenis
9801ad66f9Skettenis st = bus_space_vaddr(faa->fa_iot, ioh);
9901ad66f9Skettenis sc->sc_rs = st->RuntimeServices;
10001ad66f9Skettenis
10101ad66f9Skettenis major = st->Hdr.Revision >> 16;
10201ad66f9Skettenis minor = st->Hdr.Revision & 0xffff;
10301ad66f9Skettenis printf(": UEFI %d.%d", major, minor / 10);
10401ad66f9Skettenis if (minor % 10)
10501ad66f9Skettenis printf(".%d", minor % 10);
10601ad66f9Skettenis printf("\n");
10701ad66f9Skettenis
1081c3a0ad1Skettenis /* Early implementations can be buggy. */
1091c3a0ad1Skettenis if (major < 2 || (major == 2 && minor < 10))
1101c3a0ad1Skettenis return;
1111c3a0ad1Skettenis
11201ad66f9Skettenis efi_map_runtime(sc);
11301ad66f9Skettenis
11401ad66f9Skettenis /*
11501ad66f9Skettenis * Activate our pmap such that we can access the
11601ad66f9Skettenis * FirmwareVendor and ConfigurationTable fields.
11701ad66f9Skettenis */
11801ad66f9Skettenis efi_enter(sc);
11901ad66f9Skettenis if (st->FirmwareVendor) {
12001ad66f9Skettenis printf("%s: ", sc->sc_dev.dv_xname);
12101ad66f9Skettenis for (i = 0; st->FirmwareVendor[i]; i++)
12201ad66f9Skettenis printf("%c", st->FirmwareVendor[i]);
12301ad66f9Skettenis printf(" rev 0x%x\n", st->FirmwareRevision);
12401ad66f9Skettenis }
12501ad66f9Skettenis for (i = 0; i < st->NumberOfTableEntries; i++) {
12601ad66f9Skettenis EFI_CONFIGURATION_TABLE *ct = &st->ConfigurationTable[i];
12701ad66f9Skettenis static EFI_GUID acpi_guid = EFI_ACPI_20_TABLE_GUID;
128cad45e2eSkettenis static EFI_GUID smbios_guid = SMBIOS_TABLE_GUID;
129cad45e2eSkettenis static EFI_GUID smbios3_guid = SMBIOS3_TABLE_GUID;
13001ad66f9Skettenis
13101ad66f9Skettenis if (efi_guidcmp(&acpi_guid, &ct->VendorGuid) == 0)
13201ad66f9Skettenis efi_acpi_table = (uint64_t)ct->VendorTable;
13301ad66f9Skettenis if (efi_guidcmp(&smbios_guid, &ct->VendorGuid) == 0)
13401ad66f9Skettenis efi_smbios_table = (uint64_t)ct->VendorTable;
135cad45e2eSkettenis if (efi_guidcmp(&smbios3_guid, &ct->VendorGuid) == 0)
136cad45e2eSkettenis efi_smbios_table = (uint64_t)ct->VendorTable;
13701ad66f9Skettenis }
13801ad66f9Skettenis efi_leave(sc);
13901ad66f9Skettenis
14001ad66f9Skettenis if (efi_smbios_table != 0) {
14101ad66f9Skettenis struct fdt_reg reg = { .addr = efi_smbios_table };
14201ad66f9Skettenis struct fdt_attach_args fa;
14301ad66f9Skettenis
14401ad66f9Skettenis fa.fa_name = "smbios";
14501ad66f9Skettenis fa.fa_iot = faa->fa_iot;
14601ad66f9Skettenis fa.fa_reg = ®
14701ad66f9Skettenis fa.fa_nreg = 1;
14801ad66f9Skettenis config_found(self, &fa, NULL);
14901ad66f9Skettenis }
15001ad66f9Skettenis
15160f107aaSkettenis if (efi_enter_check(sc))
15260f107aaSkettenis return;
15301ad66f9Skettenis status = sc->sc_rs->GetTime(&time, NULL);
15401ad66f9Skettenis efi_leave(sc);
15501ad66f9Skettenis if (status != EFI_SUCCESS)
15601ad66f9Skettenis return;
15701ad66f9Skettenis
1580701a158Skettenis /*
1590701a158Skettenis * EDK II implementations provide an implementation of
1600701a158Skettenis * GetTime() that returns a fixed compiled-in time on hardware
1610701a158Skettenis * without a (supported) RTC. So only use this interface as a
1620701a158Skettenis * last resort.
1630701a158Skettenis */
16401ad66f9Skettenis sc->sc_todr.cookie = sc;
16501ad66f9Skettenis sc->sc_todr.todr_gettime = efi_gettime;
16601ad66f9Skettenis sc->sc_todr.todr_settime = efi_settime;
1670701a158Skettenis sc->sc_todr.todr_quality = -1000;
1680701a158Skettenis todr_attach(&sc->sc_todr);
16901ad66f9Skettenis }
17001ad66f9Skettenis
17101ad66f9Skettenis void
efi_map_runtime(struct efi_softc * sc)17201ad66f9Skettenis efi_map_runtime(struct efi_softc *sc)
17301ad66f9Skettenis {
17401ad66f9Skettenis EFI_MEMORY_DESCRIPTOR *desc;
17501ad66f9Skettenis int i;
17601ad66f9Skettenis
17701ad66f9Skettenis /*
17801ad66f9Skettenis * We don't really want some random executable non-OpenBSD
17901ad66f9Skettenis * code lying around in kernel space. So create a separate
18001ad66f9Skettenis * pmap and only activate it when we call runtime services.
18101ad66f9Skettenis */
18201ad66f9Skettenis sc->sc_pm = pmap_create();
18301ad66f9Skettenis sc->sc_pm->pm_privileged = 1;
18401ad66f9Skettenis sc->sc_pm->have_4_level_pt = 1;
18501ad66f9Skettenis
18601ad66f9Skettenis desc = mmap;
18701ad66f9Skettenis for (i = 0; i < mmap_size / mmap_desc_size; i++) {
18801ad66f9Skettenis if (desc->Attribute & EFI_MEMORY_RUNTIME) {
18901ad66f9Skettenis vaddr_t va = desc->VirtualStart;
19001ad66f9Skettenis paddr_t pa = desc->PhysicalStart;
19101ad66f9Skettenis int npages = desc->NumberOfPages;
19201ad66f9Skettenis vm_prot_t prot = PROT_READ | PROT_WRITE;
19301ad66f9Skettenis
19401ad66f9Skettenis #ifdef EFI_DEBUG
19501ad66f9Skettenis printf("type 0x%x pa 0x%llx va 0x%llx pages 0x%llx attr 0x%llx\n",
19601ad66f9Skettenis desc->Type, desc->PhysicalStart,
19701ad66f9Skettenis desc->VirtualStart, desc->NumberOfPages,
19801ad66f9Skettenis desc->Attribute);
19901ad66f9Skettenis #endif
20001ad66f9Skettenis
20101ad66f9Skettenis /*
20201ad66f9Skettenis * If the virtual address is still zero, use
20301ad66f9Skettenis * an identity mapping.
20401ad66f9Skettenis */
20501ad66f9Skettenis if (va == 0)
20601ad66f9Skettenis va = pa;
20701ad66f9Skettenis
20801ad66f9Skettenis /*
20901ad66f9Skettenis * Normal memory is expected to be "write
21001ad66f9Skettenis * back" cacheable. Everything else is mapped
21101ad66f9Skettenis * as device memory.
21201ad66f9Skettenis */
21301ad66f9Skettenis if ((desc->Attribute & EFI_MEMORY_WB) == 0)
21401ad66f9Skettenis pa |= PMAP_DEVICE;
21501ad66f9Skettenis
21601ad66f9Skettenis /*
21701ad66f9Skettenis * Only make pages marked as runtime service code
21801ad66f9Skettenis * executable. This violates the standard but it
21901ad66f9Skettenis * seems we can get away with it.
22001ad66f9Skettenis */
22101ad66f9Skettenis if (desc->Type == EfiRuntimeServicesCode)
22201ad66f9Skettenis prot |= PROT_EXEC;
22301ad66f9Skettenis
22401ad66f9Skettenis if (desc->Attribute & EFI_MEMORY_RP)
22501ad66f9Skettenis prot &= ~PROT_READ;
22601ad66f9Skettenis if (desc->Attribute & EFI_MEMORY_XP)
22701ad66f9Skettenis prot &= ~PROT_EXEC;
22801ad66f9Skettenis if (desc->Attribute & EFI_MEMORY_RO)
22901ad66f9Skettenis prot &= ~PROT_WRITE;
23001ad66f9Skettenis
23101ad66f9Skettenis while (npages--) {
23201ad66f9Skettenis pmap_enter(sc->sc_pm, va, pa, prot,
23301ad66f9Skettenis prot | PMAP_WIRED);
23401ad66f9Skettenis va += PAGE_SIZE;
23501ad66f9Skettenis pa += PAGE_SIZE;
23601ad66f9Skettenis }
23701ad66f9Skettenis }
23801ad66f9Skettenis
23901ad66f9Skettenis desc = NextMemoryDescriptor(desc, mmap_desc_size);
24001ad66f9Skettenis }
24101ad66f9Skettenis }
24201ad66f9Skettenis
24301ad66f9Skettenis void
efi_fault(void)24460f107aaSkettenis efi_fault(void)
24560f107aaSkettenis {
24660f107aaSkettenis longjmp(&efi_jmpbuf);
24760f107aaSkettenis }
24860f107aaSkettenis
24960f107aaSkettenis void
efi_enter(struct efi_softc * sc)25001ad66f9Skettenis efi_enter(struct efi_softc *sc)
25101ad66f9Skettenis {
25201ad66f9Skettenis struct pmap *pm = sc->sc_pm;
25301ad66f9Skettenis uint64_t tcr;
25401ad66f9Skettenis
25501ad66f9Skettenis sc->sc_psw = intr_disable();
25601ad66f9Skettenis WRITE_SPECIALREG(ttbr0_el1, pmap_kernel()->pm_pt0pa);
25701ad66f9Skettenis __asm volatile("isb");
25801ad66f9Skettenis tcr = READ_SPECIALREG(tcr_el1);
25901ad66f9Skettenis tcr &= ~TCR_T0SZ(0x3f);
26001ad66f9Skettenis tcr |= TCR_T0SZ(64 - EFI_SPACE_BITS);
26101ad66f9Skettenis WRITE_SPECIALREG(tcr_el1, tcr);
26201ad66f9Skettenis cpu_setttb(pm->pm_asid, pm->pm_pt0pa);
26301ad66f9Skettenis
26401ad66f9Skettenis fpu_kernel_enter();
26560f107aaSkettenis
26660f107aaSkettenis curcpu()->ci_curpcb->pcb_onfault = (void *)efi_fault;
26701ad66f9Skettenis }
26801ad66f9Skettenis
26901ad66f9Skettenis void
efi_leave(struct efi_softc * sc)27001ad66f9Skettenis efi_leave(struct efi_softc *sc)
27101ad66f9Skettenis {
27201ad66f9Skettenis struct pmap *pm = curcpu()->ci_curpm;
27301ad66f9Skettenis uint64_t tcr;
27401ad66f9Skettenis
27560f107aaSkettenis curcpu()->ci_curpcb->pcb_onfault = NULL;
27660f107aaSkettenis
27701ad66f9Skettenis fpu_kernel_exit();
27801ad66f9Skettenis
27901ad66f9Skettenis WRITE_SPECIALREG(ttbr0_el1, pmap_kernel()->pm_pt0pa);
28001ad66f9Skettenis __asm volatile("isb");
28101ad66f9Skettenis tcr = READ_SPECIALREG(tcr_el1);
28201ad66f9Skettenis tcr &= ~TCR_T0SZ(0x3f);
28301ad66f9Skettenis tcr |= TCR_T0SZ(64 - USER_SPACE_BITS);
28401ad66f9Skettenis WRITE_SPECIALREG(tcr_el1, tcr);
28501ad66f9Skettenis cpu_setttb(pm->pm_asid, pm->pm_pt0pa);
28601ad66f9Skettenis intr_restore(sc->sc_psw);
28701ad66f9Skettenis }
28801ad66f9Skettenis
28901ad66f9Skettenis int
efi_gettime(struct todr_chip_handle * handle,struct timeval * tv)29001ad66f9Skettenis efi_gettime(struct todr_chip_handle *handle, struct timeval *tv)
29101ad66f9Skettenis {
29201ad66f9Skettenis struct efi_softc *sc = handle->cookie;
29301ad66f9Skettenis struct clock_ymdhms dt;
29401ad66f9Skettenis EFI_TIME time;
29501ad66f9Skettenis EFI_STATUS status;
29601ad66f9Skettenis
29760f107aaSkettenis if (efi_enter_check(sc))
29860f107aaSkettenis return EFAULT;
29901ad66f9Skettenis status = sc->sc_rs->GetTime(&time, NULL);
30001ad66f9Skettenis efi_leave(sc);
30101ad66f9Skettenis if (status != EFI_SUCCESS)
30201ad66f9Skettenis return EIO;
30301ad66f9Skettenis
30401ad66f9Skettenis dt.dt_year = time.Year;
30501ad66f9Skettenis dt.dt_mon = time.Month;
30601ad66f9Skettenis dt.dt_day = time.Day;
30701ad66f9Skettenis dt.dt_hour = time.Hour;
30801ad66f9Skettenis dt.dt_min = time.Minute;
30901ad66f9Skettenis dt.dt_sec = time.Second;
31001ad66f9Skettenis
31101ad66f9Skettenis if (dt.dt_sec > 59 || dt.dt_min > 59 || dt.dt_hour > 23 ||
31201ad66f9Skettenis dt.dt_day > 31 || dt.dt_day == 0 ||
31301ad66f9Skettenis dt.dt_mon > 12 || dt.dt_mon == 0 ||
31401ad66f9Skettenis dt.dt_year < POSIX_BASE_YEAR)
31501ad66f9Skettenis return EINVAL;
31601ad66f9Skettenis
31701ad66f9Skettenis tv->tv_sec = clock_ymdhms_to_secs(&dt);
31801ad66f9Skettenis tv->tv_usec = 0;
31901ad66f9Skettenis return 0;
32001ad66f9Skettenis }
32101ad66f9Skettenis
32201ad66f9Skettenis int
efi_settime(struct todr_chip_handle * handle,struct timeval * tv)32301ad66f9Skettenis efi_settime(struct todr_chip_handle *handle, struct timeval *tv)
32401ad66f9Skettenis {
32501ad66f9Skettenis struct efi_softc *sc = handle->cookie;
32601ad66f9Skettenis struct clock_ymdhms dt;
32701ad66f9Skettenis EFI_TIME time;
32801ad66f9Skettenis EFI_STATUS status;
32901ad66f9Skettenis
33001ad66f9Skettenis clock_secs_to_ymdhms(tv->tv_sec, &dt);
33101ad66f9Skettenis
33201ad66f9Skettenis time.Year = dt.dt_year;
33301ad66f9Skettenis time.Month = dt.dt_mon;
33401ad66f9Skettenis time.Day = dt.dt_day;
33501ad66f9Skettenis time.Hour = dt.dt_hour;
33601ad66f9Skettenis time.Minute = dt.dt_min;
33701ad66f9Skettenis time.Second = dt.dt_sec;
33801ad66f9Skettenis time.Nanosecond = 0;
33901ad66f9Skettenis time.TimeZone = 0;
34001ad66f9Skettenis time.Daylight = 0;
34101ad66f9Skettenis
34260f107aaSkettenis if (efi_enter_check(sc))
34360f107aaSkettenis return EFAULT;
34401ad66f9Skettenis status = sc->sc_rs->SetTime(&time);
34501ad66f9Skettenis efi_leave(sc);
34601ad66f9Skettenis if (status != EFI_SUCCESS)
34701ad66f9Skettenis return EIO;
34801ad66f9Skettenis return 0;
34901ad66f9Skettenis }
350