2009-06-11 Felix Zielcke <fzielcke@z-51.de>
[grub2/phcoder.git] / kern / efi / mm.c
blobe1fca81f376c4c24649760a3e1780d1abd46d73b
1 /* mm.c - generic EFI memory management */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2006,2007,2008 Free Software Foundation, Inc.
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
20 #include <grub/misc.h>
21 #include <grub/mm.h>
22 #include <grub/efi/api.h>
23 #include <grub/efi/efi.h>
25 #define NEXT_MEMORY_DESCRIPTOR(desc, size) \
26 ((grub_efi_memory_descriptor_t *) ((char *) (desc) + (size)))
28 #define BYTES_TO_PAGES(bytes) ((bytes) >> 12)
29 #define PAGES_TO_BYTES(pages) ((pages) << 12)
31 /* The size of a memory map obtained from the firmware. This must be
32 a multiplier of 4KB. */
33 #define MEMORY_MAP_SIZE 0x3000
35 /* Maintain the list of allocated pages. */
36 struct allocated_page
38 grub_efi_physical_address_t addr;
39 grub_efi_uint64_t num_pages;
42 #define ALLOCATED_PAGES_SIZE 0x1000
43 #define MAX_ALLOCATED_PAGES \
44 (ALLOCATED_PAGES_SIZE / sizeof (struct allocated_page))
46 static struct allocated_page *allocated_pages = 0;
48 /* The minimum and maximum heap size for GRUB itself. */
49 #define MIN_HEAP_SIZE 0x100000
50 #define MAX_HEAP_SIZE (1600 * 0x100000)
53 /* Allocate pages. Return the pointer to the first of allocated pages. */
54 void *
55 grub_efi_allocate_pages (grub_efi_physical_address_t address,
56 grub_efi_uintn_t pages)
58 grub_efi_allocate_type_t type;
59 grub_efi_status_t status;
60 grub_efi_boot_services_t *b;
62 #if GRUB_TARGET_SIZEOF_VOID_P < 8
63 /* Limit the memory access to less than 4GB for 32-bit platforms. */
64 if (address > 0xffffffff)
65 return 0;
66 #endif
68 #if GRUB_TARGET_SIZEOF_VOID_P < 8 || defined (MCMODEL_SMALL)
69 if (address == 0)
71 type = GRUB_EFI_ALLOCATE_MAX_ADDRESS;
72 address = 0xffffffff;
74 else
75 type = GRUB_EFI_ALLOCATE_ADDRESS;
76 #else
77 if (address == 0)
78 type = GRUB_EFI_ALLOCATE_ANY_PAGES;
79 else
80 type = GRUB_EFI_ALLOCATE_ADDRESS;
81 #endif
83 b = grub_efi_system_table->boot_services;
84 status = efi_call_4 (b->allocate_pages, type, GRUB_EFI_LOADER_DATA, pages, &address);
85 if (status != GRUB_EFI_SUCCESS)
86 return 0;
88 if (address == 0)
90 /* Uggh, the address 0 was allocated... This is too annoying,
91 so reallocate another one. */
92 address = 0xffffffff;
93 status = efi_call_4 (b->allocate_pages, type, GRUB_EFI_LOADER_DATA, pages, &address);
94 grub_efi_free_pages (0, pages);
95 if (status != GRUB_EFI_SUCCESS)
96 return 0;
99 if (allocated_pages)
101 unsigned i;
103 for (i = 0; i < MAX_ALLOCATED_PAGES; i++)
104 if (allocated_pages[i].addr == 0)
106 allocated_pages[i].addr = address;
107 allocated_pages[i].num_pages = pages;
108 break;
111 if (i == MAX_ALLOCATED_PAGES)
112 grub_fatal ("too many page allocations");
115 return (void *) ((grub_addr_t) address);
118 /* Free pages starting from ADDRESS. */
119 void
120 grub_efi_free_pages (grub_efi_physical_address_t address,
121 grub_efi_uintn_t pages)
123 grub_efi_boot_services_t *b;
125 if (allocated_pages
126 && ((grub_efi_physical_address_t) ((grub_addr_t) allocated_pages)
127 != address))
129 unsigned i;
131 for (i = 0; i < MAX_ALLOCATED_PAGES; i++)
132 if (allocated_pages[i].addr == address)
134 allocated_pages[i].addr = 0;
135 break;
139 b = grub_efi_system_table->boot_services;
140 efi_call_2 (b->free_pages, address, pages);
143 /* Get the memory map as defined in the EFI spec. Return 1 if successful,
144 return 0 if partial, or return -1 if an error occurs. */
146 grub_efi_get_memory_map (grub_efi_uintn_t *memory_map_size,
147 grub_efi_memory_descriptor_t *memory_map,
148 grub_efi_uintn_t *map_key,
149 grub_efi_uintn_t *descriptor_size,
150 grub_efi_uint32_t *descriptor_version)
152 grub_efi_status_t status;
153 grub_efi_boot_services_t *b;
154 grub_efi_uintn_t key;
155 grub_efi_uint32_t version;
157 /* Allow some parameters to be missing. */
158 if (! map_key)
159 map_key = &key;
160 if (! descriptor_version)
161 descriptor_version = &version;
163 b = grub_efi_system_table->boot_services;
164 status = efi_call_5 (b->get_memory_map, memory_map_size, memory_map, map_key,
165 descriptor_size, descriptor_version);
166 if (status == GRUB_EFI_SUCCESS)
167 return 1;
168 else if (status == GRUB_EFI_BUFFER_TOO_SMALL)
169 return 0;
170 else
171 return -1;
174 /* Sort the memory map in place. */
175 static void
176 sort_memory_map (grub_efi_memory_descriptor_t *memory_map,
177 grub_efi_uintn_t desc_size,
178 grub_efi_memory_descriptor_t *memory_map_end)
180 grub_efi_memory_descriptor_t *d1;
181 grub_efi_memory_descriptor_t *d2;
183 for (d1 = memory_map;
184 d1 < memory_map_end;
185 d1 = NEXT_MEMORY_DESCRIPTOR (d1, desc_size))
187 grub_efi_memory_descriptor_t *max_desc = d1;
189 for (d2 = NEXT_MEMORY_DESCRIPTOR (d1, desc_size);
190 d2 < memory_map_end;
191 d2 = NEXT_MEMORY_DESCRIPTOR (d2, desc_size))
193 if (max_desc->num_pages < d2->num_pages)
194 max_desc = d2;
197 if (max_desc != d1)
199 grub_efi_memory_descriptor_t tmp;
201 tmp = *d1;
202 *d1 = *max_desc;
203 *max_desc = tmp;
208 /* Filter the descriptors. GRUB needs only available memory. */
209 static grub_efi_memory_descriptor_t *
210 filter_memory_map (grub_efi_memory_descriptor_t *memory_map,
211 grub_efi_memory_descriptor_t *filtered_memory_map,
212 grub_efi_uintn_t desc_size,
213 grub_efi_memory_descriptor_t *memory_map_end)
215 grub_efi_memory_descriptor_t *desc;
216 grub_efi_memory_descriptor_t *filtered_desc;
218 for (desc = memory_map, filtered_desc = filtered_memory_map;
219 desc < memory_map_end;
220 desc = NEXT_MEMORY_DESCRIPTOR (desc, desc_size))
222 if (desc->type == GRUB_EFI_CONVENTIONAL_MEMORY
223 #if GRUB_TARGET_SIZEOF_VOID_P < 8 || defined (MCMODEL_SMALL)
224 && desc->physical_start <= 0xffffffff
225 #endif
226 && desc->physical_start + PAGES_TO_BYTES (desc->num_pages) > 0x100000
227 && desc->num_pages != 0)
229 grub_memcpy (filtered_desc, desc, desc_size);
231 /* Avoid less than 1MB, because some loaders seem to be confused. */
232 if (desc->physical_start < 0x100000)
234 desc->num_pages -= BYTES_TO_PAGES (0x100000
235 - desc->physical_start);
236 desc->physical_start = 0x100000;
239 #if GRUB_TARGET_SIZEOF_VOID_P < 8 || defined (MCMODEL_SMALL)
240 if (BYTES_TO_PAGES (filtered_desc->physical_start)
241 + filtered_desc->num_pages
242 > BYTES_TO_PAGES (0x100000000LL))
243 filtered_desc->num_pages
244 = (BYTES_TO_PAGES (0x100000000LL)
245 - BYTES_TO_PAGES (filtered_desc->physical_start));
246 #endif
248 if (filtered_desc->num_pages == 0)
249 continue;
251 filtered_desc = NEXT_MEMORY_DESCRIPTOR (filtered_desc, desc_size);
255 return filtered_desc;
258 /* Return the total number of pages. */
259 static grub_efi_uint64_t
260 get_total_pages (grub_efi_memory_descriptor_t *memory_map,
261 grub_efi_uintn_t desc_size,
262 grub_efi_memory_descriptor_t *memory_map_end)
264 grub_efi_memory_descriptor_t *desc;
265 grub_efi_uint64_t total = 0;
267 for (desc = memory_map;
268 desc < memory_map_end;
269 desc = NEXT_MEMORY_DESCRIPTOR (desc, desc_size))
270 total += desc->num_pages;
272 return total;
275 /* Add memory regions. */
276 static void
277 add_memory_regions (grub_efi_memory_descriptor_t *memory_map,
278 grub_efi_uintn_t desc_size,
279 grub_efi_memory_descriptor_t *memory_map_end,
280 grub_efi_uint64_t required_pages)
282 grub_efi_memory_descriptor_t *desc;
284 for (desc = memory_map;
285 desc < memory_map_end;
286 desc = NEXT_MEMORY_DESCRIPTOR (desc, desc_size))
288 grub_efi_uint64_t pages;
289 grub_efi_physical_address_t start;
290 void *addr;
292 start = desc->physical_start;
293 pages = desc->num_pages;
294 if (pages > required_pages)
296 start += PAGES_TO_BYTES (pages - required_pages);
297 pages = required_pages;
300 addr = grub_efi_allocate_pages (start, pages);
301 if (! addr)
302 grub_fatal ("cannot allocate conventional memory %p with %u pages",
303 (void *) ((grub_addr_t) start),
304 (unsigned) pages);
306 grub_mm_init_region (addr, PAGES_TO_BYTES (pages));
308 required_pages -= pages;
309 if (required_pages == 0)
310 break;
313 if (required_pages > 0)
314 grub_fatal ("too little memory");
317 #if 0
318 /* Print the memory map. */
319 static void
320 print_memory_map (grub_efi_memory_descriptor_t *memory_map,
321 grub_efi_uintn_t desc_size,
322 grub_efi_memory_descriptor_t *memory_map_end)
324 grub_efi_memory_descriptor_t *desc;
325 int i;
327 for (desc = memory_map, i = 0;
328 desc < memory_map_end;
329 desc = NEXT_MEMORY_DESCRIPTOR (desc, desc_size), i++)
331 grub_printf ("MD: t=%x, p=%llx, v=%llx, n=%llx, a=%llx\n",
332 desc->type, desc->physical_start, desc->virtual_start,
333 desc->num_pages, desc->attribute);
336 #endif
338 void
339 grub_efi_mm_init (void)
341 grub_efi_memory_descriptor_t *memory_map;
342 grub_efi_memory_descriptor_t *memory_map_end;
343 grub_efi_memory_descriptor_t *filtered_memory_map;
344 grub_efi_memory_descriptor_t *filtered_memory_map_end;
345 grub_efi_uintn_t map_size;
346 grub_efi_uintn_t desc_size;
347 grub_efi_uint64_t total_pages;
348 grub_efi_uint64_t required_pages;
350 /* First of all, allocate pages to maintain allocations. */
351 allocated_pages
352 = grub_efi_allocate_pages (0, BYTES_TO_PAGES (ALLOCATED_PAGES_SIZE));
353 if (! allocated_pages)
354 grub_fatal ("cannot allocate memory");
356 grub_memset (allocated_pages, 0, ALLOCATED_PAGES_SIZE);
358 /* Prepare a memory region to store two memory maps. */
359 memory_map = grub_efi_allocate_pages (0,
360 2 * BYTES_TO_PAGES (MEMORY_MAP_SIZE));
361 if (! memory_map)
362 grub_fatal ("cannot allocate memory");
364 filtered_memory_map = NEXT_MEMORY_DESCRIPTOR (memory_map, MEMORY_MAP_SIZE);
366 /* Obtain descriptors for available memory. */
367 map_size = MEMORY_MAP_SIZE;
369 if (grub_efi_get_memory_map (&map_size, memory_map, 0, &desc_size, 0) < 0)
370 grub_fatal ("cannot get memory map");
372 memory_map_end = NEXT_MEMORY_DESCRIPTOR (memory_map, map_size);
374 filtered_memory_map_end = filter_memory_map (memory_map, filtered_memory_map,
375 desc_size, memory_map_end);
377 /* By default, request a quarter of the available memory. */
378 total_pages = get_total_pages (filtered_memory_map, desc_size,
379 filtered_memory_map_end);
380 required_pages = (total_pages >> 2);
381 if (required_pages < BYTES_TO_PAGES (MIN_HEAP_SIZE))
382 required_pages = BYTES_TO_PAGES (MIN_HEAP_SIZE);
383 else if (required_pages > BYTES_TO_PAGES (MAX_HEAP_SIZE))
384 required_pages = BYTES_TO_PAGES (MAX_HEAP_SIZE);
386 /* Sort the filtered descriptors, so that GRUB can allocate pages
387 from smaller regions. */
388 sort_memory_map (filtered_memory_map, desc_size, filtered_memory_map_end);
390 /* Allocate memory regions for GRUB's memory management. */
391 add_memory_regions (filtered_memory_map, desc_size,
392 filtered_memory_map_end, required_pages);
394 #if 0
395 /* For debug. */
396 map_size = MEMORY_MAP_SIZE;
398 if (grub_efi_get_memory_map (&map_size, memory_map, 0, &desc_size, 0) < 0)
399 grub_fatal ("cannot get memory map");
401 grub_printf ("printing memory map\n");
402 print_memory_map (memory_map, desc_size,
403 NEXT_MEMORY_DESCRIPTOR (memory_map, map_size));
404 grub_abort ();
405 #endif
407 /* Release the memory maps. */
408 grub_efi_free_pages ((grub_addr_t) memory_map,
409 2 * BYTES_TO_PAGES (MEMORY_MAP_SIZE));
412 void
413 grub_efi_mm_fini (void)
415 if (allocated_pages)
417 unsigned i;
419 for (i = 0; i < MAX_ALLOCATED_PAGES; i++)
421 struct allocated_page *p;
423 p = allocated_pages + i;
424 if (p->addr != 0)
425 grub_efi_free_pages ((grub_addr_t) p->addr, p->num_pages);
428 grub_efi_free_pages ((grub_addr_t) allocated_pages,
429 BYTES_TO_PAGES (ALLOCATED_PAGES_SIZE));