fixed bug in physical page allocator init: would mark way much more
[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 "panic.h"
21 #include "mem.h"
22 #include "intr.h"
23
24 #define FREE            0
25 #define USED            1
26
27 #define BM_IDX(pg)      ((pg) / 32)
28 #define BM_BIT(pg)      ((pg) & 0x1f)
29
30 #define IS_FREE(pg) ((bitmap[BM_IDX(pg)] & (1 << BM_BIT(pg))) == 0)
31
32 #define MEM_START       ((uint32_t)&_mem_start)
33
34
35 struct mem_range {
36         uint32_t start;
37         uint32_t size;
38 };
39
40 static void mark_page(int pg, int used);
41 static void add_memory(uint32_t start, size_t size);
42
43 #define MAX_MAP_SIZE    16
44 extern struct mem_range boot_mem_map[MAX_MAP_SIZE];
45 extern int boot_mem_map_size;
46
47 /* linker supplied symbol, points to the end of the kernel image */
48 extern uint32_t _mem_start;
49
50
51 /* A bitmap is used to track which physical memory pages are used, and which
52  * are available for allocation by alloc_ppage.
53  *
54  * last_alloc_idx keeps track of the last 32bit element in the bitmap array
55  * where a free page was found. It's guaranteed that all the elements before
56  * this have no free pages, but it doesn't imply that there will be another
57  * free page there. So it's used as a starting point for the search.
58  */
59 static uint32_t *bitmap;
60 static int bmsize, last_alloc_idx;
61
62
63
64 void init_mem(void)
65 {
66         int i, pg, max_pg = 0;
67         uint32_t used_end, start, end, sz, total = 0, rem;
68         const char *suffix[] = {"bytes", "KB", "MB", "GB"};
69
70         last_alloc_idx = 0;
71
72         /* the allocation bitmap starts right at the end of the kernel image */
73         bitmap = (uint32_t*)&_mem_start;
74
75         /* start by marking all posible pages (2**20) as used. We do not "reserve"
76          * all this space. Pages beyond the end of the useful bitmap area
77          * ((char*)bitmap + bmsize), which will be determined after we traverse the
78          * memory map, are going to be marked as available for allocation.
79          */
80         memset(bitmap, 0xff, 1024 * 1024 / 8);
81
82
83         if(boot_mem_map_size <= 0 || boot_mem_map_size > MAX_MAP_SIZE) {
84                 panic("invalid memory map size reported by the boot loader: %d\n", boot_mem_map_size);
85         }
86
87         printf("Memory map:\n");
88         for(i=0; i<boot_mem_map_size; i++) {
89                 printf(" start: %08x - size: %08x\n", (unsigned int)boot_mem_map[i].start,
90                                 (unsigned int)boot_mem_map[i].size);
91
92                 start = boot_mem_map[i].start;
93                 sz = boot_mem_map[i].size & 0xfffffffc;
94                 end = start + sz;
95                 if(end < sz) {
96                         end = 0xfffffffc;
97                         sz = end - start;
98                 }
99
100                 /* ignore any memory ranges which end before the end of the kernel image */
101                 if(end < MEM_START) continue;
102                 if(start < MEM_START) {
103                         start = MEM_START;
104                 }
105
106                 add_memory(start, sz);
107                 total += sz;
108
109                 pg = ADDR_TO_PAGE(end);
110                 if(max_pg < pg) {
111                         max_pg = pg;
112                 }
113         }
114
115         i = 0;
116         while(total > 1024) {
117                 rem = total & 0x3ff;
118                 total >>= 10;
119                 ++i;
120         }
121         printf("Total usable RAM: %u.%u %s\n", total, 100 * rem / 1024, suffix[i]);
122
123         /* size of the useful part of the bitmap in bytes padded to 4-byte
124          * boundaries to allow 32bit at a time operations.
125          */
126         bmsize = (max_pg / 32 + 1) * 4;
127
128         /* mark all pages occupied by the bitmap as used */
129         used_end = (uint32_t)bitmap + bmsize - 1;
130
131         max_pg = ADDR_TO_PAGE(used_end);
132         printf("marking pages up to %x (page: %d) as used\n", used_end, max_pg);
133         for(i=0; i<=max_pg; i++) {
134                 mark_page(i, USED);
135         }
136 }
137
138 int alloc_ppage(void)
139 {
140         return alloc_ppages(1);
141 }
142
143 /* free_ppage marks the physical page, free in the allocation bitmap.
144  *
145  * CAUTION: no checks are done that this page should actually be freed or not.
146  * If you call free_ppage with the address of some part of memory that was
147  * originally reserved due to it being in a memory hole or part of the kernel
148  * image or whatever, it will be subsequently allocatable by alloc_ppage.
149  */
150 void free_ppage(int pg)
151 {
152         int bmidx = BM_IDX(pg);
153
154         int intr_state = get_intr_flag();
155         disable_intr();
156
157         if(IS_FREE(pg)) {
158                 panic("free_ppage(%d): I thought that was already free!\n", pg);
159         }
160
161         mark_page(pg, FREE);
162         if(bmidx < last_alloc_idx) {
163                 last_alloc_idx = bmidx;
164         }
165
166         set_intr_flag(intr_state);
167 }
168
169
170 int alloc_ppages(int count)
171 {
172         int i, pg, idx, max, intr_state, found_free = 0;
173
174         intr_state = get_intr_flag();
175         disable_intr();
176
177         idx = last_alloc_idx;
178         max = bmsize / 4;
179
180         while(idx <= max) {
181                 /* if at least one bit is 0 then we have at least
182                  * one free page. find it and try to allocate a range starting from there
183                  */
184                 if(bitmap[idx] != 0xffffffff) {
185                         for(i=0; i<32; i++) {
186                                 pg = idx * 32 + i;
187
188                                 if(IS_FREE(pg)) {
189                                         if(!found_free) {
190                                                 last_alloc_idx = idx;
191                                                 found_free = 1;
192                                         }
193
194                                         if(alloc_ppage_range(pg, count) != -1) {
195                                                 set_intr_flag(intr_state);
196                                                 return pg;
197                                         }
198                                 }
199                         }
200                 }
201                 idx++;
202         }
203
204         set_intr_flag(intr_state);
205         return -1;
206 }
207
208 void free_ppages(int pg0, int count)
209 {
210         int i;
211
212         for(i=0; i<count; i++) {
213                 free_ppage(pg0++);
214         }
215 }
216
217 int alloc_ppage_range(int start, int size)
218 {
219         int i, pg = start;
220         int intr_state;
221
222         intr_state = get_intr_flag();
223         disable_intr();
224
225         /* first validate that no page in the requested range is allocated */
226         for(i=0; i<size; i++) {
227                 if(!IS_FREE(pg)) {
228                         return -1;
229                 }
230                 ++pg;
231         }
232
233         /* all is well, mark them as used */
234         pg = start;
235         for(i=0; i<size; i++) {
236                 mark_page(pg++, USED);
237         }
238
239         set_intr_flag(intr_state);
240         return 0;
241 }
242
243 int free_ppage_range(int start, int size)
244 {
245         int i, pg = start;
246
247         for(i=0; i<size; i++) {
248                 free_ppage(pg++);
249         }
250         return 0;
251 }
252
253 /* adds a range of physical memory to the available pool. used during init_mem
254  * when traversing the memory map.
255  */
256 static void add_memory(uint32_t start, size_t sz)
257 {
258         int i, szpg, pg;
259
260         szpg = ADDR_TO_PAGE(sz + 4095);
261         pg = ADDR_TO_PAGE(start);
262
263         for(i=0; i<szpg; i++) {
264                 mark_page(pg++, FREE);
265         }
266 }
267
268 /* maps a page as used or free in the allocation bitmap */
269 static void mark_page(int pg, int used)
270 {
271         int idx = BM_IDX(pg);
272         int bit = BM_BIT(pg);
273
274         if(used) {
275                 bitmap[idx] |= 1 << bit;
276         } else {
277                 bitmap[idx] &= ~(1 << bit);
278         }
279 }
280