"use strict"; /** @const */ var PIC_LOG_VERBOSE = false; /** * Programmable Interrupt Controller * http://stanislavs.org/helppc/8259.html * * @constructor * @param {CPU} cpu * @param {PIC=} master */ function PIC(cpu, master) { /** * all irqs off * @type {number} */ this.irq_mask = 0; /** * @type {number} * * Bogus default value (both master and slave mapped to 0). * Will be initialized by the BIOS */ this.irq_map = 0; /** * in-service register * Holds interrupts that are currently being serviced * @type {number} */ this.isr = 0; /** * interrupt request register * Holds interrupts that have been requested * @type {number} */ this.irr = 0; this.irq_value = 0; /** * @type {number} */ this.requested_irq = -1; this.master = master; this.is_master = this.master === undefined; this.slave = undefined; this.name = this.is_master ? "master" : "slave "; this.expect_icw4 = false; this.state = 0; this.read_isr = 0; this.auto_eoi = 1; this.special_mask_mode = 0; this.elcr = 0; this.cpu = cpu; // Checking for callable interrupts: // (cpu changes interrupt flag) -> cpu.handle_irqs -> pic.check_irqs -> cpu.pic_call_irq // (pic changes isr/irr) -> cpu.handle_irqs -> ... // triggering irqs: // (io device has irq) -> cpu.device_raise_irq -> pic.set_irq -> cpu.handle_irqs -> (see above) if(this.is_master) { this.slave = new PIC(this.cpu, this); this.check_irqs = function() { if(this.requested_irq >= 0) { PIC_LOG_VERBOSE && dbg_log("master> Already requested irq: " + this.requested_irq, LOG_PIC); this.cpu.handle_irqs(); return; } var enabled_irr = this.irr & this.irq_mask; if(!enabled_irr) { if(PIC_LOG_VERBOSE) { dbg_log("master> no unmasked irrs. irr=" + h(this.irr, 2) + " mask=" + h(this.irq_mask & 0xff, 2) + " isr=" + h(this.isr, 2), LOG_PIC); } return; } var irq_mask = enabled_irr & -enabled_irr; var special_mask = this.special_mask_mode ? this.irq_mask : -1; if(this.isr && (this.isr & -this.isr & special_mask) <= irq_mask) { // wait for eoi of higher or same priority interrupt dbg_log("master> higher prio: isr=" + h(this.isr, 2) + " mask=" + h(this.irq_mask & 0xff, 2) + " irq=" + h(irq_mask, 2), LOG_PIC); return; } dbg_assert(irq_mask !== 0); var irq_number = v86util.int_log2_byte(irq_mask); dbg_assert(irq_mask === (1 << irq_number)); PIC_LOG_VERBOSE && dbg_log("master> request irq " + irq_number, LOG_PIC); this.requested_irq = irq_number; this.cpu.handle_irqs(); }; this.acknowledge_irq = function() { if(this.requested_irq === -1) { return; } if(this.irr === 0) { PIC_LOG_VERBOSE && dbg_log("master> spurious requested=" + this.requested_irq, LOG_PIC); this.requested_irq = -1; //this.cpu.pic_call_irq(this.irq_map | 7); return; } dbg_assert(this.irr); // spurious dbg_assert(this.requested_irq >= 0); var irq_mask = 1 << this.requested_irq; if((this.elcr & irq_mask) === 0) // not in level mode { this.irr &= ~irq_mask; } if(!this.auto_eoi) { this.isr |= irq_mask; } PIC_LOG_VERBOSE && dbg_log("master> acknowledge " + this.requested_irq, LOG_PIC); if(this.requested_irq === 2) { this.slave.acknowledge_irq(); } else { this.cpu.pic_call_irq(this.irq_map | this.requested_irq); } this.requested_irq = -1; this.check_irqs(); }; } else { // is slave this.check_irqs = function() { if(this.requested_irq >= 0) { PIC_LOG_VERBOSE && dbg_log("slave > Already requested irq: " + this.requested_irq, LOG_PIC); this.cpu.handle_irqs(); return; } var enabled_irr = this.irr & this.irq_mask; if(!enabled_irr) { if(PIC_LOG_VERBOSE) { dbg_log("slave > no unmasked irrs. irr=" + h(this.irr, 2) + " mask=" + h(this.irq_mask & 0xff, 2) + " isr=" + h(this.isr, 2), LOG_PIC); } return; } var irq_mask = enabled_irr & -enabled_irr; var special_mask = this.special_mask_mode ? this.irq_mask : -1; if(this.isr && (this.isr & -this.isr & special_mask) <= irq_mask) { // wait for eoi of higher or same priority interrupt PIC_LOG_VERBOSE && dbg_log("slave > higher prio: isr=" + h(this.isr, 2) + " irq=" + h(irq_mask, 2), LOG_PIC); return; } dbg_assert(irq_mask !== 0); var irq_number = v86util.int_log2_byte(irq_mask); dbg_assert(irq_mask === (1 << irq_number)); PIC_LOG_VERBOSE && dbg_log("slave > request irq " + irq_number, LOG_PIC); this.requested_irq = irq_number; this.master.set_irq(2); }; this.acknowledge_irq = function() { if(this.requested_irq === -1) { return; } if(this.irr === 0) { PIC_LOG_VERBOSE && dbg_log("slave > spurious requested=" + this.requested_irq, LOG_PIC); this.requested_irq = -1; this.master.irq_value &= ~(1 << 2); this.cpu.pic_call_irq(this.irq_map | 7); return; } dbg_assert(this.irr); // spurious dbg_assert(this.requested_irq >= 0); var irq_mask = 1 << this.requested_irq; if((this.elcr & irq_mask) === 0) // not in level mode { this.irr &= ~irq_mask; } if(!this.auto_eoi) { this.isr |= irq_mask; } this.master.irq_value &= ~(1 << 2); PIC_LOG_VERBOSE && dbg_log("slave > acknowledge " + this.requested_irq, LOG_PIC); this.cpu.pic_call_irq(this.irq_map | this.requested_irq); this.requested_irq = -1; this.check_irqs(); }; } this.dump = function() { dbg_log("mask: " + h(this.irq_mask & 0xFF), LOG_PIC); dbg_log("base: " + h(this.irq_map), LOG_PIC); dbg_log("requested: " + h(this.irr), LOG_PIC); dbg_log("serviced: " + h(this.isr), LOG_PIC); if(this.is_master) { this.slave.dump(); } }; var io_base; var iobase_high; if(this.is_master) { io_base = 0x20; iobase_high = 0x4D0; } else { io_base = 0xA0; iobase_high = 0x4D1; } this.cpu.io.register_write(io_base, this, this.port20_write); this.cpu.io.register_read(io_base, this, this.port20_read); this.cpu.io.register_write(io_base | 1, this, this.port21_write); this.cpu.io.register_read(io_base | 1, this, this.port21_read); this.cpu.io.register_write(iobase_high, this, this.port4D0_write); this.cpu.io.register_read(iobase_high, this, this.port4D0_read); if(this.is_master) { this.set_irq = function(irq_number) { dbg_assert(irq_number >= 0 && irq_number < 16); if(irq_number >= 8) { this.slave.set_irq(irq_number - 8); return; } var irq_mask = 1 << irq_number; if((this.irq_value & irq_mask) === 0) { if(PIC_LOG_VERBOSE) { dbg_log("master> set irq " + irq_number, LOG_PIC); } this.irr |= irq_mask; this.irq_value |= irq_mask; this.check_irqs(); } else { if(PIC_LOG_VERBOSE) { dbg_log("master> set irq " + irq_number + ": already set!", LOG_PIC); } } }; this.clear_irq = function(irq_number) { dbg_assert(irq_number >= 0 && irq_number < 16); if(PIC_LOG_VERBOSE) { dbg_log("master> clear irq " + irq_number, LOG_PIC); } if(irq_number >= 8) { this.slave.clear_irq(irq_number - 8); return; } var irq_mask = 1 << irq_number; if(this.irq_value & irq_mask) { this.irq_value &= ~irq_mask; this.irr &= ~irq_mask; this.check_irqs(); } }; } else { this.set_irq = function(irq_number) { dbg_assert(irq_number >= 0 && irq_number < 8); var irq_mask = 1 << irq_number; if((this.irq_value & irq_mask) === 0) { if(PIC_LOG_VERBOSE) { dbg_log("slave > set irq " + irq_number, LOG_PIC); } this.irr |= irq_mask; this.irq_value |= irq_mask; this.check_irqs(); } else { if(PIC_LOG_VERBOSE) { dbg_log("slave > set irq " + irq_number + ": already set!", LOG_PIC); } } }; this.clear_irq = function(irq_number) { dbg_assert(irq_number >= 0 && irq_number < 8); if(PIC_LOG_VERBOSE) { dbg_log("slave > clear irq " + irq_number, LOG_PIC); } var irq_mask = 1 << irq_number; if(this.irq_value & irq_mask) { this.irq_value &= ~irq_mask; this.irr &= ~irq_mask; this.check_irqs(); } }; } this.get_isr = function() { return this.isr; }; } PIC.prototype.get_state = function() { var state = []; state[0] = this.irq_mask; state[1] = this.irq_map; state[2] = this.isr; state[3] = this.irr; state[4] = this.is_master; state[5] = this.slave; state[6] = this.expect_icw4; state[7] = this.state; state[8] = this.read_isr; state[9] = this.auto_eoi; state[10] = this.elcr; return state; }; PIC.prototype.set_state = function(state) { this.irq_mask = state[0]; this.irq_map = state[1]; this.isr = state[2]; this.irr = state[3]; this.is_master = state[4]; this.slave && this.slave.set_state(state[5]); this.expect_icw4 = state[6]; this.state = state[7]; this.read_isr = state[8]; this.auto_eoi = state[9]; this.elcr = state[10]; }; PIC.prototype.port20_write = function(data_byte) { //dbg_log("20 write: " + h(data_byte), LOG_PIC); if(data_byte & 0x10) // xxxx1xxx { // icw1 dbg_log("icw1 = " + h(data_byte), LOG_PIC); this.isr = 0; this.irr = 0; this.irq_mask = 0; this.irq_value = 0; this.auto_eoi = 1; this.requested_irq = -1; this.expect_icw4 = data_byte & 1; this.state = 1; } else if(data_byte & 8) // xxx01xxx { // ocw3 dbg_log("ocw3: " + h(data_byte), LOG_PIC); if(data_byte & 2) { this.read_isr = data_byte & 1; } if(data_byte & 4) { dbg_assert(false, "unimplemented: polling", LOG_PIC); } if(data_byte & 0x40) { this.special_mask_mode = (data_byte & 0x20) === 0x20; dbg_log("special mask mode: " + this.special_mask_mode, LOG_PIC); } } else // xxx00xxx { // ocw2 // end of interrupt dbg_log("eoi: " + h(data_byte) + " (" + this.name + ")", LOG_PIC); var eoi_type = data_byte >> 5; if(eoi_type === 1) { // non-specific eoi this.isr &= this.isr - 1; dbg_log("new isr: " + h(this.isr, 2), LOG_PIC); } else if(eoi_type === 3) { // specific eoi this.isr &= ~(1 << (data_byte & 7)); } else if((data_byte & 0xC8) === 0xC0) { // os2 v4 let priority = data_byte & 7; dbg_log("lowest priority: " + h(priority), LOG_PIC); } else { dbg_log("Unknown eoi: " + h(data_byte), LOG_PIC); dbg_assert(false); this.isr &= this.isr - 1; } this.check_irqs(); } }; PIC.prototype.port20_read = function() { if(this.read_isr) { dbg_log("read port 20h (isr): " + h(this.isr), LOG_PIC); return this.isr; } else { dbg_log("read port 20h (irr): " + h(this.irr), LOG_PIC); return this.irr; } }; PIC.prototype.port21_write = function(data_byte) { //dbg_log("21 write: " + h(data_byte), LOG_PIC); if(this.state === 0) { if(this.expect_icw4) { // icw4 this.expect_icw4 = false; this.auto_eoi = data_byte & 2; dbg_log("icw4: " + h(data_byte) + " autoeoi=" + this.auto_eoi, LOG_PIC); if((data_byte & 1) === 0) { dbg_assert(false, "unimplemented: not 8086 mode", LOG_PIC); } } else { // ocw1 this.irq_mask = ~data_byte; if(PIC_LOG_VERBOSE) { dbg_log("interrupt mask: " + (this.irq_mask & 0xFF).toString(2) + " (" + this.name + ")", LOG_PIC); } this.check_irqs(); } } else if(this.state === 1) { // icw2 this.irq_map = data_byte; dbg_log("interrupts are mapped to " + h(this.irq_map) + " (" + this.name + ")", LOG_PIC); this.state++; } else if(this.state === 2) { // icw3 this.state = 0; dbg_log("icw3: " + h(data_byte), LOG_PIC); } }; PIC.prototype.port21_read = function() { dbg_log("21h read " + h(~this.irq_mask & 0xff), LOG_PIC); return ~this.irq_mask & 0xFF; }; PIC.prototype.port4D0_read = function() { dbg_log("elcr read: " + h(this.elcr, 2), LOG_PIC); return this.elcr; }; PIC.prototype.port4D0_write = function(value) { dbg_log("elcr write: " + h(value, 2), LOG_PIC); // set by seabios to 00 0C (only set for pci interrupts) this.elcr = value; };