| "use strict"; |
| |
| // See Intel's System Programming Guide |
| |
| |
| /** @const */ |
| var APIC_LOG_VERBOSE = false; |
| |
| /** @const */ |
| var APIC_ADDRESS = 0xFEE00000; |
| |
| /** @const */ |
| var APIC_TIMER_MODE_MASK = 3 << 17; |
| |
| /** @const */ |
| var APIC_TIMER_MODE_ONE_SHOT = 0; |
| |
| /** @const */ |
| var APIC_TIMER_MODE_PERIODIC = 1 << 17; |
| |
| /** @const */ |
| 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; |
| |
| 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_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 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 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 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; |
| } |
| //dbg_log(now + " " + this.next_tick, LOG_APIC); |
| |
| var steps = (now - this.next_tick) * APIC_TIMER_FREQ / (1 << this.timer_divider_shift) >>> 0; |
| |
| if(steps === 0) |
| { |
| return; |
| } |
| |
| this.next_tick += steps / APIC_TIMER_FREQ * (1 << this.timer_divider_shift); |
| |
| 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 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; |
| |
| 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); |
| } |
| } |
| } |
| }; |
| |
| 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; |
| } |
| |
| 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.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; |
| |
| 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; |
| }; |