[PATCH 3/4] usb: gadget: dummy_hcd: fix SG transfer handling across chunks

Igor Skalkin <[email protected]>
Newsgroups org.kernel.vger.linux-kernel,org.kernel.vger.linux-usb
Message-ID <[email protected]>
dummy_perform_transfer() may be called multiple times for the same URB
as the transfer is progressed in chunks.  The previous sg_miter-based
implementation kept iterator state in struct urbp between calls; on
resumed chunks sg_miter_next() would advance past the already-consumed
segment, causing -EINVAL and broken transfers.

Replace the stateful iterator with sg_miter_skip() to seek to the
correct SG position (urb->actual_length bytes in) on every call,
followed by a fresh manual walk for the requested chunk length.  This
makes each call self-contained and correct regardless of how many
partial transfers have already occurred.

Return -EINVAL if the total copied length does not match the requested
length (e.g. SG list exhausted prematurely).

Fixes: 14fce33a960a ("usb: gadget: dummy_hcd: add sg support")
Assisted-by: OpenCode:claude-sonnet-5
Signed-off-by: Igor Skalkin <[email protected]>
---
 drivers/usb/gadget/udc/dummy_hcd.c | 59 +++++++++++-------------------
 1 file changed, 22 insertions(+), 37 deletions(-)

diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c
index 5384806347ab..7597c9e9fa66 100644
--- a/drivers/usb/gadget/udc/dummy_hcd.c
+++ b/drivers/usb/gadget/udc/dummy_hcd.c
@@ -229,8 +229,6 @@ static const struct {
 struct urbp {
 	struct urb		*urb;
 	struct list_head	urbp_list;
-	struct sg_mapping_iter	miter;
-	u32			miter_started;
 };
 
 
@@ -1278,7 +1276,6 @@ static int dummy_urb_enqueue(
 	if (!urbp)
 		return -ENOMEM;
 	urbp->urb = urb;
-	urbp->miter_started = 0;
 
 	dum_hcd = hcd_to_dummy_hcd(hcd);
 	spin_lock_irqsave(&dum_hcd->dum->lock, flags);
@@ -1345,12 +1342,11 @@ static int dummy_perform_transfer(struct urb *urb, struct dummy_request *req,
 		u32 len)
 {
 	void *ubuf, *rbuf;
-	struct urbp *urbp = urb->hcpriv;
 	int to_host;
-	struct sg_mapping_iter *miter = &urbp->miter;
 	u32 trans = 0;
-	u32 this_sg;
-	bool next_sg;
+	u32 req_len = len;
+	struct sg_mapping_iter miter;
+	u32 flags = SG_MITER_ATOMIC;
 
 	to_host = usb_urb_dir_in(urb);
 	rbuf = req->req.buf + req->req.actual;
@@ -1364,46 +1360,35 @@ static int dummy_perform_transfer(struct urb *urb, struct dummy_request *req,
 		return len;
 	}
 
-	if (!urbp->miter_started) {
-		u32 flags = SG_MITER_ATOMIC;
+	if (to_host)
+		flags |= SG_MITER_TO_SG;
+	else
+		flags |= SG_MITER_FROM_SG;
 
-		if (to_host)
-			flags |= SG_MITER_TO_SG;
-		else
-			flags |= SG_MITER_FROM_SG;
+	sg_miter_start(&miter, urb->sg, urb->num_sgs, flags);
 
-		sg_miter_start(miter, urb->sg, urb->num_sgs, flags);
-		urbp->miter_started = 1;
-	}
-	next_sg = sg_miter_next(miter);
-	if (next_sg == false) {
-		WARN_ON_ONCE(1);
+	if (!sg_miter_skip(&miter, urb->actual_length)) {
+		sg_miter_stop(&miter);
 		return -EINVAL;
 	}
-	do {
-		ubuf = miter->addr;
-		this_sg = min_t(u32, len, miter->length);
-		miter->consumed = this_sg;
-		trans += this_sg;
+
+	while (len && sg_miter_next(&miter)) {
+		u32 chunk = min_t(u32, len, miter.length);
 
 		if (to_host)
-			memcpy(ubuf, rbuf, this_sg);
+			memcpy(miter.addr, rbuf + trans, chunk);
 		else
-			memcpy(rbuf, ubuf, this_sg);
-		len -= this_sg;
+			memcpy(rbuf + trans, miter.addr, chunk);
+		miter.consumed = chunk;
+		trans += chunk;
+		len -= chunk;
+	}
 
-		if (!len)
-			break;
-		next_sg = sg_miter_next(miter);
-		if (next_sg == false) {
-			WARN_ON_ONCE(1);
-			return -EINVAL;
-		}
+	sg_miter_stop(&miter);
 
-		rbuf += this_sg;
-	} while (1);
+	if (unlikely(trans != req_len))
+		return -EINVAL;
 
-	sg_miter_stop(miter);
 	return trans;
 }
 
-- 
2.49.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.