Skip to content

Commit

Permalink
usb: dwc3: gadget: Don't setup more than requested
Browse files Browse the repository at this point in the history
commit 5d187c0 upstream.

The SG list may be set up with entry size more than the requested
length. Check the usb_request->length and make sure that we don't setup
the TRBs to send/receive more than requested. This case may occur when
the SG entry is allocated up to a certain minimum size, but the request
length is less than that. It can also occur when the request is reused
for a different request length.

Cc: <stable@vger.kernel.org> # v4.18+
Fixes: a31e63b ("usb: dwc3: gadget: Correct handling of scattergather lists")
Signed-off-by: Thinh Nguyen <thinhn@synopsys.com>
Signed-off-by: Felipe Balbi <balbi@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Thinh Nguyen authored and gregkh committed Sep 3, 2020
1 parent 3b9953f commit d884a90
Showing 1 changed file with 35 additions and 16 deletions.
51 changes: 35 additions & 16 deletions drivers/usb/dwc3/gadget.c
Expand Up @@ -1054,27 +1054,25 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb,
* dwc3_prepare_one_trb - setup one TRB from one request
* @dep: endpoint for which this request is prepared
* @req: dwc3_request pointer
* @trb_length: buffer size of the TRB
* @chain: should this TRB be chained to the next?
* @node: only for isochronous endpoints. First TRB needs different type.
*/
static void dwc3_prepare_one_trb(struct dwc3_ep *dep,
struct dwc3_request *req, unsigned chain, unsigned node)
struct dwc3_request *req, unsigned int trb_length,
unsigned chain, unsigned node)
{
struct dwc3_trb *trb;
unsigned int length;
dma_addr_t dma;
unsigned stream_id = req->request.stream_id;
unsigned short_not_ok = req->request.short_not_ok;
unsigned no_interrupt = req->request.no_interrupt;
unsigned is_last = req->request.is_last;

if (req->request.num_sgs > 0) {
length = sg_dma_len(req->start_sg);
if (req->request.num_sgs > 0)
dma = sg_dma_address(req->start_sg);
} else {
length = req->request.length;
else
dma = req->request.dma;
}

trb = &dep->trb_pool[dep->trb_enqueue];

Expand All @@ -1086,7 +1084,7 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep,

req->num_trbs++;

__dwc3_prepare_one_trb(dep, trb, dma, length, chain, node,
__dwc3_prepare_one_trb(dep, trb, dma, trb_length, chain, node,
stream_id, short_not_ok, no_interrupt, is_last);
}

Expand All @@ -1096,24 +1094,35 @@ static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep,
struct scatterlist *sg = req->start_sg;
struct scatterlist *s;
int i;

unsigned int length = req->request.length;
unsigned int remaining = req->request.num_mapped_sgs
- req->num_queued_sgs;

/*
* If we resume preparing the request, then get the remaining length of
* the request and resume where we left off.
*/
for_each_sg(req->request.sg, s, req->num_queued_sgs, i)
length -= sg_dma_len(s);

for_each_sg(sg, s, remaining, i) {
unsigned int length = req->request.length;
unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc);
unsigned int rem = length % maxp;
unsigned int trb_length;
unsigned chain = true;

trb_length = min_t(unsigned int, length, sg_dma_len(s));

length -= trb_length;

/*
* IOMMU driver is coalescing the list of sgs which shares a
* page boundary into one and giving it to USB driver. With
* this the number of sgs mapped is not equal to the number of
* sgs passed. So mark the chain bit to false if it isthe last
* mapped sg.
*/
if (i == remaining - 1)
if ((i == remaining - 1) || !length)
chain = false;

if (rem && usb_endpoint_dir_out(dep->endpoint.desc) && !chain) {
Expand All @@ -1123,7 +1132,7 @@ static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep,
req->needs_extra_trb = true;

/* prepare normal TRB */
dwc3_prepare_one_trb(dep, req, true, i);
dwc3_prepare_one_trb(dep, req, trb_length, true, i);

/* Now prepare one extra TRB to align transfer size */
trb = &dep->trb_pool[dep->trb_enqueue];
Expand All @@ -1135,7 +1144,7 @@ static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep,
req->request.no_interrupt,
req->request.is_last);
} else {
dwc3_prepare_one_trb(dep, req, chain, i);
dwc3_prepare_one_trb(dep, req, trb_length, chain, i);
}

/*
Expand All @@ -1150,6 +1159,16 @@ static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep,

req->num_queued_sgs++;

/*
* The number of pending SG entries may not correspond to the
* number of mapped SG entries. If all the data are queued, then
* don't include unused SG entries.
*/
if (length == 0) {
req->num_pending_sgs -= req->request.num_mapped_sgs - req->num_queued_sgs;
break;
}

if (!dwc3_calc_trbs_left(dep))
break;
}
Expand All @@ -1169,7 +1188,7 @@ static void dwc3_prepare_one_trb_linear(struct dwc3_ep *dep,
req->needs_extra_trb = true;

/* prepare normal TRB */
dwc3_prepare_one_trb(dep, req, true, 0);
dwc3_prepare_one_trb(dep, req, length, true, 0);

/* Now prepare one extra TRB to align transfer size */
trb = &dep->trb_pool[dep->trb_enqueue];
Expand All @@ -1187,7 +1206,7 @@ static void dwc3_prepare_one_trb_linear(struct dwc3_ep *dep,
req->needs_extra_trb = true;

/* prepare normal TRB */
dwc3_prepare_one_trb(dep, req, true, 0);
dwc3_prepare_one_trb(dep, req, length, true, 0);

/* Now prepare one extra TRB to handle ZLP */
trb = &dep->trb_pool[dep->trb_enqueue];
Expand All @@ -1198,7 +1217,7 @@ static void dwc3_prepare_one_trb_linear(struct dwc3_ep *dep,
req->request.no_interrupt,
req->request.is_last);
} else {
dwc3_prepare_one_trb(dep, req, false, 0);
dwc3_prepare_one_trb(dep, req, length, false, 0);
}
}

Expand Down

0 comments on commit d884a90

Please sign in to comment.