www.pudn.com > vxworks_usb.rar > usbdCoreLib.c


/* usbdCoreLib.c - USBD core logic */

/* Copyright 2000-2002 Wind River Systems, Inc. */

/*
Modification history
--------------------
01l,08nov01,wef  USBD_NODE struture element changed from hubStatus to 
		 pHubStatus, reflect this change
01k,25oct01,wef  repalce automatic buffer creations with calls to OSS_MALLOC
		 in places where the buffer is passed to the hardware (related 
		 to SPR 70492).
01j,18sep01,wef  merge from wrs.tor2_0.usb1_1-f for veloce
01i,08aug01,dat  Removing warnings
01h,12aug00,bri  Modify interrogateDeviceClass() to 
		 1. Allow multifunction devices that belong to same USB class
		    but have interfaces belonging to different subclasses.
		 2. Store the configuration Value as mentioned in the 
		    configuration descriptor in the nodeClass.
		 3. Ensure nodeclasses are created for all devices.
01g,20mar00,rcb  Add USB_DEVICE_DESCR field to USBD_NODE.
01f,17jan00,rcb  Modify interrogateDeviceClass() to use usbConfigDescrGet()...
		 ensures that all descriptors associated with a configuration
		 descriptor will be read, even if the total length is very long.
01e,20dec99,rcb  Fix benign bug in fncConfigSet() regarding pRoot.
		 Fix benign bug in HCD detach logic which detached the HCD
		 before deleting all pipes.
01d,23nov99,rcb  Replace HCD bandwidth alloc/release with pipe create/destroy
		 in order to generalize approach for OHCI HCD.
01c,24sep99,rcb  Change packet count calculation in transferIrpCallback()
		 to handle case where a single 0-length packet is transferred.
01b,07sep99,rcb  Add support for management callbacks and set-bus-state API.
01a,08jun99,rcb  First.
*/

/*
DESCRIPTION

This module contains the primary USBD entry point, usbdUrbExec() and the
individual USBD function execution routines.  usbdUrbExec() is responsible for 
interpreting each URB's function code and then fanning-out to the individual 
function processor.  

IMPLEMENTATION NOTES

When the USBD is first initialized it creates an internal "client" which is 
used by the USBD when performing control pipe transfers to each hub/device.  This
"client" remains active until the USBD is shut down.

For each client registered with the USBD, including the internal client, the USBD
allocates an individual task, and that task inherits the priority of the client
task which invokes the usbdClientRegister() function.  This task is normally 
inactive, and only wakes up when a client callback is to be executed.  Therefore, 
each client's callbacks are invoked on a task which is unique to that client.
Other USBD tasks (see below) are therefore shielded from the behavoir of an
individual client's callback routine.

For each USB which is attached to the USBD through the usbdHcdAttach() function,
the USBD also creates a unique "bus monitor" task.  This task is responsible
for configuring and monitoring each hub on a given USB.  Whenever a hub or device
is attached/removed from the USB, the bus monitor thread is responsible for
updating internal data structures, configuring/re-configuring hubs, and for
triggering "dynamic attach/removal" callbacks - which themselves are performed by
each individual client's callback task.

All USBD internal data structures, e.g., client lists, HCD lists and releated
node structures, are protected by a single mutex, structMutex.	All threads which
rely on the stability of internal data structures or which can modify internal
data structures take this mutex.  usbdCoreEntry() is the single entry point
responsible for taking this mutex when clients invoke the USBD.  Each "bus
monitor" thread also takes this mutex each time it may update bus structures.  
IRP callback, however, do not take the mutex.  While certain IRP "userPtr" fields
may point to other USBD structures, the organization of the code guarantees that
IRPs will be completed or canceled prior to dismantling the USBD structures with
which they are associated.
*/


/* defines */

#define USBD_VERSION	0x0001  /* USBD version in BCD */
#define USBD_MFG	"Wind River Systems, Inc."  /* USBD Mfg */

#define INTERNAL_CLIENT_NAME	"usbd"


/* includes */

#include "usb/usbPlatform.h"

#include "string.h"

#include "usb/ossLib.h"
#include "usb/usbQueueLib.h"
#include "usb/usbLib.h"         /* USB utility functions */
#include "usb/usbdCoreLib.h"    /* our API */

#include "drv/usb/usbHcd.h"     /* HCD interface */
#include "usb/usbHcdLib.h"      /* HCD function library */

#include "usb/usbdStructures.h" /* usbdCoreLib data structures */


/* defines */

#define PENDING 		1

#define CALLBACK_Q_DEPTH	128 /* Needs to be fairly deep to handle */
                    /* a potentially large number of */
                    /* notification callbacks */

#define BUS_Q_DEPTH		32      /* Outstanding bus service requests */


/* clientThread request codes...stored in msg field of OSS_MESSAGE. */

#define CALLBACK_FNC_TERMINATE	    0   /* Terminate the callback thread */
#define CALLBACK_FNC_IRP_COMPLETE   1   /* issue an IRP completion callback */
#define CALLBACK_FNC_NOTIFY_ATTACH  2   /* issue an attach callback */
#define CALLBACK_FNC_MNGMT_EVENT    3   /* management event callback */


#define CALLBACK_TIMEOUT	5000    /* Wait 5 seconds for callback to */
                    /* exit in response to terminate fnc */

/* busThread request codes...stored in msg field of OSS_MESSAGE. */

#define BUS_FNC_TERMINATE	0   /* Terminate the bus monitor thread */
#define BUS_FNC_UPDATE_HUB	1   /* update a hub */


#define BUS_TIMEOUT		5000    /* Wait 5 seconds for bus monitor */
                    /* thread to terminate */


/* general timeout */

#define IO_TIMEOUT		5000    /* 5 seconds */
#define EXCLUSION_TIMEOUT	10000   /* 10 seconds */


/* HUB_STATUS_LEN() returns length of status structure for indicated hub node */

#define HUB_STATUS_LEN(pNode)	\
    min ((pNode->numPorts + 1 + 7) / 8, (int) MAX_HUB_STATUS_LEN)


/* Constants used by resetDataToggle(). */

#define ANY_CONFIGURATION	0xffff
#define ANY_INTERFACE		0xffff
#define ANY_ENDPOINT		0xffff


/* typedefs */

/* NOTIFICATION
 * 
 * Created by notifyIfMatch() and consumed by client's callback thread.
 */

typedef struct notification
{
    USBD_ATTACH_CALLBACK callback;  /* client's callback routine */
    USBD_NODE_ID nodeId;        /* node being attached/removed */
    UINT16 attachCode;          /* attach code */
    UINT16 configuration;       /* config matching notify request */
    UINT16 interface;           /* interface matching notify request */
    UINT16 deviceClass;         /* device/interface class */
    UINT16 deviceSubClass;      /* device/interface sub class */
    UINT16 deviceProtocol;      /* device/interface protcol */
}
NOTIFICATION, *pNOTIFICATION;


/* locals */

LOCAL int initCount = 0;

LOCAL MUTEX_HANDLE structMutex = NULL;  /* guards USBD structures */

LOCAL LIST_HEAD hcdList = { 0 };    /* List of attached HCDs */
LOCAL LIST_HEAD clientList = { 0 }; /* list of registered clients */

LOCAL USBD_CLIENT_HANDLE internalClient = NULL; /* internal client */


/* forward declarations */

LOCAL BOOL initHubIrp (pUSBD_NODE pNode);

LOCAL pUSBD_NODE createNode (pUSBD_BUS pBus,    /* node's parent bus */
                             USBD_NODE_ID rootId,   /* root id */
                             USBD_NODE_ID parentHubId,  /* parent hub id */
                             UINT16 parentHubPort,  /* parent hub port no */
                             UINT16 nodeSpeed,  /* node speed */
                             UINT16 topologyDepth   /* this node's depth in topology */
    );


/* functions */

/***************************************************************************
*
* validateClient - Determines if a client handle is valid
*
* RETURNS: TRUE if client valid, else FALSE
*/

LOCAL BOOL validateClient (USBD_CLIENT_HANDLE clientHandle, /* client to be validated */
                           pUSBD_CLIENT * ppClient  /* ptr to USBD_CLIENT if valid */
    )
{
    if (usbHandleValidate (clientHandle, USBD_CLIENT_SIG, (pVOID *) ppClient)
        != OK)
        return FALSE;

    return TRUE;
}


/***************************************************************************
*
* validateNode - Determines if a node handle is valid
*
* RETURNS: TRUE if node valid, else FALSE
*/

LOCAL BOOL validateNode (USBD_NODE_ID nodeId,   /* Node to be validated */
                         pUSBD_NODE * ppNode    /* ptr to USBD_NODE if valid */
    )
{
    if (usbHandleValidate (nodeId, USBD_NODE_SIG, (pVOID *) ppNode) != OK ||
        (*ppNode)->nodeDeletePending)
        return FALSE;

    return TRUE;
}


/***************************************************************************
*
* validatePipe - Determines if a pipe is valid
*
* RETURNS: TRUE if pipe valid, else FALSE
*/

LOCAL BOOL validatePipe (USBD_PIPE_HANDLE pipeHandle,   /* pipe to be validated */
                         pUSBD_PIPE * ppPipe    /* ptr to USBD_PIPE if valid */
    )
{
    if (usbHandleValidate (pipeHandle, USBD_PIPE_SIG, (pVOID *) ppPipe) != OK ||
        (*ppPipe)->pipeDeletePending || (*ppPipe)->pNode->nodeDeletePending)
        return FALSE;

    return TRUE;
}


/***************************************************************************
*
* validateUrb - Performs validation checks on URB
*
* Checks the URB length set in the URB_HEADER against the 
* passed by the caller.  If  is not NULL, also attempts to
* validate the USBD_CLIENT_HANDLE.  If successful, stores the client
* handle in 
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int validateUrb (pVOID pUrb, UINT16 expectedLen, pUSBD_CLIENT * ppClient)
{
    pURB_HEADER pHeader = (pURB_HEADER) pUrb;

    if (initCount == 0)
        return S_usbdLib_NOT_INITIALIZED;

    if (pHeader->urbLength != expectedLen)
        return S_usbdLib_BAD_PARAM;

    if (ppClient != NULL) {
        if (!validateClient (pHeader->handle, ppClient)) {
            return S_usbdLib_BAD_HANDLE;
        }
    }


    return OK;
}


/***************************************************************************
*
* setUrbResult - Sets URB_HEADER.status and .result fields
*
* Based on the  parameter passed by the caller, set the 
* status and result fields in the URB_HEADER of . 
*
* RETURNS: Value of  parameter
*/

LOCAL int setUrbResult (pURB_HEADER pUrb,   /* Completed URB */
                        int result  /* S_usbdLib_xxxx code */
    )
{
    if (result != PENDING) {
        pUrb->result = result;

        if (pUrb->callback != NULL)
            (*pUrb->callback) (pUrb);
    }

    return result;
}


/***************************************************************************
*
* doShutdown - Shut down USBD core lib
*
* RETURNS: N/A
*/

LOCAL VOID
doShutdown (void)
{
    /* unregister any clients which may still be registered */

    pUSBD_CLIENT pClient;
    pUSBD_HCD pHcd;

    while ((pClient = usbListFirst (&clientList)) != NULL)
        usbdClientUnregister (pClient->handle);

    /* detach any HCDs which may still be attached */

    while ((pHcd = usbListFirst (&hcdList)) != NULL)
        usbdHcdDetach (pHcd->attachToken);

    /* release the structure guard mutex */

    if (structMutex != NULL) {
        OSS_MUTEX_DESTROY (structMutex);
        structMutex = NULL;
    }

    /* Shut down libraries */

    usbHandleShutdown ();
    ossShutdown ();
}


/***************************************************************************
*
* fncInitialize - Initialize USBD
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncInitialize (pURB_HEADER pUrb)
{
    int s = OK;

    if (initCount == 0) {

        /* Initialize the osServices library */

        if (ossInitialize () != OK)
            return S_usbdLib_GENERAL_FAULT;

        if (usbHandleInitialize (0 /* use default */ ) != OK) {
            ossShutdown ();
            return S_usbdLib_GENERAL_FAULT;
        }

        ++initCount;

        /* Initialize the lists of HCDs and clients */

        if (OSS_MUTEX_CREATE (&structMutex) != OK)
            s = S_usbdLib_OUT_OF_RESOURCES;

        memset (&hcdList, 0, sizeof (hcdList));
        memset (&clientList, 0, sizeof (clientList));
    }

    if (s == OK) {
        /* Register an internal client for hub I/O, etc. */

        internalClient = NULL;

        if (usbdClientRegister (INTERNAL_CLIENT_NAME, &internalClient) != OK)
            s = S_usbdLib_GENERAL_FAULT;
    }

    if (s != OK) {
        doShutdown ();
        initCount = 0;
    }

    return s;
}


/***************************************************************************
*
* fncShutdown - Shuts down USBD
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncShutdown (pURB_HEADER pUrb)
{
    int s;

    /* validate URB */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), NULL)) != OK)
        return s;

    if (initCount == 1)
        doShutdown ();

    --initCount;

    return s;
}


