/* 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.pci; import org.jpc.emulator.motherboard.IOPortHandler; import org.jpc.emulator.memory.PhysicalAddressSpace; import org.jpc.emulator.pci.peripheral.VGACard; import org.jpc.emulator.*; import java.io.*; import java.util.logging.*; /** * Provides an implementation of a PCI bus to allow access to all PCI devices. * <p> * Currently the PCI bus also performs the auto-configuration of all PCI devices * a role which will eventually be taken over by a later version of the system * bios. * @author Chris Dennis */ public class PCIBus extends AbstractHardwareComponent { private static final Logger LOGGING = Logger.getLogger(PCIBus.class.getName()); static final int PCI_DEVICES_MAX = 64; static final int PCI_IRQ_WORDS = ((PCI_DEVICES_MAX + 31) / 32); private static final byte[] PCI_IRQS = new byte[]{11, 9, 11, 9}; private int busNumber; private int devFNMinimum; private boolean updated; private int biosIOAddress; private int biosMemoryAddress; private PCIDevice devices[]; private PCIISABridge isaBridge; private IOPortHandler ioports; private PhysicalAddressSpace memory; private int pciIRQIndex; private int pciIRQLevels[][]; /** * Constructs a default primary PCI bus. * <p> * This bus can support up to 256 devices, currently only one PCI bus is * supported by the JPC emulation. */ public PCIBus() { busNumber = 0; pciIRQIndex = 0; devices = new PCIDevice[256]; pciIRQLevels = new int[4][PCI_IRQ_WORDS]; devFNMinimum = 8; } public void saveState(DataOutput output) throws IOException { output.writeInt(busNumber); output.writeInt(devFNMinimum); output.writeInt(pciIRQIndex); output.writeInt(pciIRQLevels.length); output.writeInt(pciIRQLevels[0].length); for (int[] inner : pciIRQLevels) { for (int level : inner) { output.writeInt(level); } } System.out.println(biosIOAddress); System.out.println(biosMemoryAddress); output.writeInt(biosIOAddress); output.writeInt(biosMemoryAddress); } public void loadState(DataInput input) throws IOException { updated = false; devices = new PCIDevice[256]; busNumber = input.readInt(); devFNMinimum = input.readInt(); pciIRQIndex = input.readInt(); int len1 = input.readInt(); int len2 = input.readInt(); pciIRQLevels = new int[len1][len2]; for (int i = 0; i < pciIRQLevels.length; i++) { for (int j = 0; j < pciIRQLevels[i].length; j++) { pciIRQLevels[i][j] = input.readInt(); } } biosIOAddress = input.readInt(); biosMemoryAddress = input.readInt(); } /** * Connect a device to this PCI bus. * <p> * This will trigger the allocation of three basic resources. Firstly the * device is assigned/chooses a device/function number. Secondly an * <code>IRQBouncer</code> is created so that this device can throw interrupts * when necessary. Thirdly the devices io-regions are registered, but not * yet allocated addresses. * <p> * If this bus can support no more devices, or if the device returns an * invalid io-region. * @param device PCI card to be connected. * @return <code>true</code> if the device is successfully registered. */ public boolean registerDevice(PCIDevice device) { if (pciIRQIndex >= PCI_DEVICES_MAX) { return false; } if (device.autoAssignDeviceFunctionNumber()) { int devFN = findFreeDevFN(); if (0 <= devFN) { device.assignDeviceFunctionNumber(devFN); } } else { PCIDevice oldDevice = devices[device.getDeviceFunctionNumber()]; if (oldDevice != null) { LOGGING.log(Level.INFO, "unregistering pci device {0}", oldDevice); oldDevice.deassignDeviceFunctionNumber(); } } if (device.getIRQIndex() == -1) device.setIRQIndex(pciIRQIndex++); this.addDevice(device); IRQBouncer bouncer = isaBridge.makeBouncer(device); device.addIRQBouncer(bouncer); return this.registerPCIIORegions(device); } private int findFreeDevFN() { for (int i = devFNMinimum; i < 256; i += 8) { if (null == devices[i]) { return i; } } return -1; } private boolean registerPCIIORegions(PCIDevice device) { IORegion[] regions = device.getIORegions(); if (regions == null) { return true; } boolean ret = true; for (IORegion region : regions) { if (PCIDevice.PCI_NUM_REGIONS <= region.getRegionNumber()) { ret = false; continue; } //region.setAddress(-1); if (region.getRegionNumber() == PCIDevice.PCI_ROM_SLOT) { device.putConfigLong(PCIDevice.PCI_CONFIG_EXPANSION_ROM_BASE_ADDRESS, region.getType()); } else { device.putConfigLong(PCIDevice.PCI_CONFIG_BASE_ADDRESS + region.getRegionNumber() * 4, region.getType()); } } return ret; } private void updateMappings(PCIDevice device) { IORegion[] regions = device.getIORegions(); if (regions == null) { return; } short command = device.configReadWord(PCIDevice.PCI_CONFIG_COMMAND); for (IORegion region : regions) { if (null == region) { continue; } if (PCIDevice.PCI_NUM_REGIONS <= region.getRegionNumber()) { continue; } int configOffset; if (PCIDevice.PCI_ROM_SLOT == region.getRegionNumber()) { configOffset = PCIDevice.PCI_CONFIG_EXPANSION_ROM_BASE_ADDRESS; } else { configOffset = PCIDevice.PCI_CONFIG_BASE_ADDRESS + region.getRegionNumber() * 4; } int newAddress = -1; if (region instanceof IOPortIORegion) { if (0 != (command & PCIDevice.PCI_COMMAND_IO)) { newAddress = device.configReadLong(configOffset); newAddress &= ~(region.getSize() - 1); int lastAddress = newAddress + (int) region.getSize() - 1; if (lastAddress <= (0xffffffffl & newAddress) || 0 == newAddress || 0x10000 <= (0xffffffffl & lastAddress)) { newAddress = -1; } } } else if (region instanceof MemoryMappedIORegion) { if (0 != (command & PCIDevice.PCI_COMMAND_MEMORY)) { newAddress = device.configReadLong(configOffset); if (PCIDevice.PCI_ROM_SLOT == region.getRegionNumber() && (0 == (newAddress & 1))) { newAddress = -1; } else { newAddress &= ~(region.getSize() - 1); int lastAddress = newAddress + (int) region.getSize() - 1; if (lastAddress <= newAddress || 0 == newAddress || -1 == lastAddress) { newAddress = -1; } } } } else { throw new IllegalStateException("Unknown IORegion Type"); } if (region.getAddress() != newAddress) { if (region.getAddress() != -1) { if (region instanceof IOPortIORegion) { int deviceClass = device.configReadWord(PCIDevice.PCI_CONFIG_CLASS_DEVICE); if (0x0101 == deviceClass && 4 == region.getSize()) { //r.unmap(); must actually be partial LOGGING.log(Level.WARNING, "supposed to partially unmap"); ioports.deregisterIOPortCapable((IOPortIORegion) region); } else //r.unmap(); { ioports.deregisterIOPortCapable((IOPortIORegion) region); } } else if (region instanceof MemoryMappedIORegion) { memory.unmap(region.getAddress(), (int) region.getSize()); } } region.setAddress(newAddress); if (region.getAddress() != -1) { if (region instanceof IOPortIORegion) { ioports.registerIOPortCapable((IOPortIORegion) region); } else if (region instanceof MemoryMappedIORegion) { memory.mapMemoryRegion((MemoryMappedIORegion) region, region.getAddress(), (int) region.getSize()); } } } } } private void loadMappings(PCIDevice device) { IORegion[] regions = device.getIORegions(); for (IORegion region : regions) if (region.getAddress() != -1) { if (region instanceof IOPortIORegion) ioports.registerIOPortCapable((IOPortIORegion) region); else if (region instanceof MemoryMappedIORegion) memory.mapMemoryRegion((MemoryMappedIORegion) region, region.getAddress(), (int) region.getSize()); } } private void addDevice(PCIDevice device) { devices[device.getDeviceFunctionNumber()] = device; } //PCIHostBridge shifted functionality private PCIDevice validPCIDataAccess(int address) { int bus = (address >>> 16) & 0xff; if (0 != bus) { return null; } return this.devices[(address >>> 8) & 0xff]; } void writePCIDataByte(int address, byte data) { PCIDevice device = this.validPCIDataAccess(address); if (null == device) { return; } if (device.configWriteByte(address & 0xff, data)) { this.updateMappings(device); } } void writePCIDataWord(int address, short data) { PCIDevice device = this.validPCIDataAccess(address); if (null == device) { return; } if (device.configWriteWord(address & 0xff, data)) { this.updateMappings(device); } } void writePCIDataLong(int address, int data) { PCIDevice device = this.validPCIDataAccess(address); if (null == device) { return; } if (device.configWriteLong(address & 0xff, data)) { this.updateMappings(device); } } byte readPCIDataByte(int address) { PCIDevice device = this.validPCIDataAccess(address); if (null == device) { return (byte) 0xff; } return device.configReadByte(address & 0xff); } short readPCIDataWord(int address) { PCIDevice device = this.validPCIDataAccess(address); if (null == device) { return (short) 0xffff; } return device.configReadWord(address & 0xff); } int readPCIDataLong(int address) { PCIDevice device = this.validPCIDataAccess(address); if (null == device) { return 0xffffffff; } return device.configReadLong(address & 0xff); } /** * Performs the auto-configuration of PCI devices that is normally handled * by the system bios. * <p> * In later versions this method should be replaced by a more featured BIOS * ROM image, coupled with a more complete PCI emulation. */ public void biosInit() { biosIOAddress = 0xc000; biosMemoryAddress = 0xf0000000; byte elcr[] = new byte[2]; /* activate IRQ mappings */ elcr[0] = 0x00; elcr[1] = 0x00; for (int i = 0; i < 4; i++) { byte irq = PCI_IRQS[i]; /* set to trigger level */ elcr[irq >> 3] |= (1 << (irq & 7)); /* activate irq remapping in PIIX */ isaBridge.configWriteByte(0x60 + i, irq); } ioports.ioPortWrite8(0x4d0, elcr[0]); // setup io master ioports.ioPortWrite8(0x4d1, elcr[1]); // setup io slave for (int devFN = 0; devFN < 256; devFN++) { PCIDevice device = devices[devFN]; if (device != null) { biosInitDevice(device); } } } private final void biosInitDevice(PCIDevice device) { int deviceClass = 0xffff & device.configReadWord(PCIDevice.PCI_CONFIG_CLASS_DEVICE); int vendorID = 0xffff & device.configReadWord(PCIDevice.PCI_CONFIG_VENDOR_ID); int deviceID = 0xffff & device.configReadWord(PCIDevice.PCI_CONFIG_DEVICE_ID); switch (deviceClass) { case 0x0101: if ((0xffff & vendorID) == 0x8086 && (0xffff & deviceID) == 0x7010) { /* PIIX3 IDE */ device.configWriteWord(0x40, (short) 0x8000); device.configWriteWord(0x42, (short) 0x8000); defaultIOMap(device); } else { /* IDE: we map it as in ISA mode */ this.setIORegionAddress(device, 0, 0x1f0); this.setIORegionAddress(device, 1, 0x3f4); this.setIORegionAddress(device, 2, 0x170); this.setIORegionAddress(device, 3, 0x374); } break; case 0x0300: if (vendorID == 0x1234) /* VGA: map frame buffer to default Bochs VBE address */ { this.setIORegionAddress(device, 0, 0xe0000000); } else { defaultIOMap(device); } break; case 0x0800: /* PIC */ if (vendorID == 0x1014) /* IBM */ { if (deviceID == 0x0046 || deviceID == 0xffff) /* MPIC & MPIC2 */ { this.setIORegionAddress(device, 0, 0x80800000 + 0x00040000); } } break; case 0xff00: if (vendorID == 0x0106b && (deviceID == 0x0017 || deviceID == 0x0022)) /* macio bridge */ { this.setIORegionAddress(device, 0, 0x80800000); } break; case 0x200: //Ethernet card IO region this.setIORegionAddress(device, 0, device.configReadLong(0x10)); break; default: defaultIOMap(device); break; } /* map the interrupt */ int pin = device.configReadByte(PCIDevice.PCI_CONFIG_INTERRUPT_PIN); if (pin != 0) { pin = isaBridge.slotGetPIRQ(device, pin - 1); device.configWriteByte(PCIDevice.PCI_CONFIG_INTERRUPT_LINE, PCI_IRQS[pin]); } } private void defaultIOMap(PCIDevice device) { IORegion[] regions = device.getIORegions(); if (regions == null) { return; } for (IORegion region : regions) { if (region == null) { continue; } if (region instanceof IOPortIORegion) { int paddr = biosIOAddress; paddr = (int) ((paddr + region.getSize() - 1) & ~(region.getSize() - 1)); this.setIORegionAddress(device, region.getRegionNumber(), paddr); biosIOAddress += region.getSize(); } else if (region instanceof MemoryMappedIORegion) { int paddr = biosMemoryAddress; paddr = (int) ((paddr + region.getSize() - 1) & ~(region.getSize() - 1)); this.setIORegionAddress(device, region.getRegionNumber(), paddr); biosMemoryAddress += region.getSize(); } } } private void setIORegionAddress(PCIDevice device, int regionNumber, int address) { int offset; if (regionNumber == PCIDevice.PCI_ROM_SLOT) { offset = PCIDevice.PCI_CONFIG_EXPANSION_ROM_BASE_ADDRESS; } else { offset = PCIDevice.PCI_CONFIG_BASE_ADDRESS + regionNumber * 4; } if (device.configWriteLong(offset, address)) { this.updateMappings(device); /* enable memory mappings */ } IORegion region = device.getIORegion(regionNumber); if (region == null) { return; } short command = device.configReadWord(PCIDevice.PCI_CONFIG_COMMAND); if (region.getRegionNumber() == PCIDevice.PCI_ROM_SLOT) { command |= PCIDevice.PCI_COMMAND_MEMORY; } else if (region instanceof IOPortIORegion) { command |= PCIDevice.PCI_COMMAND_IO; } else { command |= PCIDevice.PCI_COMMAND_MEMORY; } if (device.configWriteWord(PCIDevice.PCI_CONFIG_COMMAND, command)) { this.updateMappings(device); } } public void reset() { isaBridge = null; ioports = null; memory = null; pciIRQIndex = 0; devices = new PCIDevice[256]; pciIRQLevels = new int[4][PCI_IRQ_WORDS]; } public boolean initialised() { return ((isaBridge != null) && (ioports != null) && (memory != null)); } public void acceptComponent(HardwareComponent component) { if (component instanceof PCIISABridge) { isaBridge = (PCIISABridge) component; } if ((component instanceof IOPortHandler) && component.initialised()) { ioports = (IOPortHandler) component; } if ((component instanceof PhysicalAddressSpace) && component.initialised()) { memory = (PhysicalAddressSpace) component; //The following call may be unnecessary } if ((component instanceof VGACard) && (memory != null)) { updateMappings((VGACard) component); } } public boolean updated() { return updated; } public void updateComponent(HardwareComponent component) { if ((component instanceof VGACard) && component.updated()) { updated = true; } } }