/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.driver.net.lance;
import java.security.PrivilegedExceptionAction;
import javax.naming.NameNotFoundException;
import org.jnode.driver.DriverException;
import org.jnode.driver.bus.pci.PCIBaseAddress;
import org.jnode.driver.bus.pci.PCIConstants;
import org.jnode.driver.bus.pci.PCIDevice;
import org.jnode.driver.bus.pci.PCIHeaderType0;
import org.jnode.driver.net.NetworkException;
import org.jnode.driver.net.ethernet.spi.Flags;
import org.jnode.driver.net.spi.AbstractDeviceCore;
import org.jnode.naming.InitialNaming;
import org.jnode.net.HardwareAddress;
import org.jnode.net.SocketBuffer;
import org.jnode.net.ethernet.EthernetAddress;
import org.jnode.net.ethernet.EthernetConstants;
import org.jnode.system.resource.IOResource;
import org.jnode.system.resource.IRQHandler;
import org.jnode.system.resource.IRQResource;
import org.jnode.system.resource.ResourceManager;
import org.jnode.system.resource.ResourceNotFreeException;
import org.jnode.system.resource.ResourceOwner;
import org.jnode.util.AccessControllerUtils;
import org.jnode.util.NumberUtils;
import org.jnode.util.TimeoutException;
/**
* This is the DeviceCore for LANCE and PCnet 32 compatable ethernet PCI drivers.
* <p/>
* The current implementation was specificaly designed for the AMD PCnet-PCI II
* Ethernet Controller (Am79C970A), but should work for other AMD PCnet PCI devices.
* The driver is based on information in the following specification from AMD.
* http://www.amd.com/files/connectivitysolutions/networking/archivednetworking/19436.pdf
* <p/>
* Specificaly the following assumptions were made:
* - Device supports Software Style 2 (PCnet-PCI) which defines the layout of the initialaztion
* block and the descriptor rings.
* <p/>
* Note: It should be easy to expand this driver to remove these assuptions.
*
* @author Chirs Cole
*/
public class LanceCore extends AbstractDeviceCore implements IRQHandler, LanceConstants,
EthernetConstants {
// This is the number of descriptors for the receive and transmit rings
// Note: Valid numbers are 2^x where x is 0..8 (1, 2, 4, 8, 16, .., 512)
private static final int RX_DESCRIPTOR_LENGTH = 4;
private static final int TX_DESCRIPTOR_LENGTH = 4;
/**
* Device Driver
*/
private final LanceDriver driver;
/**
* Start of IO address space
*/
private final int iobase;
/**
* IO address space resource
*/
private final IOResource ioResource;
private final IOAccess io;
/**
* IRQ resource
*/
private final IRQResource irq;
/**
* My ethernet address
*/
private EthernetAddress hwAddress;
/**
* Flags for the specific device found
*/
private final LanceFlags flags;
/**
* Manager for receive and transmit rings as well as data buffers
*/
private final BufferManager bufferManager;
/**
* Create a new instance and allocate all resources
*
* @throws ResourceNotFreeException
*/
public LanceCore(LanceDriver driver, ResourceOwner owner, PCIDevice device, Flags flags)
throws ResourceNotFreeException, DriverException {
this.driver = driver;
this.flags = (LanceFlags) flags;
final PCIHeaderType0 config = device.getConfig().asHeaderType0();
final int irq = config.getInterruptLine();
final PCIBaseAddress[] addrs = config.getBaseAddresses();
if (addrs.length < 1) {
throw new DriverException("Cannot find iobase: not base addresses");
}
if (!addrs[0].isIOSpace()) {
throw new DriverException("Cannot find iobase: first address is not I/O");
}
// Get the start of the IO address space
iobase = addrs[0].getIOBase();
final int iolength = addrs[0].getSize();
log.debug("Found Lance IOBase: 0x" + NumberUtils.hex(iobase) + ", length: " + iolength);
ResourceManager rm;
try {
rm = InitialNaming.lookup(ResourceManager.NAME);
} catch (NameNotFoundException ex) {
throw new DriverException("Cannot find ResourceManager");
}
this.irq = rm.claimIRQ(owner, irq, this, true);
try {
ioResource = claimPorts(rm, owner, iobase, iolength);
} catch (ResourceNotFreeException ex) {
this.irq.release();
throw ex;
}
// Determine the type of IO access (Word or DWord)
io = getIOAccess();
log.debug("IO Access set to " + io.getType());
// Set the flags based on the version of the device found
setFlags();
// Load the hw address
this.hwAddress = loadHWAddress();
log.info("Found " + this.flags.getChipName() + " at 0x" + NumberUtils.hex(iobase, 4) +
" with MAC Address " + hwAddress);
// Create rx & tx descriptor rings, initdata and databuffers
this.bufferManager =
new BufferManager(RX_DESCRIPTOR_LENGTH, TX_DESCRIPTOR_LENGTH,
CSR15_DRX | CSR15_DTX, hwAddress, 0, rm, owner);
// Enable device to become a bus master on the PCI bus.
config.setCommand(config.getCommand() | PCIConstants.PCI_COMMAND_MASTER);
}
private IOAccess getIOAccess() {
// reset
ioResource.inPortWord(iobase + WIO_RESET);
ioResource.outPortWord(iobase + WIO_RAP, 0);
if (ioResource.inPortWord(iobase + WIO_RDP) == 4) {
ioResource.outPortWord(iobase + WIO_RAP, 88);
if (ioResource.inPortWord(iobase + WIO_RAP) == 88) {
return new WordIOAccess(ioResource, iobase);
}
}
ioResource.inPortDword(iobase + DWIO_RESET);
ioResource.outPortDword(iobase + DWIO_RAP, 0);
if (ioResource.inPortDword(iobase + DWIO_RDP) == 4) {
ioResource.outPortDword(iobase + DWIO_RAP, 88);
if ((ioResource.inPortDword(iobase + DWIO_RAP) & 0xFFFF) == 88) {
return new DWordIOAccess(ioResource, iobase);
}
}
return null;
}
/**
* Initialize this device.
*/
public void initialize() {
// reset the chip
io.reset();
// Set the Software Style to mode 2 (PCnet-PCI)
// Note: this may not be compatable with older lance controllers (non PCnet)
io.setBCR(20, 2);
// TODO the device should be setup based on the flags for the chip version
// Auto select port
io.setBCR(2, BCR2_ASEL);
// Enable full duplex
io.setBCR(9, BCR9_FDEN);
io.setCSR(4, CSR4_DMAPLUS | CSR4_APAD_XMT);
io.setCSR(5, CSR5_LTINTEN | CSR5_SINTE | CSR5_SLPINTE | CSR5_EXDINTE | CSR5_MPINTE);
// Set the address of the Initialization Block
final int iaddr = bufferManager.getInitDataAddressAs32Bit();
io.setCSR(1, iaddr & 0xFFFF);
io.setCSR(2, (iaddr >> 16) & 0xFFFF);
// Initialize the device with the Initialization Block and enable interrupts
io.setCSR(0, CSR0_INIT | CSR0_IENA);
}
/**
* Disable this device
*/
public void disable() {
io.reset();
io.setCSR(0, CSR0_STOP);
}
/**
* Release all resources
*/
public void release() {
ioResource.release();
irq.release();
}
/**
* Gets the hardware address of this card.
*/
public HardwareAddress getHwAddress() {
return hwAddress;
}
/**
* Read the hardware address
*/
private final EthernetAddress loadHWAddress() {
final byte[] addr = new byte[ETH_ALEN];
for (int i = 0; i < addr.length; i++) {
addr[i] = (byte) ioResource.inPortByte(iobase + R_ETH_ADDR_OFFSET + i);
}
return new EthernetAddress(addr, 0);
}
private final void setFlags() {
int chipVersion = io.getCSR(88) | (io.getCSR(89) << 16);
chipVersion = (chipVersion >> 12) & 0xffff;
flags.setForVersion(chipVersion);
}
/**
* Transmit the given buffer
*
* @param buf
* @param timeout
* @throws InterruptedException
* @throws TimeoutException
*/
public synchronized void transmit(SocketBuffer buf, HardwareAddress destination, long timeout)
throws InterruptedException, TimeoutException {
// Set the source address
hwAddress.writeTo(buf, 6);
// debug dump for investigating VWWare 3 network problems
// dumpDebugInfo();
// ask buffer manager to send out the data
bufferManager.transmit(buf);
// force the device to poll current transmit descriptor for new data
io.setCSR(0, io.getCSR(0) | CSR0_TDMD);
}
/**
* @see org.jnode.system.resource.IRQHandler#handleInterrupt(int)
*/
public void handleInterrupt(int irq) {
while ((io.getCSR(0) & CSR0_INTR) != 0) {
final int csr0 = io.getCSR(0);
final int csr4 = io.getCSR(4);
final int csr5 = io.getCSR(5);
io.setCSR(0, csr0);
io.setCSR(4, csr4);
io.setCSR(5, csr5);
// check if interrupt is due to Initialization Done
if ((csr0 & CSR0_IDON) != 0) {
log.info(flags.getChipName() + " Initialization Complete");
// Now enable RX/TX
io.setCSR(15, 0);
// assert the Start and clear Initialization Done (IDON) flag
// Note: there are reported errors due to setting IDON here but I have not seen any
io.setCSR(0, CSR0_STRT | CSR0_IENA | CSR0_IDON);
}
// check if interrupt is due to Transmition Interrupt
if ((csr0 & CSR0_TINT) != 0) {
//log.debug("Transmition Interrupt");
}
// check if interrupt is due to Receive Interrupt
if ((csr0 & CSR0_RINT) != 0) {
//log.debug("Receive Interrupt");
rxProcess();
}
// check if interrupt is due an error
if ((csr0 & CSR0_ERR) != 0) {
log.debug("Error Interrupt");
// check if interrupt is due to Memory Error
if ((csr0 & CSR0_MERR) != 0) {
log.debug("Memory Error");
}
// check if interrupt is due to Missed Frame
if ((csr0 & CSR0_MISS) != 0) {
log.debug("Missed Frame");
}
// check if interrupt is due to Collision Error
if ((csr0 & CSR0_CERR) != 0) {
log.debug("Collision Error");
}
// check if interrupt is due to a Bable transmitter time-out
if ((csr0 & CSR0_BABL) != 0) {
log.debug("Bable transmitter time-out");
}
}
// check if interrupt is due to a Missed Frame Counter Overflow
if ((csr4 & CSR4_MFCO) == CSR4_MFCO) {
log.debug("Missed Frame Counter Overflow");
}
// check if interrupt is due to a User Interrupt
if ((csr4 & CSR4_UINT) == CSR4_UINT) {
log.debug("User Interrupt");
}
// check if interrupt is due to a Receive Collision Counter Overflow
if ((csr4 & CSR4_RCVCCO) == CSR4_RCVCCO) {
log.debug("Receive Collision Counter Overflow");
}
// check if interrupt is due to a Transmit Start
if ((csr4 & CSR4_TXSTRT) == CSR4_TXSTRT) {
log.debug("Transmit Start");
}
// check if interrupt is due to a Jabber Error
if ((csr4 & CSR4_JAB) == CSR4_JAB) {
log.debug("Jabber Error");
}
// check if interrupt is due to a Jabber Error
if ((csr4 & CSR4_JAB) == CSR4_JAB) {
log.debug("Jabber Error");
}
// check if interrupt is due to a System Interrupt
if ((csr5 & CSR5_SINT) == CSR5_SINT) {
log.debug("System Interrupt");
}
// check if interrupt is due to a Sleep Interrupt
if ((csr5 & CSR5_SLPINT) == CSR5_SLPINT) {
log.debug("Sleep Interrupt");
}
// check if interrupt is due to a Excessive Deferral Interrupt
if ((csr5 & CSR5_EXDINT) == CSR5_EXDINT) {
log.debug("Excessive Deferral Interrupt");
}
// check if interrupt is due to a Magic Packet Interrupt
if ((csr5 & CSR5_MPINT) == CSR5_MPINT) {
log.debug("Magic Packet Interrupt");
}
}
}
private void rxProcess() {
SocketBuffer skbuf;
while ((skbuf = bufferManager.getPacket()) != null) {
try {
if (skbuf != null)
driver.onReceive(skbuf);
} catch (NetworkException e) {
// FIXME
e.printStackTrace();
} finally {
// FIXME
}
}
}
private IOResource claimPorts(final ResourceManager rm, final ResourceOwner owner,
final int low, final int length) throws ResourceNotFreeException, DriverException {
try {
return AccessControllerUtils.doPrivileged(new PrivilegedExceptionAction<IOResource>() {
public IOResource run() throws ResourceNotFreeException {
return rm.claimIOResource(owner, low, length);
}
});
} catch (ResourceNotFreeException ex) {
throw ex;
} catch (Exception ex) {
throw new DriverException("Unknown exception", ex);
}
}
final void dumpDebugInfo() {
log.debug("Debug Dump");
log.debug("CSR0 = " + io.getCSR(0));
// stop the device so we can read all registers
io.setCSR(0, CSR0_STOP);
bufferManager.dumpData(log);
int validVMWareLanceRegs[] = {
0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 24, 25, 30, 31, 58, 76, 77, 80,
82, 88, 89, 112, 124};
for (int validVMWareLanceReg : validVMWareLanceRegs) {
int csr_val = io.getCSR(validVMWareLanceReg);
log.debug("CSR" + validVMWareLanceReg + " : " + NumberUtils.hex(csr_val, 4));
}
// try to start again, not sure if this works?
io.setCSR(0, CSR0_STRT);
}
}