diff options
author | Krzysztof Opasiak <k.opasiak@samsung.com> | 2015-10-14 16:49:40 -0400 |
---|---|---|
committer | Felipe Balbi <balbi@ti.com> | 2015-10-15 11:19:52 -0400 |
commit | 91c42b0da8e353697c9b49fe541056c5d0518c49 (patch) | |
tree | a90a3c3f10aa4b5d0d64e37da0d1acc6b5dc7827 /drivers/usb/gadget/function/f_loopback.c | |
parent | 3e9d992f93fecc746baa9d4854acc026d422094f (diff) |
usb: gadget: loopback: Fix looping back logic implementation
Since commit e0857ce58e8658657f5f12fe25272b93cfeb16aa
("usb: gadget: loopback: don't queue requests to bogus endpoints")
Loopback function is not realy working as that commit removed
all looping back logic. After that commit ep-out works like
/dev/null and ep-in works like /dev/zero.
This commit fix this issue by allocating set of out requests
and set of in requests but each out req shares buffer with
one in req:
out_req->buf ---> buf <--- in_req.buf
out_req->context <---> in_req.context
The completion routine simply enqueue the suitable req in
an oposite direction.
Cc: <stable@vger.kernel.org> # 3.18+
Fixes: e0857ce58e8658657f5f12fe25272b93cfeb16aa
("usb: gadget: loopback: don't queue requests to bogus endpoints")
Signed-off-by: Krzysztof Opasiak <k.opasiak@samsung.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
Diffstat (limited to 'drivers/usb/gadget/function/f_loopback.c')
-rw-r--r-- | drivers/usb/gadget/function/f_loopback.c | 131 |
1 files changed, 92 insertions, 39 deletions
diff --git a/drivers/usb/gadget/function/f_loopback.c b/drivers/usb/gadget/function/f_loopback.c index 79e284bba059..6b2102bc0699 100644 --- a/drivers/usb/gadget/function/f_loopback.c +++ b/drivers/usb/gadget/function/f_loopback.c | |||
@@ -243,22 +243,38 @@ static void loopback_complete(struct usb_ep *ep, struct usb_request *req) | |||
243 | int status = req->status; | 243 | int status = req->status; |
244 | 244 | ||
245 | switch (status) { | 245 | switch (status) { |
246 | |||
247 | case 0: /* normal completion? */ | 246 | case 0: /* normal completion? */ |
248 | if (ep == loop->out_ep) { | 247 | if (ep == loop->out_ep) { |
249 | req->zero = (req->actual < req->length); | 248 | /* |
250 | req->length = req->actual; | 249 | * We received some data from the host so let's |
250 | * queue it so host can read the from our in ep | ||
251 | */ | ||
252 | struct usb_request *in_req = req->context; | ||
253 | |||
254 | in_req->zero = (req->actual < req->length); | ||
255 | in_req->length = req->actual; | ||
256 | ep = loop->in_ep; | ||
257 | req = in_req; | ||
258 | } else { | ||
259 | /* | ||
260 | * We have just looped back a bunch of data | ||
261 | * to host. Now let's wait for some more data. | ||
262 | */ | ||
263 | req = req->context; | ||
264 | ep = loop->out_ep; | ||
251 | } | 265 | } |
252 | 266 | ||
253 | /* queue the buffer for some later OUT packet */ | 267 | /* queue the buffer back to host or for next bunch of data */ |
254 | req->length = loop->buflen; | ||
255 | status = usb_ep_queue(ep, req, GFP_ATOMIC); | 268 | status = usb_ep_queue(ep, req, GFP_ATOMIC); |
256 | if (status == 0) | 269 | if (status == 0) { |
257 | return; | 270 | return; |
271 | } else { | ||
272 | ERROR(cdev, "Unable to loop back buffer to %s: %d\n", | ||
273 | ep->name, status); | ||
274 | goto free_req; | ||
275 | } | ||
258 | 276 | ||
259 | /* "should never get here" */ | 277 | /* "should never get here" */ |
260 | /* FALLTHROUGH */ | ||
261 | |||
262 | default: | 278 | default: |
263 | ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name, | 279 | ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name, |
264 | status, req->actual, req->length); | 280 | status, req->actual, req->length); |
@@ -272,6 +288,10 @@ static void loopback_complete(struct usb_ep *ep, struct usb_request *req) | |||
272 | case -ECONNABORTED: /* hardware forced ep reset */ | 288 | case -ECONNABORTED: /* hardware forced ep reset */ |
273 | case -ECONNRESET: /* request dequeued */ | 289 | case -ECONNRESET: /* request dequeued */ |
274 | case -ESHUTDOWN: /* disconnect from host */ | 290 | case -ESHUTDOWN: /* disconnect from host */ |
291 | free_req: | ||
292 | usb_ep_free_request(ep == loop->in_ep ? | ||
293 | loop->out_ep : loop->in_ep, | ||
294 | req->context); | ||
275 | free_ep_req(ep, req); | 295 | free_ep_req(ep, req); |
276 | return; | 296 | return; |
277 | } | 297 | } |
@@ -293,50 +313,72 @@ static inline struct usb_request *lb_alloc_ep_req(struct usb_ep *ep, int len) | |||
293 | return alloc_ep_req(ep, len, loop->buflen); | 313 | return alloc_ep_req(ep, len, loop->buflen); |
294 | } | 314 | } |
295 | 315 | ||
296 | static int enable_endpoint(struct usb_composite_dev *cdev, struct f_loopback *loop, | 316 | static int alloc_requests(struct usb_composite_dev *cdev, |
297 | struct usb_ep *ep) | 317 | struct f_loopback *loop) |
298 | { | 318 | { |
299 | struct usb_request *req; | 319 | struct usb_request *in_req, *out_req; |
300 | unsigned i; | 320 | int i; |
301 | int result; | 321 | int result = 0; |
302 | |||
303 | /* | ||
304 | * one endpoint writes data back IN to the host while another endpoint | ||
305 | * just reads OUT packets | ||
306 | */ | ||
307 | result = config_ep_by_speed(cdev->gadget, &(loop->function), ep); | ||
308 | if (result) | ||
309 | goto fail0; | ||
310 | result = usb_ep_enable(ep); | ||
311 | if (result < 0) | ||
312 | goto fail0; | ||
313 | ep->driver_data = loop; | ||
314 | 322 | ||
315 | /* | 323 | /* |
316 | * allocate a bunch of read buffers and queue them all at once. | 324 | * allocate a bunch of read buffers and queue them all at once. |
317 | * we buffer at most 'qlen' transfers; fewer if any need more | 325 | * we buffer at most 'qlen' transfers; We allocate buffers only |
318 | * than 'buflen' bytes each. | 326 | * for out transfer and reuse them in IN transfers to implement |
327 | * our loopback functionality | ||
319 | */ | 328 | */ |
320 | for (i = 0; i < loop->qlen && result == 0; i++) { | 329 | for (i = 0; i < loop->qlen && result == 0; i++) { |
321 | req = lb_alloc_ep_req(ep, 0); | 330 | result = -ENOMEM; |
322 | if (!req) | 331 | |
323 | goto fail1; | 332 | in_req = usb_ep_alloc_request(loop->in_ep, GFP_KERNEL); |
333 | if (!in_req) | ||
334 | goto fail; | ||
335 | |||
336 | out_req = lb_alloc_ep_req(loop->out_ep, 0); | ||
337 | if (!out_req) | ||
338 | goto fail_in; | ||
339 | |||
340 | in_req->complete = loopback_complete; | ||
341 | out_req->complete = loopback_complete; | ||
342 | |||
343 | in_req->buf = out_req->buf; | ||
344 | /* length will be set in complete routine */ | ||
345 | in_req->context = out_req; | ||
346 | out_req->context = in_req; | ||
324 | 347 | ||
325 | req->complete = loopback_complete; | 348 | result = usb_ep_queue(loop->out_ep, out_req, GFP_ATOMIC); |
326 | result = usb_ep_queue(ep, req, GFP_ATOMIC); | ||
327 | if (result) { | 349 | if (result) { |
328 | ERROR(cdev, "%s queue req --> %d\n", | 350 | ERROR(cdev, "%s queue req --> %d\n", |
329 | ep->name, result); | 351 | loop->out_ep->name, result); |
330 | goto fail1; | 352 | goto fail_out; |
331 | } | 353 | } |
332 | } | 354 | } |
333 | 355 | ||
334 | return 0; | 356 | return 0; |
335 | 357 | ||
336 | fail1: | 358 | fail_out: |
337 | usb_ep_disable(ep); | 359 | free_ep_req(loop->out_ep, out_req); |
360 | fail_in: | ||
361 | usb_ep_free_request(loop->in_ep, in_req); | ||
362 | fail: | ||
363 | return result; | ||
364 | } | ||
365 | |||
366 | static int enable_endpoint(struct usb_composite_dev *cdev, | ||
367 | struct f_loopback *loop, struct usb_ep *ep) | ||
368 | { | ||
369 | int result; | ||
370 | |||
371 | result = config_ep_by_speed(cdev->gadget, &(loop->function), ep); | ||
372 | if (result) | ||
373 | goto out; | ||
338 | 374 | ||
339 | fail0: | 375 | result = usb_ep_enable(ep); |
376 | if (result < 0) | ||
377 | goto out; | ||
378 | ep->driver_data = loop; | ||
379 | result = 0; | ||
380 | |||
381 | out: | ||
340 | return result; | 382 | return result; |
341 | } | 383 | } |
342 | 384 | ||
@@ -347,13 +389,24 @@ enable_loopback(struct usb_composite_dev *cdev, struct f_loopback *loop) | |||
347 | 389 | ||
348 | result = enable_endpoint(cdev, loop, loop->in_ep); | 390 | result = enable_endpoint(cdev, loop, loop->in_ep); |
349 | if (result) | 391 | if (result) |
350 | return result; | 392 | goto out; |
351 | 393 | ||
352 | result = enable_endpoint(cdev, loop, loop->out_ep); | 394 | result = enable_endpoint(cdev, loop, loop->out_ep); |
353 | if (result) | 395 | if (result) |
354 | return result; | 396 | goto disable_in; |
397 | |||
398 | result = alloc_requests(cdev, loop); | ||
399 | if (result) | ||
400 | goto disable_out; | ||
355 | 401 | ||
356 | DBG(cdev, "%s enabled\n", loop->function.name); | 402 | DBG(cdev, "%s enabled\n", loop->function.name); |
403 | return 0; | ||
404 | |||
405 | disable_out: | ||
406 | usb_ep_disable(loop->out_ep); | ||
407 | disable_in: | ||
408 | usb_ep_disable(loop->in_ep); | ||
409 | out: | ||
357 | return result; | 410 | return result; |
358 | } | 411 | } |
359 | 412 | ||