Rewrite of EFI memory map walk code
PATCH 1/1 Rewrite of EFI memory map walk code
In an attempt to simplify EFI emmory range management and EFI memory map walk, I have rewritten how EFI memory map is managed in the kernel. Here is how I have approached this code. I am walking the EFI memory map and from there I am constructing a linked list of valid memory ranges for kernel to use. To find space to store this linked list in, from within reserve_memory(), I walk the EFI memory map and look for the smallest granule aligned block that will be sufficient to hold the linked list. Amount of space I am allocating for linked list is the number of entries in efi_memmap + another few for safety margin. I then mark this memory reserved.
I have added two new functions - efi_gather_memory() which walks the EFI memory map and builds a linked list of kernel usable memory map, and efi_trim_memory() which applies the "max_addr=" and "mem=" restrictions by splitting the linked list of kernel uable memory ranges appropriately. Each node in this linked list has a field called "ignore". To trim a memory range, I can split the list at approrpiate point and mark every node in the list from that point onwards as ignored by setting this field.
I rewrote efi_memmap_walk() to use the new kern_memmap list. I have also replaced occurences of "md->num_pages << EFI_PAGE_SHIFT" with a macro.
One issue I have not been able to come up with a good solution for is what to do if I fail to allocate memory for the linked list of memory ranges. The only time this will happen is if I can not find a single memory range that is larger than granule size. If that were to happen, I do not see how the machine will boot even if we did not need space for EFI memory map. So for now I have shelved this issue and simply do a machine_restart() if this were to happen ever.
Tony, please apply.
Signature · 9 lines
Khalid ==================================================================== Khalid Aziz Linux and Open Source Lab (970)898-9214 Hewlett-Packard [email protected] Fort Collins, CO "The Linux kernel is subject to relentless development" - Alessandro Rubini
arch/ia64/kernel/efi.c +208 -112
@@ -17,6 +17,10 @@* * Goutham Rao: <[email protected]> * Skip non-WB memory and ignore empty memory ranges. +* +* Rewrote efi_memap_walk() to create a linked list of available +* memory regions instead of editing EFI memory map in place +* - Khalid Aziz <[email protected]> */ #include <linux/config.h> #include <linux/module.h>
@@ -35,12 +39,17 @@#define EFI_DEBUG 0 +#define efi_md_size (md) (md->num_pages << EFI_PAGE_SHIFT) +extern efi_status_t efi_call_phys (void *, ...);struct efi efi;EXPORT_SYMBOL (efi);static efi_runtime_services_t *runtime;static unsigned long mem_limit = ~0UL , max_addr = ~0UL ; +static kern_memdesc_t *kern_memmap =NULL ; +static unsigned long efi_total_mem =0UL ; +kern_memdesc_t *memdesc_area, *memdesc_end; #define efi_call_virt(f, args...) (*(f))(args)
@@ -222,60 +231,43 @@ efi_gettimeofday (struct timespec *ts) ts->tv_nsec = tm.nanosecond; } -static int -is_available_memory (efi_memory_desc_t *md) -{ -if (!(md->attribute &EFI_MEMORY_WB )) -return 0 ; - -switch (md->type) { -case EFI_LOADER_CODE : -case EFI_LOADER_DATA : -case EFI_BOOT_SERVICES_CODE : -case EFI_BOOT_SERVICES_DATA : -case EFI_CONVENTIONAL_MEMORY : -return 1 ; - } -return 0 ; -} -/* * Trim descriptor MD so its starts at address START_ADDR. If the descriptor covers * memory that is normally available to the kernel, issue a warning that some memory * is being ignored. */ static void -trim_bottom (efi_memory_desc_t *md,u64 start_addr) +granule_align_bottom (kern_memdesc_t *md,u64 start_addr) {u64 num_skipped_pages; -if (md->phys_addr >= start_addr || !md->num_pages) +if (md->start >= start_addr || !md->num_pages)return ; - num_skipped_pages = (start_addr - md->phys_addr) >>EFI_PAGE_SHIFT ; + num_skipped_pages = (start_addr - md->start) >>EFI_PAGE_SHIFT ;if (num_skipped_pages > md->num_pages) num_skipped_pages = md->num_pages;if (is_available_memory (md)) -printk (KERN_NOTICE "efi.%s: ignoring %luKB of memory at 0x%lx due to granule hole " -"at 0x%lx\n" , __FUNCTION__, - (num_skipped_pages <<EFI_PAGE_SHIFT ) >>10 , - md->phys_addr, start_addr -IA64_GRANULE_SIZE ); +printk (KERN_NOTICE "efi.%s: ignoring %luKB of memory at " +"0x%lx due to granule hole at 0x%lx\n" , __FUNCTION__, + (num_skipped_pages <<EFI_PAGE_SHIFT ) >>10 , + md->start, start_addr -IA64_GRANULE_SIZE );/* -* NOTE: Don't set md->phys_addr to START_ADDR because that could cause the memory -* descriptor list to become unsorted. In such a case, md->num_pages will be -* zero, so the Right Thing will happen. +* NOTE: Don't set md->start to start_addr because that could +* cause the memory descriptor list to become unsorted. In such +* a case, md->num_pages will be zero, so the Right Thing will happen. */ - md->phys_addr += num_skipped_pages <<EFI_PAGE_SHIFT ; + md->start += num_skipped_pages <<EFI_PAGE_SHIFT ; md->num_pages -= num_skipped_pages; }static void -trim_top (efi_memory_desc_t *md,u64 end_addr) +granule_align_top (kern_memdesc_t *md,u64 end_addr) {u64 num_dropped_pages, md_end_addr; - md_end_addr = md->phys_addr + (md->num_pages <<EFI_PAGE_SHIFT ); + md_end_addr = md->start +efi_md_size (md);if (md_end_addr <= end_addr || !md->num_pages)return ;
@@ -288,124 +280,228 @@ trim_top (efi_memory_desc_t *md, u64 endprintk (KERN_NOTICE "efi.%s: ignoring %luKB of memory at 0x%lx due to granule hole " "at 0x%lx\n" , __FUNCTION__, (num_dropped_pages <<EFI_PAGE_SHIFT ) >>10 , - md->phys_addr, end_addr); + md->start, end_addr); md->num_pages -= num_dropped_pages; } +/* +* Allocate a node for kernel memory descriptor. These allocations are never +* freed. +*/ +static kern_memdesc_t * +memdesc_alloc (void ) +{ +if (memdesc_area >= memdesc_end) +return (-1UL ); +return (memdesc_area++); +} +/* -* Walks the EFI memory map and calls CALLBACK once for each EFI memory descriptor that -* has memory that is available for OS use. +* Walks the EFI memory map and calls CALLBACK once for each EFI +* memory descriptor that has memory that is available for OS use. */ void efi_memmap_walk (efi_freemem_callback_t callback,void *arg) { -int prev_valid =0 ; -struct range { -u64 start; -u64 end; - } prev, curr; +kern_memdesc_t *memnode; +u64 start, end; + + memnode = kern_memmap; + +while (memnode !=NULL ) { +if (!is_available_memory (memnode) || memnode->ignore) { + memnode = memnode->next; +continue ; + } + start =PAGE_OFFSET + memnode->start; + end = (start +efi_md_size (memnode)) &PAGE_MASK ; + +if ((*callback)(start, end, arg) <0 ) +return ; + memnode = memnode->next; + } +} + +/* +* Trim memory size in accordance with "mem=" and "max_addr=" parameters +*/ +void +efi_trim_memory (void ) +{ +kern_memdesc_t *memnode, *newnode; +unsigned long total_mem =0UL ; +unsigned long new_pages; + +/* Do we have a memory size limit? If not, we are done */ +if ((mem_limit == ~0UL ) && (max_addr == ~0UL )) +return ; + +/* +* walk the memory map to find where the limit kicks in +*/ + memnode = kern_memmap; +while (memnode !=NULL ) { + total_mem +=efi_md_size (memnode); + +if (memnode->start +efi_md_size (memnode) > max_addr) { + new_pages = (max_addr - memnode->start) >>EFI_PAGE_SHIFT ; +break ; + } +if (total_mem > mem_limit) { + new_pages = memnode->num_pages - + ((total_mem - mem_limit) >>EFI_PAGE_SHIFT ); +break ; + } + memnode = memnode->next; + } + +/* Check for the case where the limit is larger than available mem */ +if (memnode ==NULL ) +return ; + +if (new_pages ==0 ) { + memnode->ignore =1 ;/* trim the whole range */ + memnode = memnode->next; + } +else { +/* Allocate a new node that will hold split range */ +if ((newnode =memdesc_alloc ()) ==-1UL ) { +/* +* Print an error message and throw away the split +* range. Not the best option, but we do not have +* many choices here. +*/ +printk (KERN_ERR "ERROR: Failed to allocate space to split memory map for max_addr/mem\n" ); + memnode->num_pages = new_pages; + memnode = memnode->next; + } +else { + newnode->start = memnode->start + (new_pages <<EFI_PAGE_SHIFT ); + newnode->num_pages = memnode->num_pages - new_pages; + newnode->type = memnode->type; + newnode->ignore =1 ; + memnode->num_pages = new_pages; + newnode->prev = memnode; + newnode->next = memnode->next; + memnode->next = newnode; + newnode->next->prev = newnode; + memnode = newnode->next; + } + } + +/* Now walk the rest of the ranges and mark them ignored */ +while (memnode !=NULL ) { + memnode->ignore =1 ; + memnode = memnode->next; + } +} + +/* +* Walk the EFI memory map and gather all memory available for kernel +* to use. +*/ +void +efi_gather_memory (void ) +{void *efi_map_start, *efi_map_end, *p, *q;efi_memory_desc_t *md, *check_md; -u64 efi_desc_size, start, end, granule_addr, last_granule_addr, first_non_wb_addr =0 ; -unsigned long total_mem =0 ; +u64 efi_desc_size, granule_addr, last_granule_addr, prev_end; +u64 first_non_wb_addr =0 ; +int no_allocate =0 ; +kern_memdesc_t *newnode, *prevnode; efi_map_start =__va (ia64_boot_param->efi_memmap); efi_map_end = efi_map_start + ia64_boot_param->efi_memmap_size; efi_desc_size = ia64_boot_param->efi_memdesc_size; + prevnode = newnode =NULL ;for (p = efi_map_start; p < efi_map_end; p += efi_desc_size) { md = p; -/* skip over non-WB memory descriptors; that's all we're interested in... */ if (!(md->attribute &EFI_MEMORY_WB ))continue ; +if (!no_allocate && (newnode =memdesc_alloc ()) ==NULL ) { +printk (KERN_ERR "ERROR: Failed to allocate node for kernel memory descriptor\n" ); +printk (KERN_ERR " Continuing with limited memory\n" ); +break ; + } + newnode->type = md->type; + newnode->ignore =0 ; + newnode->start = md->phys_addr; + newnode->num_pages = md->num_pages; + newnode->next = newnode->prev =NULL ; +if (kern_memmap ==NULL ) + kern_memmap = newnode; +/* +* Do we need to granule align? * granule_addr is the base of md's first granule. * [granule_addr - first_non_wb_addr) is guaranteed to * be contiguous WB memory. */ - granule_addr =GRANULEROUNDDOWN (md->phys_addr); + granule_addr =GRANULEROUNDDOWN (newnode->start); first_non_wb_addr =max (first_non_wb_addr, granule_addr); -if (first_non_wb_addr < md->phys_addr) { -trim_bottom (md, granule_addr +IA64_GRANULE_SIZE ); - granule_addr =GRANULEROUNDDOWN (md->phys_addr); +if (first_non_wb_addr < newnode->start) { +granule_align_bottom (newnode, granule_addr +IA64_GRANULE_SIZE ); + granule_addr =GRANULEROUNDDOWN (newnode->start); first_non_wb_addr =max (first_non_wb_addr, granule_addr); } -for (q = p; q < efi_map_end; q += efi_desc_size) { +if (newnode->start == first_non_wb_addr) + first_non_wb_addr +=efi_md_size (newnode); + +for (q = p+efi_desc_size; q < efi_map_end; q += efi_desc_size) { check_md = q;if ((check_md->attribute &EFI_MEMORY_WB ) && - (check_md->phys_addr == first_non_wb_addr)) - first_non_wb_addr += check_md->num_pages <<EFI_PAGE_SHIFT ; + (check_md->phys_addr == first_non_wb_addr)) { + first_non_wb_addr +=efi_md_size (check_md); + }else -break ;/* non-WB or hole */ +break ;/* non-WB or hole */ } last_granule_addr =GRANULEROUNDDOWN (first_non_wb_addr); -if (last_granule_addr < md->phys_addr + (md->num_pages <<EFI_PAGE_SHIFT )) -trim_top (md, last_granule_addr); +if (last_granule_addr < newnode->start +efi_md_size (newnode)) +granule_align_top (newnode, last_granule_addr); -if (is_available_memory (md)) { -if (md->phys_addr + (md->num_pages <<EFI_PAGE_SHIFT ) >= max_addr) { -if (md->phys_addr >= max_addr) -continue ; - md->num_pages = (max_addr - md->phys_addr) >>EFI_PAGE_SHIFT ; - first_non_wb_addr = max_addr; - } - -if (total_mem >= mem_limit) -continue ; - -if (total_mem + (md->num_pages <<EFI_PAGE_SHIFT ) > mem_limit) { -unsigned long limit_addr = md->phys_addr; - limit_addr += mem_limit - total_mem; - limit_addr =GRANULEROUNDDOWN (limit_addr); - -if (md->phys_addr > limit_addr) -continue ; - - md->num_pages = (limit_addr - md->phys_addr) >> -EFI_PAGE_SHIFT ; - first_non_wb_addr = max_addr = md->phys_addr + - (md->num_pages <<EFI_PAGE_SHIFT ); - } - total_mem += (md->num_pages <<EFI_PAGE_SHIFT ); +/* +* Are we left with any pages after all the alignment? +* If not, we will simply reuse the node we just allocated +* and not allocate a new one. +*/ +if (!newnode->num_pages) { + no_allocate =1 ; +continue ; + }else + no_allocate =0 ; -if (md->num_pages ==0 ) +/* +* Can we coalesce this memory range with previous one +*/ +if (prevnode) { + prev_end = prevnode->start +efi_md_size (prevnode); +if ((prev_end == newnode->start) && + (is_available_memory (newnode)) && + (is_available_memory (prevnode))) { + prevnode->num_pages += newnode->num_pages; + no_allocate =1 ; + efi_total_mem +=efi_md_size (newnode);continue ; - - curr.start =PAGE_OFFSET + md->phys_addr; - curr.end = curr.start + (md->num_pages <<EFI_PAGE_SHIFT ); - -if (!prev_valid) { - prev = curr; - prev_valid =1 ; - }else { -if (curr.start < prev.start) -printk (KERN_ERR "Oops: EFI memory table not ordered!\n" ); - -if (prev.end == curr.start) { -/* merge two consecutive memory ranges */ - prev.end = curr.end; - }else { - start =PAGE_ALIGN (prev.start); - end = prev.end &PAGE_MASK ; -if ((end > start) && (*callback)(start, end, arg) <0 ) -return ; - prev = curr; - } } } - } -if (prev_valid) { - start =PAGE_ALIGN (prev.start); - end = prev.end &PAGE_MASK ; -if (end > start) - (*callback)(start, end, arg); +if (is_available_memory (newnode)) + efi_total_mem +=efi_md_size (newnode); + +/* Link this node in the list */ +if (prevnode !=NULL ) { + newnode->prev = prevnode; + prevnode->next = newnode; + } + prevnode = newnode; } }
@@ -464,14 +560,14 @@ efi_map_pal_code (void)continue ; } -if (md->num_pages <<EFI_PAGE_SHIFT >IA64_GRANULE_SIZE ) +if (efi_md_size (md) >IA64_GRANULE_SIZE )panic ("Woah! PAL code size bigger than a granule!" ); mask = ~((1 <<IA64_GRANULE_SHIFT ) -1 );#if EFI_DEBUG printk (KERN_INFO "CPU %d: mapping PAL code [0x%lx-0x%lx) into [0x%lx-0x%lx)\n" ,smp_processor_id (), md->phys_addr, - md->phys_addr + (md->num_pages <<EFI_PAGE_SHIFT ), + md->phys_addr +efi_md_size (md), vaddr & mask, (vaddr & mask) +IA64_GRANULE_SIZE );#endif
@@ -604,7 +700,7 @@ efi_init (void) md = p;printk ("mem%02u: type=%u, attr=0x%lx, range=[0x%016lx-0x%016lx) (%luMB)\n" , i, md->type, md->attribute, md->phys_addr, - md->phys_addr + (md->num_pages <<EFI_PAGE_SHIFT ), + md->phys_addr +efi_md_size (md), md->num_pages >> (20 -EFI_PAGE_SHIFT )); } }
@@ -725,7 +821,7 @@ efi_mem_type (unsigned long phys_addr)for (p = efi_map_start; p < efi_map_end; p += efi_desc_size) { md = p; -if (phys_addr - md->phys_addr < (md->num_pages <<EFI_PAGE_SHIFT )) +if (phys_addr - md->phys_addr <efi_md_size (md))return md->type; }return 0 ;
@@ -745,7 +841,7 @@ efi_mem_attributes (unsigned long phys_afor (p = efi_map_start; p < efi_map_end; p += efi_desc_size) { md = p; -if (phys_addr - md->phys_addr < (md->num_pages <<EFI_PAGE_SHIFT )) +if (phys_addr - md->phys_addr <efi_md_size (md))return md->attribute; }return 0 ;
@@ -765,12 +861,12 @@ valid_phys_addr_range (unsigned long phyfor (p = efi_map_start; p < efi_map_end; p += efi_desc_size) { md = p; -if (phys_addr - md->phys_addr < (md->num_pages <<EFI_PAGE_SHIFT )) { +if (phys_addr - md->phys_addr <efi_md_size (md)) {if (!(md->attribute &EFI_MEMORY_WB ))return 0 ; -if (*size > md->phys_addr + (md->num_pages <<EFI_PAGE_SHIFT ) - phys_addr) - *size = md->phys_addr + (md->num_pages <<EFI_PAGE_SHIFT ) - phys_addr; +if (*size > md->phys_addr +efi_md_size (md) - phys_addr) + *size = md->phys_addr +efi_md_size (md) - phys_addr;return 1 ; } }
arch/ia64/kernel/setup.c +81 -0
99 lines not on this page · raw message
arch/ia64/mm/contig.c +3 -0
@@ -146,6 +146,9 @@ find_memory (void)reserve_memory (); +efi_gather_memory (); +efi_trim_memory (); +/* first find highest page frame number */ max_pfn =0 ;efi_memmap_walk (find_max_pfn, &max_pfn);
arch/ia64/mm/discontig.c +3 -0
@@ -429,6 +429,9 @@ void __init find_memory(void)reserve_memory (); +efi_gather_memory (); +efi_trim_memory (); +if (numnodes ==0 ) {printk (KERN_ERR "node info missing!\n" ); numnodes =1 ;
include/asm-ia64/meminit.h +16 -1
@@ -16,10 +16,11 @@* - initrd (optional) * - command line string * - kernel code & data +* - Kernel memory map built from EFI memory map * * More could be added if necessary */ -#define IA64_MAX_RSVD_REGIONS 5 +#define IA64_MAX_RSVD_REGIONS 6struct rsvd_region {unsigned long start;/* virtual address of beginning of element */
@@ -29,6 +30,14 @@ struct rsvd_region {extern struct rsvd_region rsvd_region[IA64_MAX_RSVD_REGIONS +1 ];extern int num_rsvd_regions; +typedef struct kern_memdesc { +u32 ignore; +u32 type; +u64 start; +u64 num_pages; +struct kern_memdesc *next, *prev; +}kern_memdesc_t ; +extern void find_memory (void );extern void reserve_memory (void );extern void find_initrd (void );
@@ -57,4 +66,10 @@ extern int filter_rsvd_memory (unsignedextern int create_mem_map_page_table (u64 start,u64 end,void *arg);#endif +#define is_available_memory (md) ((md->type == EFI_LOADER_CODE)? 1: \ + ((md->type == EFI_LOADER_DATA)? 1: \ + ((md->type == EFI_BOOT_SERVICES_CODE)? 1: \ + ((md->type == EFI_BOOT_SERVICES_DATA)? 1: \ + ((md->type == EFI_CONVENTIONAL_MEMORY)? 1:0))))) +#endif /* meminit_h */