usb: cdnsp: fix lack of ZLP for ep0

Message ID 20221122085138.332434-1-pawell@cadence.com
State New
Headers
Series usb: cdnsp: fix lack of ZLP for ep0 |

Commit Message

Pawel Laszczak Nov. 22, 2022, 8:51 a.m. UTC
  Patch implements the handling of ZLP for control transfer.
To send the ZLP driver must prepare the extra TRB in TD with
length set to zero and TRB type to TRB_NORMAL.
The first TRB must have set TRB_CHAIN flag, TD_SIZE = 1
and TRB type to TRB_DATA.

cc: <stable@vger.kernel.org>
Fixes: 3d82904559f4 ("usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver")
Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/cdnsp-ring.c | 42 ++++++++++++++++++++++++++--------
 1 file changed, 32 insertions(+), 10 deletions(-)
  

Comments

Peter Chen Nov. 27, 2022, 6:44 a.m. UTC | #1
On Tue, Nov 22, 2022 at 4:52 PM Pawel Laszczak <pawell@cadence.com> wrote:
>
> Patch implements the handling of ZLP for control transfer.
> To send the ZLP driver must prepare the extra TRB in TD with
> length set to zero and TRB type to TRB_NORMAL.
> The first TRB must have set TRB_CHAIN flag, TD_SIZE = 1
> and TRB type to TRB_DATA.
>
> cc: <stable@vger.kernel.org>
> Fixes: 3d82904559f4 ("usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver")
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>

Reviewed-by: Peter Chen <peter.chen@kernel.org>

Peter

> ---
>  drivers/usb/cdns3/cdnsp-ring.c | 42 ++++++++++++++++++++++++++--------
>  1 file changed, 32 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/usb/cdns3/cdnsp-ring.c b/drivers/usb/cdns3/cdnsp-ring.c
> index 86e1141e150f..fa1fa9b2ff38 100644
> --- a/drivers/usb/cdns3/cdnsp-ring.c
> +++ b/drivers/usb/cdns3/cdnsp-ring.c
> @@ -2006,10 +2006,11 @@ int cdnsp_queue_bulk_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq)
>
>  int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq)
>  {
> -       u32 field, length_field, remainder;
> +       u32 field, length_field, zlp = 0;
>         struct cdnsp_ep *pep = preq->pep;
>         struct cdnsp_ring *ep_ring;
>         int num_trbs;
> +       u32 maxp;
>         int ret;
>
>         ep_ring = cdnsp_request_to_transfer_ring(pdev, preq);
> @@ -2019,26 +2020,33 @@ int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq)
>         /* 1 TRB for data, 1 for status */
>         num_trbs = (pdev->three_stage_setup) ? 2 : 1;
>
> +       maxp = usb_endpoint_maxp(pep->endpoint.desc);
> +
> +       if (preq->request.zero && preq->request.length &&
> +           (preq->request.length % maxp == 0)) {
> +               num_trbs++;
> +               zlp = 1;
> +       }
> +
>         ret = cdnsp_prepare_transfer(pdev, preq, num_trbs);
>         if (ret)
>                 return ret;
>
>         /* If there's data, queue data TRBs */
> -       if (pdev->ep0_expect_in)
> -               field = TRB_TYPE(TRB_DATA) | TRB_IOC;
> -       else
> -               field = TRB_ISP | TRB_TYPE(TRB_DATA) | TRB_IOC;
> -
>         if (preq->request.length > 0) {
> -               remainder = cdnsp_td_remainder(pdev, 0, preq->request.length,
> -                                              preq->request.length, preq, 1, 0);
> +               field = TRB_TYPE(TRB_DATA);
>
> -               length_field = TRB_LEN(preq->request.length) |
> -                               TRB_TD_SIZE(remainder) | TRB_INTR_TARGET(0);
> +               if (zlp)
> +                       field |= TRB_CHAIN;
> +               else
> +                       field |= TRB_IOC | (pdev->ep0_expect_in ? 0 : TRB_ISP);
>
>                 if (pdev->ep0_expect_in)
>                         field |= TRB_DIR_IN;
>
> +               length_field = TRB_LEN(preq->request.length) |
> +                              TRB_TD_SIZE(zlp) | TRB_INTR_TARGET(0);
> +
>                 cdnsp_queue_trb(pdev, ep_ring, true,
>                                 lower_32_bits(preq->request.dma),
>                                 upper_32_bits(preq->request.dma), length_field,
> @@ -2046,6 +2054,20 @@ int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq)
>                                 TRB_SETUPID(pdev->setup_id) |
>                                 pdev->setup_speed);
>
> +               if (zlp) {
> +                       field = TRB_TYPE(TRB_NORMAL) | TRB_IOC;
> +
> +                       if (!pdev->ep0_expect_in)
> +                               field = TRB_ISP;
> +
> +                       cdnsp_queue_trb(pdev, ep_ring, true,
> +                                       lower_32_bits(preq->request.dma),
> +                                       upper_32_bits(preq->request.dma), 0,
> +                                       field | ep_ring->cycle_state |
> +                                       TRB_SETUPID(pdev->setup_id) |
> +                                       pdev->setup_speed);
> +               }
> +
>                 pdev->ep0_stage = CDNSP_DATA_STAGE;
>         }
>
> --
> 2.25.1
>
  

