#include <sys/usb/hcd/xhci/xhci.h>
int
xhci_check_dma_handle(xhci_t *xhcip, xhci_dma_buffer_t *xdb)
{
ddi_fm_error_t de;
if (!DDI_FM_DMA_ERR_CAP(xhcip->xhci_fm_caps))
return (0);
ddi_fm_dma_err_get(xdb->xdb_dma_handle, &de, DDI_FME_VERSION);
return (de.fme_status);
}
void
xhci_dma_acc_attr(xhci_t *xhcip, ddi_device_acc_attr_t *accp)
{
accp->devacc_attr_version = DDI_DEVICE_ATTR_V0;
accp->devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
accp->devacc_attr_dataorder = DDI_STRICTORDER_ACC;
if (DDI_FM_DMA_ERR_CAP(xhcip->xhci_fm_caps)) {
accp->devacc_attr_access = DDI_FLAGERR_ACC;
} else {
accp->devacc_attr_access = DDI_DEFAULT_ACC;
}
}
void
xhci_dma_transfer_attr(xhci_t *xhcip, ddi_dma_attr_t *attrp, uint_t sgl)
{
VERIFY3U(sgl, >, 0);
VERIFY3U(sgl, <=, XHCI_TRANSFER_DMA_SGL);
attrp->dma_attr_version = DMA_ATTR_V0;
attrp->dma_attr_addr_lo = 0x0;
if (xhcip->xhci_caps.xcap_flags & XCAP_AC64) {
attrp->dma_attr_addr_hi = UINT64_MAX;
} else {
attrp->dma_attr_addr_hi = UINT32_MAX;
}
attrp->dma_attr_count_max = XHCI_TRB_MAX_TRANSFER;
attrp->dma_attr_align = XHCI_DMA_ALIGN;
attrp->dma_attr_seg = XHCI_TRB_MAX_TRANSFER - 1;
attrp->dma_attr_burstsizes = 0xfff;
attrp->dma_attr_minxfer = 0x1;
attrp->dma_attr_maxxfer = UINT32_MAX;
attrp->dma_attr_sgllen = sgl;
attrp->dma_attr_granular = 1;
if (DDI_FM_DMA_ERR_CAP(xhcip->xhci_fm_caps)) {
attrp->dma_attr_flags = DDI_DMA_FLAGERR;
} else {
attrp->dma_attr_flags = 0;
}
}
void
xhci_dma_dma_attr(xhci_t *xhcip, ddi_dma_attr_t *attrp)
{
xhci_dma_transfer_attr(xhcip, attrp, XHCI_DEF_DMA_SGL);
attrp->dma_attr_count_max = xhcip->xhci_caps.xcap_pagesize;
attrp->dma_attr_maxxfer = xhcip->xhci_caps.xcap_pagesize;
attrp->dma_attr_seg = xhcip->xhci_caps.xcap_pagesize - 1;
}
void
xhci_dma_scratchpad_attr(xhci_t *xhcip, ddi_dma_attr_t *attrp)
{
xhci_dma_dma_attr(xhcip, attrp);
attrp->dma_attr_align = xhcip->xhci_caps.xcap_pagesize;
attrp->dma_attr_flags |= DDI_DMA_RELAXED_ORDERING;
}
uint64_t
xhci_dma_pa(xhci_dma_buffer_t *xdb)
{
ASSERT(xdb->xdb_ncookies == 1);
return (xdb->xdb_cookies[0].dmac_laddress);
}
void
xhci_dma_free(xhci_dma_buffer_t *xdb)
{
if (xdb->xdb_ncookies != 0) {
VERIFY(xdb->xdb_dma_handle != NULL);
(void) ddi_dma_unbind_handle(xdb->xdb_dma_handle);
xdb->xdb_ncookies = 0;
bzero(xdb->xdb_cookies, sizeof (ddi_dma_cookie_t) *
XHCI_TRANSFER_DMA_SGL);
xdb->xdb_len = 0;
}
if (xdb->xdb_acc_handle != NULL) {
ddi_dma_mem_free(&xdb->xdb_acc_handle);
xdb->xdb_acc_handle = NULL;
xdb->xdb_va = NULL;
}
if (xdb->xdb_dma_handle != NULL) {
ddi_dma_free_handle(&xdb->xdb_dma_handle);
xdb->xdb_dma_handle = NULL;
}
ASSERT(xdb->xdb_va == NULL);
ASSERT(xdb->xdb_ncookies == 0);
ASSERT(xdb->xdb_cookies[0].dmac_laddress == 0);
ASSERT(xdb->xdb_len == 0);
}
boolean_t
xhci_dma_alloc(xhci_t *xhcip, xhci_dma_buffer_t *xdb,
ddi_dma_attr_t *attrp, ddi_device_acc_attr_t *accp, boolean_t zero,
size_t size, boolean_t wait)
{
int ret, i;
uint_t flags = DDI_DMA_CONSISTENT;
size_t len;
ddi_dma_cookie_t cookie;
uint_t ncookies;
int (*memcb)(caddr_t);
if (wait == B_TRUE) {
memcb = DDI_DMA_SLEEP;
} else {
memcb = DDI_DMA_DONTWAIT;
}
ret = ddi_dma_alloc_handle(xhcip->xhci_dip, attrp, memcb, NULL,
&xdb->xdb_dma_handle);
if (ret != 0) {
xhci_log(xhcip, "!failed to allocate DMA handle: %d", ret);
xdb->xdb_dma_handle = NULL;
return (B_FALSE);
}
ret = ddi_dma_mem_alloc(xdb->xdb_dma_handle, size, accp, flags, memcb,
NULL, &xdb->xdb_va, &len, &xdb->xdb_acc_handle);
if (ret != DDI_SUCCESS) {
xhci_log(xhcip, "!failed to allocate DMA memory: %d", ret);
xdb->xdb_va = NULL;
xdb->xdb_acc_handle = NULL;
xhci_dma_free(xdb);
return (B_FALSE);
}
if (zero == B_TRUE)
bzero(xdb->xdb_va, len);
ret = ddi_dma_addr_bind_handle(xdb->xdb_dma_handle, NULL,
xdb->xdb_va, len, DDI_DMA_RDWR | flags, memcb, NULL, &cookie,
&ncookies);
if (ret != 0) {
xhci_log(xhcip, "!failed to bind DMA memory: %d", ret);
xhci_dma_free(xdb);
return (B_FALSE);
}
xdb->xdb_len = size;
xdb->xdb_ncookies = ncookies;
xdb->xdb_cookies[0] = cookie;
for (i = 1; i < ncookies; i++) {
ddi_dma_nextcookie(xdb->xdb_dma_handle, &xdb->xdb_cookies[i]);
}
return (B_TRUE);
}
void
xhci_transfer_free(xhci_t *xhcip, xhci_transfer_t *xt)
{
if (xt == NULL)
return;
VERIFY(xhcip != NULL);
xhci_dma_free(&xt->xt_buffer);
if (xt->xt_isoc != NULL) {
ASSERT3U(xt->xt_ntrbs, >, 0);
kmem_free(xt->xt_isoc, sizeof (usb_isoc_pkt_descr_t) *
xt->xt_ntrbs);
xt->xt_isoc = NULL;
}
if (xt->xt_trbs != NULL) {
ASSERT3U(xt->xt_ntrbs, >, 0);
kmem_free(xt->xt_trbs, sizeof (xhci_trb_t) * xt->xt_ntrbs);
xt->xt_trbs = NULL;
}
if (xt->xt_trbs_pa != NULL) {
ASSERT3U(xt->xt_ntrbs, >, 0);
kmem_free(xt->xt_trbs_pa, sizeof (uint64_t) * xt->xt_ntrbs);
xt->xt_trbs_pa = NULL;
}
kmem_free(xt, sizeof (xhci_transfer_t));
}
xhci_transfer_t *
xhci_transfer_alloc(xhci_t *xhcip, xhci_endpoint_t *xep, size_t size,
uint_t trbs, int usb_flags)
{
int kmflags;
boolean_t dmawait;
xhci_transfer_t *xt;
ddi_device_acc_attr_t acc;
ddi_dma_attr_t attr;
if (usb_flags & USB_FLAGS_SLEEP) {
kmflags = KM_SLEEP;
dmawait = B_TRUE;
} else {
kmflags = KM_NOSLEEP;
dmawait = B_FALSE;
}
xt = kmem_zalloc(sizeof (xhci_transfer_t), kmflags);
if (xt == NULL)
return (NULL);
if (size != 0) {
int sgl = XHCI_DEF_DMA_SGL;
if (xep->xep_type == USB_EP_ATTR_BULK) {
sgl = XHCI_TRANSFER_DMA_SGL;
trbs++;
}
xhci_dma_acc_attr(xhcip, &acc);
xhci_dma_transfer_attr(xhcip, &attr, sgl);
if (xhci_dma_alloc(xhcip, &xt->xt_buffer, &attr, &acc, B_FALSE,
size, dmawait) == B_FALSE) {
kmem_free(xt, sizeof (xhci_transfer_t));
return (NULL);
}
if (xep->xep_type != USB_EP_ATTR_ISOCH)
trbs += xt->xt_buffer.xdb_ncookies;
}
xt->xt_trbs = kmem_zalloc(sizeof (xhci_trb_t) * trbs, kmflags);
if (xt->xt_trbs == NULL) {
xhci_dma_free(&xt->xt_buffer);
kmem_free(xt, sizeof (xhci_transfer_t));
return (NULL);
}
xt->xt_trbs_pa = kmem_zalloc(sizeof (uint64_t) * trbs, kmflags);
if (xt->xt_trbs_pa == NULL) {
kmem_free(xt->xt_trbs, sizeof (xhci_trb_t) * trbs);
xhci_dma_free(&xt->xt_buffer);
kmem_free(xt, sizeof (xhci_transfer_t));
return (NULL);
}
if (xep->xep_type == USB_EP_ATTR_ISOCH) {
xt->xt_isoc = kmem_zalloc(sizeof (usb_isoc_pkt_descr_t) * trbs,
kmflags);
if (xt->xt_isoc == NULL) {
kmem_free(xt->xt_trbs_pa, sizeof (uint64_t) * trbs);
kmem_free(xt->xt_trbs, sizeof (xhci_trb_t) * trbs);
xhci_dma_free(&xt->xt_buffer);
kmem_free(xt, sizeof (xhci_transfer_t));
return (NULL);
}
}
xt->xt_ntrbs = trbs;
xt->xt_cr = USB_CR_OK;
return (xt);
}
void
xhci_transfer_copy(xhci_transfer_t *xt, void *buf, size_t len,
boolean_t tobuf)
{
void *dmabuf = xt->xt_buffer.xdb_va;
if (tobuf == B_TRUE)
bcopy(dmabuf, buf, len);
else
bcopy(buf, dmabuf, len);
}
int
xhci_transfer_sync(xhci_t *xhcip, xhci_transfer_t *xt, uint_t type)
{
XHCI_DMA_SYNC(xt->xt_buffer, type);
return (xhci_check_dma_handle(xhcip, &xt->xt_buffer));
}
static uint_t
xhci_transfer_get_tdsize(xhci_transfer_t *xt, uint_t off, uint_t mps)
{
int i;
uint_t npkt = 0;
ASSERT(xt->xt_buffer.xdb_ncookies > 0);
for (i = xt->xt_buffer.xdb_ncookies - 1; i > off; i--) {
size_t len = roundup(xt->xt_buffer.xdb_cookies[i].dmac_size,
mps);
npkt += len / mps;
}
if (npkt >= XHCI_MAX_TDSIZE)
return (XHCI_MAX_TDSIZE);
return (npkt);
}
void
xhci_transfer_trb_fill_data(xhci_endpoint_t *xep, xhci_transfer_t *xt, int off,
boolean_t in)
{
uint_t mps, tdsize, flags;
int i;
VERIFY(xt->xt_buffer.xdb_ncookies > 0);
VERIFY(xep->xep_pipe != NULL);
VERIFY(off + xt->xt_buffer.xdb_ncookies <= xt->xt_ntrbs);
mps = xep->xep_pipe->p_ep.wMaxPacketSize;
if (in == B_TRUE) {
xt->xt_data_tohost = B_TRUE;
}
if (xep->xep_type != USB_EP_ATTR_BULK) {
VERIFY3U(xt->xt_buffer.xdb_ncookies, ==, 1);
}
for (i = 0; i < xt->xt_buffer.xdb_ncookies; i++) {
uint64_t pa, dmasz;
pa = xt->xt_buffer.xdb_cookies[i].dmac_laddress;
dmasz = xt->xt_buffer.xdb_cookies[i].dmac_size;
tdsize = xhci_transfer_get_tdsize(xt, i, mps);
flags = XHCI_TRB_TYPE_NORMAL;
if (i == 0 && xep->xep_type == USB_EP_ATTR_CONTROL) {
flags = XHCI_TRB_TYPE_DATA;
if (in == B_TRUE)
flags |= XHCI_TRB_DIR_IN;
}
if (xep->xep_type == USB_EP_ATTR_BULK ||
xt->xt_buffer.xdb_ncookies > 1) {
flags |= XHCI_TRB_CHAIN;
}
if (i + 1 == xt->xt_buffer.xdb_ncookies) {
switch (xep->xep_type) {
case USB_EP_ATTR_BULK:
flags |= XHCI_TRB_ENT;
break;
case USB_EP_ATTR_CONTROL:
flags |= XHCI_TRB_ISP;
break;
default:
flags |= XHCI_TRB_IOC;
break;
}
}
xt->xt_trbs[off + i].trb_addr = LE_64(pa);
xt->xt_trbs[off + i].trb_status = LE_32(XHCI_TRB_LEN(dmasz) |
XHCI_TRB_TDREM(tdsize) | XHCI_TRB_INTR(0));
xt->xt_trbs[off + i].trb_flags = LE_32(flags);
}
if (xep->xep_type == USB_EP_ATTR_BULK) {
VERIFY(off + xt->xt_buffer.xdb_ncookies + 1 <= xt->xt_ntrbs);
xt->xt_trbs[off + i].trb_addr = LE_64((uintptr_t)xt);
xt->xt_trbs[off + i].trb_status = LE_32(XHCI_TRB_INTR(0));
xt->xt_trbs[off + i].trb_flags = LE_32(XHCI_TRB_TYPE_EVENT |
XHCI_TRB_IOC);
}
}
void
xhci_transfer_calculate_isoc(xhci_device_t *xd, xhci_endpoint_t *xep,
uint_t trb_len, uint_t *tbc, uint_t *tlbpc)
{
uint_t mps, tdpc, burst;
if (trb_len == 0)
trb_len = 1;
mps = XHCI_EPCTX_GET_MPS(xd->xd_endout[xep->xep_num]->xec_info2);
burst = XHCI_EPCTX_GET_MAXB(xd->xd_endout[xep->xep_num]->xec_info2);
tdpc = howmany(trb_len, mps);
*tbc = howmany(tdpc, burst + 1) - 1;
if ((tdpc % (burst + 1)) == 0)
*tlbpc = burst;
else
*tlbpc = (tdpc % (burst + 1)) - 1;
}