/* * $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.uhci; import java.util.ArrayList; import org.apache.log4j.Logger; import org.jnode.driver.bus.usb.USBConstants; import org.jnode.driver.bus.usb.USBDevice; import org.jnode.driver.bus.usb.USBEndPoint; import org.jnode.driver.bus.usb.USBException; import org.jnode.driver.bus.usb.USBPipe; import org.jnode.driver.bus.usb.USBPipeListener; import org.jnode.driver.bus.usb.USBRequest; import org.jnode.driver.bus.usb.spi.AbstractUSBRequest; import org.jnode.system.resource.ResourceManager; import org.jnode.util.NumberUtils; import org.jnode.util.Queue; /** * @author Ewout Prangsma (epr@users.sourceforge.net) */ public class UHCIPipe implements USBPipe, USBConstants { /** * My logger */ private static final Logger log = Logger.getLogger(UHCIPipe.class); /** * The pipe manager */ private final UHCIPipeManager pm; /** * The resource manager */ private final ResourceManager rm; /** * The transfer type of this pipe */ private final int transferType; /** * The device of this pipe */ protected final USBDevice device; /** * The endpoint number for this pipe */ protected final int endPointNum; /** * The endpoint for this pipe */ private final USBEndPoint endPoint; /** * Is this pipe open */ private boolean open; /** * The skeleton QueueHead where qh will be linked to */ private final QueueHead skelQH; /** * The QueueHead where my requests will be added to */ private final QueueHead qh; /** * List of waiting requests */ private final Queue<USBRequest> queue; /** * The active request */ private UHCIRequest activeRequest; /** * My listeners */ private ArrayList<USBPipeListener> listeners = new ArrayList<USBPipeListener>(); /** * The maximum packet size, or -1 if not set */ private final int maxPktSize; /** * Create a new instance * * @param pm * @param rm * @param device * @param ep * @param transferType * @param skelQH */ public UHCIPipe(UHCIPipeManager pm, ResourceManager rm, USBDevice device, USBEndPoint ep, int transferType, QueueHead skelQH) { this.pm = pm; this.rm = rm; this.device = device; this.endPoint = ep; this.transferType = transferType; this.endPointNum = (endPoint != null) ? endPoint.getDescriptor().getEndPointNumber() : 0; this.qh = new QueueHead(rm); this.skelQH = skelQH; this.queue = new Queue<USBRequest>(); this.maxPktSize = (endPoint != null) ? endPoint.getDescriptor().getMaxPacketSize() : -1; } /** * Is this a control pipe. */ public final boolean isControlPipe() { return (transferType == USB_ENDPOINT_XFER_CONTROL); } /** * Is this an interrupt pipe. */ public final boolean isInterruptPipe() { return (transferType == USB_ENDPOINT_XFER_INT); } /** * Is this a isochronous pipe. */ public boolean isIsochronousPipe() { return (transferType == USB_ENDPOINT_XFER_ISOC); } /** * Is this a bulk pipe. */ public boolean isBulkPipe() { return (transferType == USB_ENDPOINT_XFER_BULK); } /** * Is this pipe open. */ public final boolean isOpen() { return this.open; } /** * Open this pipe. * * @throws USBException */ public void open() throws USBException { if (!this.open) { pm.add(this); skelQH.insertLink(qh); this.open = true; } } /** * Close this pipe. */ public void close() { if (this.open) { skelQH.removeLink(qh); pm.remove(this); this.open = false; } } /** * Submit a given request via this pipe and return immediately. * * @param request */ public synchronized void asyncSubmit(USBRequest request) throws USBException { if (!open) { throw new USBException("Not open"); } if (!(request instanceof UHCIRequest)) { throw new IllegalArgumentException("Invalid request type (IUHCIRequest)"); } if (!(request instanceof AbstractUSBRequest)) { throw new IllegalArgumentException("Invalid request type (AbstractUSBRequest)"); } final UHCIRequest req = (UHCIRequest) request; final AbstractUSBRequest usbReq = (AbstractUSBRequest) request; usbReq.setCompleted(false); usbReq.setActualLength(0); usbReq.setStatus(0); if (qh.isEmpty()) { activateRequest(req); } else { queue.add(req); } } /** * Submit a given request via this pipe and wait for it to complete. * * @param request * @param timeout */ public void syncSubmit(USBRequest request, long timeout) throws USBException { asyncSubmit(request); request.waitUntilComplete(timeout); if (!request.isCompleted()) { throw new USBException("Timeout on request"); } final int status = request.getStatus(); if ((status & USBREQ_ST_ERROR_MASK) != 0) { throw new USBException("USB error 0x" + NumberUtils.hex(status)); } } /** * Add a listener to this pipe. * * @param listener */ public synchronized void addListener(USBPipeListener listener) { listeners.add(listener); } /** * Remove a listener from this pipe. * * @param listener */ public synchronized void removeListener(USBPipeListener listener) { listeners.remove(listener); } /** * Handle an interrupt. */ protected final synchronized void handleInterrupt() { if (this.open) { final UHCIRequest req = this.activeRequest; if (req != null) { handleInterrupt(req); if (req.isCompleted()) { qh.remove(req); this.activeRequest = null; // If interrupt, then restart the interrupt if (isInterruptPipe() && open) { try { asyncSubmit(req); } catch (USBException ex) { // Ignore } } } } if ((!queue.isEmpty()) && qh.isEmpty()) { final UHCIRequest nextReq = (UHCIRequest) queue.get(); try { activateRequest(nextReq); } catch (USBException ex) { final AbstractUSBRequest areq = (AbstractUSBRequest) nextReq; areq.setStatus(USBConstants.USBREQ_ST_BITSTUFF); areq.setCompleted(true); log.error("Unknown errr in activateRequest", ex); } } } } /** * Active the given request * * @param req */ private final void activateRequest(UHCIRequest req) throws USBException { TransferDescriptor td = req.getFirstTD(); if (td == null) { //log.debug("create"); // It is a new request, create the TD's req.createTDs(this); } else { //log.debug("recycle"); // It is a recycled request, reset the TD's while (td != null) { td.resetStatus(); td = td.getNextTD(); } } this.activeRequest = req; //log.debug("add"); qh.add(req); //log.debug("activateRequest done"); } /** * An interrupt has occurred, see if all TD's are finished and if so, notify all waiting * threads. */ private final void handleInterrupt(UHCIRequest request) { //log.debug("handleInterrupt"); // Go through all the TD's and look for active ones and error ones. TransferDescriptor errorTD = null; TransferDescriptor td = request.getFirstTD(); int actualLength = 0; while ((td != null) && (errorTD == null)) { if (td.isAnyError()) { //log.debug("Found error TD"); errorTD = td; } else if (td.isActive()) { //log.debug("Found active TD"); return; } else { td = td.getNextTD(); } } // Throw the corresponding exception in case of errors. int status = 0; if (errorTD != null) { if (errorTD.isStalled()) { status |= USBREQ_ST_STALLED; } if (errorTD.isDataBufferError()) { status |= USBREQ_ST_DATABUFFER; } if (errorTD.isBabbleDetected()) { status |= USBREQ_ST_BABBLE; } if (errorTD.isNAKReceived()) { status |= USBREQ_ST_NAK; } if (errorTD.isCRCTimeOutError()) { status |= USBREQ_ST_TIMEOUT; } if (errorTD.isBitstuffError()) { status |= USBREQ_ST_BITSTUFF; } } else { status |= USBREQ_ST_COMPLETED; } final AbstractUSBRequest usbReq = (AbstractUSBRequest) request; usbReq.setStatus(status); usbReq.setActualLength(actualLength); usbReq.setCompleted(true); firePipeEvent(request); } /** * Fire a pipe event to all my listeners * * @param request */ protected void firePipeEvent(UHCIRequest request) { final int max = listeners.size(); final int status = request.getStatus(); final boolean error = ((status & USBREQ_ST_ERROR_MASK) != 0); if (error && isInterruptPipe()) { if ((status & USBREQ_ST_NAK) != 0) { // No interrupt, ignore event return; } } for (int i = 0; i < max; i++) { USBPipeListener l = (USBPipeListener) listeners.get(i); if (error) { l.requestFailed(request); } else { l.requestCompleted(request); } } } /** * Create a transfer descriptor for use on this pipe. * * @param packetId * @param data0 * @param dataBuffer * @param dataBufferOffset * @param bufLength * @param ioc * @return The new TD */ protected TransferDescriptor createTD(int packetId, boolean data0, byte[] dataBuffer, int dataBufferOffset, int bufLength, boolean ioc) { final int devAddr = device.getUSBDeviceId(); final boolean iso = isIsochronousPipe(); final boolean ls = device.isLowSpeed(); return new TransferDescriptor(rm, devAddr, endPointNum, packetId, data0, dataBuffer, dataBufferOffset, bufLength, iso, ls, ioc); } /** * Gets the maximum packet size to use in this pipe. */ protected final int getMaxPacketSize() { if (maxPktSize > 0) { return maxPktSize; } else { return device.getMaxPacketSize(endPointNum); } } /** * @return Returns the endPoint. */ public USBEndPoint getEndPoint() { return this.endPoint; } }