Print this page
MFV: illumos-gate@2aba3acda67326648fd60aaf2bfb4e18ee8c04ed
9816 Multi-TRB xhci transfers should use event data
9817 xhci needs to always set slot context
8550 increase xhci bulk transfer sgl count
9818 xhci_transfer_get_tdsize can return values that are too large
Reviewed by: Alex Wilson <alex.wilson@joyent.com>
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
Approved by: Joshua M. Clulow <josh@sysmgr.org>
Author: Robert Mustacchi <rm@joyent.com>
        
*** 8,18 ****
   * source.  A copy of the CDDL is also available via the Internet at
   * http://www.illumos.org/license/CDDL.
   */
  
  /*
!  * Copyright 2016 Joyent, Inc.
   */
  
  /*
   * xHCI DMA Management Routines
   *
--- 8,18 ----
   * source.  A copy of the CDDL is also available via the Internet at
   * http://www.illumos.org/license/CDDL.
   */
  
  /*
!  * Copyright (c) 2018, Joyent, Inc.
   */
  
  /*
   * xHCI DMA Management Routines
   *
*** 266,291 ****
                  return;
  
          VERIFY(xhcip != NULL);
          xhci_dma_free(&xt->xt_buffer);
          if (xt->xt_isoc != NULL) {
!                 ASSERT(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) {
!                 ASSERT(xt->xt_ntrbs > 0);
                  kmem_free(xt->xt_trbs, sizeof (xhci_trb_t) * xt->xt_ntrbs);
                  xt->xt_trbs = NULL;
          }
          kmem_free(xt, sizeof (xhci_transfer_t));
  }
  
  xhci_transfer_t *
! xhci_transfer_alloc(xhci_t *xhcip, xhci_endpoint_t *xep, size_t size, int trbs,
!     int usb_flags)
  {
          int kmflags;
          boolean_t dmawait;
          xhci_transfer_t *xt;
          ddi_device_acc_attr_t acc;
--- 266,296 ----
                  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;
*** 317,329 ****
                   * To simplify things, we don't use additional SGL entries for
                   * ISOC transfers. While this isn't the best, it isn't too far
                   * off from what ehci and co. have done before. If this becomes
                   * a technical issue, it's certainly possible to increase the
                   * SGL entry count.
                   */
!                 if (xep->xep_type == USB_EP_ATTR_BULK)
                          sgl = XHCI_TRANSFER_DMA_SGL;
  
                  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) {
--- 322,342 ----
                   * To simplify things, we don't use additional SGL entries for
                   * ISOC transfers. While this isn't the best, it isn't too far
                   * off from what ehci and co. have done before. If this becomes
                   * a technical issue, it's certainly possible to increase the
                   * SGL entry count.
+                  *
+                  * When we use the larger SGL count, we change our strategy for
+                  * being notified. In such a case we will opt to use an event
+                  * data packet. This helps deal with cases where some
+                  * controllers don't properly generate events for the last entry
+                  * in a TD with IOC when IOSP is set.
                   */
!                 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) {
*** 344,360 ****
--- 357,382 ----
                  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);
+         }
+ 
          /*
           * For ISOCH transfers, we need to also allocate the results data.
           */
          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);
                  }
*** 405,415 ****
   * Act TD       10      2       1       0
   *
   * This means that the only safe way forward here is to work backwards and see
   * how many we need to work up to this point.
   */
