More complete APIC and IOAPIC implementation (#86)

This commit is contained in:
copy 2017-04-02 11:52:51 -05:00
parent 9e4b2c5ace
commit 96f61bb4d5
8 changed files with 919 additions and 342 deletions

View file

@ -74,7 +74,7 @@ TRANSPILE_ES6_FLAGS=\
CORE_FILES=src/const.js src/io.js src/main.js src/lib.js src/fpu.js src/ide.js src/pci.js src/floppy.js src/memory.js\
src/dma.js src/pit.js src/vga.js src/ps2.js src/pic.js src/rtc.js src/uart.js src/hpet.js src/acpi.js src/apic.js\
src/dma.js src/pit.js src/vga.js src/ps2.js src/pic.js src/rtc.js src/uart.js src/hpet.js src/acpi.js src/apic.js src/ioapic.js\
src/state.js src/ne2k.js src/virtio.js src/bus.js src/log.js src/config.js\
src/cpu.js src/translate.js src/modrm.js src/string.js src/arith.js src/misc_instr.js src/instructions.js src/debug.js \
src/elf.js

View file

@ -5,7 +5,7 @@
var CORE_FILES =
"const.js config.js log.js cpu.js debug.js translate.js modrm.js string.js arith.js misc_instr.js instructions.js " +
"io.js main.js lib.js ide.js fpu.js pci.js floppy.js " +
"memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js acpi.js apic.js hpet.js " +
"memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js acpi.js apic.js ioapic.js hpet.js " +
"ne2k.js state.js virtio.js bus.js elf.js";
var BROWSER_FILES = "main.js screen.js keyboard.js mouse.js serial.js lib.js network.js starter.js worker_bus.js";

View file

@ -1,27 +1,14 @@
"use strict";
// See Intel's System Programming Guide
/** @const */
var APIC_LOG_VERBOSE = false;
/** @const */
var APIC_ADDRESS = 0xFEE00000;
/** @const */
var IOAPIC_ADDRESS = 0xFEC00000;
/** @const */
var IOREGSEL = 0;
/** @const */
var IOWIN = 0x10;
/** @const */
var IOAPIC_IRQ_COUNT = 24;
/** @const */
var IOAPIC_ID = 0; // must match value in seabios
/** @const */
var APIC_TIMER_FREQ = 1 * 1024;
/** @const */
var APIC_TIMER_MODE_MASK = 3 << 17;
@ -35,225 +22,348 @@ var APIC_TIMER_MODE_PERIODIC = 1 << 17;
var APIC_TIMER_MODE_TSC = 2 << 17;
/** @const */
var DELIVERY_MODES = [
"Fixed (0)",
"Lowest Prio (1)",
"SMI (2)",
"Reserved (3)",
"NMI (4)",
"INIT (5)",
"Reserved (6)",
"ExtINT (7)",
];
/** @const */
var DESTINATION_MODES = ["physical", "logical"];
/**
* @constructor
* @param {CPU} cpu
*/
function APIC(cpu)
{
/** @type {CPU} */
this.cpu = cpu;
var io = cpu.io;
this.ioredtlb = new Int32Array(0x10 + IOAPIC_IRQ_COUNT * 2);
for(var i = 0; i < this.ioredtlb.length; i += 2)
{
// disable interrupts
this.ioredtlb[i] = 1 << 16;
}
this.apic_id = 0;
this.timer_divider = 0;
this.timer_divider_shift = 1;
this.timer_initial_count = 0;
this.timer_current_count = 0;
this.lvt_timer = 0x10000;
this.next_tick = v86.microtick();
this.irqs = [];
this.irqs_set = 0;
this.ioapic_id = IOAPIC_ID;
this.lvt_timer = 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.lapic_tpr = 0;
this.lapic_icr = new Int32Array(2);
this.tpr = 0;
this.icr0 = 0
this.icr1 = 0
io.mmap_register(APIC_ADDRESS, 0x100000,
function(addr)
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_trace();
dbg_log("unsupported read8 from apic: " + h(addr >>> 0), LOG_APIC);
dbg_log("Unsupported read8 from apic: " + h(addr >>> 0), LOG_APIC);
var off = addr & 3;
addr &= ~3;
return read32(addr) >> (off * 8) & 0xFF;
return this.read32(addr) >> (off * 8) & 0xFF;
},
function(addr, value)
(addr, value) =>
{
dbg_log("Unsupported write8 from apic: " + h(addr) + " <- " + h(value), LOG_APIC);
dbg_trace();
dbg_log("unsupported write8 from apic: " + h(addr) + " <- " + h(value), LOG_APIC);
dbg_assert(false);
},
read32,
function(addr, value)
{
addr = addr - APIC_ADDRESS | 0;
switch(addr)
{
case 0x80:
// tpr
me.lapic_tpr = value;
break;
case 0xb0:
//dbg_log("APIC write eoi: " + h(value, 8), LOG_APIC);
break;
case 0x300:
var vector = value & 0xFF;
var delivery_mode = value >> 8 & 7;
var destination_mode = value >> 11 & 1;
var destination_shorthand = value >> 18 & 3;
var destination = me.lapic_icr[1] >>> 24;
dbg_log("APIC write icr0: " + h(value, 8) + " vector=" + h(vector, 2) + " " +
"destination_mode=" + destination_mode + " delivery_mode=" + delivery_mode + " " +
"destination_shorthand=" + ["no", "self", "all with self", "all without self"][destination_shorthand], LOG_APIC);
//value |= 0x1000;
me.lapic_icr[0] = value;
if(delivery_mode === 0 || delivery_mode === 1)
{
me.do_irq(value);
}
break;
case 0x310:
dbg_log("APIC write icr1: " + h(value, 8), LOG_APIC);
me.lapic_icr[1] = value;
break;
case 0x320:
dbg_log("timer lvt: " + h(value >>> 0, 8), LOG_APIC);
me.lvt_timer = value;
break;
case 0x3E0:
dbg_log("timer divider: " + h(value >>> 0, 8), LOG_APIC);
me.timer_divider = value;
var divide_shift = value & 0b11 | (value & 0b1000) >> 1;
me.timer_divider_shift = divide_shift === 7 ? 0 : divide_shift + 1;
break;
case 0x380:
//dbg_log("timer initial: " + h(value >>> 0, 8), LOG_APIC);
me.timer_initial_count = value >>> 0;
me.timer_current_count = value >>> 0;
me.next_tick = v86.microtick();
break;
case 0x390:
dbg_log("timer current: " + h(value >>> 0, 8), LOG_APIC);
me.timer_current_count = value >>> 0;
break;
default:
dbg_log("APIC write32 " + h(addr) + " <- " + h(value >>> 0, 8), LOG_APIC);
}
});
function read32(addr)
{
addr = addr - APIC_ADDRESS | 0;
switch(addr)
{
case 0x30:
// version
dbg_log("APIC read version", LOG_APIC);
return 0x50011
case 0x80:
// tpr
//dbg_log("APIC read tpr", LOG_APIC);
return me.lapic_tpr;
case 0x300:
dbg_log("APIC read icr0", LOG_APIC);
return me.lapic_icr[0];
case 0x310:
dbg_log("APIC read icr1", LOG_APIC);
return me.lapic_icr[1];
case 0x320:
dbg_log("read timer lvt", LOG_APIC);
return me.lvt_timer;
case 0x3E0:
// divider
dbg_log("read timer divider", LOG_APIC);
return me.timer_divider;
case 0x380:
dbg_log("read timer initial count", LOG_APIC);
return me.timer_initial_count;
case 0x390:
//dbg_log("read timer current count: " + h(me.timer_current_count >>> 0, 8), LOG_APIC);
return me.timer_current_count;
default:
dbg_log("APIC read " + h(addr), LOG_APIC);
return 0;
}
}
this.iotable = new Int32Array(IOAPIC_IRQ_COUNT * 2);
// IOAPIC register selection
var ioregsel = 0;
var me = this;
dbg_assert(MMAP_BLOCK_SIZE >= 0x20);
io.mmap_register(IOAPIC_ADDRESS, MMAP_BLOCK_SIZE,
function(addr)
{
dbg_assert(false, "unsupported read8 from ioapic: " + h(addr));
return 0;
},
function(addr, value)
{
dbg_assert(false, "unsupported write8 from ioapic: " + h(addr));
},
function(addr)
{
addr = addr - IOAPIC_ADDRESS | 0;
if(addr === IOREGSEL)
{
dbg_log("Unexpected IOAPIC register read ioregsel", LOG_APIC);
}
else if(addr === IOWIN)
{
dbg_log("IOAPIC read32 " + h(ioregsel), LOG_APIC);
return me.ioapic_read(ioregsel);
}
else
{
dbg_log("Unexpected IOAPIC register read: " + h(addr), LOG_APIC);
return 0;
}
},
function(addr, value)
{
addr = addr - IOAPIC_ADDRESS | 0;
if(addr === IOREGSEL)
{
ioregsel = value;
}
else if(addr === IOWIN)
{
me.ioapic_write(ioregsel, value);
}
else
{
dbg_log("Unexpected IOAPIC register write: " + h(addr) + " <- " + h(value >>> 0, 8), LOG_APIC);
}
});
(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 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 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 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)
@ -272,156 +382,243 @@ APIC.prototype.timer = function(now)
this.next_tick += steps / APIC_TIMER_FREQ * (1 << this.timer_divider_shift);
this.timer_current_count -= steps;
var mode = this.lvt_timer & APIC_TIMER_MODE_MASK;
if(mode === APIC_TIMER_MODE_PERIODIC)
if(this.timer_current_count <= 0)
{
while(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_initial_count + this.timer_current_count % this.timer_initial_count;
}
// This isn't exact, because timer_current_count might already be
// negative at this point since timer() fires late
this.timer_current_count = this.timer_initial_count;
this.do_irq(this.lvt_timer);
}
else if(mode === APIC_TIMER_MODE_ONE_SHOT)
{
if(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("XXX one shot end");
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);
}
}
this.do_irq(this.lvt_timer);
}
};
APIC.prototype.check_irqs = function()
APIC.prototype.route = function(vector, mode, is_level, destination, destination_mode)
{
if(!this.irqs.length)
// 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 irq_number = this.irqs.pop();
this.irqs_set &= ~(1 << irq_number);
var highest_isr = this.highest_isr();
//if(irq_number !== 0x30) // timer interrupt on linux
//{
// dbg_log("Handle irq " + h(irq_number), LOG_APIC);
//}
this.cpu.previous_ip = this.cpu.instruction_pointer;
this.cpu.call_interrupt_vector(irq_number, false, false);
};
APIC.prototype.set_irq = function(i)
{
//dbg_log("apic raise irq " + h(i), LOG_APIC);
var infos = this.ioredtlb[0x10 + (i << 1)]
this.do_irq(infos);
};
APIC.prototype.clear_irq = function(i)
{
};
APIC.prototype.do_irq = function(infos)
{
if(infos & (1 << 16))
if(highest_isr >= highest_irr)
{
//dbg_log("irq " + h(i, 2) + " disabled", LOG_APIC);
APIC_LOG_VERBOSE && dbg_log("Higher isr, isr=" + h(highest_isr) + " irr=" + h(highest_irr), LOG_APIC);
return;
}
var irq = infos & 0xFF;
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.irqs.push(irq);
this.irqs_set |= 1 << irq;
this.cpu.handle_irqs();
};
APIC.prototype.ioapic_read = function(reg)
APIC.prototype.acknowledge_irq = function()
{
switch(reg)
var highest_irr = this.highest_irr();
if(highest_irr === -1)
{
case 0:
return this.ioapic_id << 24;
case 1:
return 0x11 | IOAPIC_IRQ_COUNT - 1 << 16;
case 2:
// Arbitration ID
return 0;
default:
if(reg >= 0x10 && reg < 0x10 + 2 * IOAPIC_IRQ_COUNT)
{
var irq = reg - 0x10 >> 1;
var index = reg & 1;
var value = this.ioredtlb[reg];
if(index)
{
dbg_log("Read destination irq=" + h(irq) + " -> " + h(value, 8), LOG_APIC);
}
else
{
dbg_log("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);
return 0;
}
//dbg_log("Spurious", LOG_APIC);
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.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.cpu.pic_call_irq(highest_irr);
this.check_vector();
};
APIC.prototype.ioapic_write = function(reg, value)
APIC.prototype.get_state = function()
{
dbg_log("IOAPIC write " + h(reg) + " <- " + h(value, 8), LOG_APIC);
var state = [];
switch(reg)
{
case 0:
this.ioapic_id = value >>> 24;
break;
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;
default:
if(reg >= 0x10 && reg < 0x10 + 2 * IOAPIC_IRQ_COUNT)
{
this.ioredtlb[reg] = value;
var irq = reg - 0x10 >> 1;
var index = reg & 1;
if(index)
{
dbg_log("Write destination " + h(value >>> 0, 8) + " irq=" + h(irq) + " dest=" + h(value >>> 24, 2), LOG_APIC);
}
else
{
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) +
" delivery=" + h(delivery_mode) +
" destmode=" + destination_mode +
" is_level=" + is_level +
" disabled=" + disabled, LOG_APIC);
}
}
else
{
dbg_log("IOAPIC register write outside of range " + h(reg) + ": " + h(value >>> 0, 8), LOG_APIC);
}
}
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];
};
// 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 v86util.int_log2(word >>> 0) | i << 5;
}
}
return -1;
};

