Cleanups for malloc code.
[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" // EBDA_SEGMENT_MINIMUM
12
13
14 /****************************************************************
15  * malloc
16  ****************************************************************/
17
18 #if MODE16
19 // The 16bit pmm entry points runs in "big real" mode, and can
20 // therefore read/write to the 32bit malloc variables.
21 #define GET_PMMVAR(var) GET_FARVAR(0, (var))
22 #define SET_PMMVAR(var, val) SET_FARVAR(0, (var), (val))
23 #else
24 #define GET_PMMVAR(var) (var)
25 #define SET_PMMVAR(var, val) do { (var) = (val); } while (0)
26 #endif
27
28 // Zone definitions
29 struct zone_s {
30     u32 top, bottom, cur;
31 };
32
33 struct zone_s ZoneLow VAR32VISIBLE, ZoneHigh VAR32VISIBLE;
34 struct zone_s ZoneFSeg VAR32VISIBLE;
35 struct zone_s ZoneTmpLow VAR32VISIBLE, ZoneTmpHigh VAR32VISIBLE;
36
37 struct zone_s *Zones[] VAR32VISIBLE = {
38     &ZoneTmpLow, &ZoneLow, &ZoneFSeg, &ZoneTmpHigh, &ZoneHigh
39 };
40
41 // Obtain memory from a given zone.
42 void *
43 zone_malloc(struct zone_s *zone, u32 size, u32 align)
44 {
45     u32 oldpos = GET_PMMVAR(zone->cur);
46     u32 newpos = ALIGN_DOWN(oldpos - size, align);
47     if (newpos < GET_PMMVAR(zone->bottom) || newpos > oldpos)
48         // No space
49         return NULL;
50     SET_PMMVAR(zone->cur, newpos);
51     return (void*)newpos;
52 }
53
54 // Return memory to a zone (if it was the last to be allocated).
55 static void
56 zone_free(struct zone_s *zone, void *data, u32 olddata)
57 {
58     if (! data || GET_PMMVAR(zone->cur) != (u32)data)
59         return;
60     SET_PMMVAR(zone->cur, olddata);
61 }
62
63 // Find the zone that contains the given data block.
64 static struct zone_s *
65 zone_find(void *data)
66 {
67     int i;
68     for (i=0; i<ARRAY_SIZE(Zones); i++) {
69         struct zone_s *zone = GET_PMMVAR(Zones[i]);
70         if ((u32)data >= GET_PMMVAR(zone->cur)
71             && (u32)data < GET_PMMVAR(zone->top))
72             return zone;
73     }
74     return NULL;
75 }
76
77 // Report the status of all the zones.
78 static void
79 dumpZones()
80 {
81     int i;
82     for (i=0; i<ARRAY_SIZE(Zones); i++) {
83         struct zone_s *zone = Zones[i];
84         u32 used = zone->top - zone->cur;
85         u32 avail = zone->top - zone->bottom;
86         u32 pct = avail ? ((100 * used) / avail) : 0;
87         dprintf(2, "zone %d: %08x-%08x used=%d (%d%%)\n"
88                 , i, zone->bottom, zone->top, used, pct);
89     }
90 }
91
92 void
93 malloc_setup()
94 {
95     ASSERT32();
96     dprintf(3, "malloc setup\n");
97
98     // Memory in 0xf0000 area.
99     memset(BiosTableSpace, 0, CONFIG_MAX_BIOSTABLE);
100     ZoneFSeg.bottom = (u32)BiosTableSpace;
101     ZoneFSeg.top = ZoneFSeg.cur = ZoneFSeg.bottom + CONFIG_MAX_BIOSTABLE;
102
103     // Memory under 1Meg.
104     ZoneTmpLow.bottom = BUILD_STACK_ADDR;
105     ZoneTmpLow.top = ZoneTmpLow.cur = (u32)MAKE_FLATPTR(EBDA_SEGMENT_MINIMUM, 0);
106
107     // Permanent memory under 1Meg.  XXX - not implemented yet.
108     ZoneLow.bottom = ZoneLow.top = ZoneLow.cur = 0xa0000;
109
110     // Find memory at the top of ram.
111     struct e820entry *e = find_high_area(CONFIG_MAX_HIGHTABLE+MALLOC_MIN_ALIGN);
112     if (!e) {
113         // No memory above 1Meg
114         memset(&ZoneHigh, 0, sizeof(ZoneHigh));
115         memset(&ZoneTmpHigh, 0, sizeof(ZoneTmpHigh));
116         return;
117     }
118     u32 top = e->start + e->size, bottom = e->start;
119
120     // Memory at top of ram.
121     ZoneHigh.bottom = ALIGN(top - CONFIG_MAX_HIGHTABLE, MALLOC_MIN_ALIGN);
122     ZoneHigh.top = ZoneHigh.cur = ZoneHigh.bottom + CONFIG_MAX_HIGHTABLE;
123     add_e820(ZoneHigh.bottom, CONFIG_MAX_HIGHTABLE, E820_RESERVED);
124
125     // Memory above 1Meg
126     ZoneTmpHigh.bottom = ALIGN(bottom, MALLOC_MIN_ALIGN);
127     ZoneTmpHigh.top = ZoneTmpHigh.cur = ZoneHigh.bottom;
128 }
129
130 void
131 malloc_finalize()
132 {
133     dprintf(3, "malloc finalize\n");
134
135     dumpZones();
136
137     // Give back unused high ram.
138     u32 giveback = ALIGN_DOWN(ZoneHigh.cur - ZoneHigh.bottom, PAGE_SIZE);
139     add_e820(ZoneHigh.bottom, giveback, E820_RAM);
140     dprintf(1, "Returned %d bytes of ZoneHigh\n", giveback);
141
142     // Clear low-memory allocations.
143     memset((void*)ZoneTmpLow.bottom, 0, ZoneTmpLow.top - ZoneTmpLow.bottom);
144 }
145
146
147 /****************************************************************
148  * pmm allocation
149  ****************************************************************/
150
151 // Information on PMM tracked allocations
152 struct pmmalloc_s {
153     void *data;
154     u32 olddata;
155     u32 handle;
156     u32 oldallocdata;
157     struct pmmalloc_s *next;
158 };
159
160 struct pmmalloc_s *PMMAllocs VAR32VISIBLE;
161
162 // Memory zone that pmm allocation tracking info is stored in
163 #define ZONEALLOC (&ZoneTmpHigh)
164
165 // Allocate memory from the given zone and track it as a PMM allocation
166 static void *
167 pmm_malloc(struct zone_s *zone, u32 handle, u32 size, u32 align)
168 {
169     u32 oldallocdata = GET_PMMVAR(ZONEALLOC->cur);
170     struct pmmalloc_s *info = zone_malloc(ZONEALLOC, sizeof(*info)
171                                           , MALLOC_MIN_ALIGN);
172     if (!info)
173         return NULL;
174     u32 olddata = GET_PMMVAR(zone->cur);
175     void *data = zone_malloc(zone, size, align);
176     if (! data) {
177         zone_free(ZONEALLOC, info, oldallocdata);
178         return NULL;
179     }
180     dprintf(8, "pmm_malloc zone=%p handle=%x size=%d align=%x"
181             " ret=%p (info=%p)\n"
182             , zone, handle, size, align
183             , data, info);
184     SET_PMMVAR(info->data, data);
185     SET_PMMVAR(info->olddata, olddata);
186     SET_PMMVAR(info->handle, handle);
187     SET_PMMVAR(info->oldallocdata, oldallocdata);
188     SET_PMMVAR(info->next, GET_PMMVAR(PMMAllocs));
189     SET_PMMVAR(PMMAllocs, info);
190     return data;
191 }
192
193 // Free a raw data block (either from a zone or from pmm alloc list).
194 static void
195 pmm_free_data(struct zone_s *zone, void *data, u32 olddata)
196 {
197     if (GET_PMMVAR(zone->cur) == (u32)data) {
198         zone_free(zone, data, olddata);
199         return;
200     }
201     struct pmmalloc_s *info;
202     for (info=GET_PMMVAR(PMMAllocs); info; info = GET_PMMVAR(info->next))
203         if (GET_PMMVAR(info->olddata) == (u32)data) {
204             SET_PMMVAR(info->olddata, olddata);
205             return;
206         } else if (GET_PMMVAR(info->oldallocdata) == (u32)data) {
207             SET_PMMVAR(info->oldallocdata, olddata);
208             return;
209         }
210 }
211
212 // Free a data block allocated with pmm_malloc
213 static int
214 pmm_free(void *data)
215 {
216     struct zone_s *zone = zone_find(GET_PMMVAR(data));
217     if (!zone)
218         return -1;
219     struct pmmalloc_s **pinfo = &PMMAllocs;
220     for (;;) {
221         struct pmmalloc_s *info = GET_PMMVAR(*pinfo);
222         if (!info)
223             return -1;
224         if (GET_PMMVAR(info->data) == data) {
225             SET_PMMVAR(*pinfo, GET_PMMVAR(info->next));
226             u32 oldallocdata = GET_PMMVAR(info->oldallocdata);
227             u32 olddata = GET_PMMVAR(info->olddata);
228             pmm_free_data(zone, data, olddata);
229             pmm_free_data(ZONEALLOC, info, oldallocdata);
230             dprintf(8, "pmm_free data=%p zone=%p olddata=%p oldallocdata=%p"
231                     " info=%p\n"
232                     , data, zone, (void*)olddata, (void*)oldallocdata
233                     , info);
234             return 0;
235         }
236         pinfo = &info->next;
237     }
238 }
239
240 // Find the amount of free space in a given zone.
241 static u32
242 pmm_getspace(struct zone_s *zone)
243 {
244     u32 space = GET_PMMVAR(zone->cur) - GET_PMMVAR(zone->bottom);
245     if (zone != ZONEALLOC)
246         return space;
247     u32 reserve = ALIGN(sizeof(struct pmmalloc_s), MALLOC_MIN_ALIGN);
248     if (space <= reserve)
249         return 0;
250     return space - reserve;
251 }
252
253 // Find the data block allocated with pmm_malloc with a given handle.
254 static void *
255 pmm_find(u32 handle)
256 {
257     struct pmmalloc_s *info;
258     for (info=GET_PMMVAR(PMMAllocs); info; info = GET_PMMVAR(info->next))
259         if (GET_PMMVAR(info->handle) == handle)
260             return GET_PMMVAR(info->data);
261     return NULL;
262 }
263
264
265 /****************************************************************
266  * pmm interface
267  ****************************************************************/
268
269 struct pmmheader {
270     u32 signature;
271     u8 version;
272     u8 length;
273     u8 checksum;
274     u16 entry_offset;
275     u16 entry_seg;
276     u8 reserved[5];
277 } PACKED;
278
279 extern struct pmmheader PMMHEADER;
280
281 #define PMM_SIGNATURE 0x4d4d5024 // $PMM
282
283 #if CONFIG_PMM
284 struct pmmheader PMMHEADER __aligned(16) VAR16EXPORT = {
285     .version = 0x01,
286     .length = sizeof(PMMHEADER),
287     .entry_seg = SEG_BIOS,
288 };
289 #endif
290
291 #define PMM_FUNCTION_NOT_SUPPORTED 0xffffffff
292
293 // PMM - allocate
294 static u32
295 handle_pmm00(u16 *args)
296 {
297     u32 length = *(u32*)&args[1], handle = *(u32*)&args[3];
298     u16 flags = args[5];
299     dprintf(3, "pmm00: length=%x handle=%x flags=%x\n"
300             , length, handle, flags);
301     struct zone_s *lowzone = &ZoneTmpLow, *highzone = &ZoneTmpHigh;
302     if (flags & 8) {
303         // Permanent memory request.
304         lowzone = &ZoneLow;
305         highzone = &ZoneHigh;
306     }
307     if (!length) {
308         // Memory size request
309         switch (flags & 3) {
310         default:
311         case 0:
312             return 0;
313         case 1:
314             return pmm_getspace(lowzone);
315         case 2:
316             return pmm_getspace(highzone);
317         case 3: {
318             u32 spacelow = pmm_getspace(lowzone);
319             u32 spacehigh = pmm_getspace(highzone);
320             if (spacelow > spacehigh)
321                 return spacelow;
322             return spacehigh;
323         }
324         }
325     }
326     u32 size = length * 16;
327     if ((s32)size <= 0)
328         return 0;
329     u32 align = MALLOC_MIN_ALIGN;
330     if (flags & 4) {
331         align = 1<<__ffs(size);
332         if (align < MALLOC_MIN_ALIGN)
333             align = MALLOC_MIN_ALIGN;
334     }
335     switch (flags & 3) {
336     default:
337     case 0:
338         return 0;
339     case 1:
340         return (u32)pmm_malloc(lowzone, handle, size, align);
341     case 2:
342         return (u32)pmm_malloc(highzone, handle, size, align);
343     case 3: {
344         void *data = pmm_malloc(lowzone, handle, size, align);
345         if (data)
346             return (u32)data;
347         return (u32)pmm_malloc(highzone, handle, size, align);
348     }
349     }
350 }
351
352 // PMM - find
353 static u32
354 handle_pmm01(u16 *args)
355 {
356     u32 handle = *(u32*)&args[1];
357     dprintf(3, "pmm01: handle=%x\n", handle);
358     if (handle == 0xFFFFFFFF)
359         return 0;
360     return (u32)pmm_find(handle);
361 }
362
363 // PMM - deallocate
364 static u32
365 handle_pmm02(u16 *args)
366 {
367     u32 buffer = *(u32*)&args[1];
368     dprintf(3, "pmm02: buffer=%x\n", buffer);
369     int ret = pmm_free((void*)buffer);
370     if (ret)
371         // Error
372         return 1;
373     return 0;
374 }
375
376 static u32
377 handle_pmmXX(u16 *args)
378 {
379     return PMM_FUNCTION_NOT_SUPPORTED;
380 }
381
382 u32 VISIBLE16
383 handle_pmm(u16 *args)
384 {
385     if (! CONFIG_PMM)
386         return PMM_FUNCTION_NOT_SUPPORTED;
387
388     u16 arg1 = args[0];
389     dprintf(DEBUG_HDL_pmm, "pmm call arg1=%x\n", arg1);
390
391     switch (arg1) {
392     case 0x00: return handle_pmm00(args);
393     case 0x01: return handle_pmm01(args);
394     case 0x02: return handle_pmm02(args);
395     default:   return handle_pmmXX(args);
396     }
397 }
398
399 // romlayout.S
400 extern void entry_pmm();
401
402 void
403 pmm_setup()
404 {
405     if (! CONFIG_PMM)
406         return;
407
408     dprintf(3, "init PMM\n");
409
410     PMMAllocs = NULL;
411
412     PMMHEADER.signature = PMM_SIGNATURE;
413     PMMHEADER.entry_offset = (u32)entry_pmm - BUILD_BIOS_ADDR;
414     PMMHEADER.checksum -= checksum(&PMMHEADER, sizeof(PMMHEADER));
415 }
416
417 void
418 pmm_finalize()
419 {
420     if (! CONFIG_PMM)
421         return;
422
423     dprintf(3, "finalize PMM\n");
424
425     PMMHEADER.signature = 0;
426     PMMHEADER.entry_offset = 0;
427 }