! static int
  xhci_transfer_get_tdsize(xhci_transfer_t *xt, uint_t off, uint_t mps)
  {
          int i;
          uint_t npkt = 0;
  
--- 427,437 ----
   * Act TD       10      2       1       0
   *
   * This means that the only safe way forward here is to work backwards and see
   * how many we need to work up to this point.
   */
! static uint_t
  xhci_transfer_get_tdsize(xhci_transfer_t *xt, uint_t off, uint_t mps)
  {
          int i;
          uint_t npkt = 0;
  
*** 416,438 ****
          /*
           * There are always zero packets for the last TRB.
           */
          ASSERT(xt->xt_buffer.xdb_ncookies > 0);
          for (i = xt->xt_buffer.xdb_ncookies - 1; i > off; i--) {
!                 size_t len;
  
                  /*
!                  * The maximum value we can return is 31 packets. So, in that
!                  * case we short-circuit and return.
                   */
!                 if (npkt >= 31)
!                         return (31);
  
-                 len = roundup(xt->xt_buffer.xdb_cookies[i].dmac_size, mps);
-                 npkt += len / mps;
-         }
- 
          return (npkt);
  }
  
  void
  xhci_transfer_trb_fill_data(xhci_endpoint_t *xep, xhci_transfer_t *xt, int off,
--- 438,458 ----
          /*
           * There are always zero packets for the last TRB.
           */
          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;
!         }
  
          /*
!          * Make sure to clamp this value otherwise we risk truncation.
           */
!         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,
*** 444,453 ****
--- 464,486 ----
          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;
+         }
+ 
+         /*
+          * We assume that if we have a non-bulk endpoint, then we should only
+          * have a single cookie. This falls out from the default SGL length that
+          * we use for these other device types.
+          */
+         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;
*** 460,504 ****
                          if (in == B_TRUE)
                                  flags |= XHCI_TRB_DIR_IN;
                  }
  
                  /*
!                  * When reading data in (from the device), we may get shorter
!                  * transfers than the buffer allowed for. To make sure we get
!                  * notified about that and handle that, we need to set the ISP
!                  * flag.
                   */
!                 if (in == B_TRUE) {
!                         flags |= XHCI_TRB_ISP;
!                         xt->xt_data_tohost = B_TRUE;
!                 }
! 
!                 /*
!                  * When we have more than one cookie, we are technically
!                  * chaining together things according to the controllers view,
!                  * hence why we need to set the chain flag.
!                  */
!                 if (xt->xt_buffer.xdb_ncookies > 1 &&
!                     i != (xt->xt_buffer.xdb_ncookies - 1)) {
                          flags |= XHCI_TRB_CHAIN;
                  }
  
                  /*
!                  * If we have a non-control transfer, then we need to make sure
!                  * that we set ourselves up to be interrupted, which we set for
!                  * the last entry.
                   */
!                 if (i + 1 == xt->xt_buffer.xdb_ncookies &&
!                     xep->xep_type != USB_EP_ATTR_CONTROL) {
                          flags |= XHCI_TRB_IOC;
                  }
  
                  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);
          }
  }
  
  /*
   * These are utility functions for isochronus transfers to help calculate the
   * transfer burst count (TBC) and transfer last burst packet count (TLPBC)
--- 493,559 ----
                          if (in == B_TRUE)
                                  flags |= XHCI_TRB_DIR_IN;
                  }
  
                  /*
!                  * If we have more than one cookie, then we need to set chaining
!                  * on every TRB and the last TRB will turn into an event data
!                  * TRB. If we only have a single TRB, then we just set interrupt
!                  * on completion (IOC). There's no need to specifically set
!                  * interrupt on short packet (IOSP) in that case, as we'll
!                  * always get the event notification. We still need the chain
!                  * bit set on the last packet, so we can chain into the event
!                  * data. Even if all the data on a bulk endpoint (the only
!                  * endpoint type that uses chaining today) has only one cookie,
!                  * then we'll still schedule an event data block.
                   */
!                 if (xep->xep_type == USB_EP_ATTR_BULK ||
!                     xt->xt_buffer.xdb_ncookies > 1) {
                          flags |= XHCI_TRB_CHAIN;
                  }
  
                  /*
!                  * What we set for the last TRB depends on the type of the
!                  * endpoint. If it's a bulk endpoint, then we have to set
!                  * evaluate next trb (ENT) so we successfully process the event
!                  * data TRB we'll set up. Otherwise, we need to make sure that
!                  * we set interrupt on completion, so we get the event. However,
!                  * we don't set the event on control endpoints, as the status
!                  * stage TD will be the one where we get the event. But, we do
!                  * still need an interrupt on short packet, because technically
!                  * the status stage is in its own TD.
                   */
!                 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);
          }
+ 
+         /*
+          * The last TRB in any bulk transfer is the Event Data TRB.
+          */
+         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);
+         }
  }
  
  /*
   * These are utility functions for isochronus transfers to help calculate the
   * transfer burst count (TBC) and transfer last burst packet count (TLPBC)