Commit 91bb163e authored by Minas Harutyunyan's avatar Minas Harutyunyan Committed by Greg Kroah-Hartman

usb: dwc2: gadget: Fix ISOC flow for BDMA and Slave

According USB spec each ISOC transaction should be performed in a
designated for that transaction interval. On bus errors or delays
in operating system scheduling of client software can result in no
packet being transferred for a (micro)frame. An error indication
should be returned as status to the client software in such a case.

Current implementation in case of missed/dropped interval send same
data in next possible interval instead of reporting missed isoc.

This fix complete requests with -ENODATA if interval elapsed.

HSOTG core in BDMA and Slave modes haven't HW support for
(micro)frames tracking, this is why SW should care about tracking
of (micro)frames. Because of that method and consider operating
system scheduling delays, added few additional checking's of elapsed
target (micro)frame:
1. Immediately before enabling EP to start transfer.
2. With any transfer completion interrupt.
3. With incomplete isoc in/out interrupt.
4. With EP disabled interrupt because of incomplete transfer.
5. With OUT token received while EP disabled interrupt (for OUT
transfers).
6. With NAK replied to IN token interrupt (for IN transfers).

As part of ISOC flow, additionally fixed 'current' and 'target' frame
calculation functions. In HS mode SOF limits provided by DSTS register
is 0x3fff, but in non HS mode this limit is 0x7ff.

