SAMA5 EHCI: Add data transfer logic for asynchronous endpoints
This commit is contained in:
parent
c1c5e195ce
commit
577b19920e
@ -295,6 +295,13 @@ static int sam_qh_flush(struct sam_qh_s *qh);
|
||||
static int sam_ioc_setup(struct sam_rhport_s *rhport, struct sam_epinfo_s *epinfo);
|
||||
static int sam_ioc_wait(struct sam_epinfo_s *epinfo);
|
||||
static void sam_qh_enqueue(struct sam_qh_s *qh);
|
||||
static struct sam_qh_s *sam_qh_create(struct sam_rhport_s *rhport,
|
||||
struct sam_epinfo_s *epinfo);
|
||||
static int sam_qtd_addbpl(struct sam_qtd_s *qtd, const void *buffer, size_t buflen);
|
||||
static struct sam_qtd_s *sam_qtd_setupphase(const struct usb_ctrlreq_s *req);
|
||||
static struct sam_qtd_s *sam_qtd_dataphase(void *buffer, int buflen,
|
||||
uint32_t tokenbits);
|
||||
static struct sam_qtd_s *sam_qtd_statusphase(uint32_t tokenbits);
|
||||
static int sam_async_transfer(struct sam_rhport_s *rhport,
|
||||
struct sam_epinfo_s *epinfo, const struct usb_ctrlreq_s *req,
|
||||
uint8_t *buffer, size_t buflen);
|
||||
@ -874,26 +881,25 @@ static int sam_qh_foreach(struct sam_qh_s *qh, uint32_t **bp, foreach_qh_t handl
|
||||
* Setup and call sam_qh_foreach to that every element of the asynchronous
|
||||
* queue is examined.
|
||||
*
|
||||
* Assumption: The caller holds the EHCI exclsem
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
static int sam_qh_forall(foreach_qh_t handler, void *arg)
|
||||
{
|
||||
struct sam_qh_s *qh;
|
||||
uint32_t *bp;
|
||||
int ret;
|
||||
|
||||
/* Preemption is disabled to prevent concurrent modification of the queue
|
||||
* head by the other threads.
|
||||
/* Set the back pointer to the forward qTD pointer of the asynchronous
|
||||
* queue head.
|
||||
*/
|
||||
|
||||
bp = (uint32_t *)&qh->hw.hlp;
|
||||
|
||||
sched_lock();
|
||||
bp = (uint32_t *)&g_asynchead.hw.hlp;
|
||||
qh = (struct sam_qh_s *)sam_virtramaddr(sam_swap32(*bp) & QH_HLP_MASK);
|
||||
sam_qh_foreach(qh, &bp, handler, arg);
|
||||
sched_unlock();
|
||||
|
||||
return ret;
|
||||
/* Then traverse and operate on every QH and qTD in the list */
|
||||
|
||||
return sam_qh_foreach(qh, &bp, handler, arg);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@ -1186,20 +1192,15 @@ static int sam_ioc_wait(struct sam_epinfo_s *epinfo)
|
||||
* Description:
|
||||
* Add a new, ready-to-go QH w/attached qTDs to the asynchonous queue.
|
||||
*
|
||||
* Assumptions: The caller holds the EHCI exclsem
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
static void sam_qh_enqueue(struct sam_qh_s *qh)
|
||||
{
|
||||
uintptr_t physaddr;
|
||||
|
||||
/* Add the new QH to the head of the asynchronous queue list. Preemption
|
||||
* is disabled momentarily to prevent concurrent modification of the queue
|
||||
* head by the worker thread.
|
||||
*/
|
||||
|
||||
physaddr = (uintptr_t)sam_physramaddr((uintptr_t)qh);
|
||||
sched_lock();
|
||||
|
||||
/* Add the new QH to the head of the asynchronous queue list. */
|
||||
/* Attach the old head as the new QH HLP and flush the new QH and its attached
|
||||
* qTDs to RAM.
|
||||
*/
|
||||
@ -1211,10 +1212,330 @@ static void sam_qh_enqueue(struct sam_qh_s *qh)
|
||||
* modified head to RAM.
|
||||
*/
|
||||
|
||||
physaddr = (uintptr_t)sam_physramaddr((uintptr_t)qh);
|
||||
g_asynchead.hw.hlp = sam_swap32(physaddr | QH_HLP_TYP_QH);
|
||||
cp15_coherent_dcache((uintptr_t)&g_asynchead,
|
||||
(uintptr_t)&g_asynchead + sizeof(struct ehci_qh_s));
|
||||
sched_unlock();
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Name: sam_qh_create
|
||||
*
|
||||
* Description:
|
||||
* Create a new Queue Head (QH)
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
static struct sam_qh_s *sam_qh_create(struct sam_rhport_s *rhport,
|
||||
struct sam_epinfo_s *epinfo)
|
||||
{
|
||||
struct sam_qh_s *qh;
|
||||
uint32_t regval;
|
||||
|
||||
/* Allocate a new queue head structure */
|
||||
|
||||
qh = sam_qh_alloc();
|
||||
if (qh == NULL)
|
||||
{
|
||||
udbg("ERROR: Failed to allocate a QH\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Save the endpoint information with the QH itself */
|
||||
|
||||
qh->epinfo = epinfo;
|
||||
|
||||
/* Write QH endpoint characteristics:
|
||||
*
|
||||
* FIELD DESCRIPTION VALUE/SOURCE
|
||||
* -------- ------------------------------- --------------------
|
||||
* DEVADDR Device address Endpoint structure
|
||||
* I Inactivate on Next Transaction 0
|
||||
* ENDPT Endpoint number Endpoint structure
|
||||
* EPS Endpoint speed Endpoint structure
|
||||
* DTC Data toggle control 1
|
||||
* MAXPKT Max packet size Endpoint structure
|
||||
* C Control endpoint Calculated
|
||||
* RL NAK count reloaded 8
|
||||
*/
|
||||
|
||||
regval = ((uint32_t)epinfo->devaddr << QH_EPCHAR_DEVADDR_SHIFT) |
|
||||
((uint32_t)epinfo->epno << QH_EPCHAR_ENDPT_SHIFT) |
|
||||
((uint32_t)epinfo->speed << QH_EPCHAR_EPS_SHIFT) |
|
||||
QH_EPCHAR_DTC |
|
||||
((uint32_t)epinfo->maxpacket << QH_EPCHAR_MAXPKT_SHIFT) |
|
||||
((uint32_t)8 << QH_EPCHAR_RL_SHIFT);
|
||||
|
||||
if (epinfo->speed != EHCI_FULL_SPEED && epinfo->epno == 0)
|
||||
{
|
||||
regval |= QH_EPCHAR_C;
|
||||
}
|
||||
|
||||
qh->hw.epchar = sam_swap32(regval);
|
||||
|
||||
/* Write QH endpoint capabilities
|
||||
*
|
||||
* FIELD DESCRIPTION VALUE/SOURCE
|
||||
* -------- ------------------------------- --------------------
|
||||
* SSMASK Interrupt Schedule Mask 0
|
||||
* SCMASK Split Completion Mask 0
|
||||
* HUBADDR Hub Address Always 0 for now
|
||||
* PORT Port number RH port index + 1
|
||||
* MULT High band width multiplier 1
|
||||
*
|
||||
* REVISIT: Future HUB support will require the HUB port number
|
||||
* and HUB device address to be included here.
|
||||
*/
|
||||
|
||||
regval = ((uint32_t)0 << QH_EPCAPS_HUBADDR_SHIFT) |
|
||||
((uint32_t)(rhport->rhpndx + 1) << QH_EPCAPS_PORT_SHIFT) |
|
||||
((uint32_t)1 << QH_EPCAPS_MULT_SHIFT);
|
||||
|
||||
qh->hw.epcaps = sam_swap32(regval);
|
||||
|
||||
/* Mark this as the end of this list. This will be overwritten if/when the
|
||||
* next qTD is added to the queue.
|
||||
*/
|
||||
|
||||
qh->hw.overlay.nqp = sam_swap32(QH_NQP_T);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Name: sam_qtd_addbpl
|
||||
*
|
||||
* Description:
|
||||
* Add a buffer pointer list to a qTD.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
static int sam_qtd_addbpl(struct sam_qtd_s *qtd, const void *buffer, size_t buflen)
|
||||
{
|
||||
uint32_t physaddr;
|
||||
uint32_t nbytes;
|
||||
uint32_t next;
|
||||
int ndx;
|
||||
|
||||
physaddr = (uint32_t)sam_physramaddr((uintptr_t)buffer);
|
||||
|
||||
for (ndx = 0; ndx < 5; ndx++)
|
||||
{
|
||||
/* Write the physical address of the buffer into the qTD buffer pointer
|
||||
* list.
|
||||
*/
|
||||
|
||||
qtd->hw.bpl[ndx] = sam_swap32(physaddr);
|
||||
|
||||
/* Get the next buffer pointer (in the case where we will have to transfer
|
||||
* more then on 4KB chunks.
|
||||
*/
|
||||
|
||||
next = (physaddr + 4096) & ~4095;
|
||||
|
||||
/* How many bytes were included in the last buffer? Was the the whole
|
||||
* thing?
|
||||
*/
|
||||
|
||||
nbytes = next - physaddr;
|
||||
if (nbytes >= buflen)
|
||||
{
|
||||
/* Yes... it was the whole thing. Break out of the loop early. */
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* Adjust the buffer length and physical address for the next time
|
||||
* through the loop.
|
||||
*/
|
||||
|
||||
buflen -= nbytes;
|
||||
physaddr = next;
|
||||
}
|
||||
|
||||
/* Handle the case of a huge buffer > 4*4KB = 16KB */
|
||||
|
||||
if (ndx >= 5)
|
||||
{
|
||||
uvdbg("ERROR: Buffer too big. Remaining %d\n", buflen);
|
||||
return -EFBIG;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Name: sam_qtd_setupphase
|
||||
*
|
||||
* Description:
|
||||
* Create a SETUP phase request qTD.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
static struct sam_qtd_s *sam_qtd_setupphase(const struct usb_ctrlreq_s *req)
|
||||
{
|
||||
struct sam_qtd_s *qtd;
|
||||
uint32_t regval;
|
||||
int ret;
|
||||
|
||||
/* Allocate a new Queue Element Transfer Descriptor (qTD) */
|
||||
|
||||
qtd = sam_qtd_alloc();
|
||||
if (qtd == NULL)
|
||||
{
|
||||
udbg("ERROR: Failed to allocate request qTD");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Mark this as the end of the list (this will be overwritten if another
|
||||
* qTD is added after this one.
|
||||
*/
|
||||
|
||||
qtd->hw.nqp = sam_swap32(QTD_NQP_T);
|
||||
qtd->hw.alt = sam_swap32(QTD_AQP_T);
|
||||
|
||||
/* Write qTD token:
|
||||
*
|
||||
* FIELD DESCRIPTION VALUE/SOURCE
|
||||
* -------- ------------------------------- --------------------
|
||||
* STATUS Status QTD_TOKEN_ACTIVE
|
||||
* PID PID Code QTD_TOKEN_PID_SETUP
|
||||
* CERR Error Counter 3
|
||||
* CPAGE Current Page 0
|
||||
* IOC Interrupt on complete 0
|
||||
* NBYTES Total Bytes to Transfer USB_SIZEOF_CTRLREQ
|
||||
* TOGGLE Data Toggle 0
|
||||
*/
|
||||
|
||||
regval = QTD_TOKEN_ACTIVE | QTD_TOKEN_PID_SETUP |
|
||||
((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) |
|
||||
((uint32_t)USB_SIZEOF_CTRLREQ << QTD_TOKEN_NBYTES_SHIFT);
|
||||
|
||||
qtd->hw.token = sam_swap32(regval);
|
||||
|
||||
/* Add the buffer data */
|
||||
|
||||
ret = sam_qtd_addbpl(qtd, req, USB_SIZEOF_CTRLREQ);
|
||||
if (ret < 0)
|
||||
{
|
||||
uvdbg("ERROR: sam_qtd_addbpl failed: %d\n", ret);
|
||||
sam_qtd_free(qtd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return qtd;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Name: sam_qtd_dataphase
|
||||
*
|
||||
* Description:
|
||||
* Create a data transfer or SET data phase qTD.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
static struct sam_qtd_s *sam_qtd_dataphase(void *buffer, int buflen,
|
||||
uint32_t tokenbits)
|
||||
{
|
||||
struct sam_qtd_s *qtd;
|
||||
uint32_t regval;
|
||||
int ret;
|
||||
|
||||
/* Allocate a new Queue Element Transfer Descriptor (qTD) */
|
||||
|
||||
qtd = sam_qtd_alloc();
|
||||
if (qtd == NULL)
|
||||
{
|
||||
udbg("ERROR: Failed to allocate data buffer qTD");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Mark this as the end of the list (this will be overwritten if another
|
||||
* qTD is added after this one.
|
||||
*/
|
||||
|
||||
qtd->hw.nqp = sam_swap32(QTD_NQP_T);
|
||||
qtd->hw.alt = sam_swap32(QTD_AQP_T);
|
||||
|
||||
/* Write qTD token:
|
||||
*
|
||||
* FIELD DESCRIPTION VALUE/SOURCE
|
||||
* -------- ------------------------------- --------------------
|
||||
* STATUS Status QTD_TOKEN_ACTIVE
|
||||
* PID PID Code Contained in tokenbits
|
||||
* CERR Error Counter 3
|
||||
* CPAGE Current Page 0
|
||||
* IOC Interrupt on complete Contained in tokenbits
|
||||
* NBYTES Total Bytes to Transfer buflen
|
||||
* TOGGLE Data Toggle Contained in tokenbits
|
||||
*/
|
||||
|
||||
regval = tokenbits | QTD_TOKEN_ACTIVE |
|
||||
((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) |
|
||||
((uint32_t)buflen << QTD_TOKEN_NBYTES_SHIFT);
|
||||
|
||||
qtd->hw.token = sam_swap32(regval);
|
||||
|
||||
/* Add the buffer information to the bufffer pointer list */
|
||||
|
||||
ret = sam_qtd_addbpl(qtd, buffer, buflen);
|
||||
if (ret < 0)
|
||||
{
|
||||
udbg("ERROR: sam_qtd_addbpl failed: %d\n", ret);
|
||||
sam_qtd_free(qtd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return qtd;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Name: sam_qtd_statusphase
|
||||
*
|
||||
* Description:
|
||||
* Create a STATUS phase request qTD.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
static struct sam_qtd_s *sam_qtd_statusphase(uint32_t tokenbits)
|
||||
{
|
||||
struct sam_qtd_s *qtd;
|
||||
uint32_t regval;
|
||||
|
||||
/* Allocate a new Queue Element Transfer Descriptor (qTD) */
|
||||
|
||||
qtd = sam_qtd_alloc();
|
||||
if (qtd == NULL)
|
||||
{
|
||||
udbg("ERROR: Failed to allocate request qTD");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Mark this as the end of the list (this will be overwritten if another
|
||||
* qTD is added after this one.
|
||||
*/
|
||||
|
||||
qtd->hw.nqp = sam_swap32(QTD_NQP_T);
|
||||
qtd->hw.alt = sam_swap32(QTD_AQP_T);
|
||||
|
||||
/* Write qTD token:
|
||||
*
|
||||
* FIELD DESCRIPTION VALUE/SOURCE
|
||||
* -------- ------------------------------- --------------------
|
||||
* STATUS Status QTD_TOKEN_ACTIVE
|
||||
* PID PID Code Contained in tokenbits
|
||||
* CERR Error Counter 3
|
||||
* CPAGE Current Page 0
|
||||
* IOC Interrupt on complete QH_TOKEN_IOC
|
||||
* NBYTES Total Bytes to Transfer 0
|
||||
* TOGGLE Data Toggle Contained in tokenbits
|
||||
*/
|
||||
|
||||
regval = tokenbits | QTD_TOKEN_ACTIVE |
|
||||
((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) |
|
||||
QH_TOKEN_IOC |
|
||||
((uint32_t)USB_SIZEOF_CTRLREQ << QTD_TOKEN_NBYTES_SHIFT);
|
||||
|
||||
qtd->hw.token = sam_swap32(regval);
|
||||
return qtd;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@ -1222,11 +1543,15 @@ static void sam_qh_enqueue(struct sam_qh_s *qh)
|
||||
*
|
||||
* Description:
|
||||
* Process a IN or OUT request on any asynchronous endpoint (bulk or control).
|
||||
* This function will enqueue the request and wait for it to complete.
|
||||
* This function will enqueue the request and wait for it to complete. Bulk
|
||||
* data transfers differ in that req == NULL and there are not SETUP or STATUS
|
||||
* phases.
|
||||
*
|
||||
* This is a blocking function; it will not return until the control transfer
|
||||
* has completed.
|
||||
*
|
||||
* Assumption: The caller holds the EHCI exclsem
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
static int sam_async_transfer(struct sam_rhport_s *rhport,
|
||||
@ -1234,11 +1559,19 @@ static int sam_async_transfer(struct sam_rhport_s *rhport,
|
||||
const struct usb_ctrlreq_s *req,
|
||||
uint8_t *buffer, size_t buflen)
|
||||
{
|
||||
struct sam_qh_s *qh;
|
||||
struct sam_qtd_s *qtd;
|
||||
uintptr_t physaddr;
|
||||
uint32_t *flink;
|
||||
uint32_t toggle;
|
||||
uint32_t datapid;
|
||||
int ret;
|
||||
|
||||
uvdbg("RHport%d EP%d: buffer=%p, buflen=%d, req=%p\n",
|
||||
rhport->rhpndx+1, epinfo->epno, buffer, buflen, req);
|
||||
|
||||
DEBUGASSERT(rhport && epinfo);
|
||||
|
||||
if (req != NULL)
|
||||
{
|
||||
uvdbg("req=%02x type=%02x value=%04x index=%04x\n",
|
||||
@ -1246,6 +1579,12 @@ static int sam_async_transfer(struct sam_rhport_s *rhport,
|
||||
sam_read16(req->index));
|
||||
}
|
||||
|
||||
/* A buffer may or may be supplied with an EP0 SETUP transfer. A buffer will
|
||||
* always be present for normal endpoint data transfers.
|
||||
*/
|
||||
|
||||
DEBUGASSERT(req || (buffer && buflen > 0));
|
||||
|
||||
/* Set the request for the IOC event well BEFORE enabling the transfer. */
|
||||
|
||||
ret = sam_ioc_setup(rhport, epinfo);
|
||||
@ -1255,7 +1594,132 @@ static int sam_async_transfer(struct sam_rhport_s *rhport,
|
||||
return ret;
|
||||
}
|
||||
|
||||
#warning "Missing logic"
|
||||
/* Get the data token direction */
|
||||
|
||||
datapid = QTD_TOKEN_PID_OUT;
|
||||
if (req)
|
||||
{
|
||||
if ((req->req & USB_REQ_DIR_MASK) == USB_REQ_DIR_IN)
|
||||
{
|
||||
datapid = QTD_TOKEN_PID_IN;
|
||||
}
|
||||
}
|
||||
else if (epinfo->dirin)
|
||||
{
|
||||
datapid = QTD_TOKEN_PID_IN;
|
||||
}
|
||||
|
||||
/* Create and initialize a Queue Head (QH) structure for this transfer */
|
||||
|
||||
qh = sam_qh_create(rhport, epinfo);
|
||||
if (qh == NULL)
|
||||
{
|
||||
udbg("ERROR: sam_qh_create failed\n");
|
||||
ret = -ENOMEM;
|
||||
goto errout_with_iocwait;
|
||||
}
|
||||
|
||||
/* Initialize the QH link and get the next data toggle (not used for SETUP
|
||||
* transfers)
|
||||
*/
|
||||
|
||||
flink = &qh->hw.overlay.nqp;
|
||||
toggle = epinfo->toggle ? 0 : QTD_TOKEN_TOGGLE;
|
||||
ret = -EIO;
|
||||
|
||||
/* Is the an EP0 SETUP request? If req will be non-NULL */
|
||||
|
||||
if (req != NULL)
|
||||
{
|
||||
/* Allocate a new Queue Element Transfer Descriptor (qTD) for the SETUP
|
||||
* phase of the request sequence.
|
||||
*/
|
||||
|
||||
qtd = sam_qtd_setupphase(req);
|
||||
if (qtd == NULL)
|
||||
{
|
||||
udbg("ERROR: sam_qtd_setupphase failed\n");
|
||||
goto errout_with_qh;
|
||||
}
|
||||
|
||||
/* Link the new qTD to the QH head. */
|
||||
|
||||
physaddr = sam_physramaddr((uintptr_t)qtd);
|
||||
*flink = sam_swap32(physaddr);
|
||||
|
||||
/* Get the new forward link pointer and data toggle */
|
||||
|
||||
flink = &qtd->hw.nqp;
|
||||
toggle = QTD_TOKEN_TOGGLE;
|
||||
}
|
||||
|
||||
/* A buffer may or may be supplied with an EP0 SETUP transfer. A buffer will
|
||||
* always be present for normal endpoint data transfers.
|
||||
*/
|
||||
|
||||
if (buffer && buflen > 0)
|
||||
{
|
||||
/* Extra TOKEN bits include the data toggle, the data PID, and if there
|
||||
* is no request, and indication to interrupt at the end of this
|
||||
* transfer.
|
||||
*/
|
||||
|
||||
uint32_t tokenbits = toggle | datapid;
|
||||
|
||||
/* If this is not an EP0 SETUP request, then nothing follows the data and
|
||||
* we want the IOC interrupt when the data transfer completes.
|
||||
*/
|
||||
|
||||
if (!req)
|
||||
{
|
||||
tokenbits |= QTD_TOKEN_IOC;
|
||||
}
|
||||
|
||||
/* Allocate a new Queue Element Transfer Descriptor (qTD) for the data
|
||||
* buffer.
|
||||
*/
|
||||
|
||||
qtd = sam_qtd_dataphase(buffer, buflen, tokenbits);
|
||||
if (qtd == NULL)
|
||||
{
|
||||
udbg("ERROR: sam_qtd_dataphase failed\n");
|
||||
goto errout_with_qh;
|
||||
}
|
||||
|
||||
/* Link the new qTD to either QH head of the SETUP qTD. */
|
||||
|
||||
physaddr = sam_physramaddr((uintptr_t)qtd);
|
||||
*flink = sam_swap32(physaddr);
|
||||
|
||||
/* Set the forward link pointer to this new qTD */
|
||||
|
||||
flink = &qtd->hw.nqp;
|
||||
}
|
||||
|
||||
if (req != NULL)
|
||||
{
|
||||
/* Extra TOKEN bits include the data toggle and the data PID. */
|
||||
|
||||
uint32_t tokenbits = toggle | datapid ;
|
||||
|
||||
/* Allocate a new Queue Element Transfer Descriptor (qTD) for the status */
|
||||
|
||||
qtd = sam_qtd_statusphase(tokenbits);
|
||||
if (qtd == NULL)
|
||||
{
|
||||
udbg("ERROR: sam_qtd_statusphase failed\n");
|
||||
goto errout_with_qh;
|
||||
}
|
||||
|
||||
/* Link the new qTD to either the SETUP or data qTD. */
|
||||
|
||||
physaddr = sam_physramaddr((uintptr_t)qtd);
|
||||
*flink = sam_swap32(physaddr);
|
||||
}
|
||||
|
||||
/* Add the new QH to the head of the asynchronous queue list */
|
||||
|
||||
sam_qh_enqueue(qh);
|
||||
|
||||
/* Wait for the IOC completion event */
|
||||
|
||||
@ -1270,6 +1734,10 @@ static int sam_async_transfer(struct sam_rhport_s *rhport,
|
||||
|
||||
return OK;
|
||||
|
||||
/* Clean-up after an error */
|
||||
|
||||
errout_with_qh:
|
||||
sam_qh_discard(qh);
|
||||
errout_with_iocwait:
|
||||
epinfo->iocwait = false;
|
||||
return ret;
|
||||
|
Loading…
Reference in New Issue
Block a user