380 lines
11 KiB
C
380 lines
11 KiB
C
/*
|
||
* Copyright : (C) 2022 Phytium Information Technology, Inc.
|
||
* All Rights Reserved.
|
||
*
|
||
* This program is OPEN SOURCE software: you can redistribute it and/or modify it
|
||
* under the terms of the Phytium Public License as published by the Phytium Technology Co.,Ltd,
|
||
* either version 1.0 of the License, or (at your option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY;
|
||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||
* See the Phytium Public License for more details.
|
||
*
|
||
*
|
||
* FilePath: fxhci_evt.c
|
||
* Date: 2022-02-11 13:33:12
|
||
* LastEditTime: 2022-02-18 09:13:09
|
||
* Description: This files is for implementation of XHCI event
|
||
*
|
||
* Modify History:
|
||
* Ver Who Date Changes
|
||
* ----- ------ -------- --------------------------------------
|
||
* 1.0 zhugengyu 2022/2/7 init commit
|
||
*/
|
||
|
||
#include "fsleep.h"
|
||
#include "fdebug.h"
|
||
|
||
#include "fxhci_private.h"
|
||
|
||
#define FUSB_DEBUG_TAG "FXHCI-EVT"
|
||
#define FUSB_ERROR(format, ...) FT_DEBUG_PRINT_E(FUSB_DEBUG_TAG, format, ##__VA_ARGS__)
|
||
#define FUSB_WARN(format, ...) FT_DEBUG_PRINT_W(FUSB_DEBUG_TAG, format, ##__VA_ARGS__)
|
||
#define FUSB_INFO(format, ...) FT_DEBUG_PRINT_I(FUSB_DEBUG_TAG, format, ##__VA_ARGS__)
|
||
#define FUSB_DEBUG(format, ...) FT_DEBUG_PRINT_D(FUSB_DEBUG_TAG, format, ##__VA_ARGS__)
|
||
|
||
/**
|
||
* @name: FXhciResetEvtRing
|
||
* @msg: 重置Event TRB ring
|
||
* @return {*}
|
||
* @param {FXhciEvtRing} *er, Event TRB ring实例
|
||
*/
|
||
void FXhciResetEvtRing(FXhciEvtRing *const er)
|
||
{
|
||
int i;
|
||
for (i = 0; i < FXHCI_EVENT_RING_SIZE; ++i)
|
||
{
|
||
er->ring[i].control &= ~FXHCI_TRB_CYCLE;
|
||
}
|
||
er->cur = er->ring;
|
||
er->last = er->ring + FXHCI_EVENT_RING_SIZE;
|
||
er->ccs = 1;
|
||
er->adv = 1;
|
||
}
|
||
|
||
static inline int FXhciEvtReady(const FXhciEvtRing *const er)
|
||
{
|
||
return (er->cur->control & FXHCI_TRB_CYCLE) == er->ccs;
|
||
}
|
||
|
||
void FXhciUpdateEvtDQ(FXhci *const xhci)
|
||
{
|
||
if (xhci->er.adv)
|
||
{
|
||
FUSB_DEBUG("Updating dq ptr: @0x%lx -> %p.",
|
||
FXhciReadRt64(&xhci->mmio, 0, FXHCI_REG_RT_IR_ERDP),
|
||
xhci->er.cur);
|
||
FXhciWriteRt64(&xhci->mmio, 0, FXHCI_REG_RT_IR_ERDP, FXHCI_REG_RT_IR_ERDP_MASK & ((u64)(uintptr)xhci->er.cur));
|
||
xhci->er.adv = 0;
|
||
}
|
||
}
|
||
|
||
void FXhciAdvanceEvtRing(FXhci *const xhci)
|
||
{
|
||
xhci->er.cur++;
|
||
xhci->er.adv = 1;
|
||
if (xhci->er.cur == xhci->er.last)
|
||
{
|
||
FUSB_DEBUG("Roll over in event ring.");
|
||
xhci->er.cur = xhci->er.ring;
|
||
xhci->er.ccs ^= 1;
|
||
FXhciUpdateEvtDQ(xhci);
|
||
}
|
||
}
|
||
|
||
static void FXhciHandleTransferEvt(FXhci *const xhci)
|
||
{
|
||
const FXhciTrb *const ev = xhci->er.cur;
|
||
|
||
const FXhciTransCode cc = FXHCI_TRB_GET(CC, ev); /* Completion Code */
|
||
const int id = FXHCI_TRB_GET(ID, ev);
|
||
const int ep = FXHCI_TRB_GET(EP, ev);
|
||
|
||
FXhciIntrQ *intrq;
|
||
|
||
if (id && id <= xhci->max_slots_en &&
|
||
(intrq = xhci->dev[id].interrupt_queues[ep]))
|
||
{
|
||
/* It's a running interrupt endpoint */
|
||
intrq->ready = (void *)(uintptr)(ev->ptr_low);
|
||
if (cc == FXHCI_CC_SUCCESS || cc == FXHCI_CC_SHORT_PACKET)
|
||
{
|
||
FXHCI_TRB_SET(TL, intrq->ready,
|
||
intrq->size - FXHCI_TRB_GET(EVTL, ev)); /* Transfer Length */
|
||
}
|
||
else
|
||
{
|
||
FUSB_INFO("Interrupt transfer failed: %d.", cc);
|
||
FXHCI_TRB_SET(TL, intrq->ready, 0); /* Transfer Length */
|
||
}
|
||
}
|
||
else if (cc == FXHCI_CC_STOPPED || cc == FXHCI_CC_STOPPED_LENGTH_INVALID)
|
||
{
|
||
/* Ignore 'Forced Stop Events' */
|
||
}
|
||
else
|
||
{
|
||
FUSB_WARN("Warning: \r\n"
|
||
"Spurious transfer event for ID %d, EP %d: \r\n"
|
||
" Pointer: 0x%08x%08x \r\n"
|
||
" TL: 0x%06x \r\n"
|
||
" CC: %d ",
|
||
id, ep,
|
||
ev->ptr_high, ev->ptr_low,
|
||
FXHCI_TRB_GET(EVTL, ev), cc);
|
||
}
|
||
|
||
FXhciAdvanceEvtRing(xhci);
|
||
}
|
||
|
||
static void FXhciHandleCmdCompletionEvt(FXhci *const xhci)
|
||
{
|
||
const FXhciTrb *const ev = xhci->er.cur;
|
||
|
||
FUSB_INFO("Warning: Spurious command completion event: \r\n"
|
||
" Pointer: 0x%08x%08x \r\n"
|
||
" CC: %d \r\n"
|
||
" Slot ID: %d \r\n"
|
||
" Cycle: %d ",
|
||
ev->ptr_high, ev->ptr_low,
|
||
FXHCI_TRB_GET(CC, ev), FXHCI_TRB_GET(ID, ev), ev->control & FXHCI_TRB_CYCLE);
|
||
FXhciAdvanceEvtRing(xhci);
|
||
}
|
||
|
||
static void FXhciHandleHostCtrlEvt(FXhci *const xhci)
|
||
{
|
||
const FXhciTrb *const ev = xhci->er.cur;
|
||
|
||
const FXhciTransCode cc = FXHCI_TRB_GET(CC, ev);
|
||
switch (cc)
|
||
{
|
||
case FXHCI_CC_EVENT_RING_FULL_ERROR:
|
||
FUSB_INFO("Event ring full! (@%p).", xhci->er.cur);
|
||
/*
|
||
* If we get here, we have processed the whole queue:
|
||
* xHC pushes this event, when it sees the ring full,
|
||
* full of other events.
|
||
* IMO it's save and necessary to update the dequeue
|
||
* pointer here.
|
||
*/
|
||
FXhciAdvanceEvtRing(xhci);
|
||
FXhciUpdateEvtDQ(xhci);
|
||
break;
|
||
default:
|
||
FUSB_INFO("Warning: spurious host controller event: %d.", cc);
|
||
FXhciAdvanceEvtRing(xhci);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* handle standard types:
|
||
* - command completion event
|
||
* - port status change event
|
||
* - transfer event
|
||
* - host controller event
|
||
*/
|
||
static void FXhciHandleEvt(FXhci *const xhci)
|
||
{
|
||
const FXhciTrb *const ev = xhci->er.cur;
|
||
|
||
const int trb_type = FXHCI_TRB_GET(TT, ev);
|
||
switch (trb_type)
|
||
{
|
||
/* Either pass along the event or advance event ring */
|
||
case FXHCI_TRB_EV_TRANSFER:
|
||
FXhciHandleTransferEvt(xhci);
|
||
break;
|
||
case FXHCI_TRB_EV_CMD_CMPL:
|
||
FXhciHandleCmdCompletionEvt(xhci);
|
||
break;
|
||
case FXHCI_TRB_EV_PORTSC:
|
||
FUSB_INFO("Port status change event for %d: %d. ",
|
||
FXHCI_TRB_GET(PORT, ev), FXHCI_TRB_GET(CC, ev));
|
||
/* We ignore the event as we look for the PORTSC
|
||
registers instead, at a time when it suits _us_. */
|
||
FXhciAdvanceEvtRing(xhci);
|
||
break;
|
||
case FXHCI_TRB_EV_HOST:
|
||
FXhciHandleHostCtrlEvt(xhci);
|
||
break;
|
||
default:
|
||
FUSB_INFO("Warning: spurious event: %d, completion code: %d.",
|
||
trb_type, FXHCI_TRB_GET(CC, ev));
|
||
FXhciAdvanceEvtRing(xhci);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void FXhciHandleEvts(FXhci *const xhci)
|
||
{
|
||
while (FXhciEvtReady(&xhci->er))
|
||
{
|
||
FXhciHandleEvt(xhci);
|
||
}
|
||
|
||
FXhciUpdateEvtDQ(xhci);
|
||
return;
|
||
}
|
||
|
||
static unsigned long FXhciWaitForEvt(const FXhciEvtRing *const er,
|
||
unsigned long *const timeout_us)
|
||
{
|
||
while (!FXhciEvtReady(er) && *timeout_us)
|
||
{
|
||
--*timeout_us;
|
||
fsleep_microsec(1);
|
||
}
|
||
return *timeout_us;
|
||
}
|
||
|
||
static unsigned long FXhciWaitForEvtType(FXhci *const xhci,
|
||
const int trb_type,
|
||
unsigned long *const timeout_us)
|
||
{
|
||
while (FXhciWaitForEvt(&xhci->er, timeout_us))
|
||
{
|
||
if (FXHCI_TRB_GET(TT, xhci->er.cur) == (unsigned int)trb_type)
|
||
{
|
||
break;
|
||
}
|
||
|
||
FXhciHandleEvt(xhci);
|
||
}
|
||
|
||
return *timeout_us;
|
||
}
|
||
|
||
/*
|
||
* Ref. xHCI Specification Revision 1.2, May 2019.
|
||
* Section 4.6.1.2.
|
||
*
|
||
* Process events from xHCI Abort command.
|
||
*
|
||
* Returns FXHCI_CC_COMMAND_RING_STOPPED on success and FXHCI_CC_TIMEOUT on failure.
|
||
*/
|
||
|
||
FXhciTransCode FXhciWaitForCmdAborted(FXhci *const xhci, const FXhciTrb *const address)
|
||
{
|
||
/*
|
||
* Specification says that something might be seriously wrong, if
|
||
* we don't get a response after 5s. Still, let the caller decide,
|
||
* what to do then.
|
||
*/
|
||
unsigned long timeout_us = FUSB_USB_MAX_PROCESSING_TIME_US; /* 5s */
|
||
FXhciTransCode cc = FXHCI_CC_TIMEOUT;
|
||
/*
|
||
* Expects two command completion events:
|
||
* The first with CC == COMMAND_ABORTED should point to address
|
||
* (not present if command was not running),
|
||
* the second with CC == COMMAND_RING_STOPPED should point to new dq.
|
||
*/
|
||
while (FXhciWaitForEvtType(xhci, FXHCI_TRB_EV_CMD_CMPL, &timeout_us))
|
||
{
|
||
if ((xhci->er.cur->ptr_low == (uintptr)(address)) &&
|
||
(xhci->er.cur->ptr_high == 0))
|
||
{
|
||
cc = FXHCI_TRB_GET(CC, xhci->er.cur);
|
||
FXhciAdvanceEvtRing(xhci);
|
||
break;
|
||
}
|
||
|
||
FXhciHandleCmdCompletionEvt(xhci);
|
||
}
|
||
if (timeout_us == 0)
|
||
{
|
||
FUSB_INFO("Warning: timeout waiting COMMAND_ABORTED or COMMAND_RING_STOPPED.");
|
||
goto update_and_return;
|
||
}
|
||
if (cc == FXHCI_CC_COMMAND_RING_STOPPED)
|
||
{
|
||
/* There may not have been a command to abort. */
|
||
goto update_and_return;
|
||
}
|
||
|
||
timeout_us = FUSB_USB_MAX_PROCESSING_TIME_US; /* 5s */
|
||
while (FXhciWaitForEvtType(xhci, FXHCI_TRB_EV_CMD_CMPL, &timeout_us))
|
||
{
|
||
if (FXHCI_TRB_GET(CC, xhci->er.cur) == FXHCI_CC_COMMAND_RING_STOPPED)
|
||
{
|
||
cc = FXHCI_CC_COMMAND_RING_STOPPED;
|
||
FXhciAdvanceEvtRing(xhci);
|
||
break;
|
||
}
|
||
|
||
FXhciHandleCmdCompletionEvt(xhci);
|
||
}
|
||
if (timeout_us == 0)
|
||
FUSB_INFO("Warning: timeout waiting for COMMAND_RING_STOPPED.");
|
||
|
||
update_and_return:
|
||
FXhciUpdateEvtDQ(xhci);
|
||
return cc;
|
||
}
|
||
|
||
/*
|
||
* returns cc of command in question (pointed to by `address`)
|
||
* caller should abort command if cc is FXHCI_CC_TIMEOUT
|
||
*/
|
||
FXhciTransCode FXhciWaitForCmdDone(FXhci *const xhci,
|
||
const FXhciTrb *const address,
|
||
const int clear_event)
|
||
{
|
||
unsigned long timeout_us = FUSB_USB_MAX_PROCESSING_TIME_US; /* 5s */
|
||
FXhciTransCode cc = FXHCI_CC_TIMEOUT;
|
||
|
||
while (FXhciWaitForEvtType(xhci, FXHCI_TRB_EV_CMD_CMPL, &timeout_us))
|
||
{
|
||
if ((xhci->er.cur->ptr_low == (uintptr)(address)) &&
|
||
(xhci->er.cur->ptr_high == 0))
|
||
{
|
||
cc = FXHCI_TRB_GET(CC, xhci->er.cur);
|
||
break;
|
||
}
|
||
|
||
FXhciHandleCmdCompletionEvt(xhci);
|
||
}
|
||
|
||
if (!timeout_us)
|
||
{
|
||
FUSB_INFO("Warning: timeout waiting for FXHCI_TRB_EV_CMD_CMPL.");
|
||
}
|
||
else if (clear_event)
|
||
{
|
||
FXhciAdvanceEvtRing(xhci);
|
||
}
|
||
|
||
FXhciUpdateEvtDQ(xhci);
|
||
return cc;
|
||
}
|
||
|
||
/* returns amount of bytes transferred on success, negative CC on error */
|
||
FXhciTransCode FXhciWaitForTransfer(FXhci *const xhci, const int slot_id, const int ep_id)
|
||
{
|
||
/* 5s for all types of transfers */
|
||
unsigned long timeout_us = FUSB_USB_MAX_PROCESSING_TIME_US;
|
||
FXhciTransCode ret = FXHCI_CC_TIMEOUT;
|
||
while (FXhciWaitForEvtType(xhci, FXHCI_TRB_EV_TRANSFER, &timeout_us))
|
||
{
|
||
if (FXHCI_TRB_GET(ID, xhci->er.cur) == (unsigned int)slot_id &&
|
||
FXHCI_TRB_GET(EP, xhci->er.cur) == (unsigned int)ep_id)
|
||
{
|
||
ret = -FXHCI_TRB_GET(CC, xhci->er.cur);
|
||
if (ret == -FXHCI_CC_SUCCESS || ret == -FXHCI_CC_SHORT_PACKET)
|
||
{
|
||
ret = FXHCI_TRB_GET(EVTL, xhci->er.cur);
|
||
}
|
||
FXhciAdvanceEvtRing(xhci);
|
||
break;
|
||
}
|
||
|
||
FXhciHandleTransferEvt(xhci);
|
||
}
|
||
if (!timeout_us)
|
||
{
|
||
FUSB_INFO("Warning: timeout waiting for FXHCI_TRB_EV_TRANSFER. ");
|
||
}
|
||
|
||
FXhciUpdateEvtDQ(xhci);
|
||
return ret;
|
||
}
|