Don't move EBDA while an optionrom is running (CONFIG_THREAD_OPTIONROMS).
[seabios.git] / src / pmm.c
1 // Post memory manager (PMM) calls
2 //
3 // Copyright (C) 2009  Kevin O'Connor <kevin@koconnor.net>
4 //
5 // This file may be distributed under the terms of the GNU LGPLv3 license.
6
7 #include "util.h" // checksum
8 #include "config.h" // BUILD_BIOS_ADDR
9 #include "memmap.h" // find_high_area
10 #include "farptr.h" // GET_FARVAR
11 #include "biosvar.h" // GET_BDA
12
13
14 #if MODESEGMENT
15 // The 16bit pmm entry points runs in "big real" mode, and can
16 // therefore read/write to the 32bit malloc variables.
17 #define GET_PMMVAR(var) ({                      \
18             SET_SEG(ES, 0);                     \
19             __GET_VAR("addr32 ", ES, (var)); })
20 #define SET_PMMVAR(var, val) do {               \
21         SET_SEG(ES, 0);                         \
22         __SET_VAR("addr32 ", ES, (var), (val)); \
23     } while (0)
24 #else
25 #define GET_PMMVAR(var) (var)
26 #define SET_PMMVAR(var, val) do { (var) = (val); } while (0)
27 #endif
28
29 // Zone definitions
30 struct zone_s {
31     u32 top, bottom, cur;
32 };
33
34 struct zone_s ZoneLow VAR32FLATVISIBLE, ZoneHigh VAR32FLATVISIBLE;
35 struct zone_s ZoneFSeg VAR32FLATVISIBLE;
36 struct zone_s ZoneTmpLow VAR32FLATVISIBLE, ZoneTmpHigh VAR32FLATVISIBLE;
37
38 struct zone_s *Zones[] VAR32FLATVISIBLE = {
39     &ZoneTmpLow, &ZoneLow, &ZoneFSeg, &ZoneTmpHigh, &ZoneHigh
40 };
41
42
43 /****************************************************************
44  * ebda movement
45  ****************************************************************/
46
47 // Move ebda
48 static int
49 relocate_ebda(u32 newebda, u32 oldebda, u8 ebda_size)
50 {
51     u32 lowram = GET_BDA(mem_size_kb) * 1024;
52     if (oldebda != lowram)
53         // EBDA isn't at end of ram - give up.
54         return -1;
55
56     // Do copy
57     if (MODESEGMENT)
58         memcpy_far(FLATPTR_TO_SEG(newebda)
59                    , (void*)FLATPTR_TO_OFFSET(newebda)
60                    , FLATPTR_TO_SEG(oldebda)
61                    , (void*)FLATPTR_TO_OFFSET(oldebda)
62                    , ebda_size * 1024);
63     else
64         memmove((void*)newebda, (void*)oldebda, ebda_size * 1024);
65
66     // Update indexes
67     dprintf(1, "ebda moved from %x to %x\n", oldebda, newebda);
68     SET_BDA(mem_size_kb, newebda / 1024);
69     SET_BDA(ebda_seg, FLATPTR_TO_SEG(newebda));
70     return 0;
71 }
72
73 // Support expanding the ZoneLow dynamically.
74 static void
75 zonelow_expand(u32 size, u32 align)
76 {
77     u32 oldpos, newpos, bottom;
78     for (;;) {
79         oldpos = GET_PMMVAR(ZoneLow.cur);
80         newpos = ALIGN_DOWN(oldpos - size, align);
81         bottom = GET_PMMVAR(ZoneLow.bottom);
82         if (newpos >= bottom && newpos <= oldpos)
83             // Space already present.
84             return;
85         // Make sure to not move ebda while an optionrom is running.
86         if (likely(!wait_preempt()))
87             break;
88     }
89     u16 ebda_seg = get_ebda_seg();
90     u32 ebda_pos = (u32)MAKE_FLATPTR(ebda_seg, 0);
91     u8 ebda_size = GET_EBDA2(ebda_seg, size);
92     u32 ebda_end = ebda_pos + ebda_size * 1024;
93     if (ebda_end != bottom) {
94         // Something else is after ebda - can't use any existing space.
95         oldpos = ebda_end;
96         newpos = ALIGN_DOWN(oldpos - size, align);
97     }
98     u32 newbottom = ALIGN_DOWN(newpos, 1024);
99     u32 newebda = ALIGN_DOWN(newbottom - ebda_size * 1024, 1024);
100     if (newebda < BUILD_EBDA_MINIMUM)
101         // Not enough space.
102         return;
103
104     // Move ebda
105     int ret = relocate_ebda(newebda, ebda_pos, ebda_size);
106     if (ret)
107         return;
108
109     // Update zone
110     SET_PMMVAR(ZoneLow.cur, oldpos);
111     SET_PMMVAR(ZoneLow.bottom, newbottom);
112 }
113
114
115 /****************************************************************
116  * zone allocations
117  ****************************************************************/
118
119 // Obtain memory from a given zone.
120 static void *
121 zone_malloc(struct zone_s *zone, u32 size, u32 align)
122 {
123     u32 oldpos = GET_PMMVAR(zone->cur);
124     u32 newpos = ALIGN_DOWN(oldpos - size, align);
125     if (newpos < GET_PMMVAR(zone->bottom) || newpos > oldpos)
126         // No space
127         return NULL;
128     SET_PMMVAR(zone->cur, newpos);
129     return (void*)newpos;
130 }
131
132 // Find the zone that contains the given data block.
133 static struct zone_s *
134 zone_find(void *data)
135 {
136     int i;
137     for (i=0; i<ARRAY_SIZE(Zones); i++) {
138         struct zone_s *zone = GET_PMMVAR(Zones[i]);
139         if ((u32)data >= GET_PMMVAR(zone->cur)
140             && (u32)data < GET_PMMVAR(zone->top))
141             return zone;
142     }
143     return NULL;
144 }
145
146 // Return memory to a zone (if it was the last to be allocated).
147 static int
148 zone_free(void *data, u32 olddata)
149 {
150     struct zone_s *zone = zone_find(data);
151     if (!zone || !data || GET_PMMVAR(zone->cur) != (u32)data)
152         return -1;
153     SET_PMMVAR(zone->cur, olddata);
154     return 0;
155 }
156
157 // Report the status of all the zones.
158 static void
159 dumpZones(void)
160 {
161     int i;
162     for (i=0; i<ARRAY_SIZE(Zones); i++) {
163         struct zone_s *zone = Zones[i];
164         u32 used = zone->top - zone->cur;
165         u32 avail = zone->top - zone->bottom;
166         u32 pct = avail ? ((100 * used) / avail) : 0;
167         dprintf(2, "zone %d: %08x-%08x used=%d (%d%%)\n"
168                 , i, zone->bottom, zone->top, used, pct);
169     }
170 }
171
172
173 /****************************************************************
174  * tracked memory allocations
175  ****************************************************************/
176
177 // Information on PMM tracked allocations
178 struct pmmalloc_s {
179     void *data;
180     u32 olddata;
181     u32 handle;
182     u32 oldallocdata;
183     struct pmmalloc_s *next;
184 };
185
186 struct pmmalloc_s *PMMAllocs VAR32FLATVISIBLE;
187
188 // Allocate memory from the given zone and track it as a PMM allocation
189 void *
190 pmm_malloc(struct zone_s *zone, u32 handle, u32 size, u32 align)
191 {
192     u32 oldallocdata = GET_PMMVAR(ZoneTmpHigh.cur);
193     struct pmmalloc_s *info = zone_malloc(&ZoneTmpHigh, sizeof(*info)
194                                           , MALLOC_MIN_ALIGN);
195     if (!info) {
196         oldallocdata = GET_PMMVAR(ZoneTmpLow.cur);
197         info = zone_malloc(&ZoneTmpLow, sizeof(*info), MALLOC_MIN_ALIGN);
198         if (!info)
199             return NULL;
200     }
201     if (zone == &ZoneLow)
202         zonelow_expand(size, align);
203     u32 olddata = GET_PMMVAR(zone->cur);
204     void *data = zone_malloc(zone, size, align);
205     if (! data) {
206         zone_free(info, oldallocdata);
207         return NULL;
208     }
209     dprintf(8, "pmm_malloc zone=%p handle=%x size=%d align=%x"
210             " ret=%p (info=%p)\n"
211             , zone, handle, size, align
212             , data, info);
213     SET_PMMVAR(info->data, data);
214     SET_PMMVAR(info->olddata, olddata);
215     SET_PMMVAR(info->handle, handle);
216     SET_PMMVAR(info->oldallocdata, oldallocdata);
217     SET_PMMVAR(info->next, GET_PMMVAR(PMMAllocs));
218     SET_PMMVAR(PMMAllocs, info);
219     return data;
220 }
221
222 // Free a raw data block (either from a zone or from pmm alloc list).
223 static void
224 pmm_free_data(void *data, u32 olddata)
225 {
226     int ret = zone_free(data, olddata);
227     if (!ret)
228         // Success - done.
229         return;
230     struct pmmalloc_s *info;
231     for (info=GET_PMMVAR(PMMAllocs); info; info = GET_PMMVAR(info->next))
232         if (GET_PMMVAR(info->olddata) == (u32)data) {
233             SET_PMMVAR(info->olddata, olddata);
234             return;
235         } else if (GET_PMMVAR(info->oldallocdata) == (u32)data) {
236             SET_PMMVAR(info->oldallocdata, olddata);
237             return;
238         }
239 }
240
241 // Free a data block allocated with pmm_malloc
242 int
243 pmm_free(void *data)
244 {
245     struct pmmalloc_s **pinfo = &PMMAllocs;
246     for (;;) {
247         struct pmmalloc_s *info = GET_PMMVAR(*pinfo);
248         if (!info)
249             return -1;
250         if (GET_PMMVAR(info->data) == data) {
251             SET_PMMVAR(*pinfo, GET_PMMVAR(info->next));
252             u32 oldallocdata = GET_PMMVAR(info->oldallocdata);
253             u32 olddata = GET_PMMVAR(info->olddata);
254             pmm_free_data(data, olddata);
255             pmm_free_data(info, oldallocdata);
256             dprintf(8, "pmm_free data=%p olddata=%p oldallocdata=%p info=%p\n"
257                     , data, (void*)olddata, (void*)oldallocdata, info);
258             return 0;
259         }
260         pinfo = &info->next;
261     }
262 }
263
264 // Find the amount of free space in a given zone.
265 static u32
266 pmm_getspace(struct zone_s *zone)
267 {
268     // XXX - doesn't account for ZoneLow being able to grow.
269     u32 space = GET_PMMVAR(zone->cur) - GET_PMMVAR(zone->bottom);
270     if (zone != &ZoneTmpHigh && zone != &ZoneTmpLow)
271         return space;
272     // Account for space needed for PMM tracking.
273     u32 reserve = ALIGN(sizeof(struct pmmalloc_s), MALLOC_MIN_ALIGN);
274     if (space <= reserve)
275         return 0;
276     return space - reserve;
277 }
278
279 // Find the data block allocated with pmm_malloc with a given handle.
280 static void *
281 pmm_find(u32 handle)
282 {
283     struct pmmalloc_s *info;
284     for (info=GET_PMMVAR(PMMAllocs); info; info = GET_PMMVAR(info->next))
285         if (GET_PMMVAR(info->handle) == handle)
286             return GET_PMMVAR(info->data);
287     return NULL;
288 }
289
290 void
291 malloc_setup(void)
292 {
293     ASSERT32FLAT();
294     dprintf(3, "malloc setup\n");
295
296     PMMAllocs = NULL;
297
298     // Memory in 0xf0000 area.
299     extern u8 code32flat_start[];
300     if ((u32)code32flat_start > BUILD_BIOS_ADDR)
301         // Clear unused parts of f-segment
302         memset((void*)BUILD_BIOS_ADDR, 0
303                , (u32)code32flat_start - BUILD_BIOS_ADDR);
304     memset(BiosTableSpace, 0, CONFIG_MAX_BIOSTABLE);
305     ZoneFSeg.bottom = (u32)BiosTableSpace;
306     ZoneFSeg.top = ZoneFSeg.cur = ZoneFSeg.bottom + CONFIG_MAX_BIOSTABLE;
307
308     // Memory under 1Meg.
309     ZoneTmpLow.bottom = BUILD_STACK_ADDR;
310     ZoneTmpLow.top = ZoneTmpLow.cur = BUILD_EBDA_MINIMUM;
311
312     // Permanent memory under 1Meg.
313     ZoneLow.bottom = ZoneLow.top = ZoneLow.cur = BUILD_LOWRAM_END;
314
315     // Find memory at the top of ram.
316     struct e820entry *e = find_high_area(CONFIG_MAX_HIGHTABLE+MALLOC_MIN_ALIGN);
317     if (!e) {
318         // No memory above 1Meg
319         memset(&ZoneHigh, 0, sizeof(ZoneHigh));
320         memset(&ZoneTmpHigh, 0, sizeof(ZoneTmpHigh));
321         return;
322     }
323     u32 top = e->start + e->size, bottom = e->start;
324
325     // Memory at top of ram.
326     ZoneHigh.bottom = ALIGN(top - CONFIG_MAX_HIGHTABLE, MALLOC_MIN_ALIGN);
327     ZoneHigh.top = ZoneHigh.cur = ZoneHigh.bottom + CONFIG_MAX_HIGHTABLE;
328     add_e820(ZoneHigh.bottom, CONFIG_MAX_HIGHTABLE, E820_RESERVED);
329
330     // Memory above 1Meg
331     ZoneTmpHigh.bottom = ALIGN(bottom, MALLOC_MIN_ALIGN);
332     ZoneTmpHigh.top = ZoneTmpHigh.cur = ZoneHigh.bottom;
333 }
334
335 void
336 malloc_finalize(void)
337 {
338     dprintf(3, "malloc finalize\n");
339
340     dumpZones();
341
342     // Reserve more low-mem if needed.
343     u32 endlow = GET_BDA(mem_size_kb)*1024;
344     add_e820(endlow, BUILD_LOWRAM_END-endlow, E820_RESERVED);
345
346     // Give back unused high ram.
347     u32 giveback = ALIGN_DOWN(ZoneHigh.cur - ZoneHigh.bottom, PAGE_SIZE);
348     add_e820(ZoneHigh.bottom, giveback, E820_RAM);
349     dprintf(1, "Returned %d bytes of ZoneHigh\n", giveback);
350
351     // Clear low-memory allocations.
352     memset((void*)ZoneTmpLow.bottom, 0, ZoneTmpLow.top - ZoneTmpLow.bottom);
353 }
354
355
356 /****************************************************************
357  * pmm interface
358  ****************************************************************/
359
360 struct pmmheader {
361     u32 signature;
362     u8 version;
363     u8 length;
364     u8 checksum;
365     u16 entry_offset;
366     u16 entry_seg;
367     u8 reserved[5];
368 } PACKED;
369
370 extern struct pmmheader PMMHEADER;
371
372 #define PMM_SIGNATURE 0x4d4d5024 // $PMM
373
374 #if CONFIG_PMM
375 struct pmmheader PMMHEADER __aligned(16) VAR16EXPORT = {
376     .version = 0x01,
377     .length = sizeof(PMMHEADER),
378     .entry_seg = SEG_BIOS,
379 };
380 #endif
381
382 #define PMM_FUNCTION_NOT_SUPPORTED 0xffffffff
383
384 // PMM - allocate
385 static u32
386 handle_pmm00(u16 *args)
387 {
388     u32 length = *(u32*)&args[1], handle = *(u32*)&args[3];
389     u16 flags = args[5];
390     dprintf(3, "pmm00: length=%x handle=%x flags=%x\n"
391             , length, handle, flags);
392     struct zone_s *lowzone = &ZoneTmpLow, *highzone = &ZoneTmpHigh;
393     if (flags & 8) {
394         // Permanent memory request.
395         lowzone = &ZoneLow;
396         highzone = &ZoneHigh;
397     }
398     if (!length) {
399         // Memory size request
400         switch (flags & 3) {
401         default:
402         case 0:
403             return 0;
404         case 1:
405             return pmm_getspace(lowzone);
406         case 2:
407             return pmm_getspace(highzone);
408         case 3: {
409             u32 spacelow = pmm_getspace(lowzone);
410             u32 spacehigh = pmm_getspace(highzone);
411             if (spacelow > spacehigh)
412                 return spacelow;
413             return spacehigh;
414         }
415         }
416     }
417     u32 size = length * 16;
418     if ((s32)size <= 0)
419         return 0;
420     u32 align = MALLOC_MIN_ALIGN;
421     if (flags & 4) {
422         align = 1<<__ffs(size);
423         if (align < MALLOC_MIN_ALIGN)
424             align = MALLOC_MIN_ALIGN;
425     }
426     switch (flags & 3) {
427     default:
428     case 0:
429         return 0;
430     case 1:
431         return (u32)pmm_malloc(lowzone, handle, size, align);
432     case 2:
433         return (u32)pmm_malloc(highzone, handle, size, align);
434     case 3: {
435         void *data = pmm_malloc(lowzone, handle, size, align);
436         if (data)
437             return (u32)data;
438         return (u32)pmm_malloc(highzone, handle, size, align);
439     }
440     }
441 }
442
443 // PMM - find
444 static u32
445 handle_pmm01(u16 *args)
446 {
447     u32 handle = *(u32*)&args[1];
448     dprintf(3, "pmm01: handle=%x\n", handle);
449     if (handle == PMM_DEFAULT_HANDLE)
450         return 0;
451     return (u32)pmm_find(handle);
452 }
453
454 // PMM - deallocate
455 static u32
456 handle_pmm02(u16 *args)
457 {
458     u32 buffer = *(u32*)&args[1];
459     dprintf(3, "pmm02: buffer=%x\n", buffer);
460     int ret = pmm_free((void*)buffer);
461     if (ret)
462         // Error
463         return 1;
464     return 0;
465 }
466
467 static u32
468 handle_pmmXX(u16 *args)
469 {
470     return PMM_FUNCTION_NOT_SUPPORTED;
471 }
472
473 u32 VISIBLE16
474 handle_pmm(u16 *args)
475 {
476     if (! CONFIG_PMM)
477         return PMM_FUNCTION_NOT_SUPPORTED;
478
479     u16 arg1 = args[0];
480     dprintf(DEBUG_HDL_pmm, "pmm call arg1=%x\n", arg1);
481
482     switch (arg1) {
483     case 0x00: return handle_pmm00(args);
484     case 0x01: return handle_pmm01(args);
485     case 0x02: return handle_pmm02(args);
486     default:   return handle_pmmXX(args);
487     }
488 }
489
490 // romlayout.S
491 extern void entry_pmm(void);
492
493 void
494 pmm_setup(void)
495 {
496     if (! CONFIG_PMM)
497         return;
498
499     dprintf(3, "init PMM\n");
500
501     PMMHEADER.signature = PMM_SIGNATURE;
502     PMMHEADER.entry_offset = (u32)entry_pmm - BUILD_BIOS_ADDR;
503     PMMHEADER.checksum -= checksum(&PMMHEADER, sizeof(PMMHEADER));
504 }
505
506 void
507 pmm_finalize(void)
508 {
509     if (! CONFIG_PMM)
510         return;
511
512     dprintf(3, "finalize PMM\n");
513
514     PMMHEADER.signature = 0;
515     PMMHEADER.entry_offset = 0;
516 }