diff --git a/sys/arm64/arm64/machdep.c b/sys/arm64/arm64/machdep.c --- a/sys/arm64/arm64/machdep.c +++ b/sys/arm64/arm64/machdep.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -450,281 +451,6 @@ return (false); } -typedef void (*efi_map_entry_cb)(struct efi_md *, void *argp); - -static void -foreach_efi_map_entry(struct efi_map_header *efihdr, efi_map_entry_cb cb, void *argp) -{ - struct efi_md *map, *p; - size_t efisz; - int ndesc, i; - - /* - * Memory map data provided by UEFI via the GetMemoryMap - * Boot Services API. - */ - efisz = (sizeof(struct efi_map_header) + 0xf) & ~0xf; - map = (struct efi_md *)((uint8_t *)efihdr + efisz); - - if (efihdr->descriptor_size == 0) - return; - ndesc = efihdr->memory_size / efihdr->descriptor_size; - - for (i = 0, p = map; i < ndesc; i++, - p = efi_next_descriptor(p, efihdr->descriptor_size)) { - cb(p, argp); - } -} - -/* - * Handle the EFI memory map list. - * - * We will make two passes at this, the first (exclude == false) to populate - * physmem with valid physical memory ranges from recognized map entry types. - * In the second pass we will exclude memory ranges from physmem which must not - * be used for general allocations, either because they are used by runtime - * firmware or otherwise reserved. - * - * Adding the runtime-reserved memory ranges to physmem and excluding them - * later ensures that they are included in the DMAP, but excluded from - * phys_avail[]. - * - * Entry types not explicitly listed here are ignored and not mapped. - */ -static void -handle_efi_map_entry(struct efi_md *p, void *argp) -{ - bool exclude = *(bool *)argp; - - switch (p->md_type) { - case EFI_MD_TYPE_RECLAIM: - /* - * The recomended location for ACPI tables. Map into the - * DMAP so we can access them from userspace via /dev/mem. - */ - case EFI_MD_TYPE_RT_CODE: - /* - * Some UEFI implementations put the system table in the - * runtime code section. Include it in the DMAP, but will - * be excluded from phys_avail. - */ - case EFI_MD_TYPE_RT_DATA: - /* - * Runtime data will be excluded after the DMAP - * region is created to stop it from being added - * to phys_avail. - */ - if (exclude) { - physmem_exclude_region(p->md_phys, - p->md_pages * EFI_PAGE_SIZE, EXFLAG_NOALLOC); - break; - } - /* FALLTHROUGH */ - 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: - /* - * We're allowed to use any entry with these types. - */ - if (!exclude) - physmem_hardware_region(p->md_phys, - p->md_pages * EFI_PAGE_SIZE); - break; - default: - /* Other types shall not be handled by physmem. */ - break; - } -} - -static void -add_efi_map_entries(struct efi_map_header *efihdr) -{ - bool exclude = false; - foreach_efi_map_entry(efihdr, handle_efi_map_entry, &exclude); -} - -static void -exclude_efi_map_entries(struct efi_map_header *efihdr) -{ - bool exclude = true; - foreach_efi_map_entry(efihdr, handle_efi_map_entry, &exclude); -} - -static void -print_efi_map_entry(struct efi_md *p, void *argp __unused) -{ - const char *type; - static const char *types[] = { - "Reserved", - "LoaderCode", - "LoaderData", - "BootServicesCode", - "BootServicesData", - "RuntimeServicesCode", - "RuntimeServicesData", - "ConventionalMemory", - "UnusableMemory", - "ACPIReclaimMemory", - "ACPIMemoryNVS", - "MemoryMappedIO", - "MemoryMappedIOPortSpace", - "PalCode", - "PersistentMemory" - }; - - if (p->md_type < nitems(types)) - type = types[p->md_type]; - else - type = ""; - printf("%23s %012lx %012lx %08lx ", type, p->md_phys, - p->md_virt, p->md_pages); - if (p->md_attr & EFI_MD_ATTR_UC) - printf("UC "); - if (p->md_attr & EFI_MD_ATTR_WC) - printf("WC "); - if (p->md_attr & EFI_MD_ATTR_WT) - printf("WT "); - if (p->md_attr & EFI_MD_ATTR_WB) - printf("WB "); - if (p->md_attr & EFI_MD_ATTR_UCE) - printf("UCE "); - if (p->md_attr & EFI_MD_ATTR_WP) - printf("WP "); - if (p->md_attr & EFI_MD_ATTR_RP) - printf("RP "); - if (p->md_attr & EFI_MD_ATTR_XP) - printf("XP "); - if (p->md_attr & EFI_MD_ATTR_NV) - printf("NV "); - if (p->md_attr & EFI_MD_ATTR_MORE_RELIABLE) - printf("MORE_RELIABLE "); - if (p->md_attr & EFI_MD_ATTR_RO) - printf("RO "); - if (p->md_attr & EFI_MD_ATTR_RT) - printf("RUNTIME"); - printf("\n"); -} - -static void -print_efi_map_entries(struct efi_map_header *efihdr) -{ - - printf("%23s %12s %12s %8s %4s\n", - "Type", "Physical", "Virtual", "#Pages", "Attr"); - foreach_efi_map_entry(efihdr, print_efi_map_entry, NULL); -} - -/* - * Map the passed in VA in EFI space to a void * using the efi memory table to - * find the PA and return it in the DMAP, if it exists. We're used between the - * calls to pmap_bootstrap() and physmem_init_kernel_globals() to parse CFG - * tables We assume that either the entry you are mapping fits within its page, - * or if it spills to the next page, that's contiguous in PA and in the DMAP. - * All observed tables obey the first part of this precondition. - */ -struct early_map_data -{ - vm_offset_t va; - vm_offset_t pa; -}; - -static void -efi_early_map_entry(struct efi_md *p, void *argp) -{ - struct early_map_data *emdp = argp; - vm_offset_t s, e; - - if (emdp->pa != 0) - return; - if ((p->md_attr & EFI_MD_ATTR_RT) == 0) - return; - s = p->md_virt; - e = p->md_virt + p->md_pages * EFI_PAGE_SIZE; - if (emdp->va < s || emdp->va >= e) - return; - emdp->pa = p->md_phys + (emdp->va - p->md_virt); -} - -static void * -efi_early_map(vm_offset_t va) -{ - struct early_map_data emd = { .va = va }; - - foreach_efi_map_entry(efihdr, efi_early_map_entry, &emd); - if (emd.pa == 0) - return NULL; - return (void *)PHYS_TO_DMAP(emd.pa); -} - - -/* - * When booted via kboot, the prior kernel will pass in reserved memory areas in - * a EFI config table. We need to find that table and walk through it excluding - * the memory ranges in it. btw, this is called too early for the printf to do - * anything since msgbufp isn't initialized, let alone a console... - */ -static void -exclude_efi_memreserve(vm_offset_t efi_systbl_phys) -{ - struct efi_systbl *systbl; - struct uuid efi_memreserve = LINUX_EFI_MEMRESERVE_TABLE; - - systbl = (struct efi_systbl *)PHYS_TO_DMAP(efi_systbl_phys); - if (systbl == NULL) { - printf("can't map systbl\n"); - return; - } - if (systbl->st_hdr.th_sig != EFI_SYSTBL_SIG) { - printf("Bad signature for systbl %#lx\n", systbl->st_hdr.th_sig); - return; - } - - /* - * We don't yet have the pmap system booted enough to create a pmap for - * the efi firmware's preferred address space from the GetMemoryMap() - * table. The st_cfgtbl is a VA in this space, so we need to do the - * mapping ourselves to a kernel VA with efi_early_map. We assume that - * the cfgtbl entries don't span a page. Other pointers are PAs, as - * noted below. - */ - if (systbl->st_cfgtbl == 0) /* Failsafe st_entries should == 0 in this case */ - return; - for (int i = 0; i < systbl->st_entries; i++) { - struct efi_cfgtbl *cfgtbl; - struct linux_efi_memreserve *mr; - - cfgtbl = efi_early_map(systbl->st_cfgtbl + i * sizeof(*cfgtbl)); - if (cfgtbl == NULL) - panic("Can't map the config table entry %d\n", i); - if (memcmp(&cfgtbl->ct_uuid, &efi_memreserve, sizeof(struct uuid)) != 0) - continue; - - /* - * cfgtbl points are either VA or PA, depending on the GUID of - * the table. memreserve GUID pointers are PA and not converted - * after a SetVirtualAddressMap(). The list's mr_next pointer - * is also a PA. - */ - mr = (struct linux_efi_memreserve *)PHYS_TO_DMAP( - (vm_offset_t)cfgtbl->ct_data); - while (true) { - for (int j = 0; j < mr->mr_count; j++) { - struct linux_efi_memreserve_entry *mre; - - mre = &mr->mr_entry[j]; - physmem_exclude_region(mre->mre_base, mre->mre_size, - EXFLAG_NODUMP | EXFLAG_NOALLOC); - } - if (mr->mr_next == 0) - break; - mr = (struct linux_efi_memreserve *)PHYS_TO_DMAP(mr->mr_next); - }; - } - -} - #ifdef FDT static void try_load_dtb(caddr_t kmdp) @@ -974,7 +700,7 @@ exclude_efi_map_entries(efihdr); /* Do the same for reserve entries in the EFI MEMRESERVE table */ if (efi_systbl_phys != 0) - exclude_efi_memreserve(efi_systbl_phys); + exclude_efi_memreserve(efihdr, efi_systbl_phys); /* * We carefully bootstrap the sanitizer map after we've excluded diff --git a/sys/arm64/include/efi.h b/sys/arm64/include/efi.h --- a/sys/arm64/include/efi.h +++ b/sys/arm64/include/efi.h @@ -45,6 +45,8 @@ #define EFI_TIME_OWNED() #define EFI_RT_HANDLE_FAULTS_DEFAULT 1 + +#define EFI_HAS_MEMRESERVE 1 #endif struct efirt_callinfo { diff --git a/sys/conf/files.arm64 b/sys/conf/files.arm64 --- a/sys/conf/files.arm64 +++ b/sys/conf/files.arm64 @@ -6,6 +6,7 @@ kern/msi_if.m optional intrng kern/pic_if.m optional intrng kern/subr_devmap.c standard +kern/subr_efi_map.c standard kern/subr_intr.c optional intrng kern/subr_physmem.c standard libkern/strlen.c standard diff --git a/sys/kern/subr_efi_map.c b/sys/kern/subr_efi_map.c new file mode 100644 --- /dev/null +++ b/sys/kern/subr_efi_map.c @@ -0,0 +1,285 @@ + +#include +#include +#include +#include +#include + +#include +#include + +typedef void (*efi_map_entry_cb)(struct efi_md *, void *argp); + +static void +foreach_efi_map_entry(struct efi_map_header *efihdr, efi_map_entry_cb cb, void *argp) +{ + struct efi_md *map, *p; + size_t efisz; + int ndesc, i; + + /* + * Memory map data provided by UEFI via the GetMemoryMap + * Boot Services API. + */ + efisz = (sizeof(struct efi_map_header) + 0xf) & ~0xf; + map = (struct efi_md *)((uint8_t *)efihdr + efisz); + + if (efihdr->descriptor_size == 0) + return; + ndesc = efihdr->memory_size / efihdr->descriptor_size; + + for (i = 0, p = map; i < ndesc; i++, + p = efi_next_descriptor(p, efihdr->descriptor_size)) { + cb(p, argp); + } +} + +/* + * Handle the EFI memory map list. + * + * We will make two passes at this, the first (exclude == false) to populate + * physmem with valid physical memory ranges from recognized map entry types. + * In the second pass we will exclude memory ranges from physmem which must not + * be used for general allocations, either because they are used by runtime + * firmware or otherwise reserved. + * + * Adding the runtime-reserved memory ranges to physmem and excluding them + * later ensures that they are included in the DMAP, but excluded from + * phys_avail[]. + * + * Entry types not explicitly listed here are ignored and not mapped. + */ +static void +handle_efi_map_entry(struct efi_md *p, void *argp) +{ + bool exclude = *(bool *)argp; + + switch (p->md_type) { + case EFI_MD_TYPE_RECLAIM: + /* + * The recomended location for ACPI tables. Map into the + * DMAP so we can access them from userspace via /dev/mem. + */ + case EFI_MD_TYPE_RT_CODE: + /* + * Some UEFI implementations put the system table in the + * runtime code section. Include it in the DMAP, but will + * be excluded from phys_avail. + */ + case EFI_MD_TYPE_RT_DATA: + /* + * Runtime data will be excluded after the DMAP + * region is created to stop it from being added + * to phys_avail. + */ + if (exclude) { + physmem_exclude_region(p->md_phys, + p->md_pages * EFI_PAGE_SIZE, EXFLAG_NOALLOC); + break; + } + /* FALLTHROUGH */ + 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: + /* + * We're allowed to use any entry with these types. + */ + if (!exclude) + physmem_hardware_region(p->md_phys, + p->md_pages * EFI_PAGE_SIZE); + break; + default: + /* Other types shall not be handled by physmem. */ + break; + } +} + +void +add_efi_map_entries(struct efi_map_header *efihdr) +{ + bool exclude = false; + foreach_efi_map_entry(efihdr, handle_efi_map_entry, &exclude); +} + +void +exclude_efi_map_entries(struct efi_map_header *efihdr) +{ + bool exclude = true; + foreach_efi_map_entry(efihdr, handle_efi_map_entry, &exclude); +} + +static void +print_efi_map_entry(struct efi_md *p, void *argp __unused) +{ + const char *type; + static const char *types[] = { + "Reserved", + "LoaderCode", + "LoaderData", + "BootServicesCode", + "BootServicesData", + "RuntimeServicesCode", + "RuntimeServicesData", + "ConventionalMemory", + "UnusableMemory", + "ACPIReclaimMemory", + "ACPIMemoryNVS", + "MemoryMappedIO", + "MemoryMappedIOPortSpace", + "PalCode", + "PersistentMemory" + }; + + if (p->md_type < nitems(types)) + type = types[p->md_type]; + else + type = ""; + printf("%23s %012lx %012lx %08lx ", type, p->md_phys, + p->md_virt, p->md_pages); + if (p->md_attr & EFI_MD_ATTR_UC) + printf("UC "); + if (p->md_attr & EFI_MD_ATTR_WC) + printf("WC "); + if (p->md_attr & EFI_MD_ATTR_WT) + printf("WT "); + if (p->md_attr & EFI_MD_ATTR_WB) + printf("WB "); + if (p->md_attr & EFI_MD_ATTR_UCE) + printf("UCE "); + if (p->md_attr & EFI_MD_ATTR_WP) + printf("WP "); + if (p->md_attr & EFI_MD_ATTR_RP) + printf("RP "); + if (p->md_attr & EFI_MD_ATTR_XP) + printf("XP "); + if (p->md_attr & EFI_MD_ATTR_NV) + printf("NV "); + if (p->md_attr & EFI_MD_ATTR_MORE_RELIABLE) + printf("MORE_RELIABLE "); + if (p->md_attr & EFI_MD_ATTR_RO) + printf("RO "); + if (p->md_attr & EFI_MD_ATTR_RT) + printf("RUNTIME"); + printf("\n"); +} + +void +print_efi_map_entries(struct efi_map_header *efihdr) +{ + + printf("%23s %12s %12s %8s %4s\n", + "Type", "Physical", "Virtual", "#Pages", "Attr"); + foreach_efi_map_entry(efihdr, print_efi_map_entry, NULL); +} + +#ifdef EFI_HAS_MEMRESERVE +/* + * Map the passed in VA in EFI space to a void * using the efi memory table to + * find the PA and return it in the DMAP, if it exists. We're used between the + * calls to pmap_bootstrap() and physmem_init_kernel_globals() to parse CFG + * tables We assume that either the entry you are mapping fits within its page, + * or if it spills to the next page, that's contiguous in PA and in the DMAP. + * All observed tables obey the first part of this precondition. + */ +struct early_map_data +{ + vm_offset_t va; + vm_offset_t pa; +}; + +static void +efi_early_map_entry(struct efi_md *p, void *argp) +{ + struct early_map_data *emdp = argp; + vm_offset_t s, e; + + if (emdp->pa != 0) + return; + if ((p->md_attr & EFI_MD_ATTR_RT) == 0) + return; + s = p->md_virt; + e = p->md_virt + p->md_pages * EFI_PAGE_SIZE; + if (emdp->va < s || emdp->va >= e) + return; + emdp->pa = p->md_phys + (emdp->va - p->md_virt); +} + +static void * +efi_early_map(struct efi_map_header *efihdr, vm_offset_t va) +{ + struct early_map_data emd = { .va = va }; + + foreach_efi_map_entry(efihdr, efi_early_map_entry, &emd); + if (emd.pa == 0) + return NULL; + return (void *)PHYS_TO_DMAP(emd.pa); +} + + +/* + * When booted via kboot, the prior kernel will pass in reserved memory areas in + * a EFI config table. We need to find that table and walk through it excluding + * the memory ranges in it. btw, this is called too early for the printf to do + * anything since msgbufp isn't initialized, let alone a console... + */ +void +exclude_efi_memreserve(struct efi_map_header *efihdr, vm_paddr_t efi_systbl_phys) +{ + struct efi_systbl *systbl; + struct uuid efi_memreserve = LINUX_EFI_MEMRESERVE_TABLE; + + systbl = (struct efi_systbl *)PHYS_TO_DMAP(efi_systbl_phys); + if (systbl == NULL) { + printf("can't map systbl\n"); + return; + } + if (systbl->st_hdr.th_sig != EFI_SYSTBL_SIG) { + printf("Bad signature for systbl %#lx\n", systbl->st_hdr.th_sig); + return; + } + + /* + * We don't yet have the pmap system booted enough to create a pmap for + * the efi firmware's preferred address space from the GetMemoryMap() + * table. The st_cfgtbl is a VA in this space, so we need to do the + * mapping ourselves to a kernel VA with efi_early_map. We assume that + * the cfgtbl entries don't span a page. Other pointers are PAs, as + * noted below. + */ + if (systbl->st_cfgtbl == 0) /* Failsafe st_entries should == 0 in this case */ + return; + for (int i = 0; i < systbl->st_entries; i++) { + struct efi_cfgtbl *cfgtbl; + struct linux_efi_memreserve *mr; + + cfgtbl = efi_early_map(efihdr, systbl->st_cfgtbl + i * sizeof(*cfgtbl)); + if (cfgtbl == NULL) + panic("Can't map the config table entry %d\n", i); + if (memcmp(&cfgtbl->ct_uuid, &efi_memreserve, sizeof(struct uuid)) != 0) + continue; + + /* + * cfgtbl points are either VA or PA, depending on the GUID of + * the table. memreserve GUID pointers are PA and not converted + * after a SetVirtualAddressMap(). The list's mr_next pointer + * is also a PA. + */ + mr = (struct linux_efi_memreserve *)PHYS_TO_DMAP( + (vm_offset_t)cfgtbl->ct_data); + while (true) { + for (int j = 0; j < mr->mr_count; j++) { + struct linux_efi_memreserve_entry *mre; + + mre = &mr->mr_entry[j]; + physmem_exclude_region(mre->mre_base, mre->mre_size, + EXFLAG_NODUMP | EXFLAG_NOALLOC); + } + if (mr->mr_next == 0) + break; + mr = (struct linux_efi_memreserve *)PHYS_TO_DMAP(mr->mr_next); + }; + } +} +#endif /* EFI_HAS_MEMRESERVE */ diff --git a/sys/sys/efi_map.h b/sys/sys/efi_map.h new file mode 100644 --- /dev/null +++ b/sys/sys/efi_map.h @@ -0,0 +1,12 @@ + +#ifndef _SYS_EFI_MAP_H_ +#define _SYS_EFI_MAP_H_ + +#include + +void add_efi_map_entries(struct efi_map_header *); +void exclude_efi_map_entries(struct efi_map_header *); +void print_efi_map_entries(struct efi_map_header *); +void exclude_efi_memreserve(struct efi_map_header *, vm_paddr_t); + +#endif /* !_SYS_EFI_MAP_H_ */