Palacios Public Git Repository

To checkout Palacios execute

  git clone http://v3vee.org/palacios/palacios.web/palacios.git
This will give you the master branch. You probably want the devel branch or one of the release branches. To switch to the devel branch, simply execute
  cd palacios
  git checkout --track -b devel origin/devel
The other branches are similar.


Add HVM configuration capability, init/deinit, etc
[palacios.git] / bios / seabios / 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" // struct e820entry
10 #include "farptr.h" // GET_FARVAR
11 #include "biosvar.h" // GET_BDA
12
13 // Information on a reserved area.
14 struct allocinfo_s {
15     struct allocinfo_s *next, **pprev;
16     void *data, *dataend, *allocend;
17 };
18
19 // Information on a tracked memory allocation.
20 struct allocdetail_s {
21     struct allocinfo_s detailinfo;
22     struct allocinfo_s datainfo;
23     u32 handle;
24 };
25
26 // The various memory zones.
27 struct zone_s {
28     struct allocinfo_s *info;
29 };
30
31 struct zone_s ZoneLow, ZoneHigh, ZoneFSeg, ZoneTmpLow, ZoneTmpHigh;
32
33 static struct zone_s *Zones[] = {
34     &ZoneTmpLow, &ZoneLow, &ZoneFSeg, &ZoneTmpHigh, &ZoneHigh
35 };
36
37
38 /****************************************************************
39  * low-level memory reservations
40  ****************************************************************/
41
42 // Find and reserve space from a given zone
43 static void *
44 allocSpace(struct zone_s *zone, u32 size, u32 align, struct allocinfo_s *fill)
45 {
46     struct allocinfo_s *info;
47     for (info = zone->info; info; info = info->next) {
48         void *dataend = info->dataend;
49         void *allocend = info->allocend;
50         void *newallocend = (void*)ALIGN_DOWN((u32)allocend - size, align);
51         if (newallocend >= dataend && newallocend <= allocend) {
52             // Found space - now reserve it.
53             struct allocinfo_s **pprev = info->pprev;
54             if (!fill)
55                 fill = newallocend;
56             fill->next = info;
57             fill->pprev = pprev;
58             fill->data = newallocend;
59             fill->dataend = newallocend + size;
60             fill->allocend = allocend;
61
62             info->allocend = newallocend;
63             info->pprev = &fill->next;
64             *pprev = fill;
65             return newallocend;
66         }
67     }
68     return NULL;
69 }
70
71 // Release space allocated with allocSpace()
72 static void
73 freeSpace(struct allocinfo_s *info)
74 {
75     struct allocinfo_s *next = info->next;
76     struct allocinfo_s **pprev = info->pprev;
77     *pprev = next;
78     if (next) {
79         if (next->allocend == info->data)
80             next->allocend = info->allocend;
81         next->pprev = pprev;
82     }
83 }
84
85 // Add new memory to a zone
86 static void
87 addSpace(struct zone_s *zone, void *start, void *end)
88 {
89     // Find position to add space
90     struct allocinfo_s **pprev = &zone->info, *info;
91     for (;;) {
92         info = *pprev;
93         if (!info || info->data < start)
94             break;
95         pprev = &info->next;
96     }
97
98     // Add space using temporary allocation info.
99     struct allocdetail_s tempdetail;
100     tempdetail.datainfo.next = info;
101     tempdetail.datainfo.pprev = pprev;
102     tempdetail.datainfo.data = tempdetail.datainfo.dataend = start;
103     tempdetail.datainfo.allocend = end;
104     *pprev = &tempdetail.datainfo;
105     if (info)
106         info->pprev = &tempdetail.datainfo.next;
107
108     // Allocate final allocation info.
109     struct allocdetail_s *detail = allocSpace(
110         &ZoneTmpHigh, sizeof(*detail), MALLOC_MIN_ALIGN, NULL);
111     if (!detail) {
112         detail = allocSpace(&ZoneTmpLow, sizeof(*detail)
113                             , MALLOC_MIN_ALIGN, NULL);
114         if (!detail) {
115             *tempdetail.datainfo.pprev = tempdetail.datainfo.next;
116             if (tempdetail.datainfo.next)
117                 tempdetail.datainfo.next->pprev = tempdetail.datainfo.pprev;
118             warn_noalloc();
119             return;
120         }
121     }
122
123     // Replace temp alloc space with final alloc space
124     memcpy(&detail->datainfo, &tempdetail.datainfo, sizeof(detail->datainfo));
125     detail->handle = PMM_DEFAULT_HANDLE;
126
127     *tempdetail.datainfo.pprev = &detail->datainfo;
128     if (tempdetail.datainfo.next)
129         tempdetail.datainfo.next->pprev = &detail->datainfo.next;
130 }
131
132 // Search all zones for an allocation obtained from allocSpace()
133 static struct allocinfo_s *
134 findAlloc(void *data)
135 {
136     int i;
137     for (i=0; i<ARRAY_SIZE(Zones); i++) {
138         struct zone_s *zone = Zones[i];
139         struct allocinfo_s *info;
140         for (info = zone->info; info; info = info->next)
141             if (info->data == data)
142                 return info;
143     }
144     return NULL;
145 }
146
147 // Return the last sentinal node of a zone
148 static struct allocinfo_s *
149 findLast(struct zone_s *zone)
150 {
151     struct allocinfo_s *info = zone->info;
152     if (!info)
153         return NULL;
154     for (;;) {
155         struct allocinfo_s *next = info->next;
156         if (!next)
157             return info;
158         info = next;
159     }
160 }
161
162
163 /****************************************************************
164  * Setup
165  ****************************************************************/
166
167 void
168 malloc_setup(void)
169 {
170     ASSERT32FLAT();
171     dprintf(3, "malloc setup\n");
172
173     // Populate temp high ram
174     u32 highram = 0;
175     int i;
176     for (i=e820_count-1; i>=0; i--) {
177         struct e820entry *en = &e820_list[i];
178         u64 end = en->start + en->size;
179         if (end < 1024*1024)
180             break;
181         if (en->type != E820_RAM || end > 0xffffffff)
182             continue;
183         u32 s = en->start, e = end;
184         if (!highram) {
185             u32 newe = ALIGN_DOWN(e - CONFIG_MAX_HIGHTABLE, MALLOC_MIN_ALIGN);
186             if (newe <= e && newe >= s) {
187                 highram = newe;
188                 e = newe;
189             }
190         }
191         addSpace(&ZoneTmpHigh, (void*)s, (void*)e);
192     }
193
194     // Populate other regions
195     addSpace(&ZoneTmpLow, (void*)BUILD_STACK_ADDR, (void*)BUILD_EBDA_MINIMUM);
196     addSpace(&ZoneFSeg, BiosTableSpace, &BiosTableSpace[CONFIG_MAX_BIOSTABLE]);
197     addSpace(&ZoneLow, (void*)BUILD_LOWRAM_END, (void*)BUILD_LOWRAM_END);
198     if (highram) {
199         addSpace(&ZoneHigh, (void*)highram
200                  , (void*)highram + CONFIG_MAX_HIGHTABLE);
201         add_e820(highram, CONFIG_MAX_HIGHTABLE, E820_RESERVED);
202     }
203 }
204
205 // Update pointers after code relocation.
206 void
207 malloc_fixupreloc(void)
208 {
209     ASSERT32FLAT();
210     if (!CONFIG_RELOCATE_INIT)
211         return;
212     dprintf(3, "malloc fixup reloc\n");
213
214     int i;
215     for (i=0; i<ARRAY_SIZE(Zones); i++) {
216         struct zone_s *zone = Zones[i];
217         zone->info->pprev = &zone->info;
218     }
219
220     // Add space free'd during relocation in f-segment to ZoneFSeg
221     extern u8 code32init_end[];
222     if ((u32)code32init_end > BUILD_BIOS_ADDR) {
223         memset((void*)BUILD_BIOS_ADDR, 0, (u32)code32init_end - BUILD_BIOS_ADDR);
224         addSpace(&ZoneFSeg, (void*)BUILD_BIOS_ADDR, code32init_end);
225     }
226 }
227
228 void
229 malloc_finalize(void)
230 {
231     ASSERT32FLAT();
232     dprintf(3, "malloc finalize\n");
233
234     // Reserve more low-mem if needed.
235     u32 endlow = GET_BDA(mem_size_kb)*1024;
236     add_e820(endlow, BUILD_LOWRAM_END-endlow, E820_RESERVED);
237
238     // Give back unused high ram.
239     struct allocinfo_s *info = findLast(&ZoneHigh);
240     if (info) {
241         u32 giveback = ALIGN_DOWN(info->allocend - info->dataend, PAGE_SIZE);
242         add_e820((u32)info->dataend, giveback, E820_RAM);
243         dprintf(1, "Returned %d bytes of ZoneHigh\n", giveback);
244     }
245 }
246
247
248 /****************************************************************
249  * ebda movement
250  ****************************************************************/
251
252 // Move ebda
253 static int
254 relocate_ebda(u32 newebda, u32 oldebda, u8 ebda_size)
255 {
256     u32 lowram = GET_BDA(mem_size_kb) * 1024;
257     if (oldebda != lowram)
258         // EBDA isn't at end of ram - give up.
259         return -1;
260
261     // Do copy
262     memmove((void*)newebda, (void*)oldebda, ebda_size * 1024);
263
264     // Update indexes
265     dprintf(1, "ebda moved from %x to %x\n", oldebda, newebda);
266     SET_BDA(mem_size_kb, newebda / 1024);
267     SET_BDA(ebda_seg, FLATPTR_TO_SEG(newebda));
268     return 0;
269 }
270
271 // Support expanding the ZoneLow dynamically.
272 static void
273 zonelow_expand(u32 size, u32 align)
274 {
275     struct allocinfo_s *info = findLast(&ZoneLow);
276     if (!info)
277         return;
278     u32 oldpos = (u32)info->allocend;
279     u32 newpos = ALIGN_DOWN(oldpos - size, align);
280     u32 bottom = (u32)info->dataend;
281     if (newpos >= bottom && newpos <= oldpos)
282         // Space already present.
283         return;
284     u16 ebda_seg = get_ebda_seg();
285     u32 ebda_pos = (u32)MAKE_FLATPTR(ebda_seg, 0);
286     u8 ebda_size = GET_EBDA2(ebda_seg, size);
287     u32 ebda_end = ebda_pos + ebda_size * 1024;
288     if (ebda_end != bottom)
289         // Something else is after ebda - can't use any existing space.
290         newpos = ALIGN_DOWN(ebda_end - size, align);
291     u32 newbottom = ALIGN_DOWN(newpos, 1024);
292     u32 newebda = ALIGN_DOWN(newbottom - ebda_size * 1024, 1024);
293     if (newebda < BUILD_EBDA_MINIMUM)
294         // Not enough space.
295         return;
296
297     // Move ebda
298     int ret = relocate_ebda(newebda, ebda_pos, ebda_size);
299     if (ret)
300         return;
301
302     // Update zone
303     if (ebda_end == bottom) {
304         info->data = (void*)newbottom;
305         info->dataend = (void*)newbottom;
306     } else
307         addSpace(&ZoneLow, (void*)newbottom, (void*)ebda_end);
308 }
309
310 // Check if can expand the given zone to fulfill an allocation
311 static void *
312 allocExpandSpace(struct zone_s *zone, u32 size, u32 align
313                  , struct allocinfo_s *fill)
314 {
315     void *data = allocSpace(zone, size, align, fill);
316     if (data || zone != &ZoneLow)
317         return data;
318
319     // Make sure to not move ebda while an optionrom is running.
320     if (unlikely(wait_preempt())) {
321         data = allocSpace(zone, size, align, fill);
322         if (data)
323             return data;
324     }
325
326     zonelow_expand(size, align);
327     return allocSpace(zone, size, align, fill);
328 }
329
330
331 /****************************************************************
332  * tracked memory allocations
333  ****************************************************************/
334
335 // Allocate memory from the given zone and track it as a PMM allocation
336 void * __malloc
337 pmm_malloc(struct zone_s *zone, u32 handle, u32 size, u32 align)
338 {
339     ASSERT32FLAT();
340     if (!size)
341         return NULL;
342
343     // Find and reserve space for bookkeeping.
344     struct allocdetail_s *detail = allocSpace(
345         &ZoneTmpHigh, sizeof(*detail), MALLOC_MIN_ALIGN, NULL);
346     if (!detail) {
347         detail = allocSpace(&ZoneTmpLow, sizeof(*detail)
348                             , MALLOC_MIN_ALIGN, NULL);
349         if (!detail)
350             return NULL;
351     }
352
353     // Find and reserve space for main allocation
354     void *data = allocExpandSpace(zone, size, align, &detail->datainfo);
355     if (!data) {
356         freeSpace(&detail->detailinfo);
357         return NULL;
358     }
359
360     dprintf(8, "pmm_malloc zone=%p handle=%x size=%d align=%x"
361             " ret=%p (detail=%p)\n"
362             , zone, handle, size, align
363             , data, detail);
364     detail->handle = handle;
365
366     return data;
367 }
368
369 // Free a data block allocated with pmm_malloc
370 int
371 pmm_free(void *data)
372 {
373     ASSERT32FLAT();
374     struct allocinfo_s *info = findAlloc(data);
375     if (!info || data == (void*)info || data == info->dataend)
376         return -1;
377     struct allocdetail_s *detail = container_of(
378         info, struct allocdetail_s, datainfo);
379     dprintf(8, "pmm_free %p (detail=%p)\n", data, detail);
380     freeSpace(info);
381     freeSpace(&detail->detailinfo);
382     return 0;
383 }
384
385 // Find the amount of free space in a given zone.
386 static u32
387 pmm_getspace(struct zone_s *zone)
388 {
389     // XXX - doesn't account for ZoneLow being able to grow.
390     // XXX - results not reliable when CONFIG_THREAD_OPTIONROMS
391     u32 maxspace = 0;
392     struct allocinfo_s *info;
393     for (info = zone->info; info; info = info->next) {
394         u32 space = info->allocend - info->dataend;
395         if (space > maxspace)
396             maxspace = space;
397     }
398
399     if (zone != &ZoneTmpHigh && zone != &ZoneTmpLow)
400         return maxspace;
401     // Account for space needed for PMM tracking.
402     u32 reserve = ALIGN(sizeof(struct allocdetail_s), MALLOC_MIN_ALIGN);
403     if (maxspace <= reserve)
404         return 0;
405     return maxspace - reserve;
406 }
407
408 // Find the data block allocated with pmm_malloc with a given handle.
409 static void *
410 pmm_find(u32 handle)
411 {
412     int i;
413     for (i=0; i<ARRAY_SIZE(Zones); i++) {
414         struct zone_s *zone = Zones[i];
415         struct allocinfo_s *info;
416         for (info = zone->info; info; info = info->next) {
417             if (info->data != (void*)info)
418                 continue;
419             struct allocdetail_s *detail = container_of(
420                 info, struct allocdetail_s, detailinfo);
421             if (detail->handle == handle)
422                 return detail->datainfo.data;
423         }
424     }
425     return NULL;
426 }
427
428
429 /****************************************************************
430  * pmm interface
431  ****************************************************************/
432
433 struct pmmheader {
434     u32 signature;
435     u8 version;
436     u8 length;
437     u8 checksum;
438     u16 entry_offset;
439     u16 entry_seg;
440     u8 reserved[5];
441 } PACKED;
442
443 extern struct pmmheader PMMHEADER;
444
445 #define PMM_SIGNATURE 0x4d4d5024 // $PMM
446
447 #if CONFIG_PMM
448 struct pmmheader PMMHEADER __aligned(16) VAR16EXPORT = {
449     .version = 0x01,
450     .length = sizeof(PMMHEADER),
451     .entry_seg = SEG_BIOS,
452 };
453 #endif
454
455 #define PMM_FUNCTION_NOT_SUPPORTED 0xffffffff
456
457 // PMM - allocate
458 static u32
459 handle_pmm00(u16 *args)
460 {
461     u32 length = *(u32*)&args[1], handle = *(u32*)&args[3];
462     u16 flags = args[5];
463     dprintf(3, "pmm00: length=%x handle=%x flags=%x\n"
464             , length, handle, flags);
465     struct zone_s *lowzone = &ZoneTmpLow, *highzone = &ZoneTmpHigh;
466     if (flags & 8) {
467         // Permanent memory request.
468         lowzone = &ZoneLow;
469         highzone = &ZoneHigh;
470     }
471     if (!length) {
472         // Memory size request
473         switch (flags & 3) {
474         default:
475         case 0:
476             return 0;
477         case 1:
478             return pmm_getspace(lowzone);
479         case 2:
480             return pmm_getspace(highzone);
481         case 3: {
482             u32 spacelow = pmm_getspace(lowzone);
483             u32 spacehigh = pmm_getspace(highzone);
484             if (spacelow > spacehigh)
485                 return spacelow;
486             return spacehigh;
487         }
488         }
489     }
490     u32 size = length * 16;
491     if ((s32)size <= 0)
492         return 0;
493     u32 align = MALLOC_MIN_ALIGN;
494     if (flags & 4) {
495         align = 1<<__ffs(size);
496         if (align < MALLOC_MIN_ALIGN)
497             align = MALLOC_MIN_ALIGN;
498     }
499     switch (flags & 3) {
500     default:
501     case 0:
502         return 0;
503     case 1:
504         return (u32)pmm_malloc(lowzone, handle, size, align);
505     case 2:
506         return (u32)pmm_malloc(highzone, handle, size, align);
507     case 3: {
508         void *data = pmm_malloc(lowzone, handle, size, align);
509         if (data)
510             return (u32)data;
511         return (u32)pmm_malloc(highzone, handle, size, align);
512     }
513     }
514 }
515
516 // PMM - find
517 static u32
518 handle_pmm01(u16 *args)
519 {
520     u32 handle = *(u32*)&args[1];
521     dprintf(3, "pmm01: handle=%x\n", handle);
522     if (handle == PMM_DEFAULT_HANDLE)
523         return 0;
524     return (u32)pmm_find(handle);
525 }
526
527 // PMM - deallocate
528 static u32
529 handle_pmm02(u16 *args)
530 {
531     u32 buffer = *(u32*)&args[1];
532     dprintf(3, "pmm02: buffer=%x\n", buffer);
533     int ret = pmm_free((void*)buffer);
534     if (ret)
535         // Error
536         return 1;
537     return 0;
538 }
539
540 static u32
541 handle_pmmXX(u16 *args)
542 {
543     return PMM_FUNCTION_NOT_SUPPORTED;
544 }
545
546 u32 VISIBLE32INIT
547 handle_pmm(u16 *args)
548 {
549     ASSERT32FLAT();
550     if (! CONFIG_PMM)
551         return PMM_FUNCTION_NOT_SUPPORTED;
552
553     u16 arg1 = args[0];
554     dprintf(DEBUG_HDL_pmm, "pmm call arg1=%x\n", arg1);
555
556     int oldpreempt;
557     if (CONFIG_THREAD_OPTIONROMS) {
558         // Not a preemption event - don't wait in wait_preempt()
559         oldpreempt = CanPreempt;
560         CanPreempt = 0;
561     }
562
563     u32 ret;
564     switch (arg1) {
565     case 0x00: ret = handle_pmm00(args); break;
566     case 0x01: ret = handle_pmm01(args); break;
567     case 0x02: ret = handle_pmm02(args); break;
568     default:   ret = handle_pmmXX(args); break;
569     }
570
571     if (CONFIG_THREAD_OPTIONROMS)
572         CanPreempt = oldpreempt;
573
574     return ret;
575 }
576
577 // romlayout.S
578 extern void entry_pmm(void);
579
580 void
581 pmm_setup(void)
582 {
583     if (! CONFIG_PMM)
584         return;
585
586     dprintf(3, "init PMM\n");
587
588     PMMHEADER.signature = PMM_SIGNATURE;
589     PMMHEADER.entry_offset = (u32)entry_pmm - BUILD_BIOS_ADDR;
590     PMMHEADER.checksum -= checksum(&PMMHEADER, sizeof(PMMHEADER));
591 }
592
593 void
594 pmm_finalize(void)
595 {
596     if (! CONFIG_PMM)
597         return;
598
599     dprintf(3, "finalize PMM\n");
600
601     PMMHEADER.signature = 0;
602     PMMHEADER.entry_offset = 0;
603 }