mm: Detect bad memory from page_alloc_phys
authorJack Miller <jack@codezen.org>
Sat, 4 Jun 2016 19:45:21 +0000 (14:45 -0500)
committerJack Miller <jack@codezen.org>
Sat, 4 Jun 2016 22:22:35 +0000 (17:22 -0500)
page_alloc_phys has two consumers, page_alloc_to_virtual and the map
functions. In both of these cases, use clear_page to detect whether the
memory is unresponsive, while we're touching each byte anyway.

For page_alloc functions, properly reserve bad pages and retry the
request. For map, since we only get a bad page at a time, we just leak
it.

It could be argued that page_alloc_phys should do this itself instead of
requiring callers to intercept its output, but frankly I'd rather not
complicate the early mem situation and the physical allocator by getting
it a virtual address to work with when it's easier (for now?) to defer
testing to when we have a virtual address on hand.

include/memmap.h
mm/map.c
mm/page_alloc.c

index 074f9ec..7b7626b 100644 (file)
 
 #include <types.h>
 
-static inline void __clear_page(u64 * addr)
+static inline int __clear_page(u64 * addr)
 {
     int i;
 
     addr = (u64 *) ((u64) addr & ~PAGE_MASK);
 
-    for (i = 0; i < (PAGE_SIZE / sizeof(u64)); i++)
+    for (i = 0; i < (PAGE_SIZE / sizeof(u64)); i++) {
+        addr[i] = 0xFFFFFFFFFFFFFFFF;
+        if (addr[i] != 0xFFFFFFFFFFFFFFFF)
+            return -1;
         addr[i] = 0;
+        if (addr[i])
+            return -1;
+    }
+
+    return 0;
 }
 
 #endif
index 93375f0..93f33bd 100644 (file)
--- a/mm/map.c
+++ b/mm/map.c
@@ -48,18 +48,28 @@ static int __map_check(u64 * entry, u64 * target)
     u64 phys = 0;
 
     if (!(*entry & PF_P)) {
-        phys = page_alloc_phys(0);
-        if (phys == 0)
-            return -ENOMEM;
+        /* Loop so we can leak pages that __clear_page determines are unresponsive */
 
-        *entry = (phys | PF_RW | PF_P);
+        /* This seems like it should be the physical page allocator's problem,
+         * but in order for it to check we'd have to make it depend on
+         * early_map with a known address. Considering this is literally the
+         * only consumer of page_alloc_phys outside of the page allocator and
+         * should remain so, instead perform the check here where we already
+         * have a known virtual target address.
+         */
 
-        /* reset_cr3 so the newly mapped page is accessible, zero it, then
-         * reset cr3 again to make sure no crap mappings are in there. */
+        do {
+            phys = page_alloc_phys(0);
+            if (phys == 0)
+                return -ENOMEM;
+
+            *entry = (phys | PF_RW | PF_P);
+
+            reset_cr3();
+        } while(__clear_page(target));
 
         reset_cr3();
-        __clear_page(target);
-        reset_cr3();
+
         return 1;
     }
 
index a4bd3ff..7508146 100644 (file)
@@ -256,23 +256,39 @@ s64 page_alloc_free_phys(u64 phys)
 /* Allocate a page to a given virtual address. */
 /* Expects virtual address to be pre-allocated as well. */
 
+static void __reserve_region(u64 address, u64 pages);
+
 void *page_alloc_to_virtual(u64 virtual, u32 order)
 {
-    u64 physical = page_alloc_phys(order);
-    u64 start_virt = virtual;
+    u64 physical, cur_page;
     u64 end_virt = virtual + (1 << (order + PAGE_SHIFT));
+    u32 try_again;
 
-    if (physical == 0)
-        return NULL;
+    do {
+        try_again = 0;
+        physical = page_alloc_phys(order);
+        if (physical == 0)
+            return NULL;
 
-    while (start_virt < end_virt) {
-        map_page(start_virt, physical);
-        start_virt += PAGE_SIZE;
-        physical += PAGE_SIZE;
-    }
+        map_pages(virtual, physical, (1 << order));
+        cur_page = virtual;
 
-    return (void *)virtual;
+        while (cur_page < end_virt) {
+            if(__clear_page((void *) cur_page)) {
+                unmap_pages(virtual, (1 << order));
+                page_alloc_free_phys(physical);
+                __reserve_region(cur_page, 1);
 
+                try_again = 1;
+                break;
+            }
+
+            cur_page += PAGE_SIZE;
+        }
+
+    } while (try_again);
+
+    return (void *)virtual;
 }
 
 s64 page_alloc_free_from_virtual(u64 virtual)