usb: patches for v3.17 merge window
Surprisingly enough, while a big set of patches, the majority is composed of cleanups (using devm_*, fixing sparse errors, moving code around, adding const, etc). The highlights are addition of new support for PLX USB338x devices, and support for USB 2.0-only configurations of the DWC3 IP core. Signed-of-by: Felipe Balbi <balbi@ti.com> -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJTzTI9AAoJEIaOsuA1yqREIqcQAJbwMaDb5sp8dWst+nL1Vo8N 08bhOOM+o2KLiIBSNQh1hYtujpa06RSQMiNYZ7F/+R4Q3OAoq+uC0dD69TLEQ3Sd 1xi0UgUTAvtXo13TToRjIi0mYywrrkZ91Maqff5jZKdslOY4ZfAScpUQHyD3i1UO JNUqpayYAnp8JJIGQ8CG1wAeJh/J8JH3O607vaknKVjds+WMkeC5ubnuV4sgVw71 8JTwxk22EkMTr2MKhHjZwcv016NMEvziinWVoTXcWN2Uwk42Dn1nhfaYWSvg7kZE 8/3t6zzdFMdoeeTEn2xXIQGTjbHW0sBS5+S+6PAon2YbjS3x40cbyLJnZ+KoVXog iHDKAl5w53gd/7qMiv57dW+HRP0M7/m2iy/owIrY8H8DkM3uwFevaeq/G2raJKmA frB1k95bSyypN7wVkALYv4nurP5+d7ERy9hPj/49M5giJXPpEIbgKN2qGdafaEx7 xzAI+GqYrFtmmg25+f6AQRicVczQNMCdDT8HPZ04099Z2JVY/4uOoqTl29CePMgo OXeQ45ECMoJuRSwpF56e8h+qmsXp6WD31IhlcHseTvUvFu0Ex4SgUJhFP2UaF9WP aOqz0w36yX/ME3VhQk9YImNqGoqEk6fYHIHhxfdDugt0n68+M56EiiwtKvd5v5BI hCJAMBrv+WCGnJvWunVW =aJY4 -----END PGP SIGNATURE----- Merge tag 'usb-for-v3.17' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-next Felipe writes: usb: patches for v3.17 merge window Surprisingly enough, while a big set of patches, the majority is composed of cleanups (using devm_*, fixing sparse errors, moving code around, adding const, etc). The highlights are addition of new support for PLX USB338x devices, and support for USB 2.0-only configurations of the DWC3 IP core. Signed-of-by: Felipe Balbi <balbi@ti.com>
This commit is contained in:
commit
61fe2d75f1
159 changed files with 7024 additions and 4938 deletions
34
drivers/usb/gadget/function/Makefile
Normal file
34
drivers/usb/gadget/function/Makefile
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#
|
||||
# USB peripheral controller drivers
|
||||
#
|
||||
|
||||
ccflags-y := -I$(PWD)/drivers/usb/gadget/
|
||||
ccflags-y += -I$(PWD)/drivers/usb/gadget/udc/
|
||||
|
||||
# USB Functions
|
||||
usb_f_acm-y := f_acm.o
|
||||
obj-$(CONFIG_USB_F_ACM) += usb_f_acm.o
|
||||
usb_f_ss_lb-y := f_loopback.o f_sourcesink.o
|
||||
obj-$(CONFIG_USB_F_SS_LB) += usb_f_ss_lb.o
|
||||
obj-$(CONFIG_USB_U_SERIAL) += u_serial.o
|
||||
usb_f_serial-y := f_serial.o
|
||||
obj-$(CONFIG_USB_F_SERIAL) += usb_f_serial.o
|
||||
usb_f_obex-y := f_obex.o
|
||||
obj-$(CONFIG_USB_F_OBEX) += usb_f_obex.o
|
||||
obj-$(CONFIG_USB_U_ETHER) += u_ether.o
|
||||
usb_f_ncm-y := f_ncm.o
|
||||
obj-$(CONFIG_USB_F_NCM) += usb_f_ncm.o
|
||||
usb_f_ecm-y := f_ecm.o
|
||||
obj-$(CONFIG_USB_F_ECM) += usb_f_ecm.o
|
||||
usb_f_phonet-y := f_phonet.o
|
||||
obj-$(CONFIG_USB_F_PHONET) += usb_f_phonet.o
|
||||
usb_f_eem-y := f_eem.o
|
||||
obj-$(CONFIG_USB_F_EEM) += usb_f_eem.o
|
||||
usb_f_ecm_subset-y := f_subset.o
|
||||
obj-$(CONFIG_USB_F_SUBSET) += usb_f_ecm_subset.o
|
||||
usb_f_rndis-y := f_rndis.o rndis.o
|
||||
obj-$(CONFIG_USB_F_RNDIS) += usb_f_rndis.o
|
||||
usb_f_mass_storage-y := f_mass_storage.o storage_common.o
|
||||
obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o
|
||||
usb_f_fs-y := f_fs.o
|
||||
obj-$(CONFIG_USB_F_FS) += usb_f_fs.o
|
||||
848
drivers/usb/gadget/function/f_acm.c
Normal file
848
drivers/usb/gadget/function/f_acm.c
Normal file
|
|
@ -0,0 +1,848 @@
|
|||
/*
|
||||
* f_acm.c -- USB CDC serial (ACM) function driver
|
||||
*
|
||||
* Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
|
||||
* Copyright (C) 2008 by David Brownell
|
||||
* Copyright (C) 2008 by Nokia Corporation
|
||||
* Copyright (C) 2009 by Samsung Electronics
|
||||
* Author: Michal Nazarewicz (mina86@mina86.com)
|
||||
*
|
||||
* This software is distributed under the terms of the GNU General
|
||||
* Public License ("GPL") as published by the Free Software Foundation,
|
||||
* either version 2 of that License or (at your option) any later version.
|
||||
*/
|
||||
|
||||
/* #define VERBOSE_DEBUG */
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include "u_serial.h"
|
||||
#include "gadget_chips.h"
|
||||
|
||||
|
||||
/*
|
||||
* This CDC ACM function support just wraps control functions and
|
||||
* notifications around the generic serial-over-usb code.
|
||||
*
|
||||
* Because CDC ACM is standardized by the USB-IF, many host operating
|
||||
* systems have drivers for it. Accordingly, ACM is the preferred
|
||||
* interop solution for serial-port type connections. The control
|
||||
* models are often not necessary, and in any case don't do much in
|
||||
* this bare-bones implementation.
|
||||
*
|
||||
* Note that even MS-Windows has some support for ACM. However, that
|
||||
* support is somewhat broken because when you use ACM in a composite
|
||||
* device, having multiple interfaces confuses the poor OS. It doesn't
|
||||
* seem to understand CDC Union descriptors. The new "association"
|
||||
* descriptors (roughly equivalent to CDC Unions) may sometimes help.
|
||||
*/
|
||||
|
||||
struct f_acm {
|
||||
struct gserial port;
|
||||
u8 ctrl_id, data_id;
|
||||
u8 port_num;
|
||||
|
||||
u8 pending;
|
||||
|
||||
/* lock is mostly for pending and notify_req ... they get accessed
|
||||
* by callbacks both from tty (open/close/break) under its spinlock,
|
||||
* and notify_req.complete() which can't use that lock.
|
||||
*/
|
||||
spinlock_t lock;
|
||||
|
||||
struct usb_ep *notify;
|
||||
struct usb_request *notify_req;
|
||||
|
||||
struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */
|
||||
|
||||
/* SetControlLineState request -- CDC 1.1 section 6.2.14 (INPUT) */
|
||||
u16 port_handshake_bits;
|
||||
#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */
|
||||
#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */
|
||||
|
||||
/* SerialState notification -- CDC 1.1 section 6.3.5 (OUTPUT) */
|
||||
u16 serial_state;
|
||||
#define ACM_CTRL_OVERRUN (1 << 6)
|
||||
#define ACM_CTRL_PARITY (1 << 5)
|
||||
#define ACM_CTRL_FRAMING (1 << 4)
|
||||
#define ACM_CTRL_RI (1 << 3)
|
||||
#define ACM_CTRL_BRK (1 << 2)
|
||||
#define ACM_CTRL_DSR (1 << 1)
|
||||
#define ACM_CTRL_DCD (1 << 0)
|
||||
};
|
||||
|
||||
static inline struct f_acm *func_to_acm(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_acm, port.func);
|
||||
}
|
||||
|
||||
static inline struct f_acm *port_to_acm(struct gserial *p)
|
||||
{
|
||||
return container_of(p, struct f_acm, port);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* notification endpoint uses smallish and infrequent fixed-size messages */
|
||||
|
||||
#define GS_NOTIFY_INTERVAL_MS 32
|
||||
#define GS_NOTIFY_MAXPACKET 10 /* notification + 2 bytes */
|
||||
|
||||
/* interface and class descriptors: */
|
||||
|
||||
static struct usb_interface_assoc_descriptor
|
||||
acm_iad_descriptor = {
|
||||
.bLength = sizeof acm_iad_descriptor,
|
||||
.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
|
||||
|
||||
/* .bFirstInterface = DYNAMIC, */
|
||||
.bInterfaceCount = 2, // control + data
|
||||
.bFunctionClass = USB_CLASS_COMM,
|
||||
.bFunctionSubClass = USB_CDC_SUBCLASS_ACM,
|
||||
.bFunctionProtocol = USB_CDC_ACM_PROTO_AT_V25TER,
|
||||
/* .iFunction = DYNAMIC */
|
||||
};
|
||||
|
||||
|
||||
static struct usb_interface_descriptor acm_control_interface_desc = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_COMM,
|
||||
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
|
||||
.bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor acm_data_interface_desc = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||
.bInterfaceSubClass = 0,
|
||||
.bInterfaceProtocol = 0,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_cdc_header_desc acm_header_desc = {
|
||||
.bLength = sizeof(acm_header_desc),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_HEADER_TYPE,
|
||||
.bcdCDC = cpu_to_le16(0x0110),
|
||||
};
|
||||
|
||||
static struct usb_cdc_call_mgmt_descriptor
|
||||
acm_call_mgmt_descriptor = {
|
||||
.bLength = sizeof(acm_call_mgmt_descriptor),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE,
|
||||
.bmCapabilities = 0,
|
||||
/* .bDataInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_cdc_acm_descriptor acm_descriptor = {
|
||||
.bLength = sizeof(acm_descriptor),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_ACM_TYPE,
|
||||
.bmCapabilities = USB_CDC_CAP_LINE,
|
||||
};
|
||||
|
||||
static struct usb_cdc_union_desc acm_union_desc = {
|
||||
.bLength = sizeof(acm_union_desc),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_UNION_TYPE,
|
||||
/* .bMasterInterface0 = DYNAMIC */
|
||||
/* .bSlaveInterface0 = DYNAMIC */
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor acm_fs_notify_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET),
|
||||
.bInterval = GS_NOTIFY_INTERVAL_MS,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor acm_fs_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor acm_fs_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *acm_fs_function[] = {
|
||||
(struct usb_descriptor_header *) &acm_iad_descriptor,
|
||||
(struct usb_descriptor_header *) &acm_control_interface_desc,
|
||||
(struct usb_descriptor_header *) &acm_header_desc,
|
||||
(struct usb_descriptor_header *) &acm_call_mgmt_descriptor,
|
||||
(struct usb_descriptor_header *) &acm_descriptor,
|
||||
(struct usb_descriptor_header *) &acm_union_desc,
|
||||
(struct usb_descriptor_header *) &acm_fs_notify_desc,
|
||||
(struct usb_descriptor_header *) &acm_data_interface_desc,
|
||||
(struct usb_descriptor_header *) &acm_fs_in_desc,
|
||||
(struct usb_descriptor_header *) &acm_fs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
static struct usb_endpoint_descriptor acm_hs_notify_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET),
|
||||
.bInterval = USB_MS_TO_HS_INTERVAL(GS_NOTIFY_INTERVAL_MS),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor acm_hs_in_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 acm_hs_out_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 *acm_hs_function[] = {
|
||||
(struct usb_descriptor_header *) &acm_iad_descriptor,
|
||||
(struct usb_descriptor_header *) &acm_control_interface_desc,
|
||||
(struct usb_descriptor_header *) &acm_header_desc,
|
||||
(struct usb_descriptor_header *) &acm_call_mgmt_descriptor,
|
||||
(struct usb_descriptor_header *) &acm_descriptor,
|
||||
(struct usb_descriptor_header *) &acm_union_desc,
|
||||
(struct usb_descriptor_header *) &acm_hs_notify_desc,
|
||||
(struct usb_descriptor_header *) &acm_data_interface_desc,
|
||||
(struct usb_descriptor_header *) &acm_hs_in_desc,
|
||||
(struct usb_descriptor_header *) &acm_hs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor acm_ss_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(1024),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor acm_ss_out_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 acm_ss_bulk_comp_desc = {
|
||||
.bLength = sizeof acm_ss_bulk_comp_desc,
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *acm_ss_function[] = {
|
||||
(struct usb_descriptor_header *) &acm_iad_descriptor,
|
||||
(struct usb_descriptor_header *) &acm_control_interface_desc,
|
||||
(struct usb_descriptor_header *) &acm_header_desc,
|
||||
(struct usb_descriptor_header *) &acm_call_mgmt_descriptor,
|
||||
(struct usb_descriptor_header *) &acm_descriptor,
|
||||
(struct usb_descriptor_header *) &acm_union_desc,
|
||||
(struct usb_descriptor_header *) &acm_hs_notify_desc,
|
||||
(struct usb_descriptor_header *) &acm_ss_bulk_comp_desc,
|
||||
(struct usb_descriptor_header *) &acm_data_interface_desc,
|
||||
(struct usb_descriptor_header *) &acm_ss_in_desc,
|
||||
(struct usb_descriptor_header *) &acm_ss_bulk_comp_desc,
|
||||
(struct usb_descriptor_header *) &acm_ss_out_desc,
|
||||
(struct usb_descriptor_header *) &acm_ss_bulk_comp_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* string descriptors: */
|
||||
|
||||
#define ACM_CTRL_IDX 0
|
||||
#define ACM_DATA_IDX 1
|
||||
#define ACM_IAD_IDX 2
|
||||
|
||||
/* static strings, in UTF-8 */
|
||||
static struct usb_string acm_string_defs[] = {
|
||||
[ACM_CTRL_IDX].s = "CDC Abstract Control Model (ACM)",
|
||||
[ACM_DATA_IDX].s = "CDC ACM Data",
|
||||
[ACM_IAD_IDX ].s = "CDC Serial",
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings acm_string_table = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = acm_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *acm_strings[] = {
|
||||
&acm_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* ACM control ... data handling is delegated to tty library code.
|
||||
* The main task of this function is to activate and deactivate
|
||||
* that code based on device state; track parameters like line
|
||||
* speed, handshake state, and so on; and issue notifications.
|
||||
*/
|
||||
|
||||
static void acm_complete_set_line_coding(struct usb_ep *ep,
|
||||
struct usb_request *req)
|
||||
{
|
||||
struct f_acm *acm = ep->driver_data;
|
||||
struct usb_composite_dev *cdev = acm->port.func.config->cdev;
|
||||
|
||||
if (req->status != 0) {
|
||||
DBG(cdev, "acm ttyGS%d completion, err %d\n",
|
||||
acm->port_num, req->status);
|
||||
return;
|
||||
}
|
||||
|
||||
/* normal completion */
|
||||
if (req->actual != sizeof(acm->port_line_coding)) {
|
||||
DBG(cdev, "acm ttyGS%d short resp, len %d\n",
|
||||
acm->port_num, req->actual);
|
||||
usb_ep_set_halt(ep);
|
||||
} else {
|
||||
struct usb_cdc_line_coding *value = req->buf;
|
||||
|
||||
/* REVISIT: we currently just remember this data.
|
||||
* If we change that, (a) validate it first, then
|
||||
* (b) update whatever hardware needs updating,
|
||||
* (c) worry about locking. This is information on
|
||||
* the order of 9600-8-N-1 ... most of which means
|
||||
* nothing unless we control a real RS232 line.
|
||||
*/
|
||||
acm->port_line_coding = *value;
|
||||
}
|
||||
}
|
||||
|
||||
static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct f_acm *acm = func_to_acm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_request *req = cdev->req;
|
||||
int value = -EOPNOTSUPP;
|
||||
u16 w_index = le16_to_cpu(ctrl->wIndex);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u16 w_length = le16_to_cpu(ctrl->wLength);
|
||||
|
||||
/* composite driver infrastructure handles everything except
|
||||
* CDC class messages; interface activation uses set_alt().
|
||||
*
|
||||
* Note CDC spec table 4 lists the ACM request profile. It requires
|
||||
* encapsulated command support ... we don't handle any, and respond
|
||||
* to them by stalling. Options include get/set/clear comm features
|
||||
* (not that useful) and SEND_BREAK.
|
||||
*/
|
||||
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
|
||||
|
||||
/* SET_LINE_CODING ... just read and save what the host sends */
|
||||
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
|
||||
| USB_CDC_REQ_SET_LINE_CODING:
|
||||
if (w_length != sizeof(struct usb_cdc_line_coding)
|
||||
|| w_index != acm->ctrl_id)
|
||||
goto invalid;
|
||||
|
||||
value = w_length;
|
||||
cdev->gadget->ep0->driver_data = acm;
|
||||
req->complete = acm_complete_set_line_coding;
|
||||
break;
|
||||
|
||||
/* GET_LINE_CODING ... return what host sent, or initial value */
|
||||
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
|
||||
| USB_CDC_REQ_GET_LINE_CODING:
|
||||
if (w_index != acm->ctrl_id)
|
||||
goto invalid;
|
||||
|
||||
value = min_t(unsigned, w_length,
|
||||
sizeof(struct usb_cdc_line_coding));
|
||||
memcpy(req->buf, &acm->port_line_coding, value);
|
||||
break;
|
||||
|
||||
/* SET_CONTROL_LINE_STATE ... save what the host sent */
|
||||
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
|
||||
| USB_CDC_REQ_SET_CONTROL_LINE_STATE:
|
||||
if (w_index != acm->ctrl_id)
|
||||
goto invalid;
|
||||
|
||||
value = 0;
|
||||
|
||||
/* FIXME we should not allow data to flow until the
|
||||
* host sets the ACM_CTRL_DTR bit; and when it clears
|
||||
* that bit, we should return to that no-flow state.
|
||||
*/
|
||||
acm->port_handshake_bits = w_value;
|
||||
break;
|
||||
|
||||
default:
|
||||
invalid:
|
||||
VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
}
|
||||
|
||||
/* respond with data transfer or status phase? */
|
||||
if (value >= 0) {
|
||||
DBG(cdev, "acm ttyGS%d req%02x.%02x v%04x i%04x l%d\n",
|
||||
acm->port_num, ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
req->zero = 0;
|
||||
req->length = value;
|
||||
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
||||
if (value < 0)
|
||||
ERROR(cdev, "acm response on ttyGS%d, err %d\n",
|
||||
acm->port_num, value);
|
||||
}
|
||||
|
||||
/* device either stalls (value < 0) or reports success */
|
||||
return value;
|
||||
}
|
||||
|
||||
static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_acm *acm = func_to_acm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
/* we know alt == 0, so this is an activation or a reset */
|
||||
|
||||
if (intf == acm->ctrl_id) {
|
||||
if (acm->notify->driver_data) {
|
||||
VDBG(cdev, "reset acm control interface %d\n", intf);
|
||||
usb_ep_disable(acm->notify);
|
||||
} else {
|
||||
VDBG(cdev, "init acm ctrl interface %d\n", intf);
|
||||
if (config_ep_by_speed(cdev->gadget, f, acm->notify))
|
||||
return -EINVAL;
|
||||
}
|
||||
usb_ep_enable(acm->notify);
|
||||
acm->notify->driver_data = acm;
|
||||
|
||||
} else if (intf == acm->data_id) {
|
||||
if (acm->port.in->driver_data) {
|
||||
DBG(cdev, "reset acm ttyGS%d\n", acm->port_num);
|
||||
gserial_disconnect(&acm->port);
|
||||
}
|
||||
if (!acm->port.in->desc || !acm->port.out->desc) {
|
||||
DBG(cdev, "activate acm ttyGS%d\n", acm->port_num);
|
||||
if (config_ep_by_speed(cdev->gadget, f,
|
||||
acm->port.in) ||
|
||||
config_ep_by_speed(cdev->gadget, f,
|
||||
acm->port.out)) {
|
||||
acm->port.in->desc = NULL;
|
||||
acm->port.out->desc = NULL;
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
gserial_connect(&acm->port, acm->port_num);
|
||||
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void acm_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_acm *acm = func_to_acm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "acm ttyGS%d deactivated\n", acm->port_num);
|
||||
gserial_disconnect(&acm->port);
|
||||
usb_ep_disable(acm->notify);
|
||||
acm->notify->driver_data = NULL;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* acm_cdc_notify - issue CDC notification to host
|
||||
* @acm: wraps host to be notified
|
||||
* @type: notification type
|
||||
* @value: Refer to cdc specs, wValue field.
|
||||
* @data: data to be sent
|
||||
* @length: size of data
|
||||
* Context: irqs blocked, acm->lock held, acm_notify_req non-null
|
||||
*
|
||||
* Returns zero on success or a negative errno.
|
||||
*
|
||||
* See section 6.3.5 of the CDC 1.1 specification for information
|
||||
* about the only notification we issue: SerialState change.
|
||||
*/
|
||||
static int acm_cdc_notify(struct f_acm *acm, u8 type, u16 value,
|
||||
void *data, unsigned length)
|
||||
{
|
||||
struct usb_ep *ep = acm->notify;
|
||||
struct usb_request *req;
|
||||
struct usb_cdc_notification *notify;
|
||||
const unsigned len = sizeof(*notify) + length;
|
||||
void *buf;
|
||||
int status;
|
||||
|
||||
req = acm->notify_req;
|
||||
acm->notify_req = NULL;
|
||||
acm->pending = false;
|
||||
|
||||
req->length = len;
|
||||
notify = req->buf;
|
||||
buf = notify + 1;
|
||||
|
||||
notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS
|
||||
| USB_RECIP_INTERFACE;
|
||||
notify->bNotificationType = type;
|
||||
notify->wValue = cpu_to_le16(value);
|
||||
notify->wIndex = cpu_to_le16(acm->ctrl_id);
|
||||
notify->wLength = cpu_to_le16(length);
|
||||
memcpy(buf, data, length);
|
||||
|
||||
/* ep_queue() can complete immediately if it fills the fifo... */
|
||||
spin_unlock(&acm->lock);
|
||||
status = usb_ep_queue(ep, req, GFP_ATOMIC);
|
||||
spin_lock(&acm->lock);
|
||||
|
||||
if (status < 0) {
|
||||
ERROR(acm->port.func.config->cdev,
|
||||
"acm ttyGS%d can't notify serial state, %d\n",
|
||||
acm->port_num, status);
|
||||
acm->notify_req = req;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int acm_notify_serial_state(struct f_acm *acm)
|
||||
{
|
||||
struct usb_composite_dev *cdev = acm->port.func.config->cdev;
|
||||
int status;
|
||||
|
||||
spin_lock(&acm->lock);
|
||||
if (acm->notify_req) {
|
||||
DBG(cdev, "acm ttyGS%d serial state %04x\n",
|
||||
acm->port_num, acm->serial_state);
|
||||
status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE,
|
||||
0, &acm->serial_state, sizeof(acm->serial_state));
|
||||
} else {
|
||||
acm->pending = true;
|
||||
status = 0;
|
||||
}
|
||||
spin_unlock(&acm->lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void acm_cdc_notify_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_acm *acm = req->context;
|
||||
u8 doit = false;
|
||||
|
||||
/* on this call path we do NOT hold the port spinlock,
|
||||
* which is why ACM needs its own spinlock
|
||||
*/
|
||||
spin_lock(&acm->lock);
|
||||
if (req->status != -ESHUTDOWN)
|
||||
doit = acm->pending;
|
||||
acm->notify_req = req;
|
||||
spin_unlock(&acm->lock);
|
||||
|
||||
if (doit)
|
||||
acm_notify_serial_state(acm);
|
||||
}
|
||||
|
||||
/* connect == the TTY link is open */
|
||||
|
||||
static void acm_connect(struct gserial *port)
|
||||
{
|
||||
struct f_acm *acm = port_to_acm(port);
|
||||
|
||||
acm->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD;
|
||||
acm_notify_serial_state(acm);
|
||||
}
|
||||
|
||||
static void acm_disconnect(struct gserial *port)
|
||||
{
|
||||
struct f_acm *acm = port_to_acm(port);
|
||||
|
||||
acm->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD);
|
||||
acm_notify_serial_state(acm);
|
||||
}
|
||||
|
||||
static int acm_send_break(struct gserial *port, int duration)
|
||||
{
|
||||
struct f_acm *acm = port_to_acm(port);
|
||||
u16 state;
|
||||
|
||||
state = acm->serial_state;
|
||||
state &= ~ACM_CTRL_BRK;
|
||||
if (duration)
|
||||
state |= ACM_CTRL_BRK;
|
||||
|
||||
acm->serial_state = state;
|
||||
return acm_notify_serial_state(acm);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* ACM function driver setup/binding */
|
||||
static int
|
||||
acm_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_acm *acm = func_to_acm(f);
|
||||
struct usb_string *us;
|
||||
int status;
|
||||
struct usb_ep *ep;
|
||||
|
||||
/* REVISIT might want instance-specific strings to help
|
||||
* distinguish instances ...
|
||||
*/
|
||||
|
||||
/* maybe allocate device-global string IDs, and patch descriptors */
|
||||
us = usb_gstrings_attach(cdev, acm_strings,
|
||||
ARRAY_SIZE(acm_string_defs));
|
||||
if (IS_ERR(us))
|
||||
return PTR_ERR(us);
|
||||
acm_control_interface_desc.iInterface = us[ACM_CTRL_IDX].id;
|
||||
acm_data_interface_desc.iInterface = us[ACM_DATA_IDX].id;
|
||||
acm_iad_descriptor.iFunction = us[ACM_IAD_IDX].id;
|
||||
|
||||
/* allocate instance-specific interface IDs, and patch descriptors */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
acm->ctrl_id = status;
|
||||
acm_iad_descriptor.bFirstInterface = status;
|
||||
|
||||
acm_control_interface_desc.bInterfaceNumber = status;
|
||||
acm_union_desc .bMasterInterface0 = status;
|
||||
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
acm->data_id = status;
|
||||
|
||||
acm_data_interface_desc.bInterfaceNumber = status;
|
||||
acm_union_desc.bSlaveInterface0 = status;
|
||||
acm_call_mgmt_descriptor.bDataInterface = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
acm->port.in = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
acm->port.out = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
acm->notify = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* allocate notification */
|
||||
acm->notify_req = gs_alloc_req(ep,
|
||||
sizeof(struct usb_cdc_notification) + 2,
|
||||
GFP_KERNEL);
|
||||
if (!acm->notify_req)
|
||||
goto fail;
|
||||
|
||||
acm->notify_req->complete = acm_cdc_notify_complete;
|
||||
acm->notify_req->context = acm;
|
||||
|
||||
/* support all relevant hardware speeds... we expect that when
|
||||
* hardware is dual speed, all bulk-capable endpoints work at
|
||||
* both speeds
|
||||
*/
|
||||
acm_hs_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress;
|
||||
acm_hs_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress;
|
||||
acm_hs_notify_desc.bEndpointAddress =
|
||||
acm_fs_notify_desc.bEndpointAddress;
|
||||
|
||||
acm_ss_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress;
|
||||
acm_ss_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress;
|
||||
|
||||
status = usb_assign_descriptors(f, acm_fs_function, acm_hs_function,
|
||||
acm_ss_function);
|
||||
if (status)
|
||||
goto fail;
|
||||
|
||||
DBG(cdev, "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n",
|
||||
acm->port_num,
|
||||
gadget_is_superspeed(c->cdev->gadget) ? "super" :
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
acm->port.in->name, acm->port.out->name,
|
||||
acm->notify->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (acm->notify_req)
|
||||
gs_free_req(acm->notify, acm->notify_req);
|
||||
|
||||
/* we might as well release our claims on endpoints */
|
||||
if (acm->notify)
|
||||
acm->notify->driver_data = NULL;
|
||||
if (acm->port.out)
|
||||
acm->port.out->driver_data = NULL;
|
||||
if (acm->port.in)
|
||||
acm->port.in->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void acm_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct f_acm *acm = func_to_acm(f);
|
||||
|
||||
acm_string_defs[0].id = 0;
|
||||
usb_free_all_descriptors(f);
|
||||
if (acm->notify_req)
|
||||
gs_free_req(acm->notify, acm->notify_req);
|
||||
}
|
||||
|
||||
static void acm_free_func(struct usb_function *f)
|
||||
{
|
||||
struct f_acm *acm = func_to_acm(f);
|
||||
|
||||
kfree(acm);
|
||||
}
|
||||
|
||||
static struct usb_function *acm_alloc_func(struct usb_function_instance *fi)
|
||||
{
|
||||
struct f_serial_opts *opts;
|
||||
struct f_acm *acm;
|
||||
|
||||
acm = kzalloc(sizeof(*acm), GFP_KERNEL);
|
||||
if (!acm)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
spin_lock_init(&acm->lock);
|
||||
|
||||
acm->port.connect = acm_connect;
|
||||
acm->port.disconnect = acm_disconnect;
|
||||
acm->port.send_break = acm_send_break;
|
||||
|
||||
acm->port.func.name = "acm";
|
||||
acm->port.func.strings = acm_strings;
|
||||
/* descriptors are per-instance copies */
|
||||
acm->port.func.bind = acm_bind;
|
||||
acm->port.func.set_alt = acm_set_alt;
|
||||
acm->port.func.setup = acm_setup;
|
||||
acm->port.func.disable = acm_disable;
|
||||
|
||||
opts = container_of(fi, struct f_serial_opts, func_inst);
|
||||
acm->port_num = opts->port_num;
|
||||
acm->port.func.unbind = acm_unbind;
|
||||
acm->port.func.free_func = acm_free_func;
|
||||
|
||||
return &acm->port.func;
|
||||
}
|
||||
|
||||
static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct f_serial_opts,
|
||||
func_inst.group);
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR_STRUCT(f_serial_opts);
|
||||
static ssize_t f_acm_attr_show(struct config_item *item,
|
||||
struct configfs_attribute *attr,
|
||||
char *page)
|
||||
{
|
||||
struct f_serial_opts *opts = to_f_serial_opts(item);
|
||||
struct f_serial_opts_attribute *f_serial_opts_attr =
|
||||
container_of(attr, struct f_serial_opts_attribute, attr);
|
||||
ssize_t ret = 0;
|
||||
|
||||
if (f_serial_opts_attr->show)
|
||||
ret = f_serial_opts_attr->show(opts, page);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void acm_attr_release(struct config_item *item)
|
||||
{
|
||||
struct f_serial_opts *opts = to_f_serial_opts(item);
|
||||
|
||||
usb_put_function_instance(&opts->func_inst);
|
||||
}
|
||||
|
||||
static struct configfs_item_operations acm_item_ops = {
|
||||
.release = acm_attr_release,
|
||||
.show_attribute = f_acm_attr_show,
|
||||
};
|
||||
|
||||
static ssize_t f_acm_port_num_show(struct f_serial_opts *opts, char *page)
|
||||
{
|
||||
return sprintf(page, "%u\n", opts->port_num);
|
||||
}
|
||||
|
||||
static struct f_serial_opts_attribute f_acm_port_num =
|
||||
__CONFIGFS_ATTR_RO(port_num, f_acm_port_num_show);
|
||||
|
||||
|
||||
static struct configfs_attribute *acm_attrs[] = {
|
||||
&f_acm_port_num.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct config_item_type acm_func_type = {
|
||||
.ct_item_ops = &acm_item_ops,
|
||||
.ct_attrs = acm_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void acm_free_instance(struct usb_function_instance *fi)
|
||||
{
|
||||
struct f_serial_opts *opts;
|
||||
|
||||
opts = container_of(fi, struct f_serial_opts, func_inst);
|
||||
gserial_free_line(opts->port_num);
|
||||
kfree(opts);
|
||||
}
|
||||
|
||||
static struct usb_function_instance *acm_alloc_instance(void)
|
||||
{
|
||||
struct f_serial_opts *opts;
|
||||
int ret;
|
||||
|
||||
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
||||
if (!opts)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
opts->func_inst.free_func_inst = acm_free_instance;
|
||||
ret = gserial_alloc_line(&opts->port_num);
|
||||
if (ret) {
|
||||
kfree(opts);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
config_group_init_type_name(&opts->func_inst.group, "",
|
||||
&acm_func_type);
|
||||
return &opts->func_inst;
|
||||
}
|
||||
DECLARE_USB_FUNCTION_INIT(acm, acm_alloc_instance, acm_alloc_func);
|
||||
MODULE_LICENSE("GPL");
|
||||
973
drivers/usb/gadget/function/f_ecm.c
Normal file
973
drivers/usb/gadget/function/f_ecm.c
Normal file
|
|
@ -0,0 +1,973 @@
|
|||
/*
|
||||
* f_ecm.c -- USB CDC Ethernet (ECM) link function driver
|
||||
*
|
||||
* Copyright (C) 2003-2005,2008 David Brownell
|
||||
* Copyright (C) 2008 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/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/etherdevice.h>
|
||||
|
||||
#include "u_ether.h"
|
||||
#include "u_ether_configfs.h"
|
||||
#include "u_ecm.h"
|
||||
|
||||
|
||||
/*
|
||||
* This function is a "CDC Ethernet Networking Control Model" (CDC ECM)
|
||||
* Ethernet link. The data transfer model is simple (packets sent and
|
||||
* received over bulk endpoints using normal short packet termination),
|
||||
* and the control model exposes various data and optional notifications.
|
||||
*
|
||||
* ECM is well standardized and (except for Microsoft) supported by most
|
||||
* operating systems with USB host support. It's the preferred interop
|
||||
* solution for Ethernet over USB, at least for firmware based solutions.
|
||||
* (Hardware solutions tend to be more minimalist.) A newer and simpler
|
||||
* "Ethernet Emulation Model" (CDC EEM) hasn't yet caught on.
|
||||
*
|
||||
* Note that ECM requires the use of "alternate settings" for its data
|
||||
* interface. This means that the set_alt() method has real work to do,
|
||||
* and also means that a get_alt() method is required.
|
||||
*/
|
||||
|
||||
|
||||
enum ecm_notify_state {
|
||||
ECM_NOTIFY_NONE, /* don't notify */
|
||||
ECM_NOTIFY_CONNECT, /* issue CONNECT next */
|
||||
ECM_NOTIFY_SPEED, /* issue SPEED_CHANGE next */
|
||||
};
|
||||
|
||||
struct f_ecm {
|
||||
struct gether port;
|
||||
u8 ctrl_id, data_id;
|
||||
|
||||
char ethaddr[14];
|
||||
|
||||
struct usb_ep *notify;
|
||||
struct usb_request *notify_req;
|
||||
u8 notify_state;
|
||||
bool is_open;
|
||||
|
||||
/* FIXME is_open needs some irq-ish locking
|
||||
* ... possibly the same as port.ioport
|
||||
*/
|
||||
};
|
||||
|
||||
static inline struct f_ecm *func_to_ecm(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_ecm, port.func);
|
||||
}
|
||||
|
||||
/* peak (theoretical) bulk transfer rate in bits-per-second */
|
||||
static inline unsigned ecm_bitrate(struct usb_gadget *g)
|
||||
{
|
||||
if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER)
|
||||
return 13 * 1024 * 8 * 1000 * 8;
|
||||
else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
|
||||
return 13 * 512 * 8 * 1000 * 8;
|
||||
else
|
||||
return 19 * 64 * 1 * 1000 * 8;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Include the status endpoint if we can, even though it's optional.
|
||||
*
|
||||
* Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one
|
||||
* packet, to simplify cancellation; and a big transfer interval, to
|
||||
* waste less bandwidth.
|
||||
*
|
||||
* Some drivers (like Linux 2.4 cdc-ether!) "need" it to exist even
|
||||
* if they ignore the connect/disconnect notifications that real aether
|
||||
* can provide. More advanced cdc configurations might want to support
|
||||
* encapsulated commands (vendor-specific, using control-OUT).
|
||||
*/
|
||||
|
||||
#define ECM_STATUS_INTERVAL_MS 32
|
||||
#define ECM_STATUS_BYTECOUNT 16 /* 8 byte header + data */
|
||||
|
||||
|
||||
/* interface descriptor: */
|
||||
|
||||
static struct usb_interface_assoc_descriptor
|
||||
ecm_iad_descriptor = {
|
||||
.bLength = sizeof ecm_iad_descriptor,
|
||||
.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
|
||||
|
||||
/* .bFirstInterface = DYNAMIC, */
|
||||
.bInterfaceCount = 2, /* control + data */
|
||||
.bFunctionClass = USB_CLASS_COMM,
|
||||
.bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET,
|
||||
.bFunctionProtocol = USB_CDC_PROTO_NONE,
|
||||
/* .iFunction = DYNAMIC */
|
||||
};
|
||||
|
||||
|
||||
static struct usb_interface_descriptor ecm_control_intf = {
|
||||
.bLength = sizeof ecm_control_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
/* status endpoint is optional; this could be patched later */
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_COMM,
|
||||
.bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET,
|
||||
.bInterfaceProtocol = USB_CDC_PROTO_NONE,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_cdc_header_desc ecm_header_desc = {
|
||||
.bLength = sizeof ecm_header_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_HEADER_TYPE,
|
||||
|
||||
.bcdCDC = cpu_to_le16(0x0110),
|
||||
};
|
||||
|
||||
static struct usb_cdc_union_desc ecm_union_desc = {
|
||||
.bLength = sizeof(ecm_union_desc),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_UNION_TYPE,
|
||||
/* .bMasterInterface0 = DYNAMIC */
|
||||
/* .bSlaveInterface0 = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_cdc_ether_desc ecm_desc = {
|
||||
.bLength = sizeof ecm_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_ETHERNET_TYPE,
|
||||
|
||||
/* this descriptor actually adds value, surprise! */
|
||||
/* .iMACAddress = DYNAMIC */
|
||||
.bmEthernetStatistics = cpu_to_le32(0), /* no statistics */
|
||||
.wMaxSegmentSize = cpu_to_le16(ETH_FRAME_LEN),
|
||||
.wNumberMCFilters = cpu_to_le16(0),
|
||||
.bNumberPowerFilters = 0,
|
||||
};
|
||||
|
||||
/* the default data interface has no endpoints ... */
|
||||
|
||||
static struct usb_interface_descriptor ecm_data_nop_intf = {
|
||||
.bLength = sizeof ecm_data_nop_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 0,
|
||||
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||
.bInterfaceSubClass = 0,
|
||||
.bInterfaceProtocol = 0,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* ... but the "real" data interface has two bulk endpoints */
|
||||
|
||||
static struct usb_interface_descriptor ecm_data_intf = {
|
||||
.bLength = sizeof ecm_data_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 1,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||
.bInterfaceSubClass = 0,
|
||||
.bInterfaceProtocol = 0,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor fs_ecm_notify_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = cpu_to_le16(ECM_STATUS_BYTECOUNT),
|
||||
.bInterval = ECM_STATUS_INTERVAL_MS,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor fs_ecm_in_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_ecm_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *ecm_fs_function[] = {
|
||||
/* CDC ECM control descriptors */
|
||||
(struct usb_descriptor_header *) &ecm_iad_descriptor,
|
||||
(struct usb_descriptor_header *) &ecm_control_intf,
|
||||
(struct usb_descriptor_header *) &ecm_header_desc,
|
||||
(struct usb_descriptor_header *) &ecm_union_desc,
|
||||
(struct usb_descriptor_header *) &ecm_desc,
|
||||
|
||||
/* NOTE: status endpoint might need to be removed */
|
||||
(struct usb_descriptor_header *) &fs_ecm_notify_desc,
|
||||
|
||||
/* data interface, altsettings 0 and 1 */
|
||||
(struct usb_descriptor_header *) &ecm_data_nop_intf,
|
||||
(struct usb_descriptor_header *) &ecm_data_intf,
|
||||
(struct usb_descriptor_header *) &fs_ecm_in_desc,
|
||||
(struct usb_descriptor_header *) &fs_ecm_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor hs_ecm_notify_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = cpu_to_le16(ECM_STATUS_BYTECOUNT),
|
||||
.bInterval = USB_MS_TO_HS_INTERVAL(ECM_STATUS_INTERVAL_MS),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor hs_ecm_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor hs_ecm_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *ecm_hs_function[] = {
|
||||
/* CDC ECM control descriptors */
|
||||
(struct usb_descriptor_header *) &ecm_iad_descriptor,
|
||||
(struct usb_descriptor_header *) &ecm_control_intf,
|
||||
(struct usb_descriptor_header *) &ecm_header_desc,
|
||||
(struct usb_descriptor_header *) &ecm_union_desc,
|
||||
(struct usb_descriptor_header *) &ecm_desc,
|
||||
|
||||
/* NOTE: status endpoint might need to be removed */
|
||||
(struct usb_descriptor_header *) &hs_ecm_notify_desc,
|
||||
|
||||
/* data interface, altsettings 0 and 1 */
|
||||
(struct usb_descriptor_header *) &ecm_data_nop_intf,
|
||||
(struct usb_descriptor_header *) &ecm_data_intf,
|
||||
(struct usb_descriptor_header *) &hs_ecm_in_desc,
|
||||
(struct usb_descriptor_header *) &hs_ecm_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* super speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor ss_ecm_notify_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = cpu_to_le16(ECM_STATUS_BYTECOUNT),
|
||||
.bInterval = USB_MS_TO_HS_INTERVAL(ECM_STATUS_INTERVAL_MS),
|
||||
};
|
||||
|
||||
static struct usb_ss_ep_comp_descriptor ss_ecm_intr_comp_desc = {
|
||||
.bLength = sizeof ss_ecm_intr_comp_desc,
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
|
||||
/* the following 3 values can be tweaked if necessary */
|
||||
/* .bMaxBurst = 0, */
|
||||
/* .bmAttributes = 0, */
|
||||
.wBytesPerInterval = cpu_to_le16(ECM_STATUS_BYTECOUNT),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor ss_ecm_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(1024),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor ss_ecm_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(1024),
|
||||
};
|
||||
|
||||
static struct usb_ss_ep_comp_descriptor ss_ecm_bulk_comp_desc = {
|
||||
.bLength = sizeof ss_ecm_bulk_comp_desc,
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
|
||||
/* the following 2 values can be tweaked if necessary */
|
||||
/* .bMaxBurst = 0, */
|
||||
/* .bmAttributes = 0, */
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *ecm_ss_function[] = {
|
||||
/* CDC ECM control descriptors */
|
||||
(struct usb_descriptor_header *) &ecm_iad_descriptor,
|
||||
(struct usb_descriptor_header *) &ecm_control_intf,
|
||||
(struct usb_descriptor_header *) &ecm_header_desc,
|
||||
(struct usb_descriptor_header *) &ecm_union_desc,
|
||||
(struct usb_descriptor_header *) &ecm_desc,
|
||||
|
||||
/* NOTE: status endpoint might need to be removed */
|
||||
(struct usb_descriptor_header *) &ss_ecm_notify_desc,
|
||||
(struct usb_descriptor_header *) &ss_ecm_intr_comp_desc,
|
||||
|
||||
/* data interface, altsettings 0 and 1 */
|
||||
(struct usb_descriptor_header *) &ecm_data_nop_intf,
|
||||
(struct usb_descriptor_header *) &ecm_data_intf,
|
||||
(struct usb_descriptor_header *) &ss_ecm_in_desc,
|
||||
(struct usb_descriptor_header *) &ss_ecm_bulk_comp_desc,
|
||||
(struct usb_descriptor_header *) &ss_ecm_out_desc,
|
||||
(struct usb_descriptor_header *) &ss_ecm_bulk_comp_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* string descriptors: */
|
||||
|
||||
static struct usb_string ecm_string_defs[] = {
|
||||
[0].s = "CDC Ethernet Control Model (ECM)",
|
||||
[1].s = "",
|
||||
[2].s = "CDC Ethernet Data",
|
||||
[3].s = "CDC ECM",
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings ecm_string_table = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = ecm_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *ecm_strings[] = {
|
||||
&ecm_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void ecm_do_notify(struct f_ecm *ecm)
|
||||
{
|
||||
struct usb_request *req = ecm->notify_req;
|
||||
struct usb_cdc_notification *event;
|
||||
struct usb_composite_dev *cdev = ecm->port.func.config->cdev;
|
||||
__le32 *data;
|
||||
int status;
|
||||
|
||||
/* notification already in flight? */
|
||||
if (!req)
|
||||
return;
|
||||
|
||||
event = req->buf;
|
||||
switch (ecm->notify_state) {
|
||||
case ECM_NOTIFY_NONE:
|
||||
return;
|
||||
|
||||
case ECM_NOTIFY_CONNECT:
|
||||
event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
|
||||
if (ecm->is_open)
|
||||
event->wValue = cpu_to_le16(1);
|
||||
else
|
||||
event->wValue = cpu_to_le16(0);
|
||||
event->wLength = 0;
|
||||
req->length = sizeof *event;
|
||||
|
||||
DBG(cdev, "notify connect %s\n",
|
||||
ecm->is_open ? "true" : "false");
|
||||
ecm->notify_state = ECM_NOTIFY_SPEED;
|
||||
break;
|
||||
|
||||
case ECM_NOTIFY_SPEED:
|
||||
event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE;
|
||||
event->wValue = cpu_to_le16(0);
|
||||
event->wLength = cpu_to_le16(8);
|
||||
req->length = ECM_STATUS_BYTECOUNT;
|
||||
|
||||
/* SPEED_CHANGE data is up/down speeds in bits/sec */
|
||||
data = req->buf + sizeof *event;
|
||||
data[0] = cpu_to_le32(ecm_bitrate(cdev->gadget));
|
||||
data[1] = data[0];
|
||||
|
||||
DBG(cdev, "notify speed %d\n", ecm_bitrate(cdev->gadget));
|
||||
ecm->notify_state = ECM_NOTIFY_NONE;
|
||||
break;
|
||||
}
|
||||
event->bmRequestType = 0xA1;
|
||||
event->wIndex = cpu_to_le16(ecm->ctrl_id);
|
||||
|
||||
ecm->notify_req = NULL;
|
||||
status = usb_ep_queue(ecm->notify, req, GFP_ATOMIC);
|
||||
if (status < 0) {
|
||||
ecm->notify_req = req;
|
||||
DBG(cdev, "notify --> %d\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
static void ecm_notify(struct f_ecm *ecm)
|
||||
{
|
||||
/* NOTE on most versions of Linux, host side cdc-ethernet
|
||||
* won't listen for notifications until its netdevice opens.
|
||||
* The first notification then sits in the FIFO for a long
|
||||
* time, and the second one is queued.
|
||||
*/
|
||||
ecm->notify_state = ECM_NOTIFY_CONNECT;
|
||||
ecm_do_notify(ecm);
|
||||
}
|
||||
|
||||
static void ecm_notify_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_ecm *ecm = req->context;
|
||||
struct usb_composite_dev *cdev = ecm->port.func.config->cdev;
|
||||
struct usb_cdc_notification *event = req->buf;
|
||||
|
||||
switch (req->status) {
|
||||
case 0:
|
||||
/* no fault */
|
||||
break;
|
||||
case -ECONNRESET:
|
||||
case -ESHUTDOWN:
|
||||
ecm->notify_state = ECM_NOTIFY_NONE;
|
||||
break;
|
||||
default:
|
||||
DBG(cdev, "event %02x --> %d\n",
|
||||
event->bNotificationType, req->status);
|
||||
break;
|
||||
}
|
||||
ecm->notify_req = req;
|
||||
ecm_do_notify(ecm);
|
||||
}
|
||||
|
||||
static int ecm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_request *req = cdev->req;
|
||||
int value = -EOPNOTSUPP;
|
||||
u16 w_index = le16_to_cpu(ctrl->wIndex);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u16 w_length = le16_to_cpu(ctrl->wLength);
|
||||
|
||||
/* composite driver infrastructure handles everything except
|
||||
* CDC class messages; interface activation uses set_alt().
|
||||
*/
|
||||
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
|
||||
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
|
||||
| USB_CDC_SET_ETHERNET_PACKET_FILTER:
|
||||
/* see 6.2.30: no data, wIndex = interface,
|
||||
* wValue = packet filter bitmap
|
||||
*/
|
||||
if (w_length != 0 || w_index != ecm->ctrl_id)
|
||||
goto invalid;
|
||||
DBG(cdev, "packet filter %02x\n", w_value);
|
||||
/* REVISIT locking of cdc_filter. This assumes the UDC
|
||||
* driver won't have a concurrent packet TX irq running on
|
||||
* another CPU; or that if it does, this write is atomic...
|
||||
*/
|
||||
ecm->port.cdc_filter = w_value;
|
||||
value = 0;
|
||||
break;
|
||||
|
||||
/* and optionally:
|
||||
* case USB_CDC_SEND_ENCAPSULATED_COMMAND:
|
||||
* case USB_CDC_GET_ENCAPSULATED_RESPONSE:
|
||||
* case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS:
|
||||
* case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER:
|
||||
* case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER:
|
||||
* case USB_CDC_GET_ETHERNET_STATISTIC:
|
||||
*/
|
||||
|
||||
default:
|
||||
invalid:
|
||||
DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
}
|
||||
|
||||
/* respond with data transfer or status phase? */
|
||||
if (value >= 0) {
|
||||
DBG(cdev, "ecm req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
req->zero = 0;
|
||||
req->length = value;
|
||||
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
||||
if (value < 0)
|
||||
ERROR(cdev, "ecm req %02x.%02x response err %d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
value);
|
||||
}
|
||||
|
||||
/* device either stalls (value < 0) or reports success */
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
static int ecm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
/* Control interface has only altsetting 0 */
|
||||
if (intf == ecm->ctrl_id) {
|
||||
if (alt != 0)
|
||||
goto fail;
|
||||
|
||||
if (ecm->notify->driver_data) {
|
||||
VDBG(cdev, "reset ecm control %d\n", intf);
|
||||
usb_ep_disable(ecm->notify);
|
||||
}
|
||||
if (!(ecm->notify->desc)) {
|
||||
VDBG(cdev, "init ecm ctrl %d\n", intf);
|
||||
if (config_ep_by_speed(cdev->gadget, f, ecm->notify))
|
||||
goto fail;
|
||||
}
|
||||
usb_ep_enable(ecm->notify);
|
||||
ecm->notify->driver_data = ecm;
|
||||
|
||||
/* Data interface has two altsettings, 0 and 1 */
|
||||
} else if (intf == ecm->data_id) {
|
||||
if (alt > 1)
|
||||
goto fail;
|
||||
|
||||
if (ecm->port.in_ep->driver_data) {
|
||||
DBG(cdev, "reset ecm\n");
|
||||
gether_disconnect(&ecm->port);
|
||||
}
|
||||
|
||||
if (!ecm->port.in_ep->desc ||
|
||||
!ecm->port.out_ep->desc) {
|
||||
DBG(cdev, "init ecm\n");
|
||||
if (config_ep_by_speed(cdev->gadget, f,
|
||||
ecm->port.in_ep) ||
|
||||
config_ep_by_speed(cdev->gadget, f,
|
||||
ecm->port.out_ep)) {
|
||||
ecm->port.in_ep->desc = NULL;
|
||||
ecm->port.out_ep->desc = NULL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* CDC Ethernet only sends data in non-default altsettings.
|
||||
* Changing altsettings resets filters, statistics, etc.
|
||||
*/
|
||||
if (alt == 1) {
|
||||
struct net_device *net;
|
||||
|
||||
/* Enable zlps by default for ECM conformance;
|
||||
* override for musb_hdrc (avoids txdma ovhead).
|
||||
*/
|
||||
ecm->port.is_zlp_ok = !(gadget_is_musbhdrc(cdev->gadget)
|
||||
);
|
||||
ecm->port.cdc_filter = DEFAULT_FILTER;
|
||||
DBG(cdev, "activate ecm\n");
|
||||
net = gether_connect(&ecm->port);
|
||||
if (IS_ERR(net))
|
||||
return PTR_ERR(net);
|
||||
}
|
||||
|
||||
/* NOTE this can be a minor disagreement with the ECM spec,
|
||||
* which says speed notifications will "always" follow
|
||||
* connection notifications. But we allow one connect to
|
||||
* follow another (if the first is in flight), and instead
|
||||
* just guarantee that a speed notification is always sent.
|
||||
*/
|
||||
ecm_notify(ecm);
|
||||
} else
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Because the data interface supports multiple altsettings,
|
||||
* this ECM function *MUST* implement a get_alt() method.
|
||||
*/
|
||||
static int ecm_get_alt(struct usb_function *f, unsigned intf)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
|
||||
if (intf == ecm->ctrl_id)
|
||||
return 0;
|
||||
return ecm->port.in_ep->driver_data ? 1 : 0;
|
||||
}
|
||||
|
||||
static void ecm_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "ecm deactivated\n");
|
||||
|
||||
if (ecm->port.in_ep->driver_data)
|
||||
gether_disconnect(&ecm->port);
|
||||
|
||||
if (ecm->notify->driver_data) {
|
||||
usb_ep_disable(ecm->notify);
|
||||
ecm->notify->driver_data = NULL;
|
||||
ecm->notify->desc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Callbacks let us notify the host about connect/disconnect when the
|
||||
* net device is opened or closed.
|
||||
*
|
||||
* For testing, note that link states on this side include both opened
|
||||
* and closed variants of:
|
||||
*
|
||||
* - disconnected/unconfigured
|
||||
* - configured but inactive (data alt 0)
|
||||
* - configured and active (data alt 1)
|
||||
*
|
||||
* Each needs to be tested with unplug, rmmod, SET_CONFIGURATION, and
|
||||
* SET_INTERFACE (altsetting). Remember also that "configured" doesn't
|
||||
* imply the host is actually polling the notification endpoint, and
|
||||
* likewise that "active" doesn't imply it's actually using the data
|
||||
* endpoints for traffic.
|
||||
*/
|
||||
|
||||
static void ecm_open(struct gether *geth)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(&geth->func);
|
||||
|
||||
DBG(ecm->port.func.config->cdev, "%s\n", __func__);
|
||||
|
||||
ecm->is_open = true;
|
||||
ecm_notify(ecm);
|
||||
}
|
||||
|
||||
static void ecm_close(struct gether *geth)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(&geth->func);
|
||||
|
||||
DBG(ecm->port.func.config->cdev, "%s\n", __func__);
|
||||
|
||||
ecm->is_open = false;
|
||||
ecm_notify(ecm);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* ethernet function driver setup/binding */
|
||||
|
||||
static int
|
||||
ecm_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
struct usb_string *us;
|
||||
int status;
|
||||
struct usb_ep *ep;
|
||||
|
||||
struct f_ecm_opts *ecm_opts;
|
||||
|
||||
if (!can_support_ecm(cdev->gadget))
|
||||
return -EINVAL;
|
||||
|
||||
ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst);
|
||||
|
||||
/*
|
||||
* in drivers/usb/gadget/configfs.c:configfs_composite_bind()
|
||||
* configurations are bound in sequence with list_for_each_entry,
|
||||
* in each configuration its functions are bound in sequence
|
||||
* with list_for_each_entry, so we assume no race condition
|
||||
* with regard to ecm_opts->bound access
|
||||
*/
|
||||
if (!ecm_opts->bound) {
|
||||
mutex_lock(&ecm_opts->lock);
|
||||
gether_set_gadget(ecm_opts->net, cdev->gadget);
|
||||
status = gether_register_netdev(ecm_opts->net);
|
||||
mutex_unlock(&ecm_opts->lock);
|
||||
if (status)
|
||||
return status;
|
||||
ecm_opts->bound = true;
|
||||
}
|
||||
|
||||
us = usb_gstrings_attach(cdev, ecm_strings,
|
||||
ARRAY_SIZE(ecm_string_defs));
|
||||
if (IS_ERR(us))
|
||||
return PTR_ERR(us);
|
||||
ecm_control_intf.iInterface = us[0].id;
|
||||
ecm_data_intf.iInterface = us[2].id;
|
||||
ecm_desc.iMACAddress = us[1].id;
|
||||
ecm_iad_descriptor.iFunction = us[3].id;
|
||||
|
||||
/* allocate instance-specific interface IDs */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
ecm->ctrl_id = status;
|
||||
ecm_iad_descriptor.bFirstInterface = status;
|
||||
|
||||
ecm_control_intf.bInterfaceNumber = status;
|
||||
ecm_union_desc.bMasterInterface0 = status;
|
||||
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
ecm->data_id = status;
|
||||
|
||||
ecm_data_nop_intf.bInterfaceNumber = status;
|
||||
ecm_data_intf.bInterfaceNumber = status;
|
||||
ecm_union_desc.bSlaveInterface0 = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_in_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
ecm->port.in_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_out_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
ecm->port.out_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* NOTE: a status/notification endpoint is *OPTIONAL* but we
|
||||
* don't treat it that way. It's simpler, and some newer CDC
|
||||
* profiles (wireless handsets) no longer treat it as optional.
|
||||
*/
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_notify_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
ecm->notify = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
status = -ENOMEM;
|
||||
|
||||
/* allocate notification request and buffer */
|
||||
ecm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
|
||||
if (!ecm->notify_req)
|
||||
goto fail;
|
||||
ecm->notify_req->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL);
|
||||
if (!ecm->notify_req->buf)
|
||||
goto fail;
|
||||
ecm->notify_req->context = ecm;
|
||||
ecm->notify_req->complete = ecm_notify_complete;
|
||||
|
||||
/* support all relevant hardware speeds... we expect that when
|
||||
* hardware is dual speed, all bulk-capable endpoints work at
|
||||
* both speeds
|
||||
*/
|
||||
hs_ecm_in_desc.bEndpointAddress = fs_ecm_in_desc.bEndpointAddress;
|
||||
hs_ecm_out_desc.bEndpointAddress = fs_ecm_out_desc.bEndpointAddress;
|
||||
hs_ecm_notify_desc.bEndpointAddress =
|
||||
fs_ecm_notify_desc.bEndpointAddress;
|
||||
|
||||
ss_ecm_in_desc.bEndpointAddress = fs_ecm_in_desc.bEndpointAddress;
|
||||
ss_ecm_out_desc.bEndpointAddress = fs_ecm_out_desc.bEndpointAddress;
|
||||
ss_ecm_notify_desc.bEndpointAddress =
|
||||
fs_ecm_notify_desc.bEndpointAddress;
|
||||
|
||||
status = usb_assign_descriptors(f, ecm_fs_function, ecm_hs_function,
|
||||
ecm_ss_function);
|
||||
if (status)
|
||||
goto fail;
|
||||
|
||||
/* NOTE: all that is done without knowing or caring about
|
||||
* the network link ... which is unavailable to this code
|
||||
* until we're activated via set_alt().
|
||||
*/
|
||||
|
||||
ecm->port.open = ecm_open;
|
||||
ecm->port.close = ecm_close;
|
||||
|
||||
DBG(cdev, "CDC Ethernet: %s speed IN/%s OUT/%s NOTIFY/%s\n",
|
||||
gadget_is_superspeed(c->cdev->gadget) ? "super" :
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
ecm->port.in_ep->name, ecm->port.out_ep->name,
|
||||
ecm->notify->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (ecm->notify_req) {
|
||||
kfree(ecm->notify_req->buf);
|
||||
usb_ep_free_request(ecm->notify, ecm->notify_req);
|
||||
}
|
||||
|
||||
/* we might as well release our claims on endpoints */
|
||||
if (ecm->notify)
|
||||
ecm->notify->driver_data = NULL;
|
||||
if (ecm->port.out_ep)
|
||||
ecm->port.out_ep->driver_data = NULL;
|
||||
if (ecm->port.in_ep)
|
||||
ecm->port.in_ep->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static inline struct f_ecm_opts *to_f_ecm_opts(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct f_ecm_opts,
|
||||
func_inst.group);
|
||||
}
|
||||
|
||||
/* f_ecm_item_ops */
|
||||
USB_ETHERNET_CONFIGFS_ITEM(ecm);
|
||||
|
||||
/* f_ecm_opts_dev_addr */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(ecm);
|
||||
|
||||
/* f_ecm_opts_host_addr */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(ecm);
|
||||
|
||||
/* f_ecm_opts_qmult */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(ecm);
|
||||
|
||||
/* f_ecm_opts_ifname */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(ecm);
|
||||
|
||||
static struct configfs_attribute *ecm_attrs[] = {
|
||||
&f_ecm_opts_dev_addr.attr,
|
||||
&f_ecm_opts_host_addr.attr,
|
||||
&f_ecm_opts_qmult.attr,
|
||||
&f_ecm_opts_ifname.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct config_item_type ecm_func_type = {
|
||||
.ct_item_ops = &ecm_item_ops,
|
||||
.ct_attrs = ecm_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void ecm_free_inst(struct usb_function_instance *f)
|
||||
{
|
||||
struct f_ecm_opts *opts;
|
||||
|
||||
opts = container_of(f, struct f_ecm_opts, func_inst);
|
||||
if (opts->bound)
|
||||
gether_cleanup(netdev_priv(opts->net));
|
||||
else
|
||||
free_netdev(opts->net);
|
||||
kfree(opts);
|
||||
}
|
||||
|
||||
static struct usb_function_instance *ecm_alloc_inst(void)
|
||||
{
|
||||
struct f_ecm_opts *opts;
|
||||
|
||||
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
||||
if (!opts)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
mutex_init(&opts->lock);
|
||||
opts->func_inst.free_func_inst = ecm_free_inst;
|
||||
opts->net = gether_setup_default();
|
||||
if (IS_ERR(opts->net)) {
|
||||
struct net_device *net = opts->net;
|
||||
kfree(opts);
|
||||
return ERR_CAST(net);
|
||||
}
|
||||
|
||||
config_group_init_type_name(&opts->func_inst.group, "", &ecm_func_type);
|
||||
|
||||
return &opts->func_inst;
|
||||
}
|
||||
|
||||
static void ecm_free(struct usb_function *f)
|
||||
{
|
||||
struct f_ecm *ecm;
|
||||
struct f_ecm_opts *opts;
|
||||
|
||||
ecm = func_to_ecm(f);
|
||||
opts = container_of(f->fi, struct f_ecm_opts, func_inst);
|
||||
kfree(ecm);
|
||||
mutex_lock(&opts->lock);
|
||||
opts->refcnt--;
|
||||
mutex_unlock(&opts->lock);
|
||||
}
|
||||
|
||||
static void ecm_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
|
||||
DBG(c->cdev, "ecm unbind\n");
|
||||
|
||||
usb_free_all_descriptors(f);
|
||||
|
||||
kfree(ecm->notify_req->buf);
|
||||
usb_ep_free_request(ecm->notify, ecm->notify_req);
|
||||
}
|
||||
|
||||
static struct usb_function *ecm_alloc(struct usb_function_instance *fi)
|
||||
{
|
||||
struct f_ecm *ecm;
|
||||
struct f_ecm_opts *opts;
|
||||
int status;
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
ecm = kzalloc(sizeof(*ecm), GFP_KERNEL);
|
||||
if (!ecm)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
opts = container_of(fi, struct f_ecm_opts, func_inst);
|
||||
mutex_lock(&opts->lock);
|
||||
opts->refcnt++;
|
||||
|
||||
/* export host's Ethernet address in CDC format */
|
||||
status = gether_get_host_addr_cdc(opts->net, ecm->ethaddr,
|
||||
sizeof(ecm->ethaddr));
|
||||
if (status < 12) {
|
||||
kfree(ecm);
|
||||
mutex_unlock(&opts->lock);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
ecm_string_defs[1].s = ecm->ethaddr;
|
||||
|
||||
ecm->port.ioport = netdev_priv(opts->net);
|
||||
mutex_unlock(&opts->lock);
|
||||
ecm->port.cdc_filter = DEFAULT_FILTER;
|
||||
|
||||
ecm->port.func.name = "cdc_ethernet";
|
||||
/* descriptors are per-instance copies */
|
||||
ecm->port.func.bind = ecm_bind;
|
||||
ecm->port.func.unbind = ecm_unbind;
|
||||
ecm->port.func.set_alt = ecm_set_alt;
|
||||
ecm->port.func.get_alt = ecm_get_alt;
|
||||
ecm->port.func.setup = ecm_setup;
|
||||
ecm->port.func.disable = ecm_disable;
|
||||
ecm->port.func.free_func = ecm_free;
|
||||
|
||||
return &ecm->port.func;
|
||||
}
|
||||
|
||||
DECLARE_USB_FUNCTION_INIT(ecm, ecm_alloc_inst, ecm_alloc);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("David Brownell");
|
||||
660
drivers/usb/gadget/function/f_eem.c
Normal file
660
drivers/usb/gadget/function/f_eem.c
Normal file
|
|
@ -0,0 +1,660 @@
|
|||
/*
|
||||
* f_eem.c -- USB CDC Ethernet (EEM) link function driver
|
||||
*
|
||||
* Copyright (C) 2003-2005,2008 David Brownell
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
* Copyright (C) 2009 EF Johnson Technologies
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/crc32.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "u_ether.h"
|
||||
#include "u_ether_configfs.h"
|
||||
#include "u_eem.h"
|
||||
|
||||
#define EEM_HLEN 2
|
||||
|
||||
/*
|
||||
* This function is a "CDC Ethernet Emulation Model" (CDC EEM)
|
||||
* Ethernet link.
|
||||
*/
|
||||
|
||||
struct f_eem {
|
||||
struct gether port;
|
||||
u8 ctrl_id;
|
||||
};
|
||||
|
||||
static inline struct f_eem *func_to_eem(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_eem, port.func);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* interface descriptor: */
|
||||
|
||||
static struct usb_interface_descriptor eem_intf = {
|
||||
.bLength = sizeof eem_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_COMM,
|
||||
.bInterfaceSubClass = USB_CDC_SUBCLASS_EEM,
|
||||
.bInterfaceProtocol = USB_CDC_PROTO_EEM,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor eem_fs_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor eem_fs_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *eem_fs_function[] = {
|
||||
/* CDC EEM control descriptors */
|
||||
(struct usb_descriptor_header *) &eem_intf,
|
||||
(struct usb_descriptor_header *) &eem_fs_in_desc,
|
||||
(struct usb_descriptor_header *) &eem_fs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor eem_hs_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor eem_hs_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *eem_hs_function[] = {
|
||||
/* CDC EEM control descriptors */
|
||||
(struct usb_descriptor_header *) &eem_intf,
|
||||
(struct usb_descriptor_header *) &eem_hs_in_desc,
|
||||
(struct usb_descriptor_header *) &eem_hs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* super speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor eem_ss_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(1024),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor eem_ss_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(1024),
|
||||
};
|
||||
|
||||
static struct usb_ss_ep_comp_descriptor eem_ss_bulk_comp_desc = {
|
||||
.bLength = sizeof eem_ss_bulk_comp_desc,
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
|
||||
/* the following 2 values can be tweaked if necessary */
|
||||
/* .bMaxBurst = 0, */
|
||||
/* .bmAttributes = 0, */
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *eem_ss_function[] = {
|
||||
/* CDC EEM control descriptors */
|
||||
(struct usb_descriptor_header *) &eem_intf,
|
||||
(struct usb_descriptor_header *) &eem_ss_in_desc,
|
||||
(struct usb_descriptor_header *) &eem_ss_bulk_comp_desc,
|
||||
(struct usb_descriptor_header *) &eem_ss_out_desc,
|
||||
(struct usb_descriptor_header *) &eem_ss_bulk_comp_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* string descriptors: */
|
||||
|
||||
static struct usb_string eem_string_defs[] = {
|
||||
[0].s = "CDC Ethernet Emulation Model (EEM)",
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings eem_string_table = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = eem_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *eem_strings[] = {
|
||||
&eem_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int eem_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
int value = -EOPNOTSUPP;
|
||||
u16 w_index = le16_to_cpu(ctrl->wIndex);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u16 w_length = le16_to_cpu(ctrl->wLength);
|
||||
|
||||
DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
|
||||
/* device either stalls (value < 0) or reports success */
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
static int eem_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_eem *eem = func_to_eem(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct net_device *net;
|
||||
|
||||
/* we know alt == 0, so this is an activation or a reset */
|
||||
if (alt != 0)
|
||||
goto fail;
|
||||
|
||||
if (intf == eem->ctrl_id) {
|
||||
|
||||
if (eem->port.in_ep->driver_data) {
|
||||
DBG(cdev, "reset eem\n");
|
||||
gether_disconnect(&eem->port);
|
||||
}
|
||||
|
||||
if (!eem->port.in_ep->desc || !eem->port.out_ep->desc) {
|
||||
DBG(cdev, "init eem\n");
|
||||
if (config_ep_by_speed(cdev->gadget, f,
|
||||
eem->port.in_ep) ||
|
||||
config_ep_by_speed(cdev->gadget, f,
|
||||
eem->port.out_ep)) {
|
||||
eem->port.in_ep->desc = NULL;
|
||||
eem->port.out_ep->desc = NULL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* zlps should not occur because zero-length EEM packets
|
||||
* will be inserted in those cases where they would occur
|
||||
*/
|
||||
eem->port.is_zlp_ok = 1;
|
||||
eem->port.cdc_filter = DEFAULT_FILTER;
|
||||
DBG(cdev, "activate eem\n");
|
||||
net = gether_connect(&eem->port);
|
||||
if (IS_ERR(net))
|
||||
return PTR_ERR(net);
|
||||
} else
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void eem_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_eem *eem = func_to_eem(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "eem deactivated\n");
|
||||
|
||||
if (eem->port.in_ep->driver_data)
|
||||
gether_disconnect(&eem->port);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* EEM function driver setup/binding */
|
||||
|
||||
static int eem_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_eem *eem = func_to_eem(f);
|
||||
struct usb_string *us;
|
||||
int status;
|
||||
struct usb_ep *ep;
|
||||
|
||||
struct f_eem_opts *eem_opts;
|
||||
|
||||
eem_opts = container_of(f->fi, struct f_eem_opts, func_inst);
|
||||
/*
|
||||
* in drivers/usb/gadget/configfs.c:configfs_composite_bind()
|
||||
* configurations are bound in sequence with list_for_each_entry,
|
||||
* in each configuration its functions are bound in sequence
|
||||
* with list_for_each_entry, so we assume no race condition
|
||||
* with regard to eem_opts->bound access
|
||||
*/
|
||||
if (!eem_opts->bound) {
|
||||
mutex_lock(&eem_opts->lock);
|
||||
gether_set_gadget(eem_opts->net, cdev->gadget);
|
||||
status = gether_register_netdev(eem_opts->net);
|
||||
mutex_unlock(&eem_opts->lock);
|
||||
if (status)
|
||||
return status;
|
||||
eem_opts->bound = true;
|
||||
}
|
||||
|
||||
us = usb_gstrings_attach(cdev, eem_strings,
|
||||
ARRAY_SIZE(eem_string_defs));
|
||||
if (IS_ERR(us))
|
||||
return PTR_ERR(us);
|
||||
eem_intf.iInterface = us[0].id;
|
||||
|
||||
/* allocate instance-specific interface IDs */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
eem->ctrl_id = status;
|
||||
eem_intf.bInterfaceNumber = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_in_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
eem->port.in_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_out_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
eem->port.out_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
status = -ENOMEM;
|
||||
|
||||
/* support all relevant hardware speeds... we expect that when
|
||||
* hardware is dual speed, all bulk-capable endpoints work at
|
||||
* both speeds
|
||||
*/
|
||||
eem_hs_in_desc.bEndpointAddress = eem_fs_in_desc.bEndpointAddress;
|
||||
eem_hs_out_desc.bEndpointAddress = eem_fs_out_desc.bEndpointAddress;
|
||||
|
||||
eem_ss_in_desc.bEndpointAddress = eem_fs_in_desc.bEndpointAddress;
|
||||
eem_ss_out_desc.bEndpointAddress = eem_fs_out_desc.bEndpointAddress;
|
||||
|
||||
status = usb_assign_descriptors(f, eem_fs_function, eem_hs_function,
|
||||
eem_ss_function);
|
||||
if (status)
|
||||
goto fail;
|
||||
|
||||
DBG(cdev, "CDC Ethernet (EEM): %s speed IN/%s OUT/%s\n",
|
||||
gadget_is_superspeed(c->cdev->gadget) ? "super" :
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
eem->port.in_ep->name, eem->port.out_ep->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
usb_free_all_descriptors(f);
|
||||
if (eem->port.out_ep)
|
||||
eem->port.out_ep->driver_data = NULL;
|
||||
if (eem->port.in_ep)
|
||||
eem->port.in_ep->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void eem_cmd_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct sk_buff *skb = (struct sk_buff *)req->context;
|
||||
|
||||
dev_kfree_skb_any(skb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the EEM header and ethernet checksum.
|
||||
* We currently do not attempt to put multiple ethernet frames
|
||||
* into a single USB transfer
|
||||
*/
|
||||
static struct sk_buff *eem_wrap(struct gether *port, struct sk_buff *skb)
|
||||
{
|
||||
struct sk_buff *skb2 = NULL;
|
||||
struct usb_ep *in = port->in_ep;
|
||||
int padlen = 0;
|
||||
u16 len = skb->len;
|
||||
|
||||
int headroom = skb_headroom(skb);
|
||||
int tailroom = skb_tailroom(skb);
|
||||
|
||||
/* When (len + EEM_HLEN + ETH_FCS_LEN) % in->maxpacket) is 0,
|
||||
* stick two bytes of zero-length EEM packet on the end.
|
||||
*/
|
||||
if (((len + EEM_HLEN + ETH_FCS_LEN) % in->maxpacket) == 0)
|
||||
padlen += 2;
|
||||
|
||||
if ((tailroom >= (ETH_FCS_LEN + padlen)) &&
|
||||
(headroom >= EEM_HLEN) && !skb_cloned(skb))
|
||||
goto done;
|
||||
|
||||
skb2 = skb_copy_expand(skb, EEM_HLEN, ETH_FCS_LEN + padlen, GFP_ATOMIC);
|
||||
dev_kfree_skb_any(skb);
|
||||
skb = skb2;
|
||||
if (!skb)
|
||||
return skb;
|
||||
|
||||
done:
|
||||
/* use the "no CRC" option */
|
||||
put_unaligned_be32(0xdeadbeef, skb_put(skb, 4));
|
||||
|
||||
/* EEM packet header format:
|
||||
* b0..13: length of ethernet frame
|
||||
* b14: bmCRC (0 == sentinel CRC)
|
||||
* b15: bmType (0 == data)
|
||||
*/
|
||||
len = skb->len;
|
||||
put_unaligned_le16(len & 0x3FFF, skb_push(skb, 2));
|
||||
|
||||
/* add a zero-length EEM packet, if needed */
|
||||
if (padlen)
|
||||
put_unaligned_le16(0, skb_put(skb, 2));
|
||||
|
||||
return skb;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the EEM header. Note that there can be many EEM packets in a single
|
||||
* USB transfer, so we need to break them out and handle them independently.
|
||||
*/
|
||||
static int eem_unwrap(struct gether *port,
|
||||
struct sk_buff *skb,
|
||||
struct sk_buff_head *list)
|
||||
{
|
||||
struct usb_composite_dev *cdev = port->func.config->cdev;
|
||||
int status = 0;
|
||||
|
||||
do {
|
||||
struct sk_buff *skb2;
|
||||
u16 header;
|
||||
u16 len = 0;
|
||||
|
||||
if (skb->len < EEM_HLEN) {
|
||||
status = -EINVAL;
|
||||
DBG(cdev, "invalid EEM header\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* remove the EEM header */
|
||||
header = get_unaligned_le16(skb->data);
|
||||
skb_pull(skb, EEM_HLEN);
|
||||
|
||||
/* EEM packet header format:
|
||||
* b0..14: EEM type dependent (data or command)
|
||||
* b15: bmType (0 == data, 1 == command)
|
||||
*/
|
||||
if (header & BIT(15)) {
|
||||
struct usb_request *req = cdev->req;
|
||||
u16 bmEEMCmd;
|
||||
|
||||
/* EEM command packet format:
|
||||
* b0..10: bmEEMCmdParam
|
||||
* b11..13: bmEEMCmd
|
||||
* b14: reserved (must be zero)
|
||||
* b15: bmType (1 == command)
|
||||
*/
|
||||
if (header & BIT(14))
|
||||
continue;
|
||||
|
||||
bmEEMCmd = (header >> 11) & 0x7;
|
||||
switch (bmEEMCmd) {
|
||||
case 0: /* echo */
|
||||
len = header & 0x7FF;
|
||||
if (skb->len < len) {
|
||||
status = -EOVERFLOW;
|
||||
goto error;
|
||||
}
|
||||
|
||||
skb2 = skb_clone(skb, GFP_ATOMIC);
|
||||
if (unlikely(!skb2)) {
|
||||
DBG(cdev, "EEM echo response error\n");
|
||||
goto next;
|
||||
}
|
||||
skb_trim(skb2, len);
|
||||
put_unaligned_le16(BIT(15) | BIT(11) | len,
|
||||
skb_push(skb2, 2));
|
||||
skb_copy_bits(skb2, 0, req->buf, skb2->len);
|
||||
req->length = skb2->len;
|
||||
req->complete = eem_cmd_complete;
|
||||
req->zero = 1;
|
||||
req->context = skb2;
|
||||
if (usb_ep_queue(port->in_ep, req, GFP_ATOMIC))
|
||||
DBG(cdev, "echo response queue fail\n");
|
||||
break;
|
||||
|
||||
case 1: /* echo response */
|
||||
case 2: /* suspend hint */
|
||||
case 3: /* response hint */
|
||||
case 4: /* response complete hint */
|
||||
case 5: /* tickle */
|
||||
default: /* reserved */
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
u32 crc, crc2;
|
||||
struct sk_buff *skb3;
|
||||
|
||||
/* check for zero-length EEM packet */
|
||||
if (header == 0)
|
||||
continue;
|
||||
|
||||
/* EEM data packet format:
|
||||
* b0..13: length of ethernet frame
|
||||
* b14: bmCRC (0 == sentinel, 1 == calculated)
|
||||
* b15: bmType (0 == data)
|
||||
*/
|
||||
len = header & 0x3FFF;
|
||||
if ((skb->len < len)
|
||||
|| (len < (ETH_HLEN + ETH_FCS_LEN))) {
|
||||
status = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* validate CRC */
|
||||
if (header & BIT(14)) {
|
||||
crc = get_unaligned_le32(skb->data + len
|
||||
- ETH_FCS_LEN);
|
||||
crc2 = ~crc32_le(~0,
|
||||
skb->data, len - ETH_FCS_LEN);
|
||||
} else {
|
||||
crc = get_unaligned_be32(skb->data + len
|
||||
- ETH_FCS_LEN);
|
||||
crc2 = 0xdeadbeef;
|
||||
}
|
||||
if (crc != crc2) {
|
||||
DBG(cdev, "invalid EEM CRC\n");
|
||||
goto next;
|
||||
}
|
||||
|
||||
skb2 = skb_clone(skb, GFP_ATOMIC);
|
||||
if (unlikely(!skb2)) {
|
||||
DBG(cdev, "unable to unframe EEM packet\n");
|
||||
continue;
|
||||
}
|
||||
skb_trim(skb2, len - ETH_FCS_LEN);
|
||||
|
||||
skb3 = skb_copy_expand(skb2,
|
||||
NET_IP_ALIGN,
|
||||
0,
|
||||
GFP_ATOMIC);
|
||||
if (unlikely(!skb3)) {
|
||||
DBG(cdev, "unable to realign EEM packet\n");
|
||||
dev_kfree_skb_any(skb2);
|
||||
continue;
|
||||
}
|
||||
dev_kfree_skb_any(skb2);
|
||||
skb_queue_tail(list, skb3);
|
||||
}
|
||||
next:
|
||||
skb_pull(skb, len);
|
||||
} while (skb->len);
|
||||
|
||||
error:
|
||||
dev_kfree_skb_any(skb);
|
||||
return status;
|
||||
}
|
||||
|
||||
static inline struct f_eem_opts *to_f_eem_opts(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct f_eem_opts,
|
||||
func_inst.group);
|
||||
}
|
||||
|
||||
/* f_eem_item_ops */
|
||||
USB_ETHERNET_CONFIGFS_ITEM(eem);
|
||||
|
||||
/* f_eem_opts_dev_addr */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(eem);
|
||||
|
||||
/* f_eem_opts_host_addr */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(eem);
|
||||
|
||||
/* f_eem_opts_qmult */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(eem);
|
||||
|
||||
/* f_eem_opts_ifname */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(eem);
|
||||
|
||||
static struct configfs_attribute *eem_attrs[] = {
|
||||
&f_eem_opts_dev_addr.attr,
|
||||
&f_eem_opts_host_addr.attr,
|
||||
&f_eem_opts_qmult.attr,
|
||||
&f_eem_opts_ifname.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct config_item_type eem_func_type = {
|
||||
.ct_item_ops = &eem_item_ops,
|
||||
.ct_attrs = eem_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void eem_free_inst(struct usb_function_instance *f)
|
||||
{
|
||||
struct f_eem_opts *opts;
|
||||
|
||||
opts = container_of(f, struct f_eem_opts, func_inst);
|
||||
if (opts->bound)
|
||||
gether_cleanup(netdev_priv(opts->net));
|
||||
else
|
||||
free_netdev(opts->net);
|
||||
kfree(opts);
|
||||
}
|
||||
|
||||
static struct usb_function_instance *eem_alloc_inst(void)
|
||||
{
|
||||
struct f_eem_opts *opts;
|
||||
|
||||
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
||||
if (!opts)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
mutex_init(&opts->lock);
|
||||
opts->func_inst.free_func_inst = eem_free_inst;
|
||||
opts->net = gether_setup_default();
|
||||
if (IS_ERR(opts->net)) {
|
||||
struct net_device *net = opts->net;
|
||||
kfree(opts);
|
||||
return ERR_CAST(net);
|
||||
}
|
||||
|
||||
config_group_init_type_name(&opts->func_inst.group, "", &eem_func_type);
|
||||
|
||||
return &opts->func_inst;
|
||||
}
|
||||
|
||||
static void eem_free(struct usb_function *f)
|
||||
{
|
||||
struct f_eem *eem;
|
||||
struct f_eem_opts *opts;
|
||||
|
||||
eem = func_to_eem(f);
|
||||
opts = container_of(f->fi, struct f_eem_opts, func_inst);
|
||||
kfree(eem);
|
||||
mutex_lock(&opts->lock);
|
||||
opts->refcnt--;
|
||||
mutex_unlock(&opts->lock);
|
||||
}
|
||||
|
||||
static void eem_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
DBG(c->cdev, "eem unbind\n");
|
||||
|
||||
usb_free_all_descriptors(f);
|
||||
}
|
||||
|
||||
static struct usb_function *eem_alloc(struct usb_function_instance *fi)
|
||||
{
|
||||
struct f_eem *eem;
|
||||
struct f_eem_opts *opts;
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
eem = kzalloc(sizeof(*eem), GFP_KERNEL);
|
||||
if (!eem)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
opts = container_of(fi, struct f_eem_opts, func_inst);
|
||||
mutex_lock(&opts->lock);
|
||||
opts->refcnt++;
|
||||
|
||||
eem->port.ioport = netdev_priv(opts->net);
|
||||
mutex_unlock(&opts->lock);
|
||||
eem->port.cdc_filter = DEFAULT_FILTER;
|
||||
|
||||
eem->port.func.name = "cdc_eem";
|
||||
/* descriptors are per-instance copies */
|
||||
eem->port.func.bind = eem_bind;
|
||||
eem->port.func.unbind = eem_unbind;
|
||||
eem->port.func.set_alt = eem_set_alt;
|
||||
eem->port.func.setup = eem_setup;
|
||||
eem->port.func.disable = eem_disable;
|
||||
eem->port.func.free_func = eem_free;
|
||||
eem->port.wrap = eem_wrap;
|
||||
eem->port.unwrap = eem_unwrap;
|
||||
eem->port.header_len = EEM_HLEN;
|
||||
|
||||
return &eem->port.func;
|
||||
}
|
||||
|
||||
DECLARE_USB_FUNCTION_INIT(eem, eem_alloc_inst, eem_alloc);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("David Brownell");
|
||||
3349
drivers/usb/gadget/function/f_fs.c
Normal file
3349
drivers/usb/gadget/function/f_fs.c
Normal file
File diff suppressed because it is too large
Load diff
763
drivers/usb/gadget/function/f_hid.c
Normal file
763
drivers/usb/gadget/function/f_hid.c
Normal file
|
|
@ -0,0 +1,763 @@
|
|||
/*
|
||||
* f_hid.c -- USB HID function driver
|
||||
*
|
||||
* Copyright (C) 2010 Fabien Chouteau <fabien.chouteau@barco.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/usb/g_hid.h>
|
||||
|
||||
#include "u_f.h"
|
||||
|
||||
static int major, minors;
|
||||
static struct class *hidg_class;
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
/* HID gadget struct */
|
||||
|
||||
struct f_hidg_req_list {
|
||||
struct usb_request *req;
|
||||
unsigned int pos;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct f_hidg {
|
||||
/* configuration */
|
||||
unsigned char bInterfaceSubClass;
|
||||
unsigned char bInterfaceProtocol;
|
||||
unsigned short report_desc_length;
|
||||
char *report_desc;
|
||||
unsigned short report_length;
|
||||
|
||||
/* recv report */
|
||||
struct list_head completed_out_req;
|
||||
spinlock_t spinlock;
|
||||
wait_queue_head_t read_queue;
|
||||
unsigned int qlen;
|
||||
|
||||
/* send report */
|
||||
struct mutex lock;
|
||||
bool write_pending;
|
||||
wait_queue_head_t write_queue;
|
||||
struct usb_request *req;
|
||||
|
||||
int minor;
|
||||
struct cdev cdev;
|
||||
struct usb_function func;
|
||||
|
||||
struct usb_ep *in_ep;
|
||||
struct usb_ep *out_ep;
|
||||
};
|
||||
|
||||
static inline struct f_hidg *func_to_hidg(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_hidg, func);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
/* Static descriptors */
|
||||
|
||||
static struct usb_interface_descriptor hidg_interface_desc = {
|
||||
.bLength = sizeof hidg_interface_desc,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_HID,
|
||||
/* .bInterfaceSubClass = DYNAMIC */
|
||||
/* .bInterfaceProtocol = DYNAMIC */
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct hid_descriptor hidg_desc = {
|
||||
.bLength = sizeof hidg_desc,
|
||||
.bDescriptorType = HID_DT_HID,
|
||||
.bcdHID = 0x0101,
|
||||
.bCountryCode = 0x00,
|
||||
.bNumDescriptors = 0x1,
|
||||
/*.desc[0].bDescriptorType = DYNAMIC */
|
||||
/*.desc[0].wDescriptorLenght = DYNAMIC */
|
||||
};
|
||||
|
||||
/* High-Speed Support */
|
||||
|
||||
static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
/*.wMaxPacketSize = DYNAMIC */
|
||||
.bInterval = 4, /* FIXME: Add this field in the
|
||||
* HID gadget configuration?
|
||||
* (struct hidg_func_descriptor)
|
||||
*/
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
/*.wMaxPacketSize = DYNAMIC */
|
||||
.bInterval = 4, /* FIXME: Add this field in the
|
||||
* HID gadget configuration?
|
||||
* (struct hidg_func_descriptor)
|
||||
*/
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *hidg_hs_descriptors[] = {
|
||||
(struct usb_descriptor_header *)&hidg_interface_desc,
|
||||
(struct usb_descriptor_header *)&hidg_desc,
|
||||
(struct usb_descriptor_header *)&hidg_hs_in_ep_desc,
|
||||
(struct usb_descriptor_header *)&hidg_hs_out_ep_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Full-Speed Support */
|
||||
|
||||
static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
/*.wMaxPacketSize = DYNAMIC */
|
||||
.bInterval = 10, /* FIXME: Add this field in the
|
||||
* HID gadget configuration?
|
||||
* (struct hidg_func_descriptor)
|
||||
*/
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
/*.wMaxPacketSize = DYNAMIC */
|
||||
.bInterval = 10, /* FIXME: Add this field in the
|
||||
* HID gadget configuration?
|
||||
* (struct hidg_func_descriptor)
|
||||
*/
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *hidg_fs_descriptors[] = {
|
||||
(struct usb_descriptor_header *)&hidg_interface_desc,
|
||||
(struct usb_descriptor_header *)&hidg_desc,
|
||||
(struct usb_descriptor_header *)&hidg_fs_in_ep_desc,
|
||||
(struct usb_descriptor_header *)&hidg_fs_out_ep_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
/* Char Device */
|
||||
|
||||
static ssize_t f_hidg_read(struct file *file, char __user *buffer,
|
||||
size_t count, loff_t *ptr)
|
||||
{
|
||||
struct f_hidg *hidg = file->private_data;
|
||||
struct f_hidg_req_list *list;
|
||||
struct usb_request *req;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
if (!count)
|
||||
return 0;
|
||||
|
||||
if (!access_ok(VERIFY_WRITE, buffer, count))
|
||||
return -EFAULT;
|
||||
|
||||
spin_lock_irqsave(&hidg->spinlock, flags);
|
||||
|
||||
#define READ_COND (!list_empty(&hidg->completed_out_req))
|
||||
|
||||
/* wait for at least one buffer to complete */
|
||||
while (!READ_COND) {
|
||||
spin_unlock_irqrestore(&hidg->spinlock, flags);
|
||||
if (file->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
|
||||
if (wait_event_interruptible(hidg->read_queue, READ_COND))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
spin_lock_irqsave(&hidg->spinlock, flags);
|
||||
}
|
||||
|
||||
/* pick the first one */
|
||||
list = list_first_entry(&hidg->completed_out_req,
|
||||
struct f_hidg_req_list, list);
|
||||
req = list->req;
|
||||
count = min_t(unsigned int, count, req->actual - list->pos);
|
||||
spin_unlock_irqrestore(&hidg->spinlock, flags);
|
||||
|
||||
/* copy to user outside spinlock */
|
||||
count -= copy_to_user(buffer, req->buf + list->pos, count);
|
||||
list->pos += count;
|
||||
|
||||
/*
|
||||
* if this request is completely handled and transfered to
|
||||
* userspace, remove its entry from the list and requeue it
|
||||
* again. Otherwise, we will revisit it again upon the next
|
||||
* call, taking into account its current read position.
|
||||
*/
|
||||
if (list->pos == req->actual) {
|
||||
spin_lock_irqsave(&hidg->spinlock, flags);
|
||||
list_del(&list->list);
|
||||
kfree(list);
|
||||
spin_unlock_irqrestore(&hidg->spinlock, flags);
|
||||
|
||||
req->length = hidg->report_length;
|
||||
ret = usb_ep_queue(hidg->out_ep, req, GFP_KERNEL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void f_hidg_req_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_hidg *hidg = (struct f_hidg *)ep->driver_data;
|
||||
|
||||
if (req->status != 0) {
|
||||
ERROR(hidg->func.config->cdev,
|
||||
"End Point Request ERROR: %d\n", req->status);
|
||||
}
|
||||
|
||||
hidg->write_pending = 0;
|
||||
wake_up(&hidg->write_queue);
|
||||
}
|
||||
|
||||
static ssize_t f_hidg_write(struct file *file, const char __user *buffer,
|
||||
size_t count, loff_t *offp)
|
||||
{
|
||||
struct f_hidg *hidg = file->private_data;
|
||||
ssize_t status = -ENOMEM;
|
||||
|
||||
if (!access_ok(VERIFY_READ, buffer, count))
|
||||
return -EFAULT;
|
||||
|
||||
mutex_lock(&hidg->lock);
|
||||
|
||||
#define WRITE_COND (!hidg->write_pending)
|
||||
|
||||
/* write queue */
|
||||
while (!WRITE_COND) {
|
||||
mutex_unlock(&hidg->lock);
|
||||
if (file->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
|
||||
if (wait_event_interruptible_exclusive(
|
||||
hidg->write_queue, WRITE_COND))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
mutex_lock(&hidg->lock);
|
||||
}
|
||||
|
||||
count = min_t(unsigned, count, hidg->report_length);
|
||||
status = copy_from_user(hidg->req->buf, buffer, count);
|
||||
|
||||
if (status != 0) {
|
||||
ERROR(hidg->func.config->cdev,
|
||||
"copy_from_user error\n");
|
||||
mutex_unlock(&hidg->lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
hidg->req->status = 0;
|
||||
hidg->req->zero = 0;
|
||||
hidg->req->length = count;
|
||||
hidg->req->complete = f_hidg_req_complete;
|
||||
hidg->req->context = hidg;
|
||||
hidg->write_pending = 1;
|
||||
|
||||
status = usb_ep_queue(hidg->in_ep, hidg->req, GFP_ATOMIC);
|
||||
if (status < 0) {
|
||||
ERROR(hidg->func.config->cdev,
|
||||
"usb_ep_queue error on int endpoint %zd\n", status);
|
||||
hidg->write_pending = 0;
|
||||
wake_up(&hidg->write_queue);
|
||||
} else {
|
||||
status = count;
|
||||
}
|
||||
|
||||
mutex_unlock(&hidg->lock);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static unsigned int f_hidg_poll(struct file *file, poll_table *wait)
|
||||
{
|
||||
struct f_hidg *hidg = file->private_data;
|
||||
unsigned int ret = 0;
|
||||
|
||||
poll_wait(file, &hidg->read_queue, wait);
|
||||
poll_wait(file, &hidg->write_queue, wait);
|
||||
|
||||
if (WRITE_COND)
|
||||
ret |= POLLOUT | POLLWRNORM;
|
||||
|
||||
if (READ_COND)
|
||||
ret |= POLLIN | POLLRDNORM;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#undef WRITE_COND
|
||||
#undef READ_COND
|
||||
|
||||
static int f_hidg_release(struct inode *inode, struct file *fd)
|
||||
{
|
||||
fd->private_data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int f_hidg_open(struct inode *inode, struct file *fd)
|
||||
{
|
||||
struct f_hidg *hidg =
|
||||
container_of(inode->i_cdev, struct f_hidg, cdev);
|
||||
|
||||
fd->private_data = hidg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
/* usb_function */
|
||||
|
||||
static inline struct usb_request *hidg_alloc_ep_req(struct usb_ep *ep,
|
||||
unsigned length)
|
||||
{
|
||||
return alloc_ep_req(ep, length, length);
|
||||
}
|
||||
|
||||
static void hidg_set_report_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_hidg *hidg = (struct f_hidg *) req->context;
|
||||
struct f_hidg_req_list *req_list;
|
||||
unsigned long flags;
|
||||
|
||||
req_list = kzalloc(sizeof(*req_list), GFP_ATOMIC);
|
||||
if (!req_list)
|
||||
return;
|
||||
|
||||
req_list->req = req;
|
||||
|
||||
spin_lock_irqsave(&hidg->spinlock, flags);
|
||||
list_add_tail(&req_list->list, &hidg->completed_out_req);
|
||||
spin_unlock_irqrestore(&hidg->spinlock, flags);
|
||||
|
||||
wake_up(&hidg->read_queue);
|
||||
}
|
||||
|
||||
static int hidg_setup(struct usb_function *f,
|
||||
const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct f_hidg *hidg = func_to_hidg(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_request *req = cdev->req;
|
||||
int status = 0;
|
||||
__u16 value, length;
|
||||
|
||||
value = __le16_to_cpu(ctrl->wValue);
|
||||
length = __le16_to_cpu(ctrl->wLength);
|
||||
|
||||
VDBG(cdev, "hid_setup crtl_request : bRequestType:0x%x bRequest:0x%x "
|
||||
"Value:0x%x\n", ctrl->bRequestType, ctrl->bRequest, value);
|
||||
|
||||
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
|
||||
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
|
||||
| HID_REQ_GET_REPORT):
|
||||
VDBG(cdev, "get_report\n");
|
||||
|
||||
/* send an empty report */
|
||||
length = min_t(unsigned, length, hidg->report_length);
|
||||
memset(req->buf, 0x0, length);
|
||||
|
||||
goto respond;
|
||||
break;
|
||||
|
||||
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
|
||||
| HID_REQ_GET_PROTOCOL):
|
||||
VDBG(cdev, "get_protocol\n");
|
||||
goto stall;
|
||||
break;
|
||||
|
||||
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
|
||||
| HID_REQ_SET_REPORT):
|
||||
VDBG(cdev, "set_report | wLenght=%d\n", ctrl->wLength);
|
||||
goto stall;
|
||||
break;
|
||||
|
||||
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
|
||||
| HID_REQ_SET_PROTOCOL):
|
||||
VDBG(cdev, "set_protocol\n");
|
||||
goto stall;
|
||||
break;
|
||||
|
||||
case ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8
|
||||
| USB_REQ_GET_DESCRIPTOR):
|
||||
switch (value >> 8) {
|
||||
case HID_DT_HID:
|
||||
VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: HID\n");
|
||||
length = min_t(unsigned short, length,
|
||||
hidg_desc.bLength);
|
||||
memcpy(req->buf, &hidg_desc, length);
|
||||
goto respond;
|
||||
break;
|
||||
case HID_DT_REPORT:
|
||||
VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: REPORT\n");
|
||||
length = min_t(unsigned short, length,
|
||||
hidg->report_desc_length);
|
||||
memcpy(req->buf, hidg->report_desc, length);
|
||||
goto respond;
|
||||
break;
|
||||
|
||||
default:
|
||||
VDBG(cdev, "Unknown descriptor request 0x%x\n",
|
||||
value >> 8);
|
||||
goto stall;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
VDBG(cdev, "Unknown request 0x%x\n",
|
||||
ctrl->bRequest);
|
||||
goto stall;
|
||||
break;
|
||||
}
|
||||
|
||||
stall:
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
respond:
|
||||
req->zero = 0;
|
||||
req->length = length;
|
||||
status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
||||
if (status < 0)
|
||||
ERROR(cdev, "usb_ep_queue error on ep0 %d\n", value);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void hidg_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_hidg *hidg = func_to_hidg(f);
|
||||
struct f_hidg_req_list *list, *next;
|
||||
|
||||
usb_ep_disable(hidg->in_ep);
|
||||
hidg->in_ep->driver_data = NULL;
|
||||
|
||||
usb_ep_disable(hidg->out_ep);
|
||||
hidg->out_ep->driver_data = NULL;
|
||||
|
||||
list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) {
|
||||
list_del(&list->list);
|
||||
kfree(list);
|
||||
}
|
||||
}
|
||||
|
||||
static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct f_hidg *hidg = func_to_hidg(f);
|
||||
int i, status = 0;
|
||||
|
||||
VDBG(cdev, "hidg_set_alt intf:%d alt:%d\n", intf, alt);
|
||||
|
||||
if (hidg->in_ep != NULL) {
|
||||
/* restart endpoint */
|
||||
if (hidg->in_ep->driver_data != NULL)
|
||||
usb_ep_disable(hidg->in_ep);
|
||||
|
||||
status = config_ep_by_speed(f->config->cdev->gadget, f,
|
||||
hidg->in_ep);
|
||||
if (status) {
|
||||
ERROR(cdev, "config_ep_by_speed FAILED!\n");
|
||||
goto fail;
|
||||
}
|
||||
status = usb_ep_enable(hidg->in_ep);
|
||||
if (status < 0) {
|
||||
ERROR(cdev, "Enable IN endpoint FAILED!\n");
|
||||
goto fail;
|
||||
}
|
||||
hidg->in_ep->driver_data = hidg;
|
||||
}
|
||||
|
||||
|
||||
if (hidg->out_ep != NULL) {
|
||||
/* restart endpoint */
|
||||
if (hidg->out_ep->driver_data != NULL)
|
||||
usb_ep_disable(hidg->out_ep);
|
||||
|
||||
status = config_ep_by_speed(f->config->cdev->gadget, f,
|
||||
hidg->out_ep);
|
||||
if (status) {
|
||||
ERROR(cdev, "config_ep_by_speed FAILED!\n");
|
||||
goto fail;
|
||||
}
|
||||
status = usb_ep_enable(hidg->out_ep);
|
||||
if (status < 0) {
|
||||
ERROR(cdev, "Enable IN endpoint FAILED!\n");
|
||||
goto fail;
|
||||
}
|
||||
hidg->out_ep->driver_data = hidg;
|
||||
|
||||
/*
|
||||
* allocate a bunch of read buffers and queue them all at once.
|
||||
*/
|
||||
for (i = 0; i < hidg->qlen && status == 0; i++) {
|
||||
struct usb_request *req =
|
||||
hidg_alloc_ep_req(hidg->out_ep,
|
||||
hidg->report_length);
|
||||
if (req) {
|
||||
req->complete = hidg_set_report_complete;
|
||||
req->context = hidg;
|
||||
status = usb_ep_queue(hidg->out_ep, req,
|
||||
GFP_ATOMIC);
|
||||
if (status)
|
||||
ERROR(cdev, "%s queue req --> %d\n",
|
||||
hidg->out_ep->name, status);
|
||||
} else {
|
||||
usb_ep_disable(hidg->out_ep);
|
||||
hidg->out_ep->driver_data = NULL;
|
||||
status = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
return status;
|
||||
}
|
||||
|
||||
const struct file_operations f_hidg_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = f_hidg_open,
|
||||
.release = f_hidg_release,
|
||||
.write = f_hidg_write,
|
||||
.read = f_hidg_read,
|
||||
.poll = f_hidg_poll,
|
||||
.llseek = noop_llseek,
|
||||
};
|
||||
|
||||
static int __init hidg_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_ep *ep;
|
||||
struct f_hidg *hidg = func_to_hidg(f);
|
||||
int status;
|
||||
dev_t dev;
|
||||
|
||||
/* allocate instance-specific interface IDs, and patch descriptors */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
hidg_interface_desc.bInterfaceNumber = status;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
status = -ENODEV;
|
||||
ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_in_ep_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
ep->driver_data = c->cdev; /* claim */
|
||||
hidg->in_ep = ep;
|
||||
|
||||
ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
ep->driver_data = c->cdev; /* claim */
|
||||
hidg->out_ep = ep;
|
||||
|
||||
/* preallocate request and buffer */
|
||||
status = -ENOMEM;
|
||||
hidg->req = usb_ep_alloc_request(hidg->in_ep, GFP_KERNEL);
|
||||
if (!hidg->req)
|
||||
goto fail;
|
||||
|
||||
hidg->req->buf = kmalloc(hidg->report_length, GFP_KERNEL);
|
||||
if (!hidg->req->buf)
|
||||
goto fail;
|
||||
|
||||
/* set descriptor dynamic values */
|
||||
hidg_interface_desc.bInterfaceSubClass = hidg->bInterfaceSubClass;
|
||||
hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol;
|
||||
hidg_hs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
|
||||
hidg_fs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
|
||||
hidg_hs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
|
||||
hidg_fs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
|
||||
hidg_desc.desc[0].bDescriptorType = HID_DT_REPORT;
|
||||
hidg_desc.desc[0].wDescriptorLength =
|
||||
cpu_to_le16(hidg->report_desc_length);
|
||||
|
||||
hidg_hs_in_ep_desc.bEndpointAddress =
|
||||
hidg_fs_in_ep_desc.bEndpointAddress;
|
||||
hidg_hs_out_ep_desc.bEndpointAddress =
|
||||
hidg_fs_out_ep_desc.bEndpointAddress;
|
||||
|
||||
status = usb_assign_descriptors(f, hidg_fs_descriptors,
|
||||
hidg_hs_descriptors, NULL);
|
||||
if (status)
|
||||
goto fail;
|
||||
|
||||
mutex_init(&hidg->lock);
|
||||
spin_lock_init(&hidg->spinlock);
|
||||
init_waitqueue_head(&hidg->write_queue);
|
||||
init_waitqueue_head(&hidg->read_queue);
|
||||
INIT_LIST_HEAD(&hidg->completed_out_req);
|
||||
|
||||
/* create char device */
|
||||
cdev_init(&hidg->cdev, &f_hidg_fops);
|
||||
dev = MKDEV(major, hidg->minor);
|
||||
status = cdev_add(&hidg->cdev, dev, 1);
|
||||
if (status)
|
||||
goto fail;
|
||||
|
||||
device_create(hidg_class, NULL, dev, NULL, "%s%d", "hidg", hidg->minor);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
ERROR(f->config->cdev, "hidg_bind FAILED\n");
|
||||
if (hidg->req != NULL) {
|
||||
kfree(hidg->req->buf);
|
||||
if (hidg->in_ep != NULL)
|
||||
usb_ep_free_request(hidg->in_ep, hidg->req);
|
||||
}
|
||||
|
||||
usb_free_all_descriptors(f);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void hidg_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct f_hidg *hidg = func_to_hidg(f);
|
||||
|
||||
device_destroy(hidg_class, MKDEV(major, hidg->minor));
|
||||
cdev_del(&hidg->cdev);
|
||||
|
||||
/* disable/free request and end point */
|
||||
usb_ep_disable(hidg->in_ep);
|
||||
usb_ep_dequeue(hidg->in_ep, hidg->req);
|
||||
kfree(hidg->req->buf);
|
||||
usb_ep_free_request(hidg->in_ep, hidg->req);
|
||||
|
||||
usb_free_all_descriptors(f);
|
||||
|
||||
kfree(hidg->report_desc);
|
||||
kfree(hidg);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
/* Strings */
|
||||
|
||||
#define CT_FUNC_HID_IDX 0
|
||||
|
||||
static struct usb_string ct_func_string_defs[] = {
|
||||
[CT_FUNC_HID_IDX].s = "HID Interface",
|
||||
{}, /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings ct_func_string_table = {
|
||||
.language = 0x0409, /* en-US */
|
||||
.strings = ct_func_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *ct_func_strings[] = {
|
||||
&ct_func_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
/* usb_configuration */
|
||||
|
||||
int __init hidg_bind_config(struct usb_configuration *c,
|
||||
struct hidg_func_descriptor *fdesc, int index)
|
||||
{
|
||||
struct f_hidg *hidg;
|
||||
int status;
|
||||
|
||||
if (index >= minors)
|
||||
return -ENOENT;
|
||||
|
||||
/* maybe allocate device-global string IDs, and patch descriptors */
|
||||
if (ct_func_string_defs[CT_FUNC_HID_IDX].id == 0) {
|
||||
status = usb_string_id(c->cdev);
|
||||
if (status < 0)
|
||||
return status;
|
||||
ct_func_string_defs[CT_FUNC_HID_IDX].id = status;
|
||||
hidg_interface_desc.iInterface = status;
|
||||
}
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
hidg = kzalloc(sizeof *hidg, GFP_KERNEL);
|
||||
if (!hidg)
|
||||
return -ENOMEM;
|
||||
|
||||
hidg->minor = index;
|
||||
hidg->bInterfaceSubClass = fdesc->subclass;
|
||||
hidg->bInterfaceProtocol = fdesc->protocol;
|
||||
hidg->report_length = fdesc->report_length;
|
||||
hidg->report_desc_length = fdesc->report_desc_length;
|
||||
hidg->report_desc = kmemdup(fdesc->report_desc,
|
||||
fdesc->report_desc_length,
|
||||
GFP_KERNEL);
|
||||
if (!hidg->report_desc) {
|
||||
kfree(hidg);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
hidg->func.name = "hid";
|
||||
hidg->func.strings = ct_func_strings;
|
||||
hidg->func.bind = hidg_bind;
|
||||
hidg->func.unbind = hidg_unbind;
|
||||
hidg->func.set_alt = hidg_set_alt;
|
||||
hidg->func.disable = hidg_disable;
|
||||
hidg->func.setup = hidg_setup;
|
||||
|
||||
/* this could me made configurable at some point */
|
||||
hidg->qlen = 4;
|
||||
|
||||
status = usb_add_function(c, &hidg->func);
|
||||
if (status)
|
||||
kfree(hidg);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int __init ghid_setup(struct usb_gadget *g, int count)
|
||||
{
|
||||
int status;
|
||||
dev_t dev;
|
||||
|
||||
hidg_class = class_create(THIS_MODULE, "hidg");
|
||||
|
||||
status = alloc_chrdev_region(&dev, 0, count, "hidg");
|
||||
if (!status) {
|
||||
major = MAJOR(dev);
|
||||
minors = count;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void ghid_cleanup(void)
|
||||
{
|
||||
if (major) {
|
||||
unregister_chrdev_region(MKDEV(major, 0), minors);
|
||||
major = minors = 0;
|
||||
}
|
||||
|
||||
class_destroy(hidg_class);
|
||||
hidg_class = NULL;
|
||||
}
|
||||
571
drivers/usb/gadget/function/f_loopback.c
Normal file
571
drivers/usb/gadget/function/f_loopback.c
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* This is currently packaged as a configuration driver, which can't be
|
||||
* combined with other functions to make composite devices. However, it
|
||||
* can be combined with other independent configurations.
|
||||
*/
|
||||
struct f_loopback {
|
||||
struct usb_function function;
|
||||
|
||||
struct usb_ep *in_ep;
|
||||
struct usb_ep *out_ep;
|
||||
};
|
||||
|
||||
static inline struct f_loopback *func_to_loop(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_loopback, function);
|
||||
}
|
||||
|
||||
static unsigned qlen;
|
||||
static unsigned buflen;
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
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->in_ep->driver_data = cdev; /* claim */
|
||||
|
||||
loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc);
|
||||
if (!loop->out_ep)
|
||||
goto autoconf_fail;
|
||||
loop->out_ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* 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) {
|
||||
/* loop this OUT packet back IN to the host */
|
||||
req->zero = (req->actual < req->length);
|
||||
req->length = req->actual;
|
||||
status = usb_ep_queue(loop->in_ep, req, GFP_ATOMIC);
|
||||
if (status == 0)
|
||||
return;
|
||||
|
||||
/* "should never get here" */
|
||||
ERROR(cdev, "can't loop %s to %s: %d\n",
|
||||
ep->name, loop->in_ep->name,
|
||||
status);
|
||||
}
|
||||
|
||||
/* queue the buffer for some later OUT packet */
|
||||
req->length = buflen;
|
||||
status = usb_ep_queue(loop->out_ep, req, GFP_ATOMIC);
|
||||
if (status == 0)
|
||||
return;
|
||||
|
||||
/* "should never get here" */
|
||||
/* FALLTHROUGH */
|
||||
|
||||
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_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)
|
||||
{
|
||||
return alloc_ep_req(ep, len, buflen);
|
||||
}
|
||||
|
||||
static int
|
||||
enable_loopback(struct usb_composite_dev *cdev, struct f_loopback *loop)
|
||||
{
|
||||
int result = 0;
|
||||
struct usb_ep *ep;
|
||||
struct usb_request *req;
|
||||
unsigned i;
|
||||
|
||||
/* one endpoint writes data back IN to the host */
|
||||
ep = loop->in_ep;
|
||||
result = config_ep_by_speed(cdev->gadget, &(loop->function), ep);
|
||||
if (result)
|
||||
return result;
|
||||
result = usb_ep_enable(ep);
|
||||
if (result < 0)
|
||||
return result;
|
||||
ep->driver_data = loop;
|
||||
|
||||
/* one endpoint just reads OUT packets */
|
||||
ep = loop->out_ep;
|
||||
result = config_ep_by_speed(cdev->gadget, &(loop->function), ep);
|
||||
if (result)
|
||||
goto fail0;
|
||||
|
||||
result = usb_ep_enable(ep);
|
||||
if (result < 0) {
|
||||
fail0:
|
||||
ep = loop->in_ep;
|
||||
usb_ep_disable(ep);
|
||||
ep->driver_data = NULL;
|
||||
return result;
|
||||
}
|
||||
ep->driver_data = loop;
|
||||
|
||||
/* allocate a bunch of read buffers and queue them all at once.
|
||||
* we buffer at most 'qlen' transfers; fewer if any need more
|
||||
* than 'buflen' bytes each.
|
||||
*/
|
||||
for (i = 0; i < qlen && result == 0; i++) {
|
||||
req = lb_alloc_ep_req(ep, 0);
|
||||
if (req) {
|
||||
req->complete = loopback_complete;
|
||||
result = usb_ep_queue(ep, req, GFP_ATOMIC);
|
||||
if (result)
|
||||
ERROR(cdev, "%s queue req --> %d\n",
|
||||
ep->name, result);
|
||||
} else {
|
||||
usb_ep_disable(ep);
|
||||
ep->driver_data = NULL;
|
||||
result = -ENOMEM;
|
||||
goto fail0;
|
||||
}
|
||||
}
|
||||
|
||||
DBG(cdev, "%s enabled\n", loop->function.name);
|
||||
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 */
|
||||
if (loop->in_ep->driver_data)
|
||||
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);
|
||||
|
||||
buflen = lb_opts->bulk_buflen;
|
||||
qlen = lb_opts->qlen;
|
||||
if (!qlen)
|
||||
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);
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR_STRUCT(f_lb_opts);
|
||||
CONFIGFS_ATTR_OPS(f_lb_opts);
|
||||
|
||||
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,
|
||||
.show_attribute = f_lb_opts_attr_show,
|
||||
.store_attribute = f_lb_opts_attr_store,
|
||||
};
|
||||
|
||||
static ssize_t f_lb_opts_qlen_show(struct f_lb_opts *opts, char *page)
|
||||
{
|
||||
int result;
|
||||
|
||||
mutex_lock(&opts->lock);
|
||||
result = sprintf(page, "%d", opts->qlen);
|
||||
mutex_unlock(&opts->lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static ssize_t f_lb_opts_qlen_store(struct f_lb_opts *opts,
|
||||
const char *page, size_t len)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
static struct f_lb_opts_attribute f_lb_opts_qlen =
|
||||
__CONFIGFS_ATTR(qlen, S_IRUGO | S_IWUSR,
|
||||
f_lb_opts_qlen_show,
|
||||
f_lb_opts_qlen_store);
|
||||
|
||||
static ssize_t f_lb_opts_bulk_buflen_show(struct f_lb_opts *opts, char *page)
|
||||
{
|
||||
int result;
|
||||
|
||||
mutex_lock(&opts->lock);
|
||||
result = sprintf(page, "%d", opts->bulk_buflen);
|
||||
mutex_unlock(&opts->lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static ssize_t f_lb_opts_bulk_buflen_store(struct f_lb_opts *opts,
|
||||
const char *page, size_t len)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
static struct f_lb_opts_attribute f_lb_opts_bulk_buflen =
|
||||
__CONFIGFS_ATTR(buflen, S_IRUGO | S_IWUSR,
|
||||
f_lb_opts_bulk_buflen_show,
|
||||
f_lb_opts_bulk_buflen_store);
|
||||
|
||||
static struct configfs_attribute *lb_attrs[] = {
|
||||
&f_lb_opts_qlen.attr,
|
||||
&f_lb_opts_bulk_buflen.attr,
|
||||
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");
|
||||
3668
drivers/usb/gadget/function/f_mass_storage.c
Normal file
3668
drivers/usb/gadget/function/f_mass_storage.c
Normal file
File diff suppressed because it is too large
Load diff
166
drivers/usb/gadget/function/f_mass_storage.h
Normal file
166
drivers/usb/gadget/function/f_mass_storage.h
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
#ifndef USB_F_MASS_STORAGE_H
|
||||
#define USB_F_MASS_STORAGE_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
#include "storage_common.h"
|
||||
|
||||
struct fsg_module_parameters {
|
||||
char *file[FSG_MAX_LUNS];
|
||||
bool ro[FSG_MAX_LUNS];
|
||||
bool removable[FSG_MAX_LUNS];
|
||||
bool cdrom[FSG_MAX_LUNS];
|
||||
bool nofua[FSG_MAX_LUNS];
|
||||
|
||||
unsigned int file_count, ro_count, removable_count, cdrom_count;
|
||||
unsigned int nofua_count;
|
||||
unsigned int luns; /* nluns */
|
||||
bool stall; /* can_stall */
|
||||
};
|
||||
|
||||
#define _FSG_MODULE_PARAM_ARRAY(prefix, params, name, type, desc) \
|
||||
module_param_array_named(prefix ## name, params.name, type, \
|
||||
&prefix ## params.name ## _count, \
|
||||
S_IRUGO); \
|
||||
MODULE_PARM_DESC(prefix ## name, desc)
|
||||
|
||||
#define _FSG_MODULE_PARAM(prefix, params, name, type, desc) \
|
||||
module_param_named(prefix ## name, params.name, type, \
|
||||
S_IRUGO); \
|
||||
MODULE_PARM_DESC(prefix ## name, desc)
|
||||
|
||||
#define __FSG_MODULE_PARAMETERS(prefix, params) \
|
||||
_FSG_MODULE_PARAM_ARRAY(prefix, params, file, charp, \
|
||||
"names of backing files or devices"); \
|
||||
_FSG_MODULE_PARAM_ARRAY(prefix, params, ro, bool, \
|
||||
"true to force read-only"); \
|
||||
_FSG_MODULE_PARAM_ARRAY(prefix, params, removable, bool, \
|
||||
"true to simulate removable media"); \
|
||||
_FSG_MODULE_PARAM_ARRAY(prefix, params, cdrom, bool, \
|
||||
"true to simulate CD-ROM instead of disk"); \
|
||||
_FSG_MODULE_PARAM_ARRAY(prefix, params, nofua, bool, \
|
||||
"true to ignore SCSI WRITE(10,12) FUA bit"); \
|
||||
_FSG_MODULE_PARAM(prefix, params, luns, uint, \
|
||||
"number of LUNs"); \
|
||||
_FSG_MODULE_PARAM(prefix, params, stall, bool, \
|
||||
"false to prevent bulk stalls")
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_DEBUG_FILES
|
||||
|
||||
#define FSG_MODULE_PARAMETERS(prefix, params) \
|
||||
__FSG_MODULE_PARAMETERS(prefix, params); \
|
||||
module_param_named(num_buffers, fsg_num_buffers, uint, S_IRUGO);\
|
||||
MODULE_PARM_DESC(num_buffers, "Number of pipeline buffers")
|
||||
#else
|
||||
|
||||
#define FSG_MODULE_PARAMETERS(prefix, params) \
|
||||
__FSG_MODULE_PARAMETERS(prefix, params)
|
||||
|
||||
#endif
|
||||
|
||||
struct fsg_common;
|
||||
|
||||
/* FSF callback functions */
|
||||
struct fsg_operations {
|
||||
/*
|
||||
* Callback function to call when thread exits. If no
|
||||
* callback is set or it returns value lower then zero MSF
|
||||
* will force eject all LUNs it operates on (including those
|
||||
* marked as non-removable or with prevent_medium_removal flag
|
||||
* set).
|
||||
*/
|
||||
int (*thread_exits)(struct fsg_common *common);
|
||||
};
|
||||
|
||||
struct fsg_lun_opts {
|
||||
struct config_group group;
|
||||
struct fsg_lun *lun;
|
||||
int lun_id;
|
||||
};
|
||||
|
||||
struct fsg_opts {
|
||||
struct fsg_common *common;
|
||||
struct usb_function_instance func_inst;
|
||||
struct fsg_lun_opts lun0;
|
||||
struct config_group *default_groups[2];
|
||||
bool no_configfs; /* for legacy gadgets */
|
||||
|
||||
/*
|
||||
* Read/write access to configfs attributes is handled by configfs.
|
||||
*
|
||||
* This is to protect the data from concurrent access by read/write
|
||||
* and create symlink/remove symlink.
|
||||
*/
|
||||
struct mutex lock;
|
||||
int refcnt;
|
||||
};
|
||||
|
||||
struct fsg_lun_config {
|
||||
const char *filename;
|
||||
char ro;
|
||||
char removable;
|
||||
char cdrom;
|
||||
char nofua;
|
||||
};
|
||||
|
||||
struct fsg_config {
|
||||
unsigned nluns;
|
||||
struct fsg_lun_config luns[FSG_MAX_LUNS];
|
||||
|
||||
/* Callback functions. */
|
||||
const struct fsg_operations *ops;
|
||||
/* Gadget's private data. */
|
||||
void *private_data;
|
||||
|
||||
const char *vendor_name; /* 8 characters or less */
|
||||
const char *product_name; /* 16 characters or less */
|
||||
|
||||
char can_stall;
|
||||
unsigned int fsg_num_buffers;
|
||||
};
|
||||
|
||||
static inline struct fsg_opts *
|
||||
fsg_opts_from_func_inst(const struct usb_function_instance *fi)
|
||||
{
|
||||
return container_of(fi, struct fsg_opts, func_inst);
|
||||
}
|
||||
|
||||
void fsg_common_get(struct fsg_common *common);
|
||||
|
||||
void fsg_common_put(struct fsg_common *common);
|
||||
|
||||
void fsg_common_set_sysfs(struct fsg_common *common, bool sysfs);
|
||||
|
||||
int fsg_common_set_num_buffers(struct fsg_common *common, unsigned int n);
|
||||
|
||||
void fsg_common_free_buffers(struct fsg_common *common);
|
||||
|
||||
int fsg_common_set_cdev(struct fsg_common *common,
|
||||
struct usb_composite_dev *cdev, bool can_stall);
|
||||
|
||||
void fsg_common_remove_lun(struct fsg_lun *lun, bool sysfs);
|
||||
|
||||
void fsg_common_remove_luns(struct fsg_common *common);
|
||||
|
||||
void fsg_common_free_luns(struct fsg_common *common);
|
||||
|
||||
int fsg_common_set_nluns(struct fsg_common *common, int nluns);
|
||||
|
||||
void fsg_common_set_ops(struct fsg_common *common,
|
||||
const struct fsg_operations *ops);
|
||||
|
||||
int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg,
|
||||
unsigned int id, const char *name,
|
||||
const char **name_pfx);
|
||||
|
||||
int fsg_common_create_luns(struct fsg_common *common, struct fsg_config *cfg);
|
||||
|
||||
void fsg_common_set_inquiry_string(struct fsg_common *common, const char *vn,
|
||||
const char *pn);
|
||||
|
||||
int fsg_common_run_thread(struct fsg_common *common);
|
||||
|
||||
void fsg_config_from_params(struct fsg_config *cfg,
|
||||
const struct fsg_module_parameters *params,
|
||||
unsigned int fsg_num_buffers);
|
||||
|
||||
#endif /* USB_F_MASS_STORAGE_H */
|
||||
986
drivers/usb/gadget/function/f_midi.c
Normal file
986
drivers/usb/gadget/function/f_midi.c
Normal file
|
|
@ -0,0 +1,986 @@
|
|||
/*
|
||||
* f_midi.c -- USB MIDI class function driver
|
||||
*
|
||||
* Copyright (C) 2006 Thumtronics Pty Ltd.
|
||||
* Developed for Thumtronics by Grey Innovation
|
||||
* Ben Williamson <ben.williamson@greyinnovation.com>
|
||||
*
|
||||
* Rewritten for the composite framework
|
||||
* Copyright (C) 2011 Daniel Mack <zonque@gmail.com>
|
||||
*
|
||||
* Based on drivers/usb/gadget/f_audio.c,
|
||||
* Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
|
||||
* Copyright (C) 2008 Analog Devices, Inc
|
||||
*
|
||||
* and drivers/usb/gadget/midi.c,
|
||||
* Copyright (C) 2006 Thumtronics Pty Ltd.
|
||||
* Ben Williamson <ben.williamson@greyinnovation.com>
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/rawmidi.h>
|
||||
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/usb/audio.h>
|
||||
#include <linux/usb/midi.h>
|
||||
|
||||
#include "u_f.h"
|
||||
|
||||
MODULE_AUTHOR("Ben Williamson");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
static const char f_midi_shortname[] = "f_midi";
|
||||
static const char f_midi_longname[] = "MIDI Gadget";
|
||||
|
||||
/*
|
||||
* We can only handle 16 cables on one single endpoint, as cable numbers are
|
||||
* stored in 4-bit fields. And as the interface currently only holds one
|
||||
* single endpoint, this is the maximum number of ports we can allow.
|
||||
*/
|
||||
#define MAX_PORTS 16
|
||||
|
||||
/*
|
||||
* This is a gadget, and the IN/OUT naming is from the host's perspective.
|
||||
* USB -> OUT endpoint -> rawmidi
|
||||
* USB <- IN endpoint <- rawmidi
|
||||
*/
|
||||
struct gmidi_in_port {
|
||||
struct f_midi *midi;
|
||||
int active;
|
||||
uint8_t cable;
|
||||
uint8_t state;
|
||||
#define STATE_UNKNOWN 0
|
||||
#define STATE_1PARAM 1
|
||||
#define STATE_2PARAM_1 2
|
||||
#define STATE_2PARAM_2 3
|
||||
#define STATE_SYSEX_0 4
|
||||
#define STATE_SYSEX_1 5
|
||||
#define STATE_SYSEX_2 6
|
||||
uint8_t data[2];
|
||||
};
|
||||
|
||||
struct f_midi {
|
||||
struct usb_function func;
|
||||
struct usb_gadget *gadget;
|
||||
struct usb_ep *in_ep, *out_ep;
|
||||
struct snd_card *card;
|
||||
struct snd_rawmidi *rmidi;
|
||||
|
||||
struct snd_rawmidi_substream *in_substream[MAX_PORTS];
|
||||
struct snd_rawmidi_substream *out_substream[MAX_PORTS];
|
||||
struct gmidi_in_port *in_port[MAX_PORTS];
|
||||
|
||||
unsigned long out_triggered;
|
||||
struct tasklet_struct tasklet;
|
||||
unsigned int in_ports;
|
||||
unsigned int out_ports;
|
||||
int index;
|
||||
char *id;
|
||||
unsigned int buflen, qlen;
|
||||
};
|
||||
|
||||
static inline struct f_midi *func_to_midi(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_midi, func);
|
||||
}
|
||||
|
||||
static void f_midi_transmit(struct f_midi *midi, struct usb_request *req);
|
||||
|
||||
DECLARE_UAC_AC_HEADER_DESCRIPTOR(1);
|
||||
DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1);
|
||||
DECLARE_USB_MS_ENDPOINT_DESCRIPTOR(16);
|
||||
|
||||
/* B.3.1 Standard AC Interface Descriptor */
|
||||
static struct usb_interface_descriptor ac_interface_desc __initdata = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
/* .bNumEndpoints = DYNAMIC */
|
||||
.bInterfaceClass = USB_CLASS_AUDIO,
|
||||
.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* B.3.2 Class-Specific AC Interface Descriptor */
|
||||
static struct uac1_ac_header_descriptor_1 ac_header_desc __initdata = {
|
||||
.bLength = UAC_DT_AC_HEADER_SIZE(1),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubtype = USB_MS_HEADER,
|
||||
.bcdADC = cpu_to_le16(0x0100),
|
||||
.wTotalLength = cpu_to_le16(UAC_DT_AC_HEADER_SIZE(1)),
|
||||
.bInCollection = 1,
|
||||
/* .baInterfaceNr = DYNAMIC */
|
||||
};
|
||||
|
||||
/* B.4.1 Standard MS Interface Descriptor */
|
||||
static struct usb_interface_descriptor ms_interface_desc __initdata = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_AUDIO,
|
||||
.bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* B.4.2 Class-Specific MS Interface Descriptor */
|
||||
static struct usb_ms_header_descriptor ms_header_desc __initdata = {
|
||||
.bLength = USB_DT_MS_HEADER_SIZE,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubtype = USB_MS_HEADER,
|
||||
.bcdMSC = cpu_to_le16(0x0100),
|
||||
/* .wTotalLength = DYNAMIC */
|
||||
};
|
||||
|
||||
/* B.5.1 Standard Bulk OUT Endpoint Descriptor */
|
||||
static struct usb_endpoint_descriptor bulk_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
/* B.5.2 Class-specific MS Bulk OUT Endpoint Descriptor */
|
||||
static struct usb_ms_endpoint_descriptor_16 ms_out_desc = {
|
||||
/* .bLength = DYNAMIC */
|
||||
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
||||
.bDescriptorSubtype = USB_MS_GENERAL,
|
||||
/* .bNumEmbMIDIJack = DYNAMIC */
|
||||
/* .baAssocJackID = DYNAMIC */
|
||||
};
|
||||
|
||||
/* B.6.1 Standard Bulk IN Endpoint Descriptor */
|
||||
static struct usb_endpoint_descriptor bulk_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
/* B.6.2 Class-specific MS Bulk IN Endpoint Descriptor */
|
||||
static struct usb_ms_endpoint_descriptor_16 ms_in_desc = {
|
||||
/* .bLength = DYNAMIC */
|
||||
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
||||
.bDescriptorSubtype = USB_MS_GENERAL,
|
||||
/* .bNumEmbMIDIJack = DYNAMIC */
|
||||
/* .baAssocJackID = DYNAMIC */
|
||||
};
|
||||
|
||||
/* string IDs are assigned dynamically */
|
||||
|
||||
#define STRING_FUNC_IDX 0
|
||||
|
||||
static struct usb_string midi_string_defs[] = {
|
||||
[STRING_FUNC_IDX].s = "MIDI function",
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings midi_stringtab = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = midi_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *midi_strings[] = {
|
||||
&midi_stringtab,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static inline struct usb_request *midi_alloc_ep_req(struct usb_ep *ep,
|
||||
unsigned length)
|
||||
{
|
||||
return alloc_ep_req(ep, length, length);
|
||||
}
|
||||
|
||||
static void free_ep_req(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
kfree(req->buf);
|
||||
usb_ep_free_request(ep, req);
|
||||
}
|
||||
|
||||
static const uint8_t f_midi_cin_length[] = {
|
||||
0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1
|
||||
};
|
||||
|
||||
/*
|
||||
* Receives a chunk of MIDI data.
|
||||
*/
|
||||
static void f_midi_read_data(struct usb_ep *ep, int cable,
|
||||
uint8_t *data, int length)
|
||||
{
|
||||
struct f_midi *midi = ep->driver_data;
|
||||
struct snd_rawmidi_substream *substream = midi->out_substream[cable];
|
||||
|
||||
if (!substream)
|
||||
/* Nobody is listening - throw it on the floor. */
|
||||
return;
|
||||
|
||||
if (!test_bit(cable, &midi->out_triggered))
|
||||
return;
|
||||
|
||||
snd_rawmidi_receive(substream, data, length);
|
||||
}
|
||||
|
||||
static void f_midi_handle_out_data(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
unsigned int i;
|
||||
u8 *buf = req->buf;
|
||||
|
||||
for (i = 0; i + 3 < req->actual; i += 4)
|
||||
if (buf[i] != 0) {
|
||||
int cable = buf[i] >> 4;
|
||||
int length = f_midi_cin_length[buf[i] & 0x0f];
|
||||
f_midi_read_data(ep, cable, &buf[i + 1], length);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
f_midi_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_midi *midi = ep->driver_data;
|
||||
struct usb_composite_dev *cdev = midi->func.config->cdev;
|
||||
int status = req->status;
|
||||
|
||||
switch (status) {
|
||||
case 0: /* normal completion */
|
||||
if (ep == midi->out_ep) {
|
||||
/* We received stuff. req is queued again, below */
|
||||
f_midi_handle_out_data(ep, req);
|
||||
} else if (ep == midi->in_ep) {
|
||||
/* Our transmit completed. See if there's more to go.
|
||||
* f_midi_transmit eats req, don't queue it again. */
|
||||
f_midi_transmit(midi, req);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
/* this endpoint is normally active while we're configured */
|
||||
case -ECONNABORTED: /* hardware forced ep reset */
|
||||
case -ECONNRESET: /* request dequeued */
|
||||
case -ESHUTDOWN: /* disconnect from host */
|
||||
VDBG(cdev, "%s gone (%d), %d/%d\n", ep->name, status,
|
||||
req->actual, req->length);
|
||||
if (ep == midi->out_ep)
|
||||
f_midi_handle_out_data(ep, req);
|
||||
|
||||
free_ep_req(ep, req);
|
||||
return;
|
||||
|
||||
case -EOVERFLOW: /* buffer overrun on read means that
|
||||
* we didn't provide a big enough buffer.
|
||||
*/
|
||||
default:
|
||||
DBG(cdev, "%s complete --> %d, %d/%d\n", ep->name,
|
||||
status, req->actual, req->length);
|
||||
break;
|
||||
case -EREMOTEIO: /* short read */
|
||||
break;
|
||||
}
|
||||
|
||||
status = usb_ep_queue(ep, req, GFP_ATOMIC);
|
||||
if (status) {
|
||||
ERROR(cdev, "kill %s: resubmit %d bytes --> %d\n",
|
||||
ep->name, req->length, status);
|
||||
usb_ep_set_halt(ep);
|
||||
/* FIXME recover later ... somehow */
|
||||
}
|
||||
}
|
||||
|
||||
static int f_midi_start_ep(struct f_midi *midi,
|
||||
struct usb_function *f,
|
||||
struct usb_ep *ep)
|
||||
{
|
||||
int err;
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
if (ep->driver_data)
|
||||
usb_ep_disable(ep);
|
||||
|
||||
err = config_ep_by_speed(midi->gadget, f, ep);
|
||||
if (err) {
|
||||
ERROR(cdev, "can't configure %s: %d\n", ep->name, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = usb_ep_enable(ep);
|
||||
if (err) {
|
||||
ERROR(cdev, "can't start %s: %d\n", ep->name, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ep->driver_data = midi;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_midi *midi = func_to_midi(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
unsigned i;
|
||||
int err;
|
||||
|
||||
err = f_midi_start_ep(midi, f, midi->in_ep);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = f_midi_start_ep(midi, f, midi->out_ep);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (midi->out_ep->driver_data)
|
||||
usb_ep_disable(midi->out_ep);
|
||||
|
||||
err = config_ep_by_speed(midi->gadget, f, midi->out_ep);
|
||||
if (err) {
|
||||
ERROR(cdev, "can't configure %s: %d\n",
|
||||
midi->out_ep->name, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = usb_ep_enable(midi->out_ep);
|
||||
if (err) {
|
||||
ERROR(cdev, "can't start %s: %d\n",
|
||||
midi->out_ep->name, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
midi->out_ep->driver_data = midi;
|
||||
|
||||
/* allocate a bunch of read buffers and queue them all at once. */
|
||||
for (i = 0; i < midi->qlen && err == 0; i++) {
|
||||
struct usb_request *req =
|
||||
midi_alloc_ep_req(midi->out_ep, midi->buflen);
|
||||
if (req == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
req->complete = f_midi_complete;
|
||||
err = usb_ep_queue(midi->out_ep, req, GFP_ATOMIC);
|
||||
if (err) {
|
||||
ERROR(midi, "%s queue req: %d\n",
|
||||
midi->out_ep->name, err);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void f_midi_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_midi *midi = func_to_midi(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "disable\n");
|
||||
|
||||
/*
|
||||
* just disable endpoints, forcing completion of pending i/o.
|
||||
* all our completion handlers free their requests in this case.
|
||||
*/
|
||||
usb_ep_disable(midi->in_ep);
|
||||
usb_ep_disable(midi->out_ep);
|
||||
}
|
||||
|
||||
static void f_midi_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct f_midi *midi = func_to_midi(f);
|
||||
struct snd_card *card;
|
||||
|
||||
DBG(cdev, "unbind\n");
|
||||
|
||||
/* just to be sure */
|
||||
f_midi_disable(f);
|
||||
|
||||
card = midi->card;
|
||||
midi->card = NULL;
|
||||
if (card)
|
||||
snd_card_free(card);
|
||||
|
||||
kfree(midi->id);
|
||||
midi->id = NULL;
|
||||
|
||||
usb_free_all_descriptors(f);
|
||||
kfree(midi);
|
||||
}
|
||||
|
||||
static int f_midi_snd_free(struct snd_device *device)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void f_midi_transmit_packet(struct usb_request *req, uint8_t p0,
|
||||
uint8_t p1, uint8_t p2, uint8_t p3)
|
||||
{
|
||||
unsigned length = req->length;
|
||||
u8 *buf = (u8 *)req->buf + length;
|
||||
|
||||
buf[0] = p0;
|
||||
buf[1] = p1;
|
||||
buf[2] = p2;
|
||||
buf[3] = p3;
|
||||
req->length = length + 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* Converts MIDI commands to USB MIDI packets.
|
||||
*/
|
||||
static void f_midi_transmit_byte(struct usb_request *req,
|
||||
struct gmidi_in_port *port, uint8_t b)
|
||||
{
|
||||
uint8_t p0 = port->cable << 4;
|
||||
|
||||
if (b >= 0xf8) {
|
||||
f_midi_transmit_packet(req, p0 | 0x0f, b, 0, 0);
|
||||
} else if (b >= 0xf0) {
|
||||
switch (b) {
|
||||
case 0xf0:
|
||||
port->data[0] = b;
|
||||
port->state = STATE_SYSEX_1;
|
||||
break;
|
||||
case 0xf1:
|
||||
case 0xf3:
|
||||
port->data[0] = b;
|
||||
port->state = STATE_1PARAM;
|
||||
break;
|
||||
case 0xf2:
|
||||
port->data[0] = b;
|
||||
port->state = STATE_2PARAM_1;
|
||||
break;
|
||||
case 0xf4:
|
||||
case 0xf5:
|
||||
port->state = STATE_UNKNOWN;
|
||||
break;
|
||||
case 0xf6:
|
||||
f_midi_transmit_packet(req, p0 | 0x05, 0xf6, 0, 0);
|
||||
port->state = STATE_UNKNOWN;
|
||||
break;
|
||||
case 0xf7:
|
||||
switch (port->state) {
|
||||
case STATE_SYSEX_0:
|
||||
f_midi_transmit_packet(req,
|
||||
p0 | 0x05, 0xf7, 0, 0);
|
||||
break;
|
||||
case STATE_SYSEX_1:
|
||||
f_midi_transmit_packet(req,
|
||||
p0 | 0x06, port->data[0], 0xf7, 0);
|
||||
break;
|
||||
case STATE_SYSEX_2:
|
||||
f_midi_transmit_packet(req,
|
||||
p0 | 0x07, port->data[0],
|
||||
port->data[1], 0xf7);
|
||||
break;
|
||||
}
|
||||
port->state = STATE_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
} else if (b >= 0x80) {
|
||||
port->data[0] = b;
|
||||
if (b >= 0xc0 && b <= 0xdf)
|
||||
port->state = STATE_1PARAM;
|
||||
else
|
||||
port->state = STATE_2PARAM_1;
|
||||
} else { /* b < 0x80 */
|
||||
switch (port->state) {
|
||||
case STATE_1PARAM:
|
||||
if (port->data[0] < 0xf0) {
|
||||
p0 |= port->data[0] >> 4;
|
||||
} else {
|
||||
p0 |= 0x02;
|
||||
port->state = STATE_UNKNOWN;
|
||||
}
|
||||
f_midi_transmit_packet(req, p0, port->data[0], b, 0);
|
||||
break;
|
||||
case STATE_2PARAM_1:
|
||||
port->data[1] = b;
|
||||
port->state = STATE_2PARAM_2;
|
||||
break;
|
||||
case STATE_2PARAM_2:
|
||||
if (port->data[0] < 0xf0) {
|
||||
p0 |= port->data[0] >> 4;
|
||||
port->state = STATE_2PARAM_1;
|
||||
} else {
|
||||
p0 |= 0x03;
|
||||
port->state = STATE_UNKNOWN;
|
||||
}
|
||||
f_midi_transmit_packet(req,
|
||||
p0, port->data[0], port->data[1], b);
|
||||
break;
|
||||
case STATE_SYSEX_0:
|
||||
port->data[0] = b;
|
||||
port->state = STATE_SYSEX_1;
|
||||
break;
|
||||
case STATE_SYSEX_1:
|
||||
port->data[1] = b;
|
||||
port->state = STATE_SYSEX_2;
|
||||
break;
|
||||
case STATE_SYSEX_2:
|
||||
f_midi_transmit_packet(req,
|
||||
p0 | 0x04, port->data[0], port->data[1], b);
|
||||
port->state = STATE_SYSEX_0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void f_midi_transmit(struct f_midi *midi, struct usb_request *req)
|
||||
{
|
||||
struct usb_ep *ep = midi->in_ep;
|
||||
int i;
|
||||
|
||||
if (!ep)
|
||||
return;
|
||||
|
||||
if (!req)
|
||||
req = midi_alloc_ep_req(ep, midi->buflen);
|
||||
|
||||
if (!req) {
|
||||
ERROR(midi, "gmidi_transmit: alloc_ep_request failed\n");
|
||||
return;
|
||||
}
|
||||
req->length = 0;
|
||||
req->complete = f_midi_complete;
|
||||
|
||||
for (i = 0; i < MAX_PORTS; i++) {
|
||||
struct gmidi_in_port *port = midi->in_port[i];
|
||||
struct snd_rawmidi_substream *substream = midi->in_substream[i];
|
||||
|
||||
if (!port || !port->active || !substream)
|
||||
continue;
|
||||
|
||||
while (req->length + 3 < midi->buflen) {
|
||||
uint8_t b;
|
||||
if (snd_rawmidi_transmit(substream, &b, 1) != 1) {
|
||||
port->active = 0;
|
||||
break;
|
||||
}
|
||||
f_midi_transmit_byte(req, port, b);
|
||||
}
|
||||
}
|
||||
|
||||
if (req->length > 0)
|
||||
usb_ep_queue(ep, req, GFP_ATOMIC);
|
||||
else
|
||||
free_ep_req(ep, req);
|
||||
}
|
||||
|
||||
static void f_midi_in_tasklet(unsigned long data)
|
||||
{
|
||||
struct f_midi *midi = (struct f_midi *) data;
|
||||
f_midi_transmit(midi, NULL);
|
||||
}
|
||||
|
||||
static int f_midi_in_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct f_midi *midi = substream->rmidi->private_data;
|
||||
|
||||
if (!midi->in_port[substream->number])
|
||||
return -EINVAL;
|
||||
|
||||
VDBG(midi, "%s()\n", __func__);
|
||||
midi->in_substream[substream->number] = substream;
|
||||
midi->in_port[substream->number]->state = STATE_UNKNOWN;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int f_midi_in_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct f_midi *midi = substream->rmidi->private_data;
|
||||
|
||||
VDBG(midi, "%s()\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void f_midi_in_trigger(struct snd_rawmidi_substream *substream, int up)
|
||||
{
|
||||
struct f_midi *midi = substream->rmidi->private_data;
|
||||
|
||||
if (!midi->in_port[substream->number])
|
||||
return;
|
||||
|
||||
VDBG(midi, "%s() %d\n", __func__, up);
|
||||
midi->in_port[substream->number]->active = up;
|
||||
if (up)
|
||||
tasklet_hi_schedule(&midi->tasklet);
|
||||
}
|
||||
|
||||
static int f_midi_out_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct f_midi *midi = substream->rmidi->private_data;
|
||||
|
||||
if (substream->number >= MAX_PORTS)
|
||||
return -EINVAL;
|
||||
|
||||
VDBG(midi, "%s()\n", __func__);
|
||||
midi->out_substream[substream->number] = substream;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int f_midi_out_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct f_midi *midi = substream->rmidi->private_data;
|
||||
|
||||
VDBG(midi, "%s()\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void f_midi_out_trigger(struct snd_rawmidi_substream *substream, int up)
|
||||
{
|
||||
struct f_midi *midi = substream->rmidi->private_data;
|
||||
|
||||
VDBG(midi, "%s()\n", __func__);
|
||||
|
||||
if (up)
|
||||
set_bit(substream->number, &midi->out_triggered);
|
||||
else
|
||||
clear_bit(substream->number, &midi->out_triggered);
|
||||
}
|
||||
|
||||
static struct snd_rawmidi_ops gmidi_in_ops = {
|
||||
.open = f_midi_in_open,
|
||||
.close = f_midi_in_close,
|
||||
.trigger = f_midi_in_trigger,
|
||||
};
|
||||
|
||||
static struct snd_rawmidi_ops gmidi_out_ops = {
|
||||
.open = f_midi_out_open,
|
||||
.close = f_midi_out_close,
|
||||
.trigger = f_midi_out_trigger
|
||||
};
|
||||
|
||||
/* register as a sound "card" */
|
||||
static int f_midi_register_card(struct f_midi *midi)
|
||||
{
|
||||
struct snd_card *card;
|
||||
struct snd_rawmidi *rmidi;
|
||||
int err;
|
||||
static struct snd_device_ops ops = {
|
||||
.dev_free = f_midi_snd_free,
|
||||
};
|
||||
|
||||
err = snd_card_new(&midi->gadget->dev, midi->index, midi->id,
|
||||
THIS_MODULE, 0, &card);
|
||||
if (err < 0) {
|
||||
ERROR(midi, "snd_card_new() failed\n");
|
||||
goto fail;
|
||||
}
|
||||
midi->card = card;
|
||||
|
||||
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, midi, &ops);
|
||||
if (err < 0) {
|
||||
ERROR(midi, "snd_device_new() failed: error %d\n", err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
strcpy(card->driver, f_midi_longname);
|
||||
strcpy(card->longname, f_midi_longname);
|
||||
strcpy(card->shortname, f_midi_shortname);
|
||||
|
||||
/* Set up rawmidi */
|
||||
snd_component_add(card, "MIDI");
|
||||
err = snd_rawmidi_new(card, card->longname, 0,
|
||||
midi->out_ports, midi->in_ports, &rmidi);
|
||||
if (err < 0) {
|
||||
ERROR(midi, "snd_rawmidi_new() failed: error %d\n", err);
|
||||
goto fail;
|
||||
}
|
||||
midi->rmidi = rmidi;
|
||||
strcpy(rmidi->name, card->shortname);
|
||||
rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
|
||||
SNDRV_RAWMIDI_INFO_INPUT |
|
||||
SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||
rmidi->private_data = midi;
|
||||
|
||||
/*
|
||||
* Yes, rawmidi OUTPUT = USB IN, and rawmidi INPUT = USB OUT.
|
||||
* It's an upside-down world being a gadget.
|
||||
*/
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &gmidi_in_ops);
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &gmidi_out_ops);
|
||||
|
||||
/* register it - we're ready to go */
|
||||
err = snd_card_register(card);
|
||||
if (err < 0) {
|
||||
ERROR(midi, "snd_card_register() failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
VDBG(midi, "%s() finished ok\n", __func__);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (midi->card) {
|
||||
snd_card_free(midi->card);
|
||||
midi->card = NULL;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* MIDI function driver setup/binding */
|
||||
|
||||
static int __init
|
||||
f_midi_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_descriptor_header **midi_function;
|
||||
struct usb_midi_in_jack_descriptor jack_in_ext_desc[MAX_PORTS];
|
||||
struct usb_midi_in_jack_descriptor jack_in_emb_desc[MAX_PORTS];
|
||||
struct usb_midi_out_jack_descriptor_1 jack_out_ext_desc[MAX_PORTS];
|
||||
struct usb_midi_out_jack_descriptor_1 jack_out_emb_desc[MAX_PORTS];
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_midi *midi = func_to_midi(f);
|
||||
int status, n, jack = 1, i = 0;
|
||||
|
||||
/* maybe allocate device-global string ID */
|
||||
if (midi_string_defs[0].id == 0) {
|
||||
status = usb_string_id(c->cdev);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
midi_string_defs[0].id = status;
|
||||
}
|
||||
|
||||
/* We have two interfaces, AudioControl and MIDIStreaming */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
ac_interface_desc.bInterfaceNumber = status;
|
||||
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
ms_interface_desc.bInterfaceNumber = status;
|
||||
ac_header_desc.baInterfaceNr[0] = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
midi->in_ep = usb_ep_autoconfig(cdev->gadget, &bulk_in_desc);
|
||||
if (!midi->in_ep)
|
||||
goto fail;
|
||||
midi->in_ep->driver_data = cdev; /* claim */
|
||||
|
||||
midi->out_ep = usb_ep_autoconfig(cdev->gadget, &bulk_out_desc);
|
||||
if (!midi->out_ep)
|
||||
goto fail;
|
||||
midi->out_ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* allocate temporary function list */
|
||||
midi_function = kcalloc((MAX_PORTS * 4) + 9, sizeof(*midi_function),
|
||||
GFP_KERNEL);
|
||||
if (!midi_function) {
|
||||
status = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/*
|
||||
* construct the function's descriptor set. As the number of
|
||||
* input and output MIDI ports is configurable, we have to do
|
||||
* it that way.
|
||||
*/
|
||||
|
||||
/* add the headers - these are always the same */
|
||||
midi_function[i++] = (struct usb_descriptor_header *) &ac_interface_desc;
|
||||
midi_function[i++] = (struct usb_descriptor_header *) &ac_header_desc;
|
||||
midi_function[i++] = (struct usb_descriptor_header *) &ms_interface_desc;
|
||||
|
||||
/* calculate the header's wTotalLength */
|
||||
n = USB_DT_MS_HEADER_SIZE
|
||||
+ (midi->in_ports + midi->out_ports) *
|
||||
(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
|
||||
ms_header_desc.wTotalLength = cpu_to_le16(n);
|
||||
|
||||
midi_function[i++] = (struct usb_descriptor_header *) &ms_header_desc;
|
||||
|
||||
/* configure the external IN jacks, each linked to an embedded OUT jack */
|
||||
for (n = 0; n < midi->in_ports; n++) {
|
||||
struct usb_midi_in_jack_descriptor *in_ext = &jack_in_ext_desc[n];
|
||||
struct usb_midi_out_jack_descriptor_1 *out_emb = &jack_out_emb_desc[n];
|
||||
|
||||
in_ext->bLength = USB_DT_MIDI_IN_SIZE;
|
||||
in_ext->bDescriptorType = USB_DT_CS_INTERFACE;
|
||||
in_ext->bDescriptorSubtype = USB_MS_MIDI_IN_JACK;
|
||||
in_ext->bJackType = USB_MS_EXTERNAL;
|
||||
in_ext->bJackID = jack++;
|
||||
in_ext->iJack = 0;
|
||||
midi_function[i++] = (struct usb_descriptor_header *) in_ext;
|
||||
|
||||
out_emb->bLength = USB_DT_MIDI_OUT_SIZE(1);
|
||||
out_emb->bDescriptorType = USB_DT_CS_INTERFACE;
|
||||
out_emb->bDescriptorSubtype = USB_MS_MIDI_OUT_JACK;
|
||||
out_emb->bJackType = USB_MS_EMBEDDED;
|
||||
out_emb->bJackID = jack++;
|
||||
out_emb->bNrInputPins = 1;
|
||||
out_emb->pins[0].baSourcePin = 1;
|
||||
out_emb->pins[0].baSourceID = in_ext->bJackID;
|
||||
out_emb->iJack = 0;
|
||||
midi_function[i++] = (struct usb_descriptor_header *) out_emb;
|
||||
|
||||
/* link it to the endpoint */
|
||||
ms_in_desc.baAssocJackID[n] = out_emb->bJackID;
|
||||
}
|
||||
|
||||
/* configure the external OUT jacks, each linked to an embedded IN jack */
|
||||
for (n = 0; n < midi->out_ports; n++) {
|
||||
struct usb_midi_in_jack_descriptor *in_emb = &jack_in_emb_desc[n];
|
||||
struct usb_midi_out_jack_descriptor_1 *out_ext = &jack_out_ext_desc[n];
|
||||
|
||||
in_emb->bLength = USB_DT_MIDI_IN_SIZE;
|
||||
in_emb->bDescriptorType = USB_DT_CS_INTERFACE;
|
||||
in_emb->bDescriptorSubtype = USB_MS_MIDI_IN_JACK;
|
||||
in_emb->bJackType = USB_MS_EMBEDDED;
|
||||
in_emb->bJackID = jack++;
|
||||
in_emb->iJack = 0;
|
||||
midi_function[i++] = (struct usb_descriptor_header *) in_emb;
|
||||
|
||||
out_ext->bLength = USB_DT_MIDI_OUT_SIZE(1);
|
||||
out_ext->bDescriptorType = USB_DT_CS_INTERFACE;
|
||||
out_ext->bDescriptorSubtype = USB_MS_MIDI_OUT_JACK;
|
||||
out_ext->bJackType = USB_MS_EXTERNAL;
|
||||
out_ext->bJackID = jack++;
|
||||
out_ext->bNrInputPins = 1;
|
||||
out_ext->iJack = 0;
|
||||
out_ext->pins[0].baSourceID = in_emb->bJackID;
|
||||
out_ext->pins[0].baSourcePin = 1;
|
||||
midi_function[i++] = (struct usb_descriptor_header *) out_ext;
|
||||
|
||||
/* link it to the endpoint */
|
||||
ms_out_desc.baAssocJackID[n] = in_emb->bJackID;
|
||||
}
|
||||
|
||||
/* configure the endpoint descriptors ... */
|
||||
ms_out_desc.bLength = USB_DT_MS_ENDPOINT_SIZE(midi->in_ports);
|
||||
ms_out_desc.bNumEmbMIDIJack = midi->in_ports;
|
||||
|
||||
ms_in_desc.bLength = USB_DT_MS_ENDPOINT_SIZE(midi->out_ports);
|
||||
ms_in_desc.bNumEmbMIDIJack = midi->out_ports;
|
||||
|
||||
/* ... and add them to the list */
|
||||
midi_function[i++] = (struct usb_descriptor_header *) &bulk_out_desc;
|
||||
midi_function[i++] = (struct usb_descriptor_header *) &ms_out_desc;
|
||||
midi_function[i++] = (struct usb_descriptor_header *) &bulk_in_desc;
|
||||
midi_function[i++] = (struct usb_descriptor_header *) &ms_in_desc;
|
||||
midi_function[i++] = NULL;
|
||||
|
||||
/*
|
||||
* support all relevant hardware speeds... we expect that when
|
||||
* hardware is dual speed, all bulk-capable endpoints work at
|
||||
* both speeds
|
||||
*/
|
||||
/* copy descriptors, and track endpoint copies */
|
||||
f->fs_descriptors = usb_copy_descriptors(midi_function);
|
||||
if (!f->fs_descriptors)
|
||||
goto fail_f_midi;
|
||||
|
||||
if (gadget_is_dualspeed(c->cdev->gadget)) {
|
||||
bulk_in_desc.wMaxPacketSize = cpu_to_le16(512);
|
||||
bulk_out_desc.wMaxPacketSize = cpu_to_le16(512);
|
||||
f->hs_descriptors = usb_copy_descriptors(midi_function);
|
||||
if (!f->hs_descriptors)
|
||||
goto fail_f_midi;
|
||||
}
|
||||
|
||||
kfree(midi_function);
|
||||
|
||||
return 0;
|
||||
|
||||
fail_f_midi:
|
||||
kfree(midi_function);
|
||||
usb_free_descriptors(f->hs_descriptors);
|
||||
fail:
|
||||
/* we might as well release our claims on endpoints */
|
||||
if (midi->out_ep)
|
||||
midi->out_ep->driver_data = NULL;
|
||||
if (midi->in_ep)
|
||||
midi->in_ep->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* f_midi_bind_config - add USB MIDI function to a configuration
|
||||
* @c: the configuration to supcard the USB audio function
|
||||
* @index: the soundcard index to use for the ALSA device creation
|
||||
* @id: the soundcard id to use for the ALSA device creation
|
||||
* @buflen: the buffer length to use
|
||||
* @qlen the number of read requests to pre-allocate
|
||||
* Context: single threaded during gadget setup
|
||||
*
|
||||
* Returns zero on success, else negative errno.
|
||||
*/
|
||||
int __init f_midi_bind_config(struct usb_configuration *c,
|
||||
int index, char *id,
|
||||
unsigned int in_ports,
|
||||
unsigned int out_ports,
|
||||
unsigned int buflen,
|
||||
unsigned int qlen)
|
||||
{
|
||||
struct f_midi *midi;
|
||||
int status, i;
|
||||
|
||||
/* sanity check */
|
||||
if (in_ports > MAX_PORTS || out_ports > MAX_PORTS)
|
||||
return -EINVAL;
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
midi = kzalloc(sizeof *midi, GFP_KERNEL);
|
||||
if (!midi) {
|
||||
status = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < in_ports; i++) {
|
||||
struct gmidi_in_port *port = kzalloc(sizeof(*port), GFP_KERNEL);
|
||||
if (!port) {
|
||||
status = -ENOMEM;
|
||||
goto setup_fail;
|
||||
}
|
||||
|
||||
port->midi = midi;
|
||||
port->active = 0;
|
||||
port->cable = i;
|
||||
midi->in_port[i] = port;
|
||||
}
|
||||
|
||||
midi->gadget = c->cdev->gadget;
|
||||
tasklet_init(&midi->tasklet, f_midi_in_tasklet, (unsigned long) midi);
|
||||
|
||||
/* set up ALSA midi devices */
|
||||
midi->in_ports = in_ports;
|
||||
midi->out_ports = out_ports;
|
||||
status = f_midi_register_card(midi);
|
||||
if (status < 0)
|
||||
goto setup_fail;
|
||||
|
||||
midi->func.name = "gmidi function";
|
||||
midi->func.strings = midi_strings;
|
||||
midi->func.bind = f_midi_bind;
|
||||
midi->func.unbind = f_midi_unbind;
|
||||
midi->func.set_alt = f_midi_set_alt;
|
||||
midi->func.disable = f_midi_disable;
|
||||
|
||||
midi->id = kstrdup(id, GFP_KERNEL);
|
||||
midi->index = index;
|
||||
midi->buflen = buflen;
|
||||
midi->qlen = qlen;
|
||||
|
||||
status = usb_add_function(c, &midi->func);
|
||||
if (status)
|
||||
goto setup_fail;
|
||||
|
||||
return 0;
|
||||
|
||||
setup_fail:
|
||||
for (--i; i >= 0; i--)
|
||||
kfree(midi->in_port[i]);
|
||||
kfree(midi);
|
||||
fail:
|
||||
return status;
|
||||
}
|
||||
|
||||
1622
drivers/usb/gadget/function/f_ncm.c
Normal file
1622
drivers/usb/gadget/function/f_ncm.c
Normal file
File diff suppressed because it is too large
Load diff
533
drivers/usb/gadget/function/f_obex.c
Normal file
533
drivers/usb/gadget/function/f_obex.c
Normal file
|
|
@ -0,0 +1,533 @@
|
|||
/*
|
||||
* f_obex.c -- USB CDC OBEX function driver
|
||||
*
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
* Contact: Felipe Balbi <felipe.balbi@nokia.com>
|
||||
*
|
||||
* Based on f_acm.c by Al Borchers and David Brownell.
|
||||
*
|
||||
* 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 "u_serial.h"
|
||||
#include "gadget_chips.h"
|
||||
|
||||
|
||||
/*
|
||||
* This CDC OBEX function support just packages a TTY-ish byte stream.
|
||||
* A user mode server will put it into "raw" mode and handle all the
|
||||
* relevant protocol details ... this is just a kernel passthrough.
|
||||
* When possible, we prevent gadget enumeration until that server is
|
||||
* ready to handle the commands.
|
||||
*/
|
||||
|
||||
struct f_obex {
|
||||
struct gserial port;
|
||||
u8 ctrl_id;
|
||||
u8 data_id;
|
||||
u8 port_num;
|
||||
u8 can_activate;
|
||||
};
|
||||
|
||||
static inline struct f_obex *func_to_obex(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_obex, port.func);
|
||||
}
|
||||
|
||||
static inline struct f_obex *port_to_obex(struct gserial *p)
|
||||
{
|
||||
return container_of(p, struct f_obex, port);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define OBEX_CTRL_IDX 0
|
||||
#define OBEX_DATA_IDX 1
|
||||
|
||||
static struct usb_string obex_string_defs[] = {
|
||||
[OBEX_CTRL_IDX].s = "CDC Object Exchange (OBEX)",
|
||||
[OBEX_DATA_IDX].s = "CDC OBEX Data",
|
||||
{ }, /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings obex_string_table = {
|
||||
.language = 0x0409, /* en-US */
|
||||
.strings = obex_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *obex_strings[] = {
|
||||
&obex_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static struct usb_interface_descriptor obex_control_intf = {
|
||||
.bLength = sizeof(obex_control_intf),
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = 0,
|
||||
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 0,
|
||||
.bInterfaceClass = USB_CLASS_COMM,
|
||||
.bInterfaceSubClass = USB_CDC_SUBCLASS_OBEX,
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor obex_data_nop_intf = {
|
||||
.bLength = sizeof(obex_data_nop_intf),
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = 1,
|
||||
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 0,
|
||||
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor obex_data_intf = {
|
||||
.bLength = sizeof(obex_data_intf),
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = 2,
|
||||
|
||||
.bAlternateSetting = 1,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||
};
|
||||
|
||||
static struct usb_cdc_header_desc obex_cdc_header_desc = {
|
||||
.bLength = sizeof(obex_cdc_header_desc),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_HEADER_TYPE,
|
||||
.bcdCDC = cpu_to_le16(0x0120),
|
||||
};
|
||||
|
||||
static struct usb_cdc_union_desc obex_cdc_union_desc = {
|
||||
.bLength = sizeof(obex_cdc_union_desc),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_UNION_TYPE,
|
||||
.bMasterInterface0 = 1,
|
||||
.bSlaveInterface0 = 2,
|
||||
};
|
||||
|
||||
static struct usb_cdc_obex_desc obex_desc = {
|
||||
.bLength = sizeof(obex_desc),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_OBEX_TYPE,
|
||||
.bcdVersion = cpu_to_le16(0x0100),
|
||||
};
|
||||
|
||||
/* High-Speed Support */
|
||||
|
||||
static struct usb_endpoint_descriptor obex_hs_ep_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor obex_hs_ep_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *hs_function[] = {
|
||||
(struct usb_descriptor_header *) &obex_control_intf,
|
||||
(struct usb_descriptor_header *) &obex_cdc_header_desc,
|
||||
(struct usb_descriptor_header *) &obex_desc,
|
||||
(struct usb_descriptor_header *) &obex_cdc_union_desc,
|
||||
|
||||
(struct usb_descriptor_header *) &obex_data_nop_intf,
|
||||
(struct usb_descriptor_header *) &obex_data_intf,
|
||||
(struct usb_descriptor_header *) &obex_hs_ep_in_desc,
|
||||
(struct usb_descriptor_header *) &obex_hs_ep_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Full-Speed Support */
|
||||
|
||||
static struct usb_endpoint_descriptor obex_fs_ep_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor obex_fs_ep_out_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_function[] = {
|
||||
(struct usb_descriptor_header *) &obex_control_intf,
|
||||
(struct usb_descriptor_header *) &obex_cdc_header_desc,
|
||||
(struct usb_descriptor_header *) &obex_desc,
|
||||
(struct usb_descriptor_header *) &obex_cdc_union_desc,
|
||||
|
||||
(struct usb_descriptor_header *) &obex_data_nop_intf,
|
||||
(struct usb_descriptor_header *) &obex_data_intf,
|
||||
(struct usb_descriptor_header *) &obex_fs_ep_in_desc,
|
||||
(struct usb_descriptor_header *) &obex_fs_ep_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int obex_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_obex *obex = func_to_obex(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
if (intf == obex->ctrl_id) {
|
||||
if (alt != 0)
|
||||
goto fail;
|
||||
/* NOP */
|
||||
DBG(cdev, "reset obex ttyGS%d control\n", obex->port_num);
|
||||
|
||||
} else if (intf == obex->data_id) {
|
||||
if (alt > 1)
|
||||
goto fail;
|
||||
|
||||
if (obex->port.in->driver_data) {
|
||||
DBG(cdev, "reset obex ttyGS%d\n", obex->port_num);
|
||||
gserial_disconnect(&obex->port);
|
||||
}
|
||||
|
||||
if (!obex->port.in->desc || !obex->port.out->desc) {
|
||||
DBG(cdev, "init obex ttyGS%d\n", obex->port_num);
|
||||
if (config_ep_by_speed(cdev->gadget, f,
|
||||
obex->port.in) ||
|
||||
config_ep_by_speed(cdev->gadget, f,
|
||||
obex->port.out)) {
|
||||
obex->port.out->desc = NULL;
|
||||
obex->port.in->desc = NULL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (alt == 1) {
|
||||
DBG(cdev, "activate obex ttyGS%d\n", obex->port_num);
|
||||
gserial_connect(&obex->port, obex->port_num);
|
||||
}
|
||||
|
||||
} else
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int obex_get_alt(struct usb_function *f, unsigned intf)
|
||||
{
|
||||
struct f_obex *obex = func_to_obex(f);
|
||||
|
||||
if (intf == obex->ctrl_id)
|
||||
return 0;
|
||||
|
||||
return obex->port.in->driver_data ? 1 : 0;
|
||||
}
|
||||
|
||||
static void obex_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_obex *obex = func_to_obex(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "obex ttyGS%d disable\n", obex->port_num);
|
||||
gserial_disconnect(&obex->port);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void obex_connect(struct gserial *g)
|
||||
{
|
||||
struct f_obex *obex = port_to_obex(g);
|
||||
struct usb_composite_dev *cdev = g->func.config->cdev;
|
||||
int status;
|
||||
|
||||
if (!obex->can_activate)
|
||||
return;
|
||||
|
||||
status = usb_function_activate(&g->func);
|
||||
if (status)
|
||||
DBG(cdev, "obex ttyGS%d function activate --> %d\n",
|
||||
obex->port_num, status);
|
||||
}
|
||||
|
||||
static void obex_disconnect(struct gserial *g)
|
||||
{
|
||||
struct f_obex *obex = port_to_obex(g);
|
||||
struct usb_composite_dev *cdev = g->func.config->cdev;
|
||||
int status;
|
||||
|
||||
if (!obex->can_activate)
|
||||
return;
|
||||
|
||||
status = usb_function_deactivate(&g->func);
|
||||
if (status)
|
||||
DBG(cdev, "obex ttyGS%d function deactivate --> %d\n",
|
||||
obex->port_num, status);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* Some controllers can't support CDC OBEX ... */
|
||||
static inline bool can_support_obex(struct usb_configuration *c)
|
||||
{
|
||||
/* Since the first interface is a NOP, we can ignore the
|
||||
* issue of multi-interface support on most controllers.
|
||||
*
|
||||
* Altsettings are mandatory, however...
|
||||
*/
|
||||
if (!gadget_supports_altsettings(c->cdev->gadget))
|
||||
return false;
|
||||
|
||||
/* everything else is *probably* fine ... */
|
||||
return true;
|
||||
}
|
||||
|
||||
static int obex_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_obex *obex = func_to_obex(f);
|
||||
struct usb_string *us;
|
||||
int status;
|
||||
struct usb_ep *ep;
|
||||
|
||||
if (!can_support_obex(c))
|
||||
return -EINVAL;
|
||||
|
||||
us = usb_gstrings_attach(cdev, obex_strings,
|
||||
ARRAY_SIZE(obex_string_defs));
|
||||
if (IS_ERR(us))
|
||||
return PTR_ERR(us);
|
||||
obex_control_intf.iInterface = us[OBEX_CTRL_IDX].id;
|
||||
obex_data_nop_intf.iInterface = us[OBEX_DATA_IDX].id;
|
||||
obex_data_intf.iInterface = us[OBEX_DATA_IDX].id;
|
||||
|
||||
/* allocate instance-specific interface IDs, and patch descriptors */
|
||||
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
obex->ctrl_id = status;
|
||||
|
||||
obex_control_intf.bInterfaceNumber = status;
|
||||
obex_cdc_union_desc.bMasterInterface0 = status;
|
||||
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
obex->data_id = status;
|
||||
|
||||
obex_data_nop_intf.bInterfaceNumber = status;
|
||||
obex_data_intf.bInterfaceNumber = status;
|
||||
obex_cdc_union_desc.bSlaveInterface0 = status;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
|
||||
status = -ENODEV;
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_in_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
obex->port.in = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_out_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
obex->port.out = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* support all relevant hardware speeds... we expect that when
|
||||
* hardware is dual speed, all bulk-capable endpoints work at
|
||||
* both speeds
|
||||
*/
|
||||
|
||||
obex_hs_ep_in_desc.bEndpointAddress =
|
||||
obex_fs_ep_in_desc.bEndpointAddress;
|
||||
obex_hs_ep_out_desc.bEndpointAddress =
|
||||
obex_fs_ep_out_desc.bEndpointAddress;
|
||||
|
||||
status = usb_assign_descriptors(f, fs_function, hs_function, NULL);
|
||||
if (status)
|
||||
goto fail;
|
||||
|
||||
/* Avoid letting this gadget enumerate until the userspace
|
||||
* OBEX server is active.
|
||||
*/
|
||||
status = usb_function_deactivate(f);
|
||||
if (status < 0)
|
||||
WARNING(cdev, "obex ttyGS%d: can't prevent enumeration, %d\n",
|
||||
obex->port_num, status);
|
||||
else
|
||||
obex->can_activate = true;
|
||||
|
||||
|
||||
DBG(cdev, "obex ttyGS%d: %s speed IN/%s OUT/%s\n",
|
||||
obex->port_num,
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
obex->port.in->name, obex->port.out->name);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
usb_free_all_descriptors(f);
|
||||
/* we might as well release our claims on endpoints */
|
||||
if (obex->port.out)
|
||||
obex->port.out->driver_data = NULL;
|
||||
if (obex->port.in)
|
||||
obex->port.in->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct f_serial_opts,
|
||||
func_inst.group);
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR_STRUCT(f_serial_opts);
|
||||
static ssize_t f_obex_attr_show(struct config_item *item,
|
||||
struct configfs_attribute *attr,
|
||||
char *page)
|
||||
{
|
||||
struct f_serial_opts *opts = to_f_serial_opts(item);
|
||||
struct f_serial_opts_attribute *f_serial_opts_attr =
|
||||
container_of(attr, struct f_serial_opts_attribute, attr);
|
||||
ssize_t ret = 0;
|
||||
|
||||
if (f_serial_opts_attr->show)
|
||||
ret = f_serial_opts_attr->show(opts, page);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void obex_attr_release(struct config_item *item)
|
||||
{
|
||||
struct f_serial_opts *opts = to_f_serial_opts(item);
|
||||
|
||||
usb_put_function_instance(&opts->func_inst);
|
||||
}
|
||||
|
||||
static struct configfs_item_operations obex_item_ops = {
|
||||
.release = obex_attr_release,
|
||||
.show_attribute = f_obex_attr_show,
|
||||
};
|
||||
|
||||
static ssize_t f_obex_port_num_show(struct f_serial_opts *opts, char *page)
|
||||
{
|
||||
return sprintf(page, "%u\n", opts->port_num);
|
||||
}
|
||||
|
||||
static struct f_serial_opts_attribute f_obex_port_num =
|
||||
__CONFIGFS_ATTR_RO(port_num, f_obex_port_num_show);
|
||||
|
||||
static struct configfs_attribute *acm_attrs[] = {
|
||||
&f_obex_port_num.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct config_item_type obex_func_type = {
|
||||
.ct_item_ops = &obex_item_ops,
|
||||
.ct_attrs = acm_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void obex_free_inst(struct usb_function_instance *f)
|
||||
{
|
||||
struct f_serial_opts *opts;
|
||||
|
||||
opts = container_of(f, struct f_serial_opts, func_inst);
|
||||
gserial_free_line(opts->port_num);
|
||||
kfree(opts);
|
||||
}
|
||||
|
||||
static struct usb_function_instance *obex_alloc_inst(void)
|
||||
{
|
||||
struct f_serial_opts *opts;
|
||||
int ret;
|
||||
|
||||
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
||||
if (!opts)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
opts->func_inst.free_func_inst = obex_free_inst;
|
||||
ret = gserial_alloc_line(&opts->port_num);
|
||||
if (ret) {
|
||||
kfree(opts);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
config_group_init_type_name(&opts->func_inst.group, "",
|
||||
&obex_func_type);
|
||||
|
||||
return &opts->func_inst;
|
||||
}
|
||||
|
||||
static void obex_free(struct usb_function *f)
|
||||
{
|
||||
struct f_obex *obex;
|
||||
|
||||
obex = func_to_obex(f);
|
||||
kfree(obex);
|
||||
}
|
||||
|
||||
static void obex_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
usb_free_all_descriptors(f);
|
||||
}
|
||||
|
||||
static struct usb_function *obex_alloc(struct usb_function_instance *fi)
|
||||
{
|
||||
struct f_obex *obex;
|
||||
struct f_serial_opts *opts;
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
obex = kzalloc(sizeof(*obex), GFP_KERNEL);
|
||||
if (!obex)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
opts = container_of(fi, struct f_serial_opts, func_inst);
|
||||
|
||||
obex->port_num = opts->port_num;
|
||||
|
||||
obex->port.connect = obex_connect;
|
||||
obex->port.disconnect = obex_disconnect;
|
||||
|
||||
obex->port.func.name = "obex";
|
||||
/* descriptors are per-instance copies */
|
||||
obex->port.func.bind = obex_bind;
|
||||
obex->port.func.unbind = obex_unbind;
|
||||
obex->port.func.set_alt = obex_set_alt;
|
||||
obex->port.func.get_alt = obex_get_alt;
|
||||
obex->port.func.disable = obex_disable;
|
||||
obex->port.func.free_func = obex_free;
|
||||
|
||||
return &obex->port.func;
|
||||
}
|
||||
|
||||
DECLARE_USB_FUNCTION_INIT(obex, obex_alloc_inst, obex_alloc);
|
||||
MODULE_AUTHOR("Felipe Balbi");
|
||||
MODULE_LICENSE("GPL");
|
||||
758
drivers/usb/gadget/function/f_phonet.c
Normal file
758
drivers/usb/gadget/function/f_phonet.c
Normal file
|
|
@ -0,0 +1,758 @@
|
|||
/*
|
||||
* f_phonet.c -- USB CDC Phonet function
|
||||
*
|
||||
* Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
|
||||
*
|
||||
* Author: Rémi Denis-Courmont
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/mm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/if_phonet.h>
|
||||
#include <linux/if_arp.h>
|
||||
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usb/cdc.h>
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
#include "u_phonet.h"
|
||||
#include "u_ether.h"
|
||||
|
||||
#define PN_MEDIA_USB 0x1B
|
||||
#define MAXPACKET 512
|
||||
#if (PAGE_SIZE % MAXPACKET)
|
||||
#error MAXPACKET must divide PAGE_SIZE!
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
struct phonet_port {
|
||||
struct f_phonet *usb;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
struct f_phonet {
|
||||
struct usb_function function;
|
||||
struct {
|
||||
struct sk_buff *skb;
|
||||
spinlock_t lock;
|
||||
} rx;
|
||||
struct net_device *dev;
|
||||
struct usb_ep *in_ep, *out_ep;
|
||||
|
||||
struct usb_request *in_req;
|
||||
struct usb_request *out_reqv[0];
|
||||
};
|
||||
|
||||
static int phonet_rxq_size = 17;
|
||||
|
||||
static inline struct f_phonet *func_to_pn(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_phonet, function);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define USB_CDC_SUBCLASS_PHONET 0xfe
|
||||
#define USB_CDC_PHONET_TYPE 0xab
|
||||
|
||||
static struct usb_interface_descriptor
|
||||
pn_control_intf_desc = {
|
||||
.bLength = sizeof pn_control_intf_desc,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
/* .bInterfaceNumber = DYNAMIC, */
|
||||
.bInterfaceClass = USB_CLASS_COMM,
|
||||
.bInterfaceSubClass = USB_CDC_SUBCLASS_PHONET,
|
||||
};
|
||||
|
||||
static const struct usb_cdc_header_desc
|
||||
pn_header_desc = {
|
||||
.bLength = sizeof pn_header_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_HEADER_TYPE,
|
||||
.bcdCDC = cpu_to_le16(0x0110),
|
||||
};
|
||||
|
||||
static const struct usb_cdc_header_desc
|
||||
pn_phonet_desc = {
|
||||
.bLength = sizeof pn_phonet_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_PHONET_TYPE,
|
||||
.bcdCDC = cpu_to_le16(0x1505), /* ??? */
|
||||
};
|
||||
|
||||
static struct usb_cdc_union_desc
|
||||
pn_union_desc = {
|
||||
.bLength = sizeof pn_union_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_UNION_TYPE,
|
||||
|
||||
/* .bMasterInterface0 = DYNAMIC, */
|
||||
/* .bSlaveInterface0 = DYNAMIC, */
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor
|
||||
pn_data_nop_intf_desc = {
|
||||
.bLength = sizeof pn_data_nop_intf_desc,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
/* .bInterfaceNumber = DYNAMIC, */
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 0,
|
||||
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor
|
||||
pn_data_intf_desc = {
|
||||
.bLength = sizeof pn_data_intf_desc,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
/* .bInterfaceNumber = DYNAMIC, */
|
||||
.bAlternateSetting = 1,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor
|
||||
pn_fs_sink_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor
|
||||
pn_hs_sink_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(MAXPACKET),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor
|
||||
pn_fs_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
|
||||
pn_hs_source_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *fs_pn_function[] = {
|
||||
(struct usb_descriptor_header *) &pn_control_intf_desc,
|
||||
(struct usb_descriptor_header *) &pn_header_desc,
|
||||
(struct usb_descriptor_header *) &pn_phonet_desc,
|
||||
(struct usb_descriptor_header *) &pn_union_desc,
|
||||
(struct usb_descriptor_header *) &pn_data_nop_intf_desc,
|
||||
(struct usb_descriptor_header *) &pn_data_intf_desc,
|
||||
(struct usb_descriptor_header *) &pn_fs_sink_desc,
|
||||
(struct usb_descriptor_header *) &pn_fs_source_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *hs_pn_function[] = {
|
||||
(struct usb_descriptor_header *) &pn_control_intf_desc,
|
||||
(struct usb_descriptor_header *) &pn_header_desc,
|
||||
(struct usb_descriptor_header *) &pn_phonet_desc,
|
||||
(struct usb_descriptor_header *) &pn_union_desc,
|
||||
(struct usb_descriptor_header *) &pn_data_nop_intf_desc,
|
||||
(struct usb_descriptor_header *) &pn_data_intf_desc,
|
||||
(struct usb_descriptor_header *) &pn_hs_sink_desc,
|
||||
(struct usb_descriptor_header *) &pn_hs_source_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int pn_net_open(struct net_device *dev)
|
||||
{
|
||||
netif_wake_queue(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pn_net_close(struct net_device *dev)
|
||||
{
|
||||
netif_stop_queue(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pn_tx_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_phonet *fp = ep->driver_data;
|
||||
struct net_device *dev = fp->dev;
|
||||
struct sk_buff *skb = req->context;
|
||||
|
||||
switch (req->status) {
|
||||
case 0:
|
||||
dev->stats.tx_packets++;
|
||||
dev->stats.tx_bytes += skb->len;
|
||||
break;
|
||||
|
||||
case -ESHUTDOWN: /* disconnected */
|
||||
case -ECONNRESET: /* disabled */
|
||||
dev->stats.tx_aborted_errors++;
|
||||
default:
|
||||
dev->stats.tx_errors++;
|
||||
}
|
||||
|
||||
dev_kfree_skb_any(skb);
|
||||
netif_wake_queue(dev);
|
||||
}
|
||||
|
||||
static int pn_net_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct phonet_port *port = netdev_priv(dev);
|
||||
struct f_phonet *fp;
|
||||
struct usb_request *req;
|
||||
unsigned long flags;
|
||||
|
||||
if (skb->protocol != htons(ETH_P_PHONET))
|
||||
goto out;
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
fp = port->usb;
|
||||
if (unlikely(!fp)) /* race with carrier loss */
|
||||
goto out_unlock;
|
||||
|
||||
req = fp->in_req;
|
||||
req->buf = skb->data;
|
||||
req->length = skb->len;
|
||||
req->complete = pn_tx_complete;
|
||||
req->zero = 1;
|
||||
req->context = skb;
|
||||
|
||||
if (unlikely(usb_ep_queue(fp->in_ep, req, GFP_ATOMIC)))
|
||||
goto out_unlock;
|
||||
|
||||
netif_stop_queue(dev);
|
||||
skb = NULL;
|
||||
|
||||
out_unlock:
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
out:
|
||||
if (unlikely(skb)) {
|
||||
dev_kfree_skb(skb);
|
||||
dev->stats.tx_dropped++;
|
||||
}
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
static int pn_net_mtu(struct net_device *dev, int new_mtu)
|
||||
{
|
||||
if ((new_mtu < PHONET_MIN_MTU) || (new_mtu > PHONET_MAX_MTU))
|
||||
return -EINVAL;
|
||||
dev->mtu = new_mtu;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct net_device_ops pn_netdev_ops = {
|
||||
.ndo_open = pn_net_open,
|
||||
.ndo_stop = pn_net_close,
|
||||
.ndo_start_xmit = pn_net_xmit,
|
||||
.ndo_change_mtu = pn_net_mtu,
|
||||
};
|
||||
|
||||
static void pn_net_setup(struct net_device *dev)
|
||||
{
|
||||
dev->features = 0;
|
||||
dev->type = ARPHRD_PHONET;
|
||||
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
|
||||
dev->mtu = PHONET_DEV_MTU;
|
||||
dev->hard_header_len = 1;
|
||||
dev->dev_addr[0] = PN_MEDIA_USB;
|
||||
dev->addr_len = 1;
|
||||
dev->tx_queue_len = 1;
|
||||
|
||||
dev->netdev_ops = &pn_netdev_ops;
|
||||
dev->destructor = free_netdev;
|
||||
dev->header_ops = &phonet_header_ops;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Queue buffer for data from the host
|
||||
*/
|
||||
static int
|
||||
pn_rx_submit(struct f_phonet *fp, struct usb_request *req, gfp_t gfp_flags)
|
||||
{
|
||||
struct page *page;
|
||||
int err;
|
||||
|
||||
page = __skb_alloc_page(gfp_flags | __GFP_NOMEMALLOC, NULL);
|
||||
if (!page)
|
||||
return -ENOMEM;
|
||||
|
||||
req->buf = page_address(page);
|
||||
req->length = PAGE_SIZE;
|
||||
req->context = page;
|
||||
|
||||
err = usb_ep_queue(fp->out_ep, req, gfp_flags);
|
||||
if (unlikely(err))
|
||||
put_page(page);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void pn_rx_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_phonet *fp = ep->driver_data;
|
||||
struct net_device *dev = fp->dev;
|
||||
struct page *page = req->context;
|
||||
struct sk_buff *skb;
|
||||
unsigned long flags;
|
||||
int status = req->status;
|
||||
|
||||
switch (status) {
|
||||
case 0:
|
||||
spin_lock_irqsave(&fp->rx.lock, flags);
|
||||
skb = fp->rx.skb;
|
||||
if (!skb)
|
||||
skb = fp->rx.skb = netdev_alloc_skb(dev, 12);
|
||||
if (req->actual < req->length) /* Last fragment */
|
||||
fp->rx.skb = NULL;
|
||||
spin_unlock_irqrestore(&fp->rx.lock, flags);
|
||||
|
||||
if (unlikely(!skb))
|
||||
break;
|
||||
|
||||
if (skb->len == 0) { /* First fragment */
|
||||
skb->protocol = htons(ETH_P_PHONET);
|
||||
skb_reset_mac_header(skb);
|
||||
/* Can't use pskb_pull() on page in IRQ */
|
||||
memcpy(skb_put(skb, 1), page_address(page), 1);
|
||||
}
|
||||
|
||||
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page,
|
||||
skb->len <= 1, req->actual, PAGE_SIZE);
|
||||
page = NULL;
|
||||
|
||||
if (req->actual < req->length) { /* Last fragment */
|
||||
skb->dev = dev;
|
||||
dev->stats.rx_packets++;
|
||||
dev->stats.rx_bytes += skb->len;
|
||||
|
||||
netif_rx(skb);
|
||||
}
|
||||
break;
|
||||
|
||||
/* Do not resubmit in these cases: */
|
||||
case -ESHUTDOWN: /* disconnect */
|
||||
case -ECONNABORTED: /* hw reset */
|
||||
case -ECONNRESET: /* dequeued (unlink or netif down) */
|
||||
req = NULL;
|
||||
break;
|
||||
|
||||
/* Do resubmit in these cases: */
|
||||
case -EOVERFLOW: /* request buffer overflow */
|
||||
dev->stats.rx_over_errors++;
|
||||
default:
|
||||
dev->stats.rx_errors++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (page)
|
||||
put_page(page);
|
||||
if (req)
|
||||
pn_rx_submit(fp, req, GFP_ATOMIC | __GFP_COLD);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void __pn_reset(struct usb_function *f)
|
||||
{
|
||||
struct f_phonet *fp = func_to_pn(f);
|
||||
struct net_device *dev = fp->dev;
|
||||
struct phonet_port *port = netdev_priv(dev);
|
||||
|
||||
netif_carrier_off(dev);
|
||||
port->usb = NULL;
|
||||
|
||||
usb_ep_disable(fp->out_ep);
|
||||
usb_ep_disable(fp->in_ep);
|
||||
if (fp->rx.skb) {
|
||||
dev_kfree_skb_irq(fp->rx.skb);
|
||||
fp->rx.skb = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int pn_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_phonet *fp = func_to_pn(f);
|
||||
struct usb_gadget *gadget = fp->function.config->cdev->gadget;
|
||||
|
||||
if (intf == pn_control_intf_desc.bInterfaceNumber)
|
||||
/* control interface, no altsetting */
|
||||
return (alt > 0) ? -EINVAL : 0;
|
||||
|
||||
if (intf == pn_data_intf_desc.bInterfaceNumber) {
|
||||
struct net_device *dev = fp->dev;
|
||||
struct phonet_port *port = netdev_priv(dev);
|
||||
|
||||
/* data intf (0: inactive, 1: active) */
|
||||
if (alt > 1)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock(&port->lock);
|
||||
__pn_reset(f);
|
||||
if (alt == 1) {
|
||||
int i;
|
||||
|
||||
if (config_ep_by_speed(gadget, f, fp->in_ep) ||
|
||||
config_ep_by_speed(gadget, f, fp->out_ep)) {
|
||||
fp->in_ep->desc = NULL;
|
||||
fp->out_ep->desc = NULL;
|
||||
spin_unlock(&port->lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
usb_ep_enable(fp->out_ep);
|
||||
usb_ep_enable(fp->in_ep);
|
||||
|
||||
port->usb = fp;
|
||||
fp->out_ep->driver_data = fp;
|
||||
fp->in_ep->driver_data = fp;
|
||||
|
||||
netif_carrier_on(dev);
|
||||
for (i = 0; i < phonet_rxq_size; i++)
|
||||
pn_rx_submit(fp, fp->out_reqv[i], GFP_ATOMIC | __GFP_COLD);
|
||||
}
|
||||
spin_unlock(&port->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int pn_get_alt(struct usb_function *f, unsigned intf)
|
||||
{
|
||||
struct f_phonet *fp = func_to_pn(f);
|
||||
|
||||
if (intf == pn_control_intf_desc.bInterfaceNumber)
|
||||
return 0;
|
||||
|
||||
if (intf == pn_data_intf_desc.bInterfaceNumber) {
|
||||
struct phonet_port *port = netdev_priv(fp->dev);
|
||||
u8 alt;
|
||||
|
||||
spin_lock(&port->lock);
|
||||
alt = port->usb != NULL;
|
||||
spin_unlock(&port->lock);
|
||||
return alt;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void pn_disconnect(struct usb_function *f)
|
||||
{
|
||||
struct f_phonet *fp = func_to_pn(f);
|
||||
struct phonet_port *port = netdev_priv(fp->dev);
|
||||
unsigned long flags;
|
||||
|
||||
/* remain disabled until set_alt */
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
__pn_reset(f);
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int pn_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct usb_gadget *gadget = cdev->gadget;
|
||||
struct f_phonet *fp = func_to_pn(f);
|
||||
struct usb_ep *ep;
|
||||
int status, i;
|
||||
|
||||
struct f_phonet_opts *phonet_opts;
|
||||
|
||||
phonet_opts = container_of(f->fi, struct f_phonet_opts, func_inst);
|
||||
|
||||
/*
|
||||
* in drivers/usb/gadget/configfs.c:configfs_composite_bind()
|
||||
* configurations are bound in sequence with list_for_each_entry,
|
||||
* in each configuration its functions are bound in sequence
|
||||
* with list_for_each_entry, so we assume no race condition
|
||||
* with regard to phonet_opts->bound access
|
||||
*/
|
||||
if (!phonet_opts->bound) {
|
||||
gphonet_set_gadget(phonet_opts->net, gadget);
|
||||
status = gphonet_register_netdev(phonet_opts->net);
|
||||
if (status)
|
||||
return status;
|
||||
phonet_opts->bound = true;
|
||||
}
|
||||
|
||||
/* Reserve interface IDs */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto err;
|
||||
pn_control_intf_desc.bInterfaceNumber = status;
|
||||
pn_union_desc.bMasterInterface0 = status;
|
||||
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto err;
|
||||
pn_data_nop_intf_desc.bInterfaceNumber = status;
|
||||
pn_data_intf_desc.bInterfaceNumber = status;
|
||||
pn_union_desc.bSlaveInterface0 = status;
|
||||
|
||||
/* Reserve endpoints */
|
||||
status = -ENODEV;
|
||||
ep = usb_ep_autoconfig(gadget, &pn_fs_sink_desc);
|
||||
if (!ep)
|
||||
goto err;
|
||||
fp->out_ep = ep;
|
||||
ep->driver_data = fp; /* Claim */
|
||||
|
||||
ep = usb_ep_autoconfig(gadget, &pn_fs_source_desc);
|
||||
if (!ep)
|
||||
goto err;
|
||||
fp->in_ep = ep;
|
||||
ep->driver_data = fp; /* Claim */
|
||||
|
||||
pn_hs_sink_desc.bEndpointAddress = pn_fs_sink_desc.bEndpointAddress;
|
||||
pn_hs_source_desc.bEndpointAddress = pn_fs_source_desc.bEndpointAddress;
|
||||
|
||||
/* Do not try to bind Phonet twice... */
|
||||
status = usb_assign_descriptors(f, fs_pn_function, hs_pn_function,
|
||||
NULL);
|
||||
if (status)
|
||||
goto err;
|
||||
|
||||
/* Incoming USB requests */
|
||||
status = -ENOMEM;
|
||||
for (i = 0; i < phonet_rxq_size; i++) {
|
||||
struct usb_request *req;
|
||||
|
||||
req = usb_ep_alloc_request(fp->out_ep, GFP_KERNEL);
|
||||
if (!req)
|
||||
goto err_req;
|
||||
|
||||
req->complete = pn_rx_complete;
|
||||
fp->out_reqv[i] = req;
|
||||
}
|
||||
|
||||
/* Outgoing USB requests */
|
||||
fp->in_req = usb_ep_alloc_request(fp->in_ep, GFP_KERNEL);
|
||||
if (!fp->in_req)
|
||||
goto err_req;
|
||||
|
||||
INFO(cdev, "USB CDC Phonet function\n");
|
||||
INFO(cdev, "using %s, OUT %s, IN %s\n", cdev->gadget->name,
|
||||
fp->out_ep->name, fp->in_ep->name);
|
||||
return 0;
|
||||
|
||||
err_req:
|
||||
for (i = 0; i < phonet_rxq_size && fp->out_reqv[i]; i++)
|
||||
usb_ep_free_request(fp->out_ep, fp->out_reqv[i]);
|
||||
err:
|
||||
usb_free_all_descriptors(f);
|
||||
if (fp->out_ep)
|
||||
fp->out_ep->driver_data = NULL;
|
||||
if (fp->in_ep)
|
||||
fp->in_ep->driver_data = NULL;
|
||||
ERROR(cdev, "USB CDC Phonet: cannot autoconfigure\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
static inline struct f_phonet_opts *to_f_phonet_opts(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct f_phonet_opts,
|
||||
func_inst.group);
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR_STRUCT(f_phonet_opts);
|
||||
static ssize_t f_phonet_attr_show(struct config_item *item,
|
||||
struct configfs_attribute *attr,
|
||||
char *page)
|
||||
{
|
||||
struct f_phonet_opts *opts = to_f_phonet_opts(item);
|
||||
struct f_phonet_opts_attribute *f_phonet_opts_attr =
|
||||
container_of(attr, struct f_phonet_opts_attribute, attr);
|
||||
ssize_t ret = 0;
|
||||
|
||||
if (f_phonet_opts_attr->show)
|
||||
ret = f_phonet_opts_attr->show(opts, page);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void phonet_attr_release(struct config_item *item)
|
||||
{
|
||||
struct f_phonet_opts *opts = to_f_phonet_opts(item);
|
||||
|
||||
usb_put_function_instance(&opts->func_inst);
|
||||
}
|
||||
|
||||
static struct configfs_item_operations phonet_item_ops = {
|
||||
.release = phonet_attr_release,
|
||||
.show_attribute = f_phonet_attr_show,
|
||||
};
|
||||
|
||||
static ssize_t f_phonet_ifname_show(struct f_phonet_opts *opts, char *page)
|
||||
{
|
||||
return gether_get_ifname(opts->net, page, PAGE_SIZE);
|
||||
}
|
||||
|
||||
static struct f_phonet_opts_attribute f_phonet_ifname =
|
||||
__CONFIGFS_ATTR_RO(ifname, f_phonet_ifname_show);
|
||||
|
||||
static struct configfs_attribute *phonet_attrs[] = {
|
||||
&f_phonet_ifname.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct config_item_type phonet_func_type = {
|
||||
.ct_item_ops = &phonet_item_ops,
|
||||
.ct_attrs = phonet_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void phonet_free_inst(struct usb_function_instance *f)
|
||||
{
|
||||
struct f_phonet_opts *opts;
|
||||
|
||||
opts = container_of(f, struct f_phonet_opts, func_inst);
|
||||
if (opts->bound)
|
||||
gphonet_cleanup(opts->net);
|
||||
else
|
||||
free_netdev(opts->net);
|
||||
kfree(opts);
|
||||
}
|
||||
|
||||
static struct usb_function_instance *phonet_alloc_inst(void)
|
||||
{
|
||||
struct f_phonet_opts *opts;
|
||||
|
||||
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
||||
if (!opts)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
opts->func_inst.free_func_inst = phonet_free_inst;
|
||||
opts->net = gphonet_setup_default();
|
||||
if (IS_ERR(opts->net)) {
|
||||
struct net_device *net = opts->net;
|
||||
kfree(opts);
|
||||
return ERR_CAST(net);
|
||||
}
|
||||
|
||||
config_group_init_type_name(&opts->func_inst.group, "",
|
||||
&phonet_func_type);
|
||||
|
||||
return &opts->func_inst;
|
||||
}
|
||||
|
||||
static void phonet_free(struct usb_function *f)
|
||||
{
|
||||
struct f_phonet *phonet;
|
||||
|
||||
phonet = func_to_pn(f);
|
||||
kfree(phonet);
|
||||
}
|
||||
|
||||
static void pn_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct f_phonet *fp = func_to_pn(f);
|
||||
int i;
|
||||
|
||||
/* We are already disconnected */
|
||||
if (fp->in_req)
|
||||
usb_ep_free_request(fp->in_ep, fp->in_req);
|
||||
for (i = 0; i < phonet_rxq_size; i++)
|
||||
if (fp->out_reqv[i])
|
||||
usb_ep_free_request(fp->out_ep, fp->out_reqv[i]);
|
||||
|
||||
usb_free_all_descriptors(f);
|
||||
}
|
||||
|
||||
static struct usb_function *phonet_alloc(struct usb_function_instance *fi)
|
||||
{
|
||||
struct f_phonet *fp;
|
||||
struct f_phonet_opts *opts;
|
||||
int size;
|
||||
|
||||
size = sizeof(*fp) + (phonet_rxq_size * sizeof(struct usb_request *));
|
||||
fp = kzalloc(size, GFP_KERNEL);
|
||||
if (!fp)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
opts = container_of(fi, struct f_phonet_opts, func_inst);
|
||||
|
||||
fp->dev = opts->net;
|
||||
fp->function.name = "phonet";
|
||||
fp->function.bind = pn_bind;
|
||||
fp->function.unbind = pn_unbind;
|
||||
fp->function.set_alt = pn_set_alt;
|
||||
fp->function.get_alt = pn_get_alt;
|
||||
fp->function.disable = pn_disconnect;
|
||||
fp->function.free_func = phonet_free;
|
||||
spin_lock_init(&fp->rx.lock);
|
||||
|
||||
return &fp->function;
|
||||
}
|
||||
|
||||
struct net_device *gphonet_setup_default(void)
|
||||
{
|
||||
struct net_device *dev;
|
||||
struct phonet_port *port;
|
||||
|
||||
/* Create net device */
|
||||
dev = alloc_netdev(sizeof(*port), "upnlink%d", pn_net_setup);
|
||||
if (!dev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
port = netdev_priv(dev);
|
||||
spin_lock_init(&port->lock);
|
||||
netif_carrier_off(dev);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
void gphonet_set_gadget(struct net_device *net, struct usb_gadget *g)
|
||||
{
|
||||
SET_NETDEV_DEV(net, &g->dev);
|
||||
}
|
||||
|
||||
int gphonet_register_netdev(struct net_device *net)
|
||||
{
|
||||
int status;
|
||||
|
||||
status = register_netdev(net);
|
||||
if (status)
|
||||
free_netdev(net);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void gphonet_cleanup(struct net_device *dev)
|
||||
{
|
||||
unregister_netdev(dev);
|
||||
}
|
||||
|
||||
DECLARE_USB_FUNCTION_INIT(phonet, phonet_alloc_inst, phonet_alloc);
|
||||
MODULE_AUTHOR("Rémi Denis-Courmont");
|
||||
MODULE_LICENSE("GPL");
|
||||
1035
drivers/usb/gadget/function/f_rndis.c
Normal file
1035
drivers/usb/gadget/function/f_rndis.c
Normal file
File diff suppressed because it is too large
Load diff
385
drivers/usb/gadget/function/f_serial.c
Normal file
385
drivers/usb/gadget/function/f_serial.c
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
* f_serial.c - generic USB serial function driver
|
||||
*
|
||||
* Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
|
||||
* Copyright (C) 2008 by David Brownell
|
||||
* Copyright (C) 2008 by Nokia Corporation
|
||||
*
|
||||
* This software is distributed under the terms of the GNU General
|
||||
* Public License ("GPL") as published by the Free Software Foundation,
|
||||
* either version 2 of that License or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "u_serial.h"
|
||||
#include "gadget_chips.h"
|
||||
|
||||
|
||||
/*
|
||||
* This function packages a simple "generic serial" port with no real
|
||||
* control mechanisms, just raw data transfer over two bulk endpoints.
|
||||
*
|
||||
* Because it's not standardized, this isn't as interoperable as the
|
||||
* CDC ACM driver. However, for many purposes it's just as functional
|
||||
* if you can arrange appropriate host side drivers.
|
||||
*/
|
||||
|
||||
struct f_gser {
|
||||
struct gserial port;
|
||||
u8 data_id;
|
||||
u8 port_num;
|
||||
};
|
||||
|
||||
static inline struct f_gser *func_to_gser(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_gser, port.func);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* interface descriptor: */
|
||||
|
||||
static struct usb_interface_descriptor gser_interface_desc = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
||||
.bInterfaceSubClass = 0,
|
||||
.bInterfaceProtocol = 0,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor gser_fs_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor gser_fs_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *gser_fs_function[] = {
|
||||
(struct usb_descriptor_header *) &gser_interface_desc,
|
||||
(struct usb_descriptor_header *) &gser_fs_in_desc,
|
||||
(struct usb_descriptor_header *) &gser_fs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor gser_hs_in_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 gser_hs_out_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 *gser_hs_function[] = {
|
||||
(struct usb_descriptor_header *) &gser_interface_desc,
|
||||
(struct usb_descriptor_header *) &gser_hs_in_desc,
|
||||
(struct usb_descriptor_header *) &gser_hs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor gser_ss_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(1024),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor gser_ss_out_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 gser_ss_bulk_comp_desc = {
|
||||
.bLength = sizeof gser_ss_bulk_comp_desc,
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *gser_ss_function[] = {
|
||||
(struct usb_descriptor_header *) &gser_interface_desc,
|
||||
(struct usb_descriptor_header *) &gser_ss_in_desc,
|
||||
(struct usb_descriptor_header *) &gser_ss_bulk_comp_desc,
|
||||
(struct usb_descriptor_header *) &gser_ss_out_desc,
|
||||
(struct usb_descriptor_header *) &gser_ss_bulk_comp_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* string descriptors: */
|
||||
|
||||
static struct usb_string gser_string_defs[] = {
|
||||
[0].s = "Generic Serial",
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings gser_string_table = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = gser_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *gser_strings[] = {
|
||||
&gser_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_gser *gser = func_to_gser(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
/* we know alt == 0, so this is an activation or a reset */
|
||||
|
||||
if (gser->port.in->driver_data) {
|
||||
DBG(cdev, "reset generic ttyGS%d\n", gser->port_num);
|
||||
gserial_disconnect(&gser->port);
|
||||
}
|
||||
if (!gser->port.in->desc || !gser->port.out->desc) {
|
||||
DBG(cdev, "activate generic ttyGS%d\n", gser->port_num);
|
||||
if (config_ep_by_speed(cdev->gadget, f, gser->port.in) ||
|
||||
config_ep_by_speed(cdev->gadget, f, gser->port.out)) {
|
||||
gser->port.in->desc = NULL;
|
||||
gser->port.out->desc = NULL;
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
gserial_connect(&gser->port, gser->port_num);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gser_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_gser *gser = func_to_gser(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "generic ttyGS%d deactivated\n", gser->port_num);
|
||||
gserial_disconnect(&gser->port);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* serial function driver setup/binding */
|
||||
|
||||
static int gser_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_gser *gser = func_to_gser(f);
|
||||
int status;
|
||||
struct usb_ep *ep;
|
||||
|
||||
/* REVISIT might want instance-specific strings to help
|
||||
* distinguish instances ...
|
||||
*/
|
||||
|
||||
/* maybe allocate device-global string ID */
|
||||
if (gser_string_defs[0].id == 0) {
|
||||
status = usb_string_id(c->cdev);
|
||||
if (status < 0)
|
||||
return status;
|
||||
gser_string_defs[0].id = status;
|
||||
}
|
||||
|
||||
/* allocate instance-specific interface IDs */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
gser->data_id = status;
|
||||
gser_interface_desc.bInterfaceNumber = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_in_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
gser->port.in = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_out_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
gser->port.out = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* support all relevant hardware speeds... we expect that when
|
||||
* hardware is dual speed, all bulk-capable endpoints work at
|
||||
* both speeds
|
||||
*/
|
||||
gser_hs_in_desc.bEndpointAddress = gser_fs_in_desc.bEndpointAddress;
|
||||
gser_hs_out_desc.bEndpointAddress = gser_fs_out_desc.bEndpointAddress;
|
||||
|
||||
gser_ss_in_desc.bEndpointAddress = gser_fs_in_desc.bEndpointAddress;
|
||||
gser_ss_out_desc.bEndpointAddress = gser_fs_out_desc.bEndpointAddress;
|
||||
|
||||
status = usb_assign_descriptors(f, gser_fs_function, gser_hs_function,
|
||||
gser_ss_function);
|
||||
if (status)
|
||||
goto fail;
|
||||
DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n",
|
||||
gser->port_num,
|
||||
gadget_is_superspeed(c->cdev->gadget) ? "super" :
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
gser->port.in->name, gser->port.out->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
/* we might as well release our claims on endpoints */
|
||||
if (gser->port.out)
|
||||
gser->port.out->driver_data = NULL;
|
||||
if (gser->port.in)
|
||||
gser->port.in->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct f_serial_opts,
|
||||
func_inst.group);
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR_STRUCT(f_serial_opts);
|
||||
static ssize_t f_serial_attr_show(struct config_item *item,
|
||||
struct configfs_attribute *attr,
|
||||
char *page)
|
||||
{
|
||||
struct f_serial_opts *opts = to_f_serial_opts(item);
|
||||
struct f_serial_opts_attribute *f_serial_opts_attr =
|
||||
container_of(attr, struct f_serial_opts_attribute, attr);
|
||||
ssize_t ret = 0;
|
||||
|
||||
if (f_serial_opts_attr->show)
|
||||
ret = f_serial_opts_attr->show(opts, page);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void serial_attr_release(struct config_item *item)
|
||||
{
|
||||
struct f_serial_opts *opts = to_f_serial_opts(item);
|
||||
|
||||
usb_put_function_instance(&opts->func_inst);
|
||||
}
|
||||
|
||||
static struct configfs_item_operations serial_item_ops = {
|
||||
.release = serial_attr_release,
|
||||
.show_attribute = f_serial_attr_show,
|
||||
};
|
||||
|
||||
static ssize_t f_serial_port_num_show(struct f_serial_opts *opts, char *page)
|
||||
{
|
||||
return sprintf(page, "%u\n", opts->port_num);
|
||||
}
|
||||
|
||||
static struct f_serial_opts_attribute f_serial_port_num =
|
||||
__CONFIGFS_ATTR_RO(port_num, f_serial_port_num_show);
|
||||
|
||||
static struct configfs_attribute *acm_attrs[] = {
|
||||
&f_serial_port_num.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct config_item_type serial_func_type = {
|
||||
.ct_item_ops = &serial_item_ops,
|
||||
.ct_attrs = acm_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void gser_free_inst(struct usb_function_instance *f)
|
||||
{
|
||||
struct f_serial_opts *opts;
|
||||
|
||||
opts = container_of(f, struct f_serial_opts, func_inst);
|
||||
gserial_free_line(opts->port_num);
|
||||
kfree(opts);
|
||||
}
|
||||
|
||||
static struct usb_function_instance *gser_alloc_inst(void)
|
||||
{
|
||||
struct f_serial_opts *opts;
|
||||
int ret;
|
||||
|
||||
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
||||
if (!opts)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
opts->func_inst.free_func_inst = gser_free_inst;
|
||||
ret = gserial_alloc_line(&opts->port_num);
|
||||
if (ret) {
|
||||
kfree(opts);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
config_group_init_type_name(&opts->func_inst.group, "",
|
||||
&serial_func_type);
|
||||
|
||||
return &opts->func_inst;
|
||||
}
|
||||
|
||||
static void gser_free(struct usb_function *f)
|
||||
{
|
||||
struct f_gser *serial;
|
||||
|
||||
serial = func_to_gser(f);
|
||||
kfree(serial);
|
||||
}
|
||||
|
||||
static void gser_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
usb_free_all_descriptors(f);
|
||||
}
|
||||
|
||||
static struct usb_function *gser_alloc(struct usb_function_instance *fi)
|
||||
{
|
||||
struct f_gser *gser;
|
||||
struct f_serial_opts *opts;
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
gser = kzalloc(sizeof(*gser), GFP_KERNEL);
|
||||
if (!gser)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
opts = container_of(fi, struct f_serial_opts, func_inst);
|
||||
|
||||
gser->port_num = opts->port_num;
|
||||
|
||||
gser->port.func.name = "gser";
|
||||
gser->port.func.strings = gser_strings;
|
||||
gser->port.func.bind = gser_bind;
|
||||
gser->port.func.unbind = gser_unbind;
|
||||
gser->port.func.set_alt = gser_set_alt;
|
||||
gser->port.func.disable = gser_disable;
|
||||
gser->port.func.free_func = gser_free;
|
||||
|
||||
return &gser->port.func;
|
||||
}
|
||||
|
||||
DECLARE_USB_FUNCTION_INIT(gser, gser_alloc_inst, gser_alloc);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Al Borchers");
|
||||
MODULE_AUTHOR("David Brownell");
|
||||
1247
drivers/usb/gadget/function/f_sourcesink.c
Normal file
1247
drivers/usb/gadget/function/f_sourcesink.c
Normal file
File diff suppressed because it is too large
Load diff
519
drivers/usb/gadget/function/f_subset.c
Normal file
519
drivers/usb/gadget/function/f_subset.c
Normal file
|
|
@ -0,0 +1,519 @@
|
|||
/*
|
||||
* f_subset.c -- "CDC Subset" Ethernet link function driver
|
||||
*
|
||||
* Copyright (C) 2003-2005,2008 David Brownell
|
||||
* Copyright (C) 2008 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.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/etherdevice.h>
|
||||
|
||||
#include "u_ether.h"
|
||||
#include "u_ether_configfs.h"
|
||||
#include "u_gether.h"
|
||||
|
||||
/*
|
||||
* This function packages a simple "CDC Subset" Ethernet port with no real
|
||||
* control mechanisms; just raw data transfer over two bulk endpoints.
|
||||
* The data transfer model is exactly that of CDC Ethernet, which is
|
||||
* why we call it the "CDC Subset".
|
||||
*
|
||||
* Because it's not standardized, this has some interoperability issues.
|
||||
* They mostly relate to driver binding, since the data transfer model is
|
||||
* so simple (CDC Ethernet). The original versions of this protocol used
|
||||
* specific product/vendor IDs: byteswapped IDs for Digital Equipment's
|
||||
* SA-1100 "Itsy" board, which could run Linux 2.4 kernels and supported
|
||||
* daughtercards with USB peripheral connectors. (It was used more often
|
||||
* with other boards, using the Itsy identifiers.) Linux hosts recognized
|
||||
* this with CONFIG_USB_ARMLINUX; these devices have only one configuration
|
||||
* and one interface.
|
||||
*
|
||||
* At some point, MCCI defined a (nonconformant) CDC MDLM variant called
|
||||
* "SAFE", which happens to have a mode which is identical to the "CDC
|
||||
* Subset" in terms of data transfer and lack of control model. This was
|
||||
* adopted by later Sharp Zaurus models, and by some other software which
|
||||
* Linux hosts recognize with CONFIG_USB_NET_ZAURUS.
|
||||
*
|
||||
* Because Microsoft's RNDIS drivers are far from robust, we added a few
|
||||
* descriptors to the CDC Subset code, making this code look like a SAFE
|
||||
* implementation. This lets you use MCCI's host side MS-Windows drivers
|
||||
* if you get fed up with RNDIS. It also makes it easier for composite
|
||||
* drivers to work, since they can use class based binding instead of
|
||||
* caring about specific product and vendor IDs.
|
||||
*/
|
||||
|
||||
struct f_gether {
|
||||
struct gether port;
|
||||
|
||||
char ethaddr[14];
|
||||
};
|
||||
|
||||
static inline struct f_gether *func_to_geth(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_gether, port.func);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* "Simple" CDC-subset option is a simple vendor-neutral model that most
|
||||
* full speed controllers can handle: one interface, two bulk endpoints.
|
||||
* To assist host side drivers, we fancy it up a bit, and add descriptors so
|
||||
* some host side drivers will understand it as a "SAFE" variant.
|
||||
*
|
||||
* "SAFE" loosely follows CDC WMC MDLM, violating the spec in various ways.
|
||||
* Data endpoints live in the control interface, there's no data interface.
|
||||
* And it's not used to talk to a cell phone radio.
|
||||
*/
|
||||
|
||||
/* interface descriptor: */
|
||||
|
||||
static struct usb_interface_descriptor subset_data_intf = {
|
||||
.bLength = sizeof subset_data_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_COMM,
|
||||
.bInterfaceSubClass = USB_CDC_SUBCLASS_MDLM,
|
||||
.bInterfaceProtocol = 0,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_cdc_header_desc mdlm_header_desc = {
|
||||
.bLength = sizeof mdlm_header_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_HEADER_TYPE,
|
||||
|
||||
.bcdCDC = cpu_to_le16(0x0110),
|
||||
};
|
||||
|
||||
static struct usb_cdc_mdlm_desc mdlm_desc = {
|
||||
.bLength = sizeof mdlm_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_MDLM_TYPE,
|
||||
|
||||
.bcdVersion = cpu_to_le16(0x0100),
|
||||
.bGUID = {
|
||||
0x5d, 0x34, 0xcf, 0x66, 0x11, 0x18, 0x11, 0xd6,
|
||||
0xa2, 0x1a, 0x00, 0x01, 0x02, 0xca, 0x9a, 0x7f,
|
||||
},
|
||||
};
|
||||
|
||||
/* since "usb_cdc_mdlm_detail_desc" is a variable length structure, we
|
||||
* can't really use its struct. All we do here is say that we're using
|
||||
* the submode of "SAFE" which directly matches the CDC Subset.
|
||||
*/
|
||||
static u8 mdlm_detail_desc[] = {
|
||||
6,
|
||||
USB_DT_CS_INTERFACE,
|
||||
USB_CDC_MDLM_DETAIL_TYPE,
|
||||
|
||||
0, /* "SAFE" */
|
||||
0, /* network control capabilities (none) */
|
||||
0, /* network data capabilities ("raw" encapsulation) */
|
||||
};
|
||||
|
||||
static struct usb_cdc_ether_desc ether_desc = {
|
||||
.bLength = sizeof ether_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_ETHERNET_TYPE,
|
||||
|
||||
/* this descriptor actually adds value, surprise! */
|
||||
/* .iMACAddress = DYNAMIC */
|
||||
.bmEthernetStatistics = cpu_to_le32(0), /* no statistics */
|
||||
.wMaxSegmentSize = cpu_to_le16(ETH_FRAME_LEN),
|
||||
.wNumberMCFilters = cpu_to_le16(0),
|
||||
.bNumberPowerFilters = 0,
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor fs_subset_in_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_subset_out_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_eth_function[] = {
|
||||
(struct usb_descriptor_header *) &subset_data_intf,
|
||||
(struct usb_descriptor_header *) &mdlm_header_desc,
|
||||
(struct usb_descriptor_header *) &mdlm_desc,
|
||||
(struct usb_descriptor_header *) &mdlm_detail_desc,
|
||||
(struct usb_descriptor_header *) ðer_desc,
|
||||
(struct usb_descriptor_header *) &fs_subset_in_desc,
|
||||
(struct usb_descriptor_header *) &fs_subset_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor hs_subset_in_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_subset_out_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_eth_function[] = {
|
||||
(struct usb_descriptor_header *) &subset_data_intf,
|
||||
(struct usb_descriptor_header *) &mdlm_header_desc,
|
||||
(struct usb_descriptor_header *) &mdlm_desc,
|
||||
(struct usb_descriptor_header *) &mdlm_detail_desc,
|
||||
(struct usb_descriptor_header *) ðer_desc,
|
||||
(struct usb_descriptor_header *) &hs_subset_in_desc,
|
||||
(struct usb_descriptor_header *) &hs_subset_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* super speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor ss_subset_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(1024),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor ss_subset_out_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_subset_bulk_comp_desc = {
|
||||
.bLength = sizeof ss_subset_bulk_comp_desc,
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
|
||||
/* the following 2 values can be tweaked if necessary */
|
||||
/* .bMaxBurst = 0, */
|
||||
/* .bmAttributes = 0, */
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *ss_eth_function[] = {
|
||||
(struct usb_descriptor_header *) &subset_data_intf,
|
||||
(struct usb_descriptor_header *) &mdlm_header_desc,
|
||||
(struct usb_descriptor_header *) &mdlm_desc,
|
||||
(struct usb_descriptor_header *) &mdlm_detail_desc,
|
||||
(struct usb_descriptor_header *) ðer_desc,
|
||||
(struct usb_descriptor_header *) &ss_subset_in_desc,
|
||||
(struct usb_descriptor_header *) &ss_subset_bulk_comp_desc,
|
||||
(struct usb_descriptor_header *) &ss_subset_out_desc,
|
||||
(struct usb_descriptor_header *) &ss_subset_bulk_comp_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* string descriptors: */
|
||||
|
||||
static struct usb_string geth_string_defs[] = {
|
||||
[0].s = "CDC Ethernet Subset/SAFE",
|
||||
[1].s = "",
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings geth_string_table = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = geth_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *geth_strings[] = {
|
||||
&geth_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int geth_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_gether *geth = func_to_geth(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct net_device *net;
|
||||
|
||||
/* we know alt == 0, so this is an activation or a reset */
|
||||
|
||||
if (geth->port.in_ep->driver_data) {
|
||||
DBG(cdev, "reset cdc subset\n");
|
||||
gether_disconnect(&geth->port);
|
||||
}
|
||||
|
||||
DBG(cdev, "init + activate cdc subset\n");
|
||||
if (config_ep_by_speed(cdev->gadget, f, geth->port.in_ep) ||
|
||||
config_ep_by_speed(cdev->gadget, f, geth->port.out_ep)) {
|
||||
geth->port.in_ep->desc = NULL;
|
||||
geth->port.out_ep->desc = NULL;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
net = gether_connect(&geth->port);
|
||||
return PTR_ERR_OR_ZERO(net);
|
||||
}
|
||||
|
||||
static void geth_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_gether *geth = func_to_geth(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "net deactivated\n");
|
||||
gether_disconnect(&geth->port);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* serial function driver setup/binding */
|
||||
|
||||
static int
|
||||
geth_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_gether *geth = func_to_geth(f);
|
||||
struct usb_string *us;
|
||||
int status;
|
||||
struct usb_ep *ep;
|
||||
|
||||
struct f_gether_opts *gether_opts;
|
||||
|
||||
gether_opts = container_of(f->fi, struct f_gether_opts, func_inst);
|
||||
|
||||
/*
|
||||
* in drivers/usb/gadget/configfs.c:configfs_composite_bind()
|
||||
* configurations are bound in sequence with list_for_each_entry,
|
||||
* in each configuration its functions are bound in sequence
|
||||
* with list_for_each_entry, so we assume no race condition
|
||||
* with regard to gether_opts->bound access
|
||||
*/
|
||||
if (!gether_opts->bound) {
|
||||
mutex_lock(&gether_opts->lock);
|
||||
gether_set_gadget(gether_opts->net, cdev->gadget);
|
||||
status = gether_register_netdev(gether_opts->net);
|
||||
mutex_unlock(&gether_opts->lock);
|
||||
if (status)
|
||||
return status;
|
||||
gether_opts->bound = true;
|
||||
}
|
||||
|
||||
us = usb_gstrings_attach(cdev, geth_strings,
|
||||
ARRAY_SIZE(geth_string_defs));
|
||||
if (IS_ERR(us))
|
||||
return PTR_ERR(us);
|
||||
|
||||
subset_data_intf.iInterface = us[0].id;
|
||||
ether_desc.iMACAddress = us[1].id;
|
||||
|
||||
/* allocate instance-specific interface IDs */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
subset_data_intf.bInterfaceNumber = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_in_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
geth->port.in_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_out_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
geth->port.out_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* support all relevant hardware speeds... we expect that when
|
||||
* hardware is dual speed, all bulk-capable endpoints work at
|
||||
* both speeds
|
||||
*/
|
||||
hs_subset_in_desc.bEndpointAddress = fs_subset_in_desc.bEndpointAddress;
|
||||
hs_subset_out_desc.bEndpointAddress =
|
||||
fs_subset_out_desc.bEndpointAddress;
|
||||
|
||||
ss_subset_in_desc.bEndpointAddress = fs_subset_in_desc.bEndpointAddress;
|
||||
ss_subset_out_desc.bEndpointAddress =
|
||||
fs_subset_out_desc.bEndpointAddress;
|
||||
|
||||
status = usb_assign_descriptors(f, fs_eth_function, hs_eth_function,
|
||||
ss_eth_function);
|
||||
if (status)
|
||||
goto fail;
|
||||
|
||||
/* NOTE: all that is done without knowing or caring about
|
||||
* the network link ... which is unavailable to this code
|
||||
* until we're activated via set_alt().
|
||||
*/
|
||||
|
||||
DBG(cdev, "CDC Subset: %s speed IN/%s OUT/%s\n",
|
||||
gadget_is_superspeed(c->cdev->gadget) ? "super" :
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
geth->port.in_ep->name, geth->port.out_ep->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
usb_free_all_descriptors(f);
|
||||
/* we might as well release our claims on endpoints */
|
||||
if (geth->port.out_ep)
|
||||
geth->port.out_ep->driver_data = NULL;
|
||||
if (geth->port.in_ep)
|
||||
geth->port.in_ep->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static inline struct f_gether_opts *to_f_gether_opts(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct f_gether_opts,
|
||||
func_inst.group);
|
||||
}
|
||||
|
||||
/* f_gether_item_ops */
|
||||
USB_ETHERNET_CONFIGFS_ITEM(gether);
|
||||
|
||||
/* f_gether_opts_dev_addr */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(gether);
|
||||
|
||||
/* f_gether_opts_host_addr */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(gether);
|
||||
|
||||
/* f_gether_opts_qmult */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(gether);
|
||||
|
||||
/* f_gether_opts_ifname */
|
||||
USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(gether);
|
||||
|
||||
static struct configfs_attribute *gether_attrs[] = {
|
||||
&f_gether_opts_dev_addr.attr,
|
||||
&f_gether_opts_host_addr.attr,
|
||||
&f_gether_opts_qmult.attr,
|
||||
&f_gether_opts_ifname.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct config_item_type gether_func_type = {
|
||||
.ct_item_ops = &gether_item_ops,
|
||||
.ct_attrs = gether_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void geth_free_inst(struct usb_function_instance *f)
|
||||
{
|
||||
struct f_gether_opts *opts;
|
||||
|
||||
opts = container_of(f, struct f_gether_opts, func_inst);
|
||||
if (opts->bound)
|
||||
gether_cleanup(netdev_priv(opts->net));
|
||||
else
|
||||
free_netdev(opts->net);
|
||||
kfree(opts);
|
||||
}
|
||||
|
||||
static struct usb_function_instance *geth_alloc_inst(void)
|
||||
{
|
||||
struct f_gether_opts *opts;
|
||||
|
||||
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
||||
if (!opts)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
mutex_init(&opts->lock);
|
||||
opts->func_inst.free_func_inst = geth_free_inst;
|
||||
opts->net = gether_setup_default();
|
||||
if (IS_ERR(opts->net)) {
|
||||
struct net_device *net = opts->net;
|
||||
kfree(opts);
|
||||
return ERR_CAST(net);
|
||||
}
|
||||
|
||||
config_group_init_type_name(&opts->func_inst.group, "",
|
||||
&gether_func_type);
|
||||
|
||||
return &opts->func_inst;
|
||||
}
|
||||
|
||||
static void geth_free(struct usb_function *f)
|
||||
{
|
||||
struct f_gether *eth;
|
||||
|
||||
eth = func_to_geth(f);
|
||||
kfree(eth);
|
||||
}
|
||||
|
||||
static void geth_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
geth_string_defs[0].id = 0;
|
||||
usb_free_all_descriptors(f);
|
||||
}
|
||||
|
||||
static struct usb_function *geth_alloc(struct usb_function_instance *fi)
|
||||
{
|
||||
struct f_gether *geth;
|
||||
struct f_gether_opts *opts;
|
||||
int status;
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
geth = kzalloc(sizeof(*geth), GFP_KERNEL);
|
||||
if (!geth)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
opts = container_of(fi, struct f_gether_opts, func_inst);
|
||||
|
||||
mutex_lock(&opts->lock);
|
||||
opts->refcnt++;
|
||||
/* export host's Ethernet address in CDC format */
|
||||
status = gether_get_host_addr_cdc(opts->net, geth->ethaddr,
|
||||
sizeof(geth->ethaddr));
|
||||
if (status < 12) {
|
||||
kfree(geth);
|
||||
mutex_unlock(&opts->lock);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
geth_string_defs[1].s = geth->ethaddr;
|
||||
|
||||
geth->port.ioport = netdev_priv(opts->net);
|
||||
mutex_unlock(&opts->lock);
|
||||
geth->port.cdc_filter = DEFAULT_FILTER;
|
||||
|
||||
geth->port.func.name = "cdc_subset";
|
||||
geth->port.func.bind = geth_bind;
|
||||
geth->port.func.unbind = geth_unbind;
|
||||
geth->port.func.set_alt = geth_set_alt;
|
||||
geth->port.func.disable = geth_disable;
|
||||
geth->port.func.free_func = geth_free;
|
||||
|
||||
return &geth->port.func;
|
||||
}
|
||||
|
||||
DECLARE_USB_FUNCTION_INIT(geth, geth_alloc_inst, geth_alloc);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("David Brownell");
|
||||
768
drivers/usb/gadget/function/f_uac1.c
Normal file
768
drivers/usb/gadget/function/f_uac1.c
Normal file
|
|
@ -0,0 +1,768 @@
|
|||
/*
|
||||
* f_audio.c -- USB Audio class function driver
|
||||
*
|
||||
* Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
|
||||
* Copyright (C) 2008 Analog Devices, Inc
|
||||
*
|
||||
* Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/atomic.h>
|
||||
|
||||
#include "u_uac1.h"
|
||||
|
||||
#define OUT_EP_MAX_PACKET_SIZE 200
|
||||
static int req_buf_size = OUT_EP_MAX_PACKET_SIZE;
|
||||
module_param(req_buf_size, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(req_buf_size, "ISO OUT endpoint request buffer size");
|
||||
|
||||
static int req_count = 256;
|
||||
module_param(req_count, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count");
|
||||
|
||||
static int audio_buf_size = 48000;
|
||||
module_param(audio_buf_size, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(audio_buf_size, "Audio buffer size");
|
||||
|
||||
static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value);
|
||||
static int generic_get_cmd(struct usb_audio_control *con, u8 cmd);
|
||||
|
||||
/*
|
||||
* DESCRIPTORS ... most are static, but strings and full
|
||||
* configuration descriptors are built on demand.
|
||||
*/
|
||||
|
||||
/*
|
||||
* We have two interfaces- AudioControl and AudioStreaming
|
||||
* TODO: only supcard playback currently
|
||||
*/
|
||||
#define F_AUDIO_AC_INTERFACE 0
|
||||
#define F_AUDIO_AS_INTERFACE 1
|
||||
#define F_AUDIO_NUM_INTERFACES 2
|
||||
|
||||
/* B.3.1 Standard AC Interface Descriptor */
|
||||
static struct usb_interface_descriptor ac_interface_desc __initdata = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bNumEndpoints = 0,
|
||||
.bInterfaceClass = USB_CLASS_AUDIO,
|
||||
.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
|
||||
};
|
||||
|
||||
DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);
|
||||
|
||||
#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES)
|
||||
/* 1 input terminal, 1 output terminal and 1 feature unit */
|
||||
#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH + UAC_DT_INPUT_TERMINAL_SIZE \
|
||||
+ UAC_DT_OUTPUT_TERMINAL_SIZE + UAC_DT_FEATURE_UNIT_SIZE(0))
|
||||
/* B.3.2 Class-Specific AC Interface Descriptor */
|
||||
static struct uac1_ac_header_descriptor_2 ac_header_desc = {
|
||||
.bLength = UAC_DT_AC_HEADER_LENGTH,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubtype = UAC_HEADER,
|
||||
.bcdADC = __constant_cpu_to_le16(0x0100),
|
||||
.wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH),
|
||||
.bInCollection = F_AUDIO_NUM_INTERFACES,
|
||||
.baInterfaceNr = {
|
||||
[0] = F_AUDIO_AC_INTERFACE,
|
||||
[1] = F_AUDIO_AS_INTERFACE,
|
||||
}
|
||||
};
|
||||
|
||||
#define INPUT_TERMINAL_ID 1
|
||||
static struct uac_input_terminal_descriptor input_terminal_desc = {
|
||||
.bLength = UAC_DT_INPUT_TERMINAL_SIZE,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubtype = UAC_INPUT_TERMINAL,
|
||||
.bTerminalID = INPUT_TERMINAL_ID,
|
||||
.wTerminalType = UAC_TERMINAL_STREAMING,
|
||||
.bAssocTerminal = 0,
|
||||
.wChannelConfig = 0x3,
|
||||
};
|
||||
|
||||
DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0);
|
||||
|
||||
#define FEATURE_UNIT_ID 2
|
||||
static struct uac_feature_unit_descriptor_0 feature_unit_desc = {
|
||||
.bLength = UAC_DT_FEATURE_UNIT_SIZE(0),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubtype = UAC_FEATURE_UNIT,
|
||||
.bUnitID = FEATURE_UNIT_ID,
|
||||
.bSourceID = INPUT_TERMINAL_ID,
|
||||
.bControlSize = 2,
|
||||
.bmaControls[0] = (UAC_FU_MUTE | UAC_FU_VOLUME),
|
||||
};
|
||||
|
||||
static struct usb_audio_control mute_control = {
|
||||
.list = LIST_HEAD_INIT(mute_control.list),
|
||||
.name = "Mute Control",
|
||||
.type = UAC_FU_MUTE,
|
||||
/* Todo: add real Mute control code */
|
||||
.set = generic_set_cmd,
|
||||
.get = generic_get_cmd,
|
||||
};
|
||||
|
||||
static struct usb_audio_control volume_control = {
|
||||
.list = LIST_HEAD_INIT(volume_control.list),
|
||||
.name = "Volume Control",
|
||||
.type = UAC_FU_VOLUME,
|
||||
/* Todo: add real Volume control code */
|
||||
.set = generic_set_cmd,
|
||||
.get = generic_get_cmd,
|
||||
};
|
||||
|
||||
static struct usb_audio_control_selector feature_unit = {
|
||||
.list = LIST_HEAD_INIT(feature_unit.list),
|
||||
.id = FEATURE_UNIT_ID,
|
||||
.name = "Mute & Volume Control",
|
||||
.type = UAC_FEATURE_UNIT,
|
||||
.desc = (struct usb_descriptor_header *)&feature_unit_desc,
|
||||
};
|
||||
|
||||
#define OUTPUT_TERMINAL_ID 3
|
||||
static struct uac1_output_terminal_descriptor output_terminal_desc = {
|
||||
.bLength = UAC_DT_OUTPUT_TERMINAL_SIZE,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
|
||||
.bTerminalID = OUTPUT_TERMINAL_ID,
|
||||
.wTerminalType = UAC_OUTPUT_TERMINAL_SPEAKER,
|
||||
.bAssocTerminal = FEATURE_UNIT_ID,
|
||||
.bSourceID = FEATURE_UNIT_ID,
|
||||
};
|
||||
|
||||
/* B.4.1 Standard AS Interface Descriptor */
|
||||
static struct usb_interface_descriptor as_interface_alt_0_desc = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 0,
|
||||
.bInterfaceClass = USB_CLASS_AUDIO,
|
||||
.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor as_interface_alt_1_desc = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bAlternateSetting = 1,
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_AUDIO,
|
||||
.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
|
||||
};
|
||||
|
||||
/* B.4.2 Class-Specific AS Interface Descriptor */
|
||||
static struct uac1_as_header_descriptor as_header_desc = {
|
||||
.bLength = UAC_DT_AS_HEADER_SIZE,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubtype = UAC_AS_GENERAL,
|
||||
.bTerminalLink = INPUT_TERMINAL_ID,
|
||||
.bDelay = 1,
|
||||
.wFormatTag = UAC_FORMAT_TYPE_I_PCM,
|
||||
};
|
||||
|
||||
DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
|
||||
|
||||
static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = {
|
||||
.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubtype = UAC_FORMAT_TYPE,
|
||||
.bFormatType = UAC_FORMAT_TYPE_I,
|
||||
.bSubframeSize = 2,
|
||||
.bBitResolution = 16,
|
||||
.bSamFreqType = 1,
|
||||
};
|
||||
|
||||
/* Standard ISO OUT Endpoint Descriptor */
|
||||
static struct usb_endpoint_descriptor as_out_ep_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE
|
||||
| USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(OUT_EP_MAX_PACKET_SIZE),
|
||||
.bInterval = 4,
|
||||
};
|
||||
|
||||
/* Class-specific AS ISO OUT Endpoint Descriptor */
|
||||
static struct uac_iso_endpoint_descriptor as_iso_out_desc __initdata = {
|
||||
.bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
|
||||
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
||||
.bDescriptorSubtype = UAC_EP_GENERAL,
|
||||
.bmAttributes = 1,
|
||||
.bLockDelayUnits = 1,
|
||||
.wLockDelay = __constant_cpu_to_le16(1),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *f_audio_desc[] __initdata = {
|
||||
(struct usb_descriptor_header *)&ac_interface_desc,
|
||||
(struct usb_descriptor_header *)&ac_header_desc,
|
||||
|
||||
(struct usb_descriptor_header *)&input_terminal_desc,
|
||||
(struct usb_descriptor_header *)&output_terminal_desc,
|
||||
(struct usb_descriptor_header *)&feature_unit_desc,
|
||||
|
||||
(struct usb_descriptor_header *)&as_interface_alt_0_desc,
|
||||
(struct usb_descriptor_header *)&as_interface_alt_1_desc,
|
||||
(struct usb_descriptor_header *)&as_header_desc,
|
||||
|
||||
(struct usb_descriptor_header *)&as_type_i_desc,
|
||||
|
||||
(struct usb_descriptor_header *)&as_out_ep_desc,
|
||||
(struct usb_descriptor_header *)&as_iso_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* This function is an ALSA sound card following USB Audio Class Spec 1.0.
|
||||
*/
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
struct f_audio_buf {
|
||||
u8 *buf;
|
||||
int actual;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
static struct f_audio_buf *f_audio_buffer_alloc(int buf_size)
|
||||
{
|
||||
struct f_audio_buf *copy_buf;
|
||||
|
||||
copy_buf = kzalloc(sizeof *copy_buf, GFP_ATOMIC);
|
||||
if (!copy_buf)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
copy_buf->buf = kzalloc(buf_size, GFP_ATOMIC);
|
||||
if (!copy_buf->buf) {
|
||||
kfree(copy_buf);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
return copy_buf;
|
||||
}
|
||||
|
||||
static void f_audio_buffer_free(struct f_audio_buf *audio_buf)
|
||||
{
|
||||
kfree(audio_buf->buf);
|
||||
kfree(audio_buf);
|
||||
}
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
struct f_audio {
|
||||
struct gaudio card;
|
||||
|
||||
/* endpoints handle full and/or high speeds */
|
||||
struct usb_ep *out_ep;
|
||||
|
||||
spinlock_t lock;
|
||||
struct f_audio_buf *copy_buf;
|
||||
struct work_struct playback_work;
|
||||
struct list_head play_queue;
|
||||
|
||||
/* Control Set command */
|
||||
struct list_head cs;
|
||||
u8 set_cmd;
|
||||
struct usb_audio_control *set_con;
|
||||
};
|
||||
|
||||
static inline struct f_audio *func_to_audio(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_audio, card.func);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void f_audio_playback_work(struct work_struct *data)
|
||||
{
|
||||
struct f_audio *audio = container_of(data, struct f_audio,
|
||||
playback_work);
|
||||
struct f_audio_buf *play_buf;
|
||||
|
||||
spin_lock_irq(&audio->lock);
|
||||
if (list_empty(&audio->play_queue)) {
|
||||
spin_unlock_irq(&audio->lock);
|
||||
return;
|
||||
}
|
||||
play_buf = list_first_entry(&audio->play_queue,
|
||||
struct f_audio_buf, list);
|
||||
list_del(&play_buf->list);
|
||||
spin_unlock_irq(&audio->lock);
|
||||
|
||||
u_audio_playback(&audio->card, play_buf->buf, play_buf->actual);
|
||||
f_audio_buffer_free(play_buf);
|
||||
}
|
||||
|
||||
static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_audio *audio = req->context;
|
||||
struct usb_composite_dev *cdev = audio->card.func.config->cdev;
|
||||
struct f_audio_buf *copy_buf = audio->copy_buf;
|
||||
int err;
|
||||
|
||||
if (!copy_buf)
|
||||
return -EINVAL;
|
||||
|
||||
/* Copy buffer is full, add it to the play_queue */
|
||||
if (audio_buf_size - copy_buf->actual < req->actual) {
|
||||
list_add_tail(©_buf->list, &audio->play_queue);
|
||||
schedule_work(&audio->playback_work);
|
||||
copy_buf = f_audio_buffer_alloc(audio_buf_size);
|
||||
if (IS_ERR(copy_buf))
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual);
|
||||
copy_buf->actual += req->actual;
|
||||
audio->copy_buf = copy_buf;
|
||||
|
||||
err = usb_ep_queue(ep, req, GFP_ATOMIC);
|
||||
if (err)
|
||||
ERROR(cdev, "%s queue req: %d\n", ep->name, err);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static void f_audio_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_audio *audio = req->context;
|
||||
int status = req->status;
|
||||
u32 data = 0;
|
||||
struct usb_ep *out_ep = audio->out_ep;
|
||||
|
||||
switch (status) {
|
||||
|
||||
case 0: /* normal completion? */
|
||||
if (ep == out_ep)
|
||||
f_audio_out_ep_complete(ep, req);
|
||||
else if (audio->set_con) {
|
||||
memcpy(&data, req->buf, req->length);
|
||||
audio->set_con->set(audio->set_con, audio->set_cmd,
|
||||
le16_to_cpu(data));
|
||||
audio->set_con = NULL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int audio_set_intf_req(struct usb_function *f,
|
||||
const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct f_audio *audio = func_to_audio(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_request *req = cdev->req;
|
||||
u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
|
||||
u16 len = le16_to_cpu(ctrl->wLength);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u8 con_sel = (w_value >> 8) & 0xFF;
|
||||
u8 cmd = (ctrl->bRequest & 0x0F);
|
||||
struct usb_audio_control_selector *cs;
|
||||
struct usb_audio_control *con;
|
||||
|
||||
DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n",
|
||||
ctrl->bRequest, w_value, len, id);
|
||||
|
||||
list_for_each_entry(cs, &audio->cs, list) {
|
||||
if (cs->id == id) {
|
||||
list_for_each_entry(con, &cs->control, list) {
|
||||
if (con->type == con_sel) {
|
||||
audio->set_con = con;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
audio->set_cmd = cmd;
|
||||
req->context = audio;
|
||||
req->complete = f_audio_complete;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int audio_get_intf_req(struct usb_function *f,
|
||||
const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct f_audio *audio = func_to_audio(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_request *req = cdev->req;
|
||||
int value = -EOPNOTSUPP;
|
||||
u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
|
||||
u16 len = le16_to_cpu(ctrl->wLength);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u8 con_sel = (w_value >> 8) & 0xFF;
|
||||
u8 cmd = (ctrl->bRequest & 0x0F);
|
||||
struct usb_audio_control_selector *cs;
|
||||
struct usb_audio_control *con;
|
||||
|
||||
DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n",
|
||||
ctrl->bRequest, w_value, len, id);
|
||||
|
||||
list_for_each_entry(cs, &audio->cs, list) {
|
||||
if (cs->id == id) {
|
||||
list_for_each_entry(con, &cs->control, list) {
|
||||
if (con->type == con_sel && con->get) {
|
||||
value = con->get(con, cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
req->context = audio;
|
||||
req->complete = f_audio_complete;
|
||||
len = min_t(size_t, sizeof(value), len);
|
||||
memcpy(req->buf, &value, len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int audio_set_endpoint_req(struct usb_function *f,
|
||||
const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
int value = -EOPNOTSUPP;
|
||||
u16 ep = le16_to_cpu(ctrl->wIndex);
|
||||
u16 len = le16_to_cpu(ctrl->wLength);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
|
||||
DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
|
||||
ctrl->bRequest, w_value, len, ep);
|
||||
|
||||
switch (ctrl->bRequest) {
|
||||
case UAC_SET_CUR:
|
||||
value = len;
|
||||
break;
|
||||
|
||||
case UAC_SET_MIN:
|
||||
break;
|
||||
|
||||
case UAC_SET_MAX:
|
||||
break;
|
||||
|
||||
case UAC_SET_RES:
|
||||
break;
|
||||
|
||||
case UAC_SET_MEM:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static int audio_get_endpoint_req(struct usb_function *f,
|
||||
const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
int value = -EOPNOTSUPP;
|
||||
u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
|
||||
u16 len = le16_to_cpu(ctrl->wLength);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
|
||||
DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
|
||||
ctrl->bRequest, w_value, len, ep);
|
||||
|
||||
switch (ctrl->bRequest) {
|
||||
case UAC_GET_CUR:
|
||||
case UAC_GET_MIN:
|
||||
case UAC_GET_MAX:
|
||||
case UAC_GET_RES:
|
||||
value = len;
|
||||
break;
|
||||
case UAC_GET_MEM:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static int
|
||||
f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_request *req = cdev->req;
|
||||
int value = -EOPNOTSUPP;
|
||||
u16 w_index = le16_to_cpu(ctrl->wIndex);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u16 w_length = le16_to_cpu(ctrl->wLength);
|
||||
|
||||
/* composite driver infrastructure handles everything; interface
|
||||
* activation uses set_alt().
|
||||
*/
|
||||
switch (ctrl->bRequestType) {
|
||||
case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
|
||||
value = audio_set_intf_req(f, ctrl);
|
||||
break;
|
||||
|
||||
case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
|
||||
value = audio_get_intf_req(f, ctrl);
|
||||
break;
|
||||
|
||||
case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
|
||||
value = audio_set_endpoint_req(f, ctrl);
|
||||
break;
|
||||
|
||||
case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
|
||||
value = audio_get_endpoint_req(f, ctrl);
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
}
|
||||
|
||||
/* respond with data transfer or status phase? */
|
||||
if (value >= 0) {
|
||||
DBG(cdev, "audio req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
req->zero = 0;
|
||||
req->length = value;
|
||||
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
||||
if (value < 0)
|
||||
ERROR(cdev, "audio response on err %d\n", value);
|
||||
}
|
||||
|
||||
/* device either stalls (value < 0) or reports success */
|
||||
return value;
|
||||
}
|
||||
|
||||
static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_audio *audio = func_to_audio(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_ep *out_ep = audio->out_ep;
|
||||
struct usb_request *req;
|
||||
int i = 0, err = 0;
|
||||
|
||||
DBG(cdev, "intf %d, alt %d\n", intf, alt);
|
||||
|
||||
if (intf == 1) {
|
||||
if (alt == 1) {
|
||||
usb_ep_enable(out_ep);
|
||||
out_ep->driver_data = audio;
|
||||
audio->copy_buf = f_audio_buffer_alloc(audio_buf_size);
|
||||
if (IS_ERR(audio->copy_buf))
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* allocate a bunch of read buffers
|
||||
* and queue them all at once.
|
||||
*/
|
||||
for (i = 0; i < req_count && err == 0; i++) {
|
||||
req = usb_ep_alloc_request(out_ep, GFP_ATOMIC);
|
||||
if (req) {
|
||||
req->buf = kzalloc(req_buf_size,
|
||||
GFP_ATOMIC);
|
||||
if (req->buf) {
|
||||
req->length = req_buf_size;
|
||||
req->context = audio;
|
||||
req->complete =
|
||||
f_audio_complete;
|
||||
err = usb_ep_queue(out_ep,
|
||||
req, GFP_ATOMIC);
|
||||
if (err)
|
||||
ERROR(cdev,
|
||||
"%s queue req: %d\n",
|
||||
out_ep->name, err);
|
||||
} else
|
||||
err = -ENOMEM;
|
||||
} else
|
||||
err = -ENOMEM;
|
||||
}
|
||||
|
||||
} else {
|
||||
struct f_audio_buf *copy_buf = audio->copy_buf;
|
||||
if (copy_buf) {
|
||||
list_add_tail(©_buf->list,
|
||||
&audio->play_queue);
|
||||
schedule_work(&audio->playback_work);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void f_audio_disable(struct usb_function *f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void f_audio_build_desc(struct f_audio *audio)
|
||||
{
|
||||
struct gaudio *card = &audio->card;
|
||||
u8 *sam_freq;
|
||||
int rate;
|
||||
|
||||
/* Set channel numbers */
|
||||
input_terminal_desc.bNrChannels = u_audio_get_playback_channels(card);
|
||||
as_type_i_desc.bNrChannels = u_audio_get_playback_channels(card);
|
||||
|
||||
/* Set sample rates */
|
||||
rate = u_audio_get_playback_rate(card);
|
||||
sam_freq = as_type_i_desc.tSamFreq[0];
|
||||
memcpy(sam_freq, &rate, 3);
|
||||
|
||||
/* Todo: Set Sample bits and other parameters */
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* audio function driver setup/binding */
|
||||
static int __init
|
||||
f_audio_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_audio *audio = func_to_audio(f);
|
||||
int status;
|
||||
struct usb_ep *ep = NULL;
|
||||
|
||||
f_audio_build_desc(audio);
|
||||
|
||||
/* allocate instance-specific interface IDs, and patch descriptors */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
ac_interface_desc.bInterfaceNumber = status;
|
||||
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
as_interface_alt_0_desc.bInterfaceNumber = status;
|
||||
as_interface_alt_1_desc.bInterfaceNumber = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
audio->out_ep = ep;
|
||||
audio->out_ep->desc = &as_out_ep_desc;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
status = -ENOMEM;
|
||||
|
||||
/* copy descriptors, and track endpoint copies */
|
||||
status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL);
|
||||
if (status)
|
||||
goto fail;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (ep)
|
||||
ep->driver_data = NULL;
|
||||
return status;
|
||||
}
|
||||
|
||||
static void
|
||||
f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct f_audio *audio = func_to_audio(f);
|
||||
|
||||
usb_free_all_descriptors(f);
|
||||
kfree(audio);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value)
|
||||
{
|
||||
con->data[cmd] = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int generic_get_cmd(struct usb_audio_control *con, u8 cmd)
|
||||
{
|
||||
return con->data[cmd];
|
||||
}
|
||||
|
||||
/* Todo: add more control selecotor dynamically */
|
||||
static int __init control_selector_init(struct f_audio *audio)
|
||||
{
|
||||
INIT_LIST_HEAD(&audio->cs);
|
||||
list_add(&feature_unit.list, &audio->cs);
|
||||
|
||||
INIT_LIST_HEAD(&feature_unit.control);
|
||||
list_add(&mute_control.list, &feature_unit.control);
|
||||
list_add(&volume_control.list, &feature_unit.control);
|
||||
|
||||
volume_control.data[UAC__CUR] = 0xffc0;
|
||||
volume_control.data[UAC__MIN] = 0xe3a0;
|
||||
volume_control.data[UAC__MAX] = 0xfff0;
|
||||
volume_control.data[UAC__RES] = 0x0030;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* audio_bind_config - add USB audio function to a configuration
|
||||
* @c: the configuration to supcard the USB audio function
|
||||
* Context: single threaded during gadget setup
|
||||
*
|
||||
* Returns zero on success, else negative errno.
|
||||
*/
|
||||
static int __init audio_bind_config(struct usb_configuration *c)
|
||||
{
|
||||
struct f_audio *audio;
|
||||
int status;
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
audio = kzalloc(sizeof *audio, GFP_KERNEL);
|
||||
if (!audio)
|
||||
return -ENOMEM;
|
||||
|
||||
audio->card.func.name = "g_audio";
|
||||
audio->card.gadget = c->cdev->gadget;
|
||||
|
||||
INIT_LIST_HEAD(&audio->play_queue);
|
||||
spin_lock_init(&audio->lock);
|
||||
|
||||
/* set up ASLA audio devices */
|
||||
status = gaudio_setup(&audio->card);
|
||||
if (status < 0)
|
||||
goto setup_fail;
|
||||
|
||||
audio->card.func.strings = audio_strings;
|
||||
audio->card.func.bind = f_audio_bind;
|
||||
audio->card.func.unbind = f_audio_unbind;
|
||||
audio->card.func.set_alt = f_audio_set_alt;
|
||||
audio->card.func.setup = f_audio_setup;
|
||||
audio->card.func.disable = f_audio_disable;
|
||||
|
||||
control_selector_init(audio);
|
||||
|
||||
INIT_WORK(&audio->playback_work, f_audio_playback_work);
|
||||
|
||||
status = usb_add_function(c, &audio->card.func);
|
||||
if (status)
|
||||
goto add_fail;
|
||||
|
||||
INFO(c->cdev, "audio_buf_size %d, req_buf_size %d, req_count %d\n",
|
||||
audio_buf_size, req_buf_size, req_count);
|
||||
|
||||
return status;
|
||||
|
||||
add_fail:
|
||||
gaudio_cleanup();
|
||||
setup_fail:
|
||||
kfree(audio);
|
||||
return status;
|
||||
}
|
||||
1374
drivers/usb/gadget/function/f_uac2.c
Normal file
1374
drivers/usb/gadget/function/f_uac2.c
Normal file
File diff suppressed because it is too large
Load diff
836
drivers/usb/gadget/function/f_uvc.c
Normal file
836
drivers/usb/gadget/function/f_uvc.c
Normal file
|
|
@ -0,0 +1,836 @@
|
|||
/*
|
||||
* uvc_gadget.c -- USB Video Class Gadget driver
|
||||
*
|
||||
* Copyright (C) 2009-2010
|
||||
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/usb/video.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include <media/v4l2-dev.h>
|
||||
#include <media/v4l2-event.h>
|
||||
|
||||
#include "uvc.h"
|
||||
|
||||
unsigned int uvc_gadget_trace_param;
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* module parameters specific to the Video streaming endpoint */
|
||||
static unsigned int streaming_interval = 1;
|
||||
module_param(streaming_interval, uint, S_IRUGO|S_IWUSR);
|
||||
MODULE_PARM_DESC(streaming_interval, "1 - 16");
|
||||
|
||||
static unsigned int streaming_maxpacket = 1024;
|
||||
module_param(streaming_maxpacket, uint, S_IRUGO|S_IWUSR);
|
||||
MODULE_PARM_DESC(streaming_maxpacket, "1 - 1023 (FS), 1 - 3072 (hs/ss)");
|
||||
|
||||
static unsigned int streaming_maxburst;
|
||||
module_param(streaming_maxburst, uint, S_IRUGO|S_IWUSR);
|
||||
MODULE_PARM_DESC(streaming_maxburst, "0 - 15 (ss only)");
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Function descriptors
|
||||
*/
|
||||
|
||||
/* string IDs are assigned dynamically */
|
||||
|
||||
#define UVC_STRING_CONTROL_IDX 0
|
||||
#define UVC_STRING_STREAMING_IDX 1
|
||||
|
||||
static struct usb_string uvc_en_us_strings[] = {
|
||||
[UVC_STRING_CONTROL_IDX].s = "UVC Camera",
|
||||
[UVC_STRING_STREAMING_IDX].s = "Video Streaming",
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings uvc_stringtab = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = uvc_en_us_strings,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *uvc_function_strings[] = {
|
||||
&uvc_stringtab,
|
||||
NULL,
|
||||
};
|
||||
|
||||
#define UVC_INTF_VIDEO_CONTROL 0
|
||||
#define UVC_INTF_VIDEO_STREAMING 1
|
||||
|
||||
#define UVC_STATUS_MAX_PACKET_SIZE 16 /* 16 bytes status */
|
||||
|
||||
static struct usb_interface_assoc_descriptor uvc_iad __initdata = {
|
||||
.bLength = sizeof(uvc_iad),
|
||||
.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
|
||||
.bFirstInterface = 0,
|
||||
.bInterfaceCount = 2,
|
||||
.bFunctionClass = USB_CLASS_VIDEO,
|
||||
.bFunctionSubClass = UVC_SC_VIDEO_INTERFACE_COLLECTION,
|
||||
.bFunctionProtocol = 0x00,
|
||||
.iFunction = 0,
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor uvc_control_intf __initdata = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = UVC_INTF_VIDEO_CONTROL,
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = UVC_SC_VIDEOCONTROL,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor uvc_control_ep __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE),
|
||||
.bInterval = 8,
|
||||
};
|
||||
|
||||
static struct usb_ss_ep_comp_descriptor uvc_ss_control_comp __initdata = {
|
||||
.bLength = sizeof(uvc_ss_control_comp),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
/* The following 3 values can be tweaked if necessary. */
|
||||
.bMaxBurst = 0,
|
||||
.bmAttributes = 0,
|
||||
.wBytesPerInterval = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE),
|
||||
};
|
||||
|
||||
static struct uvc_control_endpoint_descriptor uvc_control_cs_ep __initdata = {
|
||||
.bLength = UVC_DT_CONTROL_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
||||
.bDescriptorSubType = UVC_EP_INTERRUPT,
|
||||
.wMaxTransferSize = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE),
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor uvc_streaming_intf_alt0 __initdata = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = UVC_INTF_VIDEO_STREAMING,
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 0,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = UVC_SC_VIDEOSTREAMING,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0,
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor uvc_streaming_intf_alt1 __initdata = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = UVC_INTF_VIDEO_STREAMING,
|
||||
.bAlternateSetting = 1,
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = UVC_SC_VIDEOSTREAMING,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor uvc_fs_streaming_ep __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_SYNC_ASYNC
|
||||
| USB_ENDPOINT_XFER_ISOC,
|
||||
/* The wMaxPacketSize and bInterval values will be initialized from
|
||||
* module parameters.
|
||||
*/
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor uvc_hs_streaming_ep __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_SYNC_ASYNC
|
||||
| USB_ENDPOINT_XFER_ISOC,
|
||||
/* The wMaxPacketSize and bInterval values will be initialized from
|
||||
* module parameters.
|
||||
*/
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor uvc_ss_streaming_ep __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_SYNC_ASYNC
|
||||
| USB_ENDPOINT_XFER_ISOC,
|
||||
/* The wMaxPacketSize and bInterval values will be initialized from
|
||||
* module parameters.
|
||||
*/
|
||||
};
|
||||
|
||||
static struct usb_ss_ep_comp_descriptor uvc_ss_streaming_comp __initdata = {
|
||||
.bLength = sizeof(uvc_ss_streaming_comp),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
/* The bMaxBurst, bmAttributes and wBytesPerInterval values will be
|
||||
* initialized from module parameters.
|
||||
*/
|
||||
};
|
||||
|
||||
static const struct usb_descriptor_header * const uvc_fs_streaming[] = {
|
||||
(struct usb_descriptor_header *) &uvc_streaming_intf_alt1,
|
||||
(struct usb_descriptor_header *) &uvc_fs_streaming_ep,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct usb_descriptor_header * const uvc_hs_streaming[] = {
|
||||
(struct usb_descriptor_header *) &uvc_streaming_intf_alt1,
|
||||
(struct usb_descriptor_header *) &uvc_hs_streaming_ep,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct usb_descriptor_header * const uvc_ss_streaming[] = {
|
||||
(struct usb_descriptor_header *) &uvc_streaming_intf_alt1,
|
||||
(struct usb_descriptor_header *) &uvc_ss_streaming_ep,
|
||||
(struct usb_descriptor_header *) &uvc_ss_streaming_comp,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Control requests
|
||||
*/
|
||||
|
||||
static void
|
||||
uvc_function_ep0_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct uvc_device *uvc = req->context;
|
||||
struct v4l2_event v4l2_event;
|
||||
struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
|
||||
|
||||
if (uvc->event_setup_out) {
|
||||
uvc->event_setup_out = 0;
|
||||
|
||||
memset(&v4l2_event, 0, sizeof(v4l2_event));
|
||||
v4l2_event.type = UVC_EVENT_DATA;
|
||||
uvc_event->data.length = req->actual;
|
||||
memcpy(&uvc_event->data.data, req->buf, req->actual);
|
||||
v4l2_event_queue(uvc->vdev, &v4l2_event);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct uvc_device *uvc = to_uvc(f);
|
||||
struct v4l2_event v4l2_event;
|
||||
struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
|
||||
|
||||
/* printk(KERN_INFO "setup request %02x %02x value %04x index %04x %04x\n",
|
||||
* ctrl->bRequestType, ctrl->bRequest, le16_to_cpu(ctrl->wValue),
|
||||
* le16_to_cpu(ctrl->wIndex), le16_to_cpu(ctrl->wLength));
|
||||
*/
|
||||
|
||||
if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) {
|
||||
INFO(f->config->cdev, "invalid request type\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Stall too big requests. */
|
||||
if (le16_to_cpu(ctrl->wLength) > UVC_MAX_REQUEST_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
memset(&v4l2_event, 0, sizeof(v4l2_event));
|
||||
v4l2_event.type = UVC_EVENT_SETUP;
|
||||
memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req));
|
||||
v4l2_event_queue(uvc->vdev, &v4l2_event);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void uvc_function_setup_continue(struct uvc_device *uvc)
|
||||
{
|
||||
struct usb_composite_dev *cdev = uvc->func.config->cdev;
|
||||
|
||||
usb_composite_setup_continue(cdev);
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_function_get_alt(struct usb_function *f, unsigned interface)
|
||||
{
|
||||
struct uvc_device *uvc = to_uvc(f);
|
||||
|
||||
INFO(f->config->cdev, "uvc_function_get_alt(%u)\n", interface);
|
||||
|
||||
if (interface == uvc->control_intf)
|
||||
return 0;
|
||||
else if (interface != uvc->streaming_intf)
|
||||
return -EINVAL;
|
||||
else
|
||||
return uvc->state == UVC_STATE_STREAMING ? 1 : 0;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
|
||||
{
|
||||
struct uvc_device *uvc = to_uvc(f);
|
||||
struct v4l2_event v4l2_event;
|
||||
struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
|
||||
int ret;
|
||||
|
||||
INFO(f->config->cdev, "uvc_function_set_alt(%u, %u)\n", interface, alt);
|
||||
|
||||
if (interface == uvc->control_intf) {
|
||||
if (alt)
|
||||
return -EINVAL;
|
||||
|
||||
if (uvc->state == UVC_STATE_DISCONNECTED) {
|
||||
memset(&v4l2_event, 0, sizeof(v4l2_event));
|
||||
v4l2_event.type = UVC_EVENT_CONNECT;
|
||||
uvc_event->speed = f->config->cdev->gadget->speed;
|
||||
v4l2_event_queue(uvc->vdev, &v4l2_event);
|
||||
|
||||
uvc->state = UVC_STATE_CONNECTED;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (interface != uvc->streaming_intf)
|
||||
return -EINVAL;
|
||||
|
||||
/* TODO
|
||||
if (usb_endpoint_xfer_bulk(&uvc->desc.vs_ep))
|
||||
return alt ? -EINVAL : 0;
|
||||
*/
|
||||
|
||||
switch (alt) {
|
||||
case 0:
|
||||
if (uvc->state != UVC_STATE_STREAMING)
|
||||
return 0;
|
||||
|
||||
if (uvc->video.ep)
|
||||
usb_ep_disable(uvc->video.ep);
|
||||
|
||||
memset(&v4l2_event, 0, sizeof(v4l2_event));
|
||||
v4l2_event.type = UVC_EVENT_STREAMOFF;
|
||||
v4l2_event_queue(uvc->vdev, &v4l2_event);
|
||||
|
||||
uvc->state = UVC_STATE_CONNECTED;
|
||||
return 0;
|
||||
|
||||
case 1:
|
||||
if (uvc->state != UVC_STATE_CONNECTED)
|
||||
return 0;
|
||||
|
||||
if (uvc->video.ep) {
|
||||
ret = config_ep_by_speed(f->config->cdev->gadget,
|
||||
&(uvc->func), uvc->video.ep);
|
||||
if (ret)
|
||||
return ret;
|
||||
usb_ep_enable(uvc->video.ep);
|
||||
}
|
||||
|
||||
memset(&v4l2_event, 0, sizeof(v4l2_event));
|
||||
v4l2_event.type = UVC_EVENT_STREAMON;
|
||||
v4l2_event_queue(uvc->vdev, &v4l2_event);
|
||||
return USB_GADGET_DELAYED_STATUS;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
uvc_function_disable(struct usb_function *f)
|
||||
{
|
||||
struct uvc_device *uvc = to_uvc(f);
|
||||
struct v4l2_event v4l2_event;
|
||||
|
||||
INFO(f->config->cdev, "uvc_function_disable\n");
|
||||
|
||||
memset(&v4l2_event, 0, sizeof(v4l2_event));
|
||||
v4l2_event.type = UVC_EVENT_DISCONNECT;
|
||||
v4l2_event_queue(uvc->vdev, &v4l2_event);
|
||||
|
||||
uvc->state = UVC_STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Connection / disconnection
|
||||
*/
|
||||
|
||||
void
|
||||
uvc_function_connect(struct uvc_device *uvc)
|
||||
{
|
||||
struct usb_composite_dev *cdev = uvc->func.config->cdev;
|
||||
int ret;
|
||||
|
||||
if ((ret = usb_function_activate(&uvc->func)) < 0)
|
||||
INFO(cdev, "UVC connect failed with %d\n", ret);
|
||||
}
|
||||
|
||||
void
|
||||
uvc_function_disconnect(struct uvc_device *uvc)
|
||||
{
|
||||
struct usb_composite_dev *cdev = uvc->func.config->cdev;
|
||||
int ret;
|
||||
|
||||
if ((ret = usb_function_deactivate(&uvc->func)) < 0)
|
||||
INFO(cdev, "UVC disconnect failed with %d\n", ret);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* USB probe and disconnect
|
||||
*/
|
||||
|
||||
static int
|
||||
uvc_register_video(struct uvc_device *uvc)
|
||||
{
|
||||
struct usb_composite_dev *cdev = uvc->func.config->cdev;
|
||||
struct video_device *video;
|
||||
|
||||
/* TODO reference counting. */
|
||||
video = video_device_alloc();
|
||||
if (video == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
video->v4l2_dev = &uvc->v4l2_dev;
|
||||
video->fops = &uvc_v4l2_fops;
|
||||
video->release = video_device_release;
|
||||
strlcpy(video->name, cdev->gadget->name, sizeof(video->name));
|
||||
|
||||
uvc->vdev = video;
|
||||
video_set_drvdata(video, uvc);
|
||||
|
||||
return video_register_device(video, VFL_TYPE_GRABBER, -1);
|
||||
}
|
||||
|
||||
#define UVC_COPY_DESCRIPTOR(mem, dst, desc) \
|
||||
do { \
|
||||
memcpy(mem, desc, (desc)->bLength); \
|
||||
*(dst)++ = mem; \
|
||||
mem += (desc)->bLength; \
|
||||
} while (0);
|
||||
|
||||
#define UVC_COPY_DESCRIPTORS(mem, dst, src) \
|
||||
do { \
|
||||
const struct usb_descriptor_header * const *__src; \
|
||||
for (__src = src; *__src; ++__src) { \
|
||||
memcpy(mem, *__src, (*__src)->bLength); \
|
||||
*dst++ = mem; \
|
||||
mem += (*__src)->bLength; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static struct usb_descriptor_header ** __init
|
||||
uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
|
||||
{
|
||||
struct uvc_input_header_descriptor *uvc_streaming_header;
|
||||
struct uvc_header_descriptor *uvc_control_header;
|
||||
const struct uvc_descriptor_header * const *uvc_control_desc;
|
||||
const struct uvc_descriptor_header * const *uvc_streaming_cls;
|
||||
const struct usb_descriptor_header * const *uvc_streaming_std;
|
||||
const struct usb_descriptor_header * const *src;
|
||||
struct usb_descriptor_header **dst;
|
||||
struct usb_descriptor_header **hdr;
|
||||
unsigned int control_size;
|
||||
unsigned int streaming_size;
|
||||
unsigned int n_desc;
|
||||
unsigned int bytes;
|
||||
void *mem;
|
||||
|
||||
switch (speed) {
|
||||
case USB_SPEED_SUPER:
|
||||
uvc_control_desc = uvc->desc.ss_control;
|
||||
uvc_streaming_cls = uvc->desc.ss_streaming;
|
||||
uvc_streaming_std = uvc_ss_streaming;
|
||||
break;
|
||||
|
||||
case USB_SPEED_HIGH:
|
||||
uvc_control_desc = uvc->desc.fs_control;
|
||||
uvc_streaming_cls = uvc->desc.hs_streaming;
|
||||
uvc_streaming_std = uvc_hs_streaming;
|
||||
break;
|
||||
|
||||
case USB_SPEED_FULL:
|
||||
default:
|
||||
uvc_control_desc = uvc->desc.fs_control;
|
||||
uvc_streaming_cls = uvc->desc.fs_streaming;
|
||||
uvc_streaming_std = uvc_fs_streaming;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Descriptors layout
|
||||
*
|
||||
* uvc_iad
|
||||
* uvc_control_intf
|
||||
* Class-specific UVC control descriptors
|
||||
* uvc_control_ep
|
||||
* uvc_control_cs_ep
|
||||
* uvc_ss_control_comp (for SS only)
|
||||
* uvc_streaming_intf_alt0
|
||||
* Class-specific UVC streaming descriptors
|
||||
* uvc_{fs|hs}_streaming
|
||||
*/
|
||||
|
||||
/* Count descriptors and compute their size. */
|
||||
control_size = 0;
|
||||
streaming_size = 0;
|
||||
bytes = uvc_iad.bLength + uvc_control_intf.bLength
|
||||
+ uvc_control_ep.bLength + uvc_control_cs_ep.bLength
|
||||
+ uvc_streaming_intf_alt0.bLength;
|
||||
|
||||
if (speed == USB_SPEED_SUPER) {
|
||||
bytes += uvc_ss_control_comp.bLength;
|
||||
n_desc = 6;
|
||||
} else {
|
||||
n_desc = 5;
|
||||
}
|
||||
|
||||
for (src = (const struct usb_descriptor_header **)uvc_control_desc;
|
||||
*src; ++src) {
|
||||
control_size += (*src)->bLength;
|
||||
bytes += (*src)->bLength;
|
||||
n_desc++;
|
||||
}
|
||||
for (src = (const struct usb_descriptor_header **)uvc_streaming_cls;
|
||||
*src; ++src) {
|
||||
streaming_size += (*src)->bLength;
|
||||
bytes += (*src)->bLength;
|
||||
n_desc++;
|
||||
}
|
||||
for (src = uvc_streaming_std; *src; ++src) {
|
||||
bytes += (*src)->bLength;
|
||||
n_desc++;
|
||||
}
|
||||
|
||||
mem = kmalloc((n_desc + 1) * sizeof(*src) + bytes, GFP_KERNEL);
|
||||
if (mem == NULL)
|
||||
return NULL;
|
||||
|
||||
hdr = mem;
|
||||
dst = mem;
|
||||
mem += (n_desc + 1) * sizeof(*src);
|
||||
|
||||
/* Copy the descriptors. */
|
||||
UVC_COPY_DESCRIPTOR(mem, dst, &uvc_iad);
|
||||
UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_intf);
|
||||
|
||||
uvc_control_header = mem;
|
||||
UVC_COPY_DESCRIPTORS(mem, dst,
|
||||
(const struct usb_descriptor_header **)uvc_control_desc);
|
||||
uvc_control_header->wTotalLength = cpu_to_le16(control_size);
|
||||
uvc_control_header->bInCollection = 1;
|
||||
uvc_control_header->baInterfaceNr[0] = uvc->streaming_intf;
|
||||
|
||||
UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_ep);
|
||||
if (speed == USB_SPEED_SUPER)
|
||||
UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_control_comp);
|
||||
|
||||
UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_cs_ep);
|
||||
UVC_COPY_DESCRIPTOR(mem, dst, &uvc_streaming_intf_alt0);
|
||||
|
||||
uvc_streaming_header = mem;
|
||||
UVC_COPY_DESCRIPTORS(mem, dst,
|
||||
(const struct usb_descriptor_header**)uvc_streaming_cls);
|
||||
uvc_streaming_header->wTotalLength = cpu_to_le16(streaming_size);
|
||||
uvc_streaming_header->bEndpointAddress = uvc->video.ep->address;
|
||||
|
||||
UVC_COPY_DESCRIPTORS(mem, dst, uvc_streaming_std);
|
||||
|
||||
*dst = NULL;
|
||||
return hdr;
|
||||
}
|
||||
|
||||
static void
|
||||
uvc_function_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct uvc_device *uvc = to_uvc(f);
|
||||
|
||||
INFO(cdev, "uvc_function_unbind\n");
|
||||
|
||||
video_unregister_device(uvc->vdev);
|
||||
v4l2_device_unregister(&uvc->v4l2_dev);
|
||||
uvc->control_ep->driver_data = NULL;
|
||||
uvc->video.ep->driver_data = NULL;
|
||||
|
||||
uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id = 0;
|
||||
usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
|
||||
kfree(uvc->control_buf);
|
||||
|
||||
usb_free_all_descriptors(f);
|
||||
|
||||
kfree(uvc);
|
||||
}
|
||||
|
||||
static int __init
|
||||
uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct uvc_device *uvc = to_uvc(f);
|
||||
unsigned int max_packet_mult;
|
||||
unsigned int max_packet_size;
|
||||
struct usb_ep *ep;
|
||||
int ret = -EINVAL;
|
||||
|
||||
INFO(cdev, "uvc_function_bind\n");
|
||||
|
||||
/* Sanity check the streaming endpoint module parameters.
|
||||
*/
|
||||
streaming_interval = clamp(streaming_interval, 1U, 16U);
|
||||
streaming_maxpacket = clamp(streaming_maxpacket, 1U, 3072U);
|
||||
streaming_maxburst = min(streaming_maxburst, 15U);
|
||||
|
||||
/* Fill in the FS/HS/SS Video Streaming specific descriptors from the
|
||||
* module parameters.
|
||||
*
|
||||
* NOTE: We assume that the user knows what they are doing and won't
|
||||
* give parameters that their UDC doesn't support.
|
||||
*/
|
||||
if (streaming_maxpacket <= 1024) {
|
||||
max_packet_mult = 1;
|
||||
max_packet_size = streaming_maxpacket;
|
||||
} else if (streaming_maxpacket <= 2048) {
|
||||
max_packet_mult = 2;
|
||||
max_packet_size = streaming_maxpacket / 2;
|
||||
} else {
|
||||
max_packet_mult = 3;
|
||||
max_packet_size = streaming_maxpacket / 3;
|
||||
}
|
||||
|
||||
uvc_fs_streaming_ep.wMaxPacketSize = min(streaming_maxpacket, 1023U);
|
||||
uvc_fs_streaming_ep.bInterval = streaming_interval;
|
||||
|
||||
uvc_hs_streaming_ep.wMaxPacketSize = max_packet_size;
|
||||
uvc_hs_streaming_ep.wMaxPacketSize |= ((max_packet_mult - 1) << 11);
|
||||
uvc_hs_streaming_ep.bInterval = streaming_interval;
|
||||
|
||||
uvc_ss_streaming_ep.wMaxPacketSize = max_packet_size;
|
||||
uvc_ss_streaming_ep.bInterval = streaming_interval;
|
||||
uvc_ss_streaming_comp.bmAttributes = max_packet_mult - 1;
|
||||
uvc_ss_streaming_comp.bMaxBurst = streaming_maxburst;
|
||||
uvc_ss_streaming_comp.wBytesPerInterval =
|
||||
max_packet_size * max_packet_mult * streaming_maxburst;
|
||||
|
||||
/* Allocate endpoints. */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &uvc_control_ep);
|
||||
if (!ep) {
|
||||
INFO(cdev, "Unable to allocate control EP\n");
|
||||
goto error;
|
||||
}
|
||||
uvc->control_ep = ep;
|
||||
ep->driver_data = uvc;
|
||||
|
||||
if (gadget_is_superspeed(c->cdev->gadget))
|
||||
ep = usb_ep_autoconfig_ss(cdev->gadget, &uvc_ss_streaming_ep,
|
||||
&uvc_ss_streaming_comp);
|
||||
else if (gadget_is_dualspeed(cdev->gadget))
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &uvc_hs_streaming_ep);
|
||||
else
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &uvc_fs_streaming_ep);
|
||||
|
||||
if (!ep) {
|
||||
INFO(cdev, "Unable to allocate streaming EP\n");
|
||||
goto error;
|
||||
}
|
||||
uvc->video.ep = ep;
|
||||
ep->driver_data = uvc;
|
||||
|
||||
uvc_fs_streaming_ep.bEndpointAddress = uvc->video.ep->address;
|
||||
uvc_hs_streaming_ep.bEndpointAddress = uvc->video.ep->address;
|
||||
uvc_ss_streaming_ep.bEndpointAddress = uvc->video.ep->address;
|
||||
|
||||
/* Allocate interface IDs. */
|
||||
if ((ret = usb_interface_id(c, f)) < 0)
|
||||
goto error;
|
||||
uvc_iad.bFirstInterface = ret;
|
||||
uvc_control_intf.bInterfaceNumber = ret;
|
||||
uvc->control_intf = ret;
|
||||
|
||||
if ((ret = usb_interface_id(c, f)) < 0)
|
||||
goto error;
|
||||
uvc_streaming_intf_alt0.bInterfaceNumber = ret;
|
||||
uvc_streaming_intf_alt1.bInterfaceNumber = ret;
|
||||
uvc->streaming_intf = ret;
|
||||
|
||||
/* Copy descriptors */
|
||||
f->fs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_FULL);
|
||||
if (gadget_is_dualspeed(cdev->gadget))
|
||||
f->hs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_HIGH);
|
||||
if (gadget_is_superspeed(c->cdev->gadget))
|
||||
f->ss_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER);
|
||||
|
||||
/* Preallocate control endpoint request. */
|
||||
uvc->control_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL);
|
||||
uvc->control_buf = kmalloc(UVC_MAX_REQUEST_SIZE, GFP_KERNEL);
|
||||
if (uvc->control_req == NULL || uvc->control_buf == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
uvc->control_req->buf = uvc->control_buf;
|
||||
uvc->control_req->complete = uvc_function_ep0_complete;
|
||||
uvc->control_req->context = uvc;
|
||||
|
||||
/* Avoid letting this gadget enumerate until the userspace server is
|
||||
* active.
|
||||
*/
|
||||
if ((ret = usb_function_deactivate(f)) < 0)
|
||||
goto error;
|
||||
|
||||
if (v4l2_device_register(&cdev->gadget->dev, &uvc->v4l2_dev)) {
|
||||
printk(KERN_INFO "v4l2_device_register failed\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Initialise video. */
|
||||
ret = uvc_video_init(&uvc->video);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
/* Register a V4L2 device. */
|
||||
ret = uvc_register_video(uvc);
|
||||
if (ret < 0) {
|
||||
printk(KERN_INFO "Unable to register video device\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
v4l2_device_unregister(&uvc->v4l2_dev);
|
||||
if (uvc->vdev)
|
||||
video_device_release(uvc->vdev);
|
||||
|
||||
if (uvc->control_ep)
|
||||
uvc->control_ep->driver_data = NULL;
|
||||
if (uvc->video.ep)
|
||||
uvc->video.ep->driver_data = NULL;
|
||||
|
||||
if (uvc->control_req) {
|
||||
usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
|
||||
kfree(uvc->control_buf);
|
||||
}
|
||||
|
||||
usb_free_all_descriptors(f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* USB gadget function
|
||||
*/
|
||||
|
||||
/**
|
||||
* uvc_bind_config - add a UVC function to a configuration
|
||||
* @c: the configuration to support the UVC instance
|
||||
* Context: single threaded during gadget setup
|
||||
*
|
||||
* Returns zero on success, else negative errno.
|
||||
*
|
||||
* Caller must have called @uvc_setup(). Caller is also responsible for
|
||||
* calling @uvc_cleanup() before module unload.
|
||||
*/
|
||||
int __init
|
||||
uvc_bind_config(struct usb_configuration *c,
|
||||
const struct uvc_descriptor_header * const *fs_control,
|
||||
const struct uvc_descriptor_header * const *ss_control,
|
||||
const struct uvc_descriptor_header * const *fs_streaming,
|
||||
const struct uvc_descriptor_header * const *hs_streaming,
|
||||
const struct uvc_descriptor_header * const *ss_streaming)
|
||||
{
|
||||
struct uvc_device *uvc;
|
||||
int ret = 0;
|
||||
|
||||
/* TODO Check if the USB device controller supports the required
|
||||
* features.
|
||||
*/
|
||||
if (!gadget_is_dualspeed(c->cdev->gadget))
|
||||
return -EINVAL;
|
||||
|
||||
uvc = kzalloc(sizeof(*uvc), GFP_KERNEL);
|
||||
if (uvc == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
uvc->state = UVC_STATE_DISCONNECTED;
|
||||
|
||||
/* Validate the descriptors. */
|
||||
if (fs_control == NULL || fs_control[0] == NULL ||
|
||||
fs_control[0]->bDescriptorSubType != UVC_VC_HEADER)
|
||||
goto error;
|
||||
|
||||
if (ss_control == NULL || ss_control[0] == NULL ||
|
||||
ss_control[0]->bDescriptorSubType != UVC_VC_HEADER)
|
||||
goto error;
|
||||
|
||||
if (fs_streaming == NULL || fs_streaming[0] == NULL ||
|
||||
fs_streaming[0]->bDescriptorSubType != UVC_VS_INPUT_HEADER)
|
||||
goto error;
|
||||
|
||||
if (hs_streaming == NULL || hs_streaming[0] == NULL ||
|
||||
hs_streaming[0]->bDescriptorSubType != UVC_VS_INPUT_HEADER)
|
||||
goto error;
|
||||
|
||||
if (ss_streaming == NULL || ss_streaming[0] == NULL ||
|
||||
ss_streaming[0]->bDescriptorSubType != UVC_VS_INPUT_HEADER)
|
||||
goto error;
|
||||
|
||||
uvc->desc.fs_control = fs_control;
|
||||
uvc->desc.ss_control = ss_control;
|
||||
uvc->desc.fs_streaming = fs_streaming;
|
||||
uvc->desc.hs_streaming = hs_streaming;
|
||||
uvc->desc.ss_streaming = ss_streaming;
|
||||
|
||||
/* String descriptors are global, we only need to allocate string IDs
|
||||
* for the first UVC function. UVC functions beyond the first (if any)
|
||||
* will reuse the same IDs.
|
||||
*/
|
||||
if (uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id == 0) {
|
||||
ret = usb_string_ids_tab(c->cdev, uvc_en_us_strings);
|
||||
if (ret)
|
||||
goto error;
|
||||
uvc_iad.iFunction =
|
||||
uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id;
|
||||
uvc_control_intf.iInterface =
|
||||
uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id;
|
||||
ret = uvc_en_us_strings[UVC_STRING_STREAMING_IDX].id;
|
||||
uvc_streaming_intf_alt0.iInterface = ret;
|
||||
uvc_streaming_intf_alt1.iInterface = ret;
|
||||
}
|
||||
|
||||
/* Register the function. */
|
||||
uvc->func.name = "uvc";
|
||||
uvc->func.strings = uvc_function_strings;
|
||||
uvc->func.bind = uvc_function_bind;
|
||||
uvc->func.unbind = uvc_function_unbind;
|
||||
uvc->func.get_alt = uvc_function_get_alt;
|
||||
uvc->func.set_alt = uvc_function_set_alt;
|
||||
uvc->func.disable = uvc_function_disable;
|
||||
uvc->func.setup = uvc_function_setup;
|
||||
|
||||
ret = usb_add_function(c, &uvc->func);
|
||||
if (ret)
|
||||
kfree(uvc);
|
||||
|
||||
return ret;
|
||||
|
||||
error:
|
||||
kfree(uvc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
module_param_named(trace, uvc_gadget_trace_param, uint, S_IRUGO|S_IWUSR);
|
||||
MODULE_PARM_DESC(trace, "Trace level bitmask");
|
||||
|
||||
27
drivers/usb/gadget/function/f_uvc.h
Normal file
27
drivers/usb/gadget/function/f_uvc.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* f_uvc.h -- USB Video Class Gadget driver
|
||||
*
|
||||
* Copyright (C) 2009-2010
|
||||
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _F_UVC_H_
|
||||
#define _F_UVC_H_
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/usb/video.h>
|
||||
|
||||
int uvc_bind_config(struct usb_configuration *c,
|
||||
const struct uvc_descriptor_header * const *fs_control,
|
||||
const struct uvc_descriptor_header * const *hs_control,
|
||||
const struct uvc_descriptor_header * const *fs_streaming,
|
||||
const struct uvc_descriptor_header * const *hs_streaming,
|
||||
const struct uvc_descriptor_header * const *ss_streaming);
|
||||
|
||||
#endif /* _F_UVC_H_ */
|
||||
|
||||
67
drivers/usb/gadget/function/g_zero.h
Normal file
67
drivers/usb/gadget/function/g_zero.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* This header declares the utility functions used by "Gadget Zero", plus
|
||||
* interfaces to its two single-configuration function drivers.
|
||||
*/
|
||||
|
||||
#ifndef __G_ZERO_H
|
||||
#define __G_ZERO_H
|
||||
|
||||
#define GZERO_BULK_BUFLEN 4096
|
||||
#define GZERO_QLEN 32
|
||||
#define GZERO_ISOC_INTERVAL 4
|
||||
#define GZERO_ISOC_MAXPACKET 1024
|
||||
|
||||
struct usb_zero_options {
|
||||
unsigned pattern;
|
||||
unsigned isoc_interval;
|
||||
unsigned isoc_maxpacket;
|
||||
unsigned isoc_mult;
|
||||
unsigned isoc_maxburst;
|
||||
unsigned bulk_buflen;
|
||||
unsigned qlen;
|
||||
};
|
||||
|
||||
struct f_ss_opts {
|
||||
struct usb_function_instance func_inst;
|
||||
unsigned pattern;
|
||||
unsigned isoc_interval;
|
||||
unsigned isoc_maxpacket;
|
||||
unsigned isoc_mult;
|
||||
unsigned isoc_maxburst;
|
||||
unsigned bulk_buflen;
|
||||
|
||||
/*
|
||||
* Read/write access to configfs attributes is handled by configfs.
|
||||
*
|
||||
* This is to protect the data from concurrent access by read/write
|
||||
* and create symlink/remove symlink.
|
||||
*/
|
||||
struct mutex lock;
|
||||
int refcnt;
|
||||
};
|
||||
|
||||
struct f_lb_opts {
|
||||
struct usb_function_instance func_inst;
|
||||
unsigned bulk_buflen;
|
||||
unsigned qlen;
|
||||
|
||||
/*
|
||||
* Read/write access to configfs attributes is handled by configfs.
|
||||
*
|
||||
* This is to protect the data from concurrent access by read/write
|
||||
* and create symlink/remove symlink.
|
||||
*/
|
||||
struct mutex lock;
|
||||
int refcnt;
|
||||
};
|
||||
|
||||
void lb_modexit(void);
|
||||
int lb_modinit(void);
|
||||
|
||||
/* common utilities */
|
||||
void free_ep_req(struct usb_ep *ep, struct usb_request *req);
|
||||
void disable_endpoints(struct usb_composite_dev *cdev,
|
||||
struct usb_ep *in, struct usb_ep *out,
|
||||
struct usb_ep *iso_in, struct usb_ep *iso_out);
|
||||
|
||||
#endif /* __G_ZERO_H */
|
||||
47
drivers/usb/gadget/function/ndis.h
Normal file
47
drivers/usb/gadget/function/ndis.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* ndis.h
|
||||
*
|
||||
* ntddndis.h modified by Benedikt Spranger <b.spranger@pengutronix.de>
|
||||
*
|
||||
* Thanks to the cygwin development team,
|
||||
* espacially to Casper S. Hornstrup <chorns@users.sourceforge.net>
|
||||
*
|
||||
* THIS SOFTWARE IS NOT COPYRIGHTED
|
||||
*
|
||||
* This source code is offered for use in the public domain. You may
|
||||
* use, modify or distribute it freely.
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_NDIS_H
|
||||
#define _LINUX_NDIS_H
|
||||
|
||||
enum NDIS_DEVICE_POWER_STATE {
|
||||
NdisDeviceStateUnspecified = 0,
|
||||
NdisDeviceStateD0,
|
||||
NdisDeviceStateD1,
|
||||
NdisDeviceStateD2,
|
||||
NdisDeviceStateD3,
|
||||
NdisDeviceStateMaximum
|
||||
};
|
||||
|
||||
struct NDIS_PM_WAKE_UP_CAPABILITIES {
|
||||
enum NDIS_DEVICE_POWER_STATE MinMagicPacketWakeUp;
|
||||
enum NDIS_DEVICE_POWER_STATE MinPatternWakeUp;
|
||||
enum NDIS_DEVICE_POWER_STATE MinLinkChangeWakeUp;
|
||||
};
|
||||
|
||||
struct NDIS_PNP_CAPABILITIES {
|
||||
__le32 Flags;
|
||||
struct NDIS_PM_WAKE_UP_CAPABILITIES WakeUpCapabilities;
|
||||
};
|
||||
|
||||
struct NDIS_PM_PACKET_PATTERN {
|
||||
__le32 Priority;
|
||||
__le32 Reserved;
|
||||
__le32 MaskSize;
|
||||
__le32 PatternOffset;
|
||||
__le32 PatternSize;
|
||||
__le32 PatternFlags;
|
||||
};
|
||||
|
||||
#endif /* _LINUX_NDIS_H */
|
||||
1190
drivers/usb/gadget/function/rndis.c
Normal file
1190
drivers/usb/gadget/function/rndis.c
Normal file
File diff suppressed because it is too large
Load diff
220
drivers/usb/gadget/function/rndis.h
Normal file
220
drivers/usb/gadget/function/rndis.h
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* RNDIS Definitions for Remote NDIS
|
||||
*
|
||||
* Authors: Benedikt Spranger, Pengutronix
|
||||
* Robert Schwebel, Pengutronix
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This software was originally developed in conformance with
|
||||
* Microsoft's Remote NDIS Specification License Agreement.
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_RNDIS_H
|
||||
#define _LINUX_RNDIS_H
|
||||
|
||||
#include <linux/rndis.h>
|
||||
#include "u_ether.h"
|
||||
#include "ndis.h"
|
||||
|
||||
#define RNDIS_MAXIMUM_FRAME_SIZE 1518
|
||||
#define RNDIS_MAX_TOTAL_SIZE 1558
|
||||
|
||||
typedef struct rndis_init_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 MajorVersion;
|
||||
__le32 MinorVersion;
|
||||
__le32 MaxTransferSize;
|
||||
} rndis_init_msg_type;
|
||||
|
||||
typedef struct rndis_init_cmplt_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 Status;
|
||||
__le32 MajorVersion;
|
||||
__le32 MinorVersion;
|
||||
__le32 DeviceFlags;
|
||||
__le32 Medium;
|
||||
__le32 MaxPacketsPerTransfer;
|
||||
__le32 MaxTransferSize;
|
||||
__le32 PacketAlignmentFactor;
|
||||
__le32 AFListOffset;
|
||||
__le32 AFListSize;
|
||||
} rndis_init_cmplt_type;
|
||||
|
||||
typedef struct rndis_halt_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
} rndis_halt_msg_type;
|
||||
|
||||
typedef struct rndis_query_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 OID;
|
||||
__le32 InformationBufferLength;
|
||||
__le32 InformationBufferOffset;
|
||||
__le32 DeviceVcHandle;
|
||||
} rndis_query_msg_type;
|
||||
|
||||
typedef struct rndis_query_cmplt_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 Status;
|
||||
__le32 InformationBufferLength;
|
||||
__le32 InformationBufferOffset;
|
||||
} rndis_query_cmplt_type;
|
||||
|
||||
typedef struct rndis_set_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 OID;
|
||||
__le32 InformationBufferLength;
|
||||
__le32 InformationBufferOffset;
|
||||
__le32 DeviceVcHandle;
|
||||
} rndis_set_msg_type;
|
||||
|
||||
typedef struct rndis_set_cmplt_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 Status;
|
||||
} rndis_set_cmplt_type;
|
||||
|
||||
typedef struct rndis_reset_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 Reserved;
|
||||
} rndis_reset_msg_type;
|
||||
|
||||
typedef struct rndis_reset_cmplt_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 Status;
|
||||
__le32 AddressingReset;
|
||||
} rndis_reset_cmplt_type;
|
||||
|
||||
typedef struct rndis_indicate_status_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 Status;
|
||||
__le32 StatusBufferLength;
|
||||
__le32 StatusBufferOffset;
|
||||
} rndis_indicate_status_msg_type;
|
||||
|
||||
typedef struct rndis_keepalive_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
} rndis_keepalive_msg_type;
|
||||
|
||||
typedef struct rndis_keepalive_cmplt_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 RequestID;
|
||||
__le32 Status;
|
||||
} rndis_keepalive_cmplt_type;
|
||||
|
||||
struct rndis_packet_msg_type
|
||||
{
|
||||
__le32 MessageType;
|
||||
__le32 MessageLength;
|
||||
__le32 DataOffset;
|
||||
__le32 DataLength;
|
||||
__le32 OOBDataOffset;
|
||||
__le32 OOBDataLength;
|
||||
__le32 NumOOBDataElements;
|
||||
__le32 PerPacketInfoOffset;
|
||||
__le32 PerPacketInfoLength;
|
||||
__le32 VcHandle;
|
||||
__le32 Reserved;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct rndis_config_parameter
|
||||
{
|
||||
__le32 ParameterNameOffset;
|
||||
__le32 ParameterNameLength;
|
||||
__le32 ParameterType;
|
||||
__le32 ParameterValueOffset;
|
||||
__le32 ParameterValueLength;
|
||||
};
|
||||
|
||||
/* implementation specific */
|
||||
enum rndis_state
|
||||
{
|
||||
RNDIS_UNINITIALIZED,
|
||||
RNDIS_INITIALIZED,
|
||||
RNDIS_DATA_INITIALIZED,
|
||||
};
|
||||
|
||||
typedef struct rndis_resp_t
|
||||
{
|
||||
struct list_head list;
|
||||
u8 *buf;
|
||||
u32 length;
|
||||
int send;
|
||||
} rndis_resp_t;
|
||||
|
||||
typedef struct rndis_params
|
||||
{
|
||||
u8 confignr;
|
||||
u8 used;
|
||||
u16 saved_filter;
|
||||
enum rndis_state state;
|
||||
u32 medium;
|
||||
u32 speed;
|
||||
u32 media_state;
|
||||
|
||||
const u8 *host_mac;
|
||||
u16 *filter;
|
||||
struct net_device *dev;
|
||||
|
||||
u32 vendorID;
|
||||
const char *vendorDescr;
|
||||
void (*resp_avail)(void *v);
|
||||
void *v;
|
||||
struct list_head resp_queue;
|
||||
} rndis_params;
|
||||
|
||||
/* RNDIS Message parser and other useless functions */
|
||||
int rndis_msg_parser (u8 configNr, u8 *buf);
|
||||
int rndis_register(void (*resp_avail)(void *v), void *v);
|
||||
void rndis_deregister (int configNr);
|
||||
int rndis_set_param_dev (u8 configNr, struct net_device *dev,
|
||||
u16 *cdc_filter);
|
||||
int rndis_set_param_vendor (u8 configNr, u32 vendorID,
|
||||
const char *vendorDescr);
|
||||
int rndis_set_param_medium (u8 configNr, u32 medium, u32 speed);
|
||||
void rndis_add_hdr (struct sk_buff *skb);
|
||||
int rndis_rm_hdr(struct gether *port, struct sk_buff *skb,
|
||||
struct sk_buff_head *list);
|
||||
u8 *rndis_get_next_response (int configNr, u32 *length);
|
||||
void rndis_free_response (int configNr, u8 *buf);
|
||||
|
||||
void rndis_uninit (int configNr);
|
||||
int rndis_signal_connect (int configNr);
|
||||
int rndis_signal_disconnect (int configNr);
|
||||
int rndis_state (int configNr);
|
||||
extern void rndis_set_host_mac (int configNr, const u8 *addr);
|
||||
|
||||
#endif /* _LINUX_RNDIS_H */
|
||||
504
drivers/usb/gadget/function/storage_common.c
Normal file
504
drivers/usb/gadget/function/storage_common.c
Normal file
|
|
@ -0,0 +1,504 @@
|
|||
/*
|
||||
* storage_common.c -- Common definitions for mass storage functionality
|
||||
*
|
||||
* Copyright (C) 2003-2008 Alan Stern
|
||||
* Copyeight (C) 2009 Samsung Electronics
|
||||
* Author: Michal Nazarewicz (mina86@mina86.com)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file requires the following identifiers used in USB strings to
|
||||
* be defined (each of type pointer to char):
|
||||
* - fsg_string_interface -- name of the interface
|
||||
*/
|
||||
|
||||
/*
|
||||
* When USB_GADGET_DEBUG_FILES is defined the module param num_buffers
|
||||
* sets the number of pipeline buffers (length of the fsg_buffhd array).
|
||||
* The valid range of num_buffers is: num >= 2 && num <= 4.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
#include "storage_common.h"
|
||||
|
||||
/* There is only one interface. */
|
||||
|
||||
struct usb_interface_descriptor fsg_intf_desc = {
|
||||
.bLength = sizeof fsg_intf_desc,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
.bNumEndpoints = 2, /* Adjusted during fsg_bind() */
|
||||
.bInterfaceClass = USB_CLASS_MASS_STORAGE,
|
||||
.bInterfaceSubClass = USB_SC_SCSI, /* Adjusted during fsg_bind() */
|
||||
.bInterfaceProtocol = USB_PR_BULK, /* Adjusted during fsg_bind() */
|
||||
.iInterface = FSG_STRING_INTERFACE,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_intf_desc);
|
||||
|
||||
/*
|
||||
* Three full-speed endpoint descriptors: bulk-in, bulk-out, and
|
||||
* interrupt-in.
|
||||
*/
|
||||
|
||||
struct usb_endpoint_descriptor fsg_fs_bulk_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
/* wMaxPacketSize set by autoconfiguration */
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_fs_bulk_in_desc);
|
||||
|
||||
struct usb_endpoint_descriptor fsg_fs_bulk_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
/* wMaxPacketSize set by autoconfiguration */
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_fs_bulk_out_desc);
|
||||
|
||||
struct usb_descriptor_header *fsg_fs_function[] = {
|
||||
(struct usb_descriptor_header *) &fsg_intf_desc,
|
||||
(struct usb_descriptor_header *) &fsg_fs_bulk_in_desc,
|
||||
(struct usb_descriptor_header *) &fsg_fs_bulk_out_desc,
|
||||
NULL,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_fs_function);
|
||||
|
||||
|
||||
/*
|
||||
* USB 2.0 devices need to expose both high speed and full speed
|
||||
* descriptors, unless they only run at full speed.
|
||||
*
|
||||
* That means alternate endpoint descriptors (bigger packets)
|
||||
* and a "device qualifier" ... plus more construction options
|
||||
* for the configuration descriptor.
|
||||
*/
|
||||
struct usb_endpoint_descriptor fsg_hs_bulk_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
/* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(512),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_hs_bulk_in_desc);
|
||||
|
||||
struct usb_endpoint_descriptor fsg_hs_bulk_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
/* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(512),
|
||||
.bInterval = 1, /* NAK every 1 uframe */
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_hs_bulk_out_desc);
|
||||
|
||||
|
||||
struct usb_descriptor_header *fsg_hs_function[] = {
|
||||
(struct usb_descriptor_header *) &fsg_intf_desc,
|
||||
(struct usb_descriptor_header *) &fsg_hs_bulk_in_desc,
|
||||
(struct usb_descriptor_header *) &fsg_hs_bulk_out_desc,
|
||||
NULL,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_hs_function);
|
||||
|
||||
struct usb_endpoint_descriptor fsg_ss_bulk_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
/* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(1024),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_ss_bulk_in_desc);
|
||||
|
||||
struct usb_ss_ep_comp_descriptor fsg_ss_bulk_in_comp_desc = {
|
||||
.bLength = sizeof(fsg_ss_bulk_in_comp_desc),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
|
||||
/*.bMaxBurst = DYNAMIC, */
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_ss_bulk_in_comp_desc);
|
||||
|
||||
struct usb_endpoint_descriptor fsg_ss_bulk_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
/* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = cpu_to_le16(1024),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_ss_bulk_out_desc);
|
||||
|
||||
struct usb_ss_ep_comp_descriptor fsg_ss_bulk_out_comp_desc = {
|
||||
.bLength = sizeof(fsg_ss_bulk_in_comp_desc),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
|
||||
/*.bMaxBurst = DYNAMIC, */
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_ss_bulk_out_comp_desc);
|
||||
|
||||
struct usb_descriptor_header *fsg_ss_function[] = {
|
||||
(struct usb_descriptor_header *) &fsg_intf_desc,
|
||||
(struct usb_descriptor_header *) &fsg_ss_bulk_in_desc,
|
||||
(struct usb_descriptor_header *) &fsg_ss_bulk_in_comp_desc,
|
||||
(struct usb_descriptor_header *) &fsg_ss_bulk_out_desc,
|
||||
(struct usb_descriptor_header *) &fsg_ss_bulk_out_comp_desc,
|
||||
NULL,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsg_ss_function);
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* If the next two routines are called while the gadget is registered,
|
||||
* the caller must own fsg->filesem for writing.
|
||||
*/
|
||||
|
||||
void fsg_lun_close(struct fsg_lun *curlun)
|
||||
{
|
||||
if (curlun->filp) {
|
||||
LDBG(curlun, "close backing file\n");
|
||||
fput(curlun->filp);
|
||||
curlun->filp = NULL;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_lun_close);
|
||||
|
||||
int fsg_lun_open(struct fsg_lun *curlun, const char *filename)
|
||||
{
|
||||
int ro;
|
||||
struct file *filp = NULL;
|
||||
int rc = -EINVAL;
|
||||
struct inode *inode = NULL;
|
||||
loff_t size;
|
||||
loff_t num_sectors;
|
||||
loff_t min_sectors;
|
||||
unsigned int blkbits;
|
||||
unsigned int blksize;
|
||||
|
||||
/* R/W if we can, R/O if we must */
|
||||
ro = curlun->initially_ro;
|
||||
if (!ro) {
|
||||
filp = filp_open(filename, O_RDWR | O_LARGEFILE, 0);
|
||||
if (PTR_ERR(filp) == -EROFS || PTR_ERR(filp) == -EACCES)
|
||||
ro = 1;
|
||||
}
|
||||
if (ro)
|
||||
filp = filp_open(filename, O_RDONLY | O_LARGEFILE, 0);
|
||||
if (IS_ERR(filp)) {
|
||||
LINFO(curlun, "unable to open backing file: %s\n", filename);
|
||||
return PTR_ERR(filp);
|
||||
}
|
||||
|
||||
if (!(filp->f_mode & FMODE_WRITE))
|
||||
ro = 1;
|
||||
|
||||
inode = file_inode(filp);
|
||||
if ((!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))) {
|
||||
LINFO(curlun, "invalid file type: %s\n", filename);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we can't read the file, it's no good.
|
||||
* If we can't write the file, use it read-only.
|
||||
*/
|
||||
if (!(filp->f_mode & FMODE_CAN_READ)) {
|
||||
LINFO(curlun, "file not readable: %s\n", filename);
|
||||
goto out;
|
||||
}
|
||||
if (!(filp->f_mode & FMODE_CAN_WRITE))
|
||||
ro = 1;
|
||||
|
||||
size = i_size_read(inode->i_mapping->host);
|
||||
if (size < 0) {
|
||||
LINFO(curlun, "unable to find file size: %s\n", filename);
|
||||
rc = (int) size;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (curlun->cdrom) {
|
||||
blksize = 2048;
|
||||
blkbits = 11;
|
||||
} else if (inode->i_bdev) {
|
||||
blksize = bdev_logical_block_size(inode->i_bdev);
|
||||
blkbits = blksize_bits(blksize);
|
||||
} else {
|
||||
blksize = 512;
|
||||
blkbits = 9;
|
||||
}
|
||||
|
||||
num_sectors = size >> blkbits; /* File size in logic-block-size blocks */
|
||||
min_sectors = 1;
|
||||
if (curlun->cdrom) {
|
||||
min_sectors = 300; /* Smallest track is 300 frames */
|
||||
if (num_sectors >= 256*60*75) {
|
||||
num_sectors = 256*60*75 - 1;
|
||||
LINFO(curlun, "file too big: %s\n", filename);
|
||||
LINFO(curlun, "using only first %d blocks\n",
|
||||
(int) num_sectors);
|
||||
}
|
||||
}
|
||||
if (num_sectors < min_sectors) {
|
||||
LINFO(curlun, "file too small: %s\n", filename);
|
||||
rc = -ETOOSMALL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fsg_lun_is_open(curlun))
|
||||
fsg_lun_close(curlun);
|
||||
|
||||
curlun->blksize = blksize;
|
||||
curlun->blkbits = blkbits;
|
||||
curlun->ro = ro;
|
||||
curlun->filp = filp;
|
||||
curlun->file_length = size;
|
||||
curlun->num_sectors = num_sectors;
|
||||
LDBG(curlun, "open backing file: %s\n", filename);
|
||||
return 0;
|
||||
|
||||
out:
|
||||
fput(filp);
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_lun_open);
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Sync the file data, don't bother with the metadata.
|
||||
* This code was copied from fs/buffer.c:sys_fdatasync().
|
||||
*/
|
||||
int fsg_lun_fsync_sub(struct fsg_lun *curlun)
|
||||
{
|
||||
struct file *filp = curlun->filp;
|
||||
|
||||
if (curlun->ro || !filp)
|
||||
return 0;
|
||||
return vfs_fsync(filp, 1);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_lun_fsync_sub);
|
||||
|
||||
void store_cdrom_address(u8 *dest, int msf, u32 addr)
|
||||
{
|
||||
if (msf) {
|
||||
/* Convert to Minutes-Seconds-Frames */
|
||||
addr >>= 2; /* Convert to 2048-byte frames */
|
||||
addr += 2*75; /* Lead-in occupies 2 seconds */
|
||||
dest[3] = addr % 75; /* Frames */
|
||||
addr /= 75;
|
||||
dest[2] = addr % 60; /* Seconds */
|
||||
addr /= 60;
|
||||
dest[1] = addr; /* Minutes */
|
||||
dest[0] = 0; /* Reserved */
|
||||
} else {
|
||||
/* Absolute sector */
|
||||
put_unaligned_be32(addr, dest);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(store_cdrom_address);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
ssize_t fsg_show_ro(struct fsg_lun *curlun, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", fsg_lun_is_open(curlun)
|
||||
? curlun->ro
|
||||
: curlun->initially_ro);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_show_ro);
|
||||
|
||||
ssize_t fsg_show_nofua(struct fsg_lun *curlun, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n", curlun->nofua);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_show_nofua);
|
||||
|
||||
ssize_t fsg_show_file(struct fsg_lun *curlun, struct rw_semaphore *filesem,
|
||||
char *buf)
|
||||
{
|
||||
char *p;
|
||||
ssize_t rc;
|
||||
|
||||
down_read(filesem);
|
||||
if (fsg_lun_is_open(curlun)) { /* Get the complete pathname */
|
||||
p = d_path(&curlun->filp->f_path, buf, PAGE_SIZE - 1);
|
||||
if (IS_ERR(p))
|
||||
rc = PTR_ERR(p);
|
||||
else {
|
||||
rc = strlen(p);
|
||||
memmove(buf, p, rc);
|
||||
buf[rc] = '\n'; /* Add a newline */
|
||||
buf[++rc] = 0;
|
||||
}
|
||||
} else { /* No file, return 0 bytes */
|
||||
*buf = 0;
|
||||
rc = 0;
|
||||
}
|
||||
up_read(filesem);
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_show_file);
|
||||
|
||||
ssize_t fsg_show_cdrom(struct fsg_lun *curlun, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n", curlun->cdrom);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_show_cdrom);
|
||||
|
||||
ssize_t fsg_show_removable(struct fsg_lun *curlun, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n", curlun->removable);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_show_removable);
|
||||
|
||||
/*
|
||||
* The caller must hold fsg->filesem for reading when calling this function.
|
||||
*/
|
||||
static ssize_t _fsg_store_ro(struct fsg_lun *curlun, bool ro)
|
||||
{
|
||||
if (fsg_lun_is_open(curlun)) {
|
||||
LDBG(curlun, "read-only status change prevented\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
curlun->ro = ro;
|
||||
curlun->initially_ro = ro;
|
||||
LDBG(curlun, "read-only status set to %d\n", curlun->ro);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t fsg_store_ro(struct fsg_lun *curlun, struct rw_semaphore *filesem,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
ssize_t rc;
|
||||
bool ro;
|
||||
|
||||
rc = strtobool(buf, &ro);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* Allow the write-enable status to change only while the
|
||||
* backing file is closed.
|
||||
*/
|
||||
down_read(filesem);
|
||||
rc = _fsg_store_ro(curlun, ro);
|
||||
if (!rc)
|
||||
rc = count;
|
||||
up_read(filesem);
|
||||
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_store_ro);
|
||||
|
||||
ssize_t fsg_store_nofua(struct fsg_lun *curlun, const char *buf, size_t count)
|
||||
{
|
||||
bool nofua;
|
||||
int ret;
|
||||
|
||||
ret = strtobool(buf, &nofua);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Sync data when switching from async mode to sync */
|
||||
if (!nofua && curlun->nofua)
|
||||
fsg_lun_fsync_sub(curlun);
|
||||
|
||||
curlun->nofua = nofua;
|
||||
|
||||
return count;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_store_nofua);
|
||||
|
||||
ssize_t fsg_store_file(struct fsg_lun *curlun, struct rw_semaphore *filesem,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (curlun->prevent_medium_removal && fsg_lun_is_open(curlun)) {
|
||||
LDBG(curlun, "eject attempt prevented\n");
|
||||
return -EBUSY; /* "Door is locked" */
|
||||
}
|
||||
|
||||
/* Remove a trailing newline */
|
||||
if (count > 0 && buf[count-1] == '\n')
|
||||
((char *) buf)[count-1] = 0; /* Ugh! */
|
||||
|
||||
/* Load new medium */
|
||||
down_write(filesem);
|
||||
if (count > 0 && buf[0]) {
|
||||
/* fsg_lun_open() will close existing file if any. */
|
||||
rc = fsg_lun_open(curlun, buf);
|
||||
if (rc == 0)
|
||||
curlun->unit_attention_data =
|
||||
SS_NOT_READY_TO_READY_TRANSITION;
|
||||
} else if (fsg_lun_is_open(curlun)) {
|
||||
fsg_lun_close(curlun);
|
||||
curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT;
|
||||
}
|
||||
up_write(filesem);
|
||||
return (rc < 0 ? rc : count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_store_file);
|
||||
|
||||
ssize_t fsg_store_cdrom(struct fsg_lun *curlun, struct rw_semaphore *filesem,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
bool cdrom;
|
||||
int ret;
|
||||
|
||||
ret = strtobool(buf, &cdrom);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
down_read(filesem);
|
||||
ret = cdrom ? _fsg_store_ro(curlun, true) : 0;
|
||||
|
||||
if (!ret) {
|
||||
curlun->cdrom = cdrom;
|
||||
ret = count;
|
||||
}
|
||||
up_read(filesem);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_store_cdrom);
|
||||
|
||||
ssize_t fsg_store_removable(struct fsg_lun *curlun, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
bool removable;
|
||||
int ret;
|
||||
|
||||
ret = strtobool(buf, &removable);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
curlun->removable = removable;
|
||||
|
||||
return count;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_store_removable);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
225
drivers/usb/gadget/function/storage_common.h
Normal file
225
drivers/usb/gadget/function/storage_common.h
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
#ifndef USB_STORAGE_COMMON_H
|
||||
#define USB_STORAGE_COMMON_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/usb/storage.h>
|
||||
#include <scsi/scsi.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#ifndef DEBUG
|
||||
#undef VERBOSE_DEBUG
|
||||
#undef DUMP_MSGS
|
||||
#endif /* !DEBUG */
|
||||
|
||||
#ifdef VERBOSE_DEBUG
|
||||
#define VLDBG LDBG
|
||||
#else
|
||||
#define VLDBG(lun, fmt, args...) do { } while (0)
|
||||
#endif /* VERBOSE_DEBUG */
|
||||
|
||||
#define _LMSG(func, lun, fmt, args...) \
|
||||
do { \
|
||||
if ((lun)->name_pfx && *(lun)->name_pfx) \
|
||||
func("%s/%s: " fmt, *(lun)->name_pfx, \
|
||||
(lun)->name, ## args); \
|
||||
else \
|
||||
func("%s: " fmt, (lun)->name, ## args); \
|
||||
} while (0)
|
||||
|
||||
#define LDBG(lun, fmt, args...) _LMSG(pr_debug, lun, fmt, ## args)
|
||||
#define LERROR(lun, fmt, args...) _LMSG(pr_err, lun, fmt, ## args)
|
||||
#define LWARN(lun, fmt, args...) _LMSG(pr_warn, lun, fmt, ## args)
|
||||
#define LINFO(lun, fmt, args...) _LMSG(pr_info, lun, fmt, ## args)
|
||||
|
||||
|
||||
#ifdef DUMP_MSGS
|
||||
|
||||
# define dump_msg(fsg, /* const char * */ label, \
|
||||
/* const u8 * */ buf, /* unsigned */ length) \
|
||||
do { \
|
||||
if (length < 512) { \
|
||||
DBG(fsg, "%s, length %u:\n", label, length); \
|
||||
print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, \
|
||||
16, 1, buf, length, 0); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
# define dump_cdb(fsg) do { } while (0)
|
||||
|
||||
#else
|
||||
|
||||
# define dump_msg(fsg, /* const char * */ label, \
|
||||
/* const u8 * */ buf, /* unsigned */ length) do { } while (0)
|
||||
|
||||
# ifdef VERBOSE_DEBUG
|
||||
|
||||
# define dump_cdb(fsg) \
|
||||
print_hex_dump(KERN_DEBUG, "SCSI CDB: ", DUMP_PREFIX_NONE, \
|
||||
16, 1, (fsg)->cmnd, (fsg)->cmnd_size, 0) \
|
||||
|
||||
# else
|
||||
|
||||
# define dump_cdb(fsg) do { } while (0)
|
||||
|
||||
# endif /* VERBOSE_DEBUG */
|
||||
|
||||
#endif /* DUMP_MSGS */
|
||||
|
||||
/* Length of a SCSI Command Data Block */
|
||||
#define MAX_COMMAND_SIZE 16
|
||||
|
||||
/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */
|
||||
#define SS_NO_SENSE 0
|
||||
#define SS_COMMUNICATION_FAILURE 0x040800
|
||||
#define SS_INVALID_COMMAND 0x052000
|
||||
#define SS_INVALID_FIELD_IN_CDB 0x052400
|
||||
#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100
|
||||
#define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500
|
||||
#define SS_MEDIUM_NOT_PRESENT 0x023a00
|
||||
#define SS_MEDIUM_REMOVAL_PREVENTED 0x055302
|
||||
#define SS_NOT_READY_TO_READY_TRANSITION 0x062800
|
||||
#define SS_RESET_OCCURRED 0x062900
|
||||
#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900
|
||||
#define SS_UNRECOVERED_READ_ERROR 0x031100
|
||||
#define SS_WRITE_ERROR 0x030c02
|
||||
#define SS_WRITE_PROTECTED 0x072700
|
||||
|
||||
#define SK(x) ((u8) ((x) >> 16)) /* Sense Key byte, etc. */
|
||||
#define ASC(x) ((u8) ((x) >> 8))
|
||||
#define ASCQ(x) ((u8) (x))
|
||||
|
||||
struct fsg_lun {
|
||||
struct file *filp;
|
||||
loff_t file_length;
|
||||
loff_t num_sectors;
|
||||
|
||||
unsigned int initially_ro:1;
|
||||
unsigned int ro:1;
|
||||
unsigned int removable:1;
|
||||
unsigned int cdrom:1;
|
||||
unsigned int prevent_medium_removal:1;
|
||||
unsigned int registered:1;
|
||||
unsigned int info_valid:1;
|
||||
unsigned int nofua:1;
|
||||
|
||||
u32 sense_data;
|
||||
u32 sense_data_info;
|
||||
u32 unit_attention_data;
|
||||
|
||||
unsigned int blkbits; /* Bits of logical block size
|
||||
of bound block device */
|
||||
unsigned int blksize; /* logical block size of bound block device */
|
||||
struct device dev;
|
||||
const char *name; /* "lun.name" */
|
||||
const char **name_pfx; /* "function.name" */
|
||||
};
|
||||
|
||||
static inline bool fsg_lun_is_open(struct fsg_lun *curlun)
|
||||
{
|
||||
return curlun->filp != NULL;
|
||||
}
|
||||
|
||||
/* Default size of buffer length. */
|
||||
#define FSG_BUFLEN ((u32)16384)
|
||||
|
||||
/* Maximal number of LUNs supported in mass storage function */
|
||||
#define FSG_MAX_LUNS 8
|
||||
|
||||
enum fsg_buffer_state {
|
||||
BUF_STATE_EMPTY = 0,
|
||||
BUF_STATE_FULL,
|
||||
BUF_STATE_BUSY
|
||||
};
|
||||
|
||||
struct fsg_buffhd {
|
||||
void *buf;
|
||||
enum fsg_buffer_state state;
|
||||
struct fsg_buffhd *next;
|
||||
|
||||
/*
|
||||
* The NetChip 2280 is faster, and handles some protocol faults
|
||||
* better, if we don't submit any short bulk-out read requests.
|
||||
* So we will record the intended request length here.
|
||||
*/
|
||||
unsigned int bulk_out_intended_length;
|
||||
|
||||
struct usb_request *inreq;
|
||||
int inreq_busy;
|
||||
struct usb_request *outreq;
|
||||
int outreq_busy;
|
||||
};
|
||||
|
||||
enum fsg_state {
|
||||
/* This one isn't used anywhere */
|
||||
FSG_STATE_COMMAND_PHASE = -10,
|
||||
FSG_STATE_DATA_PHASE,
|
||||
FSG_STATE_STATUS_PHASE,
|
||||
|
||||
FSG_STATE_IDLE = 0,
|
||||
FSG_STATE_ABORT_BULK_OUT,
|
||||
FSG_STATE_RESET,
|
||||
FSG_STATE_INTERFACE_CHANGE,
|
||||
FSG_STATE_CONFIG_CHANGE,
|
||||
FSG_STATE_DISCONNECT,
|
||||
FSG_STATE_EXIT,
|
||||
FSG_STATE_TERMINATED
|
||||
};
|
||||
|
||||
enum data_direction {
|
||||
DATA_DIR_UNKNOWN = 0,
|
||||
DATA_DIR_FROM_HOST,
|
||||
DATA_DIR_TO_HOST,
|
||||
DATA_DIR_NONE
|
||||
};
|
||||
|
||||
static inline u32 get_unaligned_be24(u8 *buf)
|
||||
{
|
||||
return 0xffffff & (u32) get_unaligned_be32(buf - 1);
|
||||
}
|
||||
|
||||
static inline struct fsg_lun *fsg_lun_from_dev(struct device *dev)
|
||||
{
|
||||
return container_of(dev, struct fsg_lun, dev);
|
||||
}
|
||||
|
||||
enum {
|
||||
FSG_STRING_INTERFACE
|
||||
};
|
||||
|
||||
extern struct usb_interface_descriptor fsg_intf_desc;
|
||||
|
||||
extern struct usb_endpoint_descriptor fsg_fs_bulk_in_desc;
|
||||
extern struct usb_endpoint_descriptor fsg_fs_bulk_out_desc;
|
||||
extern struct usb_descriptor_header *fsg_fs_function[];
|
||||
|
||||
extern struct usb_endpoint_descriptor fsg_hs_bulk_in_desc;
|
||||
extern struct usb_endpoint_descriptor fsg_hs_bulk_out_desc;
|
||||
extern struct usb_descriptor_header *fsg_hs_function[];
|
||||
|
||||
extern struct usb_endpoint_descriptor fsg_ss_bulk_in_desc;
|
||||
extern struct usb_ss_ep_comp_descriptor fsg_ss_bulk_in_comp_desc;
|
||||
extern struct usb_endpoint_descriptor fsg_ss_bulk_out_desc;
|
||||
extern struct usb_ss_ep_comp_descriptor fsg_ss_bulk_out_comp_desc;
|
||||
extern struct usb_descriptor_header *fsg_ss_function[];
|
||||
|
||||
void fsg_lun_close(struct fsg_lun *curlun);
|
||||
int fsg_lun_open(struct fsg_lun *curlun, const char *filename);
|
||||
int fsg_lun_fsync_sub(struct fsg_lun *curlun);
|
||||
void store_cdrom_address(u8 *dest, int msf, u32 addr);
|
||||
ssize_t fsg_show_ro(struct fsg_lun *curlun, char *buf);
|
||||
ssize_t fsg_show_nofua(struct fsg_lun *curlun, char *buf);
|
||||
ssize_t fsg_show_file(struct fsg_lun *curlun, struct rw_semaphore *filesem,
|
||||
char *buf);
|
||||
ssize_t fsg_show_cdrom(struct fsg_lun *curlun, char *buf);
|
||||
ssize_t fsg_show_removable(struct fsg_lun *curlun, char *buf);
|
||||
ssize_t fsg_store_ro(struct fsg_lun *curlun, struct rw_semaphore *filesem,
|
||||
const char *buf, size_t count);
|
||||
ssize_t fsg_store_nofua(struct fsg_lun *curlun, const char *buf, size_t count);
|
||||
ssize_t fsg_store_file(struct fsg_lun *curlun, struct rw_semaphore *filesem,
|
||||
const char *buf, size_t count);
|
||||
ssize_t fsg_store_cdrom(struct fsg_lun *curlun, struct rw_semaphore *filesem,
|
||||
const char *buf, size_t count);
|
||||
ssize_t fsg_store_removable(struct fsg_lun *curlun, const char *buf,
|
||||
size_t count);
|
||||
|
||||
#endif /* USB_STORAGE_COMMON_H */
|
||||
36
drivers/usb/gadget/function/u_ecm.h
Normal file
36
drivers/usb/gadget/function/u_ecm.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* u_ecm.h
|
||||
*
|
||||
* Utility definitions for the ecm function
|
||||
*
|
||||
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef U_ECM_H
|
||||
#define U_ECM_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
struct f_ecm_opts {
|
||||
struct usb_function_instance func_inst;
|
||||
struct net_device *net;
|
||||
bool bound;
|
||||
|
||||
/*
|
||||
* Read/write access to configfs attributes is handled by configfs.
|
||||
*
|
||||
* This is to protect the data from concurrent access by read/write
|
||||
* and create symlink/remove symlink.
|
||||
*/
|
||||
struct mutex lock;
|
||||
int refcnt;
|
||||
};
|
||||
|
||||
#endif /* U_ECM_H */
|
||||
36
drivers/usb/gadget/function/u_eem.h
Normal file
36
drivers/usb/gadget/function/u_eem.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* u_eem.h
|
||||
*
|
||||
* Utility definitions for the eem function
|
||||
*
|
||||
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef U_EEM_H
|
||||
#define U_EEM_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
struct f_eem_opts {
|
||||
struct usb_function_instance func_inst;
|
||||
struct net_device *net;
|
||||
bool bound;
|
||||
|
||||
/*
|
||||
* Read/write access to configfs attributes is handled by configfs.
|
||||
*
|
||||
* This is to protect the data from concurrent access by read/write
|
||||
* and create symlink/remove symlink.
|
||||
*/
|
||||
struct mutex lock;
|
||||
int refcnt;
|
||||
};
|
||||
|
||||
#endif /* U_EEM_H */
|
||||
1182
drivers/usb/gadget/function/u_ether.c
Normal file
1182
drivers/usb/gadget/function/u_ether.c
Normal file
File diff suppressed because it is too large
Load diff
272
drivers/usb/gadget/function/u_ether.h
Normal file
272
drivers/usb/gadget/function/u_ether.h
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* u_ether.h -- interface to USB gadget "ethernet link" utilities
|
||||
*
|
||||
* Copyright (C) 2003-2005,2008 David Brownell
|
||||
* Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
|
||||
* Copyright (C) 2008 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.
|
||||
*/
|
||||
|
||||
#ifndef __U_ETHER_H
|
||||
#define __U_ETHER_H
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/usb/cdc.h>
|
||||
#include <linux/netdevice.h>
|
||||
|
||||
#include "gadget_chips.h"
|
||||
|
||||
#define QMULT_DEFAULT 5
|
||||
|
||||
/*
|
||||
* dev_addr: initial value
|
||||
* changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx"
|
||||
* host_addr: this address is invisible to ifconfig
|
||||
*/
|
||||
#define USB_ETHERNET_MODULE_PARAMETERS() \
|
||||
static unsigned qmult = QMULT_DEFAULT; \
|
||||
module_param(qmult, uint, S_IRUGO|S_IWUSR); \
|
||||
MODULE_PARM_DESC(qmult, "queue length multiplier at high/super speed");\
|
||||
\
|
||||
static char *dev_addr; \
|
||||
module_param(dev_addr, charp, S_IRUGO); \
|
||||
MODULE_PARM_DESC(dev_addr, "Device Ethernet Address"); \
|
||||
\
|
||||
static char *host_addr; \
|
||||
module_param(host_addr, charp, S_IRUGO); \
|
||||
MODULE_PARM_DESC(host_addr, "Host Ethernet Address")
|
||||
|
||||
struct eth_dev;
|
||||
|
||||
/*
|
||||
* This represents the USB side of an "ethernet" link, managed by a USB
|
||||
* function which provides control and (maybe) framing. Two functions
|
||||
* in different configurations could share the same ethernet link/netdev,
|
||||
* using different host interaction models.
|
||||
*
|
||||
* There is a current limitation that only one instance of this link may
|
||||
* be present in any given configuration. When that's a problem, network
|
||||
* layer facilities can be used to package multiple logical links on this
|
||||
* single "physical" one.
|
||||
*/
|
||||
struct gether {
|
||||
struct usb_function func;
|
||||
|
||||
/* updated by gether_{connect,disconnect} */
|
||||
struct eth_dev *ioport;
|
||||
|
||||
/* endpoints handle full and/or high speeds */
|
||||
struct usb_ep *in_ep;
|
||||
struct usb_ep *out_ep;
|
||||
|
||||
bool is_zlp_ok;
|
||||
|
||||
u16 cdc_filter;
|
||||
|
||||
/* hooks for added framing, as needed for RNDIS and EEM. */
|
||||
u32 header_len;
|
||||
/* NCM requires fixed size bundles */
|
||||
bool is_fixed;
|
||||
u32 fixed_out_len;
|
||||
u32 fixed_in_len;
|
||||
bool supports_multi_frame;
|
||||
struct sk_buff *(*wrap)(struct gether *port,
|
||||
struct sk_buff *skb);
|
||||
int (*unwrap)(struct gether *port,
|
||||
struct sk_buff *skb,
|
||||
struct sk_buff_head *list);
|
||||
|
||||
/* called on network open/close */
|
||||
void (*open)(struct gether *);
|
||||
void (*close)(struct gether *);
|
||||
};
|
||||
|
||||
#define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \
|
||||
|USB_CDC_PACKET_TYPE_ALL_MULTICAST \
|
||||
|USB_CDC_PACKET_TYPE_PROMISCUOUS \
|
||||
|USB_CDC_PACKET_TYPE_DIRECTED)
|
||||
|
||||
/* variant of gether_setup that allows customizing network device name */
|
||||
struct eth_dev *gether_setup_name(struct usb_gadget *g,
|
||||
const char *dev_addr, const char *host_addr,
|
||||
u8 ethaddr[ETH_ALEN], unsigned qmult, const char *netname);
|
||||
|
||||
/* netdev setup/teardown as directed by the gadget driver */
|
||||
/* gether_setup - initialize one ethernet-over-usb link
|
||||
* @g: gadget to associated with these links
|
||||
* @ethaddr: NULL, or a buffer in which the ethernet address of the
|
||||
* host side of the link is recorded
|
||||
* Context: may sleep
|
||||
*
|
||||
* This sets up the single network link that may be exported by a
|
||||
* gadget driver using this framework. The link layer addresses are
|
||||
* set up using module parameters.
|
||||
*
|
||||
* Returns a eth_dev pointer on success, or an ERR_PTR on failure
|
||||
*/
|
||||
static inline struct eth_dev *gether_setup(struct usb_gadget *g,
|
||||
const char *dev_addr, const char *host_addr,
|
||||
u8 ethaddr[ETH_ALEN], unsigned qmult)
|
||||
{
|
||||
return gether_setup_name(g, dev_addr, host_addr, ethaddr, qmult, "usb");
|
||||
}
|
||||
|
||||
/*
|
||||
* variant of gether_setup_default that allows customizing
|
||||
* network device name
|
||||
*/
|
||||
struct net_device *gether_setup_name_default(const char *netname);
|
||||
|
||||
/*
|
||||
* gether_register_netdev - register the net device
|
||||
* @net: net device to register
|
||||
*
|
||||
* Registers the net device associated with this ethernet-over-usb link
|
||||
*
|
||||
*/
|
||||
int gether_register_netdev(struct net_device *net);
|
||||
|
||||
/* gether_setup_default - initialize one ethernet-over-usb link
|
||||
* Context: may sleep
|
||||
*
|
||||
* This sets up the single network link that may be exported by a
|
||||
* gadget driver using this framework. The link layer addresses
|
||||
* are set to random values.
|
||||
*
|
||||
* Returns negative errno, or zero on success
|
||||
*/
|
||||
static inline struct net_device *gether_setup_default(void)
|
||||
{
|
||||
return gether_setup_name_default("usb");
|
||||
}
|
||||
|
||||
/**
|
||||
* gether_set_gadget - initialize one ethernet-over-usb link with a gadget
|
||||
* @net: device representing this link
|
||||
* @g: the gadget to initialize with
|
||||
*
|
||||
* This associates one ethernet-over-usb link with a gadget.
|
||||
*/
|
||||
void gether_set_gadget(struct net_device *net, struct usb_gadget *g);
|
||||
|
||||
/**
|
||||
* gether_set_dev_addr - initialize an ethernet-over-usb link with eth address
|
||||
* @net: device representing this link
|
||||
* @dev_addr: eth address of this device
|
||||
*
|
||||
* This sets the device-side Ethernet address of this ethernet-over-usb link
|
||||
* if dev_addr is correct.
|
||||
* Returns negative errno if the new address is incorrect.
|
||||
*/
|
||||
int gether_set_dev_addr(struct net_device *net, const char *dev_addr);
|
||||
|
||||
/**
|
||||
* gether_get_dev_addr - get an ethernet-over-usb link eth address
|
||||
* @net: device representing this link
|
||||
* @dev_addr: place to store device's eth address
|
||||
* @len: length of the @dev_addr buffer
|
||||
*
|
||||
* This gets the device-side Ethernet address of this ethernet-over-usb link.
|
||||
* Returns zero on success, else negative errno.
|
||||
*/
|
||||
int gether_get_dev_addr(struct net_device *net, char *dev_addr, int len);
|
||||
|
||||
/**
|
||||
* gether_set_host_addr - initialize an ethernet-over-usb link with host address
|
||||
* @net: device representing this link
|
||||
* @host_addr: eth address of the host
|
||||
*
|
||||
* This sets the host-side Ethernet address of this ethernet-over-usb link
|
||||
* if host_addr is correct.
|
||||
* Returns negative errno if the new address is incorrect.
|
||||
*/
|
||||
int gether_set_host_addr(struct net_device *net, const char *host_addr);
|
||||
|
||||
/**
|
||||
* gether_get_host_addr - get an ethernet-over-usb link host address
|
||||
* @net: device representing this link
|
||||
* @host_addr: place to store eth address of the host
|
||||
* @len: length of the @host_addr buffer
|
||||
*
|
||||
* This gets the host-side Ethernet address of this ethernet-over-usb link.
|
||||
* Returns zero on success, else negative errno.
|
||||
*/
|
||||
int gether_get_host_addr(struct net_device *net, char *host_addr, int len);
|
||||
|
||||
/**
|
||||
* gether_get_host_addr_cdc - get an ethernet-over-usb link host address
|
||||
* @net: device representing this link
|
||||
* @host_addr: place to store eth address of the host
|
||||
* @len: length of the @host_addr buffer
|
||||
*
|
||||
* This gets the CDC formatted host-side Ethernet address of this
|
||||
* ethernet-over-usb link.
|
||||
* Returns zero on success, else negative errno.
|
||||
*/
|
||||
int gether_get_host_addr_cdc(struct net_device *net, char *host_addr, int len);
|
||||
|
||||
/**
|
||||
* gether_get_host_addr_u8 - get an ethernet-over-usb link host address
|
||||
* @net: device representing this link
|
||||
* @host_mac: place to store the eth address of the host
|
||||
*
|
||||
* This gets the binary formatted host-side Ethernet address of this
|
||||
* ethernet-over-usb link.
|
||||
*/
|
||||
void gether_get_host_addr_u8(struct net_device *net, u8 host_mac[ETH_ALEN]);
|
||||
|
||||
/**
|
||||
* gether_set_qmult - initialize an ethernet-over-usb link with a multiplier
|
||||
* @net: device representing this link
|
||||
* @qmult: queue multiplier
|
||||
*
|
||||
* This sets the queue length multiplier of this ethernet-over-usb link.
|
||||
* For higher speeds use longer queues.
|
||||
*/
|
||||
void gether_set_qmult(struct net_device *net, unsigned qmult);
|
||||
|
||||
/**
|
||||
* gether_get_qmult - get an ethernet-over-usb link multiplier
|
||||
* @net: device representing this link
|
||||
*
|
||||
* This gets the queue length multiplier of this ethernet-over-usb link.
|
||||
*/
|
||||
unsigned gether_get_qmult(struct net_device *net);
|
||||
|
||||
/**
|
||||
* gether_get_ifname - get an ethernet-over-usb link interface name
|
||||
* @net: device representing this link
|
||||
* @name: place to store the interface name
|
||||
* @len: length of the @name buffer
|
||||
*
|
||||
* This gets the interface name of this ethernet-over-usb link.
|
||||
* Returns zero on success, else negative errno.
|
||||
*/
|
||||
int gether_get_ifname(struct net_device *net, char *name, int len);
|
||||
|
||||
void gether_cleanup(struct eth_dev *dev);
|
||||
|
||||
/* connect/disconnect is handled by individual functions */
|
||||
struct net_device *gether_connect(struct gether *);
|
||||
void gether_disconnect(struct gether *);
|
||||
|
||||
/* Some controllers can't support CDC Ethernet (ECM) ... */
|
||||
static inline bool can_support_ecm(struct usb_gadget *gadget)
|
||||
{
|
||||
if (!gadget_supports_altsettings(gadget))
|
||||
return false;
|
||||
|
||||
/* Everything else is *presumably* fine ... but this is a bit
|
||||
* chancy, so be **CERTAIN** there are no hardware issues with
|
||||
* your controller. Add it above if it can't handle CDC.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* __U_ETHER_H */
|
||||
164
drivers/usb/gadget/function/u_ether_configfs.h
Normal file
164
drivers/usb/gadget/function/u_ether_configfs.h
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* u_ether_configfs.h
|
||||
*
|
||||
* Utility definitions for configfs support in USB Ethernet functions
|
||||
*
|
||||
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef __U_ETHER_CONFIGFS_H
|
||||
#define __U_ETHER_CONFIGFS_H
|
||||
|
||||
#define USB_ETHERNET_CONFIGFS_ITEM(_f_) \
|
||||
CONFIGFS_ATTR_STRUCT(f_##_f_##_opts); \
|
||||
CONFIGFS_ATTR_OPS(f_##_f_##_opts); \
|
||||
\
|
||||
static void _f_##_attr_release(struct config_item *item) \
|
||||
{ \
|
||||
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
|
||||
\
|
||||
usb_put_function_instance(&opts->func_inst); \
|
||||
} \
|
||||
\
|
||||
static struct configfs_item_operations _f_##_item_ops = { \
|
||||
.release = _f_##_attr_release, \
|
||||
.show_attribute = f_##_f_##_opts_attr_show, \
|
||||
.store_attribute = f_##_f_##_opts_attr_store, \
|
||||
}
|
||||
|
||||
#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(_f_) \
|
||||
static ssize_t _f_##_opts_dev_addr_show(struct f_##_f_##_opts *opts, \
|
||||
char *page) \
|
||||
{ \
|
||||
int result; \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
result = gether_get_dev_addr(opts->net, page, PAGE_SIZE); \
|
||||
mutex_unlock(&opts->lock); \
|
||||
\
|
||||
return result; \
|
||||
} \
|
||||
\
|
||||
static ssize_t _f_##_opts_dev_addr_store(struct f_##_f_##_opts *opts, \
|
||||
const char *page, size_t len)\
|
||||
{ \
|
||||
int ret; \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
if (opts->refcnt) { \
|
||||
mutex_unlock(&opts->lock); \
|
||||
return -EBUSY; \
|
||||
} \
|
||||
\
|
||||
ret = gether_set_dev_addr(opts->net, page); \
|
||||
mutex_unlock(&opts->lock); \
|
||||
if (!ret) \
|
||||
ret = len; \
|
||||
return ret; \
|
||||
} \
|
||||
\
|
||||
static struct f_##_f_##_opts_attribute f_##_f_##_opts_dev_addr = \
|
||||
__CONFIGFS_ATTR(dev_addr, S_IRUGO | S_IWUSR, \
|
||||
_f_##_opts_dev_addr_show, \
|
||||
_f_##_opts_dev_addr_store)
|
||||
|
||||
#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(_f_) \
|
||||
static ssize_t _f_##_opts_host_addr_show(struct f_##_f_##_opts *opts, \
|
||||
char *page) \
|
||||
{ \
|
||||
int result; \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
result = gether_get_host_addr(opts->net, page, PAGE_SIZE); \
|
||||
mutex_unlock(&opts->lock); \
|
||||
\
|
||||
return result; \
|
||||
} \
|
||||
\
|
||||
static ssize_t _f_##_opts_host_addr_store(struct f_##_f_##_opts *opts, \
|
||||
const char *page, size_t len)\
|
||||
{ \
|
||||
int ret; \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
if (opts->refcnt) { \
|
||||
mutex_unlock(&opts->lock); \
|
||||
return -EBUSY; \
|
||||
} \
|
||||
\
|
||||
ret = gether_set_host_addr(opts->net, page); \
|
||||
mutex_unlock(&opts->lock); \
|
||||
if (!ret) \
|
||||
ret = len; \
|
||||
return ret; \
|
||||
} \
|
||||
\
|
||||
static struct f_##_f_##_opts_attribute f_##_f_##_opts_host_addr = \
|
||||
__CONFIGFS_ATTR(host_addr, S_IRUGO | S_IWUSR, \
|
||||
_f_##_opts_host_addr_show, \
|
||||
_f_##_opts_host_addr_store)
|
||||
|
||||
#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(_f_) \
|
||||
static ssize_t _f_##_opts_qmult_show(struct f_##_f_##_opts *opts, \
|
||||
char *page) \
|
||||
{ \
|
||||
unsigned qmult; \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
qmult = gether_get_qmult(opts->net); \
|
||||
mutex_unlock(&opts->lock); \
|
||||
return sprintf(page, "%d", qmult); \
|
||||
} \
|
||||
\
|
||||
static ssize_t _f_##_opts_qmult_store(struct f_##_f_##_opts *opts, \
|
||||
const char *page, size_t len)\
|
||||
{ \
|
||||
u8 val; \
|
||||
int ret; \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
if (opts->refcnt) { \
|
||||
ret = -EBUSY; \
|
||||
goto out; \
|
||||
} \
|
||||
\
|
||||
ret = kstrtou8(page, 0, &val); \
|
||||
if (ret) \
|
||||
goto out; \
|
||||
\
|
||||
gether_set_qmult(opts->net, val); \
|
||||
ret = len; \
|
||||
out: \
|
||||
mutex_unlock(&opts->lock); \
|
||||
return ret; \
|
||||
} \
|
||||
\
|
||||
static struct f_##_f_##_opts_attribute f_##_f_##_opts_qmult = \
|
||||
__CONFIGFS_ATTR(qmult, S_IRUGO | S_IWUSR, \
|
||||
_f_##_opts_qmult_show, \
|
||||
_f_##_opts_qmult_store)
|
||||
|
||||
#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(_f_) \
|
||||
static ssize_t _f_##_opts_ifname_show(struct f_##_f_##_opts *opts, \
|
||||
char *page) \
|
||||
{ \
|
||||
int ret; \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
ret = gether_get_ifname(opts->net, page, PAGE_SIZE); \
|
||||
mutex_unlock(&opts->lock); \
|
||||
\
|
||||
return ret; \
|
||||
} \
|
||||
\
|
||||
static struct f_##_f_##_opts_attribute f_##_f_##_opts_ifname = \
|
||||
__CONFIGFS_ATTR_RO(ifname, _f_##_opts_ifname_show)
|
||||
|
||||
#endif /* __U_ETHER_CONFIGFS_H */
|
||||
270
drivers/usb/gadget/function/u_fs.h
Normal file
270
drivers/usb/gadget/function/u_fs.h
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* u_fs.h
|
||||
*
|
||||
* Utility definitions for the FunctionFS
|
||||
*
|
||||
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef U_FFS_H
|
||||
#define U_FFS_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#ifdef VERBOSE_DEBUG
|
||||
#ifndef pr_vdebug
|
||||
# define pr_vdebug pr_debug
|
||||
#endif /* pr_vdebug */
|
||||
# define ffs_dump_mem(prefix, ptr, len) \
|
||||
print_hex_dump_bytes(pr_fmt(prefix ": "), DUMP_PREFIX_NONE, ptr, len)
|
||||
#else
|
||||
#ifndef pr_vdebug
|
||||
# define pr_vdebug(...) do { } while (0)
|
||||
#endif /* pr_vdebug */
|
||||
# define ffs_dump_mem(prefix, ptr, len) do { } while (0)
|
||||
#endif /* VERBOSE_DEBUG */
|
||||
|
||||
#define ENTER() pr_vdebug("%s()\n", __func__)
|
||||
|
||||
struct f_fs_opts;
|
||||
|
||||
struct ffs_dev {
|
||||
const char *name;
|
||||
bool name_allocated;
|
||||
bool mounted;
|
||||
bool desc_ready;
|
||||
bool single;
|
||||
struct ffs_data *ffs_data;
|
||||
struct f_fs_opts *opts;
|
||||
struct list_head entry;
|
||||
|
||||
int (*ffs_ready_callback)(struct ffs_data *ffs);
|
||||
void (*ffs_closed_callback)(struct ffs_data *ffs);
|
||||
void *(*ffs_acquire_dev_callback)(struct ffs_dev *dev);
|
||||
void (*ffs_release_dev_callback)(struct ffs_dev *dev);
|
||||
};
|
||||
|
||||
extern struct mutex ffs_lock;
|
||||
|
||||
static inline void ffs_dev_lock(void)
|
||||
{
|
||||
mutex_lock(&ffs_lock);
|
||||
}
|
||||
|
||||
static inline void ffs_dev_unlock(void)
|
||||
{
|
||||
mutex_unlock(&ffs_lock);
|
||||
}
|
||||
|
||||
int ffs_name_dev(struct ffs_dev *dev, const char *name);
|
||||
int ffs_single_dev(struct ffs_dev *dev);
|
||||
|
||||
struct ffs_epfile;
|
||||
struct ffs_function;
|
||||
|
||||
enum ffs_state {
|
||||
/*
|
||||
* Waiting for descriptors and strings.
|
||||
*
|
||||
* In this state no open(2), read(2) or write(2) on epfiles
|
||||
* may succeed (which should not be the problem as there
|
||||
* should be no such files opened in the first place).
|
||||
*/
|
||||
FFS_READ_DESCRIPTORS,
|
||||
FFS_READ_STRINGS,
|
||||
|
||||
/*
|
||||
* We've got descriptors and strings. We are or have called
|
||||
* functionfs_ready_callback(). functionfs_bind() may have
|
||||
* been called but we don't know.
|
||||
*
|
||||
* This is the only state in which operations on epfiles may
|
||||
* succeed.
|
||||
*/
|
||||
FFS_ACTIVE,
|
||||
|
||||
/*
|
||||
* All endpoints have been closed. This state is also set if
|
||||
* we encounter an unrecoverable error. The only
|
||||
* unrecoverable error is situation when after reading strings
|
||||
* from user space we fail to initialise epfiles or
|
||||
* functionfs_ready_callback() returns with error (<0).
|
||||
*
|
||||
* In this state no open(2), read(2) or write(2) (both on ep0
|
||||
* as well as epfile) may succeed (at this point epfiles are
|
||||
* unlinked and all closed so this is not a problem; ep0 is
|
||||
* also closed but ep0 file exists and so open(2) on ep0 must
|
||||
* fail).
|
||||
*/
|
||||
FFS_CLOSING
|
||||
};
|
||||
|
||||
enum ffs_setup_state {
|
||||
/* There is no setup request pending. */
|
||||
FFS_NO_SETUP,
|
||||
/*
|
||||
* User has read events and there was a setup request event
|
||||
* there. The next read/write on ep0 will handle the
|
||||
* request.
|
||||
*/
|
||||
FFS_SETUP_PENDING,
|
||||
/*
|
||||
* There was event pending but before user space handled it
|
||||
* some other event was introduced which canceled existing
|
||||
* setup. If this state is set read/write on ep0 return
|
||||
* -EIDRM. This state is only set when adding event.
|
||||
*/
|
||||
FFS_SETUP_CANCELLED
|
||||
};
|
||||
|
||||
struct ffs_data {
|
||||
struct usb_gadget *gadget;
|
||||
|
||||
/*
|
||||
* Protect access read/write operations, only one read/write
|
||||
* at a time. As a consequence protects ep0req and company.
|
||||
* While setup request is being processed (queued) this is
|
||||
* held.
|
||||
*/
|
||||
struct mutex mutex;
|
||||
|
||||
/*
|
||||
* Protect access to endpoint related structures (basically
|
||||
* usb_ep_queue(), usb_ep_dequeue(), etc. calls) except for
|
||||
* endpoint zero.
|
||||
*/
|
||||
spinlock_t eps_lock;
|
||||
|
||||
/*
|
||||
* XXX REVISIT do we need our own request? Since we are not
|
||||
* handling setup requests immediately user space may be so
|
||||
* slow that another setup will be sent to the gadget but this
|
||||
* time not to us but another function and then there could be
|
||||
* a race. Is that the case? Or maybe we can use cdev->req
|
||||
* after all, maybe we just need some spinlock for that?
|
||||
*/
|
||||
struct usb_request *ep0req; /* P: mutex */
|
||||
struct completion ep0req_completion; /* P: mutex */
|
||||
|
||||
/* reference counter */
|
||||
atomic_t ref;
|
||||
/* how many files are opened (EP0 and others) */
|
||||
atomic_t opened;
|
||||
|
||||
/* EP0 state */
|
||||
enum ffs_state state;
|
||||
|
||||
/*
|
||||
* Possible transitions:
|
||||
* + FFS_NO_SETUP -> FFS_SETUP_PENDING -- P: ev.waitq.lock
|
||||
* happens only in ep0 read which is P: mutex
|
||||
* + FFS_SETUP_PENDING -> FFS_NO_SETUP -- P: ev.waitq.lock
|
||||
* happens only in ep0 i/o which is P: mutex
|
||||
* + FFS_SETUP_PENDING -> FFS_SETUP_CANCELLED -- P: ev.waitq.lock
|
||||
* + FFS_SETUP_CANCELLED -> FFS_NO_SETUP -- cmpxchg
|
||||
*
|
||||
* This field should never be accessed directly and instead
|
||||
* ffs_setup_state_clear_cancelled function should be used.
|
||||
*/
|
||||
enum ffs_setup_state setup_state;
|
||||
|
||||
/* Events & such. */
|
||||
struct {
|
||||
u8 types[4];
|
||||
unsigned short count;
|
||||
/* XXX REVISIT need to update it in some places, or do we? */
|
||||
unsigned short can_stall;
|
||||
struct usb_ctrlrequest setup;
|
||||
|
||||
wait_queue_head_t waitq;
|
||||
} ev; /* the whole structure, P: ev.waitq.lock */
|
||||
|
||||
/* Flags */
|
||||
unsigned long flags;
|
||||
#define FFS_FL_CALL_CLOSED_CALLBACK 0
|
||||
#define FFS_FL_BOUND 1
|
||||
|
||||
/* Active function */
|
||||
struct ffs_function *func;
|
||||
|
||||
/*
|
||||
* Device name, write once when file system is mounted.
|
||||
* Intended for user to read if she wants.
|
||||
*/
|
||||
const char *dev_name;
|
||||
/* Private data for our user (ie. gadget). Managed by user. */
|
||||
void *private_data;
|
||||
|
||||
/* filled by __ffs_data_got_descs() */
|
||||
/*
|
||||
* raw_descs is what you kfree, real_descs points inside of raw_descs,
|
||||
* where full speed, high speed and super speed descriptors start.
|
||||
* real_descs_length is the length of all those descriptors.
|
||||
*/
|
||||
const void *raw_descs_data;
|
||||
const void *raw_descs;
|
||||
unsigned raw_descs_length;
|
||||
unsigned fs_descs_count;
|
||||
unsigned hs_descs_count;
|
||||
unsigned ss_descs_count;
|
||||
unsigned ms_os_descs_count;
|
||||
unsigned ms_os_descs_ext_prop_count;
|
||||
unsigned ms_os_descs_ext_prop_name_len;
|
||||
unsigned ms_os_descs_ext_prop_data_len;
|
||||
void *ms_os_descs_ext_prop_avail;
|
||||
void *ms_os_descs_ext_prop_name_avail;
|
||||
void *ms_os_descs_ext_prop_data_avail;
|
||||
|
||||
unsigned short strings_count;
|
||||
unsigned short interfaces_count;
|
||||
unsigned short eps_count;
|
||||
unsigned short _pad1;
|
||||
|
||||
/* filled by __ffs_data_got_strings() */
|
||||
/* ids in stringtabs are set in functionfs_bind() */
|
||||
const void *raw_strings;
|
||||
struct usb_gadget_strings **stringtabs;
|
||||
|
||||
/*
|
||||
* File system's super block, write once when file system is
|
||||
* mounted.
|
||||
*/
|
||||
struct super_block *sb;
|
||||
|
||||
/* File permissions, written once when fs is mounted */
|
||||
struct ffs_file_perms {
|
||||
umode_t mode;
|
||||
kuid_t uid;
|
||||
kgid_t gid;
|
||||
} file_perms;
|
||||
|
||||
/*
|
||||
* The endpoint files, filled by ffs_epfiles_create(),
|
||||
* destroyed by ffs_epfiles_destroy().
|
||||
*/
|
||||
struct ffs_epfile *epfiles;
|
||||
};
|
||||
|
||||
|
||||
struct f_fs_opts {
|
||||
struct usb_function_instance func_inst;
|
||||
struct ffs_dev *dev;
|
||||
unsigned refcnt;
|
||||
bool no_configfs;
|
||||
};
|
||||
|
||||
static inline struct f_fs_opts *to_f_fs_opts(struct usb_function_instance *fi)
|
||||
{
|
||||
return container_of(fi, struct f_fs_opts, func_inst);
|
||||
}
|
||||
|
||||
#endif /* U_FFS_H */
|
||||
36
drivers/usb/gadget/function/u_gether.h
Normal file
36
drivers/usb/gadget/function/u_gether.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* u_gether.h
|
||||
*
|
||||
* Utility definitions for the subset function
|
||||
*
|
||||
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef U_GETHER_H
|
||||
#define U_GETHER_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
struct f_gether_opts {
|
||||
struct usb_function_instance func_inst;
|
||||
struct net_device *net;
|
||||
bool bound;
|
||||
|
||||
/*
|
||||
* Read/write access to configfs attributes is handled by configfs.
|
||||
*
|
||||
* This is to protect the data from concurrent access by read/write
|
||||
* and create symlink/remove symlink.
|
||||
*/
|
||||
struct mutex lock;
|
||||
int refcnt;
|
||||
};
|
||||
|
||||
#endif /* U_GETHER_H */
|
||||
36
drivers/usb/gadget/function/u_ncm.h
Normal file
36
drivers/usb/gadget/function/u_ncm.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* u_ncm.h
|
||||
*
|
||||
* Utility definitions for the ncm function
|
||||
*
|
||||
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef U_NCM_H
|
||||
#define U_NCM_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
struct f_ncm_opts {
|
||||
struct usb_function_instance func_inst;
|
||||
struct net_device *net;
|
||||
bool bound;
|
||||
|
||||
/*
|
||||
* Read/write access to configfs attributes is handled by configfs.
|
||||
*
|
||||
* This is to protect the data from concurrent access by read/write
|
||||
* and create symlink/remove symlink.
|
||||
*/
|
||||
struct mutex lock;
|
||||
int refcnt;
|
||||
};
|
||||
|
||||
#endif /* U_NCM_H */
|
||||
29
drivers/usb/gadget/function/u_phonet.h
Normal file
29
drivers/usb/gadget/function/u_phonet.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* u_phonet.h - interface to Phonet
|
||||
*
|
||||
* Copyright (C) 2007-2008 by Nokia Corporation
|
||||
*
|
||||
* This software is distributed under the terms of the GNU General
|
||||
* Public License ("GPL") as published by the Free Software Foundation,
|
||||
* either version 2 of that License or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef __U_PHONET_H
|
||||
#define __U_PHONET_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/usb/cdc.h>
|
||||
|
||||
struct f_phonet_opts {
|
||||
struct usb_function_instance func_inst;
|
||||
bool bound;
|
||||
struct net_device *net;
|
||||
};
|
||||
|
||||
struct net_device *gphonet_setup_default(void);
|
||||
void gphonet_set_gadget(struct net_device *net, struct usb_gadget *g);
|
||||
int gphonet_register_netdev(struct net_device *net);
|
||||
int phonet_bind_config(struct usb_configuration *c, struct net_device *dev);
|
||||
void gphonet_cleanup(struct net_device *dev);
|
||||
|
||||
#endif /* __U_PHONET_H */
|
||||
46
drivers/usb/gadget/function/u_rndis.h
Normal file
46
drivers/usb/gadget/function/u_rndis.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* u_rndis.h
|
||||
*
|
||||
* Utility definitions for the subset function
|
||||
*
|
||||
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef U_RNDIS_H
|
||||
#define U_RNDIS_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
struct f_rndis_opts {
|
||||
struct usb_function_instance func_inst;
|
||||
u32 vendor_id;
|
||||
const char *manufacturer;
|
||||
struct net_device *net;
|
||||
bool bound;
|
||||
bool borrowed_net;
|
||||
|
||||
struct usb_os_desc rndis_os_desc;
|
||||
char rndis_ext_compat_id[16];
|
||||
|
||||
/*
|
||||
* Read/write access to configfs attributes is handled by configfs.
|
||||
*
|
||||
* This is to protect the data from concurrent access by read/write
|
||||
* and create symlink/remove symlink.
|
||||
*/
|
||||
struct mutex lock;
|
||||
int refcnt;
|
||||
};
|
||||
|
||||
int rndis_init(void);
|
||||
void rndis_exit(void);
|
||||
void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net);
|
||||
|
||||
#endif /* U_RNDIS_H */
|
||||
1347
drivers/usb/gadget/function/u_serial.c
Normal file
1347
drivers/usb/gadget/function/u_serial.c
Normal file
File diff suppressed because it is too large
Load diff
71
drivers/usb/gadget/function/u_serial.h
Normal file
71
drivers/usb/gadget/function/u_serial.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* u_serial.h - interface to USB gadget "serial port"/TTY utilities
|
||||
*
|
||||
* Copyright (C) 2008 David Brownell
|
||||
* Copyright (C) 2008 by Nokia Corporation
|
||||
*
|
||||
* This software is distributed under the terms of the GNU General
|
||||
* Public License ("GPL") as published by the Free Software Foundation,
|
||||
* either version 2 of that License or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef __U_SERIAL_H
|
||||
#define __U_SERIAL_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/usb/cdc.h>
|
||||
|
||||
#define MAX_U_SERIAL_PORTS 4
|
||||
|
||||
struct f_serial_opts {
|
||||
struct usb_function_instance func_inst;
|
||||
u8 port_num;
|
||||
};
|
||||
|
||||
/*
|
||||
* One non-multiplexed "serial" I/O port ... there can be several of these
|
||||
* on any given USB peripheral device, if it provides enough endpoints.
|
||||
*
|
||||
* The "u_serial" utility component exists to do one thing: manage TTY
|
||||
* style I/O using the USB peripheral endpoints listed here, including
|
||||
* hookups to sysfs and /dev for each logical "tty" device.
|
||||
*
|
||||
* REVISIT at least ACM could support tiocmget() if needed.
|
||||
*
|
||||
* REVISIT someday, allow multiplexing several TTYs over these endpoints.
|
||||
*/
|
||||
struct gserial {
|
||||
struct usb_function func;
|
||||
|
||||
/* port is managed by gserial_{connect,disconnect} */
|
||||
struct gs_port *ioport;
|
||||
|
||||
struct usb_ep *in;
|
||||
struct usb_ep *out;
|
||||
|
||||
/* REVISIT avoid this CDC-ACM support harder ... */
|
||||
struct usb_cdc_line_coding port_line_coding; /* 9600-8-N-1 etc */
|
||||
|
||||
/* notification callbacks */
|
||||
void (*connect)(struct gserial *p);
|
||||
void (*disconnect)(struct gserial *p);
|
||||
int (*send_break)(struct gserial *p, int duration);
|
||||
};
|
||||
|
||||
/* utilities to allocate/free request and buffer */
|
||||
struct usb_request *gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags);
|
||||
void gs_free_req(struct usb_ep *, struct usb_request *req);
|
||||
|
||||
/* management of individual TTY ports */
|
||||
int gserial_alloc_line(unsigned char *port_line);
|
||||
void gserial_free_line(unsigned char port_line);
|
||||
|
||||
/* connect/disconnect is handled by individual functions */
|
||||
int gserial_connect(struct gserial *, u8 port_num);
|
||||
void gserial_disconnect(struct gserial *);
|
||||
|
||||
/* functions are bound to configurations by a config or gadget driver */
|
||||
int gser_bind_config(struct usb_configuration *c, u8 port_num);
|
||||
int obex_bind_config(struct usb_configuration *c, u8 port_num);
|
||||
|
||||
#endif /* __U_SERIAL_H */
|
||||
330
drivers/usb/gadget/function/u_uac1.c
Normal file
330
drivers/usb/gadget/function/u_uac1.c
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
/*
|
||||
* u_uac1.c -- ALSA audio utilities for Gadget stack
|
||||
*
|
||||
* Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
|
||||
* Copyright (C) 2008 Analog Devices, Inc
|
||||
*
|
||||
* Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/syscalls.h>
|
||||
|
||||
#include "u_uac1.h"
|
||||
|
||||
/*
|
||||
* This component encapsulates the ALSA devices for USB audio gadget
|
||||
*/
|
||||
|
||||
#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p"
|
||||
#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c"
|
||||
#define FILE_CONTROL "/dev/snd/controlC0"
|
||||
|
||||
static char *fn_play = FILE_PCM_PLAYBACK;
|
||||
module_param(fn_play, charp, S_IRUGO);
|
||||
MODULE_PARM_DESC(fn_play, "Playback PCM device file name");
|
||||
|
||||
static char *fn_cap = FILE_PCM_CAPTURE;
|
||||
module_param(fn_cap, charp, S_IRUGO);
|
||||
MODULE_PARM_DESC(fn_cap, "Capture PCM device file name");
|
||||
|
||||
static char *fn_cntl = FILE_CONTROL;
|
||||
module_param(fn_cntl, charp, S_IRUGO);
|
||||
MODULE_PARM_DESC(fn_cntl, "Control device file name");
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Some ALSA internal helper functions
|
||||
*/
|
||||
static int snd_interval_refine_set(struct snd_interval *i, unsigned int val)
|
||||
{
|
||||
struct snd_interval t;
|
||||
t.empty = 0;
|
||||
t.min = t.max = val;
|
||||
t.openmin = t.openmax = 0;
|
||||
t.integer = 1;
|
||||
return snd_interval_refine(i, &t);
|
||||
}
|
||||
|
||||
static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params,
|
||||
snd_pcm_hw_param_t var, unsigned int val,
|
||||
int dir)
|
||||
{
|
||||
int changed;
|
||||
if (hw_is_mask(var)) {
|
||||
struct snd_mask *m = hw_param_mask(params, var);
|
||||
if (val == 0 && dir < 0) {
|
||||
changed = -EINVAL;
|
||||
snd_mask_none(m);
|
||||
} else {
|
||||
if (dir > 0)
|
||||
val++;
|
||||
else if (dir < 0)
|
||||
val--;
|
||||
changed = snd_mask_refine_set(
|
||||
hw_param_mask(params, var), val);
|
||||
}
|
||||
} else if (hw_is_interval(var)) {
|
||||
struct snd_interval *i = hw_param_interval(params, var);
|
||||
if (val == 0 && dir < 0) {
|
||||
changed = -EINVAL;
|
||||
snd_interval_none(i);
|
||||
} else if (dir == 0)
|
||||
changed = snd_interval_refine_set(i, val);
|
||||
else {
|
||||
struct snd_interval t;
|
||||
t.openmin = 1;
|
||||
t.openmax = 1;
|
||||
t.empty = 0;
|
||||
t.integer = 0;
|
||||
if (dir < 0) {
|
||||
t.min = val - 1;
|
||||
t.max = val;
|
||||
} else {
|
||||
t.min = val;
|
||||
t.max = val+1;
|
||||
}
|
||||
changed = snd_interval_refine(i, &t);
|
||||
}
|
||||
} else
|
||||
return -EINVAL;
|
||||
if (changed) {
|
||||
params->cmask |= 1 << var;
|
||||
params->rmask |= 1 << var;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Set default hardware params
|
||||
*/
|
||||
static int playback_default_hw_params(struct gaudio_snd_dev *snd)
|
||||
{
|
||||
struct snd_pcm_substream *substream = snd->substream;
|
||||
struct snd_pcm_hw_params *params;
|
||||
snd_pcm_sframes_t result;
|
||||
|
||||
/*
|
||||
* SNDRV_PCM_ACCESS_RW_INTERLEAVED,
|
||||
* SNDRV_PCM_FORMAT_S16_LE
|
||||
* CHANNELS: 2
|
||||
* RATE: 48000
|
||||
*/
|
||||
snd->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
|
||||
snd->format = SNDRV_PCM_FORMAT_S16_LE;
|
||||
snd->channels = 2;
|
||||
snd->rate = 48000;
|
||||
|
||||
params = kzalloc(sizeof(*params), GFP_KERNEL);
|
||||
if (!params)
|
||||
return -ENOMEM;
|
||||
|
||||
_snd_pcm_hw_params_any(params);
|
||||
_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS,
|
||||
snd->access, 0);
|
||||
_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT,
|
||||
snd->format, 0);
|
||||
_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
snd->channels, 0);
|
||||
_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE,
|
||||
snd->rate, 0);
|
||||
|
||||
snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
|
||||
snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, params);
|
||||
|
||||
result = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL);
|
||||
if (result < 0) {
|
||||
ERROR(snd->card,
|
||||
"Preparing sound card failed: %d\n", (int)result);
|
||||
kfree(params);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Store the hardware parameters */
|
||||
snd->access = params_access(params);
|
||||
snd->format = params_format(params);
|
||||
snd->channels = params_channels(params);
|
||||
snd->rate = params_rate(params);
|
||||
|
||||
kfree(params);
|
||||
|
||||
INFO(snd->card,
|
||||
"Hardware params: access %x, format %x, channels %d, rate %d\n",
|
||||
snd->access, snd->format, snd->channels, snd->rate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Playback audio buffer data by ALSA PCM device
|
||||
*/
|
||||
static size_t u_audio_playback(struct gaudio *card, void *buf, size_t count)
|
||||
{
|
||||
struct gaudio_snd_dev *snd = &card->playback;
|
||||
struct snd_pcm_substream *substream = snd->substream;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
mm_segment_t old_fs;
|
||||
ssize_t result;
|
||||
snd_pcm_sframes_t frames;
|
||||
|
||||
try_again:
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
|
||||
runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
|
||||
result = snd_pcm_kernel_ioctl(substream,
|
||||
SNDRV_PCM_IOCTL_PREPARE, NULL);
|
||||
if (result < 0) {
|
||||
ERROR(card, "Preparing sound card failed: %d\n",
|
||||
(int)result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
frames = bytes_to_frames(runtime, count);
|
||||
old_fs = get_fs();
|
||||
set_fs(KERNEL_DS);
|
||||
result = snd_pcm_lib_write(snd->substream, (void __user *)buf, frames);
|
||||
if (result != frames) {
|
||||
ERROR(card, "Playback error: %d\n", (int)result);
|
||||
set_fs(old_fs);
|
||||
goto try_again;
|
||||
}
|
||||
set_fs(old_fs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int u_audio_get_playback_channels(struct gaudio *card)
|
||||
{
|
||||
return card->playback.channels;
|
||||
}
|
||||
|
||||
static int u_audio_get_playback_rate(struct gaudio *card)
|
||||
{
|
||||
return card->playback.rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open ALSA PCM and control device files
|
||||
* Initial the PCM or control device
|
||||
*/
|
||||
static int gaudio_open_snd_dev(struct gaudio *card)
|
||||
{
|
||||
struct snd_pcm_file *pcm_file;
|
||||
struct gaudio_snd_dev *snd;
|
||||
|
||||
if (!card)
|
||||
return -ENODEV;
|
||||
|
||||
/* Open control device */
|
||||
snd = &card->control;
|
||||
snd->filp = filp_open(fn_cntl, O_RDWR, 0);
|
||||
if (IS_ERR(snd->filp)) {
|
||||
int ret = PTR_ERR(snd->filp);
|
||||
ERROR(card, "unable to open sound control device file: %s\n",
|
||||
fn_cntl);
|
||||
snd->filp = NULL;
|
||||
return ret;
|
||||
}
|
||||
snd->card = card;
|
||||
|
||||
/* Open PCM playback device and setup substream */
|
||||
snd = &card->playback;
|
||||
snd->filp = filp_open(fn_play, O_WRONLY, 0);
|
||||
if (IS_ERR(snd->filp)) {
|
||||
int ret = PTR_ERR(snd->filp);
|
||||
|
||||
ERROR(card, "No such PCM playback device: %s\n", fn_play);
|
||||
snd->filp = NULL;
|
||||
return ret;
|
||||
}
|
||||
pcm_file = snd->filp->private_data;
|
||||
snd->substream = pcm_file->substream;
|
||||
snd->card = card;
|
||||
playback_default_hw_params(snd);
|
||||
|
||||
/* Open PCM capture device and setup substream */
|
||||
snd = &card->capture;
|
||||
snd->filp = filp_open(fn_cap, O_RDONLY, 0);
|
||||
if (IS_ERR(snd->filp)) {
|
||||
ERROR(card, "No such PCM capture device: %s\n", fn_cap);
|
||||
snd->substream = NULL;
|
||||
snd->card = NULL;
|
||||
snd->filp = NULL;
|
||||
} else {
|
||||
pcm_file = snd->filp->private_data;
|
||||
snd->substream = pcm_file->substream;
|
||||
snd->card = card;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close ALSA PCM and control device files
|
||||
*/
|
||||
static int gaudio_close_snd_dev(struct gaudio *gau)
|
||||
{
|
||||
struct gaudio_snd_dev *snd;
|
||||
|
||||
/* Close control device */
|
||||
snd = &gau->control;
|
||||
if (snd->filp)
|
||||
filp_close(snd->filp, NULL);
|
||||
|
||||
/* Close PCM playback device and setup substream */
|
||||
snd = &gau->playback;
|
||||
if (snd->filp)
|
||||
filp_close(snd->filp, NULL);
|
||||
|
||||
/* Close PCM capture device and setup substream */
|
||||
snd = &gau->capture;
|
||||
if (snd->filp)
|
||||
filp_close(snd->filp, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct gaudio *the_card;
|
||||
/**
|
||||
* gaudio_setup - setup ALSA interface and preparing for USB transfer
|
||||
*
|
||||
* This sets up PCM, mixer or MIDI ALSA devices fore USB gadget using.
|
||||
*
|
||||
* Returns negative errno, or zero on success
|
||||
*/
|
||||
int __init gaudio_setup(struct gaudio *card)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = gaudio_open_snd_dev(card);
|
||||
if (ret)
|
||||
ERROR(card, "we need at least one control device\n");
|
||||
else if (!the_card)
|
||||
the_card = card;
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* gaudio_cleanup - remove ALSA device interface
|
||||
*
|
||||
* This is called to free all resources allocated by @gaudio_setup().
|
||||
*/
|
||||
void gaudio_cleanup(void)
|
||||
{
|
||||
if (the_card) {
|
||||
gaudio_close_snd_dev(the_card);
|
||||
the_card = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
56
drivers/usb/gadget/function/u_uac1.h
Normal file
56
drivers/usb/gadget/function/u_uac1.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities
|
||||
*
|
||||
* Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
|
||||
* Copyright (C) 2008 Analog Devices, Inc
|
||||
*
|
||||
* Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#ifndef __U_AUDIO_H
|
||||
#define __U_AUDIO_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/usb/audio.h>
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include "gadget_chips.h"
|
||||
|
||||
/*
|
||||
* This represents the USB side of an audio card device, managed by a USB
|
||||
* function which provides control and stream interfaces.
|
||||
*/
|
||||
|
||||
struct gaudio_snd_dev {
|
||||
struct gaudio *card;
|
||||
struct file *filp;
|
||||
struct snd_pcm_substream *substream;
|
||||
int access;
|
||||
int format;
|
||||
int channels;
|
||||
int rate;
|
||||
};
|
||||
|
||||
struct gaudio {
|
||||
struct usb_function func;
|
||||
struct usb_gadget *gadget;
|
||||
|
||||
/* ALSA sound device interfaces */
|
||||
struct gaudio_snd_dev control;
|
||||
struct gaudio_snd_dev playback;
|
||||
struct gaudio_snd_dev capture;
|
||||
|
||||
/* TODO */
|
||||
};
|
||||
|
||||
int gaudio_setup(struct gaudio *card);
|
||||
void gaudio_cleanup(void);
|
||||
|
||||
#endif /* __U_AUDIO_H */
|
||||
202
drivers/usb/gadget/function/uvc.h
Normal file
202
drivers/usb/gadget/function/uvc.h
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* uvc_gadget.h -- USB Video Class Gadget driver
|
||||
*
|
||||
* Copyright (C) 2009-2010
|
||||
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _UVC_GADGET_H_
|
||||
#define _UVC_GADGET_H_
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
|
||||
#define UVC_EVENT_FIRST (V4L2_EVENT_PRIVATE_START + 0)
|
||||
#define UVC_EVENT_CONNECT (V4L2_EVENT_PRIVATE_START + 0)
|
||||
#define UVC_EVENT_DISCONNECT (V4L2_EVENT_PRIVATE_START + 1)
|
||||
#define UVC_EVENT_STREAMON (V4L2_EVENT_PRIVATE_START + 2)
|
||||
#define UVC_EVENT_STREAMOFF (V4L2_EVENT_PRIVATE_START + 3)
|
||||
#define UVC_EVENT_SETUP (V4L2_EVENT_PRIVATE_START + 4)
|
||||
#define UVC_EVENT_DATA (V4L2_EVENT_PRIVATE_START + 5)
|
||||
#define UVC_EVENT_LAST (V4L2_EVENT_PRIVATE_START + 5)
|
||||
|
||||
struct uvc_request_data
|
||||
{
|
||||
__s32 length;
|
||||
__u8 data[60];
|
||||
};
|
||||
|
||||
struct uvc_event
|
||||
{
|
||||
union {
|
||||
enum usb_device_speed speed;
|
||||
struct usb_ctrlrequest req;
|
||||
struct uvc_request_data data;
|
||||
};
|
||||
};
|
||||
|
||||
#define UVCIOC_SEND_RESPONSE _IOW('U', 1, struct uvc_request_data)
|
||||
|
||||
#define UVC_INTF_CONTROL 0
|
||||
#define UVC_INTF_STREAMING 1
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Debugging, printing and logging
|
||||
*/
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
#include <linux/usb.h> /* For usb_endpoint_* */
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/version.h>
|
||||
#include <media/v4l2-fh.h>
|
||||
#include <media/v4l2-device.h>
|
||||
|
||||
#include "uvc_queue.h"
|
||||
|
||||
#define UVC_TRACE_PROBE (1 << 0)
|
||||
#define UVC_TRACE_DESCR (1 << 1)
|
||||
#define UVC_TRACE_CONTROL (1 << 2)
|
||||
#define UVC_TRACE_FORMAT (1 << 3)
|
||||
#define UVC_TRACE_CAPTURE (1 << 4)
|
||||
#define UVC_TRACE_CALLS (1 << 5)
|
||||
#define UVC_TRACE_IOCTL (1 << 6)
|
||||
#define UVC_TRACE_FRAME (1 << 7)
|
||||
#define UVC_TRACE_SUSPEND (1 << 8)
|
||||
#define UVC_TRACE_STATUS (1 << 9)
|
||||
|
||||
#define UVC_WARN_MINMAX 0
|
||||
#define UVC_WARN_PROBE_DEF 1
|
||||
|
||||
extern unsigned int uvc_gadget_trace_param;
|
||||
|
||||
#define uvc_trace(flag, msg...) \
|
||||
do { \
|
||||
if (uvc_gadget_trace_param & flag) \
|
||||
printk(KERN_DEBUG "uvcvideo: " msg); \
|
||||
} while (0)
|
||||
|
||||
#define uvc_warn_once(dev, warn, msg...) \
|
||||
do { \
|
||||
if (!test_and_set_bit(warn, &dev->warnings)) \
|
||||
printk(KERN_INFO "uvcvideo: " msg); \
|
||||
} while (0)
|
||||
|
||||
#define uvc_printk(level, msg...) \
|
||||
printk(level "uvcvideo: " msg)
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Driver specific constants
|
||||
*/
|
||||
|
||||
#define DRIVER_VERSION "0.1.0"
|
||||
#define DRIVER_VERSION_NUMBER KERNEL_VERSION(0, 1, 0)
|
||||
|
||||
#define UVC_NUM_REQUESTS 4
|
||||
#define UVC_MAX_REQUEST_SIZE 64
|
||||
#define UVC_MAX_EVENTS 4
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Structures
|
||||
*/
|
||||
|
||||
struct uvc_video
|
||||
{
|
||||
struct usb_ep *ep;
|
||||
|
||||
/* Frame parameters */
|
||||
u8 bpp;
|
||||
u32 fcc;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int imagesize;
|
||||
|
||||
/* Requests */
|
||||
unsigned int req_size;
|
||||
struct usb_request *req[UVC_NUM_REQUESTS];
|
||||
__u8 *req_buffer[UVC_NUM_REQUESTS];
|
||||
struct list_head req_free;
|
||||
spinlock_t req_lock;
|
||||
|
||||
void (*encode) (struct usb_request *req, struct uvc_video *video,
|
||||
struct uvc_buffer *buf);
|
||||
|
||||
/* Context data used by the completion handler */
|
||||
__u32 payload_size;
|
||||
__u32 max_payload_size;
|
||||
|
||||
struct uvc_video_queue queue;
|
||||
unsigned int fid;
|
||||
};
|
||||
|
||||
enum uvc_state
|
||||
{
|
||||
UVC_STATE_DISCONNECTED,
|
||||
UVC_STATE_CONNECTED,
|
||||
UVC_STATE_STREAMING,
|
||||
};
|
||||
|
||||
struct uvc_device
|
||||
{
|
||||
struct video_device *vdev;
|
||||
struct v4l2_device v4l2_dev;
|
||||
enum uvc_state state;
|
||||
struct usb_function func;
|
||||
struct uvc_video video;
|
||||
|
||||
/* Descriptors */
|
||||
struct {
|
||||
const struct uvc_descriptor_header * const *fs_control;
|
||||
const struct uvc_descriptor_header * const *ss_control;
|
||||
const struct uvc_descriptor_header * const *fs_streaming;
|
||||
const struct uvc_descriptor_header * const *hs_streaming;
|
||||
const struct uvc_descriptor_header * const *ss_streaming;
|
||||
} desc;
|
||||
|
||||
unsigned int control_intf;
|
||||
struct usb_ep *control_ep;
|
||||
struct usb_request *control_req;
|
||||
void *control_buf;
|
||||
|
||||
unsigned int streaming_intf;
|
||||
|
||||
/* Events */
|
||||
unsigned int event_length;
|
||||
unsigned int event_setup_out : 1;
|
||||
};
|
||||
|
||||
static inline struct uvc_device *to_uvc(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct uvc_device, func);
|
||||
}
|
||||
|
||||
struct uvc_file_handle
|
||||
{
|
||||
struct v4l2_fh vfh;
|
||||
struct uvc_video *device;
|
||||
};
|
||||
|
||||
#define to_uvc_file_handle(handle) \
|
||||
container_of(handle, struct uvc_file_handle, vfh)
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Functions
|
||||
*/
|
||||
|
||||
extern void uvc_function_setup_continue(struct uvc_device *uvc);
|
||||
extern void uvc_endpoint_stream(struct uvc_device *dev);
|
||||
|
||||
extern void uvc_function_connect(struct uvc_device *uvc);
|
||||
extern void uvc_function_disconnect(struct uvc_device *uvc);
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
|
||||
#endif /* _UVC_GADGET_H_ */
|
||||
|
||||
407
drivers/usb/gadget/function/uvc_queue.c
Normal file
407
drivers/usb/gadget/function/uvc_queue.c
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
* uvc_queue.c -- USB Video Class driver - Buffers management
|
||||
*
|
||||
* Copyright (C) 2005-2010
|
||||
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include <media/v4l2-common.h>
|
||||
#include <media/videobuf2-vmalloc.h>
|
||||
|
||||
#include "uvc.h"
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Video buffers queue management.
|
||||
*
|
||||
* Video queues is initialized by uvc_queue_init(). The function performs
|
||||
* basic initialization of the uvc_video_queue struct and never fails.
|
||||
*
|
||||
* Video buffers are managed by videobuf2. The driver uses a mutex to protect
|
||||
* the videobuf2 queue operations by serializing calls to videobuf2 and a
|
||||
* spinlock to protect the IRQ queue that holds the buffers to be processed by
|
||||
* the driver.
|
||||
*/
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* videobuf2 queue operations
|
||||
*/
|
||||
|
||||
static int uvc_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
|
||||
unsigned int *nbuffers, unsigned int *nplanes,
|
||||
unsigned int sizes[], void *alloc_ctxs[])
|
||||
{
|
||||
struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
|
||||
struct uvc_video *video = container_of(queue, struct uvc_video, queue);
|
||||
|
||||
if (*nbuffers > UVC_MAX_VIDEO_BUFFERS)
|
||||
*nbuffers = UVC_MAX_VIDEO_BUFFERS;
|
||||
|
||||
*nplanes = 1;
|
||||
|
||||
sizes[0] = video->imagesize;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uvc_buffer_prepare(struct vb2_buffer *vb)
|
||||
{
|
||||
struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
|
||||
struct uvc_buffer *buf = container_of(vb, struct uvc_buffer, buf);
|
||||
|
||||
if (vb->v4l2_buf.type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
|
||||
vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) {
|
||||
uvc_trace(UVC_TRACE_CAPTURE, "[E] Bytes used out of bounds.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (unlikely(queue->flags & UVC_QUEUE_DISCONNECTED))
|
||||
return -ENODEV;
|
||||
|
||||
buf->state = UVC_BUF_STATE_QUEUED;
|
||||
buf->mem = vb2_plane_vaddr(vb, 0);
|
||||
buf->length = vb2_plane_size(vb, 0);
|
||||
if (vb->v4l2_buf.type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
||||
buf->bytesused = 0;
|
||||
else
|
||||
buf->bytesused = vb2_get_plane_payload(vb, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void uvc_buffer_queue(struct vb2_buffer *vb)
|
||||
{
|
||||
struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
|
||||
struct uvc_buffer *buf = container_of(vb, struct uvc_buffer, buf);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&queue->irqlock, flags);
|
||||
|
||||
if (likely(!(queue->flags & UVC_QUEUE_DISCONNECTED))) {
|
||||
list_add_tail(&buf->queue, &queue->irqqueue);
|
||||
} else {
|
||||
/* If the device is disconnected return the buffer to userspace
|
||||
* directly. The next QBUF call will fail with -ENODEV.
|
||||
*/
|
||||
buf->state = UVC_BUF_STATE_ERROR;
|
||||
vb2_buffer_done(&buf->buf, VB2_BUF_STATE_ERROR);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&queue->irqlock, flags);
|
||||
}
|
||||
|
||||
static void uvc_wait_prepare(struct vb2_queue *vq)
|
||||
{
|
||||
struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
|
||||
|
||||
mutex_unlock(&queue->mutex);
|
||||
}
|
||||
|
||||
static void uvc_wait_finish(struct vb2_queue *vq)
|
||||
{
|
||||
struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
|
||||
|
||||
mutex_lock(&queue->mutex);
|
||||
}
|
||||
|
||||
static struct vb2_ops uvc_queue_qops = {
|
||||
.queue_setup = uvc_queue_setup,
|
||||
.buf_prepare = uvc_buffer_prepare,
|
||||
.buf_queue = uvc_buffer_queue,
|
||||
.wait_prepare = uvc_wait_prepare,
|
||||
.wait_finish = uvc_wait_finish,
|
||||
};
|
||||
|
||||
static int uvc_queue_init(struct uvc_video_queue *queue,
|
||||
enum v4l2_buf_type type)
|
||||
{
|
||||
int ret;
|
||||
|
||||
queue->queue.type = type;
|
||||
queue->queue.io_modes = VB2_MMAP | VB2_USERPTR;
|
||||
queue->queue.drv_priv = queue;
|
||||
queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
|
||||
queue->queue.ops = &uvc_queue_qops;
|
||||
queue->queue.mem_ops = &vb2_vmalloc_memops;
|
||||
queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
|
||||
| V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
|
||||
ret = vb2_queue_init(&queue->queue);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_init(&queue->mutex);
|
||||
spin_lock_init(&queue->irqlock);
|
||||
INIT_LIST_HEAD(&queue->irqqueue);
|
||||
queue->flags = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free the video buffers.
|
||||
*/
|
||||
static void uvc_free_buffers(struct uvc_video_queue *queue)
|
||||
{
|
||||
mutex_lock(&queue->mutex);
|
||||
vb2_queue_release(&queue->queue);
|
||||
mutex_unlock(&queue->mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate the video buffers.
|
||||
*/
|
||||
static int uvc_alloc_buffers(struct uvc_video_queue *queue,
|
||||
struct v4l2_requestbuffers *rb)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&queue->mutex);
|
||||
ret = vb2_reqbufs(&queue->queue, rb);
|
||||
mutex_unlock(&queue->mutex);
|
||||
|
||||
return ret ? ret : rb->count;
|
||||
}
|
||||
|
||||
static int uvc_query_buffer(struct uvc_video_queue *queue,
|
||||
struct v4l2_buffer *buf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&queue->mutex);
|
||||
ret = vb2_querybuf(&queue->queue, buf);
|
||||
mutex_unlock(&queue->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int uvc_queue_buffer(struct uvc_video_queue *queue,
|
||||
struct v4l2_buffer *buf)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&queue->mutex);
|
||||
ret = vb2_qbuf(&queue->queue, buf);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
spin_lock_irqsave(&queue->irqlock, flags);
|
||||
ret = (queue->flags & UVC_QUEUE_PAUSED) != 0;
|
||||
queue->flags &= ~UVC_QUEUE_PAUSED;
|
||||
spin_unlock_irqrestore(&queue->irqlock, flags);
|
||||
|
||||
done:
|
||||
mutex_unlock(&queue->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dequeue a video buffer. If nonblocking is false, block until a buffer is
|
||||
* available.
|
||||
*/
|
||||
static int uvc_dequeue_buffer(struct uvc_video_queue *queue,
|
||||
struct v4l2_buffer *buf, int nonblocking)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&queue->mutex);
|
||||
ret = vb2_dqbuf(&queue->queue, buf, nonblocking);
|
||||
mutex_unlock(&queue->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Poll the video queue.
|
||||
*
|
||||
* This function implements video queue polling and is intended to be used by
|
||||
* the device poll handler.
|
||||
*/
|
||||
static unsigned int uvc_queue_poll(struct uvc_video_queue *queue,
|
||||
struct file *file, poll_table *wait)
|
||||
{
|
||||
unsigned int ret;
|
||||
|
||||
mutex_lock(&queue->mutex);
|
||||
ret = vb2_poll(&queue->queue, file, wait);
|
||||
mutex_unlock(&queue->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int uvc_queue_mmap(struct uvc_video_queue *queue,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&queue->mutex);
|
||||
ret = vb2_mmap(&queue->queue, vma);
|
||||
mutex_unlock(&queue->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_MMU
|
||||
/*
|
||||
* Get unmapped area.
|
||||
*
|
||||
* NO-MMU arch need this function to make mmap() work correctly.
|
||||
*/
|
||||
static unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue,
|
||||
unsigned long pgoff)
|
||||
{
|
||||
unsigned long ret;
|
||||
|
||||
mutex_lock(&queue->mutex);
|
||||
ret = vb2_get_unmapped_area(&queue->queue, 0, 0, pgoff, 0);
|
||||
mutex_unlock(&queue->mutex);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Cancel the video buffers queue.
|
||||
*
|
||||
* Cancelling the queue marks all buffers on the irq queue as erroneous,
|
||||
* wakes them up and removes them from the queue.
|
||||
*
|
||||
* If the disconnect parameter is set, further calls to uvc_queue_buffer will
|
||||
* fail with -ENODEV.
|
||||
*
|
||||
* This function acquires the irq spinlock and can be called from interrupt
|
||||
* context.
|
||||
*/
|
||||
static void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect)
|
||||
{
|
||||
struct uvc_buffer *buf;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&queue->irqlock, flags);
|
||||
while (!list_empty(&queue->irqqueue)) {
|
||||
buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
|
||||
queue);
|
||||
list_del(&buf->queue);
|
||||
buf->state = UVC_BUF_STATE_ERROR;
|
||||
vb2_buffer_done(&buf->buf, VB2_BUF_STATE_ERROR);
|
||||
}
|
||||
/* This must be protected by the irqlock spinlock to avoid race
|
||||
* conditions between uvc_queue_buffer and the disconnection event that
|
||||
* could result in an interruptible wait in uvc_dequeue_buffer. Do not
|
||||
* blindly replace this logic by checking for the UVC_DEV_DISCONNECTED
|
||||
* state outside the queue code.
|
||||
*/
|
||||
if (disconnect)
|
||||
queue->flags |= UVC_QUEUE_DISCONNECTED;
|
||||
spin_unlock_irqrestore(&queue->irqlock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable or disable the video buffers queue.
|
||||
*
|
||||
* The queue must be enabled before starting video acquisition and must be
|
||||
* disabled after stopping it. This ensures that the video buffers queue
|
||||
* state can be properly initialized before buffers are accessed from the
|
||||
* interrupt handler.
|
||||
*
|
||||
* Enabling the video queue initializes parameters (such as sequence number,
|
||||
* sync pattern, ...). If the queue is already enabled, return -EBUSY.
|
||||
*
|
||||
* Disabling the video queue cancels the queue and removes all buffers from
|
||||
* the main queue.
|
||||
*
|
||||
* This function can't be called from interrupt context. Use
|
||||
* uvc_queue_cancel() instead.
|
||||
*/
|
||||
static int uvc_queue_enable(struct uvc_video_queue *queue, int enable)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&queue->mutex);
|
||||
if (enable) {
|
||||
ret = vb2_streamon(&queue->queue, queue->queue.type);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
queue->sequence = 0;
|
||||
queue->buf_used = 0;
|
||||
} else {
|
||||
ret = vb2_streamoff(&queue->queue, queue->queue.type);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
spin_lock_irqsave(&queue->irqlock, flags);
|
||||
INIT_LIST_HEAD(&queue->irqqueue);
|
||||
|
||||
/*
|
||||
* FIXME: We need to clear the DISCONNECTED flag to ensure that
|
||||
* applications will be able to queue buffers for the next
|
||||
* streaming run. However, clearing it here doesn't guarantee
|
||||
* that the device will be reconnected in the meantime.
|
||||
*/
|
||||
queue->flags &= ~UVC_QUEUE_DISCONNECTED;
|
||||
spin_unlock_irqrestore(&queue->irqlock, flags);
|
||||
}
|
||||
|
||||
done:
|
||||
mutex_unlock(&queue->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* called with &queue_irqlock held.. */
|
||||
static struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
|
||||
struct uvc_buffer *buf)
|
||||
{
|
||||
struct uvc_buffer *nextbuf;
|
||||
|
||||
if ((queue->flags & UVC_QUEUE_DROP_INCOMPLETE) &&
|
||||
buf->length != buf->bytesused) {
|
||||
buf->state = UVC_BUF_STATE_QUEUED;
|
||||
vb2_set_plane_payload(&buf->buf, 0, 0);
|
||||
return buf;
|
||||
}
|
||||
|
||||
list_del(&buf->queue);
|
||||
if (!list_empty(&queue->irqqueue))
|
||||
nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
|
||||
queue);
|
||||
else
|
||||
nextbuf = NULL;
|
||||
|
||||
buf->buf.v4l2_buf.field = V4L2_FIELD_NONE;
|
||||
buf->buf.v4l2_buf.sequence = queue->sequence++;
|
||||
v4l2_get_timestamp(&buf->buf.v4l2_buf.timestamp);
|
||||
|
||||
vb2_set_plane_payload(&buf->buf, 0, buf->bytesused);
|
||||
vb2_buffer_done(&buf->buf, VB2_BUF_STATE_DONE);
|
||||
|
||||
return nextbuf;
|
||||
}
|
||||
|
||||
static struct uvc_buffer *uvc_queue_head(struct uvc_video_queue *queue)
|
||||
{
|
||||
struct uvc_buffer *buf = NULL;
|
||||
|
||||
if (!list_empty(&queue->irqqueue))
|
||||
buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
|
||||
queue);
|
||||
else
|
||||
queue->flags |= UVC_QUEUE_PAUSED;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
63
drivers/usb/gadget/function/uvc_queue.h
Normal file
63
drivers/usb/gadget/function/uvc_queue.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#ifndef _UVC_QUEUE_H_
|
||||
#define _UVC_QUEUE_H_
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <media/videobuf2-core.h>
|
||||
|
||||
/* Maximum frame size in bytes, for sanity checking. */
|
||||
#define UVC_MAX_FRAME_SIZE (16*1024*1024)
|
||||
/* Maximum number of video buffers. */
|
||||
#define UVC_MAX_VIDEO_BUFFERS 32
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Structures.
|
||||
*/
|
||||
|
||||
enum uvc_buffer_state {
|
||||
UVC_BUF_STATE_IDLE = 0,
|
||||
UVC_BUF_STATE_QUEUED = 1,
|
||||
UVC_BUF_STATE_ACTIVE = 2,
|
||||
UVC_BUF_STATE_DONE = 3,
|
||||
UVC_BUF_STATE_ERROR = 4,
|
||||
};
|
||||
|
||||
struct uvc_buffer {
|
||||
struct vb2_buffer buf;
|
||||
struct list_head queue;
|
||||
|
||||
enum uvc_buffer_state state;
|
||||
void *mem;
|
||||
unsigned int length;
|
||||
unsigned int bytesused;
|
||||
};
|
||||
|
||||
#define UVC_QUEUE_DISCONNECTED (1 << 0)
|
||||
#define UVC_QUEUE_DROP_INCOMPLETE (1 << 1)
|
||||
#define UVC_QUEUE_PAUSED (1 << 2)
|
||||
|
||||
struct uvc_video_queue {
|
||||
struct vb2_queue queue;
|
||||
struct mutex mutex; /* Protects queue */
|
||||
|
||||
unsigned int flags;
|
||||
__u32 sequence;
|
||||
|
||||
unsigned int buf_used;
|
||||
|
||||
spinlock_t irqlock; /* Protects flags and irqqueue */
|
||||
struct list_head irqqueue;
|
||||
};
|
||||
|
||||
static inline int uvc_queue_streaming(struct uvc_video_queue *queue)
|
||||
{
|
||||
return vb2_is_streaming(&queue->queue);
|
||||
}
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
|
||||
#endif /* _UVC_QUEUE_H_ */
|
||||
|
||||
365
drivers/usb/gadget/function/uvc_v4l2.c
Normal file
365
drivers/usb/gadget/function/uvc_v4l2.c
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
* uvc_v4l2.c -- USB Video Class Gadget driver
|
||||
*
|
||||
* Copyright (C) 2009-2010
|
||||
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include <media/v4l2-dev.h>
|
||||
#include <media/v4l2-event.h>
|
||||
#include <media/v4l2-ioctl.h>
|
||||
|
||||
#include "uvc.h"
|
||||
#include "uvc_queue.h"
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Requests handling
|
||||
*/
|
||||
|
||||
static int
|
||||
uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data)
|
||||
{
|
||||
struct usb_composite_dev *cdev = uvc->func.config->cdev;
|
||||
struct usb_request *req = uvc->control_req;
|
||||
|
||||
if (data->length < 0)
|
||||
return usb_ep_set_halt(cdev->gadget->ep0);
|
||||
|
||||
req->length = min_t(unsigned int, uvc->event_length, data->length);
|
||||
req->zero = data->length < uvc->event_length;
|
||||
|
||||
memcpy(req->buf, data->data, req->length);
|
||||
|
||||
return usb_ep_queue(cdev->gadget->ep0, req, GFP_KERNEL);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* V4L2
|
||||
*/
|
||||
|
||||
struct uvc_format
|
||||
{
|
||||
u8 bpp;
|
||||
u32 fcc;
|
||||
};
|
||||
|
||||
static struct uvc_format uvc_formats[] = {
|
||||
{ 16, V4L2_PIX_FMT_YUYV },
|
||||
{ 0, V4L2_PIX_FMT_MJPEG },
|
||||
};
|
||||
|
||||
static int
|
||||
uvc_v4l2_get_format(struct uvc_video *video, struct v4l2_format *fmt)
|
||||
{
|
||||
fmt->fmt.pix.pixelformat = video->fcc;
|
||||
fmt->fmt.pix.width = video->width;
|
||||
fmt->fmt.pix.height = video->height;
|
||||
fmt->fmt.pix.field = V4L2_FIELD_NONE;
|
||||
fmt->fmt.pix.bytesperline = video->bpp * video->width / 8;
|
||||
fmt->fmt.pix.sizeimage = video->imagesize;
|
||||
fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
|
||||
fmt->fmt.pix.priv = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_v4l2_set_format(struct uvc_video *video, struct v4l2_format *fmt)
|
||||
{
|
||||
struct uvc_format *format;
|
||||
unsigned int imagesize;
|
||||
unsigned int bpl;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(uvc_formats); ++i) {
|
||||
format = &uvc_formats[i];
|
||||
if (format->fcc == fmt->fmt.pix.pixelformat)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == ARRAY_SIZE(uvc_formats)) {
|
||||
printk(KERN_INFO "Unsupported format 0x%08x.\n",
|
||||
fmt->fmt.pix.pixelformat);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bpl = format->bpp * fmt->fmt.pix.width / 8;
|
||||
imagesize = bpl ? bpl * fmt->fmt.pix.height : fmt->fmt.pix.sizeimage;
|
||||
|
||||
video->fcc = format->fcc;
|
||||
video->bpp = format->bpp;
|
||||
video->width = fmt->fmt.pix.width;
|
||||
video->height = fmt->fmt.pix.height;
|
||||
video->imagesize = imagesize;
|
||||
|
||||
fmt->fmt.pix.field = V4L2_FIELD_NONE;
|
||||
fmt->fmt.pix.bytesperline = bpl;
|
||||
fmt->fmt.pix.sizeimage = imagesize;
|
||||
fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
|
||||
fmt->fmt.pix.priv = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_v4l2_open(struct file *file)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
struct uvc_file_handle *handle;
|
||||
|
||||
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
|
||||
if (handle == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
v4l2_fh_init(&handle->vfh, vdev);
|
||||
v4l2_fh_add(&handle->vfh);
|
||||
|
||||
handle->device = &uvc->video;
|
||||
file->private_data = &handle->vfh;
|
||||
|
||||
uvc_function_connect(uvc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_v4l2_release(struct file *file)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data);
|
||||
struct uvc_video *video = handle->device;
|
||||
|
||||
uvc_function_disconnect(uvc);
|
||||
|
||||
uvc_video_enable(video, 0);
|
||||
uvc_free_buffers(&video->queue);
|
||||
|
||||
file->private_data = NULL;
|
||||
v4l2_fh_del(&handle->vfh);
|
||||
v4l2_fh_exit(&handle->vfh);
|
||||
kfree(handle);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long
|
||||
uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data);
|
||||
struct usb_composite_dev *cdev = uvc->func.config->cdev;
|
||||
struct uvc_video *video = &uvc->video;
|
||||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
/* Query capabilities */
|
||||
case VIDIOC_QUERYCAP:
|
||||
{
|
||||
struct v4l2_capability *cap = arg;
|
||||
|
||||
memset(cap, 0, sizeof *cap);
|
||||
strlcpy(cap->driver, "g_uvc", sizeof(cap->driver));
|
||||
strlcpy(cap->card, cdev->gadget->name, sizeof(cap->card));
|
||||
strlcpy(cap->bus_info, dev_name(&cdev->gadget->dev),
|
||||
sizeof cap->bus_info);
|
||||
cap->version = DRIVER_VERSION_NUMBER;
|
||||
cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Get & Set format */
|
||||
case VIDIOC_G_FMT:
|
||||
{
|
||||
struct v4l2_format *fmt = arg;
|
||||
|
||||
if (fmt->type != video->queue.queue.type)
|
||||
return -EINVAL;
|
||||
|
||||
return uvc_v4l2_get_format(video, fmt);
|
||||
}
|
||||
|
||||
case VIDIOC_S_FMT:
|
||||
{
|
||||
struct v4l2_format *fmt = arg;
|
||||
|
||||
if (fmt->type != video->queue.queue.type)
|
||||
return -EINVAL;
|
||||
|
||||
return uvc_v4l2_set_format(video, fmt);
|
||||
}
|
||||
|
||||
/* Buffers & streaming */
|
||||
case VIDIOC_REQBUFS:
|
||||
{
|
||||
struct v4l2_requestbuffers *rb = arg;
|
||||
|
||||
if (rb->type != video->queue.queue.type)
|
||||
return -EINVAL;
|
||||
|
||||
ret = uvc_alloc_buffers(&video->queue, rb);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case VIDIOC_QUERYBUF:
|
||||
{
|
||||
struct v4l2_buffer *buf = arg;
|
||||
|
||||
return uvc_query_buffer(&video->queue, buf);
|
||||
}
|
||||
|
||||
case VIDIOC_QBUF:
|
||||
if ((ret = uvc_queue_buffer(&video->queue, arg)) < 0)
|
||||
return ret;
|
||||
|
||||
return uvc_video_pump(video);
|
||||
|
||||
case VIDIOC_DQBUF:
|
||||
return uvc_dequeue_buffer(&video->queue, arg,
|
||||
file->f_flags & O_NONBLOCK);
|
||||
|
||||
case VIDIOC_STREAMON:
|
||||
{
|
||||
int *type = arg;
|
||||
|
||||
if (*type != video->queue.queue.type)
|
||||
return -EINVAL;
|
||||
|
||||
/* Enable UVC video. */
|
||||
ret = uvc_video_enable(video, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Complete the alternate setting selection setup phase now that
|
||||
* userspace is ready to provide video frames.
|
||||
*/
|
||||
uvc_function_setup_continue(uvc);
|
||||
uvc->state = UVC_STATE_STREAMING;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
case VIDIOC_STREAMOFF:
|
||||
{
|
||||
int *type = arg;
|
||||
|
||||
if (*type != video->queue.queue.type)
|
||||
return -EINVAL;
|
||||
|
||||
return uvc_video_enable(video, 0);
|
||||
}
|
||||
|
||||
/* Events */
|
||||
case VIDIOC_DQEVENT:
|
||||
{
|
||||
struct v4l2_event *event = arg;
|
||||
|
||||
ret = v4l2_event_dequeue(&handle->vfh, event,
|
||||
file->f_flags & O_NONBLOCK);
|
||||
if (ret == 0 && event->type == UVC_EVENT_SETUP) {
|
||||
struct uvc_event *uvc_event = (void *)&event->u.data;
|
||||
|
||||
/* Tell the complete callback to generate an event for
|
||||
* the next request that will be enqueued by
|
||||
* uvc_event_write.
|
||||
*/
|
||||
uvc->event_setup_out =
|
||||
!(uvc_event->req.bRequestType & USB_DIR_IN);
|
||||
uvc->event_length = uvc_event->req.wLength;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
case VIDIOC_SUBSCRIBE_EVENT:
|
||||
{
|
||||
struct v4l2_event_subscription *sub = arg;
|
||||
|
||||
if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST)
|
||||
return -EINVAL;
|
||||
|
||||
return v4l2_event_subscribe(&handle->vfh, arg, 2, NULL);
|
||||
}
|
||||
|
||||
case VIDIOC_UNSUBSCRIBE_EVENT:
|
||||
return v4l2_event_unsubscribe(&handle->vfh, arg);
|
||||
|
||||
case UVCIOC_SEND_RESPONSE:
|
||||
ret = uvc_send_response(uvc, arg);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long
|
||||
uvc_v4l2_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
return video_usercopy(file, cmd, arg, uvc_v4l2_do_ioctl);
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
|
||||
return uvc_queue_mmap(&uvc->video.queue, vma);
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
uvc_v4l2_poll(struct file *file, poll_table *wait)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
|
||||
return uvc_queue_poll(&uvc->video.queue, file, wait);
|
||||
}
|
||||
|
||||
#ifndef CONFIG_MMU
|
||||
static unsigned long uvc_v4l2_get_unmapped_area(struct file *file,
|
||||
unsigned long addr, unsigned long len, unsigned long pgoff,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
|
||||
return uvc_queue_get_unmapped_area(&uvc->video.queue, pgoff);
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct v4l2_file_operations uvc_v4l2_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = uvc_v4l2_open,
|
||||
.release = uvc_v4l2_release,
|
||||
.ioctl = uvc_v4l2_ioctl,
|
||||
.mmap = uvc_v4l2_mmap,
|
||||
.poll = uvc_v4l2_poll,
|
||||
#ifndef CONFIG_MMU
|
||||
.get_unmapped_area = uvc_v4l2_get_unmapped_area,
|
||||
#endif
|
||||
};
|
||||
|
||||
394
drivers/usb/gadget/function/uvc_video.c
Normal file
394
drivers/usb/gadget/function/uvc_video.c
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
/*
|
||||
* uvc_video.c -- USB Video Class Gadget driver
|
||||
*
|
||||
* Copyright (C) 2009-2010
|
||||
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
|
||||
#include <media/v4l2-dev.h>
|
||||
|
||||
#include "uvc.h"
|
||||
#include "uvc_queue.h"
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Video codecs
|
||||
*/
|
||||
|
||||
static int
|
||||
uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf,
|
||||
u8 *data, int len)
|
||||
{
|
||||
data[0] = 2;
|
||||
data[1] = UVC_STREAM_EOH | video->fid;
|
||||
|
||||
if (buf->bytesused - video->queue.buf_used <= len - 2)
|
||||
data[1] |= UVC_STREAM_EOF;
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_video_encode_data(struct uvc_video *video, struct uvc_buffer *buf,
|
||||
u8 *data, int len)
|
||||
{
|
||||
struct uvc_video_queue *queue = &video->queue;
|
||||
unsigned int nbytes;
|
||||
void *mem;
|
||||
|
||||
/* Copy video data to the USB buffer. */
|
||||
mem = buf->mem + queue->buf_used;
|
||||
nbytes = min((unsigned int)len, buf->bytesused - queue->buf_used);
|
||||
|
||||
memcpy(data, mem, nbytes);
|
||||
queue->buf_used += nbytes;
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
static void
|
||||
uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
|
||||
struct uvc_buffer *buf)
|
||||
{
|
||||
void *mem = req->buf;
|
||||
int len = video->req_size;
|
||||
int ret;
|
||||
|
||||
/* Add a header at the beginning of the payload. */
|
||||
if (video->payload_size == 0) {
|
||||
ret = uvc_video_encode_header(video, buf, mem, len);
|
||||
video->payload_size += ret;
|
||||
mem += ret;
|
||||
len -= ret;
|
||||
}
|
||||
|
||||
/* Process video data. */
|
||||
len = min((int)(video->max_payload_size - video->payload_size), len);
|
||||
ret = uvc_video_encode_data(video, buf, mem, len);
|
||||
|
||||
video->payload_size += ret;
|
||||
len -= ret;
|
||||
|
||||
req->length = video->req_size - len;
|
||||
req->zero = video->payload_size == video->max_payload_size;
|
||||
|
||||
if (buf->bytesused == video->queue.buf_used) {
|
||||
video->queue.buf_used = 0;
|
||||
buf->state = UVC_BUF_STATE_DONE;
|
||||
uvc_queue_next_buffer(&video->queue, buf);
|
||||
video->fid ^= UVC_STREAM_FID;
|
||||
|
||||
video->payload_size = 0;
|
||||
}
|
||||
|
||||
if (video->payload_size == video->max_payload_size ||
|
||||
buf->bytesused == video->queue.buf_used)
|
||||
video->payload_size = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
|
||||
struct uvc_buffer *buf)
|
||||
{
|
||||
void *mem = req->buf;
|
||||
int len = video->req_size;
|
||||
int ret;
|
||||
|
||||
/* Add the header. */
|
||||
ret = uvc_video_encode_header(video, buf, mem, len);
|
||||
mem += ret;
|
||||
len -= ret;
|
||||
|
||||
/* Process video data. */
|
||||
ret = uvc_video_encode_data(video, buf, mem, len);
|
||||
len -= ret;
|
||||
|
||||
req->length = video->req_size - len;
|
||||
|
||||
if (buf->bytesused == video->queue.buf_used) {
|
||||
video->queue.buf_used = 0;
|
||||
buf->state = UVC_BUF_STATE_DONE;
|
||||
uvc_queue_next_buffer(&video->queue, buf);
|
||||
video->fid ^= UVC_STREAM_FID;
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Request handling
|
||||
*/
|
||||
|
||||
/*
|
||||
* I somehow feel that synchronisation won't be easy to achieve here. We have
|
||||
* three events that control USB requests submission:
|
||||
*
|
||||
* - USB request completion: the completion handler will resubmit the request
|
||||
* if a video buffer is available.
|
||||
*
|
||||
* - USB interface setting selection: in response to a SET_INTERFACE request,
|
||||
* the handler will start streaming if a video buffer is available and if
|
||||
* video is not currently streaming.
|
||||
*
|
||||
* - V4L2 buffer queueing: the driver will start streaming if video is not
|
||||
* currently streaming.
|
||||
*
|
||||
* Race conditions between those 3 events might lead to deadlocks or other
|
||||
* nasty side effects.
|
||||
*
|
||||
* The "video currently streaming" condition can't be detected by the irqqueue
|
||||
* being empty, as a request can still be in flight. A separate "queue paused"
|
||||
* flag is thus needed.
|
||||
*
|
||||
* The paused flag will be set when we try to retrieve the irqqueue head if the
|
||||
* queue is empty, and cleared when we queue a buffer.
|
||||
*
|
||||
* The USB request completion handler will get the buffer at the irqqueue head
|
||||
* under protection of the queue spinlock. If the queue is empty, the streaming
|
||||
* paused flag will be set. Right after releasing the spinlock a userspace
|
||||
* application can queue a buffer. The flag will then cleared, and the ioctl
|
||||
* handler will restart the video stream.
|
||||
*/
|
||||
static void
|
||||
uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct uvc_video *video = req->context;
|
||||
struct uvc_video_queue *queue = &video->queue;
|
||||
struct uvc_buffer *buf;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
switch (req->status) {
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case -ESHUTDOWN: /* disconnect from host. */
|
||||
printk(KERN_INFO "VS request cancelled.\n");
|
||||
uvc_queue_cancel(queue, 1);
|
||||
goto requeue;
|
||||
|
||||
default:
|
||||
printk(KERN_INFO "VS request completed with status %d.\n",
|
||||
req->status);
|
||||
uvc_queue_cancel(queue, 0);
|
||||
goto requeue;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&video->queue.irqlock, flags);
|
||||
buf = uvc_queue_head(&video->queue);
|
||||
if (buf == NULL) {
|
||||
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
||||
goto requeue;
|
||||
}
|
||||
|
||||
video->encode(req, video, buf);
|
||||
|
||||
if ((ret = usb_ep_queue(ep, req, GFP_ATOMIC)) < 0) {
|
||||
printk(KERN_INFO "Failed to queue request (%d).\n", ret);
|
||||
usb_ep_set_halt(ep);
|
||||
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
||||
goto requeue;
|
||||
}
|
||||
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
||||
|
||||
return;
|
||||
|
||||
requeue:
|
||||
spin_lock_irqsave(&video->req_lock, flags);
|
||||
list_add_tail(&req->list, &video->req_free);
|
||||
spin_unlock_irqrestore(&video->req_lock, flags);
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_video_free_requests(struct uvc_video *video)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
|
||||
if (video->req[i]) {
|
||||
usb_ep_free_request(video->ep, video->req[i]);
|
||||
video->req[i] = NULL;
|
||||
}
|
||||
|
||||
if (video->req_buffer[i]) {
|
||||
kfree(video->req_buffer[i]);
|
||||
video->req_buffer[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&video->req_free);
|
||||
video->req_size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_video_alloc_requests(struct uvc_video *video)
|
||||
{
|
||||
unsigned int req_size;
|
||||
unsigned int i;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
BUG_ON(video->req_size);
|
||||
|
||||
req_size = video->ep->maxpacket
|
||||
* max_t(unsigned int, video->ep->maxburst, 1)
|
||||
* (video->ep->mult + 1);
|
||||
|
||||
for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
|
||||
video->req_buffer[i] = kmalloc(req_size, GFP_KERNEL);
|
||||
if (video->req_buffer[i] == NULL)
|
||||
goto error;
|
||||
|
||||
video->req[i] = usb_ep_alloc_request(video->ep, GFP_KERNEL);
|
||||
if (video->req[i] == NULL)
|
||||
goto error;
|
||||
|
||||
video->req[i]->buf = video->req_buffer[i];
|
||||
video->req[i]->length = 0;
|
||||
video->req[i]->complete = uvc_video_complete;
|
||||
video->req[i]->context = video;
|
||||
|
||||
list_add_tail(&video->req[i]->list, &video->req_free);
|
||||
}
|
||||
|
||||
video->req_size = req_size;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
uvc_video_free_requests(video);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Video streaming
|
||||
*/
|
||||
|
||||
/*
|
||||
* uvc_video_pump - Pump video data into the USB requests
|
||||
*
|
||||
* This function fills the available USB requests (listed in req_free) with
|
||||
* video data from the queued buffers.
|
||||
*/
|
||||
static int
|
||||
uvc_video_pump(struct uvc_video *video)
|
||||
{
|
||||
struct usb_request *req;
|
||||
struct uvc_buffer *buf;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
/* FIXME TODO Race between uvc_video_pump and requests completion
|
||||
* handler ???
|
||||
*/
|
||||
|
||||
while (1) {
|
||||
/* Retrieve the first available USB request, protected by the
|
||||
* request lock.
|
||||
*/
|
||||
spin_lock_irqsave(&video->req_lock, flags);
|
||||
if (list_empty(&video->req_free)) {
|
||||
spin_unlock_irqrestore(&video->req_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
req = list_first_entry(&video->req_free, struct usb_request,
|
||||
list);
|
||||
list_del(&req->list);
|
||||
spin_unlock_irqrestore(&video->req_lock, flags);
|
||||
|
||||
/* Retrieve the first available video buffer and fill the
|
||||
* request, protected by the video queue irqlock.
|
||||
*/
|
||||
spin_lock_irqsave(&video->queue.irqlock, flags);
|
||||
buf = uvc_queue_head(&video->queue);
|
||||
if (buf == NULL) {
|
||||
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
||||
break;
|
||||
}
|
||||
|
||||
video->encode(req, video, buf);
|
||||
|
||||
/* Queue the USB request */
|
||||
ret = usb_ep_queue(video->ep, req, GFP_ATOMIC);
|
||||
if (ret < 0) {
|
||||
printk(KERN_INFO "Failed to queue request (%d)\n", ret);
|
||||
usb_ep_set_halt(video->ep);
|
||||
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&video->req_lock, flags);
|
||||
list_add_tail(&req->list, &video->req_free);
|
||||
spin_unlock_irqrestore(&video->req_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable or disable the video stream.
|
||||
*/
|
||||
static int
|
||||
uvc_video_enable(struct uvc_video *video, int enable)
|
||||
{
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
if (video->ep == NULL) {
|
||||
printk(KERN_INFO "Video enable failed, device is "
|
||||
"uninitialized.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!enable) {
|
||||
for (i = 0; i < UVC_NUM_REQUESTS; ++i)
|
||||
usb_ep_dequeue(video->ep, video->req[i]);
|
||||
|
||||
uvc_video_free_requests(video);
|
||||
uvc_queue_enable(&video->queue, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((ret = uvc_queue_enable(&video->queue, 1)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = uvc_video_alloc_requests(video)) < 0)
|
||||
return ret;
|
||||
|
||||
if (video->max_payload_size) {
|
||||
video->encode = uvc_video_encode_bulk;
|
||||
video->payload_size = 0;
|
||||
} else
|
||||
video->encode = uvc_video_encode_isoc;
|
||||
|
||||
return uvc_video_pump(video);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the UVC video stream.
|
||||
*/
|
||||
static int
|
||||
uvc_video_init(struct uvc_video *video)
|
||||
{
|
||||
INIT_LIST_HEAD(&video->req_free);
|
||||
spin_lock_init(&video->req_lock);
|
||||
|
||||
video->fcc = V4L2_PIX_FMT_YUYV;
|
||||
video->bpp = 16;
|
||||
video->width = 320;
|
||||
video->height = 240;
|
||||
video->imagesize = 320 * 240 * 2;
|
||||
|
||||
/* Initialize the video buffers queue. */
|
||||
uvc_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue