/* * $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.prism2; import javax.naming.NameNotFoundException; import org.jnode.driver.Device; import org.jnode.driver.DriverException; import org.jnode.driver.bus.pci.PCIBaseAddress; 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.event.LinkStatusEvent; import org.jnode.driver.net.wireless.spi.WirelessDeviceCore; 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.net.wireless.AuthenticationMode; import org.jnode.net.wireless.WirelessConstants; import org.jnode.system.resource.IRQHandler; import org.jnode.system.resource.IRQResource; import org.jnode.system.resource.MemoryResource; import org.jnode.system.resource.ResourceManager; import org.jnode.system.resource.ResourceNotFreeException; import org.jnode.system.resource.ResourceOwner; import org.jnode.util.LittleEndian; import org.jnode.util.NumberUtils; import org.jnode.util.TimeoutException; import org.vmmagic.unboxed.Address; import org.vmmagic.unboxed.Extent; import org.vmmagic.unboxed.MagicUtils; import static org.jnode.driver.net.prism2.Prism2Constants.Command.ACCESS; import static org.jnode.driver.net.prism2.Prism2Constants.Command.ALLOC; import static org.jnode.driver.net.prism2.Prism2Constants.Command.DISABLE; import static org.jnode.driver.net.prism2.Prism2Constants.Command.ENABLE; import static org.jnode.driver.net.prism2.Prism2Constants.Command.INIT; import static org.jnode.driver.net.prism2.Prism2Constants.Command.TX; import static org.jnode.driver.net.prism2.Prism2Constants.LinkStatus.CONNECTED; import static org.jnode.driver.net.prism2.Prism2Constants.LinkStatus.NOTCONNECTED; import static org.jnode.driver.net.prism2.Prism2Constants.RecordID.CNFAUTHENTICATION; import static org.jnode.driver.net.prism2.Prism2Constants.RecordID.CNFMAXDATALEN; import static org.jnode.driver.net.prism2.Prism2Constants.RecordID.CNFOWNMACADDR; import static org.jnode.driver.net.prism2.Prism2Constants.RecordID.CNFPORTTYPE; import static org.jnode.driver.net.prism2.Prism2Constants.RecordID.CURRENTBSSID; import static org.jnode.driver.net.prism2.Prism2Constants.RecordID.CURRENTSSID; import static org.jnode.driver.net.prism2.Prism2Constants.RecordID.TXRATECNTL; import static org.jnode.driver.net.prism2.Prism2Constants.Register.ALLOCFID; import static org.jnode.driver.net.prism2.Prism2Constants.Register.EVACK; import static org.jnode.driver.net.prism2.Prism2Constants.Register.EVSTAT; import static org.jnode.driver.net.prism2.Prism2Constants.Register.INFOFID; import static org.jnode.driver.net.prism2.Prism2Constants.Register.INTEN; import static org.jnode.driver.net.prism2.Prism2Constants.Register.RXFID; /** * @author Ewout Prangsma (epr@users.sourceforge.net) */ final class Prism2Core extends WirelessDeviceCore implements Prism2Constants, WirelessConstants, IRQHandler { /** * The driver I'm a part of */ private final Prism2Driver driver; /** * The device flags */ private final Prism2Flags flags; /** * My ethernet address */ private EthernetAddress hwAddress = null; /** * Low level I/O helper */ private Prism2IO io; /** * The IRQ resource */ private IRQResource irq; /** * Info frame used in the irq handler. To avoid numerous allocations. */ private final byte[] irqInfoFrame = new byte[Prism2InfoFrame.MAX_FRAME_LEN]; /** * Receive frame used in the irq handler. To avoid numerous allocations. */ private final byte[] irqReceiveFrame = new byte[Prism2CommFrame.MAX_FRAME_LEN]; /** * Current link status */ @SuppressWarnings("unused") private LinkStatus linkStatus = NOTCONNECTED; /** * Address of connected BSS (only valid when link status is connected */ private EthernetAddress bssid = null; /** * The device */ private final Device device; /** * Initialize this instance. * * @param driver * @param owner * @param device * @param flags * @throws DriverException */ public Prism2Core(Prism2Driver driver, ResourceOwner owner, PCIDevice device, Flags flags) throws DriverException { if (!(flags instanceof Prism2Flags)) throw new DriverException("Wrong flags to the Prism2 driver"); this.driver = driver; this.device = device; this.flags = (Prism2Flags) flags; final ResourceManager rm; try { rm = InitialNaming.lookup(ResourceManager.NAME); } catch (NameNotFoundException ex) { throw new DriverException("Cannot find ResourceManager"); } final PCIHeaderType0 pciCfg = device.getConfig().asHeaderType0(); final PCIBaseAddress[] baseAddrs = pciCfg.getBaseAddresses(); if (baseAddrs.length < 1) { throw new DriverException( "No memory mapped I/O region in PCI config"); } final PCIBaseAddress regsAddr = pciCfg.getBaseAddresses()[0]; if (!regsAddr.isMemorySpace()) { throw new DriverException("Memory mapped I/O is not a memory space"); } // Claim the memory mapped I/O region final Address regsAddrPtr = Address.fromLong(regsAddr.getMemoryBase()); final Extent regsSize = Extent.fromIntZeroExtend(regsAddr.getSize()); try { final MemoryResource regs; regs = rm.claimMemoryResource(device, regsAddrPtr, regsSize, ResourceManager.MEMMODE_NORMAL); this.io = new Prism2IO(regs); } catch (ResourceNotFreeException ex) { throw new DriverException("Cannot claim memory mapped I/O", ex); } // Claim IRQ final int irqNo = pciCfg.getInterruptLine(); try { this.irq = rm.claimIRQ(device, irqNo, this, true); } catch (ResourceNotFreeException ex) { // Release IO io.release(); io = null; // re-throw exception throw new DriverException("Cannot claim IRQ", ex); } log.info("Found " + flags.getName() + ", regs at " + MagicUtils.toString(regsAddrPtr)); } /** * @see org.jnode.driver.net.spi.AbstractDeviceCore#disable() */ public void disable() { // Disable all interrupts io.setReg(INTEN, 0); // Disable the mac port try { executeDisableCmd(0); } catch (DriverException ex) { log.debug("Disable failed", ex); } } /** * Execute the Access command * * @throws DriverException */ private final void executeAccessCmd(RecordID rid, boolean write) throws DriverException { try { final Result result; final int cmdFlags; if (write) { cmdFlags = CMD_WRITE; } else { cmdFlags = 0; } result = io.executeCommand(ACCESS, cmdFlags, rid.getId(), 0, 0, null); io.resultToException(result); } catch (TimeoutException ex) { throw new DriverException("Timeout in Access command", ex); } } /** * Execute the Alloc command * * @throws DriverException */ private final void executeAllocCmd(int bufferLength) throws DriverException { try { final Result result; result = io.executeCommand(ALLOC, 0, bufferLength, 0, 0, null); io.resultToException(result); } catch (TimeoutException ex) { throw new DriverException("Timeout in Alloc command", ex); } } /** * Enable the given mac port of the device. * * @param macPort * @throws DriverException */ private final void executeEnableCmd(int macPort) throws DriverException { try { final int cmdFlags = ((macPort & 7) << 8); final Result result; result = io.executeCommand(ENABLE, cmdFlags, 0, 0, 0, null); io.resultToException(result); } catch (TimeoutException ex) { throw new DriverException("Timeout in Enable command", ex); } } /** * Disable the given mac port of the device. * * @param macPort * @throws DriverException */ private final void executeDisableCmd(int macPort) throws DriverException { try { final int cmdFlags = ((macPort & 7) << 8); final Result result; result = io.executeCommand(DISABLE, cmdFlags, 0, 0, 0, null); io.resultToException(result); } catch (TimeoutException ex) { throw new DriverException("Timeout in Disable command", ex); } } /** * Execute the Transmit command * * @throws DriverException */ private final void executeTransmitCmd(int fid) throws DriverException { try { final Result result; result = io.executeCommand(TX, 0, fid, 0, 0, null); io.resultToException(result); } catch (TimeoutException ex) { throw new DriverException("Timeout in Tx command", ex); } } /** * Read a configuration record * * @param rid * @param dst * @param dstOffset * @param len * @throws DriverException */ private final void getConfig(RecordID rid, byte[] dst, int dstOffset, int len) throws DriverException { // Request read of RID executeAccessCmd(rid, false); // Read record header final byte[] hdr = new byte[Prism2Record.HDR_LENGTH]; io.copyFromBAP(rid.getId(), 0, hdr, 0, hdr.length); // Validate the record length if ((Prism2Record.getRecordLength(hdr, 0) - 1) * 2 != len) { throw new DriverException("Mismatch in record length. " + len + '/' + Prism2Record.getRecordLength(hdr, 0)); } // Copy out record data io.copyFromBAP(rid.getId(), hdr.length, dst, dstOffset, len); } /** * Get a 16-bit configuration value. * * @param rid * @return The 16-bit value. * @throws DriverException */ private final int getConfig16(RecordID rid) throws DriverException { final byte[] arr = new byte[2]; getConfig(rid, arr, 0, 2); return LittleEndian.getInt16(arr, 0); } /** * Get a 32-bit configuration value. * * @param rid * @return The 32-bit value. * @throws DriverException */ @SuppressWarnings("unused") private final int getConfig32(RecordID rid) throws DriverException { final byte[] arr = new byte[4]; getConfig(rid, arr, 0, 4); return LittleEndian.getInt32(arr, 0); } /** * Read the current ESSID * * @throws DriverException * @see org.jnode.driver.net.wireless.spi.WirelessDeviceCore#getESSID() */ protected String getESSID() throws DriverException { final byte[] id = new byte[RID_CURRENTSSID_LEN]; getConfig(CURRENTSSID, id, 0, id.length); return null; } /** * @see org.jnode.driver.net.spi.AbstractDeviceCore#getHwAddress() */ public HardwareAddress getHwAddress() { return hwAddress; } /** * Initialize the device so that it is ready to transmit and receive data. * * @see org.jnode.driver.net.spi.AbstractDeviceCore#initialize() */ public void initialize() throws DriverException { // Initialize card try { io.executeCommand(INIT, 0, 0, 0, 0, null); } catch (TimeoutException ex) { throw new DriverException("Cannot initialize device in time", ex); } // Disable interrupts io.setReg(INTEN, 0); // Acknowledge any spurious events io.setReg(EVACK, 0xFFFF); // Read MAC address final byte[] macAddr = new byte[CNFOWNMACADDR.getRecordLength()]; getConfig(CNFOWNMACADDR, macAddr, 0, CNFOWNMACADDR.getRecordLength()); this.hwAddress = new EthernetAddress(macAddr, 0); log.info("MAC-address for " + flags.getName() + ' ' + hwAddress); // Set maximum data length setConfig16(CNFMAXDATALEN, WLAN_DATA_MAXLEN); // Set transmit rate control setConfig16(TXRATECNTL, 0x000f); // Set authentication to Open system setConfig16(CNFAUTHENTICATION, CNFAUTHENTICATION_OPENSYSTEM); // Maybe set desired ESSID here // Set port type to ESS port setConfig16(CNFPORTTYPE, 1); // Enable Rx & Info interrupts io.setReg(INTEN, INTEN_RX | INTEN_INFO); // Enable card executeEnableCmd(0); } /** * @see org.jnode.driver.net.spi.AbstractDeviceCore#release() */ public void release() { final Prism2IO io = this.io; if (io != null) { this.io = null; io.release(); } final IRQResource irq = this.irq; if (irq != null) { this.irq = null; irq.release(); } } /** * Write a configuration record * * @param rid * @param src * @param srcOffset * @param len * @throws DriverException */ private final void setConfig(RecordID rid, byte[] src, int srcOffset, int len) throws DriverException { // Create and write record header final byte[] hdr = new byte[Prism2Record.HDR_LENGTH]; Prism2Record.setRecordLength(hdr, 0, (len / 2) + 1); Prism2Record.setRecordRID(hdr, 0, rid.getId()); io.copyToBAP(rid.getId(), 0, hdr, 0, hdr.length); // Copy out record data (if any) if (len > 0) { io.copyToBAP(rid.getId(), hdr.length, src, srcOffset, len); } // Request write of RID executeAccessCmd(rid, true); } /** * Set a 16-bit configuration value. * * @param rid * @param value * @throws DriverException */ private final void setConfig16(RecordID rid, int value) throws DriverException { final byte[] arr = new byte[2]; LittleEndian.setInt16(arr, 0, value); setConfig(rid, arr, 0, 2); } /** * Set a 32-bit configuration value. * * @param rid * @param value * @throws DriverException */ @SuppressWarnings("unused") private final void setConfig32(RecordID rid, int value) throws DriverException { final byte[] arr = new byte[4]; LittleEndian.setInt32(arr, 0, value); setConfig(rid, arr, 0, 4); } /** * @see org.jnode.driver.net.wireless.spi.WirelessDeviceCore#setESSID(java.lang.String) */ protected void setESSID(String essid) throws DriverException { // TODO Auto-generated method stub } /** * @see org.jnode.driver.net.wireless.spi.WirelessDeviceCore#startScan() */ public void startScan() { // TODO Auto-generated method stub } /** * @see org.jnode.driver.net.spi.AbstractDeviceCore#transmit(SocketBuffer buf, * HardwareAddress destination, long timeout) */ public void transmit(SocketBuffer buf, HardwareAddress destination, long timeout) throws InterruptedException, TimeoutException { // Request buffer allocation try { executeAllocCmd(Prism2CommFrame.MAX_TXBUF_LEN); } catch (DriverException ex) { log.debug("Alloc command failed in transmit", ex); return; } // Wait for the allocation to be ready final int evstat = io.waitForEvent(EVSTAT_ALLOC, EVACK_INFO, 10, 50); if ((evstat & EVSTAT_ALLOC) == 0) { log.debug("Allocation of transmit buffer failed"); return; } final int fid = io.getReg(ALLOCFID); // Build the transmit header final int hdrLen = Prism2CommFrame.HDR_LENGTH; final byte[] hdr = new byte[hdrLen]; // txdesc.tx_control = host2hfa384x_16( HFA384x_TX_MACPORT_SET(0) | HFA384x_TX_STRUCTYPE_SET(1) | // HFA384x_TX_TXEX_SET(1) | HFA384x_TX_TXOK_SET(1) ); //txdesc.frame_control = host2ieee16( WLAN_SET_FC_FTYPE(WLAN_FTYPE_DATA) | // WLAN_SET_FC_FSTYPE(WLAN_FSTYPE_DATAONLY) | // WLAN_SET_FC_TODS(1) ); Prism2CommFrame.setAddress1(hdr, 0, bssid); Prism2CommFrame.setAddress2(hdr, 0, hwAddress); Prism2CommFrame.setAddress3(hdr, 0, (EthernetAddress) destination); try { // Copy tx-descriptor to BAP io.copyToBAP(fid, 0, hdr, 0, hdrLen); // Copy 802.11 header to BAP // Copy data to BAP } catch (DriverException ex) { log.debug("Failed to copy data to BAP", ex); return; } // Execute Tx command try { executeTransmitCmd(fid); } catch (DriverException ex) { log.debug("Transmit command failed", ex); return; } // TODO Implement error handling } /** * @see org.jnode.system.IRQHandler#handleInterrupt(int) */ public final void handleInterrupt(int irq) { for (int loop = 0; loop < 10; loop++) { final int evstat = io.getReg(EVSTAT); if (evstat == 0) { // No event for me return; } try { if ((evstat & EVSTAT_RX) != 0) { // Received a frame processReceiveEvent(); } else if ((evstat & EVSTAT_INFO) != 0) { // Get Info frame processInfoEvent(); } else { log.debug("No suitable event 0x" + NumberUtils.hex(evstat, 4)); return; } } catch (DriverException ex) { log.error("Error in IRQ handler", ex); } } } /** * A frame has been received. * * @throws DriverException */ private final void processReceiveEvent() throws DriverException { // Read the FID of the received frame final int fid = io.getReg(RXFID); final byte[] frame = this.irqReceiveFrame; log.info("Receive, FID=0x" + NumberUtils.hex(fid, 4)); // Read the frame header final int hdrLen = Prism2CommFrame.HDR_LENGTH; io.copyFromBAP(fid, 0, frame, 0, hdrLen); final int dataLength = Prism2CommFrame.getDataLength(frame, 0); // Create the SocketBuffer final int ethHLEN = EthernetConstants.ETH_HLEN; final SocketBuffer skbuf = new SocketBuffer(ethHLEN + dataLength); skbuf.append(frame, Prism2CommFrame.p8023HDR_OFF, ethHLEN); // Read the actual data if (dataLength > 0) { io.copyFromBAP(fid, hdrLen, frame, hdrLen, dataLength); skbuf.append(frame, hdrLen, dataLength); } // Process the received frame try { driver.onReceive(skbuf); } catch (NetworkException ex) { log.debug("Error in onReceive", ex); } // Acknowledge the receive io.setReg(EVACK, EVACK_RX); } /** * An Info frame is ready * * @throws DriverException */ private final void processInfoEvent() throws DriverException { // Read the FID of the info frame final int fid = io.getReg(INFOFID); final byte[] frame = this.irqInfoFrame; log.debug("Info, FID=0x" + NumberUtils.hex(fid, 4)); // Read the frame header final int hdrLen = Prism2InfoFrame.HDR_LENGTH; io.copyFromBAP(fid, 0, frame, 0, hdrLen); final int frameLen = Prism2InfoFrame.getFrameLength(frame, 0); final InformationType infoType = Prism2InfoFrame.getInfoType(frame, 0); // Read the rest of the frame io.copyFromBAP(fid, hdrLen, frame, hdrLen, frameLen - hdrLen); switch (infoType) { case COMMTALLIES: // Communication statistics. Ignore for now break; case LINKSTATUS: final LinkStatus lstat = Prism2InfoFrame.getLinkStatus(frame, 0); switch (lstat) { case CONNECTED: getConfig(CURRENTBSSID, frame, 0, WLAN_BSSID_LEN); bssid = new EthernetAddress(frame, 0); log.info("Connected to " + bssid); break; case DISCONNECTED: log.info("Disconnected"); bssid = null; break; default: log.info("Link status 0x" + NumberUtils.hex(lstat.getValue(), 4)); } this.linkStatus = lstat; // Post the event driver.postEvent(new LinkStatusEvent(device, lstat == CONNECTED)); break; default: log.info("Got Info frame, Type=0x" + NumberUtils.hex(infoType.getValue(), 4)); } // Acknowledge the info frame io.setReg(EVACK, EVACK_INFO); } /** * @see org.jnode.driver.net.wireless.spi.WirelessDeviceCore#getAuthenticationMode() */ protected AuthenticationMode getAuthenticationMode() throws DriverException { final int mode = getConfig16(CNFAUTHENTICATION); switch (mode) { case CNFAUTHENTICATION_OPENSYSTEM: return AuthenticationMode.OPENSYSTEM; case CNFAUTHENTICATION_SHAREDKEY: return AuthenticationMode.SHAREDKEY; default: throw new DriverException("Unknown authentication mode 0x" + NumberUtils.hex(mode, 4)); } } /** * @see org.jnode.driver.net.wireless.spi.WirelessDeviceCore#setAuthenticationMode( * org.jnode.net.wireless.AuthenticationMode) */ protected void setAuthenticationMode(AuthenticationMode mode) throws DriverException { final int modeVal; switch (mode) { case OPENSYSTEM: modeVal = CNFAUTHENTICATION_OPENSYSTEM; break; case SHAREDKEY: modeVal = CNFAUTHENTICATION_SHAREDKEY; break; default: throw new DriverException("Unknown authentication mode " + mode); } setConfig16(CNFAUTHENTICATION, modeVal); } }