|
@@ -41,6 +41,8 @@ static unsigned long __chunk_size = EFI_READ_CHUNK_SIZE;
|
|
|
#define EFI_ALLOC_ALIGN EFI_PAGE_SIZE
|
|
|
#endif
|
|
|
|
|
|
+#define EFI_MMAP_NR_SLACK_SLOTS 8
|
|
|
+
|
|
|
struct file_info {
|
|
|
efi_file_handle_t *handle;
|
|
|
u64 size;
|
|
@@ -63,49 +65,62 @@ void efi_printk(efi_system_table_t *sys_table_arg, char *str)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static inline bool mmap_has_headroom(unsigned long buff_size,
|
|
|
+ unsigned long map_size,
|
|
|
+ unsigned long desc_size)
|
|
|
+{
|
|
|
+ unsigned long slack = buff_size - map_size;
|
|
|
+
|
|
|
+ return slack / desc_size >= EFI_MMAP_NR_SLACK_SLOTS;
|
|
|
+}
|
|
|
+
|
|
|
efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg,
|
|
|
- efi_memory_desc_t **map,
|
|
|
- unsigned long *map_size,
|
|
|
- unsigned long *desc_size,
|
|
|
- u32 *desc_ver,
|
|
|
- unsigned long *key_ptr)
|
|
|
+ struct efi_boot_memmap *map)
|
|
|
{
|
|
|
efi_memory_desc_t *m = NULL;
|
|
|
efi_status_t status;
|
|
|
unsigned long key;
|
|
|
u32 desc_version;
|
|
|
|
|
|
- *map_size = sizeof(*m) * 32;
|
|
|
+ *map->desc_size = sizeof(*m);
|
|
|
+ *map->map_size = *map->desc_size * 32;
|
|
|
+ *map->buff_size = *map->map_size;
|
|
|
again:
|
|
|
- /*
|
|
|
- * Add an additional efi_memory_desc_t because we're doing an
|
|
|
- * allocation which may be in a new descriptor region.
|
|
|
- */
|
|
|
- *map_size += sizeof(*m);
|
|
|
status = efi_call_early(allocate_pool, EFI_LOADER_DATA,
|
|
|
- *map_size, (void **)&m);
|
|
|
+ *map->map_size, (void **)&m);
|
|
|
if (status != EFI_SUCCESS)
|
|
|
goto fail;
|
|
|
|
|
|
- *desc_size = 0;
|
|
|
+ *map->desc_size = 0;
|
|
|
key = 0;
|
|
|
- status = efi_call_early(get_memory_map, map_size, m,
|
|
|
- &key, desc_size, &desc_version);
|
|
|
- if (status == EFI_BUFFER_TOO_SMALL) {
|
|
|
+ status = efi_call_early(get_memory_map, map->map_size, m,
|
|
|
+ &key, map->desc_size, &desc_version);
|
|
|
+ if (status == EFI_BUFFER_TOO_SMALL ||
|
|
|
+ !mmap_has_headroom(*map->buff_size, *map->map_size,
|
|
|
+ *map->desc_size)) {
|
|
|
efi_call_early(free_pool, m);
|
|
|
+ /*
|
|
|
+ * Make sure there is some entries of headroom so that the
|
|
|
+ * buffer can be reused for a new map after allocations are
|
|
|
+ * no longer permitted. Its unlikely that the map will grow to
|
|
|
+ * exceed this headroom once we are ready to trigger
|
|
|
+ * ExitBootServices()
|
|
|
+ */
|
|
|
+ *map->map_size += *map->desc_size * EFI_MMAP_NR_SLACK_SLOTS;
|
|
|
+ *map->buff_size = *map->map_size;
|
|
|
goto again;
|
|
|
}
|
|
|
|
|
|
if (status != EFI_SUCCESS)
|
|
|
efi_call_early(free_pool, m);
|
|
|
|
|
|
- if (key_ptr && status == EFI_SUCCESS)
|
|
|
- *key_ptr = key;
|
|
|
- if (desc_ver && status == EFI_SUCCESS)
|
|
|
- *desc_ver = desc_version;
|
|
|
+ if (map->key_ptr && status == EFI_SUCCESS)
|
|
|
+ *map->key_ptr = key;
|
|
|
+ if (map->desc_ver && status == EFI_SUCCESS)
|
|
|
+ *map->desc_ver = desc_version;
|
|
|
|
|
|
fail:
|
|
|
- *map = m;
|
|
|
+ *map->map = m;
|
|
|
return status;
|
|
|
}
|
|
|
|
|
@@ -113,13 +128,20 @@ fail:
|
|
|
unsigned long get_dram_base(efi_system_table_t *sys_table_arg)
|
|
|
{
|
|
|
efi_status_t status;
|
|
|
- unsigned long map_size;
|
|
|
+ unsigned long map_size, buff_size;
|
|
|
unsigned long membase = EFI_ERROR;
|
|
|
struct efi_memory_map map;
|
|
|
efi_memory_desc_t *md;
|
|
|
+ struct efi_boot_memmap boot_map;
|
|
|
|
|
|
- status = efi_get_memory_map(sys_table_arg, (efi_memory_desc_t **)&map.map,
|
|
|
- &map_size, &map.desc_size, NULL, NULL);
|
|
|
+ boot_map.map = (efi_memory_desc_t **)&map.map;
|
|
|
+ boot_map.map_size = &map_size;
|
|
|
+ boot_map.desc_size = &map.desc_size;
|
|
|
+ boot_map.desc_ver = NULL;
|
|
|
+ boot_map.key_ptr = NULL;
|
|
|
+ boot_map.buff_size = &buff_size;
|
|
|
+
|
|
|
+ status = efi_get_memory_map(sys_table_arg, &boot_map);
|
|
|
if (status != EFI_SUCCESS)
|
|
|
return membase;
|
|
|
|
|
@@ -144,15 +166,22 @@ efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg,
|
|
|
unsigned long size, unsigned long align,
|
|
|
unsigned long *addr, unsigned long max)
|
|
|
{
|
|
|
- unsigned long map_size, desc_size;
|
|
|
+ unsigned long map_size, desc_size, buff_size;
|
|
|
efi_memory_desc_t *map;
|
|
|
efi_status_t status;
|
|
|
unsigned long nr_pages;
|
|
|
u64 max_addr = 0;
|
|
|
int i;
|
|
|
+ struct efi_boot_memmap boot_map;
|
|
|
+
|
|
|
+ boot_map.map = ↦
|
|
|
+ boot_map.map_size = &map_size;
|
|
|
+ boot_map.desc_size = &desc_size;
|
|
|
+ boot_map.desc_ver = NULL;
|
|
|
+ boot_map.key_ptr = NULL;
|
|
|
+ boot_map.buff_size = &buff_size;
|
|
|
|
|
|
- status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size,
|
|
|
- NULL, NULL);
|
|
|
+ status = efi_get_memory_map(sys_table_arg, &boot_map);
|
|
|
if (status != EFI_SUCCESS)
|
|
|
goto fail;
|
|
|
|
|
@@ -230,14 +259,21 @@ efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg,
|
|
|
unsigned long size, unsigned long align,
|
|
|
unsigned long *addr)
|
|
|
{
|
|
|
- unsigned long map_size, desc_size;
|
|
|
+ unsigned long map_size, desc_size, buff_size;
|
|
|
efi_memory_desc_t *map;
|
|
|
efi_status_t status;
|
|
|
unsigned long nr_pages;
|
|
|
int i;
|
|
|
+ struct efi_boot_memmap boot_map;
|
|
|
|
|
|
- status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size,
|
|
|
- NULL, NULL);
|
|
|
+ boot_map.map = ↦
|
|
|
+ boot_map.map_size = &map_size;
|
|
|
+ boot_map.desc_size = &desc_size;
|
|
|
+ boot_map.desc_ver = NULL;
|
|
|
+ boot_map.key_ptr = NULL;
|
|
|
+ boot_map.buff_size = &buff_size;
|
|
|
+
|
|
|
+ status = efi_get_memory_map(sys_table_arg, &boot_map);
|
|
|
if (status != EFI_SUCCESS)
|
|
|
goto fail;
|
|
|
|
|
@@ -704,3 +740,76 @@ char *efi_convert_cmdline(efi_system_table_t *sys_table_arg,
|
|
|
*cmd_line_len = options_bytes;
|
|
|
return (char *)cmdline_addr;
|
|
|
}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Handle calling ExitBootServices according to the requirements set out by the
|
|
|
+ * spec. Obtains the current memory map, and returns that info after calling
|
|
|
+ * ExitBootServices. The client must specify a function to perform any
|
|
|
+ * processing of the memory map data prior to ExitBootServices. A client
|
|
|
+ * specific structure may be passed to the function via priv. The client
|
|
|
+ * function may be called multiple times.
|
|
|
+ */
|
|
|
+efi_status_t efi_exit_boot_services(efi_system_table_t *sys_table_arg,
|
|
|
+ void *handle,
|
|
|
+ struct efi_boot_memmap *map,
|
|
|
+ void *priv,
|
|
|
+ efi_exit_boot_map_processing priv_func)
|
|
|
+{
|
|
|
+ efi_status_t status;
|
|
|
+
|
|
|
+ status = efi_get_memory_map(sys_table_arg, map);
|
|
|
+
|
|
|
+ if (status != EFI_SUCCESS)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ status = priv_func(sys_table_arg, map, priv);
|
|
|
+ if (status != EFI_SUCCESS)
|
|
|
+ goto free_map;
|
|
|
+
|
|
|
+ status = efi_call_early(exit_boot_services, handle, *map->key_ptr);
|
|
|
+
|
|
|
+ if (status == EFI_INVALID_PARAMETER) {
|
|
|
+ /*
|
|
|
+ * The memory map changed between efi_get_memory_map() and
|
|
|
+ * exit_boot_services(). Per the UEFI Spec v2.6, Section 6.4:
|
|
|
+ * EFI_BOOT_SERVICES.ExitBootServices we need to get the
|
|
|
+ * updated map, and try again. The spec implies one retry
|
|
|
+ * should be sufficent, which is confirmed against the EDK2
|
|
|
+ * implementation. Per the spec, we can only invoke
|
|
|
+ * get_memory_map() and exit_boot_services() - we cannot alloc
|
|
|
+ * so efi_get_memory_map() cannot be used, and we must reuse
|
|
|
+ * the buffer. For all practical purposes, the headroom in the
|
|
|
+ * buffer should account for any changes in the map so the call
|
|
|
+ * to get_memory_map() is expected to succeed here.
|
|
|
+ */
|
|
|
+ *map->map_size = *map->buff_size;
|
|
|
+ status = efi_call_early(get_memory_map,
|
|
|
+ map->map_size,
|
|
|
+ *map->map,
|
|
|
+ map->key_ptr,
|
|
|
+ map->desc_size,
|
|
|
+ map->desc_ver);
|
|
|
+
|
|
|
+ /* exit_boot_services() was called, thus cannot free */
|
|
|
+ if (status != EFI_SUCCESS)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ status = priv_func(sys_table_arg, map, priv);
|
|
|
+ /* exit_boot_services() was called, thus cannot free */
|
|
|
+ if (status != EFI_SUCCESS)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ status = efi_call_early(exit_boot_services, handle, *map->key_ptr);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* exit_boot_services() was called, thus cannot free */
|
|
|
+ if (status != EFI_SUCCESS)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ return EFI_SUCCESS;
|
|
|
+
|
|
|
+free_map:
|
|
|
+ efi_call_early(free_pool, *map->map);
|
|
|
+fail:
|
|
|
+ return status;
|
|
|
+}
|