/* 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.peripheral.PCSpeaker; import org.jpc.j2se.*; import org.jpc.support.Clock; import org.jpc.emulator.*; import java.io.*; /** * Emulation of an 8254 Interval Timer. * @see <a href="http://bochs.sourceforge.net/techspec/intel-82c54-timer.pdf.gz"> * 82C54 - Datasheet</a> * @author Chris Dennis, Ian Preston */ public class IntervalTimer extends AbstractHardwareComponent implements IODevice { private static final int RW_STATE_LSB = 1; private static final int RW_STATE_MSB = 2; private static final int RW_STATE_WORD = 3; private static final int RW_STATE_WORD_2 = 4; private static final int MODE_INTERRUPT_ON_TERMINAL_COUNT = 0; private static final int MODE_HARDWARE_RETRIGGERABLE_ONE_SHOT = 1; private static final int MODE_RATE_GENERATOR = 2; private static final int MODE_SQUARE_WAVE = 3; private static final int MODE_SOFTWARE_TRIGGERED_STROBE = 4; private static final int MODE_HARDWARE_TRIGGERED_STROBE = 5; private static final int CONTROL_ADDRESS = 3; public static final int PIT_FREQ = Option.useBochs.isSet() ? 1193181 : 1193182; private TimerChannel[] channels; private InterruptController irqDevice; private Clock timingSource; private PCSpeaker speaker; private boolean madeNewTimer; private boolean ioportRegistered; private int ioPortBase; private int irq; private BochsPIT bochs; public int[] getState() { int[] state = new int[3*4]; channels[0].getState(state, 0); channels[1].getState(state, 4); channels[2].getState(state, 8); return state; } private static final long scale64(long input, int multiply, int divide) { long rl = (0xffffffffl & input) * multiply; long rh = (input >>> 32) * multiply; rh += (rl >> 32); long resultHigh = 0xffffffffl & (rh / divide); long resultLow = 0xffffffffl & ((((rh % divide) << 32) + (rl & 0xffffffffl)) / divide); return (resultHigh << 32) | resultLow; } /** * Construct a new Interval Timer which will register at ioports * <code>ioPort</code> to <code>ioPort+3</code>. Interrupt requests will be * sent on the supplied channel number. * @param ioPort ioport base address. * @param irq interrupt channel number. */ public IntervalTimer(int ioPort, int irq) { this.irq = irq; ioPortBase = ioPort; if (Option.useBochs.isSet()) bochs = new BochsPIT(Option.ips.intValue(150000000)); } public void saveState(DataOutput output) throws IOException { output.writeInt(channels.length); for (TimerChannel channel : channels) { channel.saveState(output); } } public void loadState(DataInput input) throws IOException { madeNewTimer = false; ioportRegistered = false; int len = input.readInt(); channels = new TimerChannel[len]; for (int i = 0; i < channels.length; i++) { //if (i>10) System.exit(0); channels[i] = new TimerChannel(i); channels[i].loadState(input); } } public int[] ioPortsRequested() { return new int[]{ioPortBase, ioPortBase + 1, ioPortBase + 2, ioPortBase + 3}; } public int ioPortRead32(int address) { return (ioPortRead16(address) & 0xffff) | ((ioPortRead16(address + 2) << 16) & 0xffff0000); } public int ioPortRead16(int address) { return (ioPortRead8(address) & 0xff) | ((ioPortRead8(address + 1) << 8) & 0xff00); } public int ioPortRead8(int address) { if ((address & 3) == 3) return 0; // read from control word register return channels[address & 0x3].read(); } public void ioPortWrite32(int address, int data) { this.ioPortWrite16(address, 0xffff & data); this.ioPortWrite16(address + 2, 0xffff & (data >>> 16)); } public void ioPortWrite16(int address, int data) { this.ioPortWrite8(address, 0xff & data); this.ioPortWrite8(address + 1, 0xff & (data >>> 8)); } public void ioPortWrite8(int address, int data) { data &= 0xff; address &= 0x3; if (address == CONTROL_ADDRESS) { //writing control word int channel = data >>> 6; if (channel == 3) { // read back command for (channel = 0; channel < 3; channel++) { if (0 != (data & (2 << channel))) // if channel enabled { channels[channel].readBack(data); } } } else { channels[channel].writeControlWord(data); } } else //writing to a channels counter { channels[address].write(data); if (address == 2) //notify PCSpeaker of timer change { speaker.play(); } } } public void reset() { irqDevice = null; timingSource = null; ioportRegistered = false; } /** * Get the output pin state of the specified channel. * @param channel selected channel index. * @return counter output pin level. */ public int getOut(int channel) { return channels[channel].getOut(getTime()); } public int getMode(int channel) { return channels[channel].getMode(); } public int getInitialCount(int channel) { return channels[channel].getInitialCount(); } /** * Get the gate pin state of the specified channel. * <p> * The gate pin indicates whether the selected channel is enabled or * disabled. A logic high level indicates enabled and logic low indicates * disabled. * @param channel selected channel index. * @return counter gate pin level. */ public boolean getGate(int channel) { return channels[channel].gate; } /** * Set the gate pin state of the specified channel. * <p> * Broadly speaking setting the gate low will disable the timer, and setting * it high will enable the timer, although exact behaviour is dependent on * the current channel mode. * @param channel selected channel index. * @param value required gate status. */ public void setGate(int channel, boolean value) { channels[channel].setGate(value); } private long getTime() { return timingSource.getEmulatedMicros(); } private long getTickRate() { return 1000000; // use micro second units } private int conversionFactor() { return (int)(timingSource.getTickRate()/getTickRate()); } enum RW_Status {LSByte, MSByte, LSByte_Multple, MSByte_multiple} public class TimerChannel extends AbstractHardwareComponent implements TimerResponsive { private int countValue; // U32 private int outputLatch; // U16 private int inputLatch; // U16 private boolean countLatched_LSB; private boolean countLatched_MSB; private boolean statusLatched; private boolean nullCount; private boolean gate; private int statusLatch; // U8 private RW_Status readState = RW_Status.LSByte; private RW_Status writeState = RW_Status.LSByte; private int rwMode;// 2 bits from command word register private int mode; // 3 bits from command word register private boolean bcd; /* uimplemented */ private long countStartCycles; // currently these are microseconds /* irq handling */ private long nextChangeTime; private Timer irqTimer; private int irq; public TimerChannel(int index) { mode = MODE_SQUARE_WAVE; gate = true; loadCount(0); nullCount = true; } private void getState(int[] buf, int start) { buf[start] = getCount() % 0x10000; buf[start +1] = gate?1:0; buf[start +2] = getOut(getTime()); buf[start +3] = 0; } public void saveState(DataOutput output) throws IOException { output.writeInt(countValue); output.writeInt(outputLatch); output.writeInt(inputLatch); output.writeInt((countLatched_MSB ? 2 : 0) | (countLatched_LSB ? 1 : 0)); output.writeBoolean(statusLatched); output.writeBoolean(gate); output.writeInt(statusLatch); output.writeInt(readState.ordinal()); output.writeInt(writeState.ordinal()); output.writeInt(rwMode); output.writeInt(mode); output.writeInt(bcd ? 1 : 0); output.writeLong(countStartCycles); output.writeLong(nextChangeTime); if (irqTimer == null) { output.writeInt(0); } else { output.writeInt(1); irqTimer.saveState(output); } } public void loadState(DataInput input) throws IOException { countValue = input.readInt(); outputLatch = input.readInt(); inputLatch = input.readInt(); int cl = input.readInt(); countLatched_LSB = (cl & 1) != 0; countLatched_MSB = (cl & 2) != 0; statusLatched = input.readBoolean(); gate = input.readBoolean(); statusLatch = input.readInt(); readState = RW_Status.values()[input.readInt()]; writeState = RW_Status.values()[input.readInt()]; rwMode = input.readInt(); mode = input.readInt(); bcd = input.readInt() != 0; countStartCycles = input.readLong(); nextChangeTime = input.readLong(); int test = input.readInt(); if (test == 1) { irqTimer = timingSource.newTimer(this); irqTimer.loadState(input); } } public int read() { if (statusLatched) { if (countLatched_MSB && (readState == RW_Status.MSByte_multiple)) throw new IllegalStateException("status is latched and count half read"); statusLatched = false; return statusLatch; } // latched count read if (countLatched_LSB) { // read LSB if (readState == RW_Status.LSByte_Multple) { readState = RW_Status.MSByte_multiple; } countLatched_LSB = false; return outputLatch & 0xff; } if (countLatched_MSB) { // read MSB if (readState == RW_Status.MSByte_multiple) { readState = RW_Status.LSByte_Multple; } countLatched_MSB = false; return (outputLatch >> 8) & 0xff; } if (!((readState == RW_Status.MSByte) || (readState == RW_Status.MSByte_multiple))) { // read LSB if (readState == RW_Status.LSByte_Multple) readState = RW_Status.MSByte_multiple; return getCount() & 0xff; } else { if (readState == RW_Status.MSByte_multiple) readState = RW_Status.LSByte_Multple; return (getCount() >> 8) & 0xff; } } public void readBack(int data) { if (0 == (data & 0x20)) //latch count { latchCount(); } if (0 == (data & 0x10)) //latch status { latchStatus(); } } private void latchCount() { // do nothing if previous latch state hasn't been read if (!countLatched_LSB && !countLatched_MSB) { switch (readState) { case MSByte: outputLatch = 0xffff & getCount(); countLatched_MSB = true; break; case LSByte: outputLatch = 0xffff & getCount(); countLatched_LSB = true; break; case LSByte_Multple: outputLatch = 0xffff & getCount(); countLatched_LSB = true; countLatched_MSB = true; break; case MSByte_multiple: readState = RW_Status.LSByte_Multple; outputLatch = 0xffff & getCount(); countLatched_LSB = true; countLatched_MSB = true; break; } } } private void latchStatus() { if (!statusLatched) { statusLatch = (getOut(getTime()) << 7) | (nullCount ? 0x40 : 0x00) | (rwMode << 4) | (mode << 1) | (bcd ? 1 : 0); statusLatched = true; } } public void write(int data) { switch (writeState) { default: throw new IllegalStateException("write counter in invalid write state"); case LSByte: loadCount(0xff & data); break; case MSByte: loadCount((0xff & data) << 8); break; case LSByte_Multple: //null count setting is delayed until after second byte is written inputLatch = data; writeState = RW_Status.MSByte_multiple; break; case MSByte_multiple: writeState = RW_Status.LSByte_Multple; loadCount((0xff & inputLatch) | ((0xff & data) << 8)); break; } } public void writeControlWord(int data) { int RW = (data >> 4 ) & 3; if (RW == 0) { latchCount(); } else { nullCount = true; countLatched_LSB = false; countLatched_MSB = false; statusLatched = false; inputLatch = 0; rwMode = RW; bcd = (data & 1) != 0; mode = (data >> 1) &7; switch (RW) { case 1: // set read state to LSB readState = RW_Status.LSByte; writeState = RW_Status.LSByte; break; case 2: // set read state to MSB readState = RW_Status.MSByte; writeState = RW_Status.MSByte; break; case 3: // set read state to LSB multiple readState = RW_Status.LSByte_Multple; writeState = RW_Status.LSByte_Multple; break; default: throw new IllegalStateException("Invalid RW access field in control word write of PIT"); } } } public void setGate(boolean value) { switch (mode) { default: case MODE_INTERRUPT_ON_TERMINAL_COUNT: case MODE_SOFTWARE_TRIGGERED_STROBE: /* XXX: just disable/enable counting */ break; case MODE_HARDWARE_RETRIGGERABLE_ONE_SHOT: case MODE_HARDWARE_TRIGGERED_STROBE: if (!gate && value) { /* restart counting on rising edge */ countStartCycles = timingSource.getEmulatedNanos(); irqTimerUpdate(countStartCycles); } break; case MODE_RATE_GENERATOR: case MODE_SQUARE_WAVE: if (!gate && value) { /* restart counting on rising edge */ countStartCycles = timingSource.getEmulatedNanos(); irqTimerUpdate(countStartCycles); } /* XXX: disable/enable counting */ break; } this.gate = value; } private int getCount() { long now = scale64(timingSource.getEmulatedNanos() - countStartCycles, PIT_FREQ, (int) timingSource.getTickRate()); switch (mode) { case MODE_INTERRUPT_ON_TERMINAL_COUNT: case MODE_HARDWARE_RETRIGGERABLE_ONE_SHOT: case MODE_SOFTWARE_TRIGGERED_STROBE: case MODE_HARDWARE_TRIGGERED_STROBE: return (int) ((countValue - now) & 0xffffl); case MODE_SQUARE_WAVE: return (int) (countValue - ((2 * now) % countValue)); case MODE_RATE_GENERATOR: default: return (int) (countValue - (now % countValue)); } } private int getOut(long now) { switch (mode) { default: case MODE_INTERRUPT_ON_TERMINAL_COUNT: if (now >= countValue) { return 1; } else { return 0; } case MODE_HARDWARE_RETRIGGERABLE_ONE_SHOT: if (now < countValue) { return 1; } else { return 0; } case MODE_RATE_GENERATOR: if ((now % countValue) == countValue-1) return 0; else return 1; case MODE_SQUARE_WAVE: if ((now % countValue) < ((countValue + 1) >>> 1)) { return 1; } else { return 0; } case MODE_SOFTWARE_TRIGGERED_STROBE: case MODE_HARDWARE_TRIGGERED_STROBE: if (now == countValue) { return 1; } else { return 0; } } } private long getNextTransitionTime(long currentTime) { switch (mode) { default: case MODE_INTERRUPT_ON_TERMINAL_COUNT: case MODE_HARDWARE_RETRIGGERABLE_ONE_SHOT: { if (currentTime < countValue) { return countValue; } else { return -1; } } case MODE_RATE_GENERATOR: { long base = (currentTime / countValue) * countValue; if ((currentTime == base)) { return base + countValue-1; } else { return base + countValue; } } case MODE_SQUARE_WAVE: { long base = (currentTime / countValue) * countValue; long period2 = ((countValue + 1) >>> 1); if ((currentTime - base) < period2) { return base + period2; } else { return base + countValue; } } case MODE_SOFTWARE_TRIGGERED_STROBE: case MODE_HARDWARE_TRIGGERED_STROBE: { if (currentTime < countValue) { return countValue; } else if (currentTime == countValue) { return countValue + 1; } else { return -1; } } } } private void loadCount(int value) { nullCount = false; if (value == 0) { value = 0x10000; } countStartCycles = timingSource.getEmulatedNanos(); countValue = value; this.irqTimerUpdate(0); } private void irqTimerUpdate(long currentTime) { if (irqTimer == null) { return; } nextChangeTime = getNextTransitionTime(currentTime); int out = getOut(currentTime); irqDevice.setIRQ(irq, out); // long nanos = convertCyclesToNanos(convertPitTicksToCycles(countStartCycles, nextChangeTime)); long nanos = countStartCycles + scale64(nextChangeTime, 1000000000, PIT_FREQ); if (irqTimer.getExpiry() == nanos) // we need to trigger the next int now { irqTimer.setExpiry(nanos); timingSource.updateAndProcess(0); } else { if (nextChangeTime != -1) { irqTimer.setExpiry(nanos); } else { irqTimer.disable(); } } } public int getMode() { return this.mode; } public int getInitialCount() { return this.countValue; } public void callback() { this.irqTimerUpdate(nextChangeTime); } public void setIRQTimer(Timer object) { irqTimer = object; } public void setIRQ(int irq) { this.irq = irq; } public int getType() { return 2; } } public boolean initialised() { return ((irqDevice != null) && (timingSource != null)) && ioportRegistered; } public boolean updated() { return (irqDevice.updated() && timingSource.updated()) && ioportRegistered; } public void updateComponent(HardwareComponent component) { if (component instanceof IOPortHandler) { ((IOPortHandler) component).registerIOPortCapable(this); ioportRegistered = true; } if (this.updated() && !madeNewTimer) { channels[0].setIRQTimer(timingSource.newTimer(channels[0])); madeNewTimer = true; } } public void acceptComponent(HardwareComponent component) { if ((component instanceof InterruptController) && component.initialised()) { irqDevice = (InterruptController) component; } if ((component instanceof Clock) && component.initialised()) { timingSource = (Clock) component; } if ((component instanceof IOPortHandler) && component.initialised()) { ((IOPortHandler) component).registerIOPortCapable(this); ioportRegistered = true; } if (component instanceof PCSpeaker) { speaker = (PCSpeaker) component; } if (this.initialised() && (channels == null)) { channels = new TimerChannel[3]; for (int i = 0; i < channels.length; i++) { channels[i] = new TimerChannel(i); } channels[0].setIRQTimer(timingSource.newTimer(channels[0])); channels[0].setIRQ(irq); } } public String toString() { return "Intel i8254 Interval Timer"; } }