Add support for permanent low memory allocations.

Support pmm style permanent low-memory allocations.
When used, relocate ebda and store permanent memory at top of 640K.
diff --git a/src/biosvar.h b/src/biosvar.h
index 9ed02a3..983d28f 100644
--- a/src/biosvar.h
+++ b/src/biosvar.h
@@ -228,9 +228,7 @@
 #define EBDA_SIZE_START \
     DIV_ROUND_UP(sizeof(struct extended_bios_data_area_s), 1024)
 #define EBDA_SEGMENT_START \
-    FLATPTR_TO_SEG((640 - EBDA_SIZE_START) * 1024)
-#define EBDA_SEGMENT_MINIMUM \
-    FLATPTR_TO_SEG((640 - 256) * 1024)
+    FLATPTR_TO_SEG(BUILD_LOWRAM_END - EBDA_SIZE_START*1024)
 
 // Accessor functions
 static inline u16 get_ebda_seg() {
diff --git a/src/config.h b/src/config.h
index 494fe8d..df10bbe 100644
--- a/src/config.h
+++ b/src/config.h
@@ -123,6 +123,8 @@
 #define BUILD_STACK_ADDR          0x7c00
 #define BUILD_S3RESUME_STACK_ADDR 0x1000
 #define BUILD_AP_BOOT_ADDR        0x10000
+#define BUILD_EBDA_MINIMUM        0x90000
+#define BUILD_LOWRAM_END          0xa0000
 #define BUILD_ROM_START           0xc0000
 #define BUILD_BIOS_ADDR           0xf0000
 #define BUILD_BIOS_SIZE           0x10000
diff --git a/src/pmm.c b/src/pmm.c
index 1732c69..6ce6c6f 100644
--- a/src/pmm.c
+++ b/src/pmm.c
@@ -8,13 +8,9 @@
 #include "config.h" // BUILD_BIOS_ADDR
 #include "memmap.h" // find_high_area
 #include "farptr.h" // GET_FARVAR
-#include "biosvar.h" // EBDA_SEGMENT_MINIMUM
+#include "biosvar.h" // GET_BDA
 
 
-/****************************************************************
- * malloc
- ****************************************************************/
-
 #if MODE16
 // The 16bit pmm entry points runs in "big real" mode, and can
 // therefore read/write to the 32bit malloc variables.
@@ -38,6 +34,72 @@
     &ZoneTmpLow, &ZoneLow, &ZoneFSeg, &ZoneTmpHigh, &ZoneHigh
 };
 
+
+/****************************************************************
+ * ebda movement
+ ****************************************************************/
+
+// Move ebda
+static int
+relocate_ebda(u32 newebda, u32 oldebda, u8 ebda_size)
+{
+    u32 lowram = GET_BDA(mem_size_kb) * 1024;
+    if (oldebda != lowram)
+        // EBDA isn't at end of ram - give up.
+        return -1;
+
+    // Do copy
+    if (MODE16)
+        memcpy_far(FLATPTR_TO_SEG(newebda)
+                   , (void*)FLATPTR_TO_OFFSET(newebda)
+                   , FLATPTR_TO_SEG(oldebda)
+                   , (void*)FLATPTR_TO_OFFSET(oldebda)
+                   , ebda_size * 1024);
+    else
+        memmove((void*)newebda, (void*)oldebda, ebda_size * 1024);
+
+    // Update indexes
+    dprintf(1, "ebda moved from %x to %x\n", oldebda, newebda);
+    SET_BDA(mem_size_kb, newebda / 1024);
+    SET_BDA(ebda_seg, FLATPTR_TO_SEG(newebda));
+    return 0;
+}
+
+// Support expanding the ZoneLow dynamically.
+static int
+zonelow_expand(u32 size, u32 align)
+{
+    u16 ebda_seg = get_ebda_seg();
+    u32 ebda_pos = (u32)MAKE_FLATPTR(ebda_seg, 0);
+    u32 bottom = GET_PMMVAR(ZoneLow.bottom);
+    u32 cur = GET_PMMVAR(ZoneLow.cur);
+    u8 ebda_size = GET_EBDA2(ebda_seg, size);
+    u32 ebda_end = ebda_pos + ebda_size * 1024;
+    if (ebda_end != bottom)
+        // Something else is after ebda - can't use any existing space.
+        cur = ebda_end;
+    u32 newcur = ALIGN_DOWN(cur - size, align);
+    u32 newbottom = ALIGN_DOWN(newcur, 1024);
+    u32 newebda = ALIGN_DOWN(newbottom - ebda_size * 1024, 1024);
+    if (newebda < BUILD_EBDA_MINIMUM)
+        // Not enough space.
+        return -1;
+
+    // Move ebda
+    int ret = relocate_ebda(newebda, ebda_pos, ebda_size);
+    if (ret)
+        return ret;
+
+    // Update zone
+    SET_PMMVAR(ZoneLow.bottom, newbottom);
+    return 0;
+}
+
+
+/****************************************************************
+ * zone allocations
+ ****************************************************************/
+
 // Obtain memory from a given zone.
 void *
 zone_malloc(struct zone_s *zone, u32 size, u32 align)
