/* JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine Release Version 2.4 A project from the Physics Dept, The University of Oxford Copyright (C) 2007-2010 The University of Oxford This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Details (including contact information) can be found at: jpc.sourceforge.net or the developer website sourceforge.net/projects/jpc/ Conceived and Developed by: Rhys Newman, Ian Preston, Chris Dennis End of licence header */ package org.jpc.emulator.motherboard; import org.jpc.emulator.*; import org.jpc.emulator.processor.Processor; import org.jpc.j2se.*; import java.io.*; import java.util.logging.*; /** * i8259 Programmable Interrupt Controller emulation. */ /** * Emulation of an 8259 Programmable Interrupt Controller. * @see <a href="http://www.ee.hacettepe.edu.tr/~alkar/ELE414/8259.pdf">82C59A - Datasheet</a> * @author Chris Dennis */ public class InterruptController extends AbstractHardwareComponent implements IODevice { private static final Logger LOGGING = Logger.getLogger(InterruptController.class.getName()); private InterruptControllerElement master; private InterruptControllerElement slave; private Processor connectedCPU; /** * Constructs a <code>InterruptController</code> which will attach itself to * a <code>Processor</code> instance during the configuration stage. */ public InterruptController() { ioportRegistered = false; master = new InterruptControllerElement(true); slave = new InterruptControllerElement(false); } public void saveState(DataOutput output) throws IOException { master.saveState(output); slave.saveState(output); } public void loadState(DataInput input) throws IOException { ioportRegistered = false; master.loadState(input); slave.loadState(input); } public int getIRQ0Vector() { return master.irqBase; } public int getSpuriousVector() { return slave.irqBase + 7; } public int getSpuriousMasterVector() { return master.irqBase + 7; } public void triggerSpuriousInterrupt() { setIRQ(2, 0); setIRQ(2, 1); } public void triggerSpuriousMasterInterrupt() { connectedCPU.raiseInterrupt(); } public int getMasterIRR() { return master.interruptRequestRegister; } private void updateIRQ() { int slaveIRQ, masterIRQ; /* first look at slave irq */ slaveIRQ = slave.getIRQ(); if (slaveIRQ >= 0) { /* if irq request by slave pic, signal Master PIC */ master.setIRQ(2,1); master.setIRQ(2,0); } /* look at requested IRQ */ masterIRQ = master.getIRQ(); if(masterIRQ >= 0) { connectedCPU.raiseInterrupt(); } } /** * Set interrupt number <code>irqNumber</code> to level <code>level</code>. * @param irqNumber interrupt channel number. * @param level requested level. */ public void setIRQ(int irqNumber, int level) { switch (irqNumber >>> 3) { case 0: //master master.setIRQ(irqNumber & 7, level); this.updateIRQ(); break; case 1: //slave slave.setIRQ(irqNumber & 7, level); this.updateIRQ(); break; default: } } /** * Return the highest priority interrupt request currently awaiting service * on this interrupt controller. This is called by the processor emulation * once its <code>raiseInterrupt</code> method has been called, to get the * correct interrupt vector value. * @return highest priority interrupt vector. */ public int cpuGetInterrupt() { int masterIRQ, slaveIRQ; /* read the irq from the PIC */ masterIRQ = master.getIRQ(); if (masterIRQ >= 0) { master.intAck(masterIRQ); if (masterIRQ == 2) { slaveIRQ = slave.getIRQ(); if (slaveIRQ >= 0) { slave.intAck(slaveIRQ); } else { /* spurious IRQ on slave controller */ slaveIRQ = 7; } this.updateIRQ(); return slave.irqBase + slaveIRQ; //masterIRQ = slaveIRQ + 8; } else { this.updateIRQ(); return master.irqBase + masterIRQ; } } else { /* spurious IRQ on host controller */ masterIRQ = 7; this.updateIRQ(); return master.irqBase + masterIRQ; } } private class InterruptControllerElement implements Hibernatable { private int lastInterruptRequestRegister; //edge detection private int interruptRequestRegister; private int interruptMaskRegister; private int interruptServiceRegister; private int priorityAdd; // highest IRQ priority private int irqBase; private boolean readRegisterSelect; private boolean poll; private boolean specialMask; private int initState; private boolean fourByteInit; private int elcr; //(elcr) PIIX3 edge/level trigger selection private int elcrMask; private boolean specialFullyNestedMode; private boolean autoEOI; private boolean rotateOnAutoEOI; private int[] ioPorts; public InterruptControllerElement(boolean master) { if (master == true) { ioPorts = new int[]{0x20, 0x21, 0x4d0}; elcrMask = 0xf8; } else { ioPorts = new int[]{0xa0, 0xa1, 0x4d1}; elcrMask = 0xde; } } public void saveState(DataOutput output) throws IOException { output.writeInt(lastInterruptRequestRegister); output.writeInt(interruptRequestRegister); output.writeInt(interruptMaskRegister); output.writeInt(interruptServiceRegister); output.writeInt(priorityAdd); output.writeInt(irqBase); output.writeBoolean(readRegisterSelect); output.writeBoolean(poll); output.writeBoolean(specialMask); output.writeInt(initState); output.writeBoolean(autoEOI); output.writeBoolean(rotateOnAutoEOI); output.writeBoolean(specialFullyNestedMode); output.writeBoolean(fourByteInit); output.writeInt(elcr); output.writeInt(elcrMask); output.writeInt(ioPorts.length); for (int port : ioPorts) output.writeInt(port); } public void loadState(DataInput input) throws IOException { lastInterruptRequestRegister = input.readInt(); interruptRequestRegister = input.readInt(); interruptMaskRegister = input.readInt(); interruptServiceRegister = input.readInt(); priorityAdd = input.readInt(); irqBase = input.readInt(); readRegisterSelect = input.readBoolean(); poll = input.readBoolean(); specialMask = input.readBoolean(); initState = input.readInt(); autoEOI = input.readBoolean(); rotateOnAutoEOI = input.readBoolean(); specialFullyNestedMode = input.readBoolean(); fourByteInit = input.readBoolean(); elcr = input.readInt(); elcrMask = input.readInt(); int len = input.readInt(); ioPorts = new int[len]; for (int i=0; i< len; i++) ioPorts[i] = input.readInt(); } /* BEGIN IODevice Methods */ public int[] ioPortsRequested() { return ioPorts; } public int ioPortRead(int address) { if(poll) { poll = false; return this.pollRead(address); } if ((address & 1) == 0) { if (readRegisterSelect) { return interruptServiceRegister; } return interruptRequestRegister; } return interruptMaskRegister; } public int elcrRead() { return elcr; } public boolean ioPortWrite(int address, byte data) //t/f updateIRQ { int priority, command, irq; address &= 1; if (address == 0) { if (0 != (data & 0x10)) { /* init */ this.reset(); connectedCPU.clearInterrupt(); initState = 1; fourByteInit = ((data & 1) != 0); if (0 != (data & 0x02)) LOGGING.log(Level.INFO, "single mode not supported"); if (0 != (data & 0x08)) LOGGING.log(Level.INFO, "level sensitive irq not supported"); } else if (0 != (data & 0x08)) { if (0 != (data & 0x04)) poll = true; if (0 != (data & 0x02)) readRegisterSelect = ((data & 0x01) != 0); if (0 != (data & 0x40)) specialMask = (((data >>> 5) & 1) != 0); } else { command = data >>> 5; switch(command) { case 0: case 4: rotateOnAutoEOI = ((command >>> 2) != 0); break; case 1: // end of interrupt case 5: priority = this.getPriority(interruptServiceRegister); if (priority != 8) { irq = (priority + priorityAdd) & 7; interruptServiceRegister &= ~(1 << irq); if (command == 5) priorityAdd = (irq + 1) & 7; return true; } break; case 3: irq = data & 7; interruptServiceRegister &= ~(1 << irq); return true; case 6: priorityAdd = (data + 1) & 7; return true; case 7: irq = data & 7; interruptServiceRegister &= ~(1 << irq); priorityAdd = (irq + 1) & 7; return true; default: /* no operation */ break; } } } else { switch(initState) { case 0: /* normal mode */ interruptMaskRegister = data; return true; case 1: irqBase = data & 0xf8; initState = 2; break; case 2: if (fourByteInit) { initState = 3; } else { initState = 0; } break; case 3: specialFullyNestedMode = (((data >>> 4) & 1) != 0); autoEOI = (((data >>> 1) & 1) != 0); initState = 0; break; } } return false; } public void elcrWrite(int data) { elcr = data & elcrMask; } /* END IODevice Methods */ private int pollRead(int address) { int ret = this.getIRQ(); if (ret < 0) { InterruptController.this.updateIRQ(); return 0x07; } if (0 != (address >>> 7)) { InterruptController.this.masterPollCode(); } interruptRequestRegister &= ~(1 << ret); interruptServiceRegister &= ~(1 << ret); if (0 != (address >>> 7) || ret != 2) InterruptController.this.updateIRQ(); return ret; } public void setIRQ(int irqNumber, int level) { int mask; mask = (1 << irqNumber); if(0 != (elcr & mask)) { /* level triggered */ if (0 != level) { interruptRequestRegister |= mask; lastInterruptRequestRegister |= mask; } else { interruptRequestRegister &= ~mask; lastInterruptRequestRegister &= ~mask; } } else { /* edge triggered */ if (0 != level) { if ((lastInterruptRequestRegister & mask) == 0) { interruptRequestRegister |= mask; } lastInterruptRequestRegister |= mask; } else { lastInterruptRequestRegister &= ~mask; } } } private int getPriority(int mask) { if ((0xff & mask) == 0) { return 8; } int priority = 0; while ((mask & (1 << ((priority + priorityAdd) & 7))) == 0) { priority++; } return priority; } public int getIRQ() { int mask, currentPriority, priority; mask = interruptRequestRegister & ~interruptMaskRegister; priority = this.getPriority(mask); if (priority == 8) { return -1; } /* compute current priority. If special fully nested mode on the master, the IRQ coming from the slave is not taken into account for the priority computation. */ mask = interruptServiceRegister; if (specialFullyNestedMode && this.isMaster()) { mask &= ~(1 << 2); } currentPriority = this.getPriority(mask); if (priority < currentPriority) { /* higher priority found: an irq should be generated */ return (priority + priorityAdd) & 7; } else { return -1; } } private void intAck(int irqNumber) { if (autoEOI) { if (rotateOnAutoEOI) priorityAdd = (irqNumber + 1) & 7; } else { interruptServiceRegister |= (1 << irqNumber); } /* We don't clear a level sensitive interrupt here */ if (0 == (elcr & (1 << irqNumber))) interruptRequestRegister &= ~(1 << irqNumber); } private boolean isMaster() { return InterruptController.this.master == this; } private void reset() { //zero all variables except elcrMask lastInterruptRequestRegister = 0x0; interruptRequestRegister = 0x0; interruptMaskRegister = 0x0; interruptServiceRegister = 0x0; priorityAdd = 0; irqBase = 0x0; readRegisterSelect = false; poll = false; specialMask = false; autoEOI = false; rotateOnAutoEOI = false; specialFullyNestedMode = false; initState = 0; fourByteInit = false; elcr = 0x0; //(elcr) PIIX3 edge/level trigger selection } public String toString() { if (isMaster()) { return (InterruptController.this).toString() + ": [Master Element]"; } else { return (InterruptController.this).toString() + ": [Slave Element]"; } } } /* BEGIN IODevice Defined Methods */ public int[] ioPortsRequested() { int[] masterIOPorts = master.ioPortsRequested(); int[] slaveIOPorts = slave.ioPortsRequested(); int[] temp = new int[masterIOPorts.length + slaveIOPorts.length]; System.arraycopy(masterIOPorts, 0, temp, 0, masterIOPorts.length); System.arraycopy(slaveIOPorts, 0, temp, masterIOPorts.length, slaveIOPorts.length); return temp; } public int ioPortRead8(int address) { switch (address) { case 0x20: case 0x21: return 0xff & master.ioPortRead(address); case 0xa0: case 0xa1: return 0xff & slave.ioPortRead(address); case 0x4d0: return 0xff & master.elcrRead(); case 0x4d1: return 0xff & slave.elcrRead(); default: } return 0; } public int ioPortRead16(int address) { return (0xff & ioPortRead8(address)) | (0xff00 & (ioPortRead8(address + 1) << 8)); } public int ioPortRead32(int address) { return (0xffff & ioPortRead16(address)) | (0xffff0000 & (ioPortRead16(address + 2) << 16)); } public void ioPortWrite8(int address, int data) { switch (address) { case 0x20: case 0x21: if (master.ioPortWrite(address, (byte)data)) this.updateIRQ(); break; case 0xa0: case 0xa1: if (slave.ioPortWrite(address, (byte)data)) this.updateIRQ(); break; case 0x4d0: master.elcrWrite(data); break; case 0x4d1: slave.elcrWrite(data); break; default: } } public void ioPortWrite16(int address, int data) { this.ioPortWrite8(address, data); this.ioPortWrite8(address + 1, data >>> 8); } public void ioPortWrite32(int address, int data) { this.ioPortWrite16(address, data); this.ioPortWrite16(address + 2, data >>> 16); } /* END IODevice Defined Methods */ private void masterPollCode() { master.interruptServiceRegister &= ~(1 << 2); master.interruptRequestRegister &= ~(1 << 2); } private boolean ioportRegistered; public void reset() { master.reset(); slave.reset(); ioportRegistered = false; connectedCPU = null; } public boolean initialised() { return ((connectedCPU != null) && ioportRegistered); } public boolean updated() { return ioportRegistered; } public void updateComponent(HardwareComponent component) { if (component instanceof IOPortHandler) { ((IOPortHandler)component).registerIOPortCapable(this); ioportRegistered = true; } } public void acceptComponent(HardwareComponent component) { if (component instanceof Processor) connectedCPU = (Processor)component; if ((component instanceof IOPortHandler) && component.initialised()) { ((IOPortHandler)component).registerIOPortCapable(this); ioportRegistered = true; } } public String toString() { return "Intel i8259 Programmable Interrupt Controller"; } }