View file

@ -27,7 +27,7 @@ var LOG_LEVEL = LOG_ALL & ~LOG_PS2 & ~LOG_PIT & ~LOG_VIRTIO & ~LOG_9P & ~LOG_PIC
var ENABLE_HPET = DEBUG && false;
/** @const */
var ENABLE_ACPI = DEBUG && false;
var ENABLE_ACPI = true;
/**
@ -48,3 +48,7 @@ var TIME_PER_FRAME = 1;
* How many ticks the TSC does per millisecond
*/
var TSC_RATE = 8 * 1024;
/** @const */
var APIC_TIMER_FREQ = TSC_RATE;

View file

@ -280,6 +280,14 @@ var IA32_KERNEL_GS_BASE = 0xC0000101 | 0;
var MSR_PKG_C2_RESIDENCY = 0x60D;
/** @const */
var IA32_APIC_BASE_BSP = 1 << 8;
/** @const */
var IA32_APIC_BASE_EXTD = 1 << 10;
/** @const */
var IA32_APIC_BASE_EN = 1 << 11;
/** @const */ var TSR_BACKLINK = 0x00;
/** @const */ var TSR_CR3 = 0x1C;
/** @const */ var TSR_EIP = 0x20;

View file

@ -200,6 +200,8 @@ function CPU()
/** @type {number} */
this.previous_ip = 0;
this.apic_enabled = true;
// managed in io.js
/** @const */ this.memory_map_read8 = [];
/** @const */ this.memory_map_write8 = [];
@ -304,7 +306,7 @@ CPU.prototype.get_state = function()
state[43] = this.fpu;
state[45] = this.devices.virtio;
//state[46] = this.devices.apic;
state[46] = this.devices.apic;
state[47] = this.devices.rtc;
state[48] = this.devices.pci;
state[49] = this.devices.dma;
@ -323,6 +325,8 @@ CPU.prototype.get_state = function()
state[61] = this.a20_enabled;
state[62] = this.fw_value;
state[63] = this.devices.ioapic;
return state;
};
@ -373,7 +377,7 @@ CPU.prototype.set_state = function(state)
this.fpu = state[43];
this.devices.virtio = state[45];
//this.devices.apic = state[46];
this.devices.apic = state[46];
this.devices.rtc = state[47];
this.devices.pci = state[48];
this.devices.dma = state[49];
@ -392,6 +396,8 @@ CPU.prototype.set_state = function(state)
this.a20_enabled = state[61];
this.fw_value = state[62];
this.devices.ioapic = state[63];
this.mem16 = new Uint16Array(this.mem8.buffer, this.mem8.byteOffset, this.mem8.length >> 1);
this.mem32s = new Int32Array(this.mem8.buffer, this.mem8.byteOffset, this.mem8.length >> 2);
@ -658,6 +664,7 @@ CPU.prototype.init = function(settings, device_bus)
if(ENABLE_ACPI)
{
this.devices.ioapic = new IOAPIC(this);
this.devices.apic = new APIC(this);
this.devices.acpi = new ACPI(this);
}
@ -3108,9 +3115,9 @@ CPU.prototype.device_raise_irq = function(i)
this.devices.pic.set_irq(i);
}
if(this.devices.apic)
if(this.devices.ioapic)
{
this.devices.apic.set_irq(i);
this.devices.ioapic.set_irq(i);
}
};
@ -3121,9 +3128,9 @@ CPU.prototype.device_lower_irq = function(i)
this.devices.pic.clear_irq(i);
}
if(this.devices.apic)
if(this.devices.ioapic)
{
this.devices.apic.clear_irq(i);
this.devices.ioapic.clear_irq(i);
}
};
@ -3206,7 +3213,7 @@ CPU.prototype.cpuid = function()
vme | 1 << 3 | 1 << 4 | 1 << 5 | // vme, pse, tsc, msr
1 << 8 | 1 << 11 | 1 << 13 | 1 << 15; // cx8, sep, pge, cmov
if(ENABLE_ACPI)
if(ENABLE_ACPI && this.apic_enabled)
{
edx |= 1 << 9; // apic
}

