Skip to content

Commit

Permalink
usb: dwc2: gadget: Fix ISOC flow for BDMA and Slave
Browse files Browse the repository at this point in the history
commit 91bb163 upstream.

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: Minas Harutyunyan <Minas.Harutyunyan@synopsys.com>
Cc: stable <stable@vger.kernel.org>
Link: https://lore.kernel.org/r/95d1423adf4b0f68187c9894820c4b7e964a3f7f.1631175721.git.Minas.Harutyunyan@synopsys.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Minas Harutyunyan authored and gregkh committed Sep 30, 2021
1 parent 40fe835 commit eba5985
Showing 1 changed file with 106 additions and 83 deletions.
189 changes: 106 additions & 83 deletions drivers/usb/dwc2/gadget.c
Expand Up @@ -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)
{
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;
if (hs_ep->target_frame > DSTS_SOFFN_LIMIT) {
if (hs_ep->target_frame > limit) {
hs_ep->frame_overrun = true;
hs_ep->target_frame &= DSTS_SOFFN_LIMIT;
hs_ep->target_frame &= limit;
} else {
hs_ep->frame_overrun = false;
}
Expand All @@ -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)
{
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)
hs_ep->target_frame -= 1;
else
hs_ep->target_frame = DSTS_SOFFN_LIMIT;
hs_ep->target_frame = limit;
}

/**
Expand Down Expand Up @@ -1018,6 +1030,12 @@ static void dwc2_gadget_start_isoc_ddma(struct dwc2_hsotg_ep *hs_ep)
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
* @hsotg: The controller state.
Expand Down Expand Up @@ -1170,14 +1188,19 @@ static void dwc2_hsotg_start_req(struct dwc2_hsotg *hsotg,
}
}

if (hs_ep->isochronous && hs_ep->interval == 1) {
hs_ep->target_frame = dwc2_hsotg_read_frameno(hsotg);
dwc2_gadget_incr_frame_num(hs_ep);

if (hs_ep->target_frame & 0x1)
ctrl |= DXEPCTL_SETODDFR;
else
ctrl |= DXEPCTL_SETEVENFR;
if (hs_ep->isochronous) {
if (!dwc2_gadget_target_frame_elapsed(hs_ep)) {
if (hs_ep->interval == 1) {
if (hs_ep->target_frame & 0x1)
ctrl |= DXEPCTL_SETODDFR;
else
ctrl |= DXEPCTL_SETEVENFR;
}
ctrl |= DXEPCTL_CNAK;
} else {
dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, -ENODATA);
return;
}
}

ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */
Expand Down Expand Up @@ -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 current_frame = hsotg->frame_number;
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)
return true;

if (frame_overrun && current_frame >= target_frame &&
((current_frame - target_frame) < DSTS_SOFFN_LIMIT / 2))
((current_frame - target_frame) < limit / 2))
return true;

return false;
Expand Down Expand Up @@ -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)
{
u32 mask;
struct dwc2_hsotg *hsotg = hs_ep->parent;
int dir_in = hs_ep->dir_in;
struct dwc2_hsotg_req *hs_req;
u32 epmsk_reg = dir_in ? DIEPMSK : DOEPMSK;

if (!list_empty(&hs_ep->queue)) {
hs_req = get_ep_head(hs_ep);
Expand All @@ -1733,9 +1758,6 @@ static void dwc2_gadget_start_next_request(struct dwc2_hsotg_ep *hs_ep)
} else {
dev_dbg(hsotg->dev, "%s: No more ISOC-OUT requests\n",
__func__);
mask = dwc2_readl(hsotg, epmsk_reg);
mask |= DOEPMSK_OUTTKNEPDISMSK;
dwc2_writel(hsotg, mask, epmsk_reg);
}
}

Expand Down Expand Up @@ -2305,19 +2327,6 @@ static void dwc2_hsotg_ep0_zlp(struct dwc2_hsotg *hsotg, bool dir_in)
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
* @hs_ep - The endpoint on which transfer went
Expand Down Expand Up @@ -2438,20 +2447,11 @@ static void dwc2_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, int epnum)
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 */
if (!using_desc_dma(hsotg) && hs_ep->isochronous)
req->frame_number = hsotg->frame_number;
if (!using_desc_dma(hsotg) && hs_ep->isochronous) {
req->frame_number = hs_ep->target_frame;
dwc2_gadget_incr_frame_num(hs_ep);
}

dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, result);
}
Expand Down Expand Up @@ -2765,6 +2765,12 @@ static void dwc2_hsotg_complete_in(struct dwc2_hsotg *hsotg,
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);
}

