#include <sys/usb/hcd/xhci/xhci.h>
#include <sys/sysmacros.h>
clock_t xhci_command_abort_wait = 5 * MICROSEC;
clock_t xhci_command_wait = MICROSEC;
static void xhci_command_settimeout(xhci_t *, clock_t);
void
xhci_command_ring_fini(xhci_t *xhcip)
{
xhci_command_ring_t *xcr = &xhcip->xhci_command;
if (xcr->xcr_ring.xr_trb == NULL)
return;
VERIFY(xcr->xcr_timeout == 0);
xhci_ring_free(&xcr->xcr_ring);
mutex_destroy(&xcr->xcr_lock);
cv_destroy(&xcr->xcr_cv);
list_destroy(&xcr->xcr_commands);
}
int
xhci_command_ring_init(xhci_t *xhcip)
{
int ret;
uint64_t addr;
xhci_command_ring_t *xcr = &xhcip->xhci_command;
if (xcr->xcr_ring.xr_trb == NULL) {
if ((ret = xhci_ring_alloc(xhcip, &xcr->xcr_ring)) != 0)
return (ret);
}
if ((ret = xhci_ring_reset(xhcip, &xcr->xcr_ring)) != 0)
return (ret);
#ifdef DEBUG
addr = xhci_get64(xhcip, XHCI_R_OPER, XHCI_CRCR);
VERIFY0(addr & XHCI_CRCR_CRR);
#endif
addr = LE_64(xhci_dma_pa(&xcr->xcr_ring.xr_dma) | XHCI_CRCR_RCS);
xhci_put64(xhcip, XHCI_R_OPER, XHCI_CRCR, addr);
if (xhci_check_regs_acc(xhcip) != DDI_FM_OK)
return (EIO);
mutex_init(&xcr->xcr_lock, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(xhcip->xhci_intr_pri));
cv_init(&xcr->xcr_cv, NULL, CV_DRIVER, NULL);
list_create(&xcr->xcr_commands, sizeof (xhci_command_t),
offsetof(xhci_command_t, xco_link));
return (0);
}
static void
xhci_command_timeout(void *arg)
{
uint64_t reg;
clock_t delay;
xhci_t *xhcip = arg;
xhci_command_ring_t *xcr = &xhcip->xhci_command;
xhci_command_t *xco;
mutex_enter(&xcr->xcr_lock);
xco = list_head(&xcr->xcr_commands);
if (xco == NULL || xco->xco_state != XHCI_COMMAND_S_QUEUED) {
xcr->xcr_timeout = 0;
mutex_exit(&xcr->xcr_lock);
return;
}
xcr->xcr_state = XHCI_COMMAND_RING_ABORTING;
reg = xhci_get64(xhcip, XHCI_R_OPER, XHCI_CRCR);
if (xhci_check_regs_acc(xhcip) != DDI_FM_OK) {
xcr->xcr_timeout = 0;
mutex_exit(&xcr->xcr_lock);
xhci_error(xhcip, "encountered fatal FM error reading command "
"ring control register: resetting device");
xhci_fm_runtime_reset(xhcip);
return;
}
reg |= XHCI_CRCR_CA;
xhci_put64(xhcip, XHCI_R_OPER, XHCI_CRCR, reg);
if (xhci_check_regs_acc(xhcip) != DDI_FM_OK) {
xcr->xcr_timeout = 0;
mutex_exit(&xcr->xcr_lock);
xhci_error(xhcip, "encountered fatal FM error writing command "
"ring control register: resetting device");
xhci_fm_runtime_reset(xhcip);
return;
}
delay = drv_usectohz(xhci_command_abort_wait);
while (xcr->xcr_state != XHCI_COMMAND_RING_ABORT_DONE) {
int ret;
ret = cv_reltimedwait(&xcr->xcr_cv, &xcr->xcr_lock, delay,
TR_CLOCK_TICK);
if (ret == -1) {
xcr->xcr_timeout = 0;
mutex_exit(&xcr->xcr_lock);
xhci_error(xhcip, "abort command timed out: resetting "
"device");
xhci_fm_runtime_reset(xhcip);
return;
}
}
if (list_is_empty(&xcr->xcr_commands) != 0) {
xcr->xcr_state = XHCI_COMMAND_RING_IDLE;
xcr->xcr_timeout = 0;
} else {
xhci_put32(xhcip, XHCI_R_DOOR, XHCI_DOORBELL(0), 0);
if (xhci_check_regs_acc(xhcip) != DDI_FM_OK) {
xcr->xcr_timeout = 0;
mutex_exit(&xcr->xcr_lock);
xhci_error(xhcip, "encountered fatal FM error writing "
"command ring control register: resetting device");
xhci_fm_runtime_reset(xhcip);
return;
}
xcr->xcr_timeout = 0;
xhci_command_settimeout(xhcip, xhci_command_wait);
xcr->xcr_state = XHCI_COMMAND_RING_RUNNING;
}
mutex_exit(&xcr->xcr_lock);
}
static void
xhci_command_settimeout(xhci_t *xhcip, clock_t microsecs)
{
clock_t delay;
xhci_command_ring_t *xcr = &xhcip->xhci_command;
ASSERT(MUTEX_HELD(&xcr->xcr_lock));
ASSERT(xcr->xcr_timeout == 0);
delay = drv_usectohz(microsecs);
xcr->xcr_timeout = timeout(xhci_command_timeout, xhcip, delay);
}
void
xhci_command_init(xhci_command_t *xcp)
{
bzero(xcp, sizeof (xhci_command_t));
cv_init(&xcp->xco_cv, NULL, CV_DRIVER, NULL);
}
void
xhci_command_fini(xhci_command_t *xcp)
{
cv_destroy(&xcp->xco_cv);
}
boolean_t
xhci_command_event_callback(xhci_t *xhcip, xhci_trb_t *trb)
{
int cstat;
timeout_id_t to;
xhci_command_t *xco, *rem;
xhci_command_ring_t *xcr = &xhcip->xhci_command;
xhci_ring_t *xrp = &xcr->xcr_ring;
mutex_enter(&xcr->xcr_lock);
cstat = XHCI_TRB_GET_CODE(LE_32(trb->trb_status));
if (cstat == XHCI_CODE_CMD_RING_STOP) {
if (xcr->xcr_state == XHCI_COMMAND_RING_ABORTING)
xcr->xcr_state = XHCI_COMMAND_RING_ABORT_DONE;
cv_broadcast(&xcr->xcr_cv);
mutex_exit(&xcr->xcr_lock);
return (B_TRUE);
}
xco = list_head(&xcr->xcr_commands);
VERIFY(xco != NULL);
if (xhci_ring_trb_tail_valid(xrp, LE_64(trb->trb_addr)) == B_FALSE) {
mutex_exit(&xcr->xcr_lock);
return (B_TRUE);
}
xco->xco_state = XHCI_COMMAND_S_RECEIVED;
to = xcr->xcr_timeout;
xcr->xcr_timeout = 0;
if (xcr->xcr_state != XHCI_COMMAND_RING_ABORTING) {
mutex_exit(&xcr->xcr_lock);
(void) untimeout(to);
mutex_enter(&xcr->xcr_lock);
}
rem = list_remove_head(&xcr->xcr_commands);
VERIFY3P(rem, ==, xco);
xco->xco_res.trb_addr = LE_64(trb->trb_addr);
xco->xco_res.trb_status = LE_32(trb->trb_status);
xco->xco_res.trb_flags = LE_32(trb->trb_flags);
xco->xco_state = XHCI_COMMAND_S_DONE;
if (xhci_ring_trb_consumed(xrp, LE_64(trb->trb_addr)) == B_FALSE) {
mutex_exit(&xcr->xcr_lock);
xhci_error(xhcip, "encountered invalid TRB head while "
"processing command ring: TRB with addr 0x%"PRIx64 " could "
"not be consumed", LE_64(trb->trb_addr));
xhci_fm_runtime_reset(xhcip);
return (B_FALSE);
}
cv_broadcast(&xcr->xcr_cv);
if (xcr->xcr_state < XHCI_COMMAND_RING_ABORTING) {
if (list_is_empty(&xcr->xcr_commands) != 0) {
xcr->xcr_state = XHCI_COMMAND_RING_IDLE;
} else {
xhci_command_settimeout(xhcip, xhci_command_wait);
}
}
mutex_exit(&xcr->xcr_lock);
cv_signal(&xco->xco_cv);
return (B_TRUE);
}
static int
xhci_command_submit(xhci_t *xhcip, xhci_command_t *xco)
{
int ret;
xhci_command_ring_t *xcr = &xhcip->xhci_command;
xhci_ring_t *xrp = &xcr->xcr_ring;
mutex_enter(&xcr->xcr_lock);
while (xhci_ring_trb_space(xrp, 1U) == B_FALSE ||
xcr->xcr_state >= XHCI_COMMAND_RING_ABORTING) {
cv_wait(&xcr->xcr_cv, &xcr->xcr_lock);
}
xhci_ring_trb_put(xrp, &xco->xco_req);
xco->xco_state = XHCI_COMMAND_S_QUEUED;
list_insert_tail(&xcr->xcr_commands, xco);
XHCI_DMA_SYNC(xrp->xr_dma, DDI_DMA_SYNC_FORDEV);
if (xhci_check_dma_handle(xhcip, &xrp->xr_dma) != DDI_FM_OK) {
mutex_exit(&xcr->xcr_lock);
xhci_error(xhcip, "encountered fatal FM error syncing command "
"ring DMA contents: resetting device");
xhci_fm_runtime_reset(xhcip);
return (USB_HC_HARDWARE_ERROR);
}
xhci_put32(xhcip, XHCI_R_DOOR, XHCI_DOORBELL(0), 0);
if (xhci_check_regs_acc(xhcip) != DDI_FM_OK) {
mutex_exit(&xcr->xcr_lock);
xhci_error(xhcip, "encountered fatal FM error ringing command "
"ring doorbell: resetting device");
xhci_fm_runtime_reset(xhcip);
return (USB_HC_HARDWARE_ERROR);
}
if (xcr->xcr_state == XHCI_COMMAND_RING_IDLE) {
VERIFY(xcr->xcr_timeout == 0);
xhci_command_settimeout(xhcip, xhci_command_wait);
xcr->xcr_state = XHCI_COMMAND_RING_RUNNING;
}
while (xco->xco_state < XHCI_COMMAND_S_DONE)
cv_wait(&xco->xco_cv, &xcr->xcr_lock);
if (xco->xco_state == XHCI_COMMAND_S_DONE)
ret = USB_SUCCESS;
else
ret = USB_HC_HARDWARE_ERROR;
mutex_exit(&xcr->xcr_lock);
return (ret);
}
int
xhci_command_enable_slot(xhci_t *xhcip, uint8_t *slotp)
{
int ret;
uint8_t slot, code;
xhci_command_t co;
VERIFY(xhcip != NULL);
VERIFY(slotp != NULL);
xhci_command_init(&co);
co.xco_req.trb_flags = LE_32(XHCI_CMD_ENABLE_SLOT) |
XHCI_TRB_SET_STYPE(0);
ret = xhci_command_submit(xhcip, &co);
if (ret != 0)
goto done;
code = XHCI_TRB_GET_CODE(co.xco_res.trb_status);
slot = XHCI_TRB_GET_SLOT(co.xco_res.trb_flags);
if (code == XHCI_CODE_SUCCESS) {
*slotp = slot;
ret = USB_SUCCESS;
} else if (code == XHCI_CODE_NO_SLOTS) {
ret = USB_NO_RESOURCES;
} else if (code == XHCI_CODE_CMD_ABORTED) {
ret = USB_CR_TIMEOUT;
} else {
ret = USB_HC_HARDWARE_ERROR;
xhci_log(xhcip, "!unexpected error when enabling slot: "
"%d", code);
}
done:
xhci_command_fini(&co);
return (ret);
}
int
xhci_command_disable_slot(xhci_t *xhcip, uint8_t slot)
{
int ret, code;
xhci_command_t co;
VERIFY(xhcip != NULL);
xhci_command_init(&co);
co.xco_req.trb_flags = LE_32(XHCI_CMD_DISABLE_SLOT |
XHCI_TRB_SET_SLOT(slot));
ret = xhci_command_submit(xhcip, &co);
if (ret != 0)
goto done;
code = XHCI_TRB_GET_CODE(co.xco_res.trb_status);
if (code == XHCI_CODE_SUCCESS) {
ret = USB_SUCCESS;
} else if (code == XHCI_CODE_CMD_ABORTED) {
ret = USB_CR_TIMEOUT;
} else {
ret = USB_HC_HARDWARE_ERROR;
xhci_log(xhcip, "!unexpected error when disabling slot: "
"%d", code);
}
done:
xhci_command_fini(&co);
return (ret);
}
int
xhci_command_set_address(xhci_t *xhcip, xhci_device_t *xd, boolean_t bsr)
{
int ret, code;
xhci_command_t co;
VERIFY(xhcip != NULL);
VERIFY(xd != NULL);
xhci_command_init(&co);
co.xco_req.trb_addr = LE_64(xhci_dma_pa(&xd->xd_ictx));
co.xco_req.trb_status = 0;
co.xco_req.trb_flags = LE_32(XHCI_CMD_ADDRESS_DEVICE |
XHCI_TRB_SET_SLOT(xd->xd_slot));
if (bsr == B_TRUE)
co.xco_req.trb_flags |= LE_32(XHCI_TRB_BSR);
ret = xhci_command_submit(xhcip, &co);
if (ret != 0)
goto done;
code = XHCI_TRB_GET_CODE(co.xco_res.trb_status);
if (code == XHCI_CODE_SUCCESS) {
ret = USB_SUCCESS;
} else if (code == XHCI_CODE_CMD_ABORTED) {
ret = USB_CR_TIMEOUT;
} else {
ret = USB_HC_HARDWARE_ERROR;
xhci_log(xhcip, "!unexpected error when setting address: "
"%d", code);
}
done:
xhci_command_fini(&co);
return (ret);
}
int
xhci_command_configure_endpoint(xhci_t *xhcip, xhci_device_t *xd)
{
int ret, code;
xhci_command_t co;
VERIFY(xhcip != NULL);
VERIFY(xd != NULL);
xhci_command_init(&co);
co.xco_req.trb_addr = LE_64(xhci_dma_pa(&xd->xd_ictx));
co.xco_req.trb_status = LE_32(0);
co.xco_req.trb_flags = LE_32(XHCI_CMD_CONFIG_EP |
XHCI_TRB_SET_SLOT(xd->xd_slot));
ret = xhci_command_submit(xhcip, &co);
if (ret != 0)
goto done;
code = XHCI_TRB_GET_CODE(co.xco_res.trb_status);
switch (code) {
case XHCI_CODE_SUCCESS:
ret = USB_SUCCESS;
break;
case XHCI_CODE_CMD_ABORTED:
ret = USB_CR_TIMEOUT;
break;
case XHCI_CODE_SLOT_NOT_ON:
xhci_log(xhcip, "!failed to configure endpoints for slot %d, "
"slot not on, likely driver bug!", xd->xd_slot);
ret = USB_FAILURE;
break;
case XHCI_CODE_BANDWIDTH:
ret = USB_NO_BANDWIDTH;
break;
case XHCI_CODE_RESOURCE:
ret = USB_NO_RESOURCES;
break;
default:
ret = USB_HC_HARDWARE_ERROR;
xhci_log(xhcip, "!unexpected error when configuring enpoints: "
"%d", code);
break;
}
done:
xhci_command_fini(&co);
return (ret);
}
int
xhci_command_evaluate_context(xhci_t *xhcip, xhci_device_t *xd)
{
int ret, code;
xhci_command_t co;
VERIFY(xhcip != NULL);
VERIFY(xd != NULL);
xhci_command_init(&co);
co.xco_req.trb_addr = LE_64(xhci_dma_pa(&xd->xd_ictx));
co.xco_req.trb_status = LE_32(0);
co.xco_req.trb_flags = LE_32(XHCI_CMD_EVAL_CTX |
XHCI_TRB_SET_SLOT(xd->xd_slot));
ret = xhci_command_submit(xhcip, &co);
if (ret != 0)
goto done;
code = XHCI_TRB_GET_CODE(co.xco_res.trb_status);
switch (code) {
case XHCI_CODE_SUCCESS:
ret = USB_SUCCESS;
break;
case XHCI_CODE_CMD_ABORTED:
ret = USB_CR_TIMEOUT;
break;
case XHCI_CODE_SLOT_NOT_ON:
xhci_log(xhcip, "!failed to evaluate endpoints for slot %d, "
"slot not on, likely driver bug!", xd->xd_slot);
ret = USB_FAILURE;
break;
default:
ret = USB_HC_HARDWARE_ERROR;
xhci_log(xhcip, "!unexpected error when evaluating enpoints: "
"%d", code);
break;
}
done:
xhci_command_fini(&co);
return (ret);
}
int
xhci_command_reset_endpoint(xhci_t *xhcip, xhci_device_t *xd,
xhci_endpoint_t *xep)
{
int ret, code;
xhci_command_t co;
VERIFY(xhcip != NULL);
VERIFY(xd != NULL);
VERIFY(xep != NULL);
xhci_command_init(&co);
co.xco_req.trb_addr = LE_64(0);
co.xco_req.trb_status = LE_32(0);
co.xco_req.trb_flags = LE_32(XHCI_CMD_RESET_EP |
XHCI_TRB_SET_SLOT(xd->xd_slot) |
XHCI_TRB_SET_EP(xep->xep_num + 1));
ret = xhci_command_submit(xhcip, &co);
if (ret != 0)
goto done;
code = XHCI_TRB_GET_CODE(co.xco_res.trb_status);
switch (code) {
case XHCI_CODE_SUCCESS:
ret = USB_SUCCESS;
break;
case XHCI_CODE_CMD_ABORTED:
ret = USB_CR_TIMEOUT;
break;
case XHCI_CODE_CONTEXT_STATE:
case XHCI_CODE_SLOT_NOT_ON:
xhci_log(xhcip, "!xhci reset endpoint command: asked to modify "
"endpoint (%u)/slot (%d) in wrong state: %d", xep->xep_num,
xd->xd_slot, code);
if (code == XHCI_CODE_CONTEXT_STATE) {
xhci_endpoint_context_t *epctx;
epctx = xd->xd_endout[xep->xep_num];
xhci_log(xhcip, "!endpoint is in state %d",
XHCI_EPCTX_STATE(epctx->xec_info));
}
ret = USB_INVALID_CONTEXT;
break;
default:
ret = USB_HC_HARDWARE_ERROR;
xhci_log(xhcip, "!unexpected error when resetting enpoint: %d",
code);
break;
}
done:
xhci_command_fini(&co);
return (ret);
}
int
xhci_command_set_tr_dequeue(xhci_t *xhcip, xhci_device_t *xd,
xhci_endpoint_t *xep)
{
uint64_t pa;
int ret, code;
xhci_command_t co;
xhci_ring_t *xrp;
VERIFY(xhcip != NULL);
VERIFY(xd != NULL);
VERIFY(xep != NULL);
xhci_command_init(&co);
xrp = &xep->xep_ring;
pa = xhci_dma_pa(&xrp->xr_dma) + sizeof (xhci_trb_t) * xrp->xr_tail;
pa |= xrp->xr_cycle;
co.xco_req.trb_addr = LE_64(pa);
co.xco_req.trb_status = LE_32(0);
co.xco_req.trb_flags = LE_32(XHCI_CMD_SET_TR_DEQ |
XHCI_TRB_SET_SLOT(xd->xd_slot) |
XHCI_TRB_SET_EP(xep->xep_num + 1));
ret = xhci_command_submit(xhcip, &co);
if (ret != 0)
goto done;
code = XHCI_TRB_GET_CODE(co.xco_res.trb_status);
switch (code) {
case XHCI_CODE_SUCCESS:
ret = USB_SUCCESS;
break;
case XHCI_CODE_CMD_ABORTED:
ret = USB_CR_TIMEOUT;
break;
case XHCI_CODE_CONTEXT_STATE:
case XHCI_CODE_SLOT_NOT_ON:
xhci_log(xhcip, "!xhci set tr dequeue command: asked to modify "
"endpoint (%u)/slot (%d) in wrong state: %d", xep->xep_num,
xd->xd_slot, code);
if (code == XHCI_CODE_CONTEXT_STATE) {
xhci_endpoint_context_t *epctx;
epctx = xd->xd_endout[xep->xep_num];
xhci_log(xhcip, "!endpoint is in state %d",
XHCI_EPCTX_STATE(epctx->xec_info));
}
ret = USB_INVALID_CONTEXT;
break;
default:
ret = USB_HC_HARDWARE_ERROR;
xhci_log(xhcip, "!unexpected error when resetting enpoint: %d",
code);
break;
}
done:
xhci_command_fini(&co);
return (ret);
}
int
xhci_command_stop_endpoint(xhci_t *xhcip, xhci_device_t *xd,
xhci_endpoint_t *xep)
{
int ret, code;
xhci_command_t co;
xhci_endpoint_context_t *epctx;
VERIFY(xhcip != NULL);
VERIFY(xd != NULL);
VERIFY(xep != NULL);
xhci_command_init(&co);
co.xco_req.trb_addr = LE_64(0);
co.xco_req.trb_status = LE_32(0);
co.xco_req.trb_flags = LE_32(XHCI_CMD_STOP_EP |
XHCI_TRB_SET_SLOT(xd->xd_slot) |
XHCI_TRB_SET_EP(xep->xep_num + 1));
ret = xhci_command_submit(xhcip, &co);
if (ret != 0)
goto done;
code = XHCI_TRB_GET_CODE(co.xco_res.trb_status);
switch (code) {
case XHCI_CODE_SUCCESS:
ret = USB_SUCCESS;
break;
case XHCI_CODE_CMD_ABORTED:
ret = USB_CR_TIMEOUT;
break;
case XHCI_CODE_CONTEXT_STATE:
epctx = xd->xd_endout[xep->xep_num];
if (XHCI_EPCTX_STATE(epctx->xec_info) != XHCI_EP_STOPPED) {
xhci_log(xhcip, "!stopped endpoint (%d)/slot (%u) in "
"unexpected state %d", xep->xep_num, xd->xd_slot,
XHCI_EPCTX_STATE(epctx->xec_info));
}
ret = USB_INVALID_CONTEXT;
break;
default:
ret = USB_HC_HARDWARE_ERROR;
xhci_log(xhcip, "!unexpected error (%d) when resetting "
"endpoint (%d)/slot (%u)", code,
xep->xep_num, xd->xd_slot);
break;
}
done:
xhci_command_fini(&co);
return (ret);
}