1/*	$OpenBSD: efi_machdep.c,v 1.7 2023/07/08 07:18:39 kettenis Exp $	*/
2
3/*
4 * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/param.h>
20#include <sys/device.h>
21#include <sys/proc.h>
22#include <sys/systm.h>
23#include <sys/user.h>
24
25#include <uvm/uvm_extern.h>
26
27#include <machine/biosvar.h>
28extern paddr_t cr3_reuse_pcid;
29
30#include <dev/efi/efi.h>
31#include <machine/efivar.h>
32
33extern EFI_MEMORY_DESCRIPTOR *mmap;
34
35int	efi_match(struct device *, void *, void *);
36void	efi_attach(struct device *, struct device *, void *);
37
38const struct cfattach efi_ca = {
39	sizeof(struct efi_softc), efi_match, efi_attach
40};
41
42void	efi_map_runtime(struct efi_softc *);
43
44label_t efi_jmpbuf;
45
46int
47efi_match(struct device *parent, void *match, void *aux)
48{
49	struct bios_attach_args	*ba = aux;
50	struct cfdata *cf = match;
51
52	if (strcmp(ba->ba_name, cf->cf_driver->cd_name) == 0 &&
53	    bios_efiinfo->system_table != 0)
54		return 1;
55
56	return 0;
57}
58
59void
60efi_attach(struct device *parent, struct device *self, void *aux)
61{
62	struct efi_softc *sc = (struct efi_softc *)self;
63	struct bios_attach_args *ba = aux;
64	uint32_t mmap_desc_ver = bios_efiinfo->mmap_desc_ver;
65	uint64_t system_table;
66	bus_space_handle_t memh;
67	EFI_SYSTEM_TABLE *st;
68	uint16_t major, minor;
69	int i;
70
71	if (mmap_desc_ver != EFI_MEMORY_DESCRIPTOR_VERSION) {
72		printf(": unsupported memory descriptor version %d\n",
73		    mmap_desc_ver);
74		return;
75	}
76
77	system_table = bios_efiinfo->system_table;
78	KASSERT(system_table);
79
80	if (bus_space_map(ba->ba_memt, system_table, sizeof(EFI_SYSTEM_TABLE),
81	    BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_CACHEABLE, &memh)) {
82		printf(": can't map system table\n");
83		return;
84	}
85
86	st = bus_space_vaddr(ba->ba_memt, memh);
87	sc->sc_rs = st->RuntimeServices;
88
89	major = st->Hdr.Revision >> 16;
90	minor = st->Hdr.Revision & 0xffff;
91	printf(": UEFI %d.%d", major, minor / 10);
92	if (minor % 10)
93		printf(".%d", minor % 10);
94	printf("\n");
95
96	/* Early implementations can be buggy. */
97	if (major < 2 || (major == 2 && minor < 10))
98		return;
99
100	if ((bios_efiinfo->flags & BEI_64BIT) == 0)
101		return;
102
103	if (bios_efiinfo->flags & BEI_ESRT)
104		sc->sc_esrt = (void *)bios_efiinfo->config_esrt;
105
106	efi_map_runtime(sc);
107
108	/*
109	 * Activate our pmap such that we can access the
110	 * FirmwareVendor and ConfigurationTable fields.
111	 */
112	efi_enter(sc);
113	if (st->FirmwareVendor) {
114		printf("%s: ", sc->sc_dev.dv_xname);
115		for (i = 0; st->FirmwareVendor[i]; i++)
116			printf("%c", st->FirmwareVendor[i]);
117		printf(" rev 0x%x\n", st->FirmwareRevision);
118	}
119	efi_leave(sc);
120}
121
122void
123efi_map_runtime(struct efi_softc *sc)
124{
125	uint32_t mmap_size = bios_efiinfo->mmap_size;
126	uint32_t mmap_desc_size = bios_efiinfo->mmap_desc_size;
127	EFI_MEMORY_DESCRIPTOR *desc;
128	int i;
129
130	/*
131	 * We don't really want some random executable non-OpenBSD
132	 * code lying around in kernel space.  So create a separate
133	 * pmap and only activate it when we call runtime services.
134	 */
135	sc->sc_pm = pmap_create();
136
137	desc = mmap;
138	for (i = 0; i < mmap_size / mmap_desc_size; i++) {
139		if (desc->Attribute & EFI_MEMORY_RUNTIME) {
140			vaddr_t va = desc->VirtualStart;
141			paddr_t pa = desc->PhysicalStart;
142			int npages = desc->NumberOfPages;
143			vm_prot_t prot = PROT_READ | PROT_WRITE;
144
145#ifdef EFI_DEBUG
146			printf("type 0x%x pa 0x%llx va 0x%llx pages 0x%llx attr 0x%llx\n",
147			    desc->Type, desc->PhysicalStart,
148			    desc->VirtualStart, desc->NumberOfPages,
149			    desc->Attribute);
150#endif
151
152			/*
153			 * If the virtual address is still zero, use
154			 * an identity mapping.
155			 */
156			if (va == 0)
157				va = pa;
158
159			/*
160			 * Normal memory is expected to be "write
161			 * back" cacheable.  Everything else is mapped
162			 * as device memory.
163			 */
164			if ((desc->Attribute & EFI_MEMORY_WB) == 0)
165				pa |= PMAP_NOCACHE;
166
167			/*
168			 * Only make pages marked as runtime service code
169			 * executable.  This violates the standard but it
170			 * seems we can get away with it.
171			 */
172			if (desc->Type == EfiRuntimeServicesCode)
173				prot |= PROT_EXEC;
174
175			if (desc->Attribute & EFI_MEMORY_RP)
176				prot &= ~PROT_READ;
177			if (desc->Attribute & EFI_MEMORY_XP)
178				prot &= ~PROT_EXEC;
179			if (desc->Attribute & EFI_MEMORY_RO)
180				prot &= ~PROT_WRITE;
181
182			while (npages--) {
183				pmap_enter(sc->sc_pm, va, pa, prot,
184				   prot | PMAP_WIRED | PMAP_EFI);
185				va += PAGE_SIZE;
186				pa += PAGE_SIZE;
187			}
188		}
189
190		desc = NextMemoryDescriptor(desc, mmap_desc_size);
191	}
192}
193
194void
195efi_fault(void)
196{
197	longjmp(&efi_jmpbuf);
198}
199__asm(".pushsection .nofault, \"a\"; .quad efi_fault; .popsection");
200
201void
202efi_enter(struct efi_softc *sc)
203{
204	sc->sc_psw = intr_disable();
205	sc->sc_cr3 = rcr3() | cr3_reuse_pcid;
206	lcr3(sc->sc_pm->pm_pdirpa | (pmap_use_pcid ? PCID_EFI : 0));
207
208	fpu_kernel_enter();
209
210	curpcb->pcb_onfault = (void *)efi_fault;
211	if (curcpu()->ci_feature_sefflags_edx & SEFF0EDX_IBT)
212		lcr4(rcr4() & ~CR4_CET);
213}
214
215void
216efi_leave(struct efi_softc *sc)
217{
218	if (curcpu()->ci_feature_sefflags_edx & SEFF0EDX_IBT)
219		lcr4(rcr4() | CR4_CET);
220	curpcb->pcb_onfault = NULL;
221
222	fpu_kernel_exit();
223
224	lcr3(sc->sc_cr3);
225	intr_restore(sc->sc_psw);
226}
227