/* * $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.bus.usb; import org.apache.log4j.Logger; import org.jnode.driver.Device; import org.jnode.driver.DeviceAlreadyRegisteredException; import org.jnode.driver.DeviceManager; import org.jnode.driver.DriverException; import org.jnode.util.NumberUtils; /** * Class used to watch an USB HUB for changes in the connection status of its ports. * <p/> * To have the best chance of success we do things in the exact same order as Windoze98. This * should not be necessary, but some devices do not follow the USB specs to the letter. * <p/> * These are the events on the bus when a hub is attached: * <ul> * <li>Get device and config descriptors (see attach code)</li> * <li>Get hub descriptor (see above)</li> * <li>For all ports * <ul> * <li>turn on power</li> * <li>wait for power to become stable</li> * </ul> * </li> * <li>For all ports * <ul> * <li>clear C_PORT_CONNECTION</li> * </ul> * </li> * <li>For all ports * <ul> * <li>get port status</li> * <li>if device connected * <ul> * <li>wait 100 ms</li> * <li>turn on reset</li> * <li>wait</li> * <li>clear C_PORT_RESET</li> * <li>get port status</li> * <li>proceed with device attachment</li> * </ul> * </li> * </ul> * </li> * </ul> * * @author Ewout Prangsma (epr@users.sourceforge.net) */ public class USBHubMonitor implements USBConstants { /** * My logger */ protected final Logger log = Logger.getLogger(getClass()); /** * The hub device */ private final Device hubDevice; /** * The Hub API */ private final USBHubAPI hub; /** * The device manager */ private final DeviceManager dm; /** * The monitor thread (if started) */ private USBHubMonitorThread thread; /** * Initialize a new instance. * * @param hub */ public USBHubMonitor(Device hubDevice, USBHubAPI hub) { this.hubDevice = hubDevice; this.hub = hub; this.dm = hubDevice.getManager(); } /** * Do the actual monitoring. */ protected void checkStatus(boolean first) { try { final int ports = hub.getNumPorts(); for (int port = 0; port < ports; port++) { if (first) { hub.setPortEnabled(port, false); sleep(100); hub.resetPort(port); sleep(100); } if (first || hub.isPortConnectionStatusChanged(port)) { portConnectionStatusChanged(port); } } } catch (USBException ex) { log.error("Error in portConnectionStatusChanged", ex); } } /** * The connection status of a given port has changed. * * @param port */ protected void portConnectionStatusChanged(int port) throws USBException { log.debug("USB hub connection status changed for port " + port); if (hub.isPortConnected(port)) { //log.debug("Port " + port + " is connected"); // Wait for at least 100ms to stabilize sleep(100); // Reset the port hub.resetPort(port); // Wait a while for reset to stabilize sleep(100); final int speed; if (hub.isPortConnectedToLowSpeed(port)) { speed = USB_SPEED_LOW; log.debug("Port " + port + " is connected to lowspeed device"); } else if (hub.isPortConnectedToHighSpeed(port)) { log.debug("Port " + port + " is connected to highspeed device"); speed = USB_SPEED_HIGH; } else { log.debug("Port " + port + " is connected to fullspeed device"); speed = USB_SPEED_FULL; } // Create the new device //log.debug("Creating USBDevice"); final USBDevice dev = new USBDevice(hub.getUSBBus(), speed); dev.getDefaultControlPipe().open(); // Now set the address //log.debug("Set the address"); final int devId = dev.getUSBBus().allocDeviceID(); try { // Set the device address dev.setAddress(devId); log.debug("Now using address 0x" + NumberUtils.hex(devId, 2)); sleep(100); // Let the address settle log.debug("After sleep"); // Determine the maximum packet size by fetching 8 bytes of device descriptor. //log.debug("Fetching first 8 bytes of device descriptor"); final DeviceDescriptor devDescr = new DeviceDescriptor(); dev.readDescriptor(USB_RECIP_DEVICE, USB_DT_DEVICE, 0, 0, 8, devDescr); dev.setDescriptor(devDescr); log.debug("devDescr[0-7]=" + devDescr + ", len=" + devDescr.getLength()); // Fetch the complete device descriptor. dev.readDescriptor(USB_RECIP_DEVICE, USB_DT_DEVICE, 0, 0, USB_DT_DEVICE_SIZE, devDescr); log.debug("read devDescr=" + devDescr); dev.setFullDescriptor(devDescr); log.debug("Full devDescr=" + devDescr); // Get all configurations. final int confCount = devDescr.getNumConfigurations(); for (int i = 0; i < confCount; i++) { final USBConfiguration conf = getConfiguration(dev, i); dev.setConfiguration(i, conf); log.debug("Read configuration " + conf); } // Set configuration 0 dev.setConfiguration(dev.getConfiguration(0)); // Load the strings of the device descriptor devDescr.loadStrings(dev); log.debug("Got descriptor " + devDescr); // Register the device with the HUB log.debug("hub.setDevice"); hub.setDevice(dev, port); // Register the device with the device manager log.debug("dm.register"); dm.register(dev); log.debug("Found USB device " + devDescr); } catch (USBException ex) { log.debug("Port status 0x" + NumberUtils.hex(hub.getPortStatus(port))); dev.getUSBBus().freeDeviceID(devId); hub.unsetDevice(port); throw ex; } catch (DriverException ex) { dev.getUSBBus().freeDeviceID(devId); hub.unsetDevice(port); throw new USBException(ex); } catch (DeviceAlreadyRegisteredException ex) { dev.getUSBBus().freeDeviceID(devId); hub.unsetDevice(port); throw new USBException(ex); } } else { log.debug("Port " + port + " is not connected"); final USBDevice dev = hub.getDevice(port); if (dev != null) { try { // Unregister the device dm.unregister(dev); } catch (DriverException ex) { log.error("Error unregistering disconnected USB device", ex); } // Remove the device from the HUB hub.unsetDevice(port); } hub.setPortEnabled(port, false); } // Clear the connection status hub.clearPortConnectionStatusChanged(port); } /** * Gets a specific configuration for the given device. * * @param dev * @param confNum */ private final USBConfiguration getConfiguration(USBDevice dev, int confNum) throws USBException { // First read enough to get the wTotalLength final ConfigurationDescriptor initDescr = new ConfigurationDescriptor(); dev.readDescriptor(USB_RECIP_DEVICE, USB_DT_CONFIG, confNum, 0, USB_DT_CONFIG_SIZE, initDescr); // Now get the full configuration data final byte[] data = new byte[initDescr.getTotalLength()]; final USBPacket dataP = new USBPacket(data); dev.readDescriptor(USB_RECIP_DEVICE, USB_DT_CONFIG, confNum, 0, data.length, dataP); // Get the configuration strings final ConfigurationDescriptor confDescr = new ConfigurationDescriptor(data, 0, USB_DT_CONFIG_SIZE); confDescr.loadStrings(dev); // Create the configuration final USBConfiguration conf = new USBConfiguration(dev, confDescr); // Parse the interface descriptors final int intfCount = confDescr.getNumInterfaces(); int offset = confDescr.getLength(); for (int i = 0; i < intfCount; i++) { final InterfaceDescriptor intfDescr = new InterfaceDescriptor(data, offset, USB_DT_INTERFACE_SIZE); intfDescr.loadStrings(dev); offset += intfDescr.getLength(); final USBInterface intf = new USBInterface(conf, intfDescr); conf.setInterface(i, intf); // Parse the endpoint descriptors final int epCount = intfDescr.getNumEndPoints(); for (int epIndex = 0; epIndex < epCount;) { final int dType = dataP.getByte(offset + 1); if (dType == USB_DT_ENDPOINT) { // It is an e final EndPointDescriptor epDescr = new EndPointDescriptor(data, offset, USB_DT_ENDPOINT_SIZE); offset += epDescr.getLength(); final USBEndPoint ep = new USBEndPoint(intf, epDescr); intf.setEndPoint(epIndex++, ep); } else { // Skip this unknown descriptor log.debug("Skipping unknown descriptor type " + dType); offset += dataP.getByte(offset); // Length } } } return conf; } /** * Sleep a while * * @param ms */ private void sleep(long ms) { try { Thread.sleep(ms); } catch (InterruptedException ex) { // Ignore } } /** * @see org.jnode.driver.DeviceListener#deviceStarted(org.jnode.driver.Device) */ public void startMonitor() { if (thread == null) { thread = new USBHubMonitorThread("HubMonitor-" + hubDevice.getId()); thread.start(); } } /** * @see org.jnode.driver.DeviceListener#deviceStop(org.jnode.driver.Device) */ public void stopMonitor() { final USBHubMonitorThread thread = this.thread; if (thread != null) { thread.stopThread(); try { thread.join(2000); } catch (InterruptedException ex) { // Ignore } } this.thread = null; } /** * The actual monitor thread. * * @author Ewout Prangsma (epr@users.sourceforge.net) */ class USBHubMonitorThread extends Thread { private boolean stop; public USBHubMonitorThread(String name) { super(name); this.stop = false; } public void stopThread() { this.stop = true; } public void run() { boolean first = true; while (!stop) { try { try { Thread.sleep(1000); } catch (InterruptedException ex) { stop = true; } checkStatus(first); } catch (Exception ex) { log.error("Error in USBHubMonitor", ex); } first = false; } } } }