/***************************************************************************
*
* clientThread - client callback thread
*
* A separate clientThread() thread is spawned for each registered
* client.  This thread is responsible for invoking client callback
* routines.  Using a separate callback thread for each client helps to
* ensure that one client's behavior (such as blocking) won't affect the
* throughput of other client requests.
*
* By convention, the  is a pointer to the USBD_CLIENT structure
* for the associated client.  This thread waits on the callback queue
* in the client structure.  At the time this thread is first created,
* the USBD_CLIENT structure is only guaranteed to have an initialized
* queue...other fields may not yet be initialized.
*
* NOTE: This thread does not need to take the structMutex.  The mainline
* code ensures that the clientThread() for a given USBD_CLIENT is
* terminated prior to dismantling the USBD_CLIENT structure.
*
* RETURNS: N/A
*/

LOCAL VOID clientThread (pVOID param    /* thread parameter */
    )
{
    pUSBD_CLIENT pClient = (pUSBD_CLIENT) param;
    USB_MESSAGE msg;
    pUSB_IRP pIrp;
    pNOTIFICATION pNotification;

    /* Execute messages from the callbackQueue until a CLIENT_FNC_TERMINATE
       message is received. */

    do {
        if (usbQueueGet (pClient->callbackQueue, &msg, OSS_BLOCK) != OK)
            break;

        switch (msg.msg) {
        case CALLBACK_FNC_IRP_COMPLETE:

            /* invoke a client's IRP callback routine.  The msg.lParam
             * is a pointer to the completed USB_IRP.
             */

            pIrp = (pUSB_IRP) msg.lParam;

            if (pIrp->userCallback != NULL)
                (*pIrp->userCallback) (pIrp);

            break;


        case CALLBACK_FNC_NOTIFY_ATTACH:

            /* invoke a client's dynamic attach routine.  The msg.lParam
             * is a pointer to a NOTIFICATION structure.  
             *
             * NOTE: We dispose of the NOTIFICATION request after
             * consuming it.
             */

            pNotification = (pNOTIFICATION) msg.lParam;

            (*pNotification->callback) (pNotification->nodeId,
                                        pNotification->attachCode, pNotification->configuration,
                                        pNotification->interface, pNotification->deviceClass,
                                        pNotification->deviceSubClass,
                                        pNotification->deviceProtocol);

            OSS_FREE (pNotification);

            break;


        case CALLBACK_FNC_MNGMT_EVENT:

            /* invoke a client's managment callback routine.  The 
             * msg.wParam is the management code and the msg.lParam is
             * the USBD_NODE_ID of the root for the affected bus.
             */

            if (pClient->mngmtCallback != NULL)
                (*pClient->mngmtCallback) (pClient->mngmtCallbackParam,
                                           (USBD_NODE_ID) msg.lParam, msg.wParam);

            break;
        }
    }
    while (msg.msg != CALLBACK_FNC_TERMINATE);


    /* Mark the callback routine as having terminated. */

    OSS_SEM_GIVE (pClient->callbackExit);
}


/***************************************************************************
*
* doTransferAbort - Cancel an outstanding IRP
*
* Directs the HCD to cancel the IRP and waits for the IRP callback to
* be invoked signalling that the IRP has been unlinked successfully.
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int doTransferAbort (pUSBD_PIPE pPipe,    /* Pipe owning transfer */
                           pUSB_IRP pIrp    /* IRP to be cancelled */
    )
{
    /* The callback which indicates that an IRP has been deleted is
     * asynchronous.  However, when deleting an IRP (such as when
     * destroying a pipe) we generally need to know when the IRP
     * callback has actually been invoked - and hence the IRP unlinked
     * from the list of outstanding IRPs on the pipe.
     */

    pPipe->irpBeingDeleted = pIrp;
    pPipe->irpDeleted = FALSE;

    /* Instruct the HCD to cancel the IRP */

    if (usbHcdIrpCancel (&pPipe->pNode->pBus->pHcd->nexus, pIrp) != OK)
        return S_usbdLib_CANNOT_CANCEL;

    /* Wait for the IRP callback to be invoked. */

    while (!pPipe->irpDeleted)
        OSS_THREAD_SLEEP (1);

    return OK;
}


/***************************************************************************
*
* destroyNotify - de-allocates a USBD_NOTIFY_REQ
*
* RETURNS: N/A
*/

LOCAL VOID destroyNotify (pUSBD_NOTIFY_REQ pNotify)
{
    usbListUnlink (&pNotify->reqLink);
    OSS_FREE (pNotify);
}


/***************************************************************************
*
* destroyPipe - de-allocates a USBD_PIPE and its resources
*
* RETURNS: N/A
*/

LOCAL VOID destroyPipe (pUSBD_PIPE pPipe)
{
    pUSB_IRP pIrp;
    pUSBD_BUS pBus;


    if (pPipe != NULL) {
        pPipe->pipeDeletePending = TRUE;


        /* Cancel all IRPs outstanding on this pipe.
         *
         * NOTE: Since the IRP completion callbacks are on a different thread,
         * we need to wait until all callbacks have been completed.  This
         * functionality is built into doTransferAbort().
         */

        while ((pIrp = usbListFirst (&pPipe->irps)) != NULL)
            doTransferAbort (pPipe, pIrp);


        /* Release bandwidth and notify HCD that the pipe is going away. */

        pBus = pPipe->pNode->pBus;
        pBus->nanoseconds -= pPipe->nanoseconds;

        usbHcdPipeDestroy (&pBus->pHcd->nexus, pPipe->hcdHandle);


        /* Unlink pipe from owning client's list of pipes */

        if (pPipe->pClient != NULL)
            usbListUnlink (&pPipe->clientPipeLink);


        /* Unlink pipe from owning node's list of pipes */

        if (pPipe->pNode != NULL)
            usbListUnlink (&pPipe->nodePipeLink);


        /* Release pipe handle */

        if (pPipe->handle != NULL)
            usbHandleDestroy (pPipe->handle);


        OSS_FREE (pPipe);
    }
}


/***************************************************************************
*
* destroyClient - tears down a USBD_CLIENT structure
*
* RETURNS: N/A
*/

LOCAL VOID destroyClient (pUSBD_CLIENT pClient)
{
    pUSBD_NOTIFY_REQ pNotify;
    pUSBD_PIPE pPipe;
    pUSBD_HCD pHcd;
    UINT16 busNo;

    /* unlink client from list of clients.
     *
     * NOTE: usbListUnlink is smart and only unlinks the structure if it is
     * actually linked.
     */

    usbListUnlink (&pClient->clientLink);


    /* destroy all notification requests outstanding for the client */

    while ((pNotify = usbListFirst (&pClient->notifyReqs)) != NULL)
        destroyNotify (pNotify);


    /* destroy all pipes owned by this client */

    while ((pPipe = usbListFirst (&pClient->pipes)) != NULL)
        destroyPipe (pPipe);


    /* If this client is the current SOF master for any USBs, then release
     * the SOF master status.
     */

    pHcd = usbListFirst (&hcdList);

    while (pHcd != NULL) {
        for (busNo = 0; busNo < pHcd->busCount; busNo++)
            if (pHcd->pBuses[busNo].pSofMasterClient == pClient)
                pHcd->pBuses[busNo].pSofMasterClient = NULL;

        pHcd = usbListNext (&pHcd->hcdLink);
    }


    /* Note: callbackQueue is always created after callbackExit and
     * before callbackThread
     */

    if (pClient->callbackThread != NULL) {
        /* Terminate the client callback thread */

        usbQueuePut (pClient->callbackQueue, CALLBACK_FNC_TERMINATE, 0, 0, CALLBACK_TIMEOUT);
        OSS_SEM_TAKE (pClient->callbackExit, CALLBACK_TIMEOUT);
        OSS_THREAD_DESTROY (pClient->callbackThread);
    }

    if (pClient->callbackQueue != NULL)
        usbQueueDestroy (pClient->callbackQueue);

    if (pClient->callbackExit != NULL)
        OSS_SEM_DESTROY (pClient->callbackExit);

    if (pClient->handle != NULL)
        usbHandleDestroy (pClient->handle);

    OSS_FREE (pClient);
}


/***************************************************************************
*
* fncClientReg - Register a new USBD client
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncClientReg (pURB_CLIENT_REG pUrb)
{
    pUSBD_CLIENT pClient;
    int s;

    /* validate URB */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), NULL)) != OK)
        return s;


    /* Create structures/resources/etc., required by new client */

    if ((pClient = OSS_CALLOC (sizeof (*pClient))) == NULL)
        return S_usbdLib_OUT_OF_MEMORY;

    memcpy (pClient->clientName, pUrb->clientName, USBD_NAME_LEN);


    if (usbHandleCreate (USBD_CLIENT_SIG, pClient, &pClient->handle) != OK ||
        OSS_SEM_CREATE (1, 0, &pClient->callbackExit) != OK ||
        usbQueueCreate (CALLBACK_Q_DEPTH, &pClient->callbackQueue) != OK ||
        OSS_THREAD_CREATE (clientThread, pClient, OSS_PRIORITY_INHERIT,
                           "tUsbdClnt", &pClient->callbackThread) != OK)

        s = S_usbdLib_OUT_OF_RESOURCES;
    else {

        /* The client was initialized successfully. Add it to the list */

        usbListLink (&clientList, pClient, &pClient->clientLink, LINK_TAIL);

        /* return the client's USBD_CLIENT_HANDLE */

        pUrb->header.handle = pClient->handle;
    }


    if (s != OK) {
        destroyClient (pClient);
    }

    return s;
}


/***************************************************************************
*
* fncClientUnreg - Unregister a USBD client
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncClientUnreg (pURB_CLIENT_UNREG pUrb)
{
    pUSBD_CLIENT pClient;
    int s;

    /* validate Urb */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), &pClient)) != OK)
        return s;

    /* destroy client */

    destroyClient (pClient);

    return s;
}


/***************************************************************************
*
* fncMngmtCallbackSet - sets management callback for a client
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncMngmtCallbackSet (pURB_MNGMT_CALLBACK_SET pUrb)
{
    pUSBD_CLIENT pClient;
    int s;

    /* validate URB */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), &pClient)) != OK)
        return s;

    /* Set the management callback */

    pClient->mngmtCallback = pUrb->mngmtCallback;
    pClient->mngmtCallbackParam = pUrb->mngmtCallbackParam;

    return s;
}


/***************************************************************************
*
* fncVersionGet - Return USBD version
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncVersionGet (pURB_VERSION_GET pUrb)
{
    int s;

    /* validate urb */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), NULL)) != OK)
        return s;


    /* return version information */

    pUrb->version = USBD_VERSION;
    strncpy ((char *) pUrb->mfg, USBD_MFG, USBD_NAME_LEN);

    return s;
}


/***************************************************************************
*
* releaseAddress - release a USB device address
*
* RETURNS: N/A
*/

LOCAL VOID releaseAddress (pUSBD_NODE pNode)
{
    pUSBD_BUS pBus = pNode->pBus;

    pBus->adrsVec[pNode->busAddress / 8] &= ~(0x1 << (pNode->busAddress % 8));
}


/***************************************************************************
*
* assignAddress - assigns a unique USB address to a node
*
* RETURNS: TRUE if successful, else FALSE
*/

LOCAL BOOL assignAddress (pUSBD_NODE pNode)
{
    pUSBD_BUS pBus = pNode->pBus;
    UINT16 i;

    /* Find an available address */

    for (i = 1; i < USB_MAX_DEVICES; i++) {
        if ((pBus->adrsVec[i / 8] & (0x1 << (i % 8))) == 0) {
            /* i is the value of an unused address.  Set the device adrs. */

            if (usbdAddressSet (internalClient, pNode->nodeHandle, i) == OK) {
                pNode->busAddress = i;
                pBus->adrsVec[i / 8] |= 0x1 << (i % 8);
                return TRUE;
            } else {
                return FALSE;
            }
        }
    }

    return FALSE;
}


/***************************************************************************
*
* notifyIfMatch - Invoke attach callback if appropriate.
*
* Compares the device class/subclass/protocol for  and .
* If the two match, then invokes the callback in  with an attach
* code of .
*
* RETURNS: N/A
*/

LOCAL VOID notifyIfMatch
    (pUSBD_NODE pNode,
     pUSBD_NODE_CLASS pClassType, pUSBD_CLIENT pClient, pUSBD_NOTIFY_REQ pNotify, UINT16 attachCode)
{
    pNOTIFICATION pNotification;


    /* Do the pClassType and pNotify structures contain matching class
     * descriptions?
     */

    if (pNotify->deviceClass == USBD_NOTIFY_ALL || pClassType->deviceClass == pNotify->deviceClass) {
        if (pNotify->deviceSubClass == USBD_NOTIFY_ALL ||
            pClassType->deviceSubClass == pNotify->deviceSubClass) {
            if (pNotify->deviceProtocol == USBD_NOTIFY_ALL ||
                pClassType->deviceProtocol == pNotify->deviceProtocol) {
                /* We have a match.  Schedule the client attach callback. 
                 *
                 * NOTE: The pNotification structure is created here and
                 * released when consumed in the client callback thread.
                 *
                 * NOTE: In a very large USB topology (at least several
                 * dozen nodes) there is a chance that we could overrun
                 * a client's callback queue depending on the type of 
                 * notification requests the client has made.  If this happens,
                 * the following call to usbQueuePut() may block.  If *that*
                 * happens, there is a chance of a deadlock if the client's
                 * callback code - invoked from the clientThread() reenters
                 * the USBD.  A simple solution, if that situation is observed,
                 * is to increase the depth of the callback queue.
                 */

                if ((pNotification = OSS_CALLOC (sizeof (*pNotification)))
                    != NULL) {

                    pNotification->callback = pNotify->callback;
                    pNotification->nodeId = pNode->nodeHandle;
                    pNotification->attachCode = attachCode;
                    pNotification->configuration = pClassType->configuration;
                    pNotification->interface = pClassType->interface;
                    pNotification->deviceClass = pClassType->deviceClass;
                    pNotification->deviceSubClass = pClassType->deviceSubClass;
                    pNotification->deviceProtocol = pClassType->deviceProtocol;

                    usbQueuePut (pClient->callbackQueue,
                                 CALLBACK_FNC_NOTIFY_ATTACH, 0, (UINT32) pNotification, OSS_BLOCK);
                }
            }
        }
    }
}


