Skip to content

Commit

Permalink
Add support for PAE
Browse files Browse the repository at this point in the history
Physical memory is still limited to 32-bit addresses, but systems that
enable PAE should work now.
  • Loading branch information
pwmarcz committed Jan 13, 2022
1 parent 6b9d1f7 commit a1d7d78
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 43 deletions.
2 changes: 1 addition & 1 deletion src/browser/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -1762,7 +1762,7 @@
$("dump_gdt").onclick = debug.dump_gdt_ldt.bind(debug);
$("dump_idt").onclick = debug.dump_idt.bind(debug);
$("dump_regs").onclick = debug.dump_regs.bind(debug);
$("dump_pt").onclick = debug.dump_page_directory.bind(debug);
$("dump_pt").onclick = debug.dump_page_structures.bind(debug);

$("dump_log").onclick = function()
{
Expand Down
1 change: 1 addition & 0 deletions src/const.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ var

/** @const */
var CR0_PG = 1 << 31;
var CR4_PAE = 1 << 5;


// https://github.com/qemu/seabios/blob/14221cd86eadba82255fdc55ed174d401c7a0a04/src/fw/paravirt.c#L205-L219
Expand Down
59 changes: 44 additions & 15 deletions src/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ CPU.prototype.debug_init = function()
debug.dump_state = dump_state;
debug.dump_stack = dump_stack;

debug.dump_page_directory = dump_page_directory;
debug.dump_page_structures = dump_page_structures;
debug.dump_gdt_ldt = dump_gdt_ldt;
debug.dump_idt = dump_idt;

Expand Down Expand Up @@ -115,6 +115,7 @@ CPU.prototype.debug_init = function()
}

return ("mode=" + mode + "/" + op_size + " paging=" + (+((cpu.cr[0] & CR0_PG) !== 0)) +
" pae=" + (+((cpu.cr[4] & CR4_PAE) !== 0)) +
" iopl=" + iopl + " cpl=" + cpl + " if=" + if_ + " cs:eip=" + cs_eip +
" cs_off=" + h(cpu.get_seg_cs() >>> 0, 8) +
" flgs=" + h(cpu.get_eflags() >>> 0, 6) + " (" + flag_string + ")" +
Expand Down Expand Up @@ -297,7 +298,7 @@ CPU.prototype.debug_init = function()
}
}

function load_page_entry(dword_entry, is_directory)
function load_page_entry(dword_entry, pae, is_directory)
{
if(!DEBUG) return;

Expand All @@ -312,7 +313,7 @@ CPU.prototype.debug_init = function()

if(size && !is_directory)
{
address = dword_entry & 0xFFC00000;
address = dword_entry & (pae ? 0xFFE00000 : 0xFFC00000);
}
else
{
Expand All @@ -331,19 +332,46 @@ CPU.prototype.debug_init = function()
};
}

function dump_page_directory()
var dbg_log = console.log.bind(console);

function dump_page_structures() {
var pae = !!(cpu.cr[4] & CR4_PAE);
if (pae)
{
dbg_log("PAE enabled");

for (var i = 0; i < 4; i++) {
var addr = cpu.cr[3] + 8 * i;
var dword = cpu.read32s(addr);
if (dword & 1)
{
dump_page_directory(dword & 0xFFFFF000, true, i << 30);
}
}
}
else
{
dbg_log("PAE disabled");
dump_page_directory(cpu.cr[3], false, 0)
}
}

function dump_page_directory(pd_addr, pae, start)
{
if(!DEBUG) return;

for(var i = 0; i < 1024; i++)
var n = pae ? 512 : 1024;
var entry_size = pae ? 8 : 4;
var pd_shift = pae ? 21 : 22;

for(var i = 0; i < n; i++)
{
var addr = cpu.cr[3] + 4 * i;
var dword = cpu.read32s(addr),
entry = load_page_entry(dword, true);
var addr = pd_addr + i * entry_size,
dword = cpu.read32s(addr),
entry = load_page_entry(dword, pae, true);

if(!entry)
{
dbg_log("Not present: " + h((i << 22) >>> 0, 8));
continue;
}

Expand All @@ -357,20 +385,21 @@ CPU.prototype.debug_init = function()

if(entry.size)
{
dbg_log("=== " + h((i << 22) >>> 0, 8) + " -> " + h(entry.address >>> 0, 8) + " | " + flags);
dbg_log("=== " + h(start + (i << pd_shift) >>> 0, 8) + " -> " +
h(entry.address >>> 0, 8) + " | " + flags);
continue;
}
else
{
dbg_log("=== " + h((i << 22) >>> 0, 8) + " | " + flags);
dbg_log("=== " + h(start + (i << pd_shift) >>> 0, 8) + " | " + flags);
}

for(var j = 0; j < 1024; j++)
for(var j = 0; j < n; j++)
{
var sub_addr = entry.address + 4 * j;
var sub_addr = entry.address + j * entry_size;
dword = cpu.read32s(sub_addr);

var subentry = load_page_entry(dword, false);
var subentry = load_page_entry(dword, pae, false);

if(subentry)
{
Expand All @@ -383,7 +412,7 @@ CPU.prototype.debug_init = function()
flags += subentry.accessed ? "A " : " ";
flags += subentry.dirty ? "Di " : " ";

dbg_log("# " + h((i << 22 | j << 12) >>> 0, 8) + " -> " +
dbg_log("# " + h(start + (i << pd_shift | j << 12) >>> 0, 8) + " -> " +
h(subentry.address, 8) + " | " + flags + " (at " + h(sub_addr, 8) + ")");
}
}
Expand Down
15 changes: 7 additions & 8 deletions src/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,17 @@ v86util.view = function(constructor, memory, offset, length)
*/
function h(n, len)
{
if(!n)
{
var str = "";
}
else
{
var str = n.toString(16);
}
var str = n ? n.toString(16) : "";

return "0x" + v86util.pad0(str.toUpperCase(), len || 1);
}

function h64(hi, lo) {
var str_hi = hi ? hi.toString(16) : "";
var str_lo = lo ? lo.toString(16) : "";
return "0x" + v86util.pad0(str_hi.toUpperCase(), 8) + v86util.pad0(str_lo.toUpperCase(), 8);
}


if(typeof crypto !== "undefined" && crypto.getRandomValues)
{
Expand Down
99 changes: 85 additions & 14 deletions src/rust/cpu/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1797,6 +1797,19 @@ pub unsafe fn do_page_translation(addr: i32, for_writing: bool, user: bool) -> O
}
}

/*
* 32-bit paging:
* - 10 bits PD | 10 bits PT | 12 bits offset
* - 10 bits PD | 22 bits offset (4MB huge page)
*
* PAE paging:
* - 2 bits PDPT | 9 bits PD | 9 bits PT | 12 bits offset
* - 2 bits PDPT | 9 bits PD | 21 bits offset (2MB huge page)
*
* Note that PAE entries are 64-bit, and can describe physical addresses over 32
* bits. However, since we support only 32-bit physical addresses, we ignore the
* high half of the entry.
*/
pub unsafe fn do_page_walk(
addr: i32,
for_writing: bool,
Expand All @@ -1816,23 +1829,35 @@ pub unsafe fn do_page_walk(
else {
profiler::stat_increment(TLB_MISS);

let page_dir_addr = (*cr.offset(3) as u32 >> 2).wrapping_add((page >> 10) as u32) as i32;
let page_dir_entry = read_aligned32(page_dir_addr as u32);
// XXX
let kernel_write_override = !user && 0 == *cr & CR0_WP;
if 0 == page_dir_entry & PAGE_TABLE_PRESENT_MASK {
// to do at this place:
//
// - set cr2 = addr (which caused the page fault)
// - call_interrupt_vector with id 14, error code 0-7 (requires information if read or write)
// - prevent execution of the function that triggered this call
let pae = *cr.offset(4) & CR4_PAE != 0;

let (page_dir_addr, page_dir_entry) =
match walk_page_directory(pae, addr) {
Some((a, e)) => (a, e),
// to do at this place:
//
// - set cr2 = addr (which caused the page fault)
// - call_interrupt_vector with id 14, error code 0-7 (requires information if read or write)
// - prevent execution of the function that triggered this call
None => return Err(PageFault {
addr,
for_writing,
user,
present: false,
}),
};

if page_dir_entry & PAGE_TABLE_PRESENT_MASK == 0 {
return Err(PageFault {
addr,
for_writing,
user,
present: false,
});
}

// XXX
let kernel_write_override = !user && 0 == *cr & CR0_WP;
if page_dir_entry & PAGE_TABLE_RW_MASK == 0 && !kernel_write_override {
can_write = false;
if for_writing {
Expand Down Expand Up @@ -1868,13 +1893,17 @@ pub unsafe fn do_page_walk(
write_aligned32(page_dir_addr as u32, new_page_dir_entry);
}

high = (page_dir_entry as u32 & 0xFFC00000 | (addr & 0x3FF000) as u32) as i32;
high = if pae {
(page_dir_entry as u32 & 0xFFE00000 | (addr & 0x1FF000) as u32) as i32
} else {
(page_dir_entry as u32 & 0xFFC00000 | (addr & 0x3FF000) as u32) as i32
};
global = page_dir_entry & PAGE_TABLE_GLOBAL_MASK == PAGE_TABLE_GLOBAL_MASK
}
else {
let page_table_addr = ((page_dir_entry as u32 & 0xFFFFF000) >> 2)
.wrapping_add((page & 1023) as u32) as i32;
let page_table_entry = read_aligned32(page_table_addr as u32);
let (page_table_addr, page_table_entry) =
walk_page_table(pae, addr, page_dir_entry);

if page_table_entry & PAGE_TABLE_PRESENT_MASK == 0 {
return Err(PageFault {
addr,
Expand All @@ -1883,6 +1912,7 @@ pub unsafe fn do_page_walk(
present: false,
});
}

if page_table_entry & PAGE_TABLE_RW_MASK == 0 && !kernel_write_override {
can_write = false;
if for_writing {
Expand Down Expand Up @@ -1967,6 +1997,47 @@ pub unsafe fn do_page_walk(
return Ok(high);
}

unsafe fn walk_page_directory(pae: bool, addr: i32) -> Option<(i32, i32)> {
if pae {
let pdpt_idx = (addr as u32) >> 30;
let page_dir_idx = ((addr as u32) >> 21) & 0x1FF;

let pdpt_addr = (*cr.offset(3) as u32 >> 2).wrapping_add(pdpt_idx << 1);
let pdpt_entry = read_aligned32(pdpt_addr);
if pdpt_entry & PAGE_TABLE_PRESENT_MASK == 0 {
return None;
}

let page_dir_addr = ((pdpt_entry as u32 & 0xFFFFF000)>> 2).wrapping_add(page_dir_idx << 1);
let page_dir_entry = read_aligned32(page_dir_addr);
return Some((page_dir_addr as i32, page_dir_entry));
}

let page_dir_idx = (addr as u32) >> 22;
let page_dir_addr = (*cr.offset(3) as u32 >> 2).wrapping_add(page_dir_idx);
let page_dir_entry = read_aligned32(page_dir_addr);
return Some((page_dir_addr as i32, page_dir_entry));
}

unsafe fn walk_page_table(
pae: bool,
addr: i32,
page_dir_entry: i32
) -> (i32, i32) {
let page_table = (page_dir_entry as u32 & 0xFFFFF000) >> 2;
if pae {
let page_table_idx = (addr as u32 >> 12) & 0x1FF;
let page_table_addr = page_table.wrapping_add(page_table_idx << 1);
let page_table_entry = read_aligned32(page_table_addr);
return (page_table_addr as i32, page_table_entry);
}

let page_table_idx = (addr as u32 >> 12) & 0x3FF;
let page_table_addr = page_table.wrapping_add(page_table_idx);
let page_table_entry = read_aligned32(page_table_addr);
return (page_table_addr as i32, page_table_entry);
}

#[no_mangle]
pub unsafe fn full_clear_tlb() {
profiler::stat_increment(FULL_CLEAR_TLB);
Expand Down
7 changes: 2 additions & 5 deletions src/rust/cpu/instructions_0f.rs
Original file line number Diff line number Diff line change
Expand Up @@ -798,13 +798,10 @@ pub unsafe fn instr_0F22(r: i32, creg: i32) {
return;
}
else {
if 0 != (*cr.offset(4) ^ data) & (CR4_PGE | CR4_PSE) {
if 0 != (*cr.offset(4) ^ data) & (CR4_PGE | CR4_PSE | CR4_PAE) {
full_clear_tlb();
}
*cr.offset(4) = data;
if 0 != *cr.offset(4) & CR4_PAE {
dbg_assert!(false, "PAE is not supported");
}
}
},
_ => {
Expand Down Expand Up @@ -3177,7 +3174,7 @@ pub unsafe fn instr_0FA2() {
ecx |= 1 << 31
}; // hypervisor
edx = (if true /* have fpu */ { 1 } else { 0 }) | // fpu
vme | 1 << 3 | 1 << 4 | 1 << 5 | // vme, pse, tsc, msr
vme | 1 << 3 | 1 << 4 | 1 << 5 | 1 << 6 | // vme, pse, tsc, msr, pae
1 << 8 | 1 << 11 | 1 << 13 | 1 << 15 | // cx8, sep, pge, cmov
1 << 23 | 1 << 24 | 1 << 25 | 1 << 26; // mmx, fxsr, sse1, sse2

Expand Down
1 change: 1 addition & 0 deletions tests/kvm-unit-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ make -C ../../build/libv86.js
./run.js x86/sieve.flat
./run.js x86/ioapic.flat
./run.js x86/apic.flat
./run.js x86/pae.flat
```

Tests can also be run in browser by going to `?profile=test-$name` (for
Expand Down
1 change: 1 addition & 0 deletions tests/kvm-unit-tests/x86/Makefile.common
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ tests-common = $(TEST_DIR)/vmexit.flat $(TEST_DIR)/tsc.flat \
$(TEST_DIR)/init.flat $(TEST_DIR)/smap.flat \
$(TEST_DIR)/hyperv_synic.flat $(TEST_DIR)/hyperv_stimer.flat \
$(TEST_DIR)/hyperv_connections.flat \
$(TEST_DIR)/pae.flat \

ifdef API
tests-api = api/api-sample api/dirty-log api/dirty-log-perf
Expand Down
Loading

0 comments on commit a1d7d78

Please sign in to comment.