From 8355b2b3082d302091506703d2e4e239f7deed7f Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 22 Aug 2014 20:13:50 +0900 Subject: usb: renesas_usbhs: fix the behavior of some usbhs_pkt_handle Some gadget drivers will call usb_ep_queue() more than once before the first queue doesn't finish. However, this driver didn't handle it correctly. So, this patch fixes the behavior of some usbhs_pkt_handle using the "running" flag. Otherwise, the oops below happens if we use g_ncm driver and when the "iperf -u -c host -b 200M" is running. Unable to handle kernel NULL pointer dereference at virtual address 00000000 pgd = c0004000 [00000000] *pgd=00000000 Internal error: Oops: 80000007 [#1] SMP ARM Modules linked in: usb_f_ncm g_ncm libcomposite u_ether CPU: 0 PID: 0 Comm: swapper/0 Tainted: G W 3.17.0-rc1-00008-g8b2be8a-dirty #20 task: c051c7e0 ti: c0512000 task.ti: c0512000 PC is at 0x0 LR is at usbhsf_pkt_handler+0xa8/0x114 pc : [<00000000>] lr : [] psr: 60000193 sp : c0513ce8 ip : c0513c58 fp : c0513d24 r10: 00000001 r9 : 00000193 r8 : eebec4a0 r7 : eebec410 r6 : eebe0c6c r5 : 00000000 r4 : ee4a2774 r3 : 00000000 r2 : ee251e00 r1 : c0513cf4 r0 : ee4a2774 Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi --- drivers/usb/renesas_usbhs/fifo.c | 25 ++++++++++++++++++++++++- drivers/usb/renesas_usbhs/pipe.c | 13 +++++++++++++ drivers/usb/renesas_usbhs/pipe.h | 4 ++++ 3 files changed, 41 insertions(+), 1 deletion(-) (limited to 'drivers/usb/renesas_usbhs') diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c index 4fd36530bfa3..3efece3c72a3 100644 --- a/drivers/usb/renesas_usbhs/fifo.c +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -544,6 +544,7 @@ static int usbhsf_pio_try_push(struct usbhs_pkt *pkt, int *is_done) usbhsf_send_terminator(pipe, fifo); usbhsf_tx_irq_ctrl(pipe, !*is_done); + usbhs_pipe_running(pipe, !*is_done); usbhs_pipe_enable(pipe); dev_dbg(dev, " send %d (%d/ %d/ %d/ %d)\n", @@ -570,12 +571,21 @@ usbhs_fifo_write_busy: * retry in interrupt */ usbhsf_tx_irq_ctrl(pipe, 1); + usbhs_pipe_running(pipe, 1); return ret; } +static int usbhsf_pio_prepare_push(struct usbhs_pkt *pkt, int *is_done) +{ + if (usbhs_pipe_is_running(pkt->pipe)) + return 0; + + return usbhsf_pio_try_push(pkt, is_done); +} + struct usbhs_pkt_handle usbhs_fifo_pio_push_handler = { - .prepare = usbhsf_pio_try_push, + .prepare = usbhsf_pio_prepare_push, .try_run = usbhsf_pio_try_push, }; @@ -589,6 +599,9 @@ static int usbhsf_prepare_pop(struct usbhs_pkt *pkt, int *is_done) if (usbhs_pipe_is_busy(pipe)) return 0; + if (usbhs_pipe_is_running(pipe)) + return 0; + /* * pipe enable to prepare packet receive */ @@ -597,6 +610,7 @@ static int usbhsf_prepare_pop(struct usbhs_pkt *pkt, int *is_done) usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->length); usbhs_pipe_enable(pipe); + usbhs_pipe_running(pipe, 1); usbhsf_rx_irq_ctrl(pipe, 1); return 0; @@ -642,6 +656,7 @@ static int usbhsf_pio_try_pop(struct usbhs_pkt *pkt, int *is_done) (total_len < maxp)) { /* short packet */ *is_done = 1; usbhsf_rx_irq_ctrl(pipe, 0); + usbhs_pipe_running(pipe, 0); usbhs_pipe_disable(pipe); /* disable pipe first */ } @@ -805,6 +820,7 @@ static void xfer_work(struct work_struct *work) dev_dbg(dev, " %s %d (%d/ %d)\n", fifo->name, usbhs_pipe_number(pipe), pkt->length, pkt->zero); + usbhs_pipe_running(pipe, 1); usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->trans); usbhs_pipe_enable(pipe); usbhsf_dma_start(pipe, fifo); @@ -836,6 +852,10 @@ static int usbhsf_dma_prepare_push(struct usbhs_pkt *pkt, int *is_done) if ((uintptr_t)(pkt->buf + pkt->actual) & 0x7) /* 8byte alignment */ goto usbhsf_pio_prepare_push; + /* return at this time if the pipe is running */ + if (usbhs_pipe_is_running(pipe)) + return 0; + /* get enable DMA fifo */ fifo = usbhsf_get_dma_fifo(priv, pkt); if (!fifo) @@ -873,6 +893,7 @@ static int usbhsf_dma_push_done(struct usbhs_pkt *pkt, int *is_done) pkt->actual = pkt->trans; *is_done = !pkt->zero; /* send zero packet ? */ + usbhs_pipe_running(pipe, !*is_done); usbhsf_dma_stop(pipe, pipe->fifo); usbhsf_dma_unmap(pkt); @@ -972,8 +993,10 @@ static int usbhsf_dma_pop_done(struct usbhs_pkt *pkt, int *is_done) if ((pkt->actual == pkt->length) || /* receive all data */ (pkt->trans < maxp)) { /* short packet */ *is_done = 1; + usbhs_pipe_running(pipe, 0); } else { /* re-enable */ + usbhs_pipe_running(pipe, 0); usbhsf_prepare_pop(pkt, is_done); } diff --git a/drivers/usb/renesas_usbhs/pipe.c b/drivers/usb/renesas_usbhs/pipe.c index 75fbcf6b102e..040bcefcb040 100644 --- a/drivers/usb/renesas_usbhs/pipe.c +++ b/drivers/usb/renesas_usbhs/pipe.c @@ -578,6 +578,19 @@ int usbhs_pipe_is_dir_host(struct usbhs_pipe *pipe) return usbhsp_flags_has(pipe, IS_DIR_HOST); } +int usbhs_pipe_is_running(struct usbhs_pipe *pipe) +{ + return usbhsp_flags_has(pipe, IS_RUNNING); +} + +void usbhs_pipe_running(struct usbhs_pipe *pipe, int running) +{ + if (running) + usbhsp_flags_set(pipe, IS_RUNNING); + else + usbhsp_flags_clr(pipe, IS_RUNNING); +} + void usbhs_pipe_data_sequence(struct usbhs_pipe *pipe, int sequence) { u16 mask = (SQCLR | SQSET); diff --git a/drivers/usb/renesas_usbhs/pipe.h b/drivers/usb/renesas_usbhs/pipe.h index 406f36d050e4..d24a05972370 100644 --- a/drivers/usb/renesas_usbhs/pipe.h +++ b/drivers/usb/renesas_usbhs/pipe.h @@ -36,6 +36,7 @@ struct usbhs_pipe { #define USBHS_PIPE_FLAGS_IS_USED (1 << 0) #define USBHS_PIPE_FLAGS_IS_DIR_IN (1 << 1) #define USBHS_PIPE_FLAGS_IS_DIR_HOST (1 << 2) +#define USBHS_PIPE_FLAGS_IS_RUNNING (1 << 3) struct usbhs_pkt_handle *handler; @@ -80,6 +81,9 @@ int usbhs_pipe_probe(struct usbhs_priv *priv); void usbhs_pipe_remove(struct usbhs_priv *priv); int usbhs_pipe_is_dir_in(struct usbhs_pipe *pipe); int usbhs_pipe_is_dir_host(struct usbhs_pipe *pipe); +int usbhs_pipe_is_running(struct usbhs_pipe *pipe); +void usbhs_pipe_running(struct usbhs_pipe *pipe, int running); + void usbhs_pipe_init(struct usbhs_priv *priv, int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map)); int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe); -- cgit v1.2.3 From c4d8199ba1a7aa390b06db23f4532e2c1875aefb Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 22 Aug 2014 20:14:00 +0900 Subject: usb: renesas_usbhs: protect mod->irq_{bemp,brdy}sts by spin lock This patch protects the mod->irq_bempsts and mod->irq_brdysts by spin lock in the usbhs_status_get_each_irq() because other functions will write them during spin lock. Otherwise, the driver will clears the BRDYSTS and/or BEMPSTS wrongly, and then, the transaction will not finish. Also since the driver should use the INTSTS0 and BRDYSTS and BEMPSTS as the same timing, the patch protects them. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi --- drivers/usb/renesas_usbhs/mod.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/usb/renesas_usbhs') diff --git a/drivers/usb/renesas_usbhs/mod.c b/drivers/usb/renesas_usbhs/mod.c index 6a030b931a3b..9a705b15b3a1 100644 --- a/drivers/usb/renesas_usbhs/mod.c +++ b/drivers/usb/renesas_usbhs/mod.c @@ -213,7 +213,10 @@ static int usbhs_status_get_each_irq(struct usbhs_priv *priv, { struct usbhs_mod *mod = usbhs_mod_get_current(priv); u16 intenb0, intenb1; + unsigned long flags; + /******************** spin lock ********************/ + usbhs_lock(priv, flags); state->intsts0 = usbhs_read(priv, INTSTS0); state->intsts1 = usbhs_read(priv, INTSTS1); @@ -229,6 +232,8 @@ static int usbhs_status_get_each_irq(struct usbhs_priv *priv, state->bempsts &= mod->irq_bempsts; state->brdysts &= mod->irq_brdysts; } + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ /* * Check whether the irq enable registers and the irq status are set -- cgit v1.2.3 From c0ed8b23b257a84d103764cdbd490ee5e2749da3 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 22 Aug 2014 20:14:10 +0900 Subject: usb: renesas_usbhs: fix the condition of is_done in usbhsf_dma_push_done This patch fixes the condition of is_done in usbhsf_dma_push_done(). This function will be called after a transmission finished by DMAC. So, the function should check if the transmission packet is short packet or not. Also the function should call try_run to send the zero packet by the pio handler if the "*is_done" is not set. Otherwize, the transaction will not finish if a gadget driver sets the "zero" flag in a transmission. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi --- drivers/usb/renesas_usbhs/fifo.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'drivers/usb/renesas_usbhs') diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c index 3efece3c72a3..1564829951c0 100644 --- a/drivers/usb/renesas_usbhs/fifo.c +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -889,16 +889,29 @@ usbhsf_pio_prepare_push: static int usbhsf_dma_push_done(struct usbhs_pkt *pkt, int *is_done) { struct usbhs_pipe *pipe = pkt->pipe; + int is_short = pkt->trans % usbhs_pipe_get_maxpacket(pipe); - pkt->actual = pkt->trans; + pkt->actual += pkt->trans; + + if (pkt->actual < pkt->length) + *is_done = 0; /* there are remainder data */ + else if (is_short) + *is_done = 1; /* short packet */ + else + *is_done = !pkt->zero; /* send zero packet? */ - *is_done = !pkt->zero; /* send zero packet ? */ usbhs_pipe_running(pipe, !*is_done); usbhsf_dma_stop(pipe, pipe->fifo); usbhsf_dma_unmap(pkt); usbhsf_fifo_unselect(pipe, pipe->fifo); + if (!*is_done) { + /* change handler to PIO */ + pkt->handler = &usbhs_fifo_pio_push_handler; + return pkt->handler->try_run(pkt, is_done); + } + return 0; } -- cgit v1.2.3 From 2743e7f90dc08282d027dbc2f6486f5cb85aa493 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 22 Aug 2014 20:14:28 +0900 Subject: usb: renesas_usbhs: fix the usb_pkt_pop() This patch fixes the usb_pkt_pop(). If a gadget driver calls usb_ep_dequeue(), this driver will call the usb_pkt_pop(). So, the usb_pkt_pop() should cancel the transaction. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi --- drivers/usb/renesas_usbhs/fifo.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) (limited to 'drivers/usb/renesas_usbhs') diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c index 1564829951c0..b0c97a3f1bfe 100644 --- a/drivers/usb/renesas_usbhs/fifo.c +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -108,19 +108,45 @@ static struct usbhs_pkt *__usbhsf_pkt_get(struct usbhs_pipe *pipe) return list_first_entry(&pipe->list, struct usbhs_pkt, node); } +static void usbhsf_fifo_clear(struct usbhs_pipe *pipe, + struct usbhs_fifo *fifo); +static void usbhsf_fifo_unselect(struct usbhs_pipe *pipe, + struct usbhs_fifo *fifo); +static struct dma_chan *usbhsf_dma_chan_get(struct usbhs_fifo *fifo, + struct usbhs_pkt *pkt); +#define usbhsf_dma_map(p) __usbhsf_dma_map_ctrl(p, 1) +#define usbhsf_dma_unmap(p) __usbhsf_dma_map_ctrl(p, 0) +static int __usbhsf_dma_map_ctrl(struct usbhs_pkt *pkt, int map); struct usbhs_pkt *usbhs_pkt_pop(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt) { struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct usbhs_fifo *fifo = usbhs_pipe_to_fifo(pipe); unsigned long flags; /******************** spin lock ********************/ usbhs_lock(priv, flags); + usbhs_pipe_disable(pipe); + if (!pkt) pkt = __usbhsf_pkt_get(pipe); - if (pkt) + if (pkt) { + struct dma_chan *chan = NULL; + + if (fifo) + chan = usbhsf_dma_chan_get(fifo, pkt); + if (chan) { + dmaengine_terminate_all(chan); + usbhsf_fifo_clear(pipe, fifo); + usbhsf_dma_unmap(pkt); + } + __usbhsf_pkt_del(pkt); + } + + if (fifo) + usbhsf_fifo_unselect(pipe, fifo); usbhs_unlock(priv, flags); /******************** spin unlock ******************/ @@ -778,8 +804,6 @@ static void __usbhsf_dma_ctrl(struct usbhs_pipe *pipe, usbhs_bset(priv, fifo->sel, DREQE, dreqe); } -#define usbhsf_dma_map(p) __usbhsf_dma_map_ctrl(p, 1) -#define usbhsf_dma_unmap(p) __usbhsf_dma_map_ctrl(p, 0) static int __usbhsf_dma_map_ctrl(struct usbhs_pkt *pkt, int map) { struct usbhs_pipe *pipe = pkt->pipe; -- cgit v1.2.3