/***************************************************************************
*
* notifyClients - notify clients of a dynamic attach/removal
*
* Scans the clientList looking for clients who have requested dynamic
* attach/removal notification for a class matching .  If any
* are found, their dynamic attach callback routines will be invoked
* with an attach code of .
*
* RETURNS: N/A
*/

LOCAL VOID notifyClients (pUSBD_NODE pNode, UINT16 attachCode)
{
    pUSBD_CLIENT pClient;
    pUSBD_NOTIFY_REQ pNotify;
    pUSBD_NODE_CLASS pClassType;


    /* Scan for clients which have requested dynamic attach notification. */

    pClient = usbListFirst (&clientList);

    while (pClient != NULL) {
        /* Walk through the list of notification requests for this client */

        pNotify = usbListFirst (&pClient->notifyReqs);

        while (pNotify != NULL) {
            /* Walk through the class types for this node, looking for one
             * which matches the current client notification request.
             */

            pClassType = usbListFirst (&pNode->classTypes);

            while (pClassType != NULL) {
                notifyIfMatch (pNode, pClassType, pClient, pNotify, attachCode);
                pClassType = usbListNext (&pClassType->classLink);
            }

            pNotify = usbListNext (&pNotify->reqLink);
        }

        pClient = usbListNext (&pClient->clientLink);
    }
}


/***************************************************************************
*
* createNodeClass - Creates a USBD_NODE_CLASS structure
*
* Creates a USBD_NODE_CLASS structure, initializes it with the passed
* parameters, and attaches it to the .
*
* RETURNS: TRUE if successful, else FALSE if out of memory.
*/

LOCAL BOOL createNodeClass
    (pUSBD_NODE pNode,
     UINT16 configuration,
     UINT16 interface, UINT16 deviceClass, UINT16 deviceSubClass, UINT16 deviceProtocol)
{
    pUSBD_NODE_CLASS pClassType;

    if ((pClassType = OSS_CALLOC (sizeof (*pClassType))) == NULL)
        return FALSE;

    pClassType->configuration = configuration;
    pClassType->interface = interface;
    pClassType->deviceClass = deviceClass;
    pClassType->deviceSubClass = deviceSubClass;
    pClassType->deviceProtocol = deviceProtocol;

    usbListLink (&pNode->classTypes, pClassType, &pClassType->classLink, LINK_TAIL);

    return TRUE;
}


/***************************************************************************
*
* interrogateDeviceClass - Retrieve's device class information
*
* Interrogate the device class and construct one or more USBD_NODE_CLASS 
* structures and attach them to .  , ,
* and  are the device-level fields for this device.  If
*  is 0, then this routine will automatically interrogate
* the interface-level class information. If the information is provided
* at the device level then the interfaces are not probed. 
*
* RETURNS: TRUE if successful, else FALSE if error.
*/

LOCAL BOOL interrogateDeviceClass
    (pUSBD_NODE pNode, UINT16 deviceClass, UINT16 deviceSubClass, UINT16 deviceProtocol)
{
    pUSB_CONFIG_DESCR pCfgDescr;
    pUSB_INTERFACE_DESCR pIfDescr;
    pUINT8 pCfgBfr;
    pUINT8 pRemBfr;
    UINT16 cfgLen;
    UINT16 remLen;
    UINT16 numConfig;
    UINT8 configIndex;
    BOOL retcode = TRUE;
    UINT8 configValue = 1;


    /* If class information has been specified at the device level, then
     * record it and return.
     */

    if ((deviceClass != 0) && (deviceSubClass != 0) && (deviceProtocol != 0))
        return createNodeClass (pNode, 0, 0, deviceClass, deviceSubClass, deviceProtocol);


    /* Read the device descriptor to determine the number of configurations. */

    if (usbConfigCountGet (internalClient, pNode->nodeHandle, &numConfig) != OK)
        return FALSE;


    /* Read and process each configuration. */

    for (configIndex = 0; configIndex < numConfig && retcode; configIndex++) {
        /* Read the next configuration descriptor. */

        if (usbConfigDescrGet (internalClient, pNode->nodeHandle, configIndex,
                               &cfgLen, &pCfgBfr) != OK) {
            retcode = FALSE;
        } else {
            if ((pCfgDescr = usbDescrParse (pCfgBfr, cfgLen, USB_DESCR_CONFIGURATION)) != NULL) {
                /* Scan each interface descriptor. 
                 *
                 * NOTE: If we can't find an interface descriptor, we just keep
                 * going. 
                 */

                pRemBfr = pCfgBfr;
                remLen = cfgLen;
                configValue = pCfgDescr->configurationValue;

                while ((pIfDescr = usbDescrParseSkip (&pRemBfr, &remLen,
                                                      USB_DESCR_INTERFACE)) != NULL && retcode) {
                    if (!createNodeClass (pNode, configValue,
                                          pIfDescr->interfaceNumber, pIfDescr->interfaceClass,
                                          pIfDescr->interfaceSubClass, pIfDescr->interfaceProtocol))
                    {
                        retcode = FALSE;
                    }
                }
            }

            /* Release bfr allocated by usbConfigDescrGet(). */

            OSS_FREE (pCfgBfr);
        }
    }


    return retcode;
}


/***************************************************************************
*
* destroyNode - deletes node structure
*
* RETURNS: N/A
*/

LOCAL VOID destroyNode (pUSBD_NODE pNode)
{
    pUSBD_NODE_CLASS pClassType;
    pUSBD_PIPE pPipe;


    /* Mark node as "going down" */

    pNode->nodeDeletePending = TRUE;


    /* Cancel any other pipes associated with this node...This has the
     * effect of cancelling any IRPs outstanding on this node. */

    while ((pPipe = usbListFirst (&pNode->pipes)) != NULL)
        destroyPipe (pPipe);


    /* Notify an interested clients that this node is going away. */

    notifyClients (pNode, USBD_DYNA_REMOVE);


    /* Delete any class type structures associated with this node. */

    while ((pClassType = usbListFirst (&pNode->classTypes)) != NULL) {
        usbListUnlink (&pClassType->classLink);
        OSS_FREE (pClassType);
    }


    /* release bus address associated with node */

    releaseAddress (pNode);


    /* Release node resources/memory. */

    if (pNode->controlSem != NULL)
        OSS_SEM_DESTROY (pNode->controlSem);

    if (pNode->nodeHandle != NULL)
        usbHandleDestroy (pNode->nodeHandle);

    OSS_FREE (pNode->pHubStatus);

    OSS_FREE (pNode);
}


/***************************************************************************
*
* destroyAllNodes - destroys all nodes in a tree of nodes
*
* Recursively destroys all nodes from  and down.  If  is
* NULL, returns immediately.
*
* RETURNS: N/A
*/

LOCAL VOID destroyAllNodes (pUSBD_NODE pNode)
{
    int i;

    if (pNode != NULL) {
        if (pNode->nodeInfo.nodeType == USB_NODETYPE_HUB && pNode->pPorts != NULL) {
            /* destroy all children of this node */

            for (i = 0; i < pNode->numPorts; i++)
                destroyAllNodes (pNode->pPorts[i].pNode);
        }

        destroyNode (pNode);
    }
}


/***************************************************************************
*
* updateHubPort - Determine if a device has been attached/removed
*
* Note: The  parameter to this function is 0-based (0 = first port,
* 1 = second port, etc.)  However, on the USB interface itself, the index
* passed in a Setup packet to identify a port is 1-based.
*
* RETURNS: N/A
*/

LOCAL VOID updateHubPort (pUSBD_NODE pNode, UINT16 port)
{
    USB_HUB_STATUS *pStatus;
    UINT16 actLen;
    UINT16 nodeSpeed;

    pStatus = OSS_MALLOC (sizeof (USB_HUB_STATUS));

    /* retrieve status for the indicated hub port. */

    if (usbdStatusGet (internalClient,
                       pNode->nodeHandle,
                       USB_RT_CLASS | USB_RT_OTHER,
                       port + 1,
                       sizeof (USB_HUB_STATUS),
                       (UINT8 *) pStatus, &actLen) != OK || actLen < sizeof (USB_HUB_STATUS)) {
        OSS_FREE (pStatus);
        return;
    }

    /* correct the status byte order */

    pStatus->status = FROM_LITTLEW (pStatus->status);
    pStatus->change = FROM_LITTLEW (pStatus->change);


    /* Process the change indicated by status.change */

    /* NOTE: To see if a port's connection status has changed we also see
     * whether our internal understanding of the port's connection status
     * matches the hubs's current connection status.  If the two do not
     * agree, then we also assume a connection change, whether the hub is
     * indicating one or not. This covers the case at power on where the
     * hub does not report a connection change (i.e., the device is already
     * plugged in), but we need to initialize the port for the first time.
     */

    if ((pStatus->change & USB_HUB_C_PORT_CONNECTION) != 0 ||
        ((pNode->pPorts[port].pNode == NULL) !=
         ((pStatus->status & USB_HUB_STS_PORT_CONNECTION) == 0))) {
        /* Given that there has been a change on this port, it stands to
         * reason that all nodes connected to this port must be invalidated -
         * even if a node currently appears to be connected to the port.
         */

        destroyAllNodes (pNode->pPorts[port].pNode);
        pNode->pPorts[port].pNode = NULL;


        /* If a node appears to be connected to the port, reset the port. */

        if ((pStatus->status & USB_HUB_STS_PORT_CONNECTION) != 0) {

            /* Wait 100 msec after detecting a connection to allow power to
             * stabilize.  Then enable the port and issue a reset.
             */

            OSS_THREAD_SLEEP (USB_TIME_POWER_ON);

            usbdFeatureSet (internalClient, pNode->nodeHandle,
                            USB_RT_CLASS | USB_RT_OTHER, USB_HUB_FSEL_PORT_RESET, port + 1);

            /* 
             * Acoording to the USB spec (refer to section 7.1.7.3) we should
             * wait 50 msec for reset signaling + an additional 10 msec reset 
             * recovery time.  We double this time for safety's sake since 
             * we have seen a few devices that do not respond to the suggested 
             * time of 60 msec.
             */

            OSS_THREAD_SLEEP (2 * USB_TIME_RESET + USB_TIME_RESET_RECOVERY);
        }


        /* Clear the port connection change indicator - whether or not it
         * appears that a device is currently connected. */

        usbdFeatureClear (internalClient, pNode->nodeHandle,
                          USB_RT_CLASS | USB_RT_OTHER, USB_HUB_FSEL_C_PORT_CONNECTION, port + 1);


        /* If a node appears to be connected, create a new node structure for 
         * it and attach it to this hub. */

        if ((pStatus->status & USB_HUB_STS_PORT_CONNECTION) != 0) {
            /* If the following call to createNode() fails, it returns NULL.
             * That's fine, as the corresponding USBD_PORT structure will be
             * initialized correctly either way.
             */

            nodeSpeed = ((pStatus->status & USB_HUB_STS_PORT_LOW_SPEED) != 0) ?
                USB_SPEED_LOW : USB_SPEED_FULL;

            if ((pNode->pPorts[port].pNode = createNode (pNode->pBus,
                                                         pNode->pBus->pRoot->nodeHandle,
                                                         pNode->nodeHandle, port, nodeSpeed,
                                                         pNode->topologyDepth + 1)) == NULL) {
                /* Attempt to initialize the port failed.  Disable the port */

                usbdFeatureClear (internalClient, pNode->nodeHandle,
                                  USB_RT_CLASS | USB_RT_OTHER, USB_HUB_FSEL_PORT_ENABLE, port + 1);
            }
        }
    }

    if ((pStatus->change & USB_HUB_C_PORT_ENABLE) != 0) {
        /* Clear the port enable change indicator */

        usbdFeatureClear (internalClient, pNode->nodeHandle,
                          USB_RT_CLASS | USB_RT_OTHER, USB_HUB_FSEL_C_PORT_ENABLE, port + 1);
    }

    if ((pStatus->change & USB_HUB_C_PORT_SUSPEND) != 0) {
        /* Clear the suspend change indicator */

        usbdFeatureClear (internalClient, pNode->nodeHandle,
                          USB_RT_CLASS | USB_RT_OTHER, USB_HUB_FSEL_C_PORT_SUSPEND, port + 1);
    }

    if ((pStatus->change & USB_HUB_C_PORT_OVER_CURRENT) != 0) {
        /* Clear the over current change indicator */

        usbdFeatureClear (internalClient, pNode->nodeHandle,
                          USB_RT_CLASS | USB_RT_OTHER, USB_HUB_FSEL_C_PORT_OVER_CURRENT, port + 1);
    }

    if ((pStatus->change & USB_HUB_C_PORT_RESET) != 0) {
        /* Clear the reset change indicator */

        usbdFeatureClear (internalClient, pNode->nodeHandle,
                          USB_RT_CLASS | USB_RT_OTHER, USB_HUB_FSEL_C_PORT_RESET, port + 1);
    }
}


