>From 050df8768e10e58d01bbc24d8ee256d864cc6fff Mon Sep 17 00:00:00 2001 From: Andiry Xu <andiry.xu@xxxxxxx> Date: Fri, 26 Feb 2010 17:38:15 +0800 Subject: [PATCH 2/7] xHCI: Isoc transfer URB enqueue This patch implements isoc transfer URB enqueue part. Split the isoc URB into isoc TDs and insert them to the isoc endpoint transfer ring. A isoc TD can be made up of one isoc TRB and several normal TRBs (if needed). Signed-off-by: Andiry Xu <andiry.xu@xxxxxxx> Signed-off-by: Libin Yang <libin.yang@xxxxxxx> --- drivers/usb/host/xhci-hcd.c | 7 ++- drivers/usb/host/xhci-ring.c | 148 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci.h | 2 + 3 files changed, 156 insertions(+), 1 deletions(-) diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 36b7fa1..acd620b 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -768,7 +768,12 @@ int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) slot_id, ep_index); spin_unlock_irqrestore(&xhci->lock, flags); } else { - ret = -EINVAL; + spin_lock_irqsave(&xhci->lock, flags); + if (xhci->xhc_state & XHCI_STATE_DYING) + goto dying; + ret = xhci_queue_isoc_tx(xhci, GFP_ATOMIC, urb, + slot_id, ep_index); + spin_unlock_irqrestore(&xhci->lock, flags); } exit: return ret; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 79041b9..8815210 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2148,6 +2148,154 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, return 0; } +static int count_isoc_trbs_needed(struct xhci_hcd *xhci, + struct urb *urb, int i) +{ + int num_trbs = 0; + u64 addr, td_len, running_total; + + addr = (u64) (urb->transfer_dma + urb->iso_frame_desc[i].offset); + td_len = urb->iso_frame_desc[i].length; + + running_total = TRB_MAX_BUFF_SIZE - + (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + if (running_total != 0) + num_trbs++; + + while (running_total < td_len) { + num_trbs++; + running_total += TRB_MAX_BUFF_SIZE; + } + + return num_trbs; +} + +/* This is for isoc transfer */ +int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + struct urb *urb, int slot_id, unsigned int ep_index) +{ + struct xhci_ring *ep_ring; + struct urb_priv *urb_priv; + struct xhci_td *td; + int num_tds, trbs_per_td; + struct xhci_generic_trb *start_trb; + bool first_trb; + int start_cycle; + u32 field, length_field; + + int running_total, trb_buff_len, td_len, td_remain_len, ret; + u64 start_addr, addr; + int i, j; + + ep_ring = xhci->devs[slot_id]->eps[ep_index].ring; + + num_tds = urb->number_of_packets; + if (num_tds < 1) { + xhci_dbg(xhci, "ISOC URB with zero packets?\n"); + return -EINVAL; + } + + if (!in_interrupt()) + dev_dbg(&urb->dev->dev, "ep %#x - urb len = %#x (%d)," + " addr = %#llx, num_trbs = %d\n", + urb->ep->desc.bEndpointAddress, + urb->transfer_buffer_length, + urb->transfer_buffer_length, + (unsigned long long)urb->transfer_dma, + num_tds); + + start_addr = (u64) urb->transfer_dma; + + /* Queue the first TRB, even if it's zero-length */ + for (i = 0; i < num_tds; i++) { + first_trb = true; + + running_total = 0; + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + + addr = start_addr + urb->iso_frame_desc[i].offset; + td_len = urb->iso_frame_desc[i].length; + td_remain_len = td_len; + + trbs_per_td = count_isoc_trbs_needed(xhci, urb, i); + + ret = prepare_transfer(xhci, xhci->devs[slot_id], ep_index, + trbs_per_td, urb, i, mem_flags); + if (ret < 0) + return ret; + + urb_priv = urb->hcpriv; + td = urb_priv->td[i]; + + for (j = 0; j < trbs_per_td; j++) { + u32 remainder = 0; + field = 0; + + if (first_trb) { + /* Queue the isoc TRB */ + field |= TRB_TYPE(TRB_ISOC); + /* Assume URB_ISO_ASAP is set */ + field |= TRB_SIA; + first_trb = false; + } else { + /* Queue other normal TRBs */ + field |= TRB_TYPE(TRB_NORMAL); + field |= ep_ring->cycle_state; + } + + /* Chain all the TRBs together; clear the chain bit in + * the last TRB to indicate it's the last TRB in the + * chain. + */ + + if (j < trbs_per_td - 1) { + field |= TRB_CHAIN; + } else { + td->last_trb = ep_ring->enqueue; + field |= TRB_IOC; + } + + /* Calculate TRB length */ + trb_buff_len = TRB_MAX_BUFF_SIZE - + (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + if (trb_buff_len > td_remain_len) + trb_buff_len = td_remain_len; + + remainder = xhci_td_remainder(td_len - running_total); + length_field = TRB_LEN(trb_buff_len) | + remainder | + TRB_INTR_TARGET(0); + queue_trb(xhci, ep_ring, false, + lower_32_bits(addr), + upper_32_bits(addr), + length_field, + /* We always want to know if the TRB was short, + * or we won't get an event when it completes. + * (Unless we use event data TRBs, which are a + * waste of space and HC resources.) + */ + field | TRB_ISP); + running_total += trb_buff_len; + + addr += trb_buff_len; + td_remain_len -= trb_buff_len; + } + + /* Check TD length */ + if (running_total != td_len) { + xhci_dbg(xhci, "ISOC TD length unmatch\n"); + return -EINVAL; + } + + wmb(); + start_trb->field[3] |= start_cycle; + } + + ring_ep_doorbell(xhci, slot_id, ep_index); + return 0; +} + /**** Command Ring Operations ****/ /* Generic function for queueing a command TRB on the command ring. diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 6d4b21c..980f08b 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1305,6 +1305,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index); int xhci_queue_intr_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index); +int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, + int slot_id, unsigned int ep_index); int xhci_queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id, bool command_must_succeed); int xhci_queue_evaluate_context(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, -- 1.6.0.4 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html