Patch

diff --git a/drivers/usb/cdns3/cdnsp-ring.c b/drivers/usb/cdns3/cdnsp-ring.c
index 86e1141e150f..fa1fa9b2ff38 100644
--- a/drivers/usb/cdns3/cdnsp-ring.c
+++ b/drivers/usb/cdns3/cdnsp-ring.c
@@ -2006,10 +2006,11 @@  int cdnsp_queue_bulk_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq)
 
 int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq)
 {
-	u32 field, length_field, remainder;
+	u32 field, length_field, zlp = 0;
 	struct cdnsp_ep *pep = preq->pep;
 	struct cdnsp_ring *ep_ring;
 	int num_trbs;
+	u32 maxp;
 	int ret;
 
 	ep_ring = cdnsp_request_to_transfer_ring(pdev, preq);
@@ -2019,26 +2020,33 @@  int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq)
 	/* 1 TRB for data, 1 for status */
 	num_trbs = (pdev->three_stage_setup) ? 2 : 1;
 
+	maxp = usb_endpoint_maxp(pep->endpoint.desc);
+
+	if (preq->request.zero && preq->request.length &&
+	    (preq->request.length % maxp == 0)) {
+		num_trbs++;
+		zlp = 1;
+	}
+
 	ret = cdnsp_prepare_transfer(pdev, preq, num_trbs);
 	if (ret)
 		return ret;
 
 	/* If there's data, queue data TRBs */
-	if (pdev->ep0_expect_in)
-		field = TRB_TYPE(TRB_DATA) | TRB_IOC;
-	else
-		field = TRB_ISP | TRB_TYPE(TRB_DATA) | TRB_IOC;
-
 	if (preq->request.length > 0) {
-		remainder = cdnsp_td_remainder(pdev, 0, preq->request.length,
-					       preq->request.length, preq, 1, 0);
+		field = TRB_TYPE(TRB_DATA);
 
-		length_field = TRB_LEN(preq->request.length) |
-				TRB_TD_SIZE(remainder) | TRB_INTR_TARGET(0);
+		if (zlp)
+			field |= TRB_CHAIN;
+		else
+			field |= TRB_IOC | (pdev->ep0_expect_in ? 0 : TRB_ISP);
 
 		if (pdev->ep0_expect_in)
 			field |= TRB_DIR_IN;
 
+		length_field = TRB_LEN(preq->request.length) |
+			       TRB_TD_SIZE(zlp) | TRB_INTR_TARGET(0);
+
 		cdnsp_queue_trb(pdev, ep_ring, true,
 				lower_32_bits(preq->request.dma),
 				upper_32_bits(preq->request.dma), length_field,
@@ -2046,6 +2054,20 @@  int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq)
 				TRB_SETUPID(pdev->setup_id) |
 				pdev->setup_speed);
 
+		if (zlp) {
+			field = TRB_TYPE(TRB_NORMAL) | TRB_IOC;
+
+			if (!pdev->ep0_expect_in)
+				field = TRB_ISP;
+
+			cdnsp_queue_trb(pdev, ep_ring, true,
+					lower_32_bits(preq->request.dma),
+					upper_32_bits(preq->request.dma), 0,
+					field | ep_ring->cycle_state |
+					TRB_SETUPID(pdev->setup_id) |
+					pdev->setup_speed);
+		}
+
 		pdev->ep0_stage = CDNSP_DATA_STAGE;
 	}