]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
Fix initrd LZ4 OOM: use page-level allocation instead of kmalloc
authorTulio A M Mendes <[email protected]>
Sun, 19 Apr 2026 01:25:23 +0000 (22:25 -0300)
committerTulio A M Mendes <[email protected]>
Sun, 19 Apr 2026 01:25:23 +0000 (22:25 -0300)
initrd_init used kmalloc() to allocate the decompression buffer for
LZ4-compressed initrd images. The kernel buddy heap is only 8 MB, so
a 4 MB allocation (the default content size) can easily fail due to
fragmentation from earlier allocations, producing:

  [HEAP] OOM: kmalloc failed.
  [INITRD] OOM decompressing LZ4 (4194304 bytes)

This was especially likely with custom initrd images compressed with
lz4 -1 or -9, which can declare larger content sizes.

Replace kmalloc(orig_sz) with initrd_alloc_pages(), which allocates
individual physical pages via pmm_alloc_page() and maps them into a
dedicated virtual region (0xD0800000, above the 8MB heap). This
bypasses the buddy allocator entirely, using the physical memory
manager directly. On OOM, already-mapped pages are rolled back.

Both LZ4 Frame and legacy LZ4B paths are updated.

src/drivers/initrd.c

index 64041a7e9e7ccd9a52867be44f173588aa1601be..b060956233a28a8e713dca6582b568a4d0174a41 100644 (file)
@@ -13,6 +13,8 @@
 #include "console.h"
 #include "errno.h"
 #include "lz4.h"
+#include "pmm.h"
+#include "vmm.h"
 
 #define TAR_BLOCK 512
 
@@ -46,8 +48,40 @@ typedef struct {
     int next_sibling;
 } initrd_entry_t;
 
+/* Virtual address base for initrd decompression buffer.
+ * Placed above the kernel heap (0xD0000000 + 8MB = 0xD0800000)
+ * to avoid collisions. Pages are allocated via pmm_alloc_page()
+ * and mapped one-by-one, bypassing the buddy heap allocator. */
+#define INITRD_DECOMP_BASE 0xD0800000U
+
 static uint32_t initrd_location_base = 0;
 
+/* Allocate a contiguous virtual region of `size` bytes backed by
+ * individual physical pages.  Returns the virtual address, or NULL
+ * on OOM.  Pages are zeroed. */
+static uint8_t* initrd_alloc_pages(uint32_t size) {
+    uint32_t npages = (size + PAGE_SIZE - 1) / PAGE_SIZE;
+    uintptr_t va = INITRD_DECOMP_BASE;
+    for (uint32_t i = 0; i < npages; i++) {
+        void* phys = pmm_alloc_page();
+        if (!phys) {
+            /* Rollback: unmap + free pages already mapped */
+            for (uint32_t j = 0; j < i; j++) {
+                uintptr_t page_va = INITRD_DECOMP_BASE + (uintptr_t)j * PAGE_SIZE;
+                uintptr_t p = vmm_virt_to_phys(page_va);
+                vmm_unmap_page(page_va);
+                if (p) pmm_free_blocks((void*)p, 1);
+            }
+            return NULL;
+        }
+        memset(phys, 0, PAGE_SIZE);
+        vmm_map_page((uint64_t)(uintptr_t)phys, (uint64_t)va,
+                     VMM_FLAG_PRESENT | VMM_FLAG_RW);
+        va += PAGE_SIZE;
+    }
+    return (uint8_t*)INITRD_DECOMP_BASE;
+}
+
 static initrd_entry_t* entries = NULL;
 static fs_node_t* nodes = NULL;
 static int entry_count = 0;
@@ -302,7 +336,7 @@ fs_node_t* initrd_init(uint32_t location, uint32_t size) {
             orig_sz = 4U * 1024U * 1024U;
         }
 
-        decomp_buf = (uint8_t*)kmalloc(orig_sz);
+        decomp_buf = initrd_alloc_pages(orig_sz);
         if (!decomp_buf) {
             kprintf("[INITRD] OOM decompressing LZ4 (%u bytes)\n", orig_sz);
             return NULL;
@@ -311,7 +345,6 @@ fs_node_t* initrd_init(uint32_t location, uint32_t size) {
         int ret = lz4_decompress_frame(raw, size, decomp_buf, orig_sz);
         if (ret < 0) {
             kprintf("[INITRD] LZ4 Frame decompress failed (ret=%d)\n", ret);
-            kfree(decomp_buf);
             return NULL;
         }
 
@@ -324,7 +357,7 @@ fs_node_t* initrd_init(uint32_t location, uint32_t size) {
         uint32_t comp_sz = (uint32_t)raw[8]  | ((uint32_t)raw[9]  << 8) |
                            ((uint32_t)raw[10] << 16) | ((uint32_t)raw[11] << 24);
 
-        decomp_buf = (uint8_t*)kmalloc(orig_sz);
+        decomp_buf = initrd_alloc_pages(orig_sz);
         if (!decomp_buf) {
             kprintf("[INITRD] OOM decompressing LZ4 (%u bytes)\n", orig_sz);
             return NULL;
@@ -335,7 +368,6 @@ fs_node_t* initrd_init(uint32_t location, uint32_t size) {
         if (ret < 0 || (uint32_t)ret != orig_sz) {
             kprintf("[INITRD] LZ4 decompress failed (ret=%d, expected=%u)\n",
                     ret, orig_sz);
-            kfree(decomp_buf);
             return NULL;
         }
 
@@ -349,7 +381,7 @@ fs_node_t* initrd_init(uint32_t location, uint32_t size) {
     entry_count = 0;
 
     int root = entry_alloc();
-    if (root < 0) { kfree(decomp_buf); return NULL; }
+    if (root < 0) return NULL;
     strcpy(entries[root].name, "");
     entries[root].flags = FS_DIRECTORY;
     entries[root].data_offset = 0;