mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-17 07:54:54 +08:00
5e216d54b4
The current code tries to allocate memory with GFP_KERNEL at
interrupt context, it would show below warning during the enumeration
when I test it with chipidea hardware, change GFP flag as GFP_ATOMIC
can fix this issue.
[ 40.438237] zero gadget: high-speed config #2: loopback
[ 40.444924] ------------[ cut here ]------------
[ 40.449609] WARNING: CPU: 0 PID: 0 at kernel/locking/lockdep.c:2755 lockdep_trace_alloc+0x108/0x128()
[ 40.461715] DEBUG_LOCKS_WARN_ON(irqs_disabled_flags(flags))
[ 40.467130] Modules linked in:
[ 40.470216] usb_f_ss_lb g_zero libcomposite evbug
[ 40.473822] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 4.3.0-rc5-00168-gb730aaf #604
[ 40.481496] Hardware name: Freescale i.MX6 SoloX (Device Tree)
[ 40.487345] Backtrace:
[ 40.489857] [<80014e94>] (dump_backtrace) from [<80015088>] (show_stack+0x18/0x1c)
[ 40.497445] r6:80b67a80 r5:00000000 r4:00000000 r3:00000000
[ 40.503234] [<80015070>] (show_stack) from [<802e27b4>] (dump_stack+0x8c/0xa4)
[ 40.510503] [<802e2728>] (dump_stack) from [<8002cfe8>] (warn_slowpath_common+0x80/0xbc)
[ 40.518612] r6:8007510c r5:00000009 r4:80b49c88 r3:00000001
[ 40.524396] [<8002cf68>] (warn_slowpath_common) from [<8002d05c>] (warn_slowpath_fmt+0x38/0x40)
[ 40.533109] r8:bcfdef80 r7:bdb705cc r6:000080d0 r5:be001e80 r4:809cc278
[ 40.539965] [<8002d028>] (warn_slowpath_fmt) from [<8007510c>] (lockdep_trace_alloc+0x108/0x128)
[ 40.548766] r3:809d0128 r2:809cc278
[ 40.552401] r4:600b0193
[ 40.554990] [<80075004>] (lockdep_trace_alloc) from [<801093d4>] (kmem_cache_alloc+0x28/0x15c)
[ 40.563618] r4:000080d0 r3:80b4aa8c
[ 40.567270] [<801093ac>] (kmem_cache_alloc) from [<804d95e4>] (ep_alloc_request+0x58/0x68)
[ 40.575550] r10:7f01f104 r9:00000001 r8:bcfdef80 r7:bdb705cc r6:bc178700 r5:00000000
[ 40.583512] r4:bcfdef80 r3:813c0a38
[ 40.587183] [<804d958c>] (ep_alloc_request) from [<7f01f7ec>] (loopback_set_alt+0x114/0x21c [usb_f_ss_lb])
[ 40.596929] [<7f01f6d8>] (loopback_set_alt [usb_f_ss_lb]) from [<7f006910>] (composite_setup+0xbd0/0x17e8 [libcomposite])
[ 40.607902] r10:bd3a2c0c r9:00000000 r8:bcfdef80 r7:bc178700 r6:bdb702d0 r5:bcfdefdc
[ 40.615866] r4:7f0199b4 r3:00000002
[ 40.619542] [<7f005d40>] (composite_setup [libcomposite]) from [<804dae88>] (udc_irq+0x784/0xd1c)
[ 40.628431] r10:80bb5619 r9:c0876140 r8:00012001 r7:bdb71010 r6:bdb70568 r5:00010001
[ 40.636392] r4:bdb70014
[ 40.638985] [<804da704>] (udc_irq) from [<804d64f8>] (ci_irq+0x5c/0x118)
[ 40.645702] r10:80bb5619 r9:be11e000 r8:00000117 r7:00000000 r6:bdb71010 r5:be11e060
[ 40.653666] r4:bdb70010
[ 40.656261] [<804d649c>] (ci_irq) from [<8007f638>] (handle_irq_event_percpu+0x7c/0x13c)
[ 40.664367] r6:00000000 r5:be11e060 r4:bdb05cc0 r3:804d649c
[ 40.670149] [<8007f5bc>] (handle_irq_event_percpu) from [<8007f740>] (handle_irq_event+0x48/0x6c)
[ 40.679036] r10:00000000 r9:be008000 r8:00000001 r7:00000000 r6:bdb05cc0 r5:be11e060
[ 40.686998] r4:be11e000
[ 40.689581] [<8007f6f8>] (handle_irq_event) from [<80082850>] (handle_fasteoi_irq+0xd4/0x1b0)
[ 40.698120] r6:80b56a30 r5:be11e060 r4:be11e000 r3:00000000
[ 40.703898] [<8008277c>] (handle_fasteoi_irq) from [<8007ec04>] (generic_handle_irq+0x28/0x3c)
[ 40.712524] r7:00000000 r6:80b4aaf4 r5:00000117 r4:80b445fc
[ 40.718304] [<8007ebdc>] (generic_handle_irq) from [<8007ef20>] (__handle_domain_irq+0x6c/0xe8)
[ 40.727033] [<8007eeb4>] (__handle_domain_irq) from [<800095d4>] (gic_handle_irq+0x48/0x94)
[ 40.735402] r9:c080f100 r8:80b4ac6c r7:c080e100 r6:80b67d40 r5:80b49f00 r4:c080e10c
[ 40.743290] [<8000958c>] (gic_handle_irq) from [<80015d38>] (__irq_svc+0x58/0x78)
[ 40.750791] Exception stack(0x80b49f00 to 0x80b49f48)
[ 40.755873] 9f00: 00000001 00000001 00000000 80024320 80b48000 80b4a9d0 80b4a984 80b433e4
[ 40.764078] 9f20: 00000001 807f4680 00000000 80b49f5c 80b49f20 80b49f50 80071ca4 800113fc
[ 40.772272] 9f40: 200b0013 ffffffff
[ 40.775776] r9:807f4680 r8:00000001 r7:80b49f34 r6:ffffffff r5:200b0013 r4:800113fc
[ 40.783677] [<800113d4>] (arch_cpu_idle) from [<8006c5bc>] (default_idle_call+0x28/0x38)
[ 40.791798] [<8006c594>] (default_idle_call) from [<8006c6dc>] (cpu_startup_entry+0x110/0x1b0)
[ 40.800445] [<8006c5cc>] (cpu_startup_entry) from [<807e95dc>] (rest_init+0x12c/0x168)
[ 40.808376] r7:80b4a8c0 r3:807f4b7c
[ 40.812030] [<807e94b0>] (rest_init) from [<80ad7cc0>] (start_kernel+0x360/0x3d4)
[ 40.819528] r5:80bcb000 r4:80bcb050
[ 40.823171] [<80ad7960>] (start_kernel) from [<8000807c>] (0x8000807c)
It fixes commit 91c42b0da8
("usb: gadget: loopback: Fix looping back
logic implementation").
Cc: <stable@vger.kernel.org> # v3.18+
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Reviewed-by: Krzysztof Opasiak <k.opasiak@samsung.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
609 lines
14 KiB
C
609 lines
14 KiB
C
/*
|
|
* f_loopback.c - USB peripheral loopback configuration driver
|
|
*
|
|
* Copyright (C) 2003-2008 David Brownell
|
|
* Copyright (C) 2008 by Nokia Corporation
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
/* #define VERBOSE_DEBUG */
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/err.h>
|
|
#include <linux/usb/composite.h>
|
|
|
|
#include "g_zero.h"
|
|
#include "u_f.h"
|
|
|
|
/*
|
|
* LOOPBACK FUNCTION ... a testing vehicle for USB peripherals,
|
|
*
|
|
* This takes messages of various sizes written OUT to a device, and loops
|
|
* them back so they can be read IN from it. It has been used by certain
|
|
* test applications. It supports limited testing of data queueing logic.
|
|
*/
|
|
struct f_loopback {
|
|
struct usb_function function;
|
|
|
|
struct usb_ep *in_ep;
|
|
struct usb_ep *out_ep;
|
|
|
|
unsigned qlen;
|
|
unsigned buflen;
|
|
};
|
|
|
|
static inline struct f_loopback *func_to_loop(struct usb_function *f)
|
|
{
|
|
return container_of(f, struct f_loopback, function);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static struct usb_interface_descriptor loopback_intf = {
|
|
.bLength = sizeof(loopback_intf),
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
|
|
.bNumEndpoints = 2,
|
|
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
|
/* .iInterface = DYNAMIC */
|
|
};
|
|
|
|
/* full speed support: */
|
|
|
|
static struct usb_endpoint_descriptor fs_loop_source_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
|
|
.bEndpointAddress = USB_DIR_IN,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
};
|
|
|
|
static struct usb_endpoint_descriptor fs_loop_sink_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
|
|
.bEndpointAddress = USB_DIR_OUT,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
};
|
|
|
|
static struct usb_descriptor_header *fs_loopback_descs[] = {
|
|
(struct usb_descriptor_header *) &loopback_intf,
|
|
(struct usb_descriptor_header *) &fs_loop_sink_desc,
|
|
(struct usb_descriptor_header *) &fs_loop_source_desc,
|
|
NULL,
|
|
};
|
|
|
|
/* high speed support: */
|
|
|
|
static struct usb_endpoint_descriptor hs_loop_source_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(512),
|
|
};
|
|
|
|
static struct usb_endpoint_descriptor hs_loop_sink_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(512),
|
|
};
|
|
|
|
static struct usb_descriptor_header *hs_loopback_descs[] = {
|
|
(struct usb_descriptor_header *) &loopback_intf,
|
|
(struct usb_descriptor_header *) &hs_loop_source_desc,
|
|
(struct usb_descriptor_header *) &hs_loop_sink_desc,
|
|
NULL,
|
|
};
|
|
|
|
/* super speed support: */
|
|
|
|
static struct usb_endpoint_descriptor ss_loop_source_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(1024),
|
|
};
|
|
|
|
static struct usb_ss_ep_comp_descriptor ss_loop_source_comp_desc = {
|
|
.bLength = USB_DT_SS_EP_COMP_SIZE,
|
|
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
|
.bMaxBurst = 0,
|
|
.bmAttributes = 0,
|
|
.wBytesPerInterval = 0,
|
|
};
|
|
|
|
static struct usb_endpoint_descriptor ss_loop_sink_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(1024),
|
|
};
|
|
|
|
static struct usb_ss_ep_comp_descriptor ss_loop_sink_comp_desc = {
|
|
.bLength = USB_DT_SS_EP_COMP_SIZE,
|
|
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
|
.bMaxBurst = 0,
|
|
.bmAttributes = 0,
|
|
.wBytesPerInterval = 0,
|
|
};
|
|
|
|
static struct usb_descriptor_header *ss_loopback_descs[] = {
|
|
(struct usb_descriptor_header *) &loopback_intf,
|
|
(struct usb_descriptor_header *) &ss_loop_source_desc,
|
|
(struct usb_descriptor_header *) &ss_loop_source_comp_desc,
|
|
(struct usb_descriptor_header *) &ss_loop_sink_desc,
|
|
(struct usb_descriptor_header *) &ss_loop_sink_comp_desc,
|
|
NULL,
|
|
};
|
|
|
|
/* function-specific strings: */
|
|
|
|
static struct usb_string strings_loopback[] = {
|
|
[0].s = "loop input to output",
|
|
{ } /* end of list */
|
|
};
|
|
|
|
static struct usb_gadget_strings stringtab_loop = {
|
|
.language = 0x0409, /* en-us */
|
|
.strings = strings_loopback,
|
|
};
|
|
|
|
static struct usb_gadget_strings *loopback_strings[] = {
|
|
&stringtab_loop,
|
|
NULL,
|
|
};
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static int loopback_bind(struct usb_configuration *c, struct usb_function *f)
|
|
{
|
|
struct usb_composite_dev *cdev = c->cdev;
|
|
struct f_loopback *loop = func_to_loop(f);
|
|
int id;
|
|
int ret;
|
|
|
|
/* allocate interface ID(s) */
|
|
id = usb_interface_id(c, f);
|
|
if (id < 0)
|
|
return id;
|
|
loopback_intf.bInterfaceNumber = id;
|
|
|
|
id = usb_string_id(cdev);
|
|
if (id < 0)
|
|
return id;
|
|
strings_loopback[0].id = id;
|
|
loopback_intf.iInterface = id;
|
|
|
|
/* allocate endpoints */
|
|
|
|
loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc);
|
|
if (!loop->in_ep) {
|
|
autoconf_fail:
|
|
ERROR(cdev, "%s: can't autoconfigure on %s\n",
|
|
f->name, cdev->gadget->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc);
|
|
if (!loop->out_ep)
|
|
goto autoconf_fail;
|
|
|
|
/* support high speed hardware */
|
|
hs_loop_source_desc.bEndpointAddress =
|
|
fs_loop_source_desc.bEndpointAddress;
|
|
hs_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress;
|
|
|
|
/* support super speed hardware */
|
|
ss_loop_source_desc.bEndpointAddress =
|
|
fs_loop_source_desc.bEndpointAddress;
|
|
ss_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress;
|
|
|
|
ret = usb_assign_descriptors(f, fs_loopback_descs, hs_loopback_descs,
|
|
ss_loopback_descs);
|
|
if (ret)
|
|
return ret;
|
|
|
|
DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
|
|
(gadget_is_superspeed(c->cdev->gadget) ? "super" :
|
|
(gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")),
|
|
f->name, loop->in_ep->name, loop->out_ep->name);
|
|
return 0;
|
|
}
|
|
|
|
static void lb_free_func(struct usb_function *f)
|
|
{
|
|
struct f_lb_opts *opts;
|
|
|
|
opts = container_of(f->fi, struct f_lb_opts, func_inst);
|
|
|
|
mutex_lock(&opts->lock);
|
|
opts->refcnt--;
|
|
mutex_unlock(&opts->lock);
|
|
|
|
usb_free_all_descriptors(f);
|
|
kfree(func_to_loop(f));
|
|
}
|
|
|
|
static void loopback_complete(struct usb_ep *ep, struct usb_request *req)
|
|
{
|
|
struct f_loopback *loop = ep->driver_data;
|
|
struct usb_composite_dev *cdev = loop->function.config->cdev;
|
|
int status = req->status;
|
|
|
|
switch (status) {
|
|
case 0: /* normal completion? */
|
|
if (ep == loop->out_ep) {
|
|
/*
|
|
* We received some data from the host so let's
|
|
* queue it so host can read the from our in ep
|
|
*/
|
|
struct usb_request *in_req = req->context;
|
|
|
|
in_req->zero = (req->actual < req->length);
|
|
in_req->length = req->actual;
|
|
ep = loop->in_ep;
|
|
req = in_req;
|
|
} else {
|
|
/*
|
|
* We have just looped back a bunch of data
|
|
* to host. Now let's wait for some more data.
|
|
*/
|
|
req = req->context;
|
|
ep = loop->out_ep;
|
|
}
|
|
|
|
/* queue the buffer back to host or for next bunch of data */
|
|
status = usb_ep_queue(ep, req, GFP_ATOMIC);
|
|
if (status == 0) {
|
|
return;
|
|
} else {
|
|
ERROR(cdev, "Unable to loop back buffer to %s: %d\n",
|
|
ep->name, status);
|
|
goto free_req;
|
|
}
|
|
|
|
/* "should never get here" */
|
|
default:
|
|
ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name,
|
|
status, req->actual, req->length);
|
|
/* FALLTHROUGH */
|
|
|
|
/* NOTE: since this driver doesn't maintain an explicit record
|
|
* of requests it submitted (just maintains qlen count), we
|
|
* rely on the hardware driver to clean up on disconnect or
|
|
* endpoint disable.
|
|
*/
|
|
case -ECONNABORTED: /* hardware forced ep reset */
|
|
case -ECONNRESET: /* request dequeued */
|
|
case -ESHUTDOWN: /* disconnect from host */
|
|
free_req:
|
|
usb_ep_free_request(ep == loop->in_ep ?
|
|
loop->out_ep : loop->in_ep,
|
|
req->context);
|
|
free_ep_req(ep, req);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void disable_loopback(struct f_loopback *loop)
|
|
{
|
|
struct usb_composite_dev *cdev;
|
|
|
|
cdev = loop->function.config->cdev;
|
|
disable_endpoints(cdev, loop->in_ep, loop->out_ep, NULL, NULL);
|
|
VDBG(cdev, "%s disabled\n", loop->function.name);
|
|
}
|
|
|
|
static inline struct usb_request *lb_alloc_ep_req(struct usb_ep *ep, int len)
|
|
{
|
|
struct f_loopback *loop = ep->driver_data;
|
|
|
|
return alloc_ep_req(ep, len, loop->buflen);
|
|
}
|
|
|
|
static int alloc_requests(struct usb_composite_dev *cdev,
|
|
struct f_loopback *loop)
|
|
{
|
|
struct usb_request *in_req, *out_req;
|
|
int i;
|
|
int result = 0;
|
|
|
|
/*
|
|
* allocate a bunch of read buffers and queue them all at once.
|
|
* we buffer at most 'qlen' transfers; We allocate buffers only
|
|
* for out transfer and reuse them in IN transfers to implement
|
|
* our loopback functionality
|
|
*/
|
|
for (i = 0; i < loop->qlen && result == 0; i++) {
|
|
result = -ENOMEM;
|
|
|
|
in_req = usb_ep_alloc_request(loop->in_ep, GFP_ATOMIC);
|
|
if (!in_req)
|
|
goto fail;
|
|
|
|
out_req = lb_alloc_ep_req(loop->out_ep, 0);
|
|
if (!out_req)
|
|
goto fail_in;
|
|
|
|
in_req->complete = loopback_complete;
|
|
out_req->complete = loopback_complete;
|
|
|
|
in_req->buf = out_req->buf;
|
|
/* length will be set in complete routine */
|
|
in_req->context = out_req;
|
|
out_req->context = in_req;
|
|
|
|
result = usb_ep_queue(loop->out_ep, out_req, GFP_ATOMIC);
|
|
if (result) {
|
|
ERROR(cdev, "%s queue req --> %d\n",
|
|
loop->out_ep->name, result);
|
|
goto fail_out;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail_out:
|
|
free_ep_req(loop->out_ep, out_req);
|
|
fail_in:
|
|
usb_ep_free_request(loop->in_ep, in_req);
|
|
fail:
|
|
return result;
|
|
}
|
|
|
|
static int enable_endpoint(struct usb_composite_dev *cdev,
|
|
struct f_loopback *loop, struct usb_ep *ep)
|
|
{
|
|
int result;
|
|
|
|
result = config_ep_by_speed(cdev->gadget, &(loop->function), ep);
|
|
if (result)
|
|
goto out;
|
|
|
|
result = usb_ep_enable(ep);
|
|
if (result < 0)
|
|
goto out;
|
|
ep->driver_data = loop;
|
|
result = 0;
|
|
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
enable_loopback(struct usb_composite_dev *cdev, struct f_loopback *loop)
|
|
{
|
|
int result = 0;
|
|
|
|
result = enable_endpoint(cdev, loop, loop->in_ep);
|
|
if (result)
|
|
goto out;
|
|
|
|
result = enable_endpoint(cdev, loop, loop->out_ep);
|
|
if (result)
|
|
goto disable_in;
|
|
|
|
result = alloc_requests(cdev, loop);
|
|
if (result)
|
|
goto disable_out;
|
|
|
|
DBG(cdev, "%s enabled\n", loop->function.name);
|
|
return 0;
|
|
|
|
disable_out:
|
|
usb_ep_disable(loop->out_ep);
|
|
disable_in:
|
|
usb_ep_disable(loop->in_ep);
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
static int loopback_set_alt(struct usb_function *f,
|
|
unsigned intf, unsigned alt)
|
|
{
|
|
struct f_loopback *loop = func_to_loop(f);
|
|
struct usb_composite_dev *cdev = f->config->cdev;
|
|
|
|
/* we know alt is zero */
|
|
disable_loopback(loop);
|
|
return enable_loopback(cdev, loop);
|
|
}
|
|
|
|
static void loopback_disable(struct usb_function *f)
|
|
{
|
|
struct f_loopback *loop = func_to_loop(f);
|
|
|
|
disable_loopback(loop);
|
|
}
|
|
|
|
static struct usb_function *loopback_alloc(struct usb_function_instance *fi)
|
|
{
|
|
struct f_loopback *loop;
|
|
struct f_lb_opts *lb_opts;
|
|
|
|
loop = kzalloc(sizeof *loop, GFP_KERNEL);
|
|
if (!loop)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
lb_opts = container_of(fi, struct f_lb_opts, func_inst);
|
|
|
|
mutex_lock(&lb_opts->lock);
|
|
lb_opts->refcnt++;
|
|
mutex_unlock(&lb_opts->lock);
|
|
|
|
loop->buflen = lb_opts->bulk_buflen;
|
|
loop->qlen = lb_opts->qlen;
|
|
if (!loop->qlen)
|
|
loop->qlen = 32;
|
|
|
|
loop->function.name = "loopback";
|
|
loop->function.bind = loopback_bind;
|
|
loop->function.set_alt = loopback_set_alt;
|
|
loop->function.disable = loopback_disable;
|
|
loop->function.strings = loopback_strings;
|
|
|
|
loop->function.free_func = lb_free_func;
|
|
|
|
return &loop->function;
|
|
}
|
|
|
|
static inline struct f_lb_opts *to_f_lb_opts(struct config_item *item)
|
|
{
|
|
return container_of(to_config_group(item), struct f_lb_opts,
|
|
func_inst.group);
|
|
}
|
|
|
|
static void lb_attr_release(struct config_item *item)
|
|
{
|
|
struct f_lb_opts *lb_opts = to_f_lb_opts(item);
|
|
|
|
usb_put_function_instance(&lb_opts->func_inst);
|
|
}
|
|
|
|
static struct configfs_item_operations lb_item_ops = {
|
|
.release = lb_attr_release,
|
|
};
|
|
|
|
static ssize_t f_lb_opts_qlen_show(struct config_item *item, char *page)
|
|
{
|
|
struct f_lb_opts *opts = to_f_lb_opts(item);
|
|
int result;
|
|
|
|
mutex_lock(&opts->lock);
|
|
result = sprintf(page, "%d\n", opts->qlen);
|
|
mutex_unlock(&opts->lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
static ssize_t f_lb_opts_qlen_store(struct config_item *item,
|
|
const char *page, size_t len)
|
|
{
|
|
struct f_lb_opts *opts = to_f_lb_opts(item);
|
|
int ret;
|
|
u32 num;
|
|
|
|
mutex_lock(&opts->lock);
|
|
if (opts->refcnt) {
|
|
ret = -EBUSY;
|
|
goto end;
|
|
}
|
|
|
|
ret = kstrtou32(page, 0, &num);
|
|
if (ret)
|
|
goto end;
|
|
|
|
opts->qlen = num;
|
|
ret = len;
|
|
end:
|
|
mutex_unlock(&opts->lock);
|
|
return ret;
|
|
}
|
|
|
|
CONFIGFS_ATTR(f_lb_opts_, qlen);
|
|
|
|
static ssize_t f_lb_opts_bulk_buflen_show(struct config_item *item, char *page)
|
|
{
|
|
struct f_lb_opts *opts = to_f_lb_opts(item);
|
|
int result;
|
|
|
|
mutex_lock(&opts->lock);
|
|
result = sprintf(page, "%d\n", opts->bulk_buflen);
|
|
mutex_unlock(&opts->lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
static ssize_t f_lb_opts_bulk_buflen_store(struct config_item *item,
|
|
const char *page, size_t len)
|
|
{
|
|
struct f_lb_opts *opts = to_f_lb_opts(item);
|
|
int ret;
|
|
u32 num;
|
|
|
|
mutex_lock(&opts->lock);
|
|
if (opts->refcnt) {
|
|
ret = -EBUSY;
|
|
goto end;
|
|
}
|
|
|
|
ret = kstrtou32(page, 0, &num);
|
|
if (ret)
|
|
goto end;
|
|
|
|
opts->bulk_buflen = num;
|
|
ret = len;
|
|
end:
|
|
mutex_unlock(&opts->lock);
|
|
return ret;
|
|
}
|
|
|
|
CONFIGFS_ATTR(f_lb_opts_, bulk_buflen);
|
|
|
|
static struct configfs_attribute *lb_attrs[] = {
|
|
&f_lb_opts_attr_qlen,
|
|
&f_lb_opts_attr_bulk_buflen,
|
|
NULL,
|
|
};
|
|
|
|
static struct config_item_type lb_func_type = {
|
|
.ct_item_ops = &lb_item_ops,
|
|
.ct_attrs = lb_attrs,
|
|
.ct_owner = THIS_MODULE,
|
|
};
|
|
|
|
static void lb_free_instance(struct usb_function_instance *fi)
|
|
{
|
|
struct f_lb_opts *lb_opts;
|
|
|
|
lb_opts = container_of(fi, struct f_lb_opts, func_inst);
|
|
kfree(lb_opts);
|
|
}
|
|
|
|
static struct usb_function_instance *loopback_alloc_instance(void)
|
|
{
|
|
struct f_lb_opts *lb_opts;
|
|
|
|
lb_opts = kzalloc(sizeof(*lb_opts), GFP_KERNEL);
|
|
if (!lb_opts)
|
|
return ERR_PTR(-ENOMEM);
|
|
mutex_init(&lb_opts->lock);
|
|
lb_opts->func_inst.free_func_inst = lb_free_instance;
|
|
lb_opts->bulk_buflen = GZERO_BULK_BUFLEN;
|
|
lb_opts->qlen = GZERO_QLEN;
|
|
|
|
config_group_init_type_name(&lb_opts->func_inst.group, "",
|
|
&lb_func_type);
|
|
|
|
return &lb_opts->func_inst;
|
|
}
|
|
DECLARE_USB_FUNCTION(Loopback, loopback_alloc_instance, loopback_alloc);
|
|
|
|
int __init lb_modinit(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = usb_function_register(&Loopbackusb_func);
|
|
if (ret)
|
|
return ret;
|
|
return ret;
|
|
}
|
|
void __exit lb_modexit(void)
|
|
{
|
|
usb_function_unregister(&Loopbackusb_func);
|
|
}
|
|
|
|
MODULE_LICENSE("GPL");
|