@@ -102,10 +164,10 @@
 
     // Memory under 1Meg.
     ZoneTmpLow.bottom = BUILD_STACK_ADDR;
-    ZoneTmpLow.top = ZoneTmpLow.cur = (u32)MAKE_FLATPTR(EBDA_SEGMENT_MINIMUM, 0);
+    ZoneTmpLow.top = ZoneTmpLow.cur = BUILD_EBDA_MINIMUM;
 
-    // Permanent memory under 1Meg.  XXX - not implemented yet.
-    ZoneLow.bottom = ZoneLow.top = ZoneLow.cur = 0xa0000;
+    // Permanent memory under 1Meg.
+    ZoneLow.bottom = ZoneLow.top = ZoneLow.cur = BUILD_LOWRAM_END;
 
     // Find memory at the top of ram.
     struct e820entry *e = find_high_area(CONFIG_MAX_HIGHTABLE+MALLOC_MIN_ALIGN);
@@ -134,6 +196,10 @@
 
     dumpZones();
 
+    // Reserve more low-mem if needed.
+    u32 endlow = GET_BDA(mem_size_kb)*1024;
+    add_e820(endlow, BUILD_LOWRAM_END-endlow, E820_RESERVED);
+
     // Give back unused high ram.
     u32 giveback = ALIGN_DOWN(ZoneHigh.cur - ZoneHigh.bottom, PAGE_SIZE);
     add_e820(ZoneHigh.bottom, giveback, E820_RAM);
@@ -143,6 +209,17 @@
     memset((void*)ZoneTmpLow.bottom, 0, ZoneTmpLow.top - ZoneTmpLow.bottom);
 }
 
+// Allocate memory from ZoneLow - growing the zone if needed.
+void *
+zone_malloc_low(u32 size, u32 align)
+{
+    void *ret = zone_malloc(&ZoneLow, size, align);
+    if (ret)
+        return ret;
+    zonelow_expand(size, align);
+    return zone_malloc(&ZoneLow, size, align);
+}
+
 
 /****************************************************************
  * pmm allocation
@@ -173,6 +250,12 @@
         return NULL;
     u32 olddata = GET_PMMVAR(zone->cur);
     void *data = zone_malloc(zone, size, align);
+    if (!data && zone == &ZoneLow) {
+        // Try to expand permanent low zone.
+        zonelow_expand(size, align);
+        olddata = GET_PMMVAR(zone->cur);
+        data = zone_malloc(zone, size, align);
+    }
     if (! data) {
         zone_free(ZONEALLOC, info, oldallocdata);
         return NULL;
@@ -241,6 +324,7 @@
 static u32
 pmm_getspace(struct zone_s *zone)
 {
+    // XXX - doesn't account for ZoneLow being able to grow.
     u32 space = GET_PMMVAR(zone->cur) - GET_PMMVAR(zone->bottom);
     if (zone != ZONEALLOC)
         return space;
diff --git a/src/post.c b/src/post.c
index e2569b0..c21b46e 100644
--- a/src/post.c
+++ b/src/post.c
@@ -78,7 +78,7 @@
     memset(bda, 0, sizeof(*bda));
 
     int esize = EBDA_SIZE_START;
-    SET_BDA(mem_size_kb, 640 - esize);
+    SET_BDA(mem_size_kb, BUILD_LOWRAM_END/1024 - esize);
     u16 eseg = EBDA_SEGMENT_START;
     SET_BDA(ebda_seg, eseg);
 
@@ -119,7 +119,7 @@
     }
 
     // Don't declare any memory between 0xa0000 and 0x100000
-    add_e820(0xa0000, 0x50000, E820_HOLE);
+    add_e820(BUILD_LOWRAM_END, BUILD_BIOS_ADDR-BUILD_LOWRAM_END, E820_HOLE);
 
     // Mark known areas as reserved.
     u16 ebda_seg = get_ebda_seg();
diff --git a/src/util.h b/src/util.h
index 7601b48..a74bdec 100644
--- a/src/util.h
+++ b/src/util.h
@@ -260,6 +260,7 @@
 // pmm.c
 extern struct zone_s ZoneLow, ZoneHigh, ZoneFSeg, ZoneTmpLow, ZoneTmpHigh;
 void *zone_malloc(struct zone_s *zone, u32 size, u32 align);
+void *zone_malloc_low(u32 size, u32 align);
 void malloc_setup();
 void malloc_finalize();
 void pmm_setup();
@@ -267,6 +268,9 @@
 // Minimum alignment of malloc'd memory
 #define MALLOC_MIN_ALIGN 16
 // Helper functions for memory allocation.
+static inline void *malloc_low(u32 size) {
+    return zone_malloc_low(size, MALLOC_MIN_ALIGN);
+}
 static inline void *malloc_high(u32 size) {
     return zone_malloc(&ZoneHigh, size, MALLOC_MIN_ALIGN);
 }