Add retry loop around GetMemoryMap call to fix fragmentation bug
The call to BS->AllocatePages can cause the memory map to become framented, causing BS->GetMemoryMap to return EFI_BUFFER_TOO_SMALL more than once. For example this can happen on the MinnowBoard Turbot, causing the boot to stop with an error. Avoid this by calling GetMemoryMap in a loop. Reviewed by: imp, tsoome, kevans Differential Revision: https://reviews.freebsd.org/D19341
This commit is contained in:
parent
151c6d1020
commit
ce37b71e68
@ -287,12 +287,12 @@ static int
|
||||
bi_load_efi_data(struct preloaded_file *kfp)
|
||||
{
|
||||
EFI_MEMORY_DESCRIPTOR *mm;
|
||||
EFI_PHYSICAL_ADDRESS addr;
|
||||
EFI_PHYSICAL_ADDRESS addr = 0;
|
||||
EFI_STATUS status;
|
||||
const char *efi_novmap;
|
||||
size_t efisz;
|
||||
UINTN efi_mapkey;
|
||||
UINTN mmsz, pages, retry, sz;
|
||||
UINTN dsz, pages, retry, sz;
|
||||
UINT32 mmver;
|
||||
struct efi_map_header *efihdr;
|
||||
bool do_vmap;
|
||||
@ -323,76 +323,94 @@ bi_load_efi_data(struct preloaded_file *kfp)
|
||||
efisz = (sizeof(struct efi_map_header) + 0xf) & ~0xf;
|
||||
|
||||
/*
|
||||
* Assgin size of EFI_MEMORY_DESCRIPTOR to keep compatible with
|
||||
* Assign size of EFI_MEMORY_DESCRIPTOR to keep compatible with
|
||||
* u-boot which doesn't fill this value when buffer for memory
|
||||
* descriptors is too small (eg. 0 to obtain memory map size)
|
||||
*/
|
||||
mmsz = sizeof(EFI_MEMORY_DESCRIPTOR);
|
||||
dsz = sizeof(EFI_MEMORY_DESCRIPTOR);
|
||||
|
||||
/*
|
||||
* It is possible that the first call to ExitBootServices may change
|
||||
* the map key. Fetch a new map key and retry ExitBootServices in that
|
||||
* case.
|
||||
* Allocate enough pages to hold the bootinfo block and the
|
||||
* memory map EFI will return to us. The memory map has an
|
||||
* unknown size, so we have to determine that first. Note that
|
||||
* the AllocatePages call can itself modify the memory map, so
|
||||
* we have to take that into account as well. The changes to
|
||||
* the memory map are caused by splitting a range of free
|
||||
* memory into two, so that one is marked as being loader
|
||||
* data.
|
||||
*/
|
||||
|
||||
sz = 0;
|
||||
|
||||
/*
|
||||
* Matthew Garrett has observed at least one system changing the
|
||||
* memory map when calling ExitBootServices, causing it to return an
|
||||
* error, probably because callbacks are allocating memory.
|
||||
* So we need to retry calling it at least once.
|
||||
*/
|
||||
for (retry = 2; retry > 0; retry--) {
|
||||
/*
|
||||
* Allocate enough pages to hold the bootinfo block and the
|
||||
* memory map EFI will return to us. The memory map has an
|
||||
* unknown size, so we have to determine that first. Note that
|
||||
* the AllocatePages call can itself modify the memory map, so
|
||||
* we have to take that into account as well. The changes to
|
||||
* the memory map are caused by splitting a range of free
|
||||
* memory into two (AFAICT), so that one is marked as being
|
||||
* loader data.
|
||||
*/
|
||||
sz = 0;
|
||||
BS->GetMemoryMap(&sz, NULL, &efi_mapkey, &mmsz, &mmver);
|
||||
sz += mmsz;
|
||||
sz = (sz + 0xf) & ~0xf;
|
||||
pages = EFI_SIZE_TO_PAGES(sz + efisz);
|
||||
status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
|
||||
pages, &addr);
|
||||
if (EFI_ERROR(status)) {
|
||||
printf("%s: AllocatePages error %lu\n", __func__,
|
||||
EFI_ERROR_CODE(status));
|
||||
return (ENOMEM);
|
||||
}
|
||||
for (;;) {
|
||||
status = BS->GetMemoryMap(&sz, mm, &efi_mapkey, &dsz, &mmver);
|
||||
if (!EFI_ERROR(status))
|
||||
break;
|
||||
|
||||
/*
|
||||
* Read the memory map and stash it after bootinfo. Align the
|
||||
* memory map on a 16-byte boundary (the bootinfo block is page
|
||||
* aligned).
|
||||
*/
|
||||
efihdr = (struct efi_map_header *)(uintptr_t)addr;
|
||||
mm = (void *)((uint8_t *)efihdr + efisz);
|
||||
sz = (EFI_PAGE_SIZE * pages) - efisz;
|
||||
if (status != EFI_BUFFER_TOO_SMALL) {
|
||||
printf("%s: GetMemoryMap error %lu\n", __func__,
|
||||
EFI_ERROR_CODE(status));
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
if (addr != 0)
|
||||
BS->FreePages(addr, pages);
|
||||
|
||||
/* Add 10 descriptors to the size to allow for
|
||||
* fragmentation caused by calling AllocatePages */
|
||||
sz += (10 * dsz);
|
||||
pages = EFI_SIZE_TO_PAGES(sz + efisz);
|
||||
status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
|
||||
pages, &addr);
|
||||
if (EFI_ERROR(status)) {
|
||||
printf("%s: AllocatePages error %lu\n", __func__,
|
||||
EFI_ERROR_CODE(status));
|
||||
return (ENOMEM);
|
||||
}
|
||||
|
||||
status = BS->GetMemoryMap(&sz, mm, &efi_mapkey, &mmsz, &mmver);
|
||||
if (EFI_ERROR(status)) {
|
||||
printf("%s: GetMemoryMap error %lu\n", __func__,
|
||||
EFI_ERROR_CODE(status));
|
||||
return (EINVAL);
|
||||
}
|
||||
status = BS->ExitBootServices(IH, efi_mapkey);
|
||||
if (EFI_ERROR(status) == 0) {
|
||||
/*
|
||||
* This may be disabled by setting efi_disable_vmap in
|
||||
* loader.conf(5). By default we will setup the virtual
|
||||
* map entries.
|
||||
* Read the memory map and stash it after bootinfo. Align the
|
||||
* memory map on a 16-byte boundary (the bootinfo block is page
|
||||
* aligned).
|
||||
*/
|
||||
if (do_vmap)
|
||||
efi_do_vmap(mm, sz, mmsz, mmver);
|
||||
efihdr->memory_size = sz;
|
||||
efihdr->descriptor_size = mmsz;
|
||||
efihdr->descriptor_version = mmver;
|
||||
file_addmetadata(kfp, MODINFOMD_EFI_MAP, efisz + sz,
|
||||
efihdr);
|
||||
return (0);
|
||||
efihdr = (struct efi_map_header *)(uintptr_t)addr;
|
||||
mm = (void *)((uint8_t *)efihdr + efisz);
|
||||
sz = (EFI_PAGE_SIZE * pages) - efisz;
|
||||
}
|
||||
BS->FreePages(addr, pages);
|
||||
|
||||
status = BS->ExitBootServices(IH, efi_mapkey);
|
||||
if (!EFI_ERROR(status))
|
||||
break;
|
||||
}
|
||||
printf("ExitBootServices error %lu\n", EFI_ERROR_CODE(status));
|
||||
return (EINVAL);
|
||||
|
||||
if (retry == 0) {
|
||||
BS->FreePages(addr, pages);
|
||||
printf("ExitBootServices error %lu\n", EFI_ERROR_CODE(status));
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* This may be disabled by setting efi_disable_vmap in
|
||||
* loader.conf(5). By default we will setup the virtual
|
||||
* map entries.
|
||||
*/
|
||||
|
||||
if (do_vmap)
|
||||
efi_do_vmap(mm, sz, dsz, mmver);
|
||||
efihdr->memory_size = sz;
|
||||
efihdr->descriptor_size = dsz;
|
||||
efihdr->descriptor_version = mmver;
|
||||
file_addmetadata(kfp, MODINFOMD_EFI_MAP, efisz + sz,
|
||||
efihdr);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -95,7 +95,7 @@ static void
|
||||
efi_verify_staging_size(unsigned long *nr_pages)
|
||||
{
|
||||
UINTN sz;
|
||||
EFI_MEMORY_DESCRIPTOR *map, *p;
|
||||
EFI_MEMORY_DESCRIPTOR *map = NULL, *p;
|
||||
EFI_PHYSICAL_ADDRESS start, end;
|
||||
UINTN key, dsz;
|
||||
UINT32 dver;
|
||||
@ -104,17 +104,28 @@ efi_verify_staging_size(unsigned long *nr_pages)
|
||||
unsigned long available_pages = 0;
|
||||
|
||||
sz = 0;
|
||||
status = BS->GetMemoryMap(&sz, 0, &key, &dsz, &dver);
|
||||
if (status != EFI_BUFFER_TOO_SMALL) {
|
||||
printf("Can't determine memory map size\n");
|
||||
return;
|
||||
}
|
||||
|
||||
map = malloc(sz);
|
||||
status = BS->GetMemoryMap(&sz, map, &key, &dsz, &dver);
|
||||
if (EFI_ERROR(status)) {
|
||||
printf("Can't read memory map\n");
|
||||
goto out;
|
||||
for (;;) {
|
||||
status = BS->GetMemoryMap(&sz, map, &key, &dsz, &dver);
|
||||
if (!EFI_ERROR(status))
|
||||
break;
|
||||
|
||||
if (status != EFI_BUFFER_TOO_SMALL) {
|
||||
printf("Can't read memory map: %lu\n",
|
||||
EFI_ERROR_CODE(status));
|
||||
goto out;
|
||||
}
|
||||
|
||||
free(map);
|
||||
|
||||
/* Allocate 10 descriptors more than the size reported,
|
||||
* to allow for any fragmentation caused by calling
|
||||
* malloc */
|
||||
map = malloc(sz + (10 * dsz));
|
||||
if (map == NULL) {
|
||||
printf("Unable to allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ndesc = sz / dsz;
|
||||
|
Loading…
Reference in New Issue
Block a user