/* * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ FILE_LICENCE ( GPL2_OR_LATER ) .text .arch i386 .code16 #define SMAP 0x534d4150 /* Most documentation refers to the E820 buffer as being 20 bytes, and * the API makes it perfectly legitimate to pass only a 20-byte buffer * and expect to get valid data. However, some morons at ACPI decided * to extend the data structure by adding an extra "extended * attributes" field and by including critical information within this * field, such as whether or not the region is enabled. A caller who * passes in only a 20-byte buffer therefore risks getting very, very * misleading information. * * I have personally witnessed an HP BIOS that returns a value of * 0x0009 in the extended attributes field. If we don't pass this * value through to the caller, 32-bit WinPE will die, usually with a * PAGE_FAULT_IN_NONPAGED_AREA blue screen of death. * * Allow a ridiculously large maximum value (64 bytes) for the E820 * buffer as a guard against insufficiently creative idiots in the * future. */ #define E820MAXSIZE 64 /**************************************************************************** * * Allowed memory windows * * There are two ways to view this list. The first is as a list of * (non-overlapping) allowed memory regions, sorted by increasing * address. The second is as a list of (non-overlapping) hidden * memory regions, again sorted by increasing address. The second * view is offset by half an entry from the first: think about this * for a moment and it should make sense. * * xxx_memory_window is used to indicate an "allowed region" * structure, hidden_xxx_memory is used to indicate a "hidden region" * structure. Each structure is 16 bytes in length. * **************************************************************************** */ .section ".data16", "aw", @progbits .align 16 .globl hidemem_base .globl hidemem_umalloc .globl hidemem_textdata memory_windows: base_memory_window: .long 0x00000000, 0x00000000 /* Start of memory */ hidemem_base: .long 0x000a0000, 0x00000000 /* Changes at runtime */ ext_memory_window: .long 0x000a0000, 0x00000000 /* 640kB mark */ hidemem_umalloc: .long 0xffffffff, 0xffffffff /* Changes at runtime */ .long 0xffffffff, 0xffffffff /* Changes at runtime */ hidemem_textdata: .long 0xffffffff, 0xffffffff /* Changes at runtime */ .long 0xffffffff, 0xffffffff /* Changes at runtime */ .long 0xffffffff, 0xffffffff /* End of memory */ memory_windows_end: /**************************************************************************** * Truncate region to memory window * * Parameters: * %edx:%eax Start of region * %ecx:%ebx Length of region * %si Memory window * Returns: * %edx:%eax Start of windowed region * %ecx:%ebx Length of windowed region **************************************************************************** */ .section ".text16", "ax", @progbits window_region: /* Convert (start,len) to (start, end) */ addl %eax, %ebx adcl %edx, %ecx /* Truncate to window start */ cmpl 4(%si), %edx jne 1f cmpl 0(%si), %eax 1: jae 2f movl 4(%si), %edx movl 0(%si), %eax 2: /* Truncate to window end */ cmpl 12(%si), %ecx jne 1f cmpl 8(%si), %ebx 1: jbe 2f movl 12(%si), %ecx movl 8(%si), %ebx 2: /* Convert (start, end) back to (start, len) */ subl %eax, %ebx sbbl %edx, %ecx /* If length is <0, set length to 0 */ jae 1f xorl %ebx, %ebx xorl %ecx, %ecx ret .size window_region, . - window_region /**************************************************************************** * Patch "memory above 1MB" figure * * Parameters: * %ax Memory above 1MB, in 1kB blocks * Returns: * %ax Modified memory above 1M in 1kB blocks **************************************************************************** */ .section ".text16", "ax", @progbits patch_1m: pushal /* Convert to (start,len) format and call truncate */ xorl %ecx, %ecx movzwl %ax, %ebx shll $10, %ebx xorl %edx, %edx movl $0x100000, %eax movw $ext_memory_window, %si call window_region /* Convert back to "memory above 1MB" format and return via %ax */ pushfw shrl $10, %ebx popfw movw %sp, %bp movw %bx, 28(%bp) popal ret .size patch_1m, . - patch_1m /**************************************************************************** * Patch "memory above 16MB" figure * * Parameters: * %bx Memory above 16MB, in 64kB blocks * Returns: * %bx Modified memory above 16M in 64kB blocks **************************************************************************** */ .section ".text16", "ax", @progbits patch_16m: pushal /* Convert to (start,len) format and call truncate */ xorl %ecx, %ecx shll $16, %ebx xorl %edx, %edx movl $0x1000000, %eax movw $ext_memory_window, %si call window_region /* Convert back to "memory above 16MB" format and return via %bx */ pushfw shrl $16, %ebx popfw movw %sp, %bp movw %bx, 16(%bp) popal ret .size patch_16m, . - patch_16m /**************************************************************************** * Patch "memory between 1MB and 16MB" and "memory above 16MB" figures * * Parameters: * %ax Memory between 1MB and 16MB, in 1kB blocks * %bx Memory above 16MB, in 64kB blocks * Returns: * %ax Modified memory between 1MB and 16MB, in 1kB blocks * %bx Modified memory above 16MB, in 64kB blocks **************************************************************************** */ .section ".text16", "ax", @progbits patch_1m_16m: call patch_1m call patch_16m /* If 1M region is no longer full-length, kill off the 16M region */ cmpw $( 15 * 1024 ), %ax je 1f xorw %bx, %bx 1: ret .size patch_1m_16m, . - patch_1m_16m /**************************************************************************** * Get underlying e820 memory region to underlying_e820 buffer * * Parameters: * As for INT 15,e820 * Returns: * As for INT 15,e820 * * Wraps the underlying INT 15,e820 call so that the continuation * value (%ebx) is a 16-bit simple sequence counter (with the high 16 * bits ignored), and termination is always via CF=1 rather than * %ebx=0. * **************************************************************************** */ .section ".text16", "ax", @progbits get_underlying_e820: /* If the requested region is in the cache, return it */ cmpw %bx, underlying_e820_index jne 2f pushw %di pushw %si movw $underlying_e820_cache, %si cmpl underlying_e820_cache_size, %ecx jbe 1f movl underlying_e820_cache_size, %ecx 1: pushl %ecx rep movsb popl %ecx popw %si popw %di incw %bx movl %edx, %eax clc ret 2: /* If the requested region is earlier than the cached region, * invalidate the cache. */ cmpw %bx, underlying_e820_index jbe 1f movw $0xffff, underlying_e820_index 1: /* If the cache is invalid, reset the underlying %ebx */ cmpw $0xffff, underlying_e820_index jne 1f andl $0, underlying_e820_ebx 1: /* If the cache is valid but the continuation value is zero, * this means that the previous underlying call returned with * %ebx=0. Return with CF=1 in this case. */ cmpw $0xffff, underlying_e820_index je 1f cmpl $0, underlying_e820_ebx jne 1f stc ret 1: /* Get the next region into the cache */ pushl %eax pushl %ebx pushl %ecx pushl %edx pushl %esi /* Some implementations corrupt %esi, so we */ pushl %edi /* preserve %esi, %edi and %ebp to be paranoid */ pushl %ebp pushw %es pushw %ds popw %es movw $underlying_e820_cache, %di cmpl $E820MAXSIZE, %ecx jbe 1f movl $E820MAXSIZE, %ecx 1: movl underlying_e820_ebx, %ebx stc pushfw lcall *%cs:int15_vector popw %es popl %ebp popl %edi popl %esi /* Check for error return from underlying e820 call */ jc 2f /* CF set: error */ cmpl $SMAP, %eax je 3f /* 'SMAP' missing: error */ 2: /* An error occurred: return values returned by underlying e820 call */ stc /* Force CF set if SMAP was missing */ addr32 leal 16(%esp), %esp /* avoid changing other flags */ ret 3: /* No error occurred */ movl %ebx, underlying_e820_ebx movl %ecx, underlying_e820_cache_size popl %edx popl %ecx popl %ebx popl %eax /* Mark cache as containing this result */ incw underlying_e820_index /* Loop until found */ jmp get_underlying_e820 .size get_underlying_e820, . - get_underlying_e820 .section ".data16", "aw", @progbits underlying_e820_index: .word 0xffff /* Initialise to an invalid value */ .size underlying_e820_index, . - underlying_e820_index .section ".bss16", "aw", @nobits underlying_e820_ebx: .long 0 .size underlying_e820_ebx, . - underlying_e820_ebx .section ".bss16", "aw", @nobits underlying_e820_cache: .space E820MAXSIZE .size underlying_e820_cache, . - underlying_e820_cache .section ".bss16", "aw", @nobits underlying_e820_cache_size: .long 0 .size underlying_e820_cache_size, . - underlying_e820_cache_size /**************************************************************************** * Get windowed e820 region, without empty region stripping * * Parameters: * As for INT 15,e820 * Returns: * As for INT 15,e820 * * Wraps the underlying INT 15,e820 call so that each underlying * region is returned N times, windowed to fit within N visible-memory * windows. Termination is always via CF=1. * **************************************************************************** */ .section ".text16", "ax", @progbits get_windowed_e820: /* Preserve registers */ pushl %esi pushw %bp /* Split %ebx into %si:%bx, store original %bx in %bp */ pushl %ebx popw %bp popw %si /* %si == 0 => start of memory_windows list */ testw %si, %si jne 1f movw $memory_windows, %si 1: /* Get (cached) underlying e820 region to buffer */ call get_underlying_e820 jc 99f /* Abort on error */ /* Preserve registers */ pushal /* start => %edx:%eax, len => %ecx:%ebx */ movl %es:0(%di), %eax movl %es:4(%di), %edx movl %es:8(%di), %ebx movl %es:12(%di), %ecx /* Truncate region to current window */ call window_region 1: /* Store modified values in e820 map entry */ movl %eax, %es:0(%di) movl %edx, %es:4(%di) movl %ebx, %es:8(%di) movl %ecx, %es:12(%di) /* Restore registers */ popal /* Derive continuation value for next call */ addw $16, %si cmpw $memory_windows_end, %si jne 1f /* End of memory windows: reset %si and allow %bx to continue */ xorw %si, %si jmp 2f 1: /* More memory windows to go: restore original %bx */ movw %bp, %bx 2: /* Construct %ebx from %si:%bx */ pushw %si pushw %bx popl %ebx 98: /* Clear CF */ clc 99: /* Restore registers and return */ popw %bp popl %esi ret .size get_windowed_e820, . - get_windowed_e820 /**************************************************************************** * Get windowed e820 region, with empty region stripping * * Parameters: * As for INT 15,e820 * Returns: * As for INT 15,e820 * * Wraps the underlying INT 15,e820 call so that each underlying * region is returned up to N times, windowed to fit within N * visible-memory windows. Empty windows are never returned. * Termination is always via CF=1. * **************************************************************************** */ .section ".text16", "ax", @progbits get_nonempty_e820: /* Record entry parameters */ pushl %eax pushl %ecx pushl %edx /* Get next windowed region */ call get_windowed_e820 jc 99f /* abort on error */ /* If region is non-empty, finish here */ cmpl $0, %es:8(%di) jne 98f cmpl $0, %es:12(%di) jne 98f /* Region was empty: restore entry parameters and go to next region */ popl %edx popl %ecx popl %eax jmp get_nonempty_e820 98: /* Clear CF */ clc 99: /* Return values from underlying call */ addr32 leal 12(%esp), %esp /* avoid changing flags */ ret .size get_nonempty_e820, . - get_nonempty_e820 /**************************************************************************** * Get mangled e820 region, with empty region stripping * * Parameters: * As for INT 15,e820 * Returns: * As for INT 15,e820 * * Wraps the underlying INT 15,e820 call so that underlying regions * are windowed to the allowed memory regions. Empty regions are * stripped from the map. Termination is always via %ebx=0. * **************************************************************************** */ .section ".text16", "ax", @progbits get_mangled_e820: /* Get a nonempty region */ call get_nonempty_e820 jc 99f /* Abort on error */ /* Peek ahead to see if there are any further nonempty regions */ pushal pushw %es movw %sp, %bp subw %cx, %sp movl $0xe820, %eax movl $SMAP, %edx pushw %ss popw %es movw %sp, %di call get_nonempty_e820 movw %bp, %sp popw %es popal jnc 99f /* There are further nonempty regions */ /* No futher nonempty regions: zero %ebx and clear CF */ xorl %ebx, %ebx 99: /* Return */ ret .size get_mangled_e820, . - get_mangled_e820 /**************************************************************************** * Set/clear CF on the stack as appropriate, assumes stack is as it should * be immediately before IRET **************************************************************************** */ patch_cf: pushw %bp movw %sp, %bp setc 8(%bp) /* Set/reset CF; clears PF, AF, ZF, SF */ popw %bp ret /**************************************************************************** * INT 15,e820 handler **************************************************************************** */ .section ".text16", "ax", @progbits int15_e820: pushw %ds pushw %cs:rm_ds popw %ds call get_mangled_e820 popw %ds call patch_cf iret .size int15_e820, . - int15_e820 /**************************************************************************** * INT 15,e801 handler **************************************************************************** */ .section ".text16", "ax", @progbits int15_e801: /* Call previous handler */ pushfw lcall *%cs:int15_vector call patch_cf /* Edit result */ pushw %ds pushw %cs:rm_ds popw %ds call patch_1m_16m xchgw %ax, %cx xchgw %bx, %dx call patch_1m_16m xchgw %ax, %cx xchgw %bx, %dx popw %ds iret .size int15_e801, . - int15_e801 /**************************************************************************** * INT 15,88 handler **************************************************************************** */ .section ".text16", "ax", @progbits int15_88: /* Call previous handler */ pushfw lcall *%cs:int15_vector call patch_cf /* Edit result */ pushw %ds pushw %cs:rm_ds popw %ds call patch_1m popw %ds iret .size int15_88, . - int15_88 /**************************************************************************** * INT 15 handler **************************************************************************** */ .section ".text16", "ax", @progbits .globl int15 int15: /* See if we want to intercept this call */ pushfw cmpw $0xe820, %ax jne 1f cmpl $SMAP, %edx jne 1f popfw jmp int15_e820 1: cmpw $0xe801, %ax jne 2f popfw jmp int15_e801 2: cmpb $0x88, %ah jne 3f popfw jmp int15_88 3: popfw ljmp *%cs:int15_vector .size int15, . - int15 .section ".text16.data", "aw", @progbits .globl int15_vector int15_vector: .long 0 .size int15_vector, . - int15_vector