From: Dom Cobley <popcornmix@xxxxxxxxx> Add the NAK holdoff patch from the downstream Raspberry Pi kernel. This allows the transfer scheduler to better handle "cheeky devices that just hold off using NAKs". [original patch from Dom Cobley] Signed-off-by: Dom Cobley <popcornmix@xxxxxxxxx> [adapted to dwc2 driver by Paul Zimmerman] Signed-off-by: Paul Zimmerman <paulz@xxxxxxxxxxxx> --- drivers/staging/dwc2/core.h | 2 ++ drivers/staging/dwc2/hcd.c | 20 ++++++++++++++++++++ drivers/staging/dwc2/hcd.h | 2 ++ drivers/staging/dwc2/hcd_intr.c | 24 ++++++++++++++++++++++-- drivers/staging/dwc2/hcd_queue.c | 19 +++++++++++++++---- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/drivers/staging/dwc2/core.h b/drivers/staging/dwc2/core.h index e5b4dc8..d65e8a5 100644 --- a/drivers/staging/dwc2/core.h +++ b/drivers/staging/dwc2/core.h @@ -292,6 +292,7 @@ struct dwc2_core_params { * @otg_port: OTG port number * @frame_list: Frame list * @frame_list_dma: Frame list DMA address + * @next_sched_frame: Next scheduled frame, used by the NAK holdoff code */ struct dwc2_hsotg { struct device *dev; @@ -365,6 +366,7 @@ struct dwc2_hsotg { u8 otg_port; u32 *frame_list; dma_addr_t frame_list_dma; + int next_sched_frame; /* DWC OTG HW Release versions */ #define DWC2_CORE_REV_2_71a 0x4f54271a diff --git a/drivers/staging/dwc2/hcd.c b/drivers/staging/dwc2/hcd.c index 32b52ad..3a4ff79 100644 --- a/drivers/staging/dwc2/hcd.c +++ b/drivers/staging/dwc2/hcd.c @@ -890,6 +890,24 @@ enum dwc2_transaction_type dwc2_hcd_select_transactions( if (list_empty(&hsotg->free_hc_list)) break; qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); + + /* + * Check to see if this is a NAK'd retransmit, in which case + * ignore for retransmission. We hold off on bulk + * retransmissions to reduce NAK interrupt overhead for + * cheeky devices that just hold off using NAKs. + */ + if (dwc2_full_frame_num(qh->nak_frame) == + dwc2_full_frame_num(dwc2_hcd_get_frame_number(hsotg))) { + /* Make interrupt run on next frame (i.e. 8 uframes) */ + hsotg->next_sched_frame = ((qh->nak_frame + 8) & ~7) & + HFNUM_MAX_FRNUM; + qh_ptr = qh_ptr->next; + continue; + } else { + qh->nak_frame = 0xffff; + } + dwc2_assign_and_init_hc(hsotg, qh); /* @@ -2914,6 +2932,8 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq, hsotg->hc_ptr_array[i] = channel; } + hsotg->next_sched_frame = 0; + /* Initialize hsotg start work */ INIT_DELAYED_WORK(&hsotg->start_work, dwc2_hcd_start_func); diff --git a/drivers/staging/dwc2/hcd.h b/drivers/staging/dwc2/hcd.h index 3b06024..4f9f6c5 100644 --- a/drivers/staging/dwc2/hcd.h +++ b/drivers/staging/dwc2/hcd.h @@ -238,6 +238,7 @@ enum dwc2_transaction_type { * @interval: Interval between transfers in (micro)frames * @sched_frame: (Micro)frame to initialize a periodic transfer. * The transfer executes in the following (micro)frame. + * @nak_frame: Internal variable used by the NAK holdoff code * @start_split_frame: (Micro)frame at which last start split was initialized * @ntd: Actual number of transfer descriptors in a list * @dw_align_buf: Used instead of original buffer if its physical address @@ -271,6 +272,7 @@ struct dwc2_qh { u16 usecs; u16 interval; u16 sched_frame; + u16 nak_frame; u16 start_split_frame; u16 ntd; u8 *dw_align_buf; diff --git a/drivers/staging/dwc2/hcd_intr.c b/drivers/staging/dwc2/hcd_intr.c index 9e68ef1..b190d2b 100644 --- a/drivers/staging/dwc2/hcd_intr.c +++ b/drivers/staging/dwc2/hcd_intr.c @@ -118,9 +118,10 @@ static void dwc2_hc_handle_tt_clear(struct dwc2_hsotg *hsotg, */ static void dwc2_sof_intr(struct dwc2_hsotg *hsotg) { + enum dwc2_transaction_type tr_type; struct list_head *qh_entry; struct dwc2_qh *qh; - enum dwc2_transaction_type tr_type; + int next_sched_frame = -1; #ifdef DEBUG_SOF dev_vdbg(hsotg->dev, "--Start of Frame Interrupt--\n"); @@ -135,14 +136,23 @@ static void dwc2_sof_intr(struct dwc2_hsotg *hsotg) while (qh_entry != &hsotg->periodic_sched_inactive) { qh = list_entry(qh_entry, struct dwc2_qh, qh_list_entry); qh_entry = qh_entry->next; - if (dwc2_frame_num_le(qh->sched_frame, hsotg->frame_number)) + if (dwc2_frame_num_le(qh->sched_frame, hsotg->frame_number)) { /* * Move QH to the ready list to be executed next * (micro)frame */ list_move(&qh->qh_list_entry, &hsotg->periodic_sched_ready); + } else { + if (next_sched_frame < 0 || + dwc2_frame_num_le(qh->sched_frame, + next_sched_frame)) + next_sched_frame = qh->sched_frame; + } } + + hsotg->next_sched_frame = next_sched_frame; + tr_type = dwc2_hcd_select_transactions(hsotg); if (tr_type != DWC2_TRANSACTION_NONE) dwc2_hcd_queue_transactions(hsotg, tr_type); @@ -1205,6 +1215,16 @@ static void dwc2_hc_nak_intr(struct dwc2_hsotg *hsotg, chnum); /* + * When we get bulk NAKs then remember this so we holdoff on this qh + * until the beginning of the next frame + */ + switch (dwc2_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case USB_ENDPOINT_XFER_BULK: + chan->qh->nak_frame = dwc2_hcd_get_frame_number(hsotg); + break; + } + + /* * Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and * interrupt. Re-start the SSPLIT transfer. */ diff --git a/drivers/staging/dwc2/hcd_queue.c b/drivers/staging/dwc2/hcd_queue.c index 5461e3b..262e11f 100644 --- a/drivers/staging/dwc2/hcd_queue.c +++ b/drivers/staging/dwc2/hcd_queue.c @@ -83,6 +83,7 @@ static void dwc2_qh_init(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, dev_speed = dwc2_host_get_speed(hsotg, urb->priv); dwc2_host_hub_info(hsotg, urb->priv, &hub_addr, &hub_port); + qh->nak_frame = 0xffff; if ((dev_speed == USB_SPEED_LOW || dev_speed == USB_SPEED_FULL) && hub_addr != 0 && hub_addr != 1) { @@ -391,13 +392,18 @@ static int dwc2_schedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) return status; } - if (hsotg->core_params->dma_desc_enable > 0) + if (hsotg->core_params->dma_desc_enable > 0) { /* Don't rely on SOF and start in ready schedule */ list_add_tail(&qh->qh_list_entry, &hsotg->periodic_sched_ready); - else + } else { + if (list_empty(&hsotg->periodic_sched_inactive) || + dwc2_frame_num_le(qh->sched_frame, hsotg->next_sched_frame)) + hsotg->next_sched_frame = qh->sched_frame; + /* Always start in inactive schedule */ list_add_tail(&qh->qh_list_entry, &hsotg->periodic_sched_inactive); + } /* Reserve periodic channel */ hsotg->periodic_channels++; @@ -581,12 +587,17 @@ void dwc2_hcd_qh_deactivate(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, * Remove from periodic_sched_queued and move to * appropriate queue */ - if (qh->sched_frame == frame_number) + if (qh->sched_frame == frame_number) { list_move(&qh->qh_list_entry, &hsotg->periodic_sched_ready); - else + } else { + if (!dwc2_frame_num_le(hsotg->next_sched_frame, + qh->sched_frame)) + hsotg->next_sched_frame = + qh->sched_frame; list_move(&qh->qh_list_entry, &hsotg->periodic_sched_inactive); + } } } } -- 1.8.2.rc0.16.g20a599e -- 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