backported fixes from 256boss
[bootcensus] / src / mem.c
1 /*
2 pcboot - bootable PC demo/game kernel
3 Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY, without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 #include <stdio.h>
19 #include <string.h>
20 #include "config.h"
21 #include "panic.h"
22 #include "mem.h"
23 #include "intr.h"
24
25 #define FREE            0
26 #define USED            1
27
28 #define BM_IDX(pg)      ((pg) / 32)
29 #define BM_BIT(pg)      ((pg) & 0x1f)
30
31 #define IS_FREE(pg) ((bitmap[BM_IDX(pg)] & (1 << BM_BIT(pg))) == 0)
32
33 #define MEM_START       ((uint32_t)&_mem_start)
34
35
36 struct mem_range {
37         uint32_t start;
38         uint32_t size;
39 };
40
41 void move_stack(uint32_t newaddr);      /* defined in startup.s */
42
43 static void mark_page(int pg, int used);
44 static void add_memory(uint32_t start, size_t size);
45
46 #define MAX_MAP_SIZE    16
47 extern struct mem_range boot_mem_map[MAX_MAP_SIZE];
48 extern int boot_mem_map_size;
49
50 /* linker supplied symbol, points to the end of the kernel image */
51 extern uint32_t _mem_start;
52
53
54 /* A bitmap is used to track which physical memory pages are used, and which
55  * are available for allocation by alloc_ppage.
56  *
57  * last_alloc_idx keeps track of the last 32bit element in the bitmap array
58  * where a free page was found. It's guaranteed that all the elements before
59  * this have no free pages, but it doesn't imply that there will be another
60  * free page there. So it's used as a starting point for the search.
61  */
62 static uint32_t *bitmap;
63 static int bmsize, last_alloc_idx;
64
65
66
67 void init_mem(void)
68 {
69         int i, pg, max_used_pg, end_pg = 0;
70         uint32_t used_end, start, end, sz, total = 0, rem;
71         const char *suffix[] = {"bytes", "KB", "MB", "GB"};
72
73         last_alloc_idx = 0;
74
75         /* the allocation bitmap starts right at the end of the kernel image */
76         bitmap = (uint32_t*)&_mem_start;
77
78         /* start by marking all posible pages (2**20) as used. We do not "reserve"
79          * all this space. Pages beyond the end of the useful bitmap area
80          * ((char*)bitmap + bmsize), which will be determined after we traverse the
81          * memory map, are going to be marked as available for allocation.
82          */
83         memset(bitmap, 0xff, 1024 * 1024 / 8);
84
85
86         if(boot_mem_map_size <= 0 || boot_mem_map_size > MAX_MAP_SIZE) {
87                 panic("invalid memory map size reported by the boot loader: %d\n", boot_mem_map_size);
88         }
89
90         printf("Memory map:\n");
91         for(i=0; i<boot_mem_map_size; i++) {
92                 printf(" start: %08x - size: %08x\n", (unsigned int)boot_mem_map[i].start,
93                                 (unsigned int)boot_mem_map[i].size);
94
95                 start = boot_mem_map[i].start;
96                 sz = boot_mem_map[i].size & 0xfffffffc;
97                 end = start + sz;
98                 if(end < sz) {
99                         end = 0xfffffffc;
100                         sz = end - start;
101                 }
102
103                 /* ignore any memory ranges which end before the end of the kernel image */
104                 if(end < MEM_START) continue;
105                 if(start < MEM_START) {
106                         start = MEM_START;
107                 }
108
109                 add_memory(start, sz);
110                 total += sz;
111
112                 pg = ADDR_TO_PAGE(end);
113                 if(end_pg < pg) {
114                         end_pg = pg;
115                 }
116         }
117
118         i = 0;
119         while(total > 1024) {
120                 rem = total & 0x3ff;
121                 total >>= 10;
122                 ++i;
123         }
124         printf("Total usable RAM: %u.%u %s\n", total, 100 * rem / 1024, suffix[i]);
125
126         /* size of the useful part of the bitmap in bytes padded to 4-byte
127          * boundaries to allow 32bit at a time operations.
128          */
129         bmsize = (end_pg / 32) * 4;
130
131         /* mark all pages occupied by the bitmap as used */
132         used_end = (uint32_t)bitmap + bmsize - 1;
133
134         max_used_pg = ADDR_TO_PAGE(used_end);
135         printf("marking pages up to %x (page: %d) as used\n", used_end, max_used_pg);
136         for(i=0; i<=max_used_pg; i++) {
137                 mark_page(i, USED);
138         }
139
140 #ifdef MOVE_STACK_RAMTOP
141         /* allocate space for the stack at the top of RAM and move it there */
142         if((pg = alloc_ppages(STACK_PAGES, MEM_STACK)) != -1) {
143                 printf("moving stack-top to: %x (%d pages)\n", PAGE_TO_ADDR(end_pg) - 4, STACK_PAGES);
144                 move_stack(PAGE_TO_ADDR(pg + STACK_PAGES) - 4);
145         }
146 #endif
147 }
148
149 int alloc_ppage(int area)
150 {
151         return alloc_ppages(1, area);
152 }
153
154 /* free_ppage marks the physical page, free in the allocation bitmap.
155  *
156  * CAUTION: no checks are done that this page should actually be freed or not.
157  * If you call free_ppage with the address of some part of memory that was
158  * originally reserved due to it being in a memory hole or part of the kernel
159  * image or whatever, it will be subsequently allocatable by alloc_ppage.
160  */
161 void free_ppage(int pg)
162 {
163         int bmidx = BM_IDX(pg);
164
165         int intr_state = get_intr_flag();
166         disable_intr();
167
168         if(IS_FREE(pg)) {
169                 panic("free_ppage(%d): I thought that was already free!\n", pg);
170         }
171
172         mark_page(pg, FREE);
173         if(bmidx < last_alloc_idx) {
174                 last_alloc_idx = bmidx;
175         }
176
177         set_intr_flag(intr_state);
178 }
179
180
181 int alloc_ppages(int count, int area)
182 {
183         int i, dir, pg, idx, max, intr_state, found_free = 0;
184
185         intr_state = get_intr_flag();
186         disable_intr();
187
188         if(area == MEM_STACK) {
189                 idx = (bmsize - 1) / 4;
190                 max = -1;
191                 dir = -1;
192         } else {
193                 idx = last_alloc_idx;
194                 max = bmsize / 4;
195                 dir = 1;
196         }
197
198         while(idx != max) {
199                 /* if at least one bit is 0 then we have at least
200                  * one free page. find it and try to allocate a range starting from there
201                  */
202                 if(bitmap[idx] != 0xffffffff) {
203                         pg = idx * 32;
204                         if(dir < 0) pg += 31;
205
206                         for(i=0; i<32; i++) {
207                                 if(IS_FREE(pg)) {
208                                         if(!found_free && dir > 0) {
209                                                 last_alloc_idx = idx;
210                                                 found_free = 1;
211                                         }
212
213                                         if(alloc_ppage_range(pg, count) != -1) {
214                                                 set_intr_flag(intr_state);
215                                                 return pg;
216                                         }
217                                 }
218                                 pg += dir;
219                         }
220                 }
221                 idx += dir;
222         }
223
224         set_intr_flag(intr_state);
225         return -1;
226 }
227
228 void free_ppages(int pg0, int count)
229 {
230         int i;
231
232         for(i=0; i<count; i++) {
233                 free_ppage(pg0++);
234         }
235 }
236
237 int alloc_ppage_range(int start, int size)
238 {
239         int i, pg = start;
240         int intr_state;
241
242         intr_state = get_intr_flag();
243         disable_intr();
244
245         /* first validate that no page in the requested range is allocated */
246         for(i=0; i<size; i++) {
247                 if(!IS_FREE(pg)) {
248                         return -1;
249                 }
250                 ++pg;
251         }
252
253         /* all is well, mark them as used */
254         pg = start;
255         for(i=0; i<size; i++) {
256                 mark_page(pg++, USED);
257         }
258
259         set_intr_flag(intr_state);
260         return 0;
261 }
262
263 int free_ppage_range(int start, int size)
264 {
265         int i, pg = start;
266
267         for(i=0; i<size; i++) {
268                 free_ppage(pg++);
269         }
270         return 0;
271 }
272
273 /* adds a range of physical memory to the available pool. used during init_mem
274  * when traversing the memory map.
275  */
276 static void add_memory(uint32_t start, size_t sz)
277 {
278         int i, szpg, pg;
279
280         szpg = ADDR_TO_PAGE(sz + 4095);
281         pg = ADDR_TO_PAGE(start);
282
283         for(i=0; i<szpg; i++) {
284                 mark_page(pg++, FREE);
285         }
286 }
287
288 /* maps a page as used or free in the allocation bitmap */
289 static void mark_page(int pg, int used)
290 {
291         int idx = BM_IDX(pg);
292         int bit = BM_BIT(pg);
293
294         if(used) {
295                 bitmap[idx] |= 1 << bit;
296         } else {
297                 bitmap[idx] &= ~(1 << bit);
298         }
299 }
300
301 void print_page_bitmap(void)
302 {
303         int i;
304
305         for(i=0; i<bmsize/4; i++) {
306                 if((i & 3) == 0) {
307                         uint32_t pg = i * 32;
308                         uint32_t addr = PAGE_TO_ADDR(pg);
309                         printf("\n%5d [%08x]:", (int)pg, (unsigned long)addr);
310                 }
311                 printf(" %08x", bitmap[i]);
312         }
313         printf("\n");
314 }