/***************************************************************************
*
* hubIrpCallback - called when hub status IRP completes
*
* By convention, the USB_IRP.userPtr field is a pointer to the USBD_NODE
* for this transfer.
*
* RETURNS: N/A
*/

LOCAL VOID hubIrpCallback (pVOID p  /* completed IRP */
    )
{
    pUSB_IRP pIrp = (pUSB_IRP) p;
    pUSBD_NODE pNode = (pUSBD_NODE) pIrp->userPtr;


    /* If the IRP was canceled, it means that the hub node is being torn
     * down, so don't queue a request for the bus thread.
     */

    if (pIrp->result != S_usbHcdLib_IRP_CANCELED) {
        /* Let the bus thread update the port and re-queue the hub IRP. */

        usbQueuePut (pNode->pBus->busQueue, BUS_FNC_UPDATE_HUB,
                     0, (UINT32) pNode->nodeHandle, OSS_BLOCK);
    }
}


/***************************************************************************
*
* initHubIrp - initialize IRP used to listen for hub status
*
* RETURNS: TRUE if successful, else FALSE
*/

LOCAL BOOL initHubIrp (pUSBD_NODE pNode)
{
    pUSB_IRP pIrp = &pNode->hubIrp;


    /* initialize IRP */

    memset (pIrp, 0, sizeof (*pIrp));

    pIrp->userPtr = pNode;
    pIrp->irpLen = sizeof (USB_IRP);
    pIrp->userCallback = hubIrpCallback;
    pIrp->flags = USB_FLAG_SHORT_OK;
    pIrp->timeout = USB_TIMEOUT_NONE;

    pIrp->transferLen = HUB_STATUS_LEN (pNode);

    pIrp->bfrCount = 1;
    pIrp->bfrList[0].pid = USB_PID_IN;
    pIrp->bfrList[0].pBfr = (pUINT8) pNode->pHubStatus;
    pIrp->bfrList[0].bfrLen = pIrp->transferLen;

    memset (pNode->pHubStatus, 0, MAX_HUB_STATUS_LEN);


    /* submit the IRP. */

    if (usbdTransfer (internalClient, pNode->hubStatusPipe, pIrp) != OK)
        return FALSE;

    return TRUE;
}


/***************************************************************************
*
* initHubNode - initialize a hub
*
* RETURNS: TRUE if successful, else FALSE
*/

LOCAL BOOL initHubNode (pUSBD_NODE pNode)
{
    pUSB_CONFIG_DESCR pCfgDescr;
    pUSB_INTERFACE_DESCR pIfDescr;
    pUSB_ENDPOINT_DESCR pEpDescr;
    pUSB_HUB_DESCR pHubDescrBuf;
    UINT8 *pConfDescrBfr;
    UINT16 actLen;
    UINT16 port;

    pConfDescrBfr = OSS_MALLOC (USB_MAX_DESCR_LEN);
    pHubDescrBuf = OSS_MALLOC (sizeof (USB_HUB_DESCR));


    /* Read the hub's descriptors. */

    if (usbdDescriptorGet (internalClient,
                           pNode->nodeHandle,
                           USB_RT_STANDARD | USB_RT_DEVICE,
                           USB_DESCR_CONFIGURATION, 0, 0, USB_MAX_DESCR_LEN, pConfDescrBfr, &actLen)
        != OK) {
        OSS_FREE (pConfDescrBfr);
        OSS_FREE (pHubDescrBuf);
        return FALSE;
    }

    if ((pCfgDescr = usbDescrParse (pConfDescrBfr, actLen, USB_DESCR_CONFIGURATION))
        == NULL || (pIfDescr = usbDescrParse (pConfDescrBfr, actLen, USB_DESCR_INTERFACE))
        == NULL || (pEpDescr = usbDescrParse (pConfDescrBfr, actLen, USB_DESCR_ENDPOINT))
        == NULL) {
        OSS_FREE (pConfDescrBfr);
        OSS_FREE (pHubDescrBuf);
        return FALSE;
    }

    if (usbdDescriptorGet (internalClient,
                           pNode->nodeHandle,
                           USB_RT_CLASS | USB_RT_DEVICE,
                           USB_DESCR_HUB,
                           0, 0, sizeof (USB_HUB_DESCR), (UINT8 *) pHubDescrBuf, &actLen)
        != OK || actLen < USB_HUB_DESCR_LEN) {
        OSS_FREE (pConfDescrBfr);
        OSS_FREE (pHubDescrBuf);
        return FALSE;
    }


    /* We now have all important descriptors for this hub.  Pick out
     * important information and configure hub.
     */

    /* Record hub power capability */

    pNode->pwrGoodDelay = pHubDescrBuf->pwrOn2PwrGood * 2;  /* 2 msec per unit */
    pNode->hubContrCurrent = pHubDescrBuf->hubContrCurrent;

    if ((pCfgDescr->attributes & USB_ATTR_SELF_POWERED) != 0) {
        pNode->selfPowered = TRUE;
        pNode->maxPowerPerPort = USB_POWER_SELF_POWERED;
    } else {
        pNode->selfPowered = FALSE;
        pNode->maxPowerPerPort = USB_POWER_BUS_POWERED;
    }

    pNode->numPorts = pHubDescrBuf->nbrPorts;


    /* If this hub is not already at the maximum USB topology depth, then
     * create a pipe to monitor it's status and enable each of its ports.
     */

    if (pNode->topologyDepth < USB_MAX_TOPOLOGY_DEPTH) {
        /* Set the hub's configuration. */

        if (usbdConfigurationSet (internalClient,
                                  pNode->nodeHandle,
                                  pCfgDescr->configurationValue, pNode->hubContrCurrent)
            != OK) {
            OSS_FREE (pConfDescrBfr);
            OSS_FREE (pHubDescrBuf);
            return FALSE;
        }


        /* Create a pipe for interrupt transfers from this device. */

        if (usbdPipeCreate (internalClient,
                            pNode->nodeHandle,
                            pEpDescr->endpointAddress,
                            pCfgDescr->configurationValue,
                            0,
                            USB_XFRTYPE_INTERRUPT,
                            USB_DIR_IN,
                            FROM_LITTLEW (pEpDescr->maxPacketSize),
                            HUB_STATUS_LEN (pNode), pEpDescr->interval, &pNode->hubStatusPipe)
            != OK) {
            OSS_FREE (pConfDescrBfr);
            OSS_FREE (pHubDescrBuf);
            return FALSE;
        }


        /* Allocate structures for downstream nodes. */

        if ((pNode->pPorts = OSS_CALLOC (pNode->numPorts * sizeof (USBD_PORT)))
            == NULL) {
            OSS_FREE (pConfDescrBfr);
            OSS_FREE (pHubDescrBuf);
            return FALSE;
        }


        /* Initialize each hub port */

        for (port = 0; port < pNode->numPorts; port++) {
            /* Enable power to the port */

            usbdFeatureSet (internalClient, pNode->nodeHandle,
                            USB_RT_CLASS | USB_RT_OTHER, USB_HUB_FSEL_PORT_POWER, port + 1);
        }


        OSS_THREAD_SLEEP (pNode->pwrGoodDelay); /* let power stabilize */


        /* Initialize an IRP to listen for status changes on the hub */

        if (!initHubIrp (pNode)) {
            OSS_FREE (pConfDescrBfr);
            OSS_FREE (pHubDescrBuf);
            return FALSE;
        }
    }

    OSS_FREE (pConfDescrBfr);
    OSS_FREE (pHubDescrBuf);

    return TRUE;
}


/***************************************************************************
*
* createNode - Creates and initializes new USBD_NODE
*
* If the node is a hub, then automatically initializes hub.
*
* RETURNS: pointer to newly created USBD_NODE, or
*	   NULL if not successful
*/

LOCAL pUSBD_NODE createNode (pUSBD_BUS pBus,    /* node's parent bus */
                             USBD_NODE_ID rootId,   /* root id */
                             USBD_NODE_ID parentHubId,  /* parent hub id */
                             UINT16 parentHubPort,  /* parent hub port no */
                             UINT16 nodeSpeed,  /* node speed */
                             UINT16 topologyDepth   /* this node's depth in topology */
    )
{
    pUSBD_NODE pNode;
    pUSBD_PIPE pPipe;
    UINT16 actLen;


    /* Allocate/initialize USBD_NODE */

    if ((pNode = OSS_CALLOC (sizeof (*pNode))) == NULL)
        return NULL;

    /* 
     * The hub status buffer gets touched by hardware.  Ensure that 
     * it does not span a cache line
     */

    if ((pNode->pHubStatus = OSS_CALLOC (MAX_HUB_STATUS_LEN)) == NULL) {
        OSS_FREE (pNode);
        return NULL;
    }

    if (usbHandleCreate (USBD_NODE_SIG, pNode, &pNode->nodeHandle) != OK ||
        OSS_SEM_CREATE (1, 1, &pNode->controlSem) != OK) {
        destroyNode (pNode);
        return NULL;
    }

    pNode->pBus = pBus;
    pNode->nodeInfo.nodeSpeed = nodeSpeed;
    pNode->nodeInfo.parentHubId = parentHubId;
    pNode->nodeInfo.parentHubPort = parentHubPort;
    pNode->nodeInfo.rootId = (rootId == 0) ? pNode->nodeHandle : rootId;
    pNode->topologyDepth = topologyDepth;


    /* Create a pipe for control transfers to this device. */

    if (usbdPipeCreate (internalClient, pNode->nodeHandle, USB_ENDPOINT_CONTROL,
                        0, 0, USB_XFRTYPE_CONTROL, USB_DIR_INOUT, USB_MIN_CTRL_PACKET_SIZE, 0, 0,
                        &pNode->controlPipe) != OK) {
        destroyNode (pNode);
        return NULL;
    }


    /* Read the device descriptor to get the maximum payload size and to
     * determine if this is a hub or not.
     *
     * NOTE: We read only the first 8 bytes of the device descriptor (which
     * takes us through the maxPacketSize field) as suggested by the USB
     * spec.
     */

    if (usbdDescriptorGet (internalClient, pNode->nodeHandle,
                           USB_RT_STANDARD | USB_RT_DEVICE, USB_DESCR_DEVICE, 0, 0,
                           USB_MIN_CTRL_PACKET_SIZE, (pUINT8) & pNode->devDescr, &actLen) != OK ||
        actLen < USB_MIN_CTRL_PACKET_SIZE) {
        destroyNode (pNode);
        return NULL;
    }


    /* Now that we've read the device descriptor, we know the actual
     * packet size supported by the control pipe.  Update the pipe
     * accordingly.
     */

    validatePipe (pNode->controlPipe, &pPipe);
    pPipe->maxPacketSize = pNode->devDescr.maxPacketSize0;  /* field is byte wide */


    /* Set a unique address for this device. */

    if (!assignAddress (pNode)) {
        destroyNode (pNode);
        return NULL;
    }


    /* Notify the HCD of the change in device address and max packet size */

    usbHcdPipeModify (&pBus->pHcd->nexus, pPipe->hcdHandle,
                      pNode->busAddress, pPipe->maxPacketSize);


    /* If this is a hub node, it requires additional initialization.  Otherwise,
     * we're done unless and until a client performs additional I/O to this
     * device.
     */

    if (pNode->devDescr.deviceClass == USB_CLASS_HUB) {
        pNode->nodeInfo.nodeType = USB_NODETYPE_HUB;
        if (!initHubNode (pNode)) {
            destroyNode (pNode);
            return NULL;
        }
    } else {
        pNode->nodeInfo.nodeType = USB_NODETYPE_DEVICE;
    }


    /* Read the device class type(s) from the device and notify interested
     * clients of the device's insertion.
     */

    interrogateDeviceClass (pNode, pNode->devDescr.deviceClass,
                            pNode->devDescr.deviceSubClass, pNode->devDescr.deviceProtocol);

    notifyClients (pNode, USBD_DYNA_ATTACH);

    return pNode;
}


/***************************************************************************
*
* checkHubStatus - checks hub status and updates hub as necessary
*
* Note: We take a Node ID as our parameter (instead of, say, a pointer
* to a USBD_NODE structure) so that we can validate the node upon entry.
* There are cases where this routine may be called when the underlying
* node has already disappeared.
*
* RETURNS: N/A
*/

LOCAL VOID checkHubStatus (USBD_NODE_ID nodeId)
{
    pUSBD_NODE pNode;
    pUSB_IRP pIrp;
    UINT16 port;
    UINT8 portMask;
    UINT8 statusIndex;


    /* Is the node still valid? */

    if (!validateNode (nodeId, &pNode))
        return;

    pIrp = &pNode->hubIrp;

    /* Determine what status is available.
     *
     * The hubStatus vector contains one bit for the hub itself and 
     * then one bit for each port.
     */

    if (pIrp->result == OK) {
        port = 0;
        statusIndex = 0;
        portMask = USB_HUB_ENDPOINT_STS_PORT0;

        while (port < pNode->numPorts && statusIndex < pIrp->bfrList[0].actLen) {
            if ((*(pNode->pHubStatus + statusIndex) & portMask) != 0) {
                /* This port appears to have status to report */

                updateHubPort (pNode, port);
            }

            port++;
            portMask <<= 1;

            if (portMask == 0) {
                portMask = 0x01;    /* next byte starts with bit 0 */
                statusIndex++;
            }
        }
    }


    /* resubmit the IRP */

    if (pIrp->result != S_usbHcdLib_IRP_CANCELED)
        initHubIrp (pNode);
}


