* Introduce split transaction order queues.
* Improve the NAK interrupt handler routine.
* Mostly move from list_move() to list_move_tail().
Those changes fix an attachment problem seen for certain devices which
are issuing NAK interrupts during split transactions, which don't get
handled correctly by the driver today. This could result in unexpected
channel halting, printing "ChHltd set, but reason is unknown", which
finally leaves the device back on a disabled USB port.
ok kettenis@
-/* $OpenBSD: dwc2_core.c,v 1.10 2021/07/22 18:32:33 mglocker Exp $ */
+/* $OpenBSD: dwc2_core.c,v 1.11 2021/07/27 13:36:59 mglocker Exp $ */
/* $NetBSD: dwc2_core.c,v 1.6 2014/04/03 06:34:58 skrll Exp $ */
/*
chan->xfer_started = 0;
+ list_del_init(&chan->split_order_list_entry);
+
/*
* Clear channel interrupt enables and any unhandled channel interrupt
* conditions
-/* $OpenBSD: dwc2_core.h,v 1.10 2021/07/22 18:32:33 mglocker Exp $ */
+/* $OpenBSD: dwc2_core.h,v 1.11 2021/07/27 13:36:59 mglocker Exp $ */
/* $NetBSD: dwc2_core.h,v 1.5 2014/04/03 06:34:58 skrll Exp $ */
/*
* periodic_sched_ready because it must be rescheduled for
* the next frame. Otherwise, the item moves to
* periodic_sched_inactive.
+ * @split_order: List keeping track of channels doing splits, in order.
* @periodic_usecs: Total bandwidth claimed so far for periodic transfers.
* This value is in microseconds per (micro)frame. The
* assumption is that all periodic transfers may occur in
struct list_head periodic_sched_ready;
struct list_head periodic_sched_assigned;
struct list_head periodic_sched_queued;
+ struct list_head split_order;
u16 periodic_usecs;
u16 frame_usecs[8];
u16 frame_number;
-/* $OpenBSD: dwc2_hcd.c,v 1.23 2021/07/22 18:32:33 mglocker Exp $ */
+/* $OpenBSD: dwc2_hcd.c,v 1.24 2021/07/27 13:36:59 mglocker Exp $ */
/* $NetBSD: dwc2_hcd.c,v 1.15 2014/11/24 10:14:14 skrll Exp $ */
/*
* periodic assigned schedule
*/
qh_ptr = qh_ptr->next;
- list_move(&qh->qh_list_entry, &hsotg->periodic_sched_assigned);
+ list_move_tail(&qh->qh_list_entry,
+ &hsotg->periodic_sched_assigned);
ret_val = DWC2_TRANSACTION_PERIODIC;
}
* non-periodic active schedule
*/
qh_ptr = qh_ptr->next;
- list_move(&qh->qh_list_entry,
+ list_move_tail(&qh->qh_list_entry,
&hsotg->non_periodic_sched_active);
if (ret_val == DWC2_TRANSACTION_NONE)
{
int retval = 0;
- if (hsotg->core_params->dma_enable > 0) {
+ if (chan->do_split)
+ /* Put ourselves on the list to keep order straight */
+ list_move_tail(&chan->split_order_list_entry,
+ &hsotg->split_order);
+
+ if (hsotg->core_params->dma_enable > 0 && chan->qh) {
if (hsotg->core_params->dma_desc_enable > 0) {
if (!chan->xfer_started ||
chan->ep_type == USB_ENDPOINT_XFER_ISOC) {
* Move the QH from the periodic assigned schedule to
* the periodic queued schedule
*/
- list_move(&qh->qh_list_entry,
+ list_move_tail(&qh->qh_list_entry,
&hsotg->periodic_sched_queued);
/* done queuing high bandwidth */
INIT_LIST_HEAD(&hsotg->periodic_sched_assigned);
INIT_LIST_HEAD(&hsotg->periodic_sched_queued);
+ INIT_LIST_HEAD(&hsotg->split_order);
+
/*
* Create a host channel descriptor for each host channel implemented
* in the controller. Initialize the channel descriptor array.
if (channel == NULL)
goto error3;
channel->hc_num = i;
+ INIT_LIST_HEAD(&channel->split_order_list_entry);
hsotg->hc_ptr_array[i] = channel;
}
-/* $OpenBSD: dwc2_hcd.h,v 1.14 2021/07/22 18:32:33 mglocker Exp $ */
+/* $OpenBSD: dwc2_hcd.h,v 1.15 2021/07/27 13:36:59 mglocker Exp $ */
/* $NetBSD: dwc2_hcd.h,v 1.9 2014/09/03 10:00:08 skrll Exp $ */
/*
* @hc_list_entry: For linking to list of host channels
* @desc_list_addr: Current QH's descriptor list DMA address
* @desc_list_sz: Current QH's descriptor list size
+ * @split_order_list_entry: List entry for keeping track of the order of splits
*
* This structure represents the state of a single host channel when acting in
* host mode. It contains the data items needed to transfer packets to an
struct usb_dma desc_list_usbdma;
dma_addr_t desc_list_addr;
u32 desc_list_sz;
+ struct list_head split_order_list_entry;
};
struct dwc2_hcd_pipe_info {
-/* $OpenBSD: dwc2_hcdddma.c,v 1.17 2021/07/22 18:32:33 mglocker Exp $ */
+/* $OpenBSD: dwc2_hcdddma.c,v 1.18 2021/07/27 13:36:59 mglocker Exp $ */
/* $NetBSD: dwc2_hcdddma.c,v 1.6 2014/04/03 06:34:58 skrll Exp $ */
/*
dwc2_hcd_qh_unlink(hsotg, qh);
} else {
/* Keep in assigned schedule to continue transfer */
- list_move(&qh->qh_list_entry,
- &hsotg->periodic_sched_assigned);
+ list_move_tail(&qh->qh_list_entry,
+ &hsotg->periodic_sched_assigned);
/*
* If channel has been halted during giveback of urb
* then prevent any new scheduling.
-/* $OpenBSD: dwc2_hcdintr.c,v 1.11 2021/07/22 18:32:33 mglocker Exp $ */
+/* $OpenBSD: dwc2_hcdintr.c,v 1.12 2021/07/27 13:36:59 mglocker Exp $ */
/* $NetBSD: dwc2_hcdintr.c,v 1.11 2014/11/24 10:14:14 skrll Exp $ */
/*
* Move QH to the ready list to be executed next
* (micro)frame
*/
- list_move(&qh->qh_list_entry,
- &hsotg->periodic_sched_ready);
+ list_move_tail(&qh->qh_list_entry,
+ &hsotg->periodic_sched_ready);
}
tr_type = dwc2_hcd_select_transactions(hsotg);
if (tr_type != DWC2_TRANSACTION_NONE)
* halt to be queued when the periodic schedule is
* processed.
*/
- list_move(&chan->qh->qh_list_entry,
- &hsotg->periodic_sched_assigned);
+ list_move_tail(&chan->qh->qh_list_entry,
+ &hsotg->periodic_sched_assigned);
/*
* Make sure the Periodic Tx FIFO Empty interrupt is
* avoid interrupt storms we'll wait before retrying if we've got
* several NAKs. If we didn't do this we'd retry directly from the
* interrupt handler and could end up quickly getting another
- * interrupt (another NAK), which we'd retry.
+ * interrupt (another NAK), which we'd retry. Note that we do not
+ * delay retries for IN parts of control requests, as those are expected
+ * to complete fairly quickly, and if we delay them we risk confusing
+ * the device and cause it issue STALL.
*
* Note that in DMA mode software only gets involved to re-send NAKed
- * transfers for split transactions unless the core is missing OUT NAK
- * enhancement.
+ * transfers for split transactions, so we only need to apply this
+ * delaying logic when handling splits. In non-DMA mode presumably we
+ * might want a similar delay if someone can demonstrate this problem
+ * affects that code path too.
*/
if (chan->do_split) {
- /*
- * When we get control/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_CONTROL:
- case USB_ENDPOINT_XFER_BULK:
- chan->qh->nak_frame = dwc2_hcd_get_frame_number(hsotg);
- break;
- }
-
if (chan->complete_split)
qtd->error_count = 0;
qtd->complete_split = 0;
qtd->num_naks++;
- qtd->qh->want_wait = qtd->num_naks >= dwc2_naks_before_delay;
+ qtd->qh->want_wait = qtd->num_naks >= DWC2_NAKS_BEFORE_DELAY &&
+ !(chan->ep_type == USB_ENDPOINT_XFER_CONTROL &&
+ chan->ep_is_in);
dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NAK);
goto handle_nak_done;
}
*/
qtd->error_count = 0;
- if (hsotg->core_params->dma_enable > 0 && !chan->ep_is_in) {
- /*
- * Avoid interrupt storms.
- */
- qtd->num_naks++;
- qtd->qh->want_wait = qtd->num_naks >= dwc2_out_naks_before_delay;
- }
if (!chan->qh->ping_state) {
dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb,
qtd, DWC2_HC_XFER_NAK);
qtd->error_count++;
dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb,
qtd, DWC2_HC_XFER_XACT_ERR);
+ /*
+ * We can get here after a completed transaction
+ * (urb->actual_length >= urb->length) which was not reported
+ * as completed. If that is the case, and we do not abort
+ * the transfer, a transfer of size 0 will be enqueued
+ * subsequently. If urb->actual_length is not DMA-aligned,
+ * the buffer will then point to an unaligned address, and
+ * the resulting behavior is undefined. Bail out in that
+ * situation.
+ */
+ if (qtd->urb->actual_length >= qtd->urb->length)
+ qtd->error_count = 3;
dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd);
dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_XACT_ERR);
}
{
u32 haint;
int i;
+ struct dwc2_host_chan *chan, *chan_tmp;
haint = DWC2_READ_4(hsotg, HAINT);
if (dbg_perio()) {
dev_vdbg(hsotg->dev, "HAINT=%08x\n", haint);
}
+ /*
+ * According to USB 2.0 spec section 11.18.8, a host must
+ * issue complete-split transactions in a microframe for a
+ * set of full-/low-speed endpoints in the same relative
+ * order as the start-splits were issued in a microframe for.
+ */
+ list_for_each_entry_safe(chan, chan_tmp, &hsotg->split_order,
+ split_order_list_entry) {
+ int hc_num = chan->hc_num;
+
+ if (haint & (1 << hc_num)) {
+ dwc2_hc_n_intr(hsotg, hc_num);
+ haint &= ~(1 << hc_num);
+ }
+ }
+
for (i = 0; i < hsotg->core_params->host_channels; i++) {
if (haint & (1 << i))
dwc2_hc_n_intr(hsotg, i);
-/* $OpenBSD: dwc2_hcdqueue.c,v 1.10 2021/07/22 18:32:33 mglocker Exp $ */
+/* $OpenBSD: dwc2_hcdqueue.c,v 1.11 2021/07/27 13:36:59 mglocker Exp $ */
/* $NetBSD: dwc2_hcdqueue.c,v 1.11 2014/09/03 10:00:08 skrll Exp $ */
/*
dwc2_frame_num_le(qh->sched_frame, frame_number)) ||
(hsotg->core_params->uframe_sched <= 0 &&
qh->sched_frame == frame_number))
- list_move(&qh->qh_list_entry, &hsotg->periodic_sched_ready);
+ list_move_tail(&qh->qh_list_entry,
+ &hsotg->periodic_sched_ready);
else
- list_move(&qh->qh_list_entry, &hsotg->periodic_sched_inactive);
+ list_move_tail(&qh->qh_list_entry,
+ &hsotg->periodic_sched_inactive);
}
/**
-/* $OpenBSD: list.h,v 1.1 2021/07/22 18:32:33 mglocker Exp $ */
+/* $OpenBSD: list.h,v 1.2 2021/07/27 13:36:59 mglocker Exp $ */
/* linux list functions for the BSDs, cut down for dwctwo(4).
* Created: Mon Apr 7 14:30:16 1999 by anholt@FreeBSD.org
*/
list_add(list, head);
}
+static inline void list_move_tail(struct list_head *list,
+ struct list_head *head)
+{
+ list_del(list);
+ list_add_tail(list, head);
+}
+
static inline void
list_del_init(struct list_head *entry) {
(entry)->next->prev = (entry)->prev;