| From 6eee4a6249c5554b9fd4d83f9e53ca3f7ef040ee Mon Sep 17 00:00:00 2001 |
| From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> |
| Date: Tue, 1 Oct 2019 19:10:33 +0900 |
| Subject: [PATCH] usb: renesas_usbhs: gadget: Fix usb_ep_set_{halt,wedge}() |
| behavior |
| |
| commit 4d599cd3a097a85a5c68a2c82b9a48cddf9953ec upstream. |
| |
| According to usb_ep_set_halt()'s description, |
| __usbhsg_ep_set_halt_wedge() should return -EAGAIN if the IN endpoint |
| has any queue or data. Otherwise, this driver is possible to cause |
| just STALL without sending a short packet data on g_mass_storage driver, |
| and then a few resetting a device happens on a host side during |
| a usb enumaration. |
| |
| Fixes: 2f98382dcdfe ("usb: renesas_usbhs: Add Renesas USBHS Gadget") |
| Cc: <stable@vger.kernel.org> # v3.0+ |
| Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> |
| Link: https://lore.kernel.org/r/1569924633-322-3-git-send-email-yoshihiro.shimoda.uh@renesas.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h |
| index 3777af848a35..f62bcfdf05c4 100644 |
| --- a/drivers/usb/renesas_usbhs/common.h |
| +++ b/drivers/usb/renesas_usbhs/common.h |
| @@ -209,6 +209,7 @@ struct usbhs_priv; |
| /* DCPCTR */ |
| #define BSTS (1 << 15) /* Buffer Status */ |
| #define SUREQ (1 << 14) /* Sending SETUP Token */ |
| +#define INBUFM (1 << 14) /* (PIPEnCTR) Transfer Buffer Monitor */ |
| #define CSSTS (1 << 12) /* CSSTS Status */ |
| #define ACLRM (1 << 9) /* Buffer Auto-Clear Mode */ |
| #define SQCLR (1 << 8) /* Toggle Bit Clear */ |
| diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c |
| index 6036cbae8c78..aeb53ec5cc6a 100644 |
| --- a/drivers/usb/renesas_usbhs/fifo.c |
| +++ b/drivers/usb/renesas_usbhs/fifo.c |
| @@ -89,7 +89,7 @@ static void __usbhsf_pkt_del(struct usbhs_pkt *pkt) |
| list_del_init(&pkt->node); |
| } |
| |
| -static struct usbhs_pkt *__usbhsf_pkt_get(struct usbhs_pipe *pipe) |
| +struct usbhs_pkt *__usbhsf_pkt_get(struct usbhs_pipe *pipe) |
| { |
| return list_first_entry_or_null(&pipe->list, struct usbhs_pkt, node); |
| } |
| diff --git a/drivers/usb/renesas_usbhs/fifo.h b/drivers/usb/renesas_usbhs/fifo.h |
| index 88d1816bcda2..c3d3cc35cee0 100644 |
| --- a/drivers/usb/renesas_usbhs/fifo.h |
| +++ b/drivers/usb/renesas_usbhs/fifo.h |
| @@ -97,5 +97,6 @@ void usbhs_pkt_push(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt, |
| void *buf, int len, int zero, int sequence); |
| struct usbhs_pkt *usbhs_pkt_pop(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt); |
| void usbhs_pkt_start(struct usbhs_pipe *pipe); |
| +struct usbhs_pkt *__usbhsf_pkt_get(struct usbhs_pipe *pipe); |
| |
| #endif /* RENESAS_USB_FIFO_H */ |
| diff --git a/drivers/usb/renesas_usbhs/mod_gadget.c b/drivers/usb/renesas_usbhs/mod_gadget.c |
| index 821de98e6441..7feac4128a2d 100644 |
| --- a/drivers/usb/renesas_usbhs/mod_gadget.c |
| +++ b/drivers/usb/renesas_usbhs/mod_gadget.c |
| @@ -721,6 +721,7 @@ static int __usbhsg_ep_set_halt_wedge(struct usb_ep *ep, int halt, int wedge) |
| struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); |
| struct device *dev = usbhsg_gpriv_to_dev(gpriv); |
| unsigned long flags; |
| + int ret = 0; |
| |
| dev_dbg(dev, "set halt %d (pipe %d)\n", |
| halt, usbhs_pipe_number(pipe)); |
| @@ -728,6 +729,18 @@ static int __usbhsg_ep_set_halt_wedge(struct usb_ep *ep, int halt, int wedge) |
| /******************** spin lock ********************/ |
| usbhs_lock(priv, flags); |
| |
| + /* |
| + * According to usb_ep_set_halt()'s description, this function should |
| + * return -EAGAIN if the IN endpoint has any queue or data. Note |
| + * that the usbhs_pipe_is_dir_in() returns false if the pipe is an |
| + * IN endpoint in the gadget mode. |
| + */ |
| + if (!usbhs_pipe_is_dir_in(pipe) && (__usbhsf_pkt_get(pipe) || |
| + usbhs_pipe_contains_transmittable_data(pipe))) { |
| + ret = -EAGAIN; |
| + goto out; |
| + } |
| + |
| if (halt) |
| usbhs_pipe_stall(pipe); |
| else |
| @@ -738,10 +751,11 @@ static int __usbhsg_ep_set_halt_wedge(struct usb_ep *ep, int halt, int wedge) |
| else |
| usbhsg_status_clr(gpriv, USBHSG_STATUS_WEDGE); |
| |
| +out: |
| usbhs_unlock(priv, flags); |
| /******************** spin unlock ******************/ |
| |
| - return 0; |
| + return ret; |
| } |
| |
| static int usbhsg_ep_set_halt(struct usb_ep *ep, int value) |
| diff --git a/drivers/usb/renesas_usbhs/pipe.c b/drivers/usb/renesas_usbhs/pipe.c |
| index c4922b96c93b..9e5afdde1adb 100644 |
| --- a/drivers/usb/renesas_usbhs/pipe.c |
| +++ b/drivers/usb/renesas_usbhs/pipe.c |
| @@ -277,6 +277,21 @@ int usbhs_pipe_is_accessible(struct usbhs_pipe *pipe) |
| return -EBUSY; |
| } |
| |
| +bool usbhs_pipe_contains_transmittable_data(struct usbhs_pipe *pipe) |
| +{ |
| + u16 val; |
| + |
| + /* Do not support for DCP pipe */ |
| + if (usbhs_pipe_is_dcp(pipe)) |
| + return false; |
| + |
| + val = usbhsp_pipectrl_get(pipe); |
| + if (val & INBUFM) |
| + return true; |
| + |
| + return false; |
| +} |
| + |
| /* |
| * PID ctrl |
| */ |
| diff --git a/drivers/usb/renesas_usbhs/pipe.h b/drivers/usb/renesas_usbhs/pipe.h |
| index 3080423e600c..3b130529408b 100644 |
| --- a/drivers/usb/renesas_usbhs/pipe.h |
| +++ b/drivers/usb/renesas_usbhs/pipe.h |
| @@ -83,6 +83,7 @@ void usbhs_pipe_clear(struct usbhs_pipe *pipe); |
| void usbhs_pipe_clear_without_sequence(struct usbhs_pipe *pipe, |
| int needs_bfre, int bfre_enable); |
| int usbhs_pipe_is_accessible(struct usbhs_pipe *pipe); |
| +bool usbhs_pipe_contains_transmittable_data(struct usbhs_pipe *pipe); |
| void usbhs_pipe_enable(struct usbhs_pipe *pipe); |
| void usbhs_pipe_disable(struct usbhs_pipe *pipe); |
| void usbhs_pipe_stall(struct usbhs_pipe *pipe); |
| -- |
| 2.7.4 |
| |