Expand Down Expand Up @@ -2825,23 +2831,18 @@ static void dwc2_gadget_handle_ep_disabled(struct dwc2_hsotg_ep *hs_ep)

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)) {
int dctl = dwc2_readl(hsotg, DCTL);

dctl |= DCTL_CGNPINNAK;
dwc2_writel(hsotg, dctl, DCTL);
}
return;
}
} else {

if (dctl & DCTL_GOUTNAKSTS) {
dctl |= DCTL_CGOUTNAK;
dwc2_writel(hsotg, dctl, DCTL);
if (dctl & DCTL_GOUTNAKSTS) {
dctl |= DCTL_CGOUTNAK;
dwc2_writel(hsotg, dctl, DCTL);
}
}

if (!hs_ep->isochronous)
Expand All @@ -2862,8 +2863,6 @@ static void dwc2_gadget_handle_ep_disabled(struct dwc2_hsotg_ep *hs_ep)
/* Update current frame number value. */
hsotg->frame_number = dwc2_hsotg_read_frameno(hsotg);
} while (dwc2_gadget_target_frame_elapsed(hs_ep));

dwc2_gadget_start_next_request(hs_ep);
}

/**
Expand All @@ -2880,8 +2879,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)
{
struct dwc2_hsotg *hsotg = ep->parent;
struct dwc2_hsotg_req *hs_req;
int dir_in = ep->dir_in;
u32 doepmsk;

if (dir_in || !ep->isochronous)
return;
Expand All @@ -2895,28 +2894,39 @@ static void dwc2_gadget_handle_out_token_ep_disabled(struct dwc2_hsotg_ep *ep)
return;
}

if (ep->interval > 1 &&
ep->target_frame == TARGET_FRAME_INITIAL) {
if (ep->target_frame == TARGET_FRAME_INITIAL) {
u32 ctrl;

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));
if (ep->target_frame & 0x1)
ctrl |= DXEPCTL_SETODDFR;
else
ctrl |= DXEPCTL_SETEVENFR;
dwc2_writel(hsotg, ctrl, DOEPCTL(ep->index));
}
}

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);
doepmsk = dwc2_readl(hsotg, DOEPMSK);
doepmsk &= ~DOEPMSK_OUTTKNEPDISMSK;
dwc2_writel(hsotg, doepmsk, DOEPMSK);
if (!ep->req)
dwc2_gadget_start_next_request(ep);

}

static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg,
struct dwc2_hsotg_ep *hs_ep);

/**
* dwc2_gadget_handle_nak - handle NAK interrupt
* @hs_ep: The endpoint on which interrupt is asserted.
Expand All @@ -2934,7 +2944,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)
{
struct dwc2_hsotg *hsotg = hs_ep->parent;
struct dwc2_hsotg_req *hs_req;
int dir_in = hs_ep->dir_in;
u32 ctrl;

if (!dir_in || !hs_ep->isochronous)
return;
Expand Down Expand Up @@ -2976,13 +2988,29 @@ static void dwc2_gadget_handle_nak(struct dwc2_hsotg_ep *hs_ep)

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);
/* Update current frame number value. */
hsotg->frame_number = dwc2_hsotg_read_frameno(hsotg);
}

if (!hs_ep->req)
dwc2_gadget_start_next_request(hs_ep);
}

/**
Expand Down Expand Up @@ -3047,12 +3075,8 @@ static void dwc2_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx,
* need to look at completing IN requests here
* if operating slave mode
*/
if (hs_ep->isochronous && hs_ep->interval > 1)
dwc2_gadget_incr_frame_num(hs_ep);

dwc2_hsotg_complete_in(hsotg, hs_ep);
if (ints & DXEPINT_NAKINTRPT)
ints &= ~DXEPINT_NAKINTRPT;
if (!hs_ep->isochronous || !(ints & DXEPINT_NAKINTRPT))
dwc2_hsotg_complete_in(hsotg, hs_ep);

if (idx == 0 && !hs_ep->req)
dwc2_hsotg_enqueue_setup(hsotg);
Expand All @@ -3061,10 +3085,8 @@ static void dwc2_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx,
* We're using DMA, we need to fire an OutDone here
* as we ignore the RXFIFO.
*/
if (hs_ep->isochronous && hs_ep->interval > 1)
dwc2_gadget_incr_frame_num(hs_ep);

dwc2_hsotg_handle_outdone(hsotg, idx);
if (!hs_ep->isochronous || !(ints & DXEPINT_OUTTKNEPDIS))
dwc2_hsotg_handle_outdone(hsotg, idx);
}
}

Expand Down Expand Up @@ -4083,6 +4105,7 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep,
mask |= DIEPMSK_NAKMSK;
dwc2_writel(hsotg, mask, DIEPMSK);
} else {
epctrl |= DXEPCTL_SNAK;
mask = dwc2_readl(hsotg, DOEPMSK);
mask |= DOEPMSK_OUTTKNEPDISMSK;
dwc2_writel(hsotg, mask, DOEPMSK);
Expand Down

0 comments on commit eba5985

Please sign in to comment.