port apic & ioapic to rust

- handle mmap access and port io directly in rust
- call handle_irqs after port/memory writes, rather than from the
  interrupt hw directly; this makes it more obvious that handle_irqs is
  dangerous as it can change control flow

state images produced by this version are not backwards-compatible (older stage images will still be working)
This commit is contained in:
Fabian 2025-08-21 17:46:43 -06:00
parent 244a877989
commit c6c2017ba8
13 changed files with 1161 additions and 1180 deletions

View file

@ -80,7 +80,7 @@ CARGO_FLAGS=$(CARGO_FLAGS_SAFE) -C target-feature=+bulk-memory -C target-feature
CORE_FILES=cjs.js const.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \
dma.js pit.js vga.js ps2.js rtc.js uart.js \
acpi.js apic.js ioapic.js iso9660.js \
acpi.js iso9660.js \
state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js virtio_balloon.js \
bus.js log.js cpu.js \
elf.js kernel.js

View file

@ -1,649 +0,0 @@
// See Intel's System Programming Guide
import { v86 } from "./main.js";
import { LOG_APIC } from "../src/const.js";
import { h, int_log2 } from "./lib.js";
import { dbg_assert, dbg_log, dbg_trace } from "./log.js";
import { IOAPIC_CONFIG_MASKED, IOAPIC_DELIVERY_INIT, IOAPIC_DELIVERY_NMI, IOAPIC_DELIVERY_FIXED } from "./ioapic.js";
// For Types Only
import { CPU } from "./cpu.js";
export const APIC_LOG_VERBOSE = false;
// should probably be kept in sync with TSC_RATE in cpu.rs
const APIC_TIMER_FREQ = 1 * 1000 * 1000;
const APIC_ADDRESS = 0xFEE00000;
const APIC_TIMER_MODE_MASK = 3 << 17;
const APIC_TIMER_MODE_ONE_SHOT = 0;
const APIC_TIMER_MODE_PERIODIC = 1 << 17;
const APIC_TIMER_MODE_TSC = 2 << 17;
export const DELIVERY_MODES = [
"Fixed (0)",
"Lowest Prio (1)",
"SMI (2)",
"Reserved (3)",
"NMI (4)",
"INIT (5)",
"Reserved (6)",
"ExtINT (7)",
];
export const DESTINATION_MODES = ["physical", "logical"];
/**
* @constructor
* @param {CPU} cpu
*/
export function APIC(cpu)
{
/** @type {CPU} */
this.cpu = cpu;
this.apic_id = 0;
this.timer_divider = 0;
this.timer_divider_shift = 1;
this.timer_initial_count = 0;
this.timer_current_count = 0;
this.next_tick = v86.microtick();
this.lvt_timer = IOAPIC_CONFIG_MASKED;
this.lvt_thermal_sensor = IOAPIC_CONFIG_MASKED;
this.lvt_perf_counter = IOAPIC_CONFIG_MASKED;
this.lvt_int0 = IOAPIC_CONFIG_MASKED;
this.lvt_int1 = IOAPIC_CONFIG_MASKED;
this.lvt_error = IOAPIC_CONFIG_MASKED;
this.tpr = 0;
this.icr0 = 0;
this.icr1 = 0;
this.irr = new Int32Array(8);
this.isr = new Int32Array(8);
this.tmr = new Int32Array(8);
this.spurious_vector = 0xFE;
this.destination_format = -1;
this.local_destination = 0;
this.error = 0;
this.read_error = 0;
cpu.io.mmap_register(APIC_ADDRESS, 0x100000,
(addr) =>
{
dbg_log("Unsupported read8 from apic: " + h(addr >>> 0), LOG_APIC);
var off = addr & 3;
addr &= ~3;
return this.read32(addr) >> (off * 8) & 0xFF;
},
(addr, value) =>
{
dbg_log("Unsupported write8 from apic: " + h(addr) + " <- " + h(value), LOG_APIC);
dbg_trace();
dbg_assert(false);
},
(addr) => this.read32(addr),
(addr, value) => this.write32(addr, value)
);
}
APIC.prototype.read32 = function(addr)
{
addr = addr - APIC_ADDRESS | 0;
switch(addr)
{
case 0x20:
dbg_log("APIC read id", LOG_APIC);
return this.apic_id;
case 0x30:
// version
dbg_log("APIC read version", LOG_APIC);
return 0x50014;
case 0x80:
APIC_LOG_VERBOSE && dbg_log("APIC read tpr", LOG_APIC);
return this.tpr;
case 0xD0:
dbg_log("Read local destination", LOG_APIC);
return this.local_destination;
case 0xE0:
dbg_log("Read destination format", LOG_APIC);
return this.destination_format;
case 0xF0:
return this.spurious_vector;
case 0x100:
case 0x110:
case 0x120:
case 0x130:
case 0x140:
case 0x150:
case 0x160:
case 0x170:
var index = addr - 0x100 >> 4;
dbg_log("Read isr " + index + ": " + h(this.isr[index] >>> 0, 8), LOG_APIC);
return this.isr[index];
case 0x180:
case 0x190:
case 0x1A0:
case 0x1B0:
case 0x1C0:
case 0x1D0:
case 0x1E0:
case 0x1F0:
var index = addr - 0x180 >> 4;
dbg_log("Read tmr " + index + ": " + h(this.tmr[index] >>> 0, 8), LOG_APIC);
return this.tmr[index];
case 0x200:
case 0x210:
case 0x220:
case 0x230:
case 0x240:
case 0x250:
case 0x260:
case 0x270:
var index = addr - 0x200 >> 4;
dbg_log("Read irr " + index + ": " + h(this.irr[index] >>> 0, 8), LOG_APIC);
return this.irr[index];
case 0x280:
dbg_log("Read error: " + h(this.read_error >>> 0, 8), LOG_APIC);
return this.read_error;
case 0x300:
APIC_LOG_VERBOSE && dbg_log("APIC read icr0", LOG_APIC);
return this.icr0;
case 0x310:
dbg_log("APIC read icr1", LOG_APIC);
return this.icr1;
case 0x320:
dbg_log("read timer lvt", LOG_APIC);
return this.lvt_timer;
case 0x330:
dbg_log("read lvt thermal sensor", LOG_APIC);
return this.lvt_thermal_sensor;
case 0x340:
dbg_log("read lvt perf counter", LOG_APIC);
return this.lvt_perf_counter;
case 0x350:
dbg_log("read lvt int0", LOG_APIC);
return this.lvt_int0;
case 0x360:
dbg_log("read lvt int1", LOG_APIC);
return this.lvt_int1;
case 0x370:
dbg_log("read lvt error", LOG_APIC);
return this.lvt_error;
case 0x3E0:
// divider
dbg_log("read timer divider", LOG_APIC);
return this.timer_divider;
case 0x380:
dbg_log("read timer initial count", LOG_APIC);
return this.timer_initial_count;
case 0x390:
dbg_log("read timer current count: " + h(this.timer_current_count >>> 0, 8), LOG_APIC);
return this.timer_current_count;
default:
dbg_log("APIC read " + h(addr), LOG_APIC);
dbg_assert(false);
return 0;
}
};
APIC.prototype.write32 = function(addr, value)
{
addr = addr - APIC_ADDRESS | 0;
switch(addr)
{
case 0x20:
dbg_log("APIC write id: " + h(value >>> 8, 8), LOG_APIC);
this.apic_id = value;
break;
case 0x30:
// version
dbg_log("APIC write version: " + h(value >>> 0, 8) + ", ignored", LOG_APIC);
break;
case 0x80:
APIC_LOG_VERBOSE && dbg_log("Set tpr: " + h(value & 0xFF, 2), LOG_APIC);
this.tpr = value & 0xFF;
this.check_vector();
break;
case 0xB0:
var highest_isr = this.highest_isr();
if(highest_isr !== -1)
{
APIC_LOG_VERBOSE && dbg_log("eoi: " + h(value >>> 0, 8) + " for vector " + h(highest_isr), LOG_APIC);
this.register_clear_bit(this.isr, highest_isr);
if(this.register_get_bit(this.tmr, highest_isr))
{
// Send eoi to all IO APICs
this.cpu.devices.ioapic.remote_eoi(highest_isr);
}
this.check_vector();
}
else
{
dbg_log("Bad eoi: No isr set", LOG_APIC);
}
break;
case 0xD0:
dbg_log("Set local destination: " + h(value >>> 0, 8), LOG_APIC);
this.local_destination = value & 0xFF000000;
break;
case 0xE0:
dbg_log("Set destination format: " + h(value >>> 0, 8), LOG_APIC);
this.destination_format = value | 0xFFFFFF;
break;
case 0xF0:
dbg_log("Set spurious vector: " + h(value >>> 0, 8), LOG_APIC);
this.spurious_vector = value;
break;
case 0x280:
// updated readable error register with real error
dbg_log("Write error: " + h(value >>> 0, 8), LOG_APIC);
this.read_error = this.error;
this.error = 0;
break;
case 0x300:
var vector = value & 0xFF;
var delivery_mode = value >> 8 & 7;
var destination_mode = value >> 11 & 1;
var is_level = value >> 15 & 1;
var destination_shorthand = value >> 18 & 3;
var destination = this.icr1 >>> 24;
dbg_log("APIC write icr0: " + h(value, 8) + " vector=" + h(vector, 2) + " " +
"destination_mode=" + DESTINATION_MODES[destination_mode] + " delivery_mode=" + DELIVERY_MODES[delivery_mode] + " " +
"destination_shorthand=" + ["no", "self", "all with self", "all without self"][destination_shorthand], LOG_APIC);
value &= ~(1 << 12);
this.icr0 = value;
if(destination_shorthand === 0)
{
// no shorthand
this.route(vector, delivery_mode, is_level, destination, destination_mode);
}
else if(destination_shorthand === 1)
{
// self
this.deliver(vector, IOAPIC_DELIVERY_FIXED, is_level);
}
else if(destination_shorthand === 2)
{
// all including self
this.deliver(vector, delivery_mode, is_level);
}
else if(destination_shorthand === 3)
{
// all but self
}
else
{
dbg_assert(false);
}
break;
case 0x310:
dbg_log("APIC write icr1: " + h(value >>> 0, 8), LOG_APIC);
this.icr1 = value;
break;
case 0x320:
dbg_log("timer lvt: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_timer = value;
break;
case 0x330:
dbg_log("lvt thermal sensor: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_thermal_sensor = value;
break;
case 0x340:
dbg_log("lvt perf counter: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_perf_counter = value;
break;
case 0x350:
dbg_log("lvt int0: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_int0 = value;
break;
case 0x360:
dbg_log("lvt int1: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_int1 = value;
break;
case 0x370:
dbg_log("lvt error: " + h(value >>> 0, 8), LOG_APIC);
this.lvt_error = value;
break;
case 0x3E0:
dbg_log("timer divider: " + h(value >>> 0, 8), LOG_APIC);
this.timer_divider = value;
var divide_shift = value & 0b11 | (value & 0b1000) >> 1;
this.timer_divider_shift = divide_shift === 0b111 ? 0 : divide_shift + 1;
break;
case 0x380:
dbg_log("timer initial: " + h(value >>> 0, 8), LOG_APIC);
this.timer_initial_count = value >>> 0;
this.timer_current_count = value >>> 0;
this.next_tick = v86.microtick();
this.timer_active = true;
break;
case 0x390:
dbg_log("timer current: " + h(value >>> 0, 8), LOG_APIC);
dbg_assert(false, "read-only register");
break;
default:
dbg_log("APIC write32 " + h(addr) + " <- " + h(value >>> 0, 8), LOG_APIC);
dbg_assert(false);
}
};
APIC.prototype.timer = function(now)
{
if(this.timer_current_count === 0)
{
return 100;
}
const freq = APIC_TIMER_FREQ / (1 << this.timer_divider_shift);
const steps = (now - this.next_tick) * freq >>> 0;
this.next_tick += steps / freq;
this.timer_current_count -= steps;
if(this.timer_current_count <= 0)
{
var mode = this.lvt_timer & APIC_TIMER_MODE_MASK;
if(mode === APIC_TIMER_MODE_PERIODIC)
{
this.timer_current_count = this.timer_current_count % this.timer_initial_count;
if(this.timer_current_count <= 0)
{
this.timer_current_count += this.timer_initial_count;
}
dbg_assert(this.timer_current_count !== 0);
if((this.lvt_timer & IOAPIC_CONFIG_MASKED) === 0)
{
this.deliver(this.lvt_timer & 0xFF, IOAPIC_DELIVERY_FIXED, false);
}
}
else if(mode === APIC_TIMER_MODE_ONE_SHOT)
{
this.timer_current_count = 0;
dbg_log("APIC timer one shot end", LOG_APIC);
if((this.lvt_timer & IOAPIC_CONFIG_MASKED) === 0)
{
this.deliver(this.lvt_timer & 0xFF, IOAPIC_DELIVERY_FIXED, false);
}
}
}
return Math.max(0, this.timer_current_count / freq);
};
APIC.prototype.route = function(vector, mode, is_level, destination, destination_mode)
{
// TODO
this.deliver(vector, mode, is_level);
};
APIC.prototype.deliver = function(vector, mode, is_level)
{
APIC_LOG_VERBOSE && dbg_log("Deliver " + h(vector, 2) + " mode=" + mode + " level=" + is_level, LOG_APIC);
if(mode === IOAPIC_DELIVERY_INIT)
{
// TODO
return;
}
if(mode === IOAPIC_DELIVERY_NMI)
{
// TODO
return;
}
if(vector < 0x10 || vector === 0xFF)
{
dbg_assert(false, "TODO: Invalid vector");
}
if(this.register_get_bit(this.irr, vector))
{
dbg_log("Not delivered: irr already set, vector=" + h(vector, 2), LOG_APIC);
return;
}
this.register_set_bit(this.irr, vector);
if(is_level)
{
this.register_set_bit(this.tmr, vector);
}
else
{
this.register_clear_bit(this.tmr, vector);
}
this.check_vector();
};
APIC.prototype.highest_irr = function()
{
var highest = this.register_get_highest_bit(this.irr);
dbg_assert(highest !== 0xFF);
dbg_assert(highest >= 0x10 || highest === -1);
return highest;
};
APIC.prototype.highest_isr = function()
{
var highest = this.register_get_highest_bit(this.isr);
dbg_assert(highest !== 0xFF);
dbg_assert(highest >= 0x10 || highest === -1);
return highest;
};
APIC.prototype.check_vector = function()
{
var highest_irr = this.highest_irr();
if(highest_irr === -1)
{
return;
}
var highest_isr = this.highest_isr();
if(highest_isr >= highest_irr)
{
APIC_LOG_VERBOSE && dbg_log("Higher isr, isr=" + h(highest_isr) + " irr=" + h(highest_irr), LOG_APIC);
return;
}
if((highest_irr & 0xF0) <= (this.tpr & 0xF0))
{
APIC_LOG_VERBOSE && dbg_log("Higher tpr, tpr=" + h(this.tpr & 0xF0) + " irr=" + h(highest_irr), LOG_APIC);
return;
}
this.cpu.handle_irqs();
};
APIC.prototype.acknowledge_irq = function()
{
var highest_irr = this.highest_irr();
if(highest_irr === -1)
{
//dbg_log("Spurious", LOG_APIC);
return -1;
}
var highest_isr = this.highest_isr();
if(highest_isr >= highest_irr)
{
APIC_LOG_VERBOSE && dbg_log("Higher isr, isr=" + h(highest_isr) + " irr=" + h(highest_irr), LOG_APIC);
return -1;
}
if((highest_irr & 0xF0) <= (this.tpr & 0xF0))
{
APIC_LOG_VERBOSE && dbg_log("Higher tpr, tpr=" + h(this.tpr & 0xF0) + " irr=" + h(highest_irr), LOG_APIC);
return -1;
}
this.register_clear_bit(this.irr, highest_irr);
this.register_set_bit(this.isr, highest_irr);
APIC_LOG_VERBOSE && dbg_log("Calling vector " + h(highest_irr), LOG_APIC);
this.check_vector();
return highest_irr;
};
APIC.prototype.get_state = function()
{
var state = [];
state[0] = this.apic_id;
state[1] = this.timer_divider;
state[2] = this.timer_divider_shift;
state[3] = this.timer_initial_count;
state[4] = this.timer_current_count;
state[5] = this.next_tick;
state[6] = this.lvt_timer;
state[7] = this.lvt_perf_counter;
state[8] = this.lvt_int0;
state[9] = this.lvt_int1;
state[10] = this.lvt_error;
state[11] = this.tpr;
state[12] = this.icr0;
state[13] = this.icr1;
state[14] = this.irr;
state[15] = this.isr;
state[16] = this.tmr;
state[17] = this.spurious_vector;
state[18] = this.destination_format;
state[19] = this.local_destination;
state[20] = this.error;
state[21] = this.read_error;
state[22] = this.lvt_thermal_sensor;
return state;
};
APIC.prototype.set_state = function(state)
{
this.apic_id = state[0];
this.timer_divider = state[1];
this.timer_divider_shift = state[2];
this.timer_initial_count = state[3];
this.timer_current_count = state[4];
this.next_tick = state[5];
this.lvt_timer = state[6];
this.lvt_perf_counter = state[7];
this.lvt_int0 = state[8];
this.lvt_int1 = state[9];
this.lvt_error = state[10];
this.tpr = state[11];
this.icr0 = state[12];
this.icr1 = state[13];
this.irr = state[14];
this.isr = state[15];
this.tmr = state[16];
this.spurious_vector = state[17];
this.destination_format = state[18];
this.local_destination = state[19];
this.error = state[20];
this.read_error = state[21];
this.lvt_thermal_sensor = state[22] || IOAPIC_CONFIG_MASKED;
};
// functions operating on 256-bit registers (for irr, isr, tmr)
APIC.prototype.register_get_bit = function(v, bit)
{
dbg_assert(bit >= 0 && bit < 256);
return v[bit >> 5] >> (bit & 31) & 1;
};
APIC.prototype.register_set_bit = function(v, bit)
{
dbg_assert(bit >= 0 && bit < 256);
v[bit >> 5] |= 1 << (bit & 31);
};
APIC.prototype.register_clear_bit = function(v, bit)
{
dbg_assert(bit >= 0 && bit < 256);
v[bit >> 5] &= ~(1 << (bit & 31));
};
APIC.prototype.register_get_highest_bit = function(v)
{
for(var i = 7; i >= 0; i--)
{
var word = v[i];
if(word)
{
return int_log2(word >>> 0) | i << 5;
}
}
return -1;
};

View file

@ -67,7 +67,6 @@ export function V86(options)
"abort": function() { dbg_assert(false); },
"microtick": v86.microtick,
"get_rand_int": function() { return get_rand_int(); },
"apic_acknowledge_irq": function() { return cpu.devices.apic.acknowledge_irq(); },
"stop_idling": function() { return cpu.stop_idling(); },
"io_port_read8": function(addr) { return cpu.io.port_read8(addr); },
@ -637,8 +636,7 @@ V86.prototype.zstd_decompress_worker = async function(decompressed_size, src)
{
const env = Object.fromEntries([
"cpu_exception_hook", "run_hardware_timers",
"cpu_event_halt", "microtick", "get_rand_int",
"apic_acknowledge_irq", "stop_idling",
"cpu_event_halt", "microtick", "get_rand_int", "stop_idling",
"io_port_read8", "io_port_read16", "io_port_read32",
"io_port_write8", "io_port_write16", "io_port_write32",
"mmap_read8", "mmap_read16", "mmap_read32",

View file

@ -15,8 +15,6 @@ import { h, view, pads, Bitmap, dump_file } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
import { SB16 } from "./sb16.js";
import { IOAPIC } from "./ioapic.js";
import { APIC } from "./apic.js";
import { ACPI } from "./acpi.js";
import { PIT } from "./pit.js";
import { DMA } from "./dma.js";
@ -407,8 +405,10 @@ CPU.prototype.wasm_patch = function()
this.set_cpuid_level = get_import("set_cpuid_level");
this.pic_set_irq = get_import("pic_set_irq");
this.pic_clear_irq = get_import("pic_clear_irq");
this.device_raise_irq = get_import("device_raise_irq");
this.device_lower_irq = get_import("device_lower_irq");
this.apic_timer = get_import("apic_timer");
if(DEBUG)
{
@ -430,27 +430,14 @@ CPU.prototype.wasm_patch = function()
this.get_pic_addr_master = get_import("get_pic_addr_master");
this.get_pic_addr_slave = get_import("get_pic_addr_slave");
this.get_apic_addr = get_import("get_apic_addr");
this.get_ioapic_addr = get_import("get_ioapic_addr");
this.zstd_create_ctx = get_import("zstd_create_ctx");
this.zstd_get_src_ptr = get_import("zstd_get_src_ptr");
this.zstd_free_ctx = get_import("zstd_free_ctx");
this.zstd_read = get_import("zstd_read");
this.zstd_read_free = get_import("zstd_read_free");
this.port20_read = get_import("port20_read");
this.port21_read = get_import("port21_read");
this.portA0_read = get_import("portA0_read");
this.portA1_read = get_import("portA1_read");
this.port20_write = get_import("port20_write");
this.port21_write = get_import("port21_write");
this.portA0_write = get_import("portA0_write");
this.portA1_write = get_import("portA1_write");
this.port4D0_read = get_import("port4D0_read");
this.port4D1_read = get_import("port4D1_read");
this.port4D0_write = get_import("port4D0_write");
this.port4D1_write = get_import("port4D1_write");
};
CPU.prototype.jit_force_generate = function(addr)
@ -525,7 +512,7 @@ CPU.prototype.get_state = function()
state[43] = this.current_tsc;
state[45] = this.devices.virtio_9p;
state[46] = this.devices.apic;
state[46] = this.get_state_apic();
state[47] = this.devices.rtc;
state[48] = this.devices.pci;
state[49] = this.devices.dma;
@ -559,7 +546,7 @@ CPU.prototype.get_state = function()
state[62] = this.fw_value;
state[63] = this.devices.ioapic;
state[63] = this.get_state_ioapic();
state[64] = this.tss_size_32[0];
@ -629,6 +616,18 @@ CPU.prototype.get_state_pic = function()
return state;
};
CPU.prototype.get_state_apic = function()
{
const APIC_STRUCT_SIZE = 4 * 46; // keep in sync with apic.rs
return new Uint8Array(this.wasm_memory.buffer, this.get_apic_addr(), APIC_STRUCT_SIZE);
};
CPU.prototype.get_state_ioapic = function()
{
const IOAPIC_STRUCT_SIZE = 4 * 52; // keep in sync with ioapic.rs
return new Uint8Array(this.wasm_memory.buffer, this.get_ioapic_addr(), IOAPIC_STRUCT_SIZE);
};
CPU.prototype.set_state = function(state)
{
this.memory_size[0] = state[0];
@ -695,7 +694,7 @@ CPU.prototype.set_state = function(state)
this.set_tsc(state[43][0], state[43][1]);
this.devices.virtio_9p && this.devices.virtio_9p.set_state(state[45]);
this.devices.apic && this.devices.apic.set_state(state[46]);
state[46] && this.set_state_apic(state[46]);
this.devices.rtc && this.devices.rtc.set_state(state[47]);
this.devices.dma && this.devices.dma.set_state(state[49]);
this.devices.acpi && this.devices.acpi.set_state(state[50]);
@ -744,7 +743,7 @@ CPU.prototype.set_state = function(state)
this.fw_value = state[62];
this.devices.ioapic && this.devices.ioapic.set_state(state[63]);
state[63] && this.set_state_ioapic(state[63]);
this.tss_size_32[0] = state[64];
@ -809,6 +808,75 @@ CPU.prototype.set_state_pic = function(state)
pic_slave[12] = state_slave[12]; // special_mask_mode (undefined in old state images)
};
CPU.prototype.set_state_apic = function(state)
{
const APIC_STRUCT_SIZE = 4 * 46; // keep in sync with apic.rs
const IOAPIC_CONFIG_MASKED = 1 << 16;
if(state instanceof Array)
{
// old js state image; delete this code path when the state version changes
const apic = new Int32Array(this.wasm_memory.buffer, this.get_apic_addr(), APIC_STRUCT_SIZE >> 2);
apic[0] = state[0]; // apic_id
apic[1] = state[1]; // timer_divier
apic[2] = state[2]; // timer_divider_shift
apic[3] = state[3]; // timer_initial_count
apic[4] = state[4]; // timer_current_count
// skip next_tick (in js: state[4]; in rust: apic[5] and apic[6])
apic[7] = state[6]; // lvt_timer
apic[8] = state[7]; // lvt_perf_counter
apic[9] = state[8]; // lvt_int0
apic[10] = state[9]; // lvt_int1
apic[11] = state[10]; // lvt_error
apic[12] = state[11]; // tpr
apic[13] = state[12]; // icr0
apic[14] = state[13]; // icr1
apic.set(state[15], 16); // irr
apic.set(state[15], 24); // isr
apic.set(state[16], 32); // tmr
apic[40] = state[17]; // spurious_vector
apic[41] = state[18]; // destination_format
apic[42] = state[19]; // local_destination
apic[43] = state[20]; // error
apic[44] = state[21]; // read_error
apic[45] = state[22] || IOAPIC_CONFIG_MASKED; // lvt_thermal_sensor
}
else
{
const apic = new Uint8Array(this.wasm_memory.buffer, this.get_apic_addr(), APIC_STRUCT_SIZE);
dbg_assert(state instanceof Uint8Array);
dbg_assert(state.length === apic.length); // later versions might need to handle state upgrades here
apic.set(state);
}
};
CPU.prototype.set_state_ioapic = function(state)
{
const IOAPIC_STRUCT_SIZE = 4 * 52; // keep in sync with ioapic.rs
if(state instanceof Array)
{
// old js state image; delete this code path when the state version changes
dbg_assert(state[0].length === 24);
dbg_assert(state[1].length === 24);
dbg_assert(state.length === 6);
const ioapic = new Int32Array(this.wasm_memory.buffer, this.get_ioapic_addr(), IOAPIC_STRUCT_SIZE >> 2);
ioapic.set(state[0], 0); // ioredtbl_config
ioapic.set(state[1], 24); // ioredtbl_destination
ioapic[48] = state[2]; // ioregsel
ioapic[49] = state[3]; // ioapic_id
ioapic[50] = state[4]; // irr
ioapic[51] = state[5]; // irq_value
}
else
{
const ioapic = new Uint8Array(this.wasm_memory.buffer, this.get_ioapic_addr(), IOAPIC_STRUCT_SIZE);
dbg_assert(state instanceof Uint8Array);
dbg_assert(state.length === ioapic.length); // later versions might need to handle state upgrades here
ioapic.set(state);
}
};
CPU.prototype.pack_memory = function()
{
dbg_assert((this.mem8.length & 0xFFF) === 0);
@ -1086,21 +1154,6 @@ CPU.prototype.init = function(settings, device_bus)
io.register_write(0xE9, this, function(out_byte) {});
}
io.register_read(0x20, this, this.port20_read);
io.register_read(0x21, this, this.port21_read);
io.register_read(0xA0, this, this.portA0_read);
io.register_read(0xA1, this, this.portA1_read);
io.register_write(0x20, this, this.port20_write);
io.register_write(0x21, this, this.port21_write);
io.register_write(0xA0, this, this.portA0_write);
io.register_write(0xA1, this, this.portA1_write);
io.register_read(0x4D0, this, this.port4D0_read);
io.register_read(0x4D1, this, this.port4D1_read);
io.register_write(0x4D0, this, this.port4D0_write);
io.register_write(0x4D1, this, this.port4D1_write);
this.devices = {};
// TODO: Make this more configurable
@ -1110,8 +1163,6 @@ CPU.prototype.init = function(settings, device_bus)
if(this.acpi_enabled[0])
{
this.devices.ioapic = new IOAPIC(this);
this.devices.apic = new APIC(this);
this.devices.acpi = new ACPI(this);
}
@ -1854,33 +1905,12 @@ CPU.prototype.run_hardware_timers = function(acpi_enabled, now)
if(acpi_enabled)
{
acpi_time = this.devices.acpi.timer(now);
apic_time = this.devices.apic.timer(now);
apic_time = this.apic_timer(now);
}
return Math.min(pit_time, rtc_time, acpi_time, apic_time);
};
CPU.prototype.device_raise_irq = function(i)
{
dbg_assert(arguments.length === 1);
this.pic_set_irq(i);
if(this.devices.ioapic)
{
this.devices.ioapic.set_irq(i);
}
};
CPU.prototype.device_lower_irq = function(i)
{
this.pic_clear_irq(i);
if(this.devices.ioapic)
{
this.devices.ioapic.clear_irq(i);
}
};
CPU.prototype.debug_init = function()
{
if(!DEBUG) return;

View file

@ -1,353 +0,0 @@
// http://download.intel.com/design/chipsets/datashts/29056601.pdf
import { LOG_APIC, MMAP_BLOCK_SIZE } from "./const.js";
import { h } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
import { DELIVERY_MODES, DESTINATION_MODES, APIC_LOG_VERBOSE } from "./apic.js";
// For Types Only
import { CPU } from "./cpu.js";
export const IOAPIC_ADDRESS = 0xFEC00000;
export const IOREGSEL = 0;
export const IOWIN = 0x10;
export const IOAPIC_IRQ_COUNT = 24;
export const IOAPIC_ID = 0; // must match value in seabios
export const IOAPIC_CONFIG_TRIGGER_MODE_LEVEL = 1 << 15;
export const IOAPIC_CONFIG_MASKED = 1 << 16;
export const IOAPIC_CONFIG_DELIVS = 1 << 12;
export const IOAPIC_CONFIG_REMOTE_IRR = 1 << 14;
export const IOAPIC_CONFIG_READONLY_MASK = IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000;
export const IOAPIC_DELIVERY_FIXED = 0;
export const IOAPIC_DELIVERY_LOWEST_PRIORITY = 1;
export const IOAPIC_DELIVERY_NMI = 4;
export const IOAPIC_DELIVERY_INIT = 5;
/**
* @constructor
* @param {CPU} cpu
*/
export function IOAPIC(cpu)
{
/** @type {CPU} */
this.cpu = cpu;
this.ioredtbl_config = new Int32Array(IOAPIC_IRQ_COUNT);
this.ioredtbl_destination = new Int32Array(IOAPIC_IRQ_COUNT);
for(var i = 0; i < this.ioredtbl_config.length; i++)
{
// disable interrupts
this.ioredtbl_config[i] = IOAPIC_CONFIG_MASKED;
}
// IOAPIC register selection
this.ioregsel = 0;
this.ioapic_id = IOAPIC_ID;
this.irr = 0;
this.irq_value = 0;
dbg_assert(MMAP_BLOCK_SIZE >= 0x20);
cpu.io.mmap_register(IOAPIC_ADDRESS, MMAP_BLOCK_SIZE,
(addr) =>
{
addr = addr - IOAPIC_ADDRESS | 0;
if(addr >= IOWIN && addr < IOWIN + 4)
{
const byte = addr - IOWIN;
dbg_log("ioapic read8 byte " + byte + " " + h(this.ioregsel), LOG_APIC);
return this.read(this.ioregsel) >> (8 * byte) & 0xFF;
}
else
{
dbg_log("Unexpected IOAPIC register read: " + h(addr >>> 0), LOG_APIC);
dbg_assert(false);
return 0;
}
},
(addr, value) =>
{
dbg_assert(false, "unsupported write8 from ioapic: " + h(addr >>> 0));
},
(addr) =>
{
addr = addr - IOAPIC_ADDRESS | 0;
if(addr === IOREGSEL)
{
return this.ioregsel;
}
else if(addr === IOWIN)
{
return this.read(this.ioregsel);
}
else
{
dbg_log("Unexpected IOAPIC register read: " + h(addr >>> 0), LOG_APIC);
dbg_assert(false);
return 0;
}
},
(addr, value) =>
{
addr = addr - IOAPIC_ADDRESS | 0;
if(addr === IOREGSEL)
{
this.ioregsel = value;
}
else if(addr === IOWIN)
{
this.write(this.ioregsel, value);
}
else
{
dbg_log("Unexpected IOAPIC register write: " + h(addr >>> 0) + " <- " + h(value >>> 0, 8), LOG_APIC);
dbg_assert(false);
}
});
}
IOAPIC.prototype.remote_eoi = function(vector)
{
for(var i = 0; i < IOAPIC_IRQ_COUNT; i++)
{
var config = this.ioredtbl_config[i];
if((config & 0xFF) === vector && (config & IOAPIC_CONFIG_REMOTE_IRR))
{
dbg_log("Clear remote IRR for irq=" + h(i), LOG_APIC);
this.ioredtbl_config[i] &= ~IOAPIC_CONFIG_REMOTE_IRR;
this.check_irq(i);
}
}
};
IOAPIC.prototype.check_irq = function(irq)
{
var mask = 1 << irq;
if((this.irr & mask) === 0)
{
return;
}
var config = this.ioredtbl_config[irq];
if((config & IOAPIC_CONFIG_MASKED) === 0)
{
var delivery_mode = config >> 8 & 7;
var destination_mode = config >> 11 & 1;
var vector = config & 0xFF;
var destination = this.ioredtbl_destination[irq] >>> 24;
var is_level = (config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL) === IOAPIC_CONFIG_TRIGGER_MODE_LEVEL;
if((config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL) === 0)
{
this.irr &= ~mask;
}
else
{
this.ioredtbl_config[irq] |= IOAPIC_CONFIG_REMOTE_IRR;
if(config & IOAPIC_CONFIG_REMOTE_IRR)
{
dbg_log("No route: level interrupt and remote IRR still set", LOG_APIC);
return;
}
}
if(delivery_mode === IOAPIC_DELIVERY_FIXED || delivery_mode === IOAPIC_DELIVERY_LOWEST_PRIORITY)
{
this.cpu.devices.apic.route(vector, delivery_mode, is_level, destination, destination_mode);
}
else
{
dbg_assert(false, "TODO");
}
this.ioredtbl_config[irq] &= ~IOAPIC_CONFIG_DELIVS;
}
};
IOAPIC.prototype.set_irq = function(i)
{
if(i >= IOAPIC_IRQ_COUNT)
{
dbg_assert(false, "Bad irq: " + i, LOG_APIC);
return;
}
var mask = 1 << i;
if((this.irq_value & mask) === 0)
{
APIC_LOG_VERBOSE && dbg_log("apic set irq " + i, LOG_APIC);
this.irq_value |= mask;
var config = this.ioredtbl_config[i];
if((config & (IOAPIC_CONFIG_TRIGGER_MODE_LEVEL|IOAPIC_CONFIG_MASKED)) ===
IOAPIC_CONFIG_MASKED)
{
// edge triggered and masked
return;
}
this.irr |= mask;
this.check_irq(i);
}
};
IOAPIC.prototype.clear_irq = function(i)
{
if(i >= IOAPIC_IRQ_COUNT)
{
dbg_assert(false, "Bad irq: " + i, LOG_APIC);
return;
}
var mask = 1 << i;
if((this.irq_value & mask) === mask)
{
this.irq_value &= ~mask;
var config = this.ioredtbl_config[i];
if(config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL)
{
this.irr &= ~mask;
}
}
};
IOAPIC.prototype.read = function(reg)
{
if(reg === 0)
{
dbg_log("IOAPIC Read id", LOG_APIC);
return this.ioapic_id << 24;
}
else if(reg === 1)
{
dbg_log("IOAPIC Read version", LOG_APIC);
return 0x11 | IOAPIC_IRQ_COUNT - 1 << 16;
}
else if(reg === 2)
{
dbg_log("IOAPIC Read arbitration id", LOG_APIC);
return this.ioapic_id << 24;
}
else if(reg >= 0x10 && reg < 0x10 + 2 * IOAPIC_IRQ_COUNT)
{
var irq = reg - 0x10 >> 1;
var index = reg & 1;
if(index)
{
var value = this.ioredtbl_destination[irq];
dbg_log("IOAPIC Read destination irq=" + h(irq) + " -> " + h(value, 8), LOG_APIC);
}
else
{
var value = this.ioredtbl_config[irq];
dbg_log("IOAPIC Read config irq=" + h(irq) + " -> " + h(value, 8), LOG_APIC);
}
return value;
}
else
{
dbg_log("IOAPIC register read outside of range " + h(reg), LOG_APIC);
dbg_assert(false);
return 0;
}
};
IOAPIC.prototype.write = function(reg, value)
{
//dbg_log("IOAPIC write " + h(reg) + " <- " + h(value, 8), LOG_APIC);
if(reg === 0)
{
this.ioapic_id = value >>> 24 & 0x0F;
}
else if(reg === 1 || reg === 2)
{
dbg_log("Invalid write: " + reg, LOG_APIC);
}
else if(reg >= 0x10 && reg < 0x10 + 2 * IOAPIC_IRQ_COUNT)
{
var irq = reg - 0x10 >> 1;
var index = reg & 1;
if(index)
{
this.ioredtbl_destination[irq] = value & 0xFF000000;
dbg_log("Write destination " + h(value >>> 0, 8) + " irq=" + h(irq) + " dest=" + h(value >>> 24, 2), LOG_APIC);
}
else
{
var old_value = this.ioredtbl_config[irq];
this.ioredtbl_config[irq] = value & ~IOAPIC_CONFIG_READONLY_MASK | old_value & IOAPIC_CONFIG_READONLY_MASK;
var vector = value & 0xFF;
var delivery_mode = value >> 8 & 7;
var destination_mode = value >> 11 & 1;
var is_level = value >> 15 & 1;
var disabled = value >> 16 & 1;
dbg_log("Write config " + h(value >>> 0, 8) +
" irq=" + h(irq) +
" vector=" + h(vector, 2) +
" deliverymode=" + DELIVERY_MODES[delivery_mode] +
" destmode=" + DESTINATION_MODES[destination_mode] +
" is_level=" + is_level +
" disabled=" + disabled, LOG_APIC);
this.check_irq(irq);
}
}
else
{
dbg_log("IOAPIC register write outside of range " + h(reg) + ": " + h(value >>> 0, 8), LOG_APIC);
dbg_assert(false);
}
};
IOAPIC.prototype.get_state = function()
{
var state = [];
state[0] = this.ioredtbl_config;
state[1] = this.ioredtbl_destination;
state[2] = this.ioregsel;
state[3] = this.ioapic_id;
state[4] = this.irr;
state[5] = this.irq_value;
return state;
};
IOAPIC.prototype.set_state = function(state)
{
this.ioredtbl_config = state[0];
this.ioredtbl_destination = state[1];
this.ioregsel = state[2];
this.ioapic_id = state[3];
this.irr = state[4];
this.irq_value = state[5];
};

597
src/rust/cpu/apic.rs Normal file
View file

@ -0,0 +1,597 @@
// See Intel's System Programming Guide
use crate::cpu::{cpu::js, global_pointers::acpi_enabled, ioapic};
use std::sync::{Mutex, MutexGuard};
const APIC_LOG_VERBOSE: bool = false;
// should probably be kept in sync with TSC_RATE in cpu.rs
const APIC_TIMER_FREQ: f64 = 1.0 * 1000.0 * 1000.0;
const APIC_TIMER_MODE_MASK: u32 = 3 << 17;
const APIC_TIMER_MODE_ONE_SHOT: u32 = 0;
const APIC_TIMER_MODE_PERIODIC: u32 = 1 << 17;
const _APIC_TIMER_MODE_TSC: u32 = 2 << 17;
const DELIVERY_MODES: [&str; 8] = [
"Fixed (0)",
"Lowest Prio (1)",
"SMI (2)",
"Reserved (3)",
"NMI (4)",
"INIT (5)",
"Reserved (6)",
"ExtINT (7)",
];
const DESTINATION_MODES: [&str; 2] = ["physical", "logical"];
const IOAPIC_CONFIG_MASKED: u32 = 0x10000;
const IOAPIC_DELIVERY_INIT: u8 = 5;
const IOAPIC_DELIVERY_NMI: u8 = 4;
const IOAPIC_DELIVERY_FIXED: u8 = 0;
// keep in sync with cpu.js
#[allow(dead_code)]
const APIC_STRUCT_SIZE: usize = 4 * 46;
// Note: JavaScript (cpu.get_state_apic) depens on this layout
const _: () = assert!(std::mem::offset_of!(Apic, icr0) == 14 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, irr) == 16 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, isr) == 24 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, tmr) == 32 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, spurious_vector) == 40 * 4);
const _: () = assert!(std::mem::offset_of!(Apic, lvt_thermal_sensor) == 45 * 4);
const _: () = assert!(std::mem::size_of::<Apic>() == APIC_STRUCT_SIZE);
#[repr(C)]
pub struct Apic {
apic_id: u32,
timer_divider: u32,
timer_divider_shift: u32,
timer_initial_count: u32,
timer_current_count: u32,
next_tick: f64,
lvt_timer: u32,
lvt_perf_counter: u32,
lvt_int0: u32,
lvt_int1: u32,
lvt_error: u32,
tpr: u32,
icr0: u32,
icr1: u32,
irr: [u32; 8],
isr: [u32; 8],
tmr: [u32; 8],
spurious_vector: u32,
destination_format: u32,
local_destination: u32,
error: u32,
read_error: u32,
lvt_thermal_sensor: u32,
}
static APIC: Mutex<Apic> = Mutex::new(Apic {
apic_id: 0,
timer_divider: 0,
timer_divider_shift: 1,
timer_initial_count: 0,
timer_current_count: 0,
next_tick: 0.0,
lvt_timer: IOAPIC_CONFIG_MASKED,
lvt_thermal_sensor: IOAPIC_CONFIG_MASKED,
lvt_perf_counter: IOAPIC_CONFIG_MASKED,
lvt_int0: IOAPIC_CONFIG_MASKED,
lvt_int1: IOAPIC_CONFIG_MASKED,
lvt_error: IOAPIC_CONFIG_MASKED,
tpr: 0,
icr0: 0,
icr1: 0,
irr: [0; 8],
isr: [0; 8],
tmr: [0; 8],
spurious_vector: 0xFE,
destination_format: !0,
local_destination: 0,
error: 0,
read_error: 0,
});
pub fn get_apic() -> MutexGuard<'static, Apic> { APIC.try_lock().unwrap() }
#[no_mangle]
pub fn get_apic_addr() -> u32 { &raw mut *get_apic() as u32 }
pub fn read32(addr: u32) -> u32 {
if unsafe { !*acpi_enabled } {
return 0;
}
read32_internal(&mut get_apic(), addr)
}
fn read32_internal(apic: &mut Apic, addr: u32) -> u32 {
match addr {
0x20 => {
dbg_log!("APIC read id");
apic.apic_id
},
0x30 => {
// version
dbg_log!("APIC read version");
0x50014
},
0x80 => {
if APIC_LOG_VERBOSE {
dbg_log!("APIC read tpr");
}
apic.tpr
},
0xB0 => {
// write-only (written by DSL)
dbg_log!("APIC read eio register");
0
},
0xD0 => {
dbg_log!("Read local destination");
apic.local_destination
},
0xE0 => {
dbg_log!("Read destination format");
apic.destination_format
},
0xF0 => apic.spurious_vector,
0x100 | 0x110 | 0x120 | 0x130 | 0x140 | 0x150 | 0x160 | 0x170 => {
let index = ((addr - 0x100) >> 4) as usize;
dbg_log!("Read isr {}: {:08x}", index, apic.isr[index] as u32);
apic.isr[index]
},
0x180 | 0x190 | 0x1A0 | 0x1B0 | 0x1C0 | 0x1D0 | 0x1E0 | 0x1F0 => {
let index = ((addr - 0x180) >> 4) as usize;
dbg_log!("Read tmr {}: {:08x}", index, apic.tmr[index] as u32);
apic.tmr[index]
},
0x200 | 0x210 | 0x220 | 0x230 | 0x240 | 0x250 | 0x260 | 0x270 => {
let index = ((addr - 0x200) >> 4) as usize;
dbg_log!("Read irr {}: {:08x}", index, apic.irr[index] as u32);
apic.irr[index]
},
0x280 => {
dbg_log!("Read error: {:08x}", apic.read_error);
apic.read_error
},
0x300 => {
if APIC_LOG_VERBOSE {
dbg_log!("APIC read icr0");
}
apic.icr0
},
0x310 => {
dbg_log!("APIC read icr1");
apic.icr1
},
0x320 => {
dbg_log!("read timer lvt");
apic.lvt_timer
},
0x330 => {
dbg_log!("read lvt thermal sensor");
apic.lvt_thermal_sensor
},
0x340 => {
dbg_log!("read lvt perf counter");
apic.lvt_perf_counter
},
0x350 => {
dbg_log!("read lvt int0");
apic.lvt_int0
},
0x360 => {
dbg_log!("read lvt int1");
apic.lvt_int1
},
0x370 => {
dbg_log!("read lvt error");
apic.lvt_error
},
0x3E0 => {
// divider
dbg_log!("read timer divider");
apic.timer_divider
},
0x380 => {
dbg_log!("read timer initial count");
apic.timer_initial_count
},
0x390 => {
dbg_log!("read timer current count: {:08x}", apic.timer_current_count);
apic.timer_current_count
},
_ => {
dbg_log!("APIC read {:x}", addr);
dbg_assert!(false);
0
},
}
}
pub fn write32(addr: u32, value: u32) {
if unsafe { !*acpi_enabled } {
return;
}
write32_internal(&mut get_apic(), addr, value)
}
fn write32_internal(apic: &mut Apic, addr: u32, value: u32) {
match addr {
0x20 => {
dbg_log!("APIC write id: {:08x}", value >> 8);
apic.apic_id = value;
},
0x30 => {
// version
dbg_log!("APIC write version: {:08x}, ignored", value);
},
0x80 => {
if APIC_LOG_VERBOSE {
dbg_log!("Set tpr: {:02x}", value & 0xFF);
}
apic.tpr = value & 0xFF;
},
0xB0 => {
if let Some(highest_isr) = highest_isr(apic) {
if APIC_LOG_VERBOSE {
dbg_log!("eoi: {:08x} for vector {:x}", value, highest_isr);
}
register_clear_bit(&mut apic.isr, highest_isr);
if register_get_bit(&apic.tmr, highest_isr) {
// Send eoi to all IO APICs
ioapic::remote_eoi(apic, highest_isr);
}
}
else {
dbg_log!("Bad eoi: No isr set");
}
},
0xD0 => {
dbg_log!("Set local destination: {:08x}", value);
apic.local_destination = value & 0xFF000000;
},
0xE0 => {
dbg_log!("Set destination format: {:08x}", value);
apic.destination_format = value | 0xFFFFFF;
},
0xF0 => {
dbg_log!("Set spurious vector: {:08x}", value);
apic.spurious_vector = value;
},
0x280 => {
// updated readable error register with real error
dbg_log!("Write error: {:08x}", value);
apic.read_error = apic.error;
apic.error = 0;
},
0x300 => {
let vector = (value & 0xFF) as u8;
let delivery_mode = ((value >> 8) & 7) as u8;
let destination_mode = ((value >> 11) & 1) as u8;
let is_level = value & ioapic::IOAPIC_CONFIG_TRIGGER_MODE_LEVEL
== ioapic::IOAPIC_CONFIG_TRIGGER_MODE_LEVEL;
let destination_shorthand = (value >> 18) & 3;
let destination = (apic.icr1 >> 24) as u8;
dbg_log!(
"APIC write icr0: {:08x} vector={:02x} destination_mode={} delivery_mode={} destination_shorthand={}",
value,
vector,
DESTINATION_MODES[destination_mode as usize],
DELIVERY_MODES[delivery_mode as usize],
["no", "self", "all with self", "all without self"][destination_shorthand as usize]
);
let mut value = value;
value &= !(1 << 12);
apic.icr0 = value;
if destination_shorthand == 0 {
// no shorthand
route(
apic,
vector,
delivery_mode,
is_level,
destination,
destination_mode,
);
}
else if destination_shorthand == 1 {
// self
deliver(apic, vector, IOAPIC_DELIVERY_FIXED, is_level);
}
else if destination_shorthand == 2 {
// all including self
deliver(apic, vector, delivery_mode, is_level);
}
else if destination_shorthand == 3 {
// all but self
}
else {
dbg_assert!(false);
}
},
0x310 => {
dbg_log!("APIC write icr1: {:08x}", value);
apic.icr1 = value;
},
0x320 => {
dbg_log!("timer lvt: {:08x}", value);
apic.lvt_timer = value;
},
0x330 => {
dbg_log!("lvt thermal sensor: {:08x}", value);
apic.lvt_thermal_sensor = value;
},
0x340 => {
dbg_log!("lvt perf counter: {:08x}", value);
apic.lvt_perf_counter = value;
},
0x350 => {
dbg_log!("lvt int0: {:08x}", value);
apic.lvt_int0 = value;
},
0x360 => {
dbg_log!("lvt int1: {:08x}", value);
apic.lvt_int1 = value;
},
0x370 => {
dbg_log!("lvt error: {:08x}", value);
apic.lvt_error = value;
},
0x3E0 => {
dbg_log!("timer divider: {:08x}", value);
apic.timer_divider = value;
let divide_shift = (value & 0b11) | ((value & 0b1000) >> 1);
apic.timer_divider_shift = if divide_shift == 0b111 { 0 } else { divide_shift + 1 };
},
0x380 => {
if APIC_LOG_VERBOSE {
dbg_log!("timer initial: {:08x}", value);
}
apic.timer_initial_count = value;
apic.timer_current_count = value;
apic.next_tick = unsafe { js::microtick() };
},
0x390 => {
dbg_log!("timer current: {:08x}", value);
dbg_assert!(false, "read-only register");
},
_ => {
dbg_log!("APIC write32 {:x} <- {:08x}", addr, value);
dbg_assert!(false);
},
}
}
#[no_mangle]
pub fn apic_timer(now: f64) -> f64 { timer(&mut get_apic(), now) }
fn timer(apic: &mut Apic, now: f64) -> f64 {
if apic.timer_current_count == 0 {
return 100.0;
}
let freq = APIC_TIMER_FREQ / (1 << apic.timer_divider_shift) as f64;
let steps = ((now - apic.next_tick) * freq).trunc() as u32;
apic.next_tick += steps as f64 / freq;
match apic.timer_current_count.checked_sub(steps) {
Some(t) => apic.timer_current_count = if t == 0 { 1 } else { t },
None => {
let mode = apic.lvt_timer & APIC_TIMER_MODE_MASK;
if mode == APIC_TIMER_MODE_PERIODIC {
apic.timer_current_count = apic.timer_initial_count; // XXX
if apic.timer_current_count == 0 {
apic.timer_current_count += apic.timer_initial_count;
}
dbg_assert!(apic.timer_current_count != 0);
if apic.lvt_timer & IOAPIC_CONFIG_MASKED == 0 {
deliver(
apic,
(apic.lvt_timer & 0xFF) as u8,
IOAPIC_DELIVERY_FIXED,
false,
);
}
}
else if mode == APIC_TIMER_MODE_ONE_SHOT {
apic.timer_current_count = 0;
if APIC_LOG_VERBOSE {
dbg_log!("APIC timer one shot end");
}
if apic.lvt_timer & IOAPIC_CONFIG_MASKED == 0 {
deliver(
apic,
(apic.lvt_timer & 0xFF) as u8,
IOAPIC_DELIVERY_FIXED,
false,
);
}
}
else {
dbg_assert!(false, "apic unimplemented timer mode: {:x}", mode);
}
}
}
(apic.timer_current_count as f64 / freq).max(0.0)
}
pub fn route(
apic: &mut Apic,
vector: u8,
mode: u8,
is_level: bool,
_destination: u8,
_destination_mode: u8,
) {
// TODO
deliver(apic, vector, mode, is_level);
}
fn deliver(apic: &mut Apic, vector: u8, mode: u8, is_level: bool) {
if APIC_LOG_VERBOSE {
dbg_log!("Deliver {:02x} mode={} level={}", vector, mode, is_level);
}
if mode == IOAPIC_DELIVERY_INIT {
// TODO
return;
}
if mode == IOAPIC_DELIVERY_NMI {
// TODO
return;
}
if vector < 0x10 || vector == 0xFF {
dbg_assert!(false, "TODO: Invalid vector");
}
if register_get_bit(&apic.irr, vector) {
dbg_log!("Not delivered: irr already set, vector={:02x}", vector);
return;
}
register_set_bit(&mut apic.irr, vector);
if is_level {
register_set_bit(&mut apic.tmr, vector);
}
else {
register_clear_bit(&mut apic.tmr, vector);
}
}
fn highest_irr(apic: &mut Apic) -> Option<u8> {
let highest = register_get_highest_bit(&apic.irr);
if let Some(x) = highest {
dbg_assert!(x >= 0x10);
dbg_assert!(x != 0xFF);
}
highest
}
fn highest_isr(apic: &mut Apic) -> Option<u8> {
let highest = register_get_highest_bit(&apic.isr);
if let Some(x) = highest {
dbg_assert!(x >= 0x10);
dbg_assert!(x != 0xFF);
}
highest
}
pub fn acknowledge_irq() -> Option<u8> { acknowledge_irq_internal(&mut get_apic()) }
fn acknowledge_irq_internal(apic: &mut Apic) -> Option<u8> {
let highest_irr = match highest_irr(apic) {
None => return None,
Some(x) => x,
};
if let Some(highest_isr) = highest_isr(apic) {
if highest_isr >= highest_irr {
if APIC_LOG_VERBOSE {
dbg_log!("Higher isr, isr={:x} irr={:x}", highest_isr, highest_irr);
}
return None;
}
}
if highest_irr & 0xF0 <= apic.tpr as u8 & 0xF0 {
if APIC_LOG_VERBOSE {
dbg_log!(
"Higher tpr, tpr={:x} irr={:x}",
apic.tpr & 0xF0,
highest_irr
);
}
return None;
}
register_clear_bit(&mut apic.irr, highest_irr);
register_set_bit(&mut apic.isr, highest_irr);
if APIC_LOG_VERBOSE {
dbg_log!("Calling vector {:x}", highest_irr);
}
dbg_assert!(acknowledge_irq_internal(apic).is_none());
Some(highest_irr)
}
// functions operating on 256-bit registers (for irr, isr, tmr)
fn register_get_bit(v: &[u32; 8], bit: u8) -> bool { v[(bit >> 5) as usize] & 1 << (bit & 31) != 0 }
fn register_set_bit(v: &mut [u32; 8], bit: u8) { v[(bit >> 5) as usize] |= 1 << (bit & 31); }
fn register_clear_bit(v: &mut [u32; 8], bit: u8) { v[(bit >> 5) as usize] &= !(1 << (bit & 31)); }
fn register_get_highest_bit(v: &[u32; 8]) -> Option<u8> {
for i in (0..8).rev() {
let word = v[i];
if word != 0 {
return Some(word.ilog2() as u8 | (i as u8) << 5);
}
}
None
}

View file

@ -1,23 +1,5 @@
#![allow(non_upper_case_globals)]
extern "C" {
fn cpu_exception_hook(interrupt: i32) -> bool;
fn call_indirect1(f: i32, x: u16);
pub fn microtick() -> f64;
pub fn run_hardware_timers(acpi_enabled: bool, t: f64) -> f64;
pub fn cpu_event_halt();
pub fn apic_acknowledge_irq() -> i32;
pub fn stop_idling();
pub fn io_port_read8(port: i32) -> i32;
pub fn io_port_read16(port: i32) -> i32;
pub fn io_port_read32(port: i32) -> i32;
pub fn io_port_write8(port: i32, value: i32);
pub fn io_port_write16(port: i32, value: i32);
pub fn io_port_write32(port: i32, value: i32);
}
use crate::config;
use crate::cpu::fpu::fpu_set_tag_word;
use crate::cpu::global_pointers::*;
@ -27,7 +9,7 @@ use crate::cpu::misc_instr::{
push16, push32,
};
use crate::cpu::modrm::{resolve_modrm16, resolve_modrm32};
use crate::cpu::pic;
use crate::cpu::{apic, ioapic, pic};
use crate::dbg::dbg_trace;
use crate::gen;
use crate::jit;
@ -44,6 +26,32 @@ use crate::state_flags::CachedStateFlags;
use std::collections::HashSet;
use std::ptr;
mod wasm {
extern "C" {
pub fn call_indirect1(f: i32, x: u16);
}
}
pub mod js {
extern "C" {
pub fn cpu_exception_hook(interrupt: i32) -> bool;
pub fn microtick() -> f64;
pub fn run_hardware_timers(acpi_enabled: bool, t: f64) -> f64;
pub fn cpu_event_halt();
pub fn stop_idling();
pub fn io_port_read8(port: i32) -> i32;
pub fn io_port_read16(port: i32) -> i32;
pub fn io_port_read32(port: i32) -> i32;
pub fn io_port_write8(port: i32, value: i32);
pub fn io_port_write16(port: i32, value: i32);
pub fn io_port_write32(port: i32, value: i32);
pub fn get_rand_int() -> i32;
}
}
/// The offset for our generated functions in the wasm table. Every index less than this is
/// reserved for rustc's indirect functions
pub const WASM_TABLE_OFFSET: u32 = 1024;
@ -235,7 +243,10 @@ pub const IA32_APIC_BASE_BSP: i32 = 1 << 8;
pub const IA32_APIC_BASE_EXTD: i32 = 1 << 10;
pub const IA32_APIC_BASE_EN: i32 = 1 << 11;
pub const APIC_ADDRESS: i32 = 0xFEE00000u32 as i32;
pub const IOAPIC_MEM_ADDRESS: u32 = 0xFEC00000;
pub const IOAPIC_MEM_SIZE: u32 = 32;
pub const APIC_MEM_ADDRESS: u32 = 0xFEE00000;
pub const APIC_MEM_SIZE: u32 = 0x1000;
pub const MXCSR_MASK: i32 = 0xffff;
pub const MXCSR_FZ: i32 = 1 << 15;
@ -2226,7 +2237,7 @@ pub unsafe fn trigger_fault_end_jit() {
#[allow(static_mut_refs)]
let (code, error_code) = jit_fault.take().unwrap();
if DEBUG {
if cpu_exception_hook(code) {
if js::cpu_exception_hook(code) {
return;
}
}
@ -2950,7 +2961,7 @@ pub unsafe fn cycle_internal() {
{
in_jit = true;
}
call_indirect1(
wasm::call_indirect1(
wasm_table_index as i32 + WASM_TABLE_OFFSET as i32,
initial_state,
);
@ -3135,11 +3146,11 @@ pub unsafe fn segment_prefix_op(seg: i32) {
pub unsafe fn main_loop() -> f64 {
profiler::stat_increment(stat::MAIN_LOOP);
let start = microtick();
let start = js::microtick();
if *in_hlt {
if *flags & FLAG_INTERRUPT != 0 {
let t = run_hardware_timers(*acpi_enabled, start);
let t = js::run_hardware_timers(*acpi_enabled, start);
handle_irqs();
if *in_hlt {
profiler::stat_increment(stat::MAIN_LOOP_IDLE);
@ -3155,8 +3166,8 @@ pub unsafe fn main_loop() -> f64 {
loop {
do_many_cycles_native();
let now = microtick();
let t = run_hardware_timers(*acpi_enabled, now);
let now = js::microtick();
let t = js::run_hardware_timers(*acpi_enabled, now);
handle_irqs();
if *in_hlt {
return t;
@ -3185,7 +3196,7 @@ pub unsafe fn trigger_de() {
dbg_log!("#de");
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_DE) {
if js::cpu_exception_hook(CPU_EXCEPTION_DE) {
return;
}
}
@ -3198,7 +3209,7 @@ pub unsafe fn trigger_ud() {
dbg_trace();
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_UD) {
if js::cpu_exception_hook(CPU_EXCEPTION_UD) {
return;
}
}
@ -3211,7 +3222,7 @@ pub unsafe fn trigger_nm() {
dbg_trace();
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_NM) {
if js::cpu_exception_hook(CPU_EXCEPTION_NM) {
return;
}
}
@ -3223,7 +3234,7 @@ pub unsafe fn trigger_gp(code: i32) {
dbg_log!("#gp");
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_GP) {
if js::cpu_exception_hook(CPU_EXCEPTION_GP) {
return;
}
}
@ -4093,7 +4104,7 @@ pub unsafe fn set_tsc(low: u32, high: u32) {
#[no_mangle]
pub unsafe fn read_tsc() -> u64 {
let value = (microtick() * TSC_RATE) as u64 - tsc_offset;
let value = (js::microtick() * TSC_RATE) as u64 - tsc_offset;
if !TSC_ENABLE_IMPRECISE_BROWSER_WORKAROUND {
return value;
@ -4278,7 +4289,7 @@ pub unsafe fn trigger_np(code: i32) {
dbg_log!("#np");
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_NP) {
if js::cpu_exception_hook(CPU_EXCEPTION_NP) {
return;
}
}
@ -4290,7 +4301,7 @@ pub unsafe fn trigger_ss(code: i32) {
dbg_log!("#ss");
*instruction_pointer = *previous_ip;
if DEBUG {
if cpu_exception_hook(CPU_EXCEPTION_SS) {
if js::cpu_exception_hook(CPU_EXCEPTION_SS) {
return;
}
}
@ -4301,17 +4312,14 @@ pub unsafe fn trigger_ss(code: i32) {
pub unsafe fn store_current_tsc() { *current_tsc = read_tsc(); }
#[no_mangle]
pub unsafe fn handle_irqs() { handle_irqs_internal(&mut pic::get_pic()) }
pub unsafe fn handle_irqs_internal(pic: &mut pic::Pic) {
pub unsafe fn handle_irqs() {
if *flags & FLAG_INTERRUPT != 0 {
if let Some(irq) = pic::pic_acknowledge_irq(pic) {
if let Some(irq) = pic::pic_acknowledge_irq() {
pic_call_irq(irq)
}
else if *acpi_enabled {
let irq = apic_acknowledge_irq();
if irq >= 0 {
pic_call_irq(irq as u8)
if let Some(irq) = apic::acknowledge_irq() {
pic_call_irq(irq)
}
}
}
@ -4320,12 +4328,68 @@ pub unsafe fn handle_irqs_internal(pic: &mut pic::Pic) {
unsafe fn pic_call_irq(interrupt_nr: u8) {
*previous_ip = *instruction_pointer; // XXX: What if called after instruction (port IO)
if *in_hlt {
stop_idling();
js::stop_idling();
*in_hlt = false;
}
call_interrupt_vector(interrupt_nr as i32, false, None);
}
#[no_mangle]
unsafe fn device_raise_irq(i: u8) {
pic::set_irq(i);
if *acpi_enabled {
ioapic::set_irq(i);
}
handle_irqs()
}
#[no_mangle]
unsafe fn device_lower_irq(i: u8) {
pic::clear_irq(i);
if *acpi_enabled {
ioapic::clear_irq(i);
}
handle_irqs()
}
pub fn io_port_read8(port: i32) -> i32 {
unsafe {
match port {
0x20 => pic::port20_read() as i32,
0x21 => pic::port21_read() as i32,
0xA0 => pic::portA0_read() as i32,
0xA1 => pic::portA1_read() as i32,
0x4D0 => pic::port4D0_read() as i32,
0x4D1 => pic::port4D1_read() as i32,
_ => js::io_port_read8(port),
}
}
}
pub fn io_port_read16(port: i32) -> i32 { unsafe { js::io_port_read16(port) } }
pub fn io_port_read32(port: i32) -> i32 { unsafe { js::io_port_read32(port) } }
pub fn io_port_write8(port: i32, value: i32) {
unsafe {
match port {
0x20 | 0x21 | 0xA0 | 0xA1 | 0x4D0 | 0x4D1 => {
match port {
0x20 => pic::port20_write(value as u8),
0x21 => pic::port21_write(value as u8),
0xA0 => pic::portA0_write(value as u8),
0xA1 => pic::portA1_write(value as u8),
0x4D0 => pic::port4D0_write(value as u8),
0x4D1 => pic::port4D1_write(value as u8),
_ => dbg_assert!(false),
};
handle_irqs()
},
_ => js::io_port_write8(port, value),
}
}
}
pub fn io_port_write16(port: i32, value: i32) { unsafe { js::io_port_write16(port, value) } }
pub fn io_port_write32(port: i32, value: i32) { unsafe { js::io_port_write32(port, value) } }
#[no_mangle]
#[cfg(debug_assertions)]
pub unsafe fn check_page_switch(block_addr: u32, next_block_addr: u32) {

View file

@ -1,6 +1,7 @@
#![allow(non_snake_case)]
use crate::cpu::arith::*;
use crate::cpu::cpu::js;
use crate::cpu::cpu::*;
use crate::cpu::fpu::*;
use crate::cpu::global_pointers::*;
@ -2162,12 +2163,12 @@ pub unsafe fn instr_F4() {
// due it will immediately call call_interrupt_vector and continue
// execution without an unnecessary cycle through do_run
if *flags & FLAG_INTERRUPT != 0 {
run_hardware_timers(*acpi_enabled, microtick());
js::run_hardware_timers(*acpi_enabled, js::microtick());
handle_irqs();
}
else {
// execution can never resume (until NMIs are supported)
cpu_event_halt();
js::cpu_event_halt();
}
}
#[no_mangle]

View file

@ -1,9 +1,5 @@
#![allow(non_snake_case)]
extern "C" {
fn get_rand_int() -> i32;
}
unsafe fn undefined_instruction() {
dbg_assert!(false, "Undefined instructions");
trigger_ud()
@ -1207,7 +1203,7 @@ pub unsafe fn instr_0F30() {
);
let address = low & !(IA32_APIC_BASE_BSP | IA32_APIC_BASE_EXTD | IA32_APIC_BASE_EN);
dbg_assert!(
address == APIC_ADDRESS,
address == APIC_MEM_ADDRESS as i32,
"Changing APIC address not supported"
);
dbg_assert!(low & IA32_APIC_BASE_EXTD == 0, "x2apic not supported");
@ -1285,7 +1281,7 @@ pub unsafe fn instr_0F32() {
IA32_PLATFORM_ID => {},
IA32_APIC_BASE => {
if *acpi_enabled {
low = APIC_ADDRESS;
low = APIC_MEM_ADDRESS as i32;
if *apic_enabled {
low |= IA32_APIC_BASE_EN
}
@ -3943,7 +3939,7 @@ pub unsafe fn instr32_0FC7_1_mem(addr: i32) { instr16_0FC7_1_mem(addr) }
#[no_mangle]
pub unsafe fn instr16_0FC7_6_reg(r: i32) {
// rdrand
let rand = get_rand_int();
let rand = js::get_rand_int();
write_reg16(r, rand);
*flags &= !FLAGS_ALL;
*flags |= 1;
@ -3952,7 +3948,7 @@ pub unsafe fn instr16_0FC7_6_reg(r: i32) {
#[no_mangle]
pub unsafe fn instr32_0FC7_6_reg(r: i32) {
// rdrand
let rand = get_rand_int();
let rand = js::get_rand_int();
write_reg32(r, rand);
*flags &= !FLAGS_ALL;
*flags |= 1;

316
src/rust/cpu/ioapic.rs Normal file
View file

@ -0,0 +1,316 @@
// http://download.intel.com/design/chipsets/datashts/29056601.pdf
use crate::cpu::{apic, global_pointers::acpi_enabled};
use std::sync::{Mutex, MutexGuard};
const IOAPIC_LOG_VERBOSE: bool = false;
const IOREGSEL: u32 = 0;
const IOWIN: u32 = 0x10;
const IOAPIC_IRQ_COUNT: usize = 24;
const IOAPIC_FIRST_IRQ_REG: u32 = 0x10;
const IOAPIC_LAST_IRQ_REG: u32 = 0x10 + 2 * IOAPIC_IRQ_COUNT as u32;
const IOAPIC_ID: u32 = 0; // must match value in seabios
pub const IOAPIC_CONFIG_TRIGGER_MODE_LEVEL: u32 = 1 << 15;
const IOAPIC_CONFIG_MASKED: u32 = 1 << 16;
const IOAPIC_CONFIG_DELIVS: u32 = 1 << 12;
const IOAPIC_CONFIG_REMOTE_IRR: u32 = 1 << 14;
const IOAPIC_CONFIG_READONLY_MASK: u32 =
IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000;
const IOAPIC_DELIVERY_FIXED: u8 = 0;
const IOAPIC_DELIVERY_LOWEST_PRIORITY: u8 = 1;
const _IOAPIC_DELIVERY_NMI: u8 = 4;
const _IOAPIC_DELIVERY_INIT: u8 = 5;
const DELIVERY_MODES: [&str; 8] = [
"Fixed (0)",
"Lowest Prio (1)",
"SMI (2)",
"Reserved (3)",
"NMI (4)",
"INIT (5)",
"Reserved (6)",
"ExtINT (7)",
];
const DESTINATION_MODES: [&str; 2] = ["physical", "logical"];
// keep in sync with cpu.js
#[allow(dead_code)]
const IOAPIC_STRUCT_SIZE: usize = 4 * 52;
// Note: JavaScript (cpu.get_state_apic) depens on this layout
const _: () = assert!(std::mem::offset_of!(Ioapic, ioredtbl_destination) == 24 * 4);
const _: () = assert!(std::mem::offset_of!(Ioapic, ioregsel) == 48 * 4);
const _: () = assert!(std::mem::offset_of!(Ioapic, irq_value) == 51 * 4);
const _: () = assert!(std::mem::size_of::<Ioapic>() == IOAPIC_STRUCT_SIZE);
#[repr(C)]
struct Ioapic {
ioredtbl_config: [u32; IOAPIC_IRQ_COUNT],
ioredtbl_destination: [u32; IOAPIC_IRQ_COUNT],
ioregsel: u32,
ioapic_id: u32,
irr: u32,
irq_value: u32,
}
static IOAPIC: Mutex<Ioapic> = Mutex::new(Ioapic {
ioredtbl_config: [IOAPIC_CONFIG_MASKED; IOAPIC_IRQ_COUNT],
ioredtbl_destination: [0; IOAPIC_IRQ_COUNT],
ioregsel: 0,
ioapic_id: IOAPIC_ID,
irr: 0,
irq_value: 0,
});
fn get_ioapic() -> MutexGuard<'static, Ioapic> { IOAPIC.try_lock().unwrap() }
#[no_mangle]
pub fn get_ioapic_addr() -> u32 { &raw mut *get_ioapic() as u32 }
pub fn remote_eoi(apic: &mut apic::Apic, vector: u8) {
remote_eoi_internal(&mut get_ioapic(), apic, vector);
}
fn remote_eoi_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, vector: u8) {
for i in 0..IOAPIC_IRQ_COUNT as u8 {
let config = ioapic.ioredtbl_config[i as usize];
if (config & 0xFF) as u8 == vector && config & IOAPIC_CONFIG_REMOTE_IRR != 0 {
dbg_log!("Clear remote IRR for irq={:x}", i);
ioapic.ioredtbl_config[i as usize] &= !IOAPIC_CONFIG_REMOTE_IRR;
check_irq(ioapic, apic, i);
}
}
}
fn check_irq(ioapic: &mut Ioapic, apic: &mut apic::Apic, irq: u8) {
let mask = 1 << irq;
if ioapic.irr & mask == 0 {
return;
}
let config = ioapic.ioredtbl_config[irq as usize];
if config & IOAPIC_CONFIG_MASKED == 0 {
let delivery_mode = ((config >> 8) & 7) as u8;
let destination_mode = ((config >> 11) & 1) as u8;
let vector = (config & 0xFF) as u8;
let destination = (ioapic.ioredtbl_destination[irq as usize] >> 24) as u8;
let is_level =
config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL == IOAPIC_CONFIG_TRIGGER_MODE_LEVEL;
if config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL == 0 {
ioapic.irr &= !mask;
}
else {
ioapic.ioredtbl_config[irq as usize] |= IOAPIC_CONFIG_REMOTE_IRR;
if config & IOAPIC_CONFIG_REMOTE_IRR != 0 {
dbg_log!("No route: level interrupt and remote IRR still set");
return;
}
}
if delivery_mode == IOAPIC_DELIVERY_FIXED
|| delivery_mode == IOAPIC_DELIVERY_LOWEST_PRIORITY
{
apic::route(
apic,
vector,
delivery_mode,
is_level,
destination,
destination_mode,
);
}
else {
dbg_assert!(false, "TODO");
}
ioapic.ioredtbl_config[irq as usize] &= !IOAPIC_CONFIG_DELIVS;
}
}
pub fn set_irq(i: u8) { set_irq_internal(&mut get_ioapic(), &mut apic::get_apic(), i) }
fn set_irq_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, i: u8) {
if i as usize >= IOAPIC_IRQ_COUNT {
dbg_assert!(false, "Bad irq: {}", i);
return;
}
let mask = 1 << i;
if ioapic.irq_value & mask == 0 {
if IOAPIC_LOG_VERBOSE {
dbg_log!("apic set irq {}", i);
}
ioapic.irq_value |= mask;
let config = ioapic.ioredtbl_config[i as usize];
if config & (IOAPIC_CONFIG_TRIGGER_MODE_LEVEL | IOAPIC_CONFIG_MASKED)
== IOAPIC_CONFIG_MASKED
{
// edge triggered and masked
return;
}
ioapic.irr |= mask;
check_irq(ioapic, apic, i);
}
}
pub fn clear_irq(i: u8) { clear_irq_internal(&mut get_ioapic(), i) }
fn clear_irq_internal(ioapic: &mut Ioapic, i: u8) {
if i as usize >= IOAPIC_IRQ_COUNT {
dbg_assert!(false, "Bad irq: {}", i);
return;
}
let mask = 1 << i;
if ioapic.irq_value & mask == mask {
ioapic.irq_value &= !mask;
let config = ioapic.ioredtbl_config[i as usize];
if config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL != 0 {
ioapic.irr &= !mask;
}
}
}
pub fn read32(addr: u32) -> u32 {
if unsafe { !*acpi_enabled } {
return 0;
}
read32_internal(&mut get_ioapic(), addr)
}
fn read32_internal(ioapic: &mut Ioapic, addr: u32) -> u32 {
match addr {
IOREGSEL => ioapic.ioregsel,
IOWIN => match ioapic.ioregsel {
0 => {
dbg_log!("IOAPIC Read id");
ioapic.ioapic_id << 24
},
1 => {
dbg_log!("IOAPIC Read version");
0x11 | (IOAPIC_IRQ_COUNT as u32 - 1) << 16
},
2 => {
dbg_log!("IOAPIC Read arbitration id");
ioapic.ioapic_id << 24
},
IOAPIC_FIRST_IRQ_REG..IOAPIC_LAST_IRQ_REG => {
let irq = ((ioapic.ioregsel - IOAPIC_FIRST_IRQ_REG) >> 1) as u8;
let index = ioapic.ioregsel & 1;
if index != 0 {
let value = ioapic.ioredtbl_destination[irq as usize];
dbg_log!("IOAPIC Read destination irq={:x} -> {:08x}", irq, value);
value
}
else {
let value = ioapic.ioredtbl_config[irq as usize];
dbg_log!("IOAPIC Read config irq={:x} -> {:08x}", irq, value);
value
}
},
reg => {
dbg_assert!(false, "IOAPIC register read outside of range {:x}", reg);
0
},
},
_ => {
dbg_assert!(false, "Unaligned or oob IOAPIC memory read: {:x}", addr);
0
},
}
}
pub fn write32(addr: u32, value: u32) {
if unsafe { !*acpi_enabled } {
return;
}
write32_internal(&mut get_ioapic(), &mut apic::get_apic(), addr, value)
}
fn write32_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, addr: u32, value: u32) {
//dbg_log!("IOAPIC write {:x} <- {:08x}", reg, value);
match addr {
IOREGSEL => ioapic.ioregsel = value,
IOWIN => match ioapic.ioregsel {
0 => ioapic.ioapic_id = (value >> 24) & 0x0F,
1 | 2 => {
dbg_log!("IOAPIC Invalid write: {}", ioapic.ioregsel);
},
IOAPIC_FIRST_IRQ_REG..IOAPIC_LAST_IRQ_REG => {
let irq = ((ioapic.ioregsel - IOAPIC_FIRST_IRQ_REG) >> 1) as u8;
let index = ioapic.ioregsel & 1;
if index != 0 {
dbg_log!(
"Write destination {:08x} irq={:x} dest={:02x}",
value,
irq,
value >> 24
);
ioapic.ioredtbl_destination[irq as usize] = value & 0xFF000000;
}
else {
let old_value = ioapic.ioredtbl_config[irq as usize] as u32;
ioapic.ioredtbl_config[irq as usize] = (value & !IOAPIC_CONFIG_READONLY_MASK)
| (old_value & IOAPIC_CONFIG_READONLY_MASK);
let vector = value & 0xFF;
let delivery_mode = (value >> 8) & 7;
let destination_mode = (value >> 11) & 1;
let is_level = (value >> 15) & 1;
let disabled = (value >> 16) & 1;
dbg_log!(
"Write config {:08x} irq={:x} vector={:02x} deliverymode={} destmode={} is_level={} disabled={}",
value,
irq,
vector,
DELIVERY_MODES[delivery_mode as usize],
DESTINATION_MODES[destination_mode as usize],
is_level,
disabled
);
check_irq(ioapic, apic, irq);
}
},
reg => {
dbg_assert!(
false,
"IOAPIC register write outside of range {:x} <- {:x}",
reg,
value
)
},
},
_ => {
dbg_assert!(
false,
"Unaligned or oob IOAPIC memory write: {:x} <- {:x}",
addr,
value
)
},
}
}

View file

@ -12,8 +12,12 @@ mod ext {
}
}
use crate::cpu::cpu::reg128;
use crate::cpu::apic;
use crate::cpu::cpu::{
handle_irqs, reg128, APIC_MEM_ADDRESS, APIC_MEM_SIZE, IOAPIC_MEM_ADDRESS, IOAPIC_MEM_SIZE,
};
use crate::cpu::global_pointers::memory_size;
use crate::cpu::ioapic;
use crate::cpu::vga;
use crate::jit;
use crate::page::Page;
@ -122,6 +126,12 @@ pub fn read32s(addr: u32) -> i32 {
ptr::read_unaligned(vga_mem8.offset((addr - VGA_LFB_ADDRESS) as isize) as *const i32)
} // XXX
}
else if addr >= APIC_MEM_ADDRESS && addr < APIC_MEM_ADDRESS + APIC_MEM_SIZE {
apic::read32(addr - APIC_MEM_ADDRESS) as i32
}
else if addr >= IOAPIC_MEM_ADDRESS && addr < IOAPIC_MEM_ADDRESS + IOAPIC_MEM_SIZE {
ioapic::read32(addr - IOAPIC_MEM_ADDRESS) as i32
}
else {
unsafe { ext::mmap_read32(addr) }
}
@ -206,7 +216,7 @@ pub unsafe fn write32(addr: u32, value: i32) {
else {
jit::jit_dirty_cache_small(addr, addr + 4);
write32_no_mmap_or_dirty_check(addr, value);
};
}
}
pub unsafe fn write32_no_mmap_or_dirty_check(addr: u32, value: i32) {
@ -276,6 +286,14 @@ pub unsafe fn mmap_write32(addr: u32, value: i32) {
value,
)
}
else if addr >= APIC_MEM_ADDRESS && addr < APIC_MEM_ADDRESS + APIC_MEM_SIZE {
apic::write32(addr - APIC_MEM_ADDRESS, value as u32);
handle_irqs();
}
else if addr >= IOAPIC_MEM_ADDRESS && addr < IOAPIC_MEM_ADDRESS + IOAPIC_MEM_SIZE {
ioapic::write32(addr - IOAPIC_MEM_ADDRESS, value as u32);
handle_irqs();
}
else {
ext::mmap_write32(addr, value)
}

View file

@ -1,3 +1,4 @@
pub mod apic;
pub mod arith;
pub mod call_indirect;
pub mod cpu;
@ -5,6 +6,7 @@ pub mod fpu;
pub mod global_pointers;
pub mod instructions;
pub mod instructions_0f;
pub mod ioapic;
pub mod memory;
pub mod misc_instr;
pub mod modrm;

View file

@ -3,15 +3,15 @@
// Programmable Interrupt Controller
// http://stanislavs.org/helppc/8259.html
use std::sync::{Mutex, MutexGuard};
pub const PIC_LOG: bool = false;
pub const PIC_LOG_VERBOSE: bool = false;
use crate::cpu::cpu;
use std::sync::{Mutex, MutexGuard};
// Note: This layout is deliberately chosen to match the old JavaScript pic state
// (cpu.get_state_pic depens on this layout)
#[repr(C, packed)]
const _: () = assert!(std::mem::offset_of!(Pic0, special_mask_mode) == 12);
#[repr(C)]
struct Pic0 {
irq_mask: u8,
@ -39,7 +39,7 @@ struct Pic0 {
special_mask_mode: bool,
}
pub struct Pic {
struct Pic {
master: Pic0,
slave: Pic0,
}
@ -91,14 +91,13 @@ static PIC: Mutex<Pic> = Mutex::new(Pic {
},
});
pub fn get_pic() -> MutexGuard<'static, Pic> { PIC.try_lock().unwrap() }
fn get_pic() -> MutexGuard<'static, Pic> { PIC.try_lock().unwrap() }
// Checking for callable interrupts:
// (cpu changes interrupt flag) -> cpu.handle_irqs -> pic_acknowledge_irq
// (pic changes isr/irr) -> pic.check_irqs -> cpu.handle_irqs -> ...
// triggering irqs:
// (io device has irq) -> cpu.device_raise_irq -> pic.set_irq -> pic.check_irqs -> cpu.handle_irqs -> (see above)
// called from javascript for saving/restoring state
#[no_mangle]
pub unsafe fn get_pic_addr_master() -> u32 { &raw mut get_pic().master as u32 }
#[no_mangle]
pub unsafe fn get_pic_addr_slave() -> u32 { &raw mut get_pic().slave as u32 }
impl Pic0 {
unsafe fn get_irq(&mut self) -> Option<u8> {
@ -157,10 +156,7 @@ impl Pic {
if dev.irq_value & mask == 0 || dev.elcr & mask != 0 {
dev.irr |= mask;
dev.irq_value |= mask;
if i < 8 {
self.check_irqs_master()
}
else {
if i >= 8 {
self.check_irqs_slave()
}
}
@ -172,10 +168,7 @@ impl Pic {
dev.irq_value &= !mask;
if dev.elcr & mask != 0 {
dev.irr &= !mask;
if i < 8 {
self.check_irqs_master()
}
else {
if i >= 8 {
self.check_irqs_slave()
}
}
@ -245,10 +238,7 @@ impl Pic {
dev.isr &= dev.isr - 1;
}
if index == 0 {
self.check_irqs_master()
}
else {
if index == 1 {
self.check_irqs_slave()
}
}
@ -273,10 +263,7 @@ impl Pic {
dbg_log!("interrupt mask: {:x}", dev.irq_mask);
}
if index == 0 {
self.check_irqs_master()
}
else {
if index == 1 {
self.check_irqs_slave()
}
}
@ -294,12 +281,6 @@ impl Pic {
}
}
unsafe fn check_irqs_master(&mut self) {
let is_set = self.master.get_irq().is_some();
if is_set {
cpu::handle_irqs_internal(self);
}
}
unsafe fn check_irqs_slave(&mut self) {
let is_set = self.slave.get_irq().is_some();
if is_set {
@ -312,7 +293,8 @@ impl Pic {
}
// called by the cpu
pub unsafe fn pic_acknowledge_irq(pic: &mut Pic) -> Option<u8> {
pub unsafe fn pic_acknowledge_irq() -> Option<u8> {
let mut pic = get_pic();
let irq = match pic.master.get_irq() {
Some(i) => i,
None => return None,
@ -343,7 +325,7 @@ pub unsafe fn pic_acknowledge_irq(pic: &mut Pic) -> Option<u8> {
dbg_assert!(pic.master.get_irq().is_none());
if irq == 2 {
acknowledge_irq_slave(pic)
acknowledge_irq_slave(&mut pic)
}
else {
Some(pic.master.irq_map | irq)
@ -384,9 +366,7 @@ unsafe fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> {
Some(pic.slave.irq_map | irq)
}
// called by javascript
#[no_mangle]
pub unsafe fn pic_set_irq(i: u8) {
pub unsafe fn set_irq(i: u8) {
dbg_assert!(i < 16);
if PIC_LOG_VERBOSE {
@ -396,9 +376,7 @@ pub unsafe fn pic_set_irq(i: u8) {
get_pic().set_irq(i)
}
// called by javascript
#[no_mangle]
pub unsafe fn pic_clear_irq(i: u8) {
pub unsafe fn clear_irq(i: u8) {
dbg_assert!(i < 16);
if PIC_LOG_VERBOSE {
@ -408,36 +386,19 @@ pub unsafe fn pic_clear_irq(i: u8) {
get_pic().clear_irq(i)
}
#[no_mangle]
pub unsafe fn port20_read() -> u32 { get_pic().master.port0_read() }
#[no_mangle]
pub unsafe fn port21_read() -> u32 { get_pic().master.port1_read() }
#[no_mangle]
pub unsafe fn portA0_read() -> u32 { get_pic().slave.port0_read() }
#[no_mangle]
pub unsafe fn portA1_read() -> u32 { get_pic().slave.port1_read() }
#[no_mangle]
pub unsafe fn port20_write(v: u8) { get_pic().port0_write(0, v) }
#[no_mangle]
pub unsafe fn port21_write(v: u8) { get_pic().port1_write(0, v) }
#[no_mangle]
pub unsafe fn portA0_write(v: u8) { get_pic().port0_write(1, v) }
#[no_mangle]
pub unsafe fn portA1_write(v: u8) { get_pic().port1_write(1, v) }
#[no_mangle]
pub unsafe fn port4D0_read() -> u32 { get_pic().master.elcr as u32 }
#[no_mangle]
pub unsafe fn port4D1_read() -> u32 { get_pic().slave.elcr as u32 }
#[no_mangle]
pub unsafe fn port4D0_write(v: u8) { get_pic().master.elcr = v }
#[no_mangle]
pub unsafe fn port4D1_write(v: u8) { get_pic().slave.elcr = v }
#[no_mangle]
pub unsafe fn get_pic_addr_master() -> u32 { &raw const get_pic().master as u32 }
#[no_mangle]
pub unsafe fn get_pic_addr_slave() -> u32 { &raw const get_pic().slave as u32 }