/***************************************************************************
*
* busThread - bus monitor thread
*
* A separate busThread() thread is spawned for each bus currently
* attached to the USBD.  This thread is responsible for monitoring and
* responding to bus events, like the attachment and removal of devices.
* Using a separate thread for each bus helps to ensure that one bus's 
* behavior won't affect the throughput of other buses.
*
* By convention, the  is a pointer to the USBD_BUS structure
* for the associated bus.  This thread waits on the bus queue
* in the bus structure.  At the time this thread is first created,
* the USBD_BUS structure is only guaranteed to have an initialized
* queue...other fields may not yet be initialized.
*
* RETURNS: N/A
*/

LOCAL VOID busThread (pVOID param   /* thread parameter */
    )
{
    pUSBD_BUS pBus = (pUSBD_BUS) param;
    USB_MESSAGE msg;


    /* Execute messages from the busQueue until a BUS_FNC_TERMINATE
       message is received. */

    do {
        if (usbQueueGet (pBus->busQueue, &msg, OSS_BLOCK) != OK)
            break;

        switch (msg.msg) {
        case BUS_FNC_UPDATE_HUB:

            OSS_MUTEX_TAKE (structMutex, OSS_BLOCK);
            checkHubStatus ((USBD_NODE_ID) msg.lParam);
            OSS_MUTEX_RELEASE (structMutex);

            break;
        }
    }
    while (msg.msg != BUS_FNC_TERMINATE);


    /* Mark the callback routine as having terminated. */

    OSS_SEM_GIVE (pBus->busExit);
}


/***************************************************************************
*
* hcdMngmtCallback - invoked when HCD detects managment event
*
* RETURNS: N/A
*/

LOCAL VOID hcdMngmtCallback (pVOID mngmtCallbackParam,  /* caller-defined param */
                             HCD_CLIENT_HANDLE handle,  /* handle to host controller */
                             UINT16 busNo,  /* bus number */
                             UINT16 mngmtCode   /* management code */
    )
{
    pUSBD_HCD pHcd = (pUSBD_HCD) mngmtCallbackParam;
    pUSBD_BUS pBus;
    pUSBD_CLIENT pClient;


    /* In an unusual case, this routine could be invoked by the HCD
     * before we have completely initialized our structures.  In that
     * case, just ignore the event.
     */

    if (pHcd->pBuses == NULL)
        return;

    pBus = &pHcd->pBuses[busNo];

    if (pBus->busThread == NULL || pBus->busQueue == NULL ||
        pBus->pRoot == NULL || pBus->pRoot->nodeHandle == NULL)
        return;


    /* Reflect the management event to interested clients */

    OSS_MUTEX_TAKE (structMutex, OSS_BLOCK);

    pClient = usbListFirst (&clientList);

    while (pClient != NULL) {
        if (pClient->mngmtCallback != NULL)
            usbQueuePut (pClient->callbackQueue, CALLBACK_FNC_MNGMT_EVENT,
                         mngmtCode, (UINT32) pBus->pRoot->nodeHandle, OSS_BLOCK);

        pClient = usbListNext (&pClient->clientLink);
    }

    OSS_MUTEX_RELEASE (structMutex);
}


/***************************************************************************
*
* initHcdBus - Initialize USBD_BUS element of USBD_HCD structure
*
*  points to the USBD_HCD being initialized.   is the index
* of the HCD bus to be initialized.
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int initHcdBus (pUSBD_HCD pHcd,   /* USBD_HCD being initialized */
                      UINT16 busNo  /* Bus number to initialize */
    )
{
    pUSBD_BUS pBus = &pHcd->pBuses[busNo];
    int s = OK;

    /* Allocate resources for this bus. */

    pBus->pHcd = pHcd;
    pBus->busNo = busNo;

    if (OSS_SEM_CREATE (1, 0, &pBus->busExit) != OK ||
        usbQueueCreate (BUS_Q_DEPTH, &pBus->busQueue) != OK ||
        OSS_THREAD_CREATE (busThread, pBus, OSS_PRIORITY_HIGH, "tUsbdBus",
                           &pBus->busThread) != OK ||
        (pBus->pRoot = createNode (pBus, 0, 0, 0, USB_SPEED_FULL, 0)) == NULL) {
        /* NOTE: If we fail here, destroyHcd() will clean up partially
         * allocate structures/resources/etc.
         */

        return S_usbdLib_OUT_OF_RESOURCES;
    }


    return s;
}


/***************************************************************************
*
* destroyHcd - tears down a USBD_HCD structure
*
* Detaches the indicated HCD and tears down the HCD structures.
*
* RETURNS: N/A
*/

LOCAL VOID destroyHcd (pUSBD_HCD pHcd)
{
    pUSBD_BUS pBus;
    UINT16 busNo;


    /* Unlink the HCD */

    usbListUnlink (&pHcd->hcdLink);


    /* Begin by de-allocating resources related to each bus */

    for (busNo = 0; pHcd->pBuses != NULL && busNo < pHcd->busCount; busNo++) {

        pBus = &pHcd->pBuses[busNo];

        /* Destroy all nodes associated with this bus */

        destroyAllNodes (pBus->pRoot);


        /* NOTE: The busQueue is always allocated before the busThread. */

        if (pBus->busThread != NULL) {
            /* Issue a terminate request to the bus thread */

            usbQueuePut (pBus->busQueue, BUS_FNC_TERMINATE, 0, 0, BUS_TIMEOUT);
            OSS_SEM_TAKE (pBus->busExit, BUS_TIMEOUT);
            OSS_THREAD_DESTROY (pBus->busThread);
        }

        if (pBus->busQueue != NULL)
            usbQueueDestroy (pBus->busQueue);

        if (pBus->busExit != NULL)
            OSS_SEM_DESTROY (pBus->busQueue);
    }



    /* Detach the HCD */

    if (pHcd->nexus.hcdExecFunc != NULL && pHcd->nexus.handle != NULL)
        usbHcdDetach (&pHcd->nexus);


    /* Release USBD_HCD resources */

    if (pHcd->attachToken != NULL)
        usbHandleDestroy (pHcd->attachToken);

    OSS_FREE (pHcd);
}


/***************************************************************************
*
* fncHcdAttach - Attach an HCD to the USBD
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncHcdAttach (pURB_HCD_ATTACH pUrb)
{
    pUSBD_HCD pHcd = NULL;
    UINT16 busNo;
    int s;

    /* validate URB */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), NULL)) != OK)
        return s;


    /* Allocate structure for this host controller */

    if ((pHcd = OSS_CALLOC (sizeof (*pHcd))) == NULL)
        return S_usbdLib_OUT_OF_MEMORY;


    /* Issue an attach request to the HCD.  If it succeeds, determine the
     * number of buses managed by the HCD.
     */

    if (usbHcdAttach (pUrb->hcdExecFunc, pUrb->param, hcdMngmtCallback,
                      pHcd, &pHcd->nexus, &pHcd->busCount) != OK) {
        OSS_FREE (pHcd);
        return S_usbdLib_GENERAL_FAULT;
    }


    if ((pHcd->pBuses = OSS_CALLOC (sizeof (USBD_BUS) * pHcd->busCount)) == NULL) {
        destroyHcd (pHcd);
        return S_usbdLib_OUT_OF_MEMORY;
    }


    /* Initialize the USBD_HCD */

    if (usbHandleCreate (USBD_HCD_SIG, pHcd, &pHcd->attachToken) != OK) {
        s = S_usbdLib_OUT_OF_RESOURCES;
    } else {
        /* Fetch information about each bus from the HCD. */

        for (busNo = 0; busNo < pHcd->busCount; busNo++) {
            if ((s = initHcdBus (pHcd, busNo)) != OK)
                break;
        }
    }


    /* If we succeeded in initializing each bus, then the USBD_HCD
     * is fully initialized...link it into the list of HCDs
     */

    if (s == OK) {

        usbListLink (&hcdList, pHcd, &pHcd->hcdLink, LINK_TAIL);

        /* Return attachToken to caller */

        pUrb->attachToken = pHcd->attachToken;
    } else {
        /* Failed to attach...release allocated structs, etc. */

        destroyHcd (pHcd);
    }

    return s;
}


/***************************************************************************
*
* fncHcdDetach - Detach an HCD from the USBD
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncHcdDetach (pURB_HCD_DETACH pUrb)
{
    pUSBD_HCD pHcd;
    int s;

    /* Validate URB */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), NULL)) != OK)
        return s;


    /* Validate attachToken */

    if (usbHandleValidate (pUrb->attachToken, USBD_HCD_SIG, (pVOID *) & pHcd)
        != OK)
        return S_usbdLib_BAD_HANDLE;


    /* Detach the HCD and release HCD structures, etc. */

    destroyHcd (pHcd);

    return s;
}


/***************************************************************************
*
* fncStatisticsGet - Return USBD operating statistics
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncStatisticsGet (pURB_STATISTICS_GET pUrb)
{
    pUSBD_NODE pNode;

    /* Validate root */

    if (!validateNode (pUrb->nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;

    /* Validate other parameters */

    if (pUrb->pStatistics == NULL)
        return S_usbdLib_BAD_PARAM;

    /* Copy statistics to callers buffer */

    memcpy (pUrb->pStatistics, &pNode->pBus->stats,
            min (pUrb->statLen, sizeof (pNode->pBus->stats)));

    return OK;
}


/***************************************************************************
*
* fncBusCountGet - Returns number of USB buses in system
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncBusCountGet (pURB_BUS_COUNT_GET pUrb)
{
    pUSBD_HCD pHcd;
    UINT16 busCount;

    busCount = 0;
    pHcd = usbListFirst (&hcdList);

    while (pHcd != NULL) {
        busCount += pHcd->busCount;
        pHcd = usbListNext (&pHcd->hcdLink);
    }

    pUrb->busCount = busCount;

    return OK;
}


/***************************************************************************
*
* fncRootIdGet - Returns root node id for a USB
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncRootIdGet (pURB_ROOT_ID_GET pUrb)
{
    pUSBD_HCD pHcd;
    UINT16 i;
    int s = OK;

    /* Find the HCD/bus corresponding to the index pUrb->busCount */

    i = pUrb->busIndex;
    pHcd = usbListFirst (&hcdList);

    while (pHcd != NULL) {
        if (i < pHcd->busCount) {
            if (pHcd->pBuses[i].pRoot != NULL)
                pUrb->rootId = pHcd->pBuses[i].pRoot->nodeHandle;
            else
                s = S_usbdLib_INTERNAL_FAULT;
            break;
        } else {
            i -= pHcd->busCount;
            pHcd = usbListNext (&pHcd->hcdLink);
        }
    }

    if (pHcd == NULL)
        s = S_usbdLib_BAD_PARAM;

    return s;
}


/***************************************************************************
*
* fncHubPortCountGet - Returns number of hubs on a port
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncHubPortCountGet (pURB_HUB_PORT_COUNT_GET pUrb)
{
    pUSBD_NODE pNode;

    /* validate node handle */

    if (!validateNode (pUrb->hubId, &pNode))
        return S_usbdLib_BAD_HANDLE;

    if (pNode->nodeInfo.nodeType != USB_NODETYPE_HUB)
        return S_usbdLib_NOT_HUB;


    /* return number of ports on this hub */

    pUrb->portCount = pNode->numPorts;

    return OK;
}


/***************************************************************************
*
* fncNodeIdGet - Returns id of node attached to a hub port
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncNodeIdGet (pURB_NODE_ID_GET pUrb)
{
    pUSBD_NODE pNode;

    /* validate node handle */

    if (!validateNode (pUrb->hubId, &pNode))
        return S_usbdLib_BAD_HANDLE;

    if (pNode->nodeInfo.nodeType != USB_NODETYPE_HUB)
        return S_usbdLib_NOT_HUB;


    /* If the port index is valid, return the node id, if any, attached
     * to this port.
     */

    if (pUrb->portIndex >= pNode->numPorts)
        return S_usbdLib_BAD_PARAM;

    pUrb->nodeType = USB_NODETYPE_NONE;

    if (pNode->pPorts != NULL && pNode->pPorts[pUrb->portIndex].pNode != NULL) {
        pUrb->nodeType = pNode->pPorts[pUrb->portIndex].pNode->nodeInfo.nodeType;
        pUrb->nodeId = pNode->pPorts[pUrb->portIndex].pNode->nodeHandle;
    }

    return OK;
}


/***************************************************************************
*
* scanClassTypes - Scans nodes for matching class types
*
* Scan  and all child nodes to see if they have exposed one or more
* class types matching that described in .  If we find match(es),
* then invoke the corresponding notification callbacks.
*
* RETURNS: N/A
*/