View file

@ -2112,7 +2112,11 @@ t[0x30] = cpu => {
break;
case IA32_APIC_BASE_MSR:
dbg_assert((low >>> 0) === APIC_ADDRESS, "Changing APIC address not supported");
dbg_assert(high === 0, "Changing APIC address (high 32 bits) not supported");
let address = low & ~(IA32_APIC_BASE_BSP | IA32_APIC_BASE_EXTD | IA32_APIC_BASE_EN);
dbg_assert((address >>> 0) === APIC_ADDRESS, "Changing APIC address not supported");
dbg_assert((low & IA32_APIC_BASE_EXTD) === 0, "x2apic not supported");
cpu.apic_enabled = (low & IA32_APIC_BASE_EN) === IA32_APIC_BASE_EN;
break;
case IA32_TIME_STAMP_COUNTER:
@ -2200,6 +2204,11 @@ t[0x32] = cpu => {
if(ENABLE_ACPI)
{
low = APIC_ADDRESS;
if(cpu.apic_enabled)
{
low |= IA32_APIC_BASE_EN;
}
}
break;

352
src/ioapic.js Normal file
View file

@ -0,0 +1,352 @@
"use strict";
// http://download.intel.com/design/chipsets/datashts/29056601.pdf
/** @const */
var IOAPIC_ADDRESS = 0xFEC00000;
/** @const */
var IOREGSEL = 0;
/** @const */
var IOWIN = 0x10;
/** @const */
var IOAPIC_IRQ_COUNT = 24;
/** @const */
var IOAPIC_ID = 0; // must match value in seabios
/** @const */
var IOAPIC_CONFIG_TRIGGER_MODE_LEVEL = 1 << 15;
/** @const */
var IOAPIC_CONFIG_MASKED = 1 << 16;
/** @const */
var IOAPIC_CONFIG_DELIVS = 1 << 12;
/** @const */
var IOAPIC_CONFIG_REMOTE_IRR = 1 << 14;
/** @const */
var IOAPIC_CONFIG_READONLY_MASK = IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000;
/** @const */
var IOAPIC_DELIVERY_FIXED = 0;
/** @const */
var IOAPIC_DELIVERY_LOWEST_PRIORITY = 1;
/** @const */
var IOAPIC_DELIVERY_NMI = 4;
/** @const */
var IOAPIC_DELIVERY_INIT = 5;
/**
* @constructor
* @param {CPU} cpu
*/
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) =>
{
dbg_assert(false, "unsupported read8 from ioapic: " + h(addr));
return 0;
},
(addr, value) =>
{
dbg_assert(false, "unsupported write8 from ioapic: " + h(addr));
},
(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), 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) + " <- " + 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];
};