Tested by internal tool which also using for dwc3 testing.
Signed-off-by: default avatarMinas Harutyunyan <Minas.Harutyunyan@synopsys.com>
Cc: stable <stable@vger.kernel.org>
Link: https://lore.kernel.org/r/95d1423adf4b0f68187c9894820c4b7e964a3f7f.1631175721.git.Minas.Harutyunyan@synopsys.comSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 8cfac9a6
...@@ -115,10 +115,16 @@ static inline bool using_desc_dma(struct dwc2_hsotg *hsotg) ...@@ -115,10 +115,16 @@ static inline bool using_desc_dma(struct dwc2_hsotg *hsotg)
*/ */
static inline void dwc2_gadget_incr_frame_num(struct dwc2_hsotg_ep *hs_ep) static inline void dwc2_gadget_incr_frame_num(struct dwc2_hsotg_ep *hs_ep)
{ {
struct dwc2_hsotg *hsotg = hs_ep->parent;
u16 limit = DSTS_SOFFN_LIMIT;
if (hsotg->gadget.speed != USB_SPEED_HIGH)
limit >>= 3;
hs_ep->target_frame += hs_ep->interval; hs_ep->target_frame += hs_ep->interval;
if (hs_ep->target_frame > DSTS_SOFFN_LIMIT) { if (hs_ep->target_frame > limit) {
hs_ep->frame_overrun = true; hs_ep->frame_overrun = true;
hs_ep->target_frame &= DSTS_SOFFN_LIMIT; hs_ep->target_frame &= limit;
} else { } else {
hs_ep->frame_overrun = false; hs_ep->frame_overrun = false;
} }
...@@ -136,10 +142,16 @@ static inline void dwc2_gadget_incr_frame_num(struct dwc2_hsotg_ep *hs_ep) ...@@ -136,10 +142,16 @@ static inline void dwc2_gadget_incr_frame_num(struct dwc2_hsotg_ep *hs_ep)
*/ */
static inline void dwc2_gadget_dec_frame_num_by_one(struct dwc2_hsotg_ep *hs_ep) static inline void dwc2_gadget_dec_frame_num_by_one(struct dwc2_hsotg_ep *hs_ep)
{ {
struct dwc2_hsotg *hsotg = hs_ep->parent;
u16 limit = DSTS_SOFFN_LIMIT;
if (hsotg->gadget.speed != USB_SPEED_HIGH)
limit >>= 3;
if (hs_ep->target_frame) if (hs_ep->target_frame)
hs_ep->target_frame -= 1; hs_ep->target_frame -= 1;
else else
hs_ep->target_frame = DSTS_SOFFN_LIMIT; hs_ep->target_frame = limit;
} }
/** /**
...@@ -1018,6 +1030,12 @@ static void dwc2_gadget_start_isoc_ddma(struct dwc2_hsotg_ep *hs_ep) ...@@ -1018,6 +1030,12 @@ static void dwc2_gadget_start_isoc_ddma(struct dwc2_hsotg_ep *hs_ep)
dwc2_writel(hsotg, ctrl, depctl); dwc2_writel(hsotg, ctrl, depctl);
} }
static bool dwc2_gadget_target_frame_elapsed(struct dwc2_hsotg_ep *hs_ep);
static void dwc2_hsotg_complete_request(struct dwc2_hsotg *hsotg,
struct dwc2_hsotg_ep *hs_ep,
struct dwc2_hsotg_req *hs_req,
int result);
/** /**
* dwc2_hsotg_start_req - start a USB request from an endpoint's queue * dwc2_hsotg_start_req - start a USB request from an endpoint's queue
* @hsotg: The controller state. * @hsotg: The controller state.
...@@ -1170,14 +1188,19 @@ static void dwc2_hsotg_start_req(struct dwc2_hsotg *hsotg, ...@@ -1170,14 +1188,19 @@ static void dwc2_hsotg_start_req(struct dwc2_hsotg *hsotg,
} }
} }
if (hs_ep->isochronous && hs_ep->interval == 1) { if (hs_ep->isochronous) {
hs_ep->target_frame = dwc2_hsotg_read_frameno(hsotg); if (!dwc2_gadget_target_frame_elapsed(hs_ep)) {
dwc2_gadget_incr_frame_num(hs_ep); if (hs_ep->interval == 1) {
if (hs_ep->target_frame & 0x1)
if (hs_ep->target_frame & 0x1) ctrl |= DXEPCTL_SETODDFR;
ctrl |= DXEPCTL_SETODDFR; else
else ctrl |= DXEPCTL_SETEVENFR;
ctrl |= DXEPCTL_SETEVENFR; }
ctrl |= DXEPCTL_CNAK;
} else {
dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, -ENODATA);
return;
}
} }
ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */
...@@ -1325,12 +1348,16 @@ static bool dwc2_gadget_target_frame_elapsed(struct dwc2_hsotg_ep *hs_ep) ...@@ -1325,12 +1348,16 @@ static bool dwc2_gadget_target_frame_elapsed(struct dwc2_hsotg_ep *hs_ep)
u32 target_frame = hs_ep->target_frame; u32 target_frame = hs_ep->target_frame;
u32 current_frame = hsotg->frame_number; u32 current_frame = hsotg->frame_number;
bool frame_overrun = hs_ep->frame_overrun; bool frame_overrun = hs_ep->frame_overrun;
u16 limit = DSTS_SOFFN_LIMIT;
if (hsotg->gadget.speed != USB_SPEED_HIGH)
limit >>= 3;
if (!frame_overrun && current_frame >= target_frame) if (!frame_overrun && current_frame >= target_frame)
return true; return true;
if (frame_overrun && current_frame >= target_frame && if (frame_overrun && current_frame >= target_frame &&
((current_frame - target_frame) < DSTS_SOFFN_LIMIT / 2)) ((current_frame - target_frame) < limit / 2))
return true; return true;
return false; return false;
...@@ -1713,11 +1740,9 @@ static struct dwc2_hsotg_req *get_ep_head(struct dwc2_hsotg_ep *hs_ep) ...@@ -1713,11 +1740,9 @@ static struct dwc2_hsotg_req *get_ep_head(struct dwc2_hsotg_ep *hs_ep)
*/ */
static void dwc2_gadget_start_next_request(struct dwc2_hsotg_ep *hs_ep) static void dwc2_gadget_start_next_request(struct dwc2_hsotg_ep *hs_ep)
{ {
u32 mask;
struct dwc2_hsotg *hsotg = hs_ep->parent; struct dwc2_hsotg *hsotg = hs_ep->parent;
int dir_in = hs_ep->dir_in; int dir_in = hs_ep->dir_in;
struct dwc2_hsotg_req *hs_req; struct dwc2_hsotg_req *hs_req;
u32 epmsk_reg = dir_in ? DIEPMSK : DOEPMSK;
if (!list_empty(&hs_ep->queue)) { if (!list_empty(&hs_ep->queue)) {
hs_req = get_ep_head(hs_ep); hs_req = get_ep_head(hs_ep);
...@@ -1733,9 +1758,6 @@ static void dwc2_gadget_start_next_request(struct dwc2_hsotg_ep *hs_ep) ...@@ -1733,9 +1758,6 @@ static void dwc2_gadget_start_next_request(struct dwc2_hsotg_ep *hs_ep)
} else { } else {
dev_dbg(hsotg->dev, "%s: No more ISOC-OUT requests\n", dev_dbg(hsotg->dev, "%s: No more ISOC-OUT requests\n",
__func__); __func__);
mask = dwc2_readl(hsotg, epmsk_reg);
mask |= DOEPMSK_OUTTKNEPDISMSK;
dwc2_writel(hsotg, mask, epmsk_reg);
} }
} }
...@@ -2306,19 +2328,6 @@ static void dwc2_hsotg_ep0_zlp(struct dwc2_hsotg *hsotg, bool dir_in) ...@@ -2306,19 +2328,6 @@ static void dwc2_hsotg_ep0_zlp(struct dwc2_hsotg *hsotg, bool dir_in)
dwc2_hsotg_program_zlp(hsotg, hsotg->eps_out[0]); dwc2_hsotg_program_zlp(hsotg, hsotg->eps_out[0]);
} }
static void dwc2_hsotg_change_ep_iso_parity(struct dwc2_hsotg *hsotg,
u32 epctl_reg)
{
u32 ctrl;
ctrl = dwc2_readl(hsotg, epctl_reg);
if (ctrl & DXEPCTL_EOFRNUM)
ctrl |= DXEPCTL_SETEVENFR;
else
ctrl |= DXEPCTL_SETODDFR;
dwc2_writel(hsotg, ctrl, epctl_reg);
}
/* /*
* dwc2_gadget_get_xfersize_ddma - get transferred bytes amount from desc * dwc2_gadget_get_xfersize_ddma - get transferred bytes amount from desc
* @hs_ep - The endpoint on which transfer went * @hs_ep - The endpoint on which transfer went
...@@ -2439,20 +2448,11 @@ static void dwc2_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, int epnum) ...@@ -2439,20 +2448,11 @@ static void dwc2_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, int epnum)
dwc2_hsotg_ep0_zlp(hsotg, true); dwc2_hsotg_ep0_zlp(hsotg, true);
} }
/*
* Slave mode OUT transfers do not go through XferComplete so
* adjust the ISOC parity here.
*/
if (!using_dma(hsotg)) {
if (hs_ep->isochronous && hs_ep->interval == 1)
dwc2_hsotg_change_ep_iso_parity(hsotg, DOEPCTL(epnum));
else if (hs_ep->isochronous && hs_ep->interval > 1)
dwc2_gadget_incr_frame_num(hs_ep);
}
/* Set actual frame number for completed transfers */ /* Set actual frame number for completed transfers */
if (!using_desc_dma(hsotg) && hs_ep->isochronous) if (!using_desc_dma(hsotg) && hs_ep->isochronous) {
req->frame_number = hsotg->frame_number; req->frame_number = hs_ep->target_frame;
dwc2_gadget_incr_frame_num(hs_ep);
}
dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, result); dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, result);
} }
...@@ -2766,6 +2766,12 @@ static void dwc2_hsotg_complete_in(struct dwc2_hsotg *hsotg, ...@@ -2766,6 +2766,12 @@ static void dwc2_hsotg_complete_in(struct dwc2_hsotg *hsotg,
return; return;
} }
/* Set actual frame number for completed transfers */
if (!using_desc_dma(hsotg) && hs_ep->isochronous) {
hs_req->req.frame_number = hs_ep->target_frame;
dwc2_gadget_incr_frame_num(hs_ep);
}
dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, 0);
} }
...@@ -2826,23 +2832,18 @@ static void dwc2_gadget_handle_ep_disabled(struct dwc2_hsotg_ep *hs_ep) ...@@ -2826,23 +2832,18 @@ static void dwc2_gadget_handle_ep_disabled(struct dwc2_hsotg_ep *hs_ep)
dwc2_hsotg_txfifo_flush(hsotg, hs_ep->fifo_index); dwc2_hsotg_txfifo_flush(hsotg, hs_ep->fifo_index);
if (hs_ep->isochronous) {
dwc2_hsotg_complete_in(hsotg, hs_ep);
return;
}
if ((epctl & DXEPCTL_STALL) && (epctl & DXEPCTL_EPTYPE_BULK)) { if ((epctl & DXEPCTL_STALL) && (epctl & DXEPCTL_EPTYPE_BULK)) {
int dctl = dwc2_readl(hsotg, DCTL); int dctl = dwc2_readl(hsotg, DCTL);
dctl |= DCTL_CGNPINNAK; dctl |= DCTL_CGNPINNAK;
dwc2_writel(hsotg, dctl, DCTL); dwc2_writel(hsotg, dctl, DCTL);
} }
return; } else {
}
if (dctl & DCTL_GOUTNAKSTS) { if (dctl & DCTL_GOUTNAKSTS) {
dctl |= DCTL_CGOUTNAK; dctl |= DCTL_CGOUTNAK;
dwc2_writel(hsotg, dctl, DCTL); dwc2_writel(hsotg, dctl, DCTL);
}
} }
if (!hs_ep->isochronous) if (!hs_ep->isochronous)
...@@ -2863,8 +2864,6 @@ static void dwc2_gadget_handle_ep_disabled(struct dwc2_hsotg_ep *hs_ep) ...@@ -2863,8 +2864,6 @@ static void dwc2_gadget_handle_ep_disabled(struct dwc2_hsotg_ep *hs_ep)
/* Update current frame number value. */ /* Update current frame number value. */
hsotg->frame_number = dwc2_hsotg_read_frameno(hsotg); hsotg->frame_number = dwc2_hsotg_read_frameno(hsotg);
} while (dwc2_gadget_target_frame_elapsed(hs_ep)); } while (dwc2_gadget_target_frame_elapsed(hs_ep));
dwc2_gadget_start_next_request(hs_ep);
} }
/** /**
...@@ -2881,8 +2880,8 @@ static void dwc2_gadget_handle_ep_disabled(struct dwc2_hsotg_ep *hs_ep) ...@@ -2881,8 +2880,8 @@ static void dwc2_gadget_handle_ep_disabled(struct dwc2_hsotg_ep *hs_ep)
static void dwc2_gadget_handle_out_token_ep_disabled(struct dwc2_hsotg_ep *ep) static void dwc2_gadget_handle_out_token_ep_disabled(struct dwc2_hsotg_ep *ep)
{ {
struct dwc2_hsotg *hsotg = ep->parent; struct dwc2_hsotg *hsotg = ep->parent;
struct dwc2_hsotg_req *hs_req;
int dir_in = ep->dir_in; int dir_in = ep->dir_in;
u32 doepmsk;
if (dir_in || !ep->isochronous) if (dir_in || !ep->isochronous)
return; return;
...@@ -2896,28 +2895,39 @@ static void dwc2_gadget_handle_out_token_ep_disabled(struct dwc2_hsotg_ep *ep) ...@@ -2896,28 +2895,39 @@ static void dwc2_gadget_handle_out_token_ep_disabled(struct dwc2_hsotg_ep *ep)
return; return;
} }
if (ep->interval > 1 && if (ep->target_frame == TARGET_FRAME_INITIAL) {
ep->target_frame == TARGET_FRAME_INITIAL) {
u32 ctrl; u32 ctrl;
ep->target_frame = hsotg->frame_number; ep->target_frame = hsotg->frame_number;
dwc2_gadget_incr_frame_num(ep); if (ep->interval > 1) {
ctrl = dwc2_readl(hsotg, DOEPCTL(ep->index));
if (ep->target_frame & 0x1)
ctrl |= DXEPCTL_SETODDFR;
else
ctrl |= DXEPCTL_SETEVENFR;
ctrl = dwc2_readl(hsotg, DOEPCTL(ep->index)); dwc2_writel(hsotg, ctrl, DOEPCTL(ep->index));
if (ep->target_frame & 0x1) }
ctrl |= DXEPCTL_SETODDFR; }
else
ctrl |= DXEPCTL_SETEVENFR; while (dwc2_gadget_target_frame_elapsed(ep)) {
hs_req = get_ep_head(ep);
if (hs_req)
dwc2_hsotg_complete_request(hsotg, ep, hs_req, -ENODATA);
dwc2_writel(hsotg, ctrl, DOEPCTL(ep->index)); dwc2_gadget_incr_frame_num(ep);
/* Update current frame number value. */
hsotg->frame_number = dwc2_hsotg_read_frameno(hsotg);
} }
dwc2_gadget_start_next_request(ep); if (!ep->req)
doepmsk = dwc2_readl(hsotg, DOEPMSK); dwc2_gadget_start_next_request(ep);
doepmsk &= ~DOEPMSK_OUTTKNEPDISMSK;
dwc2_writel(hsotg, doepmsk, DOEPMSK);
} }
static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg,
struct dwc2_hsotg_ep *hs_ep);
/** /**
* dwc2_gadget_handle_nak - handle NAK interrupt * dwc2_gadget_handle_nak - handle NAK interrupt
* @hs_ep: The endpoint on which interrupt is asserted. * @hs_ep: The endpoint on which interrupt is asserted.
...@@ -2935,7 +2945,9 @@ static void dwc2_gadget_handle_out_token_ep_disabled(struct dwc2_hsotg_ep *ep) ...@@ -2935,7 +2945,9 @@ static void dwc2_gadget_handle_out_token_ep_disabled(struct dwc2_hsotg_ep *ep)
static void dwc2_gadget_handle_nak(struct dwc2_hsotg_ep *hs_ep) static void dwc2_gadget_handle_nak(struct dwc2_hsotg_ep *hs_ep)
{ {
struct dwc2_hsotg *hsotg = hs_ep->parent; struct dwc2_hsotg *hsotg = hs_ep->parent;
struct dwc2_hsotg_req *hs_req;
int dir_in = hs_ep->dir_in; int dir_in = hs_ep->dir_in;
u32 ctrl;
if (!dir_in || !hs_ep->isochronous) if (!dir_in || !hs_ep->isochronous)
return; return;
...@@ -2977,13 +2989,29 @@ static void dwc2_gadget_handle_nak(struct dwc2_hsotg_ep *hs_ep) ...@@ -2977,13 +2989,29 @@ static void dwc2_gadget_handle_nak(struct dwc2_hsotg_ep *hs_ep)
dwc2_writel(hsotg, ctrl, DIEPCTL(hs_ep->index)); dwc2_writel(hsotg, ctrl, DIEPCTL(hs_ep->index));
} }
dwc2_hsotg_complete_request(hsotg, hs_ep,
get_ep_head(hs_ep), 0);
} }
if (!using_desc_dma(hsotg)) if (using_desc_dma(hsotg))
return;
ctrl = dwc2_readl(hsotg, DIEPCTL(hs_ep->index));
if (ctrl & DXEPCTL_EPENA)
dwc2_hsotg_ep_stop_xfr(hsotg, hs_ep);
else
dwc2_hsotg_txfifo_flush(hsotg, hs_ep->fifo_index);
while (dwc2_gadget_target_frame_elapsed(hs_ep)) {
hs_req = get_ep_head(hs_ep);
if (hs_req)
dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, -ENODATA);
dwc2_gadget_incr_frame_num(hs_ep); dwc2_gadget_incr_frame_num(hs_ep);
/* Update current frame number value. */
hsotg->frame_number = dwc2_hsotg_read_frameno(hsotg);
}
if (!hs_ep->req)
dwc2_gadget_start_next_request(hs_ep);
} }
/** /**
...@@ -3048,12 +3076,8 @@ static void dwc2_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, ...@@ -3048,12 +3076,8 @@ static void dwc2_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx,
* need to look at completing IN requests here * need to look at completing IN requests here
* if operating slave mode * if operating slave mode
*/ */
if (hs_ep->isochronous && hs_ep->interval > 1) if (!hs_ep->isochronous || !(ints & DXEPINT_NAKINTRPT))
dwc2_gadget_incr_frame_num(hs_ep); dwc2_hsotg_complete_in(hsotg, hs_ep);
dwc2_hsotg_complete_in(hsotg, hs_ep);
if (ints & DXEPINT_NAKINTRPT)
ints &= ~DXEPINT_NAKINTRPT;
if (idx == 0 && !hs_ep->req) if (idx == 0 && !hs_ep->req)
dwc2_hsotg_enqueue_setup(hsotg); dwc2_hsotg_enqueue_setup(hsotg);
...@@ -3062,10 +3086,8 @@ static void dwc2_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, ...@@ -3062,10 +3086,8 @@ static void dwc2_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx,
* We're using DMA, we need to fire an OutDone here * We're using DMA, we need to fire an OutDone here
* as we ignore the RXFIFO. * as we ignore the RXFIFO.
*/ */
if (hs_ep->isochronous && hs_ep->interval > 1) if (!hs_ep->isochronous || !(ints & DXEPINT_OUTTKNEPDIS))
dwc2_gadget_incr_frame_num(hs_ep); dwc2_hsotg_handle_outdone(hsotg, idx);
dwc2_hsotg_handle_outdone(hsotg, idx);
} }
} }
...@@ -4085,6 +4107,7 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep, ...@@ -4085,6 +4107,7 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep,
mask |= DIEPMSK_NAKMSK; mask |= DIEPMSK_NAKMSK;
dwc2_writel(hsotg, mask, DIEPMSK); dwc2_writel(hsotg, mask, DIEPMSK);
} else { } else {
epctrl |= DXEPCTL_SNAK;
mask = dwc2_readl(hsotg, DOEPMSK); mask = dwc2_readl(hsotg, DOEPMSK);
mask |= DOEPMSK_OUTTKNEPDISMSK; mask |= DOEPMSK_OUTTKNEPDISMSK;
dwc2_writel(hsotg, mask, DOEPMSK); dwc2_writel(hsotg, mask, DOEPMSK);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment