aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/gadget/function/f_loopback.c
diff options
context:
space:
mode:
authorKrzysztof Opasiak <k.opasiak@samsung.com>2015-10-14 16:49:40 -0400
committerFelipe Balbi <balbi@ti.com>2015-10-15 11:19:52 -0400
commit91c42b0da8e353697c9b49fe541056c5d0518c49 (patch)
treea90a3c3f10aa4b5d0d64e37da0d1acc6b5dc7827 /drivers/usb/gadget/function/f_loopback.c
parent3e9d992f93fecc746baa9d4854acc026d422094f (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.c131
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 */
291free_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
296static int enable_endpoint(struct usb_composite_dev *cdev, struct f_loopback *loop, 316static 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
336fail1: 358fail_out:
337 usb_ep_disable(ep); 359 free_ep_req(loop->out_ep, out_req);
360fail_in:
361 usb_ep_free_request(loop->in_ep, in_req);
362fail:
363 return result;
364}
365
366static 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
339fail0: 375 result = usb_ep_enable(ep);
376 if (result < 0)
377 goto out;
378 ep->driver_data = loop;
379 result = 0;
380
381out:
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
405disable_out:
406 usb_ep_disable(loop->out_ep);
407disable_in:
408 usb_ep_disable(loop->in_ep);
409out:
357 return result; 410 return result;
358} 411}
359 412