Physical memory init uses multiboot info
[akaros.git] / kern / arch / x86 / page_alloc.c
1 /* Copyright (c) 2009 The Regents of the University  of California. 
2  * See the COPYRIGHT files at the top of this source tree for full 
3  * license information.
4  * 
5  * Barret Rhoden <brho@cs.berkeley.edu>
6  * Kevin Klues <klueska@cs.berkeley.edu> */
7
8 #ifdef __SHARC__
9 #pragma nosharc
10 #define SINIT(x) x
11 #endif
12
13 #include <sys/queue.h>
14 #include <page_alloc.h>
15 #include <pmap.h>
16 #include <kmalloc.h>
17 #include <multiboot.h>
18
19 spinlock_t colored_page_free_list_lock = SPINLOCK_INITIALIZER_IRQSAVE;
20
21 page_list_t LCKD(&colored_page_free_list_lock) * CT(llc_cache->num_colors) RO
22   colored_page_free_list = NULL;
23
24 static void page_alloc_bootstrap() {
25         // Allocate space for the array required to manage the free lists
26         size_t list_size = llc_cache->num_colors*sizeof(page_list_t);
27         page_list_t LCKD(&colored_page_free_list_lock)*tmp =
28             (page_list_t*)boot_alloc(list_size,PGSIZE);
29         colored_page_free_list = SINIT(tmp);
30         for (int i = 0; i < llc_cache->num_colors; i++)
31                 LIST_INIT(&colored_page_free_list[i]);
32 }
33
34 /* Can do whatever here.  For now, our page allocator just works with colors,
35  * not NUMA zones or anything. */
36 static void track_free_page(struct page *page)
37 {
38         LIST_INSERT_HEAD(&colored_page_free_list[get_page_color(page2ppn(page),
39                                                                 llc_cache)],
40                          page, pg_link);
41         nr_free_pages++;
42 }
43
44 static struct page *pa64_to_page(uint64_t paddr)
45 {
46         return &pages[paddr >> PGSHIFT];
47 }
48
49 static bool pa64_is_in_kernel(uint64_t paddr)
50 {
51         extern char end[];
52         /* kernel is linked and loaded here (in kernel{32,64}.ld */
53         return (EXTPHYSMEM <= paddr) && (paddr <= PADDR(end));
54 }
55
56 /* Helper.  For every page in the entry, this will determine whther or not the
57  * page is free, and handle accordingly. */
58 static void parse_mboot_region(struct multiboot_mmap_entry *entry, void *data)
59 {
60         physaddr_t boot_freemem_paddr = (physaddr_t)data;
61         bool in_bootzone = (entry->addr <= boot_freemem_paddr) &&
62                            (boot_freemem_paddr < entry->addr + entry->len);
63
64         if (entry->type != MULTIBOOT_MEMORY_AVAILABLE)
65                 return;
66         /* TODO: we'll have some issues with jumbo allocation */
67         for (uint64_t i = ROUNDUP(entry->addr, PGSIZE);
68              i < entry->addr + entry->len;
69              i += PGSIZE) {
70                 /* Skip pages we'll never map (above KERNBASE).  Once we hit one of
71                  * them, we know the rest are too (for this entry). */
72                 if (i >= max_paddr)
73                         return;
74                 /* Mark low mem as busy (multiboot stuff is there, usually, too).  Since
75                  * that memory may be freed later (like the smp_boot page), we'll treat
76                  * it like it is busy/allocated. */
77                 if (i < EXTPHYSMEM)
78                         goto page_busy;
79                 /* Mark as busy pages already allocated in boot_alloc() */
80                 if (in_bootzone && (i < boot_freemem_paddr))
81                         goto page_busy;
82                 /* Need to double check for the kernel, in case it wasn't in the
83                  * bootzone.  If it was in the bootzone, we already skipped it. */
84                 if (pa64_is_in_kernel(i))
85                         goto page_busy;
86                 track_free_page(pa64_to_page(i));
87                 continue;
88 page_busy:
89                 page_setref(pa64_to_page(i), 1);
90         }
91 }
92
93 static void check_range(uint64_t start, uint64_t end, int expect)
94 {
95         int ref;
96         if (PGOFF(start))
97                 printk("Warning: check_range given an unaligned addr %p\n", start);
98         for (uint64_t i = start; i < end; i += PGSIZE)  {
99                 ref = kref_refcnt(&pa64_to_page(i)->pg_kref);
100                 if (ref != expect) {
101                         printk("Error: physaddr %p refcnt was %d, expected %d\n", i, ref,
102                                expect);
103                         panic("");
104                 }
105         }
106 }
107
108 static void check_mboot_region(struct multiboot_mmap_entry *entry, void *data)
109 {
110         extern char end[];
111         physaddr_t boot_freemem_paddr = (physaddr_t)data;
112         bool in_bootzone = (entry->addr <= boot_freemem_paddr) &&
113                            (boot_freemem_paddr < entry->addr + entry->len);
114         /* Need to deal with 32b wrap-around */
115         uint64_t zone_end = MIN(entry->addr + entry->len, (uint64_t)max_paddr);
116
117         if (entry->type != MULTIBOOT_MEMORY_AVAILABLE)
118                 return;
119         if (zone_end <= EXTPHYSMEM) {
120                 check_range(entry->addr, zone_end, 1);
121                 return;
122         }
123         /* this may include the kernel */
124         if (in_bootzone) {
125                 /* boot_freemem might not be page aligned.  If it's part-way through a
126                  * page, that page should be busy */
127                 check_range(entry->addr, ROUNDUP(PADDR(boot_freemem), PGSIZE), 1);
128                 check_range(ROUNDUP(PADDR(boot_freemem), PGSIZE), zone_end, 0);
129                 assert(zone_end == PADDR(boot_freelimit));
130                 return;
131         }
132         /* kernel's range (hardcoded in the linker script).  If we're checking now,
133          * it means the kernel is not in the same entry as the bootzone. */
134         if (entry->addr == EXTPHYSMEM) {
135                 check_range(EXTPHYSMEM, PADDR(end), 1);
136                 check_range(ROUNDUP(PADDR(end), PGSIZE), zone_end, 0);
137                 return;
138         }
139 }
140
141 /* Initialize the memory free lists.  After this, do not use boot_alloc. */
142 void page_alloc_init(struct multiboot_info *mbi)
143 {
144         page_alloc_bootstrap();
145         /* To init the free list(s), each page that is already allocated/busy will
146          * get increfed.  All other pages that were reported as 'free' will be added
147          * to a free list.  Their refcnts are all 0 (when pages was memset).
148          *
149          * To avoid a variety of headaches, any memory below 1MB is considered busy.
150          * Likewise, everything in the kernel, up to _end is also busy.  And
151          * everything we've already boot_alloc'd is busy.
152          *
153          * We'll also abort the mapping for any addresses over max_paddr, since
154          * we'll never use them.  'pages' does not track them either.
155          *
156          * One special note: we actually use the memory at 0x1000 for smp_boot.
157          * It'll get set to 'used' like the others; just FYI.
158          *
159          * Finally, if we want to use actual jumbo page allocation (not just
160          * mapping), we need to round up _end, and make sure all of multiboot's
161          * sections are jumbo-aligned. */
162         physaddr_t boot_freemem_paddr = PADDR(PTRROUNDUP(boot_freemem, PGSIZE));
163
164         mboot_foreach_mmap(mbi, parse_mboot_region, (void*)boot_freemem_paddr);
165         printk("Number of free pages: %lu\n", nr_free_pages);
166         /* Test the page alloc - if this gets slow, we can CONFIG it */
167         mboot_foreach_mmap(mbi, check_mboot_region, (void*)boot_freemem_paddr);
168         printk("Page alloc init successful\n");
169 }