x86: fixes initialization errors in page_alloc
[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 whether 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         /* Most entries are page aligned, though on some machines below EXTPHYSMEM
68          * we may have some that aren't.  If two regions collide on the same page
69          * (one of them starts unaligned), we need to only handle the page once, and
70          * err on the side of being busy.
71          *
72          * Since these regions happen below EXTPHYSMEM, they are all marked busy (or
73          * else we'll panic).  I'll probably rewrite this for jumbos before I find a
74          * machine with unaligned mboot entries in higher memory. */
75         if (PGOFF(entry->addr))
76                 assert(entry->addr < EXTPHYSMEM);
77         for (uint64_t i = ROUNDDOWN(entry->addr, PGSIZE);
78              i < entry->addr + entry->len;
79              i += PGSIZE) {
80                 /* Skip pages we'll never map (above KERNBASE).  Once we hit one of
81                  * them, we know the rest are too (for this entry). */
82                 if (i >= max_paddr)
83                         return;
84                 /* Mark low mem as busy (multiboot stuff is there, usually, too).  Since
85                  * that memory may be freed later (like the smp_boot page), we'll treat
86                  * it like it is busy/allocated. */
87                 if (i < EXTPHYSMEM)
88                         goto page_busy;
89                 /* Mark as busy pages already allocated in boot_alloc() */
90                 if (in_bootzone && (i < boot_freemem_paddr))
91                         goto page_busy;
92                 /* Need to double check for the kernel, in case it wasn't in the
93                  * bootzone.  If it was in the bootzone, we already skipped it. */
94                 if (pa64_is_in_kernel(i))
95                         goto page_busy;
96                 track_free_page(pa64_to_page(i));
97                 continue;
98 page_busy:
99                 page_setref(pa64_to_page(i), 1);
100         }
101 }
102
103 static void check_range(uint64_t start, uint64_t end, int expect)
104 {
105         int ref;
106         if (PGOFF(start))
107                 printk("Warning: check_range given an unaligned addr %p\n", start);
108         for (uint64_t i = start; i < end; i += PGSIZE)  {
109                 ref = kref_refcnt(&pa64_to_page(i)->pg_kref);
110                 if (ref != expect) {
111                         printk("Error: physaddr %p refcnt was %d, expected %d\n", i, ref,
112                                expect);
113                         panic("");
114                 }
115         }
116 }
117
118 static void check_mboot_region(struct multiboot_mmap_entry *entry, void *data)
119 {
120         extern char end[];
121         physaddr_t boot_freemem_paddr = (physaddr_t)data;
122         bool in_bootzone = (entry->addr <= boot_freemem_paddr) &&
123                            (boot_freemem_paddr < entry->addr + entry->len);
124         /* Need to deal with 32b wrap-around */
125         uint64_t zone_end = MIN(entry->addr + entry->len, (uint64_t)max_paddr);
126
127         if (entry->type != MULTIBOOT_MEMORY_AVAILABLE)
128                 return;
129         if (zone_end <= EXTPHYSMEM) {
130                 check_range(entry->addr, zone_end, 1);
131                 return;
132         }
133         /* this may include the kernel */
134         if (in_bootzone) {
135                 /* boot_freemem might not be page aligned.  If it's part-way through a
136                  * page, that page should be busy */
137                 check_range(entry->addr, ROUNDUP(PADDR(boot_freemem), PGSIZE), 1);
138                 check_range(ROUNDUP(PADDR(boot_freemem), PGSIZE), zone_end, 0);
139                 assert(zone_end == PADDR(boot_freelimit));
140                 return;
141         }
142         /* kernel's range (hardcoded in the linker script).  If we're checking now,
143          * it means the kernel is not in the same entry as the bootzone. */
144         if (entry->addr == EXTPHYSMEM) {
145                 check_range(EXTPHYSMEM, PADDR(end), 1);
146                 check_range(ROUNDUP(PADDR(end), PGSIZE), zone_end, 0);
147                 return;
148         }
149 }
150
151 /* Initialize the memory free lists.  After this, do not use boot_alloc. */
152 void page_alloc_init(struct multiboot_info *mbi)
153 {
154         page_alloc_bootstrap();
155         /* To init the free list(s), each page that is already allocated/busy will
156          * get increfed.  All other pages that were reported as 'free' will be added
157          * to a free list.  Their refcnts are all 0 (when pages was memset).
158          *
159          * To avoid a variety of headaches, any memory below 1MB is considered busy.
160          * Likewise, everything in the kernel, up to _end is also busy.  And
161          * everything we've already boot_alloc'd is busy.
162          *
163          * We'll also abort the mapping for any addresses over max_paddr, since
164          * we'll never use them.  'pages' does not track them either.
165          *
166          * One special note: we actually use the memory at 0x1000 for smp_boot.
167          * It'll get set to 'used' like the others; just FYI.
168          *
169          * Finally, if we want to use actual jumbo page allocation (not just
170          * mapping), we need to round up _end, and make sure all of multiboot's
171          * sections are jumbo-aligned. */
172         physaddr_t boot_freemem_paddr = PADDR(PTRROUNDUP(boot_freemem, PGSIZE));
173
174         mboot_foreach_mmap(mbi, parse_mboot_region, (void*)boot_freemem_paddr);
175         printk("Number of free pages: %lu\n", nr_free_pages);
176         /* Test the page alloc - if this gets slow, we can CONFIG it */
177         mboot_foreach_mmap(mbi, check_mboot_region, (void*)boot_freemem_paddr);
178         printk("Page alloc init successful\n");
179 }