#include <sys/usb/hcd/xhci/xhci.h>
#include <sys/sysmacros.h>
#include <sys/strsun.h>
#include <sys/strsubr.h>
xhci_t *
xhci_hcdi_get_xhcip_from_dev(usba_device_t *ud)
{
dev_info_t *dip = ud->usb_root_hub_dip;
xhci_t *xhcip = ddi_get_soft_state(xhci_soft_state,
ddi_get_instance(dip));
VERIFY(xhcip != NULL);
return (xhcip);
}
static xhci_t *
xhci_hcdi_get_xhcip(usba_pipe_handle_data_t *ph)
{
return (xhci_hcdi_get_xhcip_from_dev(ph->p_usba_device));
}
static int
xhci_hcdi_pm_support(dev_info_t *dip)
{
return (USB_FAILURE);
}
static int
xhci_hcdi_pipe_open(usba_pipe_handle_data_t *ph, usb_flags_t usb_flags)
{
xhci_t *xhcip = xhci_hcdi_get_xhcip(ph);
xhci_pipe_t *pipe;
xhci_endpoint_t *xep = NULL;
xhci_device_t *xd;
int kmflags = usb_flags & USB_FLAGS_SLEEP ? KM_SLEEP : KM_NOSLEEP;
int ret;
uint_t epid;
mutex_enter(&xhcip->xhci_lock);
if (xhcip->xhci_state & XHCI_S_ERROR) {
mutex_exit(&xhcip->xhci_lock);
return (USB_HC_HARDWARE_ERROR);
}
mutex_exit(&xhcip->xhci_lock);
if (ph->p_hcd_private != NULL) {
return (USB_FAILURE);
}
pipe = kmem_zalloc(sizeof (xhci_pipe_t), kmflags);
if (pipe == NULL) {
return (USB_NO_RESOURCES);
}
pipe->xp_opentime = gethrtime();
pipe->xp_pipe = ph;
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
xep = NULL;
goto add;
}
epid = xhci_endpoint_pipe_to_epid(ph);
xd = usba_hcdi_get_device_private(ph->p_usba_device);
if (xd == NULL) {
xhci_error(xhcip, "!encountered endpoint (%d) without device "
"during pipe open", epid);
kmem_free(pipe, sizeof (xhci_pipe_t));
return (USB_FAILURE);
}
mutex_enter(&xhcip->xhci_lock);
if (epid == XHCI_DEFAULT_ENDPOINT) {
xep = xd->xd_endpoints[epid];
VERIFY(xep != NULL);
VERIFY(xep->xep_pipe == NULL);
xep->xep_pipe = ph;
mutex_exit(&xhcip->xhci_lock);
ret = xhci_endpoint_update_default(xhcip, xd, xep);
if (ret != USB_SUCCESS) {
kmem_free(pipe, sizeof (xhci_pipe_t));
return (ret);
}
goto add;
}
VERIFY(xd->xd_addressed == B_TRUE);
if ((xep = xd->xd_endpoints[epid]) != NULL &&
(xep->xep_state & XHCI_ENDPOINT_OPEN)) {
mutex_exit(&xhcip->xhci_lock);
kmem_free(pipe, sizeof (xhci_pipe_t));
xhci_log(xhcip, "!asked to open endpoint %d on slot %d and "
"port %d, but endpoint already exists", epid, xd->xd_slot,
xd->xd_port);
return (USB_FAILURE);
}
if (xep != NULL) {
if ((ret = xhci_endpoint_reopen(xhcip, xd, xep, ph) != 0)) {
mutex_exit(&xhcip->xhci_lock);
kmem_free(pipe, sizeof (xhci_pipe_t));
xhci_log(xhcip, "!asked to reopen endpoint %d on "
"slot %d and port %d, but reopen failed (%d)",
epid, xd->xd_slot, xd->xd_port, ret);
return (ret);
}
xep->xep_state |= XHCI_ENDPOINT_QUIESCE;
if ((ret = xhci_endpoint_quiesce(xhcip, xd, xep)) !=
USB_SUCCESS) {
xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE;
xhci_endpoint_close(xhcip, xep);
mutex_exit(&xhcip->xhci_lock);
kmem_free(pipe, sizeof (xhci_pipe_t));
xhci_log(xhcip, "!asked to reopen endpoint %d on "
"slot %d and port %d, but quiesce failed (%d)",
epid, xd->xd_slot, xd->xd_port, ret);
return (ret);
}
mutex_exit(&xhcip->xhci_lock);
if ((ret = xhci_command_set_tr_dequeue(xhcip, xd, xep)) != 0 ||
(ret = xhci_endpoint_ring(xhcip, xd, xep)) != 0) {
mutex_enter(&xhcip->xhci_lock);
xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE;
xhci_endpoint_close(xhcip, xep);
mutex_exit(&xhcip->xhci_lock);
kmem_free(pipe, sizeof (xhci_pipe_t));
xhci_log(xhcip, "!asked to open endpoint %d on "
"slot %d and port %d, but restart failed (%d)",
epid, xd->xd_slot, xd->xd_port, ret);
return (USB_FAILURE);
}
mutex_enter(&xhcip->xhci_lock);
xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE;
mutex_exit(&xhcip->xhci_lock);
goto add;
}
ret = xhci_endpoint_init(xhcip, xd, ph);
if (ret != 0) {
mutex_exit(&xhcip->xhci_lock);
kmem_free(pipe, sizeof (xhci_pipe_t));
if (ret == EIO) {
xhci_error(xhcip, "failed to initialize endpoint %d "
"on device slot %d and port %d: encountered fatal "
"FM error, resetting device", epid, xd->xd_slot,
xd->xd_port);
xhci_fm_runtime_reset(xhcip);
}
return (USB_HC_HARDWARE_ERROR);
}
xep = xd->xd_endpoints[epid];
mutex_enter(&xd->xd_imtx);
mutex_exit(&xhcip->xhci_lock);
xd->xd_input->xic_drop_flags = LE_32(0);
xd->xd_input->xic_add_flags = LE_32(XHCI_INCTX_MASK_DCI(0) |
XHCI_INCTX_MASK_DCI(epid + 1));
if (epid + 1 > XHCI_SCTX_GET_DCI(LE_32(xd->xd_slotin->xsc_info))) {
uint32_t info;
info = xd->xd_slotin->xsc_info;
info &= ~XHCI_SCTX_DCI_MASK;
info |= XHCI_SCTX_SET_DCI(epid + 1);
xd->xd_slotin->xsc_info = info;
}
XHCI_DMA_SYNC(xd->xd_ictx, DDI_DMA_SYNC_FORDEV);
if (xhci_check_dma_handle(xhcip, &xd->xd_ictx) != DDI_FM_OK) {
mutex_exit(&xd->xd_imtx);
mutex_enter(&xhcip->xhci_lock);
xhci_endpoint_close(xhcip, xep);
xhci_endpoint_fini(xd, epid);
mutex_exit(&xhcip->xhci_lock);
kmem_free(pipe, sizeof (xhci_pipe_t));
xhci_error(xhcip, "failed to open pipe on endpoint %d of "
"device with slot %d and port %d: encountered fatal FM "
"error syncing device input context, resetting device",
epid, xd->xd_slot, xd->xd_port);
xhci_fm_runtime_reset(xhcip);
return (USB_HC_HARDWARE_ERROR);
}
if ((ret = xhci_command_configure_endpoint(xhcip, xd)) != USB_SUCCESS) {
mutex_exit(&xd->xd_imtx);
xhci_error(xhcip, "failed to open pipe on endpoint %d of "
"device with slot %d and port %d: configure endpoint "
"command failed (%d)", epid, xd->xd_slot, xd->xd_port, ret);
mutex_enter(&xhcip->xhci_lock);
xhci_endpoint_close(xhcip, xep);
xhci_endpoint_fini(xd, epid);
mutex_exit(&xhcip->xhci_lock);
kmem_free(pipe, sizeof (xhci_pipe_t));
return (ret);
}
mutex_exit(&xd->xd_imtx);
add:
pipe->xp_ep = xep;
ph->p_hcd_private = (usb_opaque_t)pipe;
mutex_enter(&xhcip->xhci_lock);
list_insert_tail(&xhcip->xhci_usba.xa_pipes, pipe);
mutex_exit(&xhcip->xhci_lock);
return (USB_SUCCESS);
}
static void
xhci_hcdi_periodic_free(xhci_t *xhcip, xhci_pipe_t *xp)
{
int i;
xhci_periodic_pipe_t *xpp = &xp->xp_periodic;
if (xpp->xpp_tsize == 0)
return;
for (i = 0; i < xpp->xpp_ntransfers; i++) {
if (xpp->xpp_transfers[i] == NULL)
continue;
xhci_transfer_free(xhcip, xpp->xpp_transfers[i]);
xpp->xpp_transfers[i] = NULL;
}
xpp->xpp_ntransfers = 0;
xpp->xpp_tsize = 0;
}
static void
xhci_hcdi_pipe_flush(xhci_t *xhcip, xhci_endpoint_t *xep, int intr_code)
{
xhci_transfer_t *xt;
ASSERT(MUTEX_HELD(&xhcip->xhci_lock));
while ((xt = list_remove_head(&xep->xep_transfers)) != NULL) {
if (xhci_endpoint_is_periodic_in(xep) == B_FALSE ||
XHCI_IS_ONESHOT_XFER(xt)) {
usba_hcdi_cb(xep->xep_pipe, xt->xt_usba_req,
USB_CR_FLUSHED);
xhci_transfer_free(xhcip, xt);
}
}
if (xhci_endpoint_is_periodic_in(xep) == B_TRUE) {
xhci_pipe_t *xp = (xhci_pipe_t *)xep->xep_pipe->p_hcd_private;
xhci_periodic_pipe_t *xpp = &xp->xp_periodic;
if (xpp->xpp_usb_req != NULL) {
usba_hcdi_cb(xep->xep_pipe, xpp->xpp_usb_req,
intr_code);
xpp->xpp_usb_req = NULL;
}
}
}
static int
xhci_hcdi_pipe_poll_fini(usba_pipe_handle_data_t *ph, boolean_t is_close)
{
int ret;
uint_t epid;
xhci_t *xhcip = xhci_hcdi_get_xhcip(ph);
xhci_device_t *xd;
xhci_endpoint_t *xep;
xhci_pipe_t *xp;
xhci_periodic_pipe_t *xpp;
usb_opaque_t urp;
mutex_enter(&xhcip->xhci_lock);
if (xhcip->xhci_state & XHCI_S_ERROR) {
mutex_exit(&xhcip->xhci_lock);
return (USB_HC_HARDWARE_ERROR);
}
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
xhci_root_hub_intr_root_disable(xhcip);
ret = USB_SUCCESS;
mutex_exit(&xhcip->xhci_lock);
return (ret);
}
xd = usba_hcdi_get_device_private(ph->p_usba_device);
epid = xhci_endpoint_pipe_to_epid(ph);
if (xd->xd_endpoints[epid] == NULL) {
mutex_exit(&xhcip->xhci_lock);
xhci_error(xhcip, "asked to stop intr polling on slot %d, "
"port %d, endpoint: %d, but no endpoint structure",
xd->xd_slot, xd->xd_port, epid);
return (USB_FAILURE);
}
xep = xd->xd_endpoints[epid];
xp = (xhci_pipe_t *)ph->p_hcd_private;
if (xp == NULL) {
mutex_exit(&xhcip->xhci_lock);
xhci_error(xhcip, "asked to do finish polling on slot %d, "
"port %d, endpoint: %d, but no pipe structure",
xd->xd_slot, xd->xd_port, epid);
return (USB_FAILURE);
}
xpp = &xp->xp_periodic;
xhci_endpoint_serialize(xhcip, xep);
if (xpp->xpp_poll_state == XHCI_PERIODIC_POLL_IDLE) {
mutex_exit(&xhcip->xhci_lock);
return (USB_SUCCESS);
}
if (xpp->xpp_poll_state == XHCI_PERIODIC_POLL_STOPPING) {
mutex_exit(&xhcip->xhci_lock);
return (USB_FAILURE);
}
xpp->xpp_poll_state = XHCI_PERIODIC_POLL_STOPPING;
xep->xep_state |= XHCI_ENDPOINT_QUIESCE;
ret = xhci_endpoint_quiesce(xhcip, xd, xep);
if (ret != USB_SUCCESS) {
xhci_error(xhcip, "!failed to quiesce endpoint on slot %d, "
"port %d, endpoint: %d, failed with %d.",
xd->xd_slot, xd->xd_port, epid, ret);
xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE;
cv_broadcast(&xep->xep_state_cv);
mutex_exit(&xhcip->xhci_lock);
return (ret);
}
while (list_is_empty(&xep->xep_transfers) == 0)
(void) list_remove_head(&xep->xep_transfers);
xhci_ring_skip(&xep->xep_ring);
mutex_exit(&xhcip->xhci_lock);
if ((ret = xhci_command_set_tr_dequeue(xhcip, xd, xep)) !=
USB_SUCCESS) {
xhci_error(xhcip, "!failed to reset endpoint ring on slot %d, "
"port %d, endpoint: %d, failed with %d.",
xd->xd_slot, xd->xd_port, epid, ret);
mutex_enter(&xhcip->xhci_lock);
xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE;
cv_broadcast(&xep->xep_state_cv);
mutex_exit(&xhcip->xhci_lock);
return (ret);
}
mutex_enter(&xhcip->xhci_lock);
urp = xpp->xpp_usb_req;
xpp->xpp_usb_req = NULL;
xpp->xpp_poll_state = XHCI_PERIODIC_POLL_IDLE;
xep->xep_state &= ~XHCI_ENDPOINT_PERIODIC;
mutex_exit(&xhcip->xhci_lock);
if (urp != NULL) {
usba_hcdi_cb(ph, urp, is_close == B_TRUE ?
USB_CR_PIPE_CLOSING : USB_CR_STOPPED_POLLING);
}
mutex_enter(&xhcip->xhci_lock);
xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE;
cv_broadcast(&xep->xep_state_cv);
mutex_exit(&xhcip->xhci_lock);
return (USB_SUCCESS);
}
static int
xhci_hcdi_pipe_close(usba_pipe_handle_data_t *ph, usb_flags_t usb_flags)
{
xhci_t *xhcip = xhci_hcdi_get_xhcip(ph);
xhci_pipe_t *xp;
xhci_device_t *xd;
xhci_endpoint_t *xep;
int ret;
uint_t epid;
if ((ph->p_ep.bmAttributes & USB_EP_ATTR_MASK) == USB_EP_ATTR_INTR &&
xhcip->xhci_usba.xa_intr_cb_ph != NULL) {
if ((ret = xhci_hcdi_pipe_poll_fini(ph, B_TRUE)) !=
USB_SUCCESS) {
return (ret);
}
}
mutex_enter(&xhcip->xhci_lock);
xp = (xhci_pipe_t *)ph->p_hcd_private;
VERIFY(xp != NULL);
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR)
goto remove;
xd = usba_hcdi_get_device_private(ph->p_usba_device);
epid = xhci_endpoint_pipe_to_epid(ph);
if (xd->xd_endpoints[epid] == NULL) {
mutex_exit(&xhcip->xhci_lock);
xhci_error(xhcip, "asked to do close pipe on slot %d, "
"port %d, endpoint: %d, but no endpoint structure",
xd->xd_slot, xd->xd_port, epid);
return (USB_FAILURE);
}
xep = xd->xd_endpoints[epid];
if (xp->xp_ep != NULL && xp->xp_ep->xep_num == XHCI_DEFAULT_ENDPOINT) {
xep->xep_pipe = NULL;
goto remove;
}
xhci_endpoint_timeout_cancel(xhcip, xep);
xep->xep_state |= XHCI_ENDPOINT_QUIESCE;
if ((ret = xhci_endpoint_quiesce(xhcip, xd, xep)) != USB_SUCCESS) {
xep->xep_state &=
~(XHCI_ENDPOINT_TEARDOWN | XHCI_ENDPOINT_QUIESCE);
cv_broadcast(&xep->xep_state_cv);
mutex_exit(&xhcip->xhci_lock);
xhci_error(xhcip, "asked to do close pipe on slot %d, "
"port %d, endpoint: %d, but quiesce failed %d",
xd->xd_slot, xd->xd_port, epid, ret);
return (USB_FAILURE);
}
xhci_hcdi_pipe_flush(xhcip, xep, USB_CR_PIPE_CLOSING);
if ((ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) {
xhci_hcdi_periodic_free(xhcip, xp);
}
xhci_endpoint_close(xhcip, xep);
if (xep->xep_type == USB_EP_ATTR_INTR ||
xep->xep_type == USB_EP_ATTR_ISOCH) {
if ((ret = xhci_endpoint_unconfigure(xhcip, xd, xep)) ==
USB_SUCCESS) {
xhci_endpoint_fini(xd, epid);
} else {
xhci_error(xhcip, "failed to unconfigure periodic "
"endpoint %u of device with slot %hhu and port %hd "
"(%d)",
epid, xd->xd_slot, xd->xd_port, ret);
}
}
remove:
ph->p_hcd_private = NULL;
list_remove(&xhcip->xhci_usba.xa_pipes, xp);
kmem_free(xp, sizeof (xhci_pipe_t));
mutex_exit(&xhcip->xhci_lock);
return (USB_SUCCESS);
}
static int
xhci_hcdi_pipe_reset(usba_pipe_handle_data_t *ph, usb_flags_t usb_flags)
{
xhci_t *xhcip = xhci_hcdi_get_xhcip(ph);
xhci_device_t *xd;
xhci_endpoint_t *xep;
uint_t epid;
int ret;
mutex_enter(&xhcip->xhci_lock);
if (xhcip->xhci_state & XHCI_S_ERROR) {
mutex_exit(&xhcip->xhci_lock);
return (USB_HC_HARDWARE_ERROR);
}
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
mutex_exit(&xhcip->xhci_lock);
return (USB_NOT_SUPPORTED);
}
xd = usba_hcdi_get_device_private(ph->p_usba_device);
epid = xhci_endpoint_pipe_to_epid(ph);
if (xd->xd_endpoints[epid] == NULL) {
mutex_exit(&xhcip->xhci_lock);
xhci_error(xhcip, "asked to do reset pipe on slot %d, "
"port %d, endpoint: %d, but no endpoint structure",
xd->xd_slot, xd->xd_port, epid);
return (USB_FAILURE);
}
xep = xd->xd_endpoints[epid];
xhci_endpoint_serialize(xhcip, xep);
xep->xep_state |= XHCI_ENDPOINT_QUIESCE;
ret = xhci_endpoint_quiesce(xhcip, xd, xep);
if (ret != USB_SUCCESS) {
xhci_error(xhcip, "!failed to quiesce endpoint on slot %d, "
"port %d, endpoint: %d, failed with %d.",
xd->xd_slot, xd->xd_port, epid, ret);
xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE;
cv_broadcast(&xep->xep_state_cv);
mutex_exit(&xhcip->xhci_lock);
return (ret);
}
xhci_ring_skip(&xep->xep_ring);
mutex_exit(&xhcip->xhci_lock);
if ((ret = xhci_command_set_tr_dequeue(xhcip, xd, xep)) !=
USB_SUCCESS) {
xhci_error(xhcip, "!failed to reset endpoint ring on slot %d, "
"port %d, endpoint: %d, failed setting ring dequeue with "
"%d.", xd->xd_slot, xd->xd_port, epid, ret);
mutex_enter(&xhcip->xhci_lock);
xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE;
cv_broadcast(&xep->xep_state_cv);
mutex_exit(&xhcip->xhci_lock);
return (ret);
}
mutex_enter(&xhcip->xhci_lock);
xhci_hcdi_pipe_flush(xhcip, xep, USB_CR_PIPE_RESET);
xep->xep_state &= ~(XHCI_ENDPOINT_QUIESCE | XHCI_ENDPOINT_PERIODIC);
cv_broadcast(&xep->xep_state_cv);
mutex_exit(&xhcip->xhci_lock);
return (USB_SUCCESS);
}
void
xhci_hcdi_pipe_reset_data_toggle(usba_pipe_handle_data_t *pipe_handle)
{
}
static uint64_t
xhci_hcdi_ctrl_req_to_trb(usb_ctrl_req_t *ucrp)
{
uint64_t ret = ucrp->ctrl_bmRequestType |
(ucrp->ctrl_bRequest << 8) |
((uint64_t)LE_16(ucrp->ctrl_wValue) << 16) |
((uint64_t)LE_16(ucrp->ctrl_wIndex) << 32) |
((uint64_t)LE_16(ucrp->ctrl_wLength) << 48);
return (LE_64(ret));
}
static int
xhci_hcdi_pipe_ctrl_xfer(usba_pipe_handle_data_t *ph, usb_ctrl_req_t *ucrp,
usb_flags_t usb_flags)
{
int ret, statusdir, trt;
uint_t ep;
xhci_device_t *xd;
xhci_endpoint_t *xep;
xhci_transfer_t *xt;
boolean_t datain;
xhci_t *xhcip = xhci_hcdi_get_xhcip(ph);
mutex_enter(&xhcip->xhci_lock);
if (xhcip->xhci_state & XHCI_S_ERROR) {
mutex_exit(&xhcip->xhci_lock);
return (USB_HC_HARDWARE_ERROR);
}
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
ret = xhci_root_hub_ctrl_req(xhcip, ph, ucrp);
mutex_exit(&xhcip->xhci_lock);
return (ret);
}
xd = usba_hcdi_get_device_private(ph->p_usba_device);
ep = xhci_endpoint_pipe_to_epid(ph);
if (xd->xd_endpoints[ep] == NULL) {
mutex_exit(&xhcip->xhci_lock);
xhci_error(xhcip, "asked to do control transfer on slot %d, "
"port %d, endpoint: %d, but no endpoint structure",
xd->xd_slot, xd->xd_port, ep);
return (USB_FAILURE);
}
xep = xd->xd_endpoints[ep];
if (ucrp->ctrl_bmRequestType == USB_DEV_REQ_HOST_TO_DEV &&
ucrp->ctrl_bRequest == USB_REQ_SET_ADDRESS) {
mutex_exit(&xhcip->xhci_lock);
usba_hcdi_cb(ph, (usb_opaque_t)ucrp, USB_CR_NOT_SUPPORTED);
return (USB_SUCCESS);
}
mutex_exit(&xhcip->xhci_lock);
xt = xhci_transfer_alloc(xhcip, xep, ucrp->ctrl_wLength, 2, usb_flags);
if (xt == NULL) {
return (USB_NO_RESOURCES);
}
xt->xt_usba_req = (usb_opaque_t)ucrp;
xt->xt_timeout = ucrp->ctrl_timeout;
if (xt->xt_timeout == 0) {
xt->xt_timeout = HCDI_DEFAULT_TIMEOUT;
}
if (ucrp->ctrl_wLength > 0) {
if ((ucrp->ctrl_bmRequestType & USB_DEV_REQ_DEV_TO_HOST) != 0) {
trt = XHCI_TRB_TRT_IN;
datain = B_TRUE;
statusdir = 0;
} else {
trt = XHCI_TRB_TRT_OUT;
datain = B_FALSE;
statusdir = XHCI_TRB_DIR_IN;
xhci_transfer_copy(xt, ucrp->ctrl_data->b_rptr,
ucrp->ctrl_wLength, B_FALSE);
if (xhci_transfer_sync(xhcip, xt,
DDI_DMA_SYNC_FORDEV) != DDI_FM_OK) {
xhci_transfer_free(xhcip, xt);
xhci_error(xhcip, "failed to synchronize ctrl "
"transfer DMA memory on endpoint %u of "
"device on slot %d and port %d: resetting "
"device", xep->xep_num, xd->xd_slot,
xd->xd_port);
xhci_fm_runtime_reset(xhcip);
return (USB_HC_HARDWARE_ERROR);
}
}
} else {
trt = 0;
datain = B_FALSE;
statusdir = XHCI_TRB_DIR_IN;
}
xt->xt_trbs[0].trb_addr = xhci_hcdi_ctrl_req_to_trb(ucrp);
xt->xt_trbs[0].trb_status = LE_32(XHCI_TRB_LEN(8) | XHCI_TRB_INTR(0));
xt->xt_trbs[0].trb_flags = LE_32(trt | XHCI_TRB_IDT |
XHCI_TRB_TYPE_SETUP);
if (ucrp->ctrl_wLength > 0)
xhci_transfer_trb_fill_data(xep, xt, 1, datain);
xt->xt_trbs[xt->xt_ntrbs - 1].trb_addr = 0;
xt->xt_trbs[xt->xt_ntrbs - 1].trb_status = LE_32(XHCI_TRB_INTR(0));
xt->xt_trbs[xt->xt_ntrbs - 1].trb_flags = LE_32(XHCI_TRB_TYPE_STATUS |
XHCI_TRB_IOC | statusdir);
mutex_enter(&xhcip->xhci_lock);
if (xhci_endpoint_schedule(xhcip, xd, xep, xt, B_TRUE) != 0) {
xhci_transfer_free(xhcip, xt);
mutex_exit(&xhcip->xhci_lock);
return (USB_NO_RESOURCES);
}
mutex_exit(&xhcip->xhci_lock);
return (USB_SUCCESS);
}
static int
xhci_hcdi_bulk_transfer_size(usba_device_t *ud, size_t *sizep)
{
if (sizep != NULL)
*sizep = XHCI_MAX_TRANSFER;
return (USB_SUCCESS);
}
static int
xhci_hcdi_pipe_bulk_xfer(usba_pipe_handle_data_t *ph, usb_bulk_req_t *ubrp,
usb_flags_t usb_flags)
{
uint_t epid;
xhci_device_t *xd;
xhci_endpoint_t *xep;
xhci_transfer_t *xt;
boolean_t datain;
xhci_t *xhcip = xhci_hcdi_get_xhcip(ph);
mutex_enter(&xhcip->xhci_lock);
if (xhcip->xhci_state & XHCI_S_ERROR) {
mutex_exit(&xhcip->xhci_lock);
return (USB_HC_HARDWARE_ERROR);
}
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
mutex_exit(&xhcip->xhci_lock);
return (USB_NOT_SUPPORTED);
}
xd = usba_hcdi_get_device_private(ph->p_usba_device);
epid = xhci_endpoint_pipe_to_epid(ph);
if (xd->xd_endpoints[epid] == NULL) {
mutex_exit(&xhcip->xhci_lock);
xhci_error(xhcip, "asked to do bulk transfer on slot %d, "
"port %d, endpoint: %d, but no endpoint structure",
xd->xd_slot, xd->xd_port, epid);
return (USB_FAILURE);
}
xep = xd->xd_endpoints[epid];
mutex_exit(&xhcip->xhci_lock);
if ((ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) {
datain = B_TRUE;
} else {
datain = B_FALSE;
}
xt = xhci_transfer_alloc(xhcip, xep, ubrp->bulk_len, 0, usb_flags);
if (xt == NULL) {
return (USB_NO_RESOURCES);
}
xt->xt_usba_req = (usb_opaque_t)ubrp;
xt->xt_timeout = ubrp->bulk_timeout;
if (xt->xt_timeout == 0) {
xt->xt_timeout = HCDI_DEFAULT_TIMEOUT;
}
if (ubrp->bulk_len > 0 && datain == B_FALSE) {
xhci_transfer_copy(xt, ubrp->bulk_data->b_rptr, ubrp->bulk_len,
B_FALSE);
if (xhci_transfer_sync(xhcip, xt, DDI_DMA_SYNC_FORDEV) !=
DDI_FM_OK) {
xhci_transfer_free(xhcip, xt);
xhci_error(xhcip, "failed to synchronize bulk "
"transfer DMA memory on endpoint %u of "
"device on slot %d and port %d: resetting "
"device", xep->xep_num, xd->xd_slot,
xd->xd_port);
xhci_fm_runtime_reset(xhcip);
return (USB_HC_HARDWARE_ERROR);
}
}
xhci_transfer_trb_fill_data(xep, xt, 0, datain);
mutex_enter(&xhcip->xhci_lock);
if (xhci_endpoint_schedule(xhcip, xd, xep, xt, B_TRUE) != 0) {
xhci_transfer_free(xhcip, xt);
mutex_exit(&xhcip->xhci_lock);
return (USB_NO_RESOURCES);
}
mutex_exit(&xhcip->xhci_lock);
return (USB_SUCCESS);
}
static void
xhci_hcdi_isoc_transfer_fill(xhci_device_t *xd, xhci_endpoint_t *xep,
xhci_transfer_t *xt, usb_isoc_req_t *usrp)
{
int i;
uintptr_t buf;
buf = xt->xt_buffer.xdb_cookies[0].dmac_laddress;
for (i = 0; i < usrp->isoc_pkts_count; i++) {
int flags;
uint_t tbc, tlbpc;
ushort_t len = usrp->isoc_pkt_descr[i].isoc_pkt_length;
xhci_trb_t *trb = &xt->xt_trbs[i];
trb->trb_addr = LE_64(buf);
trb->trb_status = LE_32(XHCI_TRB_LEN(len) | XHCI_TRB_TDREM(0) |
XHCI_TRB_INTR(0));
flags = XHCI_TRB_SIA | XHCI_TRB_ISP | XHCI_TRB_SET_FRAME(0);
flags |= XHCI_TRB_TYPE_ISOCH;
if (i + 1 == usrp->isoc_pkts_count)
flags |= XHCI_TRB_IOC;
xhci_transfer_calculate_isoc(xd, xep, len, &tbc, &tlbpc);
flags |= XHCI_TRB_SET_TBC(tbc);
flags |= XHCI_TRB_SET_TLBPC(tlbpc);
trb->trb_flags = LE_32(flags);
buf += len;
xt->xt_isoc[i].isoc_pkt_length = len;
xt->xt_isoc[i].isoc_pkt_actual_length = len;
xt->xt_isoc[i].isoc_pkt_status = USB_CR_OK;
}
}
static int
xhci_hcdi_periodic_init(xhci_t *xhcip, usba_pipe_handle_data_t *ph,
usb_opaque_t usb_req, size_t len, int usb_flags)
{
int i, ret;
uint_t epid;
xhci_device_t *xd;
xhci_endpoint_t *xep;
xhci_pipe_t *xp;
xhci_periodic_pipe_t *xpp;
mutex_enter(&xhcip->xhci_lock);
if (xhcip->xhci_state & XHCI_S_ERROR) {
mutex_exit(&xhcip->xhci_lock);
return (USB_HC_HARDWARE_ERROR);
}
xd = usba_hcdi_get_device_private(ph->p_usba_device);
epid = xhci_endpoint_pipe_to_epid(ph);
if (xd->xd_endpoints[epid] == NULL) {
xhci_error(xhcip, "asked to do periodic transfer on slot %d, "
"port %d, endpoint: %d, but no endpoint structure",
xd->xd_slot, xd->xd_port, epid);
mutex_exit(&xhcip->xhci_lock);
return (USB_FAILURE);
}
xep = xd->xd_endpoints[epid];
xp = (xhci_pipe_t *)ph->p_hcd_private;
if (xp == NULL) {
xhci_error(xhcip, "asked to do periodic transfer on slot %d, "
"port %d, endpoint: %d, but no pipe structure",
xd->xd_slot, xd->xd_port, epid);
mutex_exit(&xhcip->xhci_lock);
return (USB_FAILURE);
}
xpp = &xp->xp_periodic;
if (xpp->xpp_usb_req != NULL) {
mutex_exit(&xhcip->xhci_lock);
return (USB_BUSY);
}
if (xpp->xpp_tsize > 0 && xpp->xpp_tsize < len) {
mutex_exit(&xhcip->xhci_lock);
return (USB_INVALID_REQUEST);
}
if (xpp->xpp_tsize == 0) {
int ntrbs;
int ntransfers;
if (xep->xep_type == USB_EP_ATTR_INTR) {
ntrbs = 0;
ntransfers = XHCI_INTR_IN_NTRANSFERS;
} else {
usb_isoc_req_t *usrp;
ASSERT(xep->xep_type == USB_EP_ATTR_ISOCH);
usrp = (usb_isoc_req_t *)usb_req;
ntrbs = usrp->isoc_pkts_count;
ntransfers = XHCI_ISOC_IN_NTRANSFERS;
}
xpp->xpp_tsize = len;
xpp->xpp_ntransfers = ntransfers;
for (i = 0; i < xpp->xpp_ntransfers; i++) {
xhci_transfer_t *xt = xhci_transfer_alloc(xhcip, xep,
len, ntrbs, usb_flags);
if (xt == NULL) {
xhci_hcdi_periodic_free(xhcip, xp);
mutex_exit(&xhcip->xhci_lock);
return (USB_NO_RESOURCES);
}
if (xep->xep_type == USB_EP_ATTR_INTR) {
xhci_transfer_trb_fill_data(xep, xt, 0, B_TRUE);
} else {
usb_isoc_req_t *usrp;
usrp = (usb_isoc_req_t *)usb_req;
xhci_hcdi_isoc_transfer_fill(xd, xep, xt, usrp);
xt->xt_data_tohost = B_TRUE;
}
xpp->xpp_transfers[i] = xt;
}
}
xep->xep_state |= XHCI_ENDPOINT_PERIODIC;
for (i = 0; i < xpp->xpp_ntransfers; i++) {
int ret;
ret = xhci_endpoint_schedule(xhcip, xd, xep,
xpp->xpp_transfers[i], B_FALSE);
if (ret != 0) {
(void) xhci_ring_reset(xhcip, &xep->xep_ring);
xep->xep_state &= ~XHCI_ENDPOINT_PERIODIC;
mutex_exit(&xhcip->xhci_lock);
return (ret);
}
}
xpp->xpp_usb_req = usb_req;
xpp->xpp_poll_state = XHCI_PERIODIC_POLL_ACTIVE;
ret = xhci_endpoint_ring(xhcip, xd, xep);
mutex_exit(&xhcip->xhci_lock);
return (ret);
}
static int
xhci_hcdi_intr_oneshot(xhci_t *xhcip, usba_pipe_handle_data_t *ph,
usb_intr_req_t *uirp, usb_flags_t usb_flags)
{
uint_t epid;
xhci_device_t *xd;
xhci_endpoint_t *xep;
xhci_transfer_t *xt;
boolean_t datain;
mblk_t *mp = NULL;
mutex_enter(&xhcip->xhci_lock);
if (xhcip->xhci_state & XHCI_S_ERROR) {
mutex_exit(&xhcip->xhci_lock);
return (USB_HC_HARDWARE_ERROR);
}
xd = usba_hcdi_get_device_private(ph->p_usba_device);
epid = xhci_endpoint_pipe_to_epid(ph);
if (xd->xd_endpoints[epid] == NULL) {
xhci_error(xhcip, "asked to do interrupt transfer on slot %d, "
"port %d, endpoint: %d, but no endpoint structure",
xd->xd_slot, xd->xd_port, epid);
mutex_exit(&xhcip->xhci_lock);
return (USB_FAILURE);
}
xep = xd->xd_endpoints[epid];
mutex_exit(&xhcip->xhci_lock);
if ((ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) {
datain = B_TRUE;
} else {
datain = B_FALSE;
}
xt = xhci_transfer_alloc(xhcip, xep, uirp->intr_len, 0, usb_flags);
if (xt == NULL) {
return (USB_NO_RESOURCES);
}
xt->xt_usba_req = (usb_opaque_t)uirp;
xt->xt_timeout = uirp->intr_timeout;
if (xt->xt_timeout == 0) {
xt->xt_timeout = HCDI_DEFAULT_TIMEOUT;
}
if (uirp->intr_len > 0 && datain == B_TRUE && uirp->intr_data == NULL) {
if (usb_flags & USB_FLAGS_SLEEP) {
mp = allocb_wait(uirp->intr_len, BPRI_LO, STR_NOSIG,
NULL);
} else {
mp = allocb(uirp->intr_len, 0);
}
if (mp == NULL) {
xhci_transfer_free(xhcip, xt);
mutex_exit(&xhcip->xhci_lock);
return (USB_NO_RESOURCES);
}
uirp->intr_data = mp;
}
if (uirp->intr_len > 0 && datain == B_FALSE) {
xhci_transfer_copy(xt, uirp->intr_data->b_rptr, uirp->intr_len,
B_FALSE);
if (xhci_transfer_sync(xhcip, xt, DDI_DMA_SYNC_FORDEV) !=
DDI_FM_OK) {
xhci_transfer_free(xhcip, xt);
xhci_error(xhcip, "failed to synchronize interrupt "
"transfer DMA memory on endpoint %u of "
"device on slot %d and port %d: resetting "
"device", xep->xep_num, xd->xd_slot,
xd->xd_port);
xhci_fm_runtime_reset(xhcip);
return (USB_HC_HARDWARE_ERROR);
}
}
xhci_transfer_trb_fill_data(xep, xt, 0, datain);
mutex_enter(&xhcip->xhci_lock);
if (xhci_endpoint_schedule(xhcip, xd, xep, xt, B_TRUE) != 0) {
if (mp != NULL) {
uirp->intr_data = NULL;
freemsg(mp);
}
xhci_transfer_free(xhcip, xt);
mutex_exit(&xhcip->xhci_lock);
return (USB_NO_RESOURCES);
}
mutex_exit(&xhcip->xhci_lock);
return (USB_SUCCESS);
}
static int
xhci_hcdi_pipe_intr_xfer(usba_pipe_handle_data_t *ph, usb_intr_req_t *uirp,
usb_flags_t usb_flags)
{
int ret;
xhci_t *xhcip = xhci_hcdi_get_xhcip(ph);
if ((ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) {
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
ret = xhci_root_hub_intr_root_enable(xhcip, ph, uirp);
} else if (uirp->intr_attributes & USB_ATTRS_ONE_XFER) {
ret = xhci_hcdi_intr_oneshot(xhcip, ph, uirp,
usb_flags);
} else {
ret = xhci_hcdi_periodic_init(xhcip, ph,
(usb_opaque_t)uirp, uirp->intr_len, usb_flags);
}
} else {
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
return (USB_NOT_SUPPORTED);
}
ret = xhci_hcdi_intr_oneshot(xhcip, ph, uirp, usb_flags);
}
return (ret);
}
static int
xhci_hcdi_pipe_stop_intr_polling(usba_pipe_handle_data_t *ph,
usb_flags_t usb_flags)
{
return (xhci_hcdi_pipe_poll_fini(ph, B_FALSE));
}
static int
xhci_hcdi_isoc_periodic(xhci_t *xhcip, usba_pipe_handle_data_t *ph,
usb_isoc_req_t *usrp, usb_flags_t usb_flags)
{
int i;
size_t count;
count = 0;
for (i = 0; i < usrp->isoc_pkts_count; i++) {
count += usrp->isoc_pkt_descr[i].isoc_pkt_length;
}
return (xhci_hcdi_periodic_init(xhcip, ph, (usb_opaque_t)usrp, count,
usb_flags));
}
static int
xhci_hcdi_isoc_oneshot(xhci_t *xhcip, usba_pipe_handle_data_t *ph,
usb_isoc_req_t *usrp, usb_flags_t usb_flags)
{
int i, ret;
uint_t epid;
size_t count, mblen;
xhci_device_t *xd;
xhci_endpoint_t *xep;
xhci_transfer_t *xt;
count = 0;
for (i = 0; i < usrp->isoc_pkts_count; i++) {
count += usrp->isoc_pkt_descr[i].isoc_pkt_length;
}
mblen = MBLKL(usrp->isoc_data);
if (count != mblen) {
return (USB_INVALID_ARGS);
}
mutex_enter(&xhcip->xhci_lock);
if (xhcip->xhci_state & XHCI_S_ERROR) {
mutex_exit(&xhcip->xhci_lock);
return (USB_HC_HARDWARE_ERROR);
}
xd = usba_hcdi_get_device_private(ph->p_usba_device);
epid = xhci_endpoint_pipe_to_epid(ph);
if (xd->xd_endpoints[epid] == NULL) {
xhci_error(xhcip, "asked to do isochronous transfer on slot "
"%d, port %d, endpoint: %d, but no endpoint structure",
xd->xd_slot, xd->xd_port, epid);
mutex_exit(&xhcip->xhci_lock);
return (USB_FAILURE);
}
xep = xd->xd_endpoints[epid];
mutex_exit(&xhcip->xhci_lock);
xt = xhci_transfer_alloc(xhcip, xep, mblen, usrp->isoc_pkts_count,
usb_flags);
if (xt == NULL) {
return (USB_NO_RESOURCES);
}
xt->xt_usba_req = (usb_opaque_t)usrp;
xt->xt_timeout = HCDI_DEFAULT_TIMEOUT;
xhci_transfer_copy(xt, usrp->isoc_data->b_rptr, mblen, B_FALSE);
if (xhci_transfer_sync(xhcip, xt, DDI_DMA_SYNC_FORDEV) != DDI_FM_OK) {
xhci_transfer_free(xhcip, xt);
xhci_error(xhcip, "failed to synchronize isochronous "
"transfer DMA memory on endpoint %u of "
"device on slot %d and port %d: resetting "
"device", xep->xep_num, xd->xd_slot,
xd->xd_port);
xhci_fm_runtime_reset(xhcip);
return (USB_HC_HARDWARE_ERROR);
}
xhci_hcdi_isoc_transfer_fill(xd, xep, xt, usrp);
mutex_enter(&xhcip->xhci_lock);
ret = xhci_endpoint_schedule(xhcip, xd, xep, xt, B_TRUE);
mutex_exit(&xhcip->xhci_lock);
return (ret);
}
static int
xhci_hcdi_pipe_isoc_xfer(usba_pipe_handle_data_t *ph, usb_isoc_req_t *usrp,
usb_flags_t usb_flags)
{
int ret;
xhci_t *xhcip;
xhcip = xhci_hcdi_get_xhcip(ph);
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
return (USB_NOT_SUPPORTED);
}
if (!(usrp->isoc_attributes & USB_ATTRS_ISOC_XFER_ASAP)) {
return (USB_NOT_SUPPORTED);
}
if ((ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) {
ret = xhci_hcdi_isoc_periodic(xhcip, ph, usrp, usb_flags);
} else {
ret = xhci_hcdi_isoc_oneshot(xhcip, ph, usrp, usb_flags);
}
return (ret);
}
static int
xhci_hcdi_pipe_stop_isoc_polling(usba_pipe_handle_data_t *ph,
usb_flags_t usb_flags)
{
return (xhci_hcdi_pipe_poll_fini(ph, B_FALSE));
}
static int
xhci_hcdi_get_current_frame_number(usba_device_t *usba_device,
usb_frame_number_t *frame_number)
{
return (USB_FAILURE);
}
static int
xhci_hcdi_get_max_isoc_pkts(usba_device_t *usba_device,
uint_t *max_isoc_pkts_per_request)
{
*max_isoc_pkts_per_request = XHCI_ISOC_MAX_TRB;
return (USB_SUCCESS);
}
static void
xhci_hcdi_device_free(xhci_device_t *xd)
{
xhci_dma_free(&xd->xd_ictx);
xhci_dma_free(&xd->xd_octx);
mutex_destroy(&xd->xd_imtx);
kmem_free(xd, sizeof (xhci_device_t));
}
static void
xhci_hcdi_device_route(usba_device_t *ud, uint32_t *routep, uint32_t *root_port)
{
uint32_t route = 0;
usba_device_t *hub = ud->usb_parent_hub;
usba_device_t *port_dev = ud;
ASSERT(hub != NULL);
while (hub->usb_parent_hub != NULL) {
uint32_t p;
p = port_dev->usb_port;
if (p > 15)
p = 15;
route <<= 4;
route |= p & 0xf;
port_dev = hub;
hub = hub->usb_parent_hub;
}
ASSERT(port_dev->usb_parent_hub == hub);
*root_port = port_dev->usb_port;
*routep = XHCI_ROUTE_MASK(route);
}
static uint32_t
xhci_hcdi_device_tt(usba_device_t *ud)
{
uint32_t ret;
xhci_device_t *xd;
if (ud->usb_port_status >= USBA_HIGH_SPEED_DEV)
return (0);
if (ud->usb_hs_hub_usba_dev == NULL)
return (0);
ASSERT(ud->usb_hs_hub_usba_dev != NULL);
ASSERT(ud->usb_hs_hub_usba_dev->usb_parent_hub != NULL);
xd = usba_hcdi_get_device_private(ud->usb_hs_hub_usba_dev);
ASSERT(xd != NULL);
ret = XHCI_SCTX_SET_TT_HUB_SID(xd->xd_slot);
ret |= XHCI_SCTX_SET_TT_PORT_NUM(ud->usb_hs_hub_usba_dev->usb_port);
return (ret);
}
static int
xhci_hcdi_device_init(usba_device_t *ud, usb_port_t port, void **hcdpp)
{
int ret, i;
xhci_device_t *xd;
ddi_device_acc_attr_t acc;
ddi_dma_attr_t attr;
xhci_t *xhcip = xhci_hcdi_get_xhcip_from_dev(ud);
size_t isize, osize, incr;
uint32_t route, rp, info, info2, tt;
xd = kmem_zalloc(sizeof (xhci_device_t), KM_SLEEP);
xd->xd_port = port;
xd->xd_usbdev = ud;
mutex_init(&xd->xd_imtx, NULL, MUTEX_DRIVER,
(void *)(uintptr_t)xhcip->xhci_intr_pri);
if (xhcip->xhci_caps.xcap_flags & XCAP_CSZ) {
incr = 64;
osize = XHCI_DEVICE_CONTEXT_64;
isize = XHCI_DEVICE_CONTEXT_64 + incr;
} else {
incr = 32;
osize = XHCI_DEVICE_CONTEXT_32;
isize = XHCI_DEVICE_CONTEXT_32 + incr;
}
xhci_dma_acc_attr(xhcip, &acc);
xhci_dma_dma_attr(xhcip, &attr);
if (xhci_dma_alloc(xhcip, &xd->xd_ictx, &attr, &acc, B_TRUE,
isize, B_FALSE) == B_FALSE) {
xhci_hcdi_device_free(xd);
return (USB_NO_RESOURCES);
}
xd->xd_input = (xhci_input_context_t *)xd->xd_ictx.xdb_va;
xd->xd_slotin = (xhci_slot_context_t *)(xd->xd_ictx.xdb_va + incr);
for (i = 0; i < XHCI_NUM_ENDPOINTS; i++) {
xd->xd_endin[i] =
(xhci_endpoint_context_t *)(xd->xd_ictx.xdb_va +
(i + 2) * incr);
}
if (xhci_dma_alloc(xhcip, &xd->xd_octx, &attr, &acc, B_TRUE,
osize, B_FALSE) == B_FALSE) {
xhci_hcdi_device_free(xd);
return (USB_NO_RESOURCES);
}
xd->xd_slotout = (xhci_slot_context_t *)xd->xd_octx.xdb_va;
for (i = 0; i < XHCI_NUM_ENDPOINTS; i++) {
xd->xd_endout[i] =
(xhci_endpoint_context_t *)(xd->xd_octx.xdb_va +
(i + 1) * incr);
}
ret = xhci_command_enable_slot(xhcip, &xd->xd_slot);
if (ret != USB_SUCCESS) {
xhci_hcdi_device_free(xd);
return (ret);
}
xd->xd_input->xic_add_flags = LE_32(XHCI_INCTX_MASK_DCI(0) |
XHCI_INCTX_MASK_DCI(1));
xhci_hcdi_device_route(ud, &route, &rp);
info = XHCI_SCTX_SET_ROUTE(route) | XHCI_SCTX_SET_DCI(1);
switch (ud->usb_port_status) {
case USBA_LOW_SPEED_DEV:
info |= XHCI_SCTX_SET_SPEED(XHCI_SPEED_LOW);
break;
case USBA_HIGH_SPEED_DEV:
info |= XHCI_SCTX_SET_SPEED(XHCI_SPEED_HIGH);
break;
case USBA_FULL_SPEED_DEV:
info |= XHCI_SCTX_SET_SPEED(XHCI_SPEED_FULL);
break;
case USBA_SUPER_SPEED_DEV:
default:
info |= XHCI_SCTX_SET_SPEED(XHCI_SPEED_SUPER);
break;
}
info2 = XHCI_SCTX_SET_RHPORT(rp);
tt = XHCI_SCTX_SET_IRQ_TARGET(0);
tt |= xhci_hcdi_device_tt(ud);
xd->xd_slotin->xsc_info = LE_32(info);
xd->xd_slotin->xsc_info2 = LE_32(info2);
xd->xd_slotin->xsc_tt = LE_32(tt);
if ((ret = xhci_endpoint_init(xhcip, xd, NULL)) != 0) {
(void) xhci_command_disable_slot(xhcip, xd->xd_slot);
xhci_hcdi_device_free(xd);
return (USB_HC_HARDWARE_ERROR);
}
if (xhci_context_slot_output_init(xhcip, xd) != B_TRUE) {
(void) xhci_command_disable_slot(xhcip, xd->xd_slot);
xhci_endpoint_fini(xd, 0);
xhci_hcdi_device_free(xd);
return (USB_HC_HARDWARE_ERROR);
}
if ((ret = xhci_command_set_address(xhcip, xd, B_TRUE)) != 0) {
(void) xhci_command_disable_slot(xhcip, xd->xd_slot);
xhci_context_slot_output_fini(xhcip, xd);
xhci_endpoint_fini(xd, 0);
xhci_hcdi_device_free(xd);
return (ret);
}
mutex_enter(&xhcip->xhci_lock);
list_insert_tail(&xhcip->xhci_usba.xa_devices, xd);
mutex_exit(&xhcip->xhci_lock);
*hcdpp = xd;
return (ret);
}
static void
xhci_hcdi_device_fini(usba_device_t *ud, void *hcdp)
{
int ret;
xhci_endpoint_t *xep;
xhci_device_t *xd;
xhci_t *xhcip;
if (hcdp == NULL)
return;
xd = hcdp;
xhcip = xhci_hcdi_get_xhcip_from_dev(ud);
xep = xd->xd_endpoints[XHCI_DEFAULT_ENDPOINT];
mutex_enter(&xhcip->xhci_lock);
xep->xep_state |= XHCI_ENDPOINT_TEARDOWN;
mutex_exit(&xhcip->xhci_lock);
(void) untimeout(xep->xep_timeout);
ret = xhci_command_disable_slot(xhcip, xd->xd_slot);
if (ret != USB_SUCCESS) {
xhci_error(xhcip, "failed to disable slot %d: %d",
xd->xd_slot, ret);
return;
}
xhci_context_slot_output_fini(xhcip, xd);
for (uint_t n = 0; n < XHCI_NUM_ENDPOINTS; n++) {
if (xd->xd_endpoints[n] != NULL) {
xhci_endpoint_fini(xd, n);
}
}
mutex_enter(&xhcip->xhci_lock);
list_remove(&xhcip->xhci_usba.xa_devices, xd);
mutex_exit(&xhcip->xhci_lock);
xhci_hcdi_device_free(xd);
}
static int
xhci_hcdi_device_address(usba_device_t *ud)
{
int ret;
xhci_t *xhcip = xhci_hcdi_get_xhcip_from_dev(ud);
xhci_device_t *xd = usba_hcdi_get_device_private(ud);
xhci_endpoint_t *xep;
mutex_enter(&xhcip->xhci_lock);
if (xd->xd_addressed == B_TRUE) {
mutex_exit(&xhcip->xhci_lock);
return (USB_SUCCESS);
}
ASSERT(xd->xd_addressed == B_FALSE);
xd->xd_addressed = B_TRUE;
VERIFY3P(xd->xd_endpoints[XHCI_DEFAULT_ENDPOINT], !=, NULL);
xep = xd->xd_endpoints[XHCI_DEFAULT_ENDPOINT];
mutex_exit(&xhcip->xhci_lock);
if ((ret = xhci_endpoint_setup_default_context(xhcip, xd, xep)) != 0) {
ASSERT(ret == EIO);
return (USB_HC_HARDWARE_ERROR);
}
ret = xhci_command_set_address(xhcip, xd, B_FALSE);
if (ret != USB_SUCCESS) {
mutex_enter(&xhcip->xhci_lock);
xd->xd_addressed = B_FALSE;
mutex_exit(&xhcip->xhci_lock);
}
return (ret);
}
static int
xhci_hcdi_hub_update(usba_device_t *ud, uint8_t nports, uint8_t tt)
{
int ret;
xhci_t *xhcip = xhci_hcdi_get_xhcip_from_dev(ud);
xhci_device_t *xd = usba_hcdi_get_device_private(ud);
if (xd == NULL)
return (USB_FAILURE);
if (ud->usb_hubdi == NULL) {
return (USB_FAILURE);
}
mutex_enter(&xd->xd_imtx);
xd->xd_slotin->xsc_info |= LE_32(XHCI_SCTX_SET_HUB(1));
xd->xd_slotin->xsc_info2 |= LE_32(XHCI_SCTX_SET_NPORTS(nports));
if (ud->usb_port_status == USBA_HIGH_SPEED_DEV)
xd->xd_slotin->xsc_tt |= LE_32(XHCI_SCTX_SET_TT_THINK_TIME(tt));
xd->xd_input->xic_drop_flags = LE_32(0);
xd->xd_input->xic_add_flags = LE_32(XHCI_INCTX_MASK_DCI(0));
ret = xhci_command_evaluate_context(xhcip, xd);
mutex_exit(&xd->xd_imtx);
return (ret);
}
void
xhci_hcd_fini(xhci_t *xhcip)
{
usba_hcdi_unregister(xhcip->xhci_dip);
usba_free_hcdi_ops(xhcip->xhci_usba.xa_ops);
list_destroy(&xhcip->xhci_usba.xa_pipes);
list_destroy(&xhcip->xhci_usba.xa_devices);
}
int
xhci_hcd_init(xhci_t *xhcip)
{
usba_hcdi_register_args_t hreg;
usba_hcdi_ops_t *ops;
ops = usba_alloc_hcdi_ops();
VERIFY(ops != NULL);
ops->usba_hcdi_ops_version = HCDI_OPS_VERSION;
ops->usba_hcdi_dip = xhcip->xhci_dip;
ops->usba_hcdi_pm_support = xhci_hcdi_pm_support;
ops->usba_hcdi_pipe_open = xhci_hcdi_pipe_open;
ops->usba_hcdi_pipe_close = xhci_hcdi_pipe_close;
ops->usba_hcdi_pipe_reset = xhci_hcdi_pipe_reset;
ops->usba_hcdi_pipe_reset_data_toggle =
xhci_hcdi_pipe_reset_data_toggle;
ops->usba_hcdi_pipe_ctrl_xfer = xhci_hcdi_pipe_ctrl_xfer;
ops->usba_hcdi_bulk_transfer_size = xhci_hcdi_bulk_transfer_size;
ops->usba_hcdi_pipe_bulk_xfer = xhci_hcdi_pipe_bulk_xfer;
ops->usba_hcdi_pipe_intr_xfer = xhci_hcdi_pipe_intr_xfer;
ops->usba_hcdi_pipe_stop_intr_polling =
xhci_hcdi_pipe_stop_intr_polling;
ops->usba_hcdi_pipe_isoc_xfer = xhci_hcdi_pipe_isoc_xfer;
ops->usba_hcdi_pipe_stop_isoc_polling =
xhci_hcdi_pipe_stop_isoc_polling;
ops->usba_hcdi_get_current_frame_number =
xhci_hcdi_get_current_frame_number;
ops->usba_hcdi_get_max_isoc_pkts = xhci_hcdi_get_max_isoc_pkts;
ops->usba_hcdi_console_input_init = xhci_hcdi_console_input_init;
ops->usba_hcdi_console_input_fini = xhci_hcdi_console_input_fini;
ops->usba_hcdi_console_input_enter = xhci_hcdi_console_input_enter;
ops->usba_hcdi_console_read = xhci_hcdi_console_read;
ops->usba_hcdi_console_input_exit = xhci_hcdi_console_input_exit;
ops->usba_hcdi_console_output_init = xhci_hcdi_console_output_init;
ops->usba_hcdi_console_output_fini = xhci_hcdi_console_output_fini;
ops->usba_hcdi_console_output_enter = xhci_hcdi_console_output_enter;
ops->usba_hcdi_console_write = xhci_hcdi_console_write;
ops->usba_hcdi_console_output_exit = xhci_hcdi_console_output_exit;
ops->usba_hcdi_device_init = xhci_hcdi_device_init;
ops->usba_hcdi_device_fini = xhci_hcdi_device_fini;
ops->usba_hcdi_device_address = xhci_hcdi_device_address;
ops->usba_hcdi_hub_update = xhci_hcdi_hub_update;
hreg.usba_hcdi_register_version = HCDI_REGISTER_VERSION;
hreg.usba_hcdi_register_dip = xhcip->xhci_dip;
hreg.usba_hcdi_register_ops = ops;
xhci_dma_dma_attr(xhcip, &xhcip->xhci_usba.xa_dma_attr);
hreg.usba_hcdi_register_dma_attr = &xhcip->xhci_usba.xa_dma_attr;
hreg.usba_hcdi_register_iblock_cookie =
(ddi_iblock_cookie_t)(uintptr_t)xhcip->xhci_intr_pri;
if (usba_hcdi_register(&hreg, 0) != DDI_SUCCESS) {
usba_free_hcdi_ops(ops);
return (DDI_FAILURE);
}
xhcip->xhci_usba.xa_ops = ops;
list_create(&xhcip->xhci_usba.xa_devices, sizeof (xhci_device_t),
offsetof(xhci_device_t, xd_link));
list_create(&xhcip->xhci_usba.xa_pipes, sizeof (xhci_pipe_t),
offsetof(xhci_pipe_t, xp_link));
return (DDI_SUCCESS);
}