x86: Make e820_remove_range to handle all covered case
Yinghai Lu [Tue, 30 Mar 2010 05:38:29 +0000 (22:38 -0700)]
Rusty found on lguest with trim_bios_range, max_pfn is not right anymore, and
looks e820_remove_range does not work right.

[    0.000000] BIOS-provided physical RAM map:
[    0.000000]  LGUEST: 0000000000000000 - 0000000004000000 (usable)
[    0.000000] Notice: NX (Execute Disable) protection missing in CPU or disabled in BIOS!
[    0.000000] DMI not present or invalid.
[    0.000000] last_pfn = 0x3fa0 max_arch_pfn = 0x100000
[    0.000000] init_memory_mapping: 0000000000000000-0000000003fa0000

root cause is: the e820_remove_range doesn't handle the all covered
case.  e820_remove_range(BIOS_START, BIOS_END - BIOS_START, ...)
produces a bogus range as a result.

Make it match e820_update_range() by handling that case too.

Reported-by: Rusty Russell <rusty@rustcorp.com.au>
Signed-off-by: Yinghai Lu <yinghai@kernel.org>
Tested-by: Rusty Russell <rusty@rustcorp.com.au>
LKML-Reference: <4BB18E55.6090903@kernel.org>
Signed-off-by: H. Peter Anvin <hpa@zytor.com>

arch/x86/kernel/e820.c

index 740b440..7bca3c6 100644 (file)
@@ -519,29 +519,45 @@ u64 __init e820_remove_range(u64 start, u64 size, unsigned old_type,
        printk(KERN_DEBUG "e820 remove range: %016Lx - %016Lx ",
                       (unsigned long long) start,
                       (unsigned long long) end);
-       e820_print_type(old_type);
+       if (checktype)
+               e820_print_type(old_type);
        printk(KERN_CONT "\n");
 
        for (i = 0; i < e820.nr_map; i++) {
                struct e820entry *ei = &e820.map[i];
                u64 final_start, final_end;
+               u64 ei_end;
 
                if (checktype && ei->type != old_type)
                        continue;
+
+               ei_end = ei->addr + ei->size;
                /* totally covered? */
-               if (ei->addr >= start &&
-                   (ei->addr + ei->size) <= (start + size)) {
+               if (ei->addr >= start && ei_end <= end) {
                        real_removed_size += ei->size;
                        memset(ei, 0, sizeof(struct e820entry));
                        continue;
                }
+
+               /* new range is totally covered? */
+               if (ei->addr < start && ei_end > end) {
+                       e820_add_region(end, ei_end - end, ei->type);
+                       ei->size = start - ei->addr;
+                       real_removed_size += size;
+                       continue;
+               }
+
                /* partially covered */
                final_start = max(start, ei->addr);
-               final_end = min(start + size, ei->addr + ei->size);
+               final_end = min(end, ei_end);
                if (final_start >= final_end)
                        continue;
                real_removed_size += final_end - final_start;
 
+               /*
+                * left range could be head or tail, so need to update
+                * size at first.
+                */
                ei->size -= final_end - final_start;
                if (ei->addr < final_start)
                        continue;