LOCAL VOID scanClassTypes (pUSBD_NODE pNode, pUSBD_CLIENT pClient, pUSBD_NOTIFY_REQ pNotify)
{
    pUSBD_NODE_CLASS pClassType;
    UINT16 portNo;


    if (pNode != NULL) {
        /* Scan all class types exposed for this node. */

        pClassType = usbListFirst (&pNode->classTypes);

        while (pClassType != NULL) {
            notifyIfMatch (pNode, pClassType, pClient, pNotify, USBD_DYNA_ATTACH);

            pClassType = usbListNext (&pClassType->classLink);
        }


        /* If this node is a hub, then recursively scan child nodes. */

        if (pNode->nodeInfo.nodeType == USB_NODETYPE_HUB && pNode->pPorts != NULL) {
            for (portNo = 0; portNo < pNode->numPorts; portNo++)
                scanClassTypes (pNode->pPorts[portNo].pNode, pClient, pNotify);
        }
    }
}


/***************************************************************************
*
* fncDynaAttachReg - Register a client for dynamic attach notification
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncDynaAttachReg (pURB_DYNA_ATTACH_REG_UNREG pUrb)
{
    pUSBD_CLIENT pClient;
    pUSBD_NOTIFY_REQ pNotify;
    pUSBD_HCD pHcd;
    UINT16 busNo;
    int s;


    /* Validate parameters. */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), &pClient)) != OK)
        return s;

    if (pUrb->attachCallback == NULL)
        return S_usbdLib_BAD_PARAM;


    /* Create a new dynamic notification request structure */

    if ((pNotify = OSS_CALLOC (sizeof (*pNotify))) == NULL)
        return S_usbdLib_OUT_OF_MEMORY;

    pNotify->deviceClass = pUrb->deviceClass;
    pNotify->deviceSubClass = pUrb->deviceSubClass;
    pNotify->deviceProtocol = pUrb->deviceProtocol;
    pNotify->callback = pUrb->attachCallback;


    /* Link this to the list of notification requests for the client */

    usbListLink (&pClient->notifyReqs, pNotify, &pNotify->reqLink, LINK_TAIL);

    /* At the time of the initial notification registration (now), it is
     * necessary to scan the list of devices already attached to the USB(s)
     * in order to see if any match the request.  Devices attached/removed
     * later will be handled by the busThread.
     */

    pHcd = usbListFirst (&hcdList);

    while (pHcd != NULL) {
        for (busNo = 0; busNo < pHcd->busCount; busNo++)
            scanClassTypes (pHcd->pBuses[busNo].pRoot, pClient, pNotify);

        pHcd = usbListNext (&pHcd->hcdLink);
    }


    return OK;
}


/***************************************************************************
*
* fncDynaAttachUnreg - Unregister a client for dynamic attach notification
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncDynaAttachUnreg (pURB_DYNA_ATTACH_REG_UNREG pUrb)
{
    pUSBD_CLIENT pClient;
    pUSBD_NOTIFY_REQ pNotify;
    int s;


    /* Validate parameters. */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), &pClient)) != OK)
        return s;


    /* Search the client's list of notification requests for one which
     * matches exactly the parameters in the URB.
     */

    pNotify = usbListFirst (&pClient->notifyReqs);

    while (pNotify != NULL) {
        if (pNotify->deviceClass == pUrb->deviceClass &&
            pNotify->deviceSubClass == pUrb->deviceSubClass &&
            pNotify->deviceProtocol == pUrb->deviceProtocol &&
            pNotify->callback == pUrb->attachCallback) {
            /* We found a matching notification request.  Destroy it. */

            destroyNotify (pNotify);
            return OK;
        }

        pNotify = usbListNext (&pNotify->reqLink);
    }


    /* If we get this far, no matching request was found.  Declare an error */

    return S_usbdLib_GENERAL_FAULT;
}


/***************************************************************************
*
* controlIrpCallback - called when control pipe Irp completes
*
* By convention, the USB_IRP.userPtr field is a pointer to the USBD_NODE
* for this transfer.
*
* RETURNS: N/A
*/

LOCAL VOID controlIrpCallback (pVOID p  /* completed IRP */
    )
{
    pUSB_IRP pIrp = (pUSB_IRP) p;
    pUSBD_NODE pNode = pIrp->userPtr;
    pURB_HEADER pUrb = pNode->pUrb;

    /* Store the actual length transferred */

    if (pNode->pActLen != NULL)
        *pNode->pActLen = pIrp->bfrList[1].actLen;


    /* Store the completion result in the URB */

    setUrbResult (pUrb, (pIrp->result == OK) ? OK : S_usbdLib_IO_FAULT);


    /* We're done with the control pipe structures for this node. */

    OSS_SEM_GIVE (pNode->controlSem);
}


/***************************************************************************
*
* controlRequest - Formats and submits a control transfer request
*
* This is an internal utility function which formats a USB Setup packet
* and sends it to the control pipe of a node.
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int controlRequest (pURB_HEADER pUrb, /* URB header */
                          USBD_NODE_ID nodeId,  /* node id */
                          UINT8 requestType,    /* bmRequestType */
                          UINT8 request,    /* bRequest */
                          UINT16 value, /* wValue */
                          UINT16 index, /* wIndex */
                          UINT16 length,    /* wLength */
                          pVOID pBfr,   /* data */
                          pUINT16 pActLen   /* actual len for IN data */
    )
{
    pUSBD_CLIENT pClient;
    pUSBD_NODE pNode;
    pUSB_SETUP pSetup;
    pUSB_IRP pIrp;


    /* validate the client handle for this request */

    if (!validateClient (pUrb->handle, &pClient))
        return S_usbdLib_BAD_HANDLE;


    /* validate the node to which this request is directed */

    if (!validateNode (nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;


    /* Only one control request can be outstanding at a time. */

    if (OSS_SEM_TAKE (pNode->controlSem, EXCLUSION_TIMEOUT) != OK)
        return S_usbdLib_TIMEOUT;


    /* we now own the control mutex for this node.  format and issue the
     * control request to this node.
     */

    pSetup = &pNode->setup;
    pIrp = &pNode->irp;


    /* format the setup packet */

    pSetup->requestType = requestType;
    pSetup->request = request;
    pSetup->value = TO_LITTLEW (value);
    pSetup->index = TO_LITTLEW (index);
    pSetup->length = TO_LITTLEW (length);


    /* format an IRP to execute this control transfer */

    memset (pIrp, 0, sizeof (USB_IRP) + sizeof (USB_BFR_LIST));

    pIrp->userPtr = pNode;
    pIrp->irpLen = sizeof (USB_IRP) + sizeof (USB_BFR_LIST);
    pIrp->userCallback = controlIrpCallback;
    pIrp->flags = USB_FLAG_SHORT_OK;
    pIrp->timeout = USB_TIMEOUT_DEFAULT;

    pIrp->transferLen = sizeof (USB_SETUP) + length;

    /* format bfrList [] entry for Setup packet */

    pIrp->bfrCount = 0;

    pIrp->bfrList[pIrp->bfrCount].pid = USB_PID_SETUP;
    pIrp->bfrList[pIrp->bfrCount].pBfr = (pUINT8) pSetup;
    pIrp->bfrList[pIrp->bfrCount].bfrLen = sizeof (USB_SETUP);
    pIrp->bfrCount++;

    /* format bfrList [] entry for data stage, if any. */

    if (length > 0) {
        pIrp->bfrList[pIrp->bfrCount].pid =
            ((requestType & USB_RT_DEV_TO_HOST) != 0) ? USB_PID_IN : USB_PID_OUT;
        pIrp->bfrList[pIrp->bfrCount].pBfr = pBfr;
        pIrp->bfrList[pIrp->bfrCount].bfrLen = length;
        pIrp->bfrCount++;
    }

    /* All control transfers are followed by a "status" packet for which
     * the direction is the opposite of that for the data stage and the
     * length of the transfer is 0. */

    pIrp->bfrList[pIrp->bfrCount].pid =
        ((requestType & USB_RT_DEV_TO_HOST) != 0) ? USB_PID_OUT : USB_PID_IN;
    pIrp->bfrList[pIrp->bfrCount].pBfr = NULL;
    pIrp->bfrList[pIrp->bfrCount].bfrLen = 0;
    pIrp->bfrCount++;


    /* Store info about pending control IRP in node struct */

    pNode->pClient = pClient;
    pNode->pUrb = pUrb;
    pNode->pActLen = pActLen;


    /* submit the control transfer IRP.
     *
     * NOTE: If usbHcdIrpSubmit fails, it will still invoke the IRP callback
     * which will in turn release the controlSem taken above.  The
     * callback will also set the URB completion status.
     */

    if (usbdTransfer (internalClient, pNode->controlPipe, pIrp) != OK)
        return S_usbdLib_GENERAL_FAULT;

    return PENDING;
}



/***************************************************************************
*
* resetDataToggle - reset data toggle on affected pipes
*
* This function is called when a "configuration event" is detected
* for a given node.  This function searches all pipes associated with
* the node for any that might be affected by the configuration event
* and resets their data toggles to DATA0.
*
* RETURNS: N/A
*/

LOCAL VOID resetDataToggle
    (USBD_NODE_ID nodeId, UINT16 configuration, UINT16 interface, UINT16 endpoint)
{
    pUSBD_NODE pNode;
    pUSBD_PIPE pPipe;

    if (!validateNode (nodeId, &pNode))
        return;

    pPipe = usbListFirst (&pNode->pipes);

    while (pPipe != NULL) {
        if (configuration == ANY_CONFIGURATION || configuration == pPipe->configuration) {
            if (interface == ANY_INTERFACE || interface == pPipe->interface) {
                if (endpoint == ANY_ENDPOINT || endpoint == pPipe->endpoint) {
                    pPipe->dataToggle = USB_DATA0;
                }
            }
        }

        pPipe = usbListNext (&pPipe->nodePipeLink);
    }
}


/***************************************************************************
*
* fncFeatureClear - Clear a USB feature
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncFeatureClear (pURB_FEATURE_CLEAR_SET pUrb)
{
    /* Check if this constitutes a configuration event.  So, reset the 
     * data toggle for any affected pipes.
     */

    if (pUrb->requestType == (USB_RT_STANDARD | USB_RT_ENDPOINT) &&
        pUrb->feature == USB_FSEL_DEV_ENDPOINT_HALT) {
        resetDataToggle (pUrb->nodeId, ANY_CONFIGURATION, ANY_INTERFACE, pUrb->index);
    }

    return controlRequest (&pUrb->header, pUrb->nodeId,
                           USB_RT_HOST_TO_DEV | pUrb->requestType, USB_REQ_CLEAR_FEATURE,
                           pUrb->feature, pUrb->index, 0, NULL, NULL);
}


/***************************************************************************
*
* fncFeatureSet - Set a USB feature
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncFeatureSet (pURB_FEATURE_CLEAR_SET pUrb)
{
    return controlRequest (&pUrb->header, pUrb->nodeId,
                           USB_RT_HOST_TO_DEV | pUrb->requestType, USB_REQ_SET_FEATURE,
                           pUrb->feature, pUrb->index, 0, NULL, NULL);
}


/***************************************************************************
*
* fncConfigGet - Get a device's configuration
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncConfigGet (pURB_CONFIG_GET_SET pUrb)
{
    pUrb->configuration = 0;

    return controlRequest (&pUrb->header, pUrb->nodeId,
                           USB_RT_DEV_TO_HOST | USB_RT_STANDARD | USB_RT_DEVICE,
                           USB_REQ_GET_CONFIGURATION, 0, 0, 1, &pUrb->configuration, NULL);
}


/***************************************************************************
*
* fncConfigSet - Set a device's configuration
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncConfigSet (pURB_CONFIG_GET_SET pUrb)
{
    pUSBD_NODE pNode;
    pUSBD_NODE pParentNode;


    /* Validate parameters */

    if (!validateNode (pUrb->nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;


    /* Verify that the port to which this device is connected is capable of
     * providing the current needed by this device.
     */

    if (pNode != pNode->pBus->pRoot && pNode->pBus->pRoot != NULL) {
        /* Retrieve the pointer to the parent hub for this node */

        validateNode (pNode->nodeInfo.parentHubId, &pParentNode);

        /* Check the power required against the parent's capability */

        if (pUrb->maxPower > pParentNode->maxPowerPerPort)
            return S_usbdLib_POWER_FAULT;
    }


    /* This constitutes a configuration event.  So, reset the data toggle
     * for any affected pipes.
     */

    resetDataToggle (pUrb->nodeId, pUrb->configuration, ANY_INTERFACE, ANY_ENDPOINT);


    /* Set the device's configuration */

    return controlRequest (&pUrb->header, pUrb->nodeId,
                           USB_RT_HOST_TO_DEV | USB_RT_STANDARD | USB_RT_DEVICE,
                           USB_REQ_SET_CONFIGURATION, pUrb->configuration, 0, 0, NULL, NULL);
}


/***************************************************************************
*
* fncDescriptorGet - Retrieve a USB descriptor
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncDescriptorGet (pURB_DESCRIPTOR_GET_SET pUrb)
{
    return controlRequest (&pUrb->header, pUrb->nodeId,
                           USB_RT_DEV_TO_HOST | pUrb->requestType, USB_REQ_GET_DESCRIPTOR,
                           pUrb->descriptorType << 8 | pUrb->descriptorIndex,
                           pUrb->languageId, pUrb->bfrLen, pUrb->pBfr, &pUrb->actLen);
}


/***************************************************************************
*
* fncDescriptorSet - Sets a USB descriptor
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncDescriptorSet (pURB_DESCRIPTOR_GET_SET pUrb)
{
    return controlRequest (&pUrb->header, pUrb->nodeId,
                           USB_RT_HOST_TO_DEV | pUrb->requestType, USB_REQ_SET_DESCRIPTOR,
                           pUrb->descriptorType << 8 | pUrb->descriptorIndex,
                           pUrb->languageId, pUrb->bfrLen, pUrb->pBfr, NULL);
}


/***************************************************************************
*
* fncInterfaceGet - Returns a device's interface setting
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncInterfaceGet (pURB_INTERFACE_GET_SET pUrb)
{
    pUrb->alternateSetting = 0;

    return controlRequest (&pUrb->header, pUrb->nodeId,
                           USB_RT_DEV_TO_HOST | USB_RT_STANDARD | USB_RT_INTERFACE,
                           USB_REQ_GET_INTERFACE, 0, pUrb->interfaceIndex, 1,
                           &pUrb->alternateSetting, NULL);
}


/***************************************************************************
*
* fncInterfaceSet - Sets a device's interface setting
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncInterfaceSet (pURB_INTERFACE_GET_SET pUrb)
{
    /* This constitutes a configuration event.  So, reset the data toggle
     * for any affected pipes.
     */

    resetDataToggle (pUrb->nodeId, ANY_CONFIGURATION, pUrb->interfaceIndex, ANY_ENDPOINT);

    return controlRequest (&pUrb->header, pUrb->nodeId,
                           USB_RT_HOST_TO_DEV | USB_RT_STANDARD | USB_RT_INTERFACE,
                           USB_REQ_SET_INTERFACE, pUrb->alternateSetting, pUrb->interfaceIndex,
                           0, NULL, NULL);
}


/***************************************************************************
*
* fncStatusGet - Returns a device/interface/endpoint status word
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncStatusGet (pURB_STATUS_GET pUrb)
{
    return controlRequest (&pUrb->header, pUrb->nodeId,
                           USB_RT_DEV_TO_HOST | pUrb->requestType, USB_REQ_GET_STATUS,
                           0, pUrb->index, pUrb->bfrLen, pUrb->pBfr, &pUrb->actLen);
}


/***************************************************************************
*
* fncAddressGet - Returns a node's USB address
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncAddressGet (pURB_ADDRESS_GET_SET pUrb)
{
    pUSBD_NODE pNode;

    /* validate node handle */

    if (!validateNode (pUrb->nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;

    /* return current USB address for node */

    pUrb->deviceAddress = pNode->busAddress;

    return OK;
}


/***************************************************************************
*
* fncAddressSet - Sets a node's USB address
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncAddressSet (pURB_ADDRESS_GET_SET pUrb)
{
    int s;

    s = controlRequest (&pUrb->header, pUrb->nodeId,
                        USB_RT_HOST_TO_DEV | USB_RT_STANDARD | USB_RT_DEVICE,
                        USB_REQ_SET_ADDRESS, pUrb->deviceAddress, 0, 0, NULL, NULL);

    OSS_THREAD_SLEEP (USB_TIME_SET_ADDRESS);

    return s;
}


/***************************************************************************
*
* fncNodeInfoGet - Returns information about a node
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncNodeInfoGet (pURB_NODE_INFO_GET pUrb)
{
    pUSBD_NODE pNode;

    /* Validate root */

    if (!validateNode (pUrb->nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;

    /* Validate other parameters */

    if (pUrb->pNodeInfo == NULL)
        return S_usbdLib_BAD_PARAM;

    /* Copy node info to caller's buffer */

    memcpy (pUrb->pNodeInfo, &pNode->nodeInfo, min (pUrb->infoLen, sizeof (pNode->nodeInfo)));

    return OK;
}


/***************************************************************************
*
* fncVendorSpecific - Executes a vendor-specific USB request
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncVendorSpecific (pURB_VENDOR_SPECIFIC pUrb)
{
    return controlRequest (&pUrb->header, pUrb->nodeId,
                           pUrb->requestType, pUrb->request, pUrb->value, pUrb->index,
                           pUrb->length, pUrb->pBfr, &pUrb->actLen);
}


/***************************************************************************
*
* fncPipeCreate - Create a new transfer pipe
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncPipeCreate (pURB_PIPE_CREATE pUrb)
{
    pUSBD_CLIENT pClient;
    pUSBD_NODE pNode;
    pUSBD_PIPE pPipe;
    pUSBD_BUS pBus;
    UINT32 nanoseconds = 0;
    HCD_PIPE_HANDLE hcdPipeHandle;


    /* validate the client handle for this request */

    if (!validateClient (pUrb->header.handle, &pClient))
        return S_usbdLib_BAD_HANDLE;


    /* validate the node to which this request is directed */

    if (!validateNode (pUrb->nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;


    /* Validate other parameters */

    if ((pUrb->transferType == USB_XFRTYPE_ISOCH ||
         pUrb->transferType == USB_XFRTYPE_INTERRUPT) && pUrb->bandwidth == 0)
        return S_usbdLib_BAD_PARAM;

    if (pUrb->transferType == USB_XFRTYPE_INTERRUPT && pUrb->serviceInterval == 0)
        return S_usbdLib_BAD_PARAM;


    /* Make sure a default packet size is specified */

    if (pUrb->maxPayload == 0)
        pUrb->maxPayload = USB_MIN_CTRL_PACKET_SIZE;


    /* Notify HCD of new pipe and make sure enough bandwidth is available to 
     * create the pipe. 
     */

    pBus = pNode->pBus;

    if (usbHcdPipeCreate (&pBus->pHcd->nexus, pBus->busNo,
                          pNode->busAddress, pUrb->endpoint,
                          pUrb->transferType, pUrb->direction, pNode->nodeInfo.nodeSpeed,
                          pUrb->maxPayload, pUrb->bandwidth, pUrb->serviceInterval,
                          &nanoseconds, &hcdPipeHandle) != OK) {
        return S_usbdLib_BANDWIDTH_FAULT;
    }


    /* Try to create a new pipe structure */

    if ((pPipe = OSS_CALLOC (sizeof (*pPipe))) == NULL ||
        usbHandleCreate (USBD_PIPE_SIG, pPipe, &pPipe->handle) != OK) {
        destroyPipe (pPipe);
        return S_usbdLib_OUT_OF_MEMORY;
    }

    pPipe->hcdHandle = hcdPipeHandle;
    pPipe->pClient = pClient;
    pPipe->pNode = pNode;
    pPipe->endpoint = pUrb->endpoint;
    pPipe->configuration = pUrb->configuration;
    pPipe->interface = pUrb->interface;
    pPipe->transferType = pUrb->transferType;
    pPipe->direction = pUrb->direction;
    pPipe->maxPacketSize = pUrb->maxPayload;
    pPipe->bandwidth = pUrb->bandwidth;
    pPipe->interval = pUrb->serviceInterval;
    pPipe->dataToggle = USB_DATA0;

    pPipe->nanoseconds = nanoseconds;
    pBus->nanoseconds += nanoseconds;


    /* Link pipe to list of pipes owned by this client */

    usbListLink (&pClient->pipes, pPipe, &pPipe->clientPipeLink, LINK_HEAD);


    /* Link pipe to list of pipes addressed to this node */

    usbListLink (&pNode->pipes, pPipe, &pPipe->nodePipeLink, LINK_HEAD);


    /* Return pipe handle */

    pUrb->pipeHandle = pPipe->handle;

    return OK;
}


/***************************************************************************
*
* fncPipeDestroy - Destroy a transfer pipe
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncPipeDestroy (pURB_PIPE_DESTROY pUrb)
{
    pUSBD_PIPE pPipe;


    /* validate the pipe handle */

    if (!validatePipe (pUrb->pipeHandle, &pPipe))
        return S_usbdLib_BAD_HANDLE;


    /* Destroy the pipe */

    destroyPipe (pPipe);

    return OK;
}


/***************************************************************************
*
* transferIrpCallback - invoked when a client's IRP completes
*
* Removes the IRP from the list of IRPs being tracked by the USBD and
* triggers the invocation of the client's IRP callback routine.
*
* NOTE: By convention, the USB_IRP.usbdPtr field points to the USBD_PIPE
* on which this transfer is being performed.
*
* RETURNS: N/A
*/

LOCAL VOID transferIrpCallback (pVOID p /* pointer to completed IRP */
    )
{
    pUSB_IRP pIrp = (pUSB_IRP) p;
    pUSBD_PIPE pPipe = (pUSBD_PIPE) pIrp->usbdPtr;
    UINT16 packets;
    UINT16 i;


    /* Unlink the IRP from the list of IRPs on this pipe. */

    usbListUnlink (&pIrp->usbdLink);


    /* Check if this IRP is being deleted.  If so, let the foreground
     * thread know the callback has been invoked.
     */

    if (pIrp == pPipe->irpBeingDeleted)
        pPipe->irpDeleted = TRUE;


    /* Update bus statistics */

    if (pIrp->result != OK) {
        if (pIrp->bfrList[0].pid == USB_PID_IN)
            pPipe->pNode->pBus->stats.totalReceiveErrors++;
        else
            pPipe->pNode->pBus->stats.totalTransmitErrors++;
    }


    /* Update data toggle for pipe.  Control and isochronous transfers
     * always begin with DATA0, which is set at pipe initialization -
     * so we don't change it here.  Bulk and interrupt pipes alternate
     * between DATA0 and DATA1, and we need to keep a running track of
     * the state across IRPs.
     */

    if (pPipe->transferType == USB_XFRTYPE_INTERRUPT || pPipe->transferType == USB_XFRTYPE_BULK) {
        /* Calculate the number of packets exchanged to determine the
         * next data toggle value.  If the count of packets is odd, then
         * the data toggle needs to switch.
         *
         * NOTE: If the IRP is successful, then at least one packet MUST
         * have been transferred.  However, it may have been a 0-length
         * packet.  This case is handled after the following "for" loop.
         */

        packets = 0;

        for (i = 0; i < pIrp->bfrCount; i++) {
            packets += (pIrp->bfrList[i].actLen + pPipe->maxPacketSize - 1) / pPipe->maxPacketSize;
        }

        if (pIrp->result == OK)
            packets = max (packets, 1);

        if ((packets & 1) != 0)
            pPipe->dataToggle = (pPipe->dataToggle == USB_DATA0) ? USB_DATA1 : USB_DATA0;
    }


    /* Invoke the user's callback routine */

    if (pIrp->userCallback != NULL) {
        /* If the userCallback routine is internal to the USBD, then
         * invoke it directly as its behavior is known.  This
         * streamlines processing of internally generated IRPs.
         * Moreover, this eliminates a deadlock condition which can
         * occur if a client's code invoked on the clientThread()
         * reenters the USBD.  That thread might block waiting for
         * the execution of a clientThread() callback - which would
         * never execute.
         */

        if (pIrp->userCallback == hubIrpCallback)
            hubIrpCallback (pIrp);

        else if (pIrp->userCallback == controlIrpCallback)
            controlIrpCallback (pIrp);

        else
            usbQueuePut (pPipe->pClient->callbackQueue,
                         CALLBACK_FNC_IRP_COMPLETE, 0, (UINT32) pIrp, OSS_BLOCK);
    }
}


/***************************************************************************
*
* fncTransfer - Initiates a transfer through a pipe
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncTransfer (pURB_TRANSFER pUrb)
{
    pUSBD_PIPE pPipe;
    pUSB_IRP pIrp;


    /* validate the pipe handle */

    if (!validatePipe (pUrb->pipeHandle, &pPipe))
        return S_usbdLib_BAD_HANDLE;


    /* validate the IRP */

    if ((pIrp = pUrb->pIrp) == NULL || pIrp->userCallback == NULL)
        return S_usbdLib_BAD_PARAM;


    /* Fill in IRP fields */

    pIrp->usbdPtr = pPipe;
    pIrp->usbdCallback = transferIrpCallback;
    pIrp->dataToggle = pPipe->dataToggle;


    /* Set an appropriate timeout value */

    if (pPipe->transferType == USB_XFRTYPE_CONTROL || pPipe->transferType == USB_XFRTYPE_BULK) {
        if (pIrp->timeout == 0)
            pIrp->timeout = USB_TIMEOUT_DEFAULT;
    }


    /* Update bus statistics */

    if (pIrp->bfrList[0].pid == USB_PID_IN)
        pPipe->pNode->pBus->stats.totalTransfersIn++;
    else
        pPipe->pNode->pBus->stats.totalTransfersOut++;


    /* Link the IRP to the list of IRPs we're tracking on this pipe. */

    usbListLink (&pPipe->irps, pIrp, &pIrp->usbdLink, LINK_TAIL);


    /* Deliver the IRP to the HCD */

    if (usbHcdIrpSubmit (&pPipe->pNode->pBus->pHcd->nexus, pPipe->hcdHandle, pIrp) != OK) {
        usbListUnlink (&pIrp->usbdLink);
        return S_usbdLib_HCD_FAULT;
    }

    return OK;
}


/***************************************************************************
*
* fncTransferAbort - Abort a transfer request
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncTransferAbort (pURB_TRANSFER pUrb)
{
    pUSBD_PIPE pPipe;
    pUSB_IRP pIrp;


    /* validate the pipe handle */

    if (!validatePipe (pUrb->pipeHandle, &pPipe))
        return S_usbdLib_BAD_HANDLE;


    /* validate the IRP */

    if ((pIrp = pUrb->pIrp) == NULL)
        return S_usbdLib_BAD_PARAM;


    /* Cancel the IRP */

    return doTransferAbort (pPipe, pIrp);
}


/***************************************************************************
*
* fncSynchFrameGet - Returns isochronous synch frame for an endpoint
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncSynchFrameGet (pURB_SYNCH_FRAME_GET pUrb)
{
    return controlRequest (&pUrb->header, pUrb->nodeId,
                           USB_RT_DEV_TO_HOST | USB_RT_STANDARD | USB_RT_ENDPOINT,
                           USB_REQ_GET_SYNCH_FRAME, 0, pUrb->endpoint, 2, &pUrb->frameNo, NULL);
}


/***************************************************************************
*
* fncCurrentFrameGet - Returns current frame number on a USB
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncCurrentFrameGet (pURB_CURRENT_FRAME_GET pUrb)
{
    pUSBD_NODE pNode;

    /* validate node handle */

    if (!validateNode (pUrb->nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;

    /* Retrieve the current frame number and frame window size from the
     * HCD for this root.
     */

    if (usbHcdCurrentFrameGet (&pNode->pBus->pHcd->nexus, pNode->pBus->busNo,
                               &pUrb->frameNo, &pUrb->frameWindow) != OK)
        return S_usbdLib_HCD_FAULT;

    return OK;
}


/***************************************************************************
*
* fncSofMasterTake - take SOF master ownership for a bus
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncSofMasterTake (pURB_SOF_MASTER pUrb)
{
    pUSBD_CLIENT pClient;
    pUSBD_NODE pNode;
    int s;

    /* validate parameters */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), &pClient)) != OK)
        return s;

    if (!validateNode (pUrb->nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;


    /* Check if another client already is SOF master for the indicated bus */

    if (pNode->pBus->pSofMasterClient != NULL)
        return S_usbdLib_SOF_MASTER_FAULT;


    /* Make this client the SOF master. */

    pNode->pBus->pSofMasterClient = pClient;

    return OK;
}


/***************************************************************************
*
* fncSofMasterRelease - release SOF master ownership for a bus
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncSofMasterRelease (pURB_SOF_MASTER pUrb)
{
    pUSBD_CLIENT pClient;
    pUSBD_NODE pNode;
    int s;

    /* validate parameters */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), &pClient)) != OK)
        return s;

    if (!validateNode (pUrb->nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;


    /* Check if this client is the SOF master. */

    if (pNode->pBus->pSofMasterClient != pClient)
        return S_usbdLib_SOF_MASTER_FAULT;


    /* Clear the SOF master */

    pNode->pBus->pSofMasterClient = NULL;

    return OK;
}


/***************************************************************************
*
* fncSofIntervalGet - retrieve SOF interval for a bus
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncSofIntervalGet (pURB_SOF_INTERVAL_GET_SET pUrb)
{
    pUSBD_CLIENT pClient;
    pUSBD_NODE pNode;
    int s;

    /* validate parameters */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), &pClient)) != OK)
        return s;

    if (!validateNode (pUrb->nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;


    /* Retrieve the current SOF interval for this bus. */

    if (usbHcdSofIntervalGet (&pNode->pBus->pHcd->nexus, pNode->pBus->busNo,
                              &pUrb->sofInterval) != OK)
        return S_usbdLib_HCD_FAULT;

    return OK;
}


/***************************************************************************
*
* fncSofIntervalSet - set SOF interval for a bus
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncSofIntervalSet (pURB_SOF_INTERVAL_GET_SET pUrb)
{
    pUSBD_CLIENT pClient;
    pUSBD_NODE pNode;
    int s;

    /* validate parameters */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), &pClient)) != OK)
        return s;

    if (!validateNode (pUrb->nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;


    /* Check if this client is the SOF master. */

    if (pNode->pBus->pSofMasterClient != pClient)
        return S_usbdLib_SOF_MASTER_FAULT;


    /* Set new SOF interval */

    if (usbHcdSofIntervalSet (&pNode->pBus->pHcd->nexus, pNode->pBus->busNo,
                              pUrb->sofInterval) != OK)
        return S_usbdLib_HCD_FAULT;

    return OK;
}


/***************************************************************************
*
* fncBusStateSet - sets bus state (e.g., suspend/resume)
*
* RETURNS: S_usbdLib_xxxx
*/

LOCAL int fncBusStateSet (pURB_BUS_STATE_SET pUrb)
{
    pUSBD_CLIENT pClient;
    pUSBD_NODE pNode;
    pUSBD_BUS pBus;
    int s;

    /* validate parameters */

    if ((s = validateUrb (pUrb, sizeof (*pUrb), &pClient)) != OK)
        return s;

    if (!validateNode (pUrb->nodeId, &pNode))
        return S_usbdLib_BAD_HANDLE;

    /* Set the indicated bus state */

    pBus = pNode->pBus;

    if ((pUrb->busState & USBD_BUS_SUSPEND) != 0) {
        /* SUSPEND bus */

        if (!pBus->suspended) {
            if (usbHcdSetBusState (&pBus->pHcd->nexus, pBus->busNo, HCD_BUS_SUSPEND) != OK)
                return S_usbdLib_HCD_FAULT;

            pBus->suspended = TRUE;
        }
    }

    if ((pUrb->busState & USBD_BUS_RESUME) != 0) {
        /* RESUME bus */

        if (pBus->suspended) {
            if (usbHcdSetBusState (&pBus->pHcd->nexus, pBus->busNo, HCD_BUS_RESUME) != OK)
                return S_usbdLib_HCD_FAULT;

            pBus->suspended = FALSE;
        }
    }

    return OK;
}


/***************************************************************************
*
* usbdCoreEntry - Primary entry point
*
* usbdCoreEntry is the primary entry point to the USBD through which callers
* invoke individual URBs (USB Request Blocks).
*
* NOTE: This function is intended exclusively for the use of functions in
* usbdLib.  Clients should not construct URBs manually and invoke this 
* function directly.
*
* RETURNS: OK or ERROR
*
* ERRNO:
*  S_usbdLib_BAD_PARAM
*/

STATUS usbdCoreEntry (pURB_HEADER pUrb  /* URB to be executed */
    )
{
    int s;


    /* Validate parameters */

    if (pUrb == NULL || pUrb->urbLength < sizeof (URB_HEADER))
        return ossStatus (S_usbdLib_BAD_PARAM);


    /* Unless this call is for initialization or shutdown, take the
     * structMutex to prevent other threads from tampering with USBD
     * data structures.
     */

    if (pUrb->function != USBD_FNC_INITIALIZE && pUrb->function != USBD_FNC_SHUTDOWN) {
        OSS_MUTEX_TAKE (structMutex, OSS_BLOCK);
    }


    /* Fan-out to appropriate function processor */

    switch (pUrb->function) {
    case USBD_FNC_INITIALIZE:
        s = fncInitialize (pUrb);
        break;

    case USBD_FNC_SHUTDOWN:
        s = fncShutdown (pUrb);
        break;

    case USBD_FNC_CLIENT_REG:
        s = fncClientReg ((pURB_CLIENT_REG) pUrb);
        break;

    case USBD_FNC_CLIENT_UNREG:
        s = fncClientUnreg ((pURB_CLIENT_UNREG) pUrb);
        break;

    case USBD_FNC_MNGMT_CALLBACK_SET:
        s = fncMngmtCallbackSet ((pURB_MNGMT_CALLBACK_SET) pUrb);
        break;

    case USBD_FNC_VERSION_GET:
        s = fncVersionGet ((pURB_VERSION_GET) pUrb);
        break;

    case USBD_FNC_HCD_ATTACH:
        s = fncHcdAttach ((pURB_HCD_ATTACH) pUrb);
        break;

    case USBD_FNC_HCD_DETACH:
        s = fncHcdDetach ((pURB_HCD_DETACH) pUrb);
        break;

    case USBD_FNC_STATISTICS_GET:
        s = fncStatisticsGet ((pURB_STATISTICS_GET) pUrb);
        break;

    case USBD_FNC_BUS_COUNT_GET:
        s = fncBusCountGet ((pURB_BUS_COUNT_GET) pUrb);
        break;

    case USBD_FNC_ROOT_ID_GET:
        s = fncRootIdGet ((pURB_ROOT_ID_GET) pUrb);
        break;

    case USBD_FNC_HUB_PORT_COUNT_GET:
        s = fncHubPortCountGet ((pURB_HUB_PORT_COUNT_GET) pUrb);
        break;

    case USBD_FNC_NODE_ID_GET:
        s = fncNodeIdGet ((pURB_NODE_ID_GET) pUrb);
        break;

    case USBD_FNC_DYNA_ATTACH_REG:
        s = fncDynaAttachReg ((pURB_DYNA_ATTACH_REG_UNREG) pUrb);
        break;

    case USBD_FNC_DYNA_ATTACH_UNREG:
        s = fncDynaAttachUnreg ((pURB_DYNA_ATTACH_REG_UNREG) pUrb);
        break;

    case USBD_FNC_FEATURE_CLEAR:
        s = fncFeatureClear ((pURB_FEATURE_CLEAR_SET) pUrb);
        break;

    case USBD_FNC_FEATURE_SET:
        s = fncFeatureSet ((pURB_FEATURE_CLEAR_SET) pUrb);
        break;

    case USBD_FNC_CONFIG_GET:
        s = fncConfigGet ((pURB_CONFIG_GET_SET) pUrb);
        break;

    case USBD_FNC_CONFIG_SET:
        s = fncConfigSet ((pURB_CONFIG_GET_SET) pUrb);
        break;

    case USBD_FNC_DESCRIPTOR_GET:
        s = fncDescriptorGet ((pURB_DESCRIPTOR_GET_SET) pUrb);
        break;

    case USBD_FNC_DESCRIPTOR_SET:
        s = fncDescriptorSet ((pURB_DESCRIPTOR_GET_SET) pUrb);
        break;

    case USBD_FNC_INTERFACE_GET:
        s = fncInterfaceGet ((pURB_INTERFACE_GET_SET) pUrb);
        break;

    case USBD_FNC_INTERFACE_SET:
        s = fncInterfaceSet ((pURB_INTERFACE_GET_SET) pUrb);
        break;

    case USBD_FNC_STATUS_GET:
        s = fncStatusGet ((pURB_STATUS_GET) pUrb);
        break;

    case USBD_FNC_ADDRESS_GET:
        s = fncAddressGet ((pURB_ADDRESS_GET_SET) pUrb);
        break;

    case USBD_FNC_ADDRESS_SET:
        s = fncAddressSet ((pURB_ADDRESS_GET_SET) pUrb);
        break;

    case USBD_FNC_NODE_INFO_GET:
        s = fncNodeInfoGet ((pURB_NODE_INFO_GET) pUrb);
        break;

    case USBD_FNC_VENDOR_SPECIFIC:
        s = fncVendorSpecific ((pURB_VENDOR_SPECIFIC) pUrb);
        break;

    case USBD_FNC_PIPE_CREATE:
        s = fncPipeCreate ((pURB_PIPE_CREATE) pUrb);
        break;

    case USBD_FNC_PIPE_DESTROY:
        s = fncPipeDestroy ((pURB_PIPE_DESTROY) pUrb);
        break;

    case USBD_FNC_TRANSFER:
        s = fncTransfer ((pURB_TRANSFER) pUrb);
        break;

    case USBD_FNC_TRANSFER_ABORT:
        s = fncTransferAbort ((pURB_TRANSFER) pUrb);
        break;

    case USBD_FNC_SYNCH_FRAME_GET:
        s = fncSynchFrameGet ((pURB_SYNCH_FRAME_GET) pUrb);
        break;

    case USBD_FNC_CURRENT_FRAME_GET:
        s = fncCurrentFrameGet ((pURB_CURRENT_FRAME_GET) pUrb);
        break;

    case USBD_FNC_SOF_MASTER_TAKE:
        s = fncSofMasterTake ((pURB_SOF_MASTER) pUrb);
        break;

    case USBD_FNC_SOF_MASTER_RELEASE:
        s = fncSofMasterRelease ((pURB_SOF_MASTER) pUrb);
        break;

    case USBD_FNC_SOF_INTERVAL_GET:
        s = fncSofIntervalGet ((pURB_SOF_INTERVAL_GET_SET) pUrb);
        break;

    case USBD_FNC_SOF_INTERVAL_SET:
        s = fncSofIntervalSet ((pURB_SOF_INTERVAL_GET_SET) pUrb);
        break;

    case USBD_FNC_BUS_STATE_SET:
        s = fncBusStateSet ((pURB_BUS_STATE_SET) pUrb);
        break;

    default:                   /* Bad function code */

        s = S_usbdLib_BAD_PARAM;
        break;
    }


    /* Store result. */

    setUrbResult (pUrb, s);


    /* release structMutex */

    if (pUrb->function != USBD_FNC_INITIALIZE && pUrb->function != USBD_FNC_SHUTDOWN) {
        OSS_MUTEX_RELEASE (structMutex);
    }


    /* return status to caller */

    if (s == OK || s == PENDING)
        return OK;
    else
        return ossStatus (s);
}


/* End of file. */