Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,58 @@
# SPDX-License-Identifier: GPL-2.0
#
# USB peripheral controller drivers
#
ccflags-y := -I$(srctree)/drivers/usb/gadget/
ccflags-y += -I$(srctree)/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
obj-$(CONFIG_USB_U_AUDIO) += u_audio.o
usb_f_uac1-y := f_uac1.o
obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o
usb_f_uac1_legacy-y := f_uac1_legacy.o u_uac1_legacy.o
obj-$(CONFIG_USB_F_UAC1_LEGACY) += usb_f_uac1_legacy.o
usb_f_uac2-y := f_uac2.o
obj-$(CONFIG_USB_F_UAC2) += usb_f_uac2.o
usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o
ifneq ($(CONFIG_TRACING),)
CFLAGS_uvc_trace.o := -I$(src)
usb_f_uvc-y += uvc_trace.o
endif
obj-$(CONFIG_USB_F_UVC) += usb_f_uvc.o
usb_f_midi-y := f_midi.o
obj-$(CONFIG_USB_F_MIDI) += usb_f_midi.o
usb_f_midi2-y := f_midi2.o
obj-$(CONFIG_USB_F_MIDI2) += usb_f_midi2.o
usb_f_hid-y := f_hid.o
obj-$(CONFIG_USB_F_HID) += usb_f_hid.o
usb_f_printer-y := f_printer.o
obj-$(CONFIG_USB_F_PRINTER) += usb_f_printer.o
usb_f_tcm-y := f_tcm.o
obj-$(CONFIG_USB_F_TCM) += usb_f_tcm.o
@@ -0,0 +1,902 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* 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)
*/
/* #define VERBOSE_DEBUG */
#include <linux/cleanup.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/usb/gadget.h>
#include "u_serial.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 bInterfaceProtocol;
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;
/* SerialState notification -- CDC 1.1 section 6.3.5 (OUTPUT) */
u16 serial_state;
};
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 = DYNAMIC */
/* .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 = DYNAMIC */
/* .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) {
dev_dbg(&cdev->gadget->dev, "acm ttyGS%d completion, err %d\n",
acm->port_num, req->status);
return;
}
/* normal completion */
if (req->actual != sizeof(acm->port_line_coding)) {
dev_dbg(&cdev->gadget->dev, "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_send_break(struct gserial *port, int duration);
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 USB_CDC_CTRL_DTR bit; and when it clears
* that bit, we should return to that no-flow state.
*/
acm->port_handshake_bits = w_value;
break;
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
| USB_CDC_REQ_SEND_BREAK:
if (w_index != acm->ctrl_id)
goto invalid;
acm_send_break(&acm->port, w_value);
break;
default:
invalid:
dev_vdbg(&cdev->gadget->dev,
"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) {
dev_dbg(&cdev->gadget->dev,
"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->enabled) {
dev_vdbg(&cdev->gadget->dev,
"reset acm control interface %d\n", intf);
usb_ep_disable(acm->notify);
}
if (!acm->notify->desc)
if (config_ep_by_speed(cdev->gadget, f, acm->notify))
return -EINVAL;
usb_ep_enable(acm->notify);
} else if (intf == acm->data_id) {
if (acm->notify->enabled) {
dev_dbg(&cdev->gadget->dev,
"reset acm ttyGS%d\n", acm->port_num);
gserial_disconnect(&acm->port);
}
if (!acm->port.in->desc || !acm->port.out->desc) {
dev_dbg(&cdev->gadget->dev,
"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;
dev_dbg(&cdev->gadget->dev, "acm ttyGS%d deactivated\n", acm->port_num);
gserial_disconnect(&acm->port);
usb_ep_disable(acm->notify);
}
/*-------------------------------------------------------------------------*/
/**
* 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;
__le16 serial_state;
spin_lock(&acm->lock);
if (acm->notify_req) {
dev_dbg(&cdev->gadget->dev, "acm ttyGS%d serial state %04x\n",
acm->port_num, acm->serial_state);
serial_state = cpu_to_le16(acm->serial_state);
status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE,
0, &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 |= USB_CDC_SERIAL_STATE_DSR | USB_CDC_SERIAL_STATE_DCD;
acm_notify_serial_state(acm);
}
static void acm_disconnect(struct gserial *port)
{
struct f_acm *acm = port_to_acm(port);
acm->serial_state &= ~(USB_CDC_SERIAL_STATE_DSR | USB_CDC_SERIAL_STATE_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 &= ~USB_CDC_SERIAL_STATE_BREAK;
if (duration)
state |= USB_CDC_SERIAL_STATE_BREAK;
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;
struct usb_request *request __free(free_usb_request) = NULL;
/* 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)
return status;
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)
return status;
acm->data_id = status;
acm_data_interface_desc.bInterfaceNumber = status;
acm_union_desc.bSlaveInterface0 = status;
acm_call_mgmt_descriptor.bDataInterface = status;
/* allocate instance-specific endpoints */
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc);
if (!ep)
return -ENODEV;
acm->port.in = ep;
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc);
if (!ep)
return -ENODEV;
acm->port.out = ep;
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc);
if (!ep)
return -ENODEV;
acm->notify = ep;
acm_iad_descriptor.bFunctionProtocol = acm->bInterfaceProtocol;
acm_control_interface_desc.bInterfaceProtocol = acm->bInterfaceProtocol;
/* allocate notification */
request = gs_alloc_req(ep,
sizeof(struct usb_cdc_notification) + 2,
GFP_KERNEL);
if (!request)
return -ENODEV;
request->complete = acm_cdc_notify_complete;
request->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, acm_ss_function);
if (status)
return status;
acm->notify_req = no_free_ptr(request);
dev_dbg(&cdev->gadget->dev,
"acm ttyGS%d: IN/%s OUT/%s NOTIFY/%s\n",
acm->port_num,
acm->port.in->name, acm->port.out->name,
acm->notify->name);
return 0;
}
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);
struct f_serial_opts *opts;
opts = container_of(f->fi, struct f_serial_opts, func_inst);
kfree(acm);
mutex_lock(&opts->lock);
opts->instances--;
mutex_unlock(&opts->lock);
}
static void acm_resume(struct usb_function *f)
{
struct f_acm *acm = func_to_acm(f);
gserial_resume(&acm->port);
}
static void acm_suspend(struct usb_function *f)
{
struct f_acm *acm = func_to_acm(f);
gserial_suspend(&acm->port);
}
static struct usb_function *acm_alloc_func(struct usb_function_instance *fi)
{
struct f_serial_opts *opts;
struct f_acm *acm;
acm = kzalloc_obj(*acm);
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);
mutex_lock(&opts->lock);
acm->port_num = opts->port_num;
acm->bInterfaceProtocol = opts->protocol;
opts->instances++;
mutex_unlock(&opts->lock);
acm->port.func.unbind = acm_unbind;
acm->port.func.free_func = acm_free_func;
acm->port.func.resume = acm_resume;
acm->port.func.suspend = acm_suspend;
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);
}
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 const struct configfs_item_operations acm_item_ops = {
.release = acm_attr_release,
};
#ifdef CONFIG_U_SERIAL_CONSOLE
static ssize_t f_acm_console_store(struct config_item *item,
const char *page, size_t count)
{
return gserial_set_console(to_f_serial_opts(item)->port_num,
page, count);
}
static ssize_t f_acm_console_show(struct config_item *item, char *page)
{
return gserial_get_console(to_f_serial_opts(item)->port_num, page);
}
CONFIGFS_ATTR(f_acm_, console);
#endif /* CONFIG_U_SERIAL_CONSOLE */
static ssize_t f_acm_port_num_show(struct config_item *item, char *page)
{
return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num);
}
CONFIGFS_ATTR_RO(f_acm_, port_num);
static ssize_t f_acm_protocol_show(struct config_item *item, char *page)
{
return sprintf(page, "%u\n", to_f_serial_opts(item)->protocol);
}
static ssize_t f_acm_protocol_store(struct config_item *item,
const char *page, size_t count)
{
struct f_serial_opts *opts = to_f_serial_opts(item);
int ret;
mutex_lock(&opts->lock);
if (opts->instances) {
ret = -EBUSY;
goto out;
}
ret = kstrtou8(page, 0, &opts->protocol);
if (ret)
goto out;
ret = count;
out:
mutex_unlock(&opts->lock);
return ret;
}
CONFIGFS_ATTR(f_acm_, protocol);
static struct configfs_attribute *acm_attrs[] = {
#ifdef CONFIG_U_SERIAL_CONSOLE
&f_acm_attr_console,
#endif
&f_acm_attr_port_num,
&f_acm_attr_protocol,
NULL,
};
static const 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);
mutex_destroy(&opts->lock);
kfree(opts);
}
static struct usb_function_instance *acm_alloc_instance(void)
{
struct f_serial_opts *opts;
int ret;
opts = kzalloc_obj(*opts);
if (!opts)
return ERR_PTR(-ENOMEM);
opts->protocol = USB_CDC_ACM_PROTO_AT_V25TER;
opts->func_inst.free_func_inst = acm_free_instance;
mutex_init(&opts->lock);
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_DESCRIPTION("USB CDC serial (ACM) function driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,981 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* f_ecm.c -- USB CDC Ethernet (ECM) link function driver
*
* Copyright (C) 2003-2005,2008 David Brownell
* Copyright (C) 2008 Nokia Corporation
*/
/* #define VERBOSE_DEBUG */
#include <linux/cleanup.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/etherdevice.h>
#include <linux/string_choices.h>
#include <linux/usb/gadget.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;
atomic_t notify_count;
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);
}
/*-------------------------------------------------------------------------*/
/*
* 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 (atomic_read(&ecm->notify_count))
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", str_true_false(ecm->is_open));
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(gether_bitrate(cdev->gadget));
data[1] = data[0];
DBG(cdev, "notify speed %d\n", gether_bitrate(cdev->gadget));
ecm->notify_state = ECM_NOTIFY_NONE;
break;
}
event->bmRequestType = 0xA1;
event->wIndex = cpu_to_le16(ecm->ctrl_id);
atomic_inc(&ecm->notify_count);
status = usb_ep_queue(ecm->notify, req, GFP_ATOMIC);
if (status < 0) {
atomic_dec(&ecm->notify_count);
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 */
atomic_dec(&ecm->notify_count);
break;
case -ECONNRESET:
case -ESHUTDOWN:
atomic_set(&ecm->notify_count, 0);
ecm->notify_state = ECM_NOTIFY_NONE;
break;
default:
DBG(cdev, "event %02x --> %d\n",
event->bNotificationType, req->status);
atomic_dec(&ecm->notify_count);
break;
}
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;
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);
/* Data interface has two altsettings, 0 and 1 */
} else if (intf == ecm->data_id) {
if (alt > 1)
goto fail;
if (ecm->port.in_ep->enabled) {
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_zlp_supported(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->enabled ? 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->enabled) {
gether_disconnect(&ecm->port);
} else {
ecm->port.in_ep->desc = NULL;
ecm->port.out_ep->desc = NULL;
}
usb_ep_disable(ecm->notify);
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 = 0;
struct usb_ep *ep;
struct f_ecm_opts *ecm_opts;
struct net_device *net __free(detach_gadget) = NULL;
struct usb_request *request __free(free_usb_request) = NULL;
if (!can_support_ecm(cdev->gadget))
return -EINVAL;
ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst);
scoped_guard(mutex, &ecm_opts->lock)
if (ecm_opts->bind_count == 0 && !ecm_opts->bound) {
if (!device_is_registered(&ecm_opts->net->dev)) {
gether_set_gadget(ecm_opts->net, cdev->gadget);
status = gether_register_netdev(ecm_opts->net);
} else
status = gether_attach_gadget(ecm_opts->net, cdev->gadget);
if (status)
return status;
net = ecm_opts->net;
}
ecm_string_defs[1].s = ecm->ethaddr;
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)
return status;
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)
return status;
ecm->data_id = status;
ecm_data_nop_intf.bInterfaceNumber = status;
ecm_data_intf.bInterfaceNumber = status;
ecm_union_desc.bSlaveInterface0 = status;
/* allocate instance-specific endpoints */
ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_in_desc);
if (!ep)
return -ENODEV;
ecm->port.in_ep = ep;
ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_out_desc);
if (!ep)
return -ENODEV;
ecm->port.out_ep = ep;
/* 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)
return -ENODEV;
ecm->notify = ep;
/* allocate notification request and buffer */
request = usb_ep_alloc_request(ep, GFP_KERNEL);
if (!request)
return -ENOMEM;
request->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL);
if (!request->buf)
return -ENOMEM;
request->context = ecm;
request->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, ecm_ss_function);
if (status)
return status;
/* 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;
ecm->notify_req = no_free_ptr(request);
ecm_opts->bind_count++;
retain_and_null_ptr(net);
DBG(cdev, "CDC Ethernet: IN/%s OUT/%s NOTIFY/%s\n",
ecm->port.in_ep->name, ecm->port.out_ep->name,
ecm->notify->name);
return 0;
}
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[] = {
&ecm_opts_attr_dev_addr,
&ecm_opts_attr_host_addr,
&ecm_opts_attr_qmult,
&ecm_opts_attr_ifname,
NULL,
};
static const 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 (device_is_registered(&opts->net->dev))
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_obj(*opts);
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_suspend(struct usb_function *f)
{
struct f_ecm *ecm = func_to_ecm(f);
struct usb_composite_dev *cdev = ecm->port.func.config->cdev;
DBG(cdev, "ECM Suspend\n");
gether_suspend(&ecm->port);
}
static void ecm_resume(struct usb_function *f)
{
struct f_ecm *ecm = func_to_ecm(f);
struct usb_composite_dev *cdev = ecm->port.func.config->cdev;
DBG(cdev, "ECM Resume\n");
gether_resume(&ecm->port);
}
static int ecm_get_status(struct usb_function *f)
{
return (f->func_wakeup_armed ? USB_INTRF_STAT_FUNC_RW : 0) |
USB_INTRF_STAT_FUNC_RW_CAP;
}
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);
struct f_ecm_opts *ecm_opts;
DBG(c->cdev, "ecm unbind\n");
ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst);
usb_free_all_descriptors(f);
if (atomic_read(&ecm->notify_count)) {
usb_ep_dequeue(ecm->notify, ecm->notify_req);
atomic_set(&ecm->notify_count, 0);
}
kfree(ecm->notify_req->buf);
usb_ep_free_request(ecm->notify, ecm->notify_req);
ecm_opts->bind_count--;
if (ecm_opts->bind_count == 0 && !ecm_opts->bound)
gether_detach_gadget(ecm_opts->net);
}
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_obj(*ecm);
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->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;
ecm->port.func.suspend = ecm_suspend;
ecm->port.func.get_status = ecm_get_status;
ecm->port.func.resume = ecm_resume;
return &ecm->port.func;
}
DECLARE_USB_FUNCTION_INIT(ecm, ecm_alloc_inst, ecm_alloc);
MODULE_DESCRIPTION("USB CDC Ethernet (ECM) link function driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Brownell");
@@ -0,0 +1,687 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* 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
*/
#include <linux/cleanup.h>
#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;
};
struct in_context {
struct sk_buff *skb;
struct usb_ep *ep;
};
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;
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 -EOPNOTSUPP;
}
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) {
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->enabled)
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;
struct net_device *net __free(detach_gadget) = NULL;
eem_opts = container_of(f->fi, struct f_eem_opts, func_inst);
scoped_guard(mutex, &eem_opts->lock)
if (eem_opts->bind_count == 0 && !eem_opts->bound) {
if (!device_is_registered(&eem_opts->net->dev)) {
gether_set_gadget(eem_opts->net, cdev->gadget);
status = gether_register_netdev(eem_opts->net);
} else
status = gether_attach_gadget(eem_opts->net, cdev->gadget);
if (status)
return status;
net = eem_opts->net;
}
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)
return status;
eem->ctrl_id = status;
eem_intf.bInterfaceNumber = status;
/* allocate instance-specific endpoints */
ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_in_desc);
if (!ep)
return -ENODEV;
eem->port.in_ep = ep;
ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_out_desc);
if (!ep)
return -ENODEV;
eem->port.out_ep = ep;
/* 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, eem_ss_function);
if (status)
return status;
eem_opts->bind_count++;
retain_and_null_ptr(net);
DBG(cdev, "CDC Ethernet (EEM): IN/%s OUT/%s\n",
eem->port.in_ep->name, eem->port.out_ep->name);
return 0;
}
static void eem_cmd_complete(struct usb_ep *ep, struct usb_request *req)
{
struct in_context *ctx = req->context;
dev_kfree_skb_any(ctx->skb);
kfree(req->buf);
usb_ep_free_request(ctx->ep, req);
kfree(ctx);
}
/*
* 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 headroom, tailroom, padlen = 0;
u16 len;
if (!skb)
return NULL;
len = skb->len;
headroom = skb_headroom(skb);
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;
struct in_context *ctx;
struct usb_ep *ep;
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));
ep = port->in_ep;
req = usb_ep_alloc_request(ep, GFP_ATOMIC);
if (!req) {
dev_kfree_skb_any(skb2);
goto next;
}
req->buf = kmalloc(skb2->len, GFP_KERNEL);
if (!req->buf) {
usb_ep_free_request(ep, req);
dev_kfree_skb_any(skb2);
goto next;
}
ctx = kmalloc_obj(*ctx);
if (!ctx) {
kfree(req->buf);
usb_ep_free_request(ep, req);
dev_kfree_skb_any(skb2);
goto next;
}
ctx->skb = skb2;
ctx->ep = ep;
skb_copy_bits(skb2, 0, req->buf, skb2->len);
req->length = skb2->len;
req->complete = eem_cmd_complete;
req->zero = 1;
req->context = ctx;
if (usb_ep_queue(port->in_ep, req, GFP_ATOMIC)) {
DBG(cdev, "echo response queue fail\n");
kfree(ctx);
kfree(req->buf);
usb_ep_free_request(ep, req);
dev_kfree_skb_any(skb2);
}
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");
goto next;
}
skb_trim(skb2, len - ETH_FCS_LEN);
skb3 = skb_copy_expand(skb2,
NET_IP_ALIGN,
0,
GFP_ATOMIC);
if (unlikely(!skb3)) {
dev_kfree_skb_any(skb2);
goto next;
}
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[] = {
&eem_opts_attr_dev_addr,
&eem_opts_attr_host_addr,
&eem_opts_attr_qmult,
&eem_opts_attr_ifname,
NULL,
};
static const 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 (device_is_registered(&opts->net->dev))
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_obj(*opts);
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)
{
struct f_eem_opts *opts;
DBG(c->cdev, "eem unbind\n");
opts = container_of(f->fi, struct f_eem_opts, func_inst);
usb_free_all_descriptors(f);
opts->bind_count--;
if (opts->bind_count == 0 && !opts->bound)
gether_detach_gadget(opts->net);
}
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_obj(*eem);
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_DESCRIPTION("USB CDC Ethernet (EEM) link function driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Brownell");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,597 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* f_loopback.c - USB peripheral loopback configuration driver
*
* Copyright (C) 2003-2008 David Brownell
* Copyright (C) 2008 by Nokia Corporation
*/
/* #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 <linux/usb/func_utils.h>
#include "g_zero.h"
/*
* LOOPBACK FUNCTION ... a testing vehicle for USB peripherals,
*
* This takes messages of various sizes written OUT to a device, and loops
* them back so they can be read IN from it. It has been used by certain
* test applications. It supports limited testing of data queueing logic.
*/
struct f_loopback {
struct usb_function function;
struct usb_ep *in_ep;
struct usb_ep *out_ep;
unsigned qlen;
unsigned buflen;
};
static inline struct f_loopback *func_to_loop(struct usb_function *f)
{
return container_of(f, struct f_loopback, function);
}
/*-------------------------------------------------------------------------*/
static struct usb_interface_descriptor loopback_intf = {
.bLength = sizeof(loopback_intf),
.bDescriptorType = USB_DT_INTERFACE,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
/* .iInterface = DYNAMIC */
};
/* full speed support: */
static struct usb_endpoint_descriptor fs_loop_source_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
};
static struct usb_endpoint_descriptor fs_loop_sink_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
};
static struct usb_descriptor_header *fs_loopback_descs[] = {
(struct usb_descriptor_header *) &loopback_intf,
(struct usb_descriptor_header *) &fs_loop_sink_desc,
(struct usb_descriptor_header *) &fs_loop_source_desc,
NULL,
};
/* high speed support: */
static struct usb_endpoint_descriptor hs_loop_source_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(512),
};
static struct usb_endpoint_descriptor hs_loop_sink_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(512),
};
static struct usb_descriptor_header *hs_loopback_descs[] = {
(struct usb_descriptor_header *) &loopback_intf,
(struct usb_descriptor_header *) &hs_loop_source_desc,
(struct usb_descriptor_header *) &hs_loop_sink_desc,
NULL,
};
/* super speed support: */
static struct usb_endpoint_descriptor ss_loop_source_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(1024),
};
static struct usb_ss_ep_comp_descriptor ss_loop_source_comp_desc = {
.bLength = USB_DT_SS_EP_COMP_SIZE,
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
.bMaxBurst = 0,
.bmAttributes = 0,
.wBytesPerInterval = 0,
};
static struct usb_endpoint_descriptor ss_loop_sink_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(1024),
};
static struct usb_ss_ep_comp_descriptor ss_loop_sink_comp_desc = {
.bLength = USB_DT_SS_EP_COMP_SIZE,
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
.bMaxBurst = 0,
.bmAttributes = 0,
.wBytesPerInterval = 0,
};
static struct usb_descriptor_header *ss_loopback_descs[] = {
(struct usb_descriptor_header *) &loopback_intf,
(struct usb_descriptor_header *) &ss_loop_source_desc,
(struct usb_descriptor_header *) &ss_loop_source_comp_desc,
(struct usb_descriptor_header *) &ss_loop_sink_desc,
(struct usb_descriptor_header *) &ss_loop_sink_comp_desc,
NULL,
};
/* function-specific strings: */
static struct usb_string strings_loopback[] = {
[0].s = "loop input to output",
{ } /* end of list */
};
static struct usb_gadget_strings stringtab_loop = {
.language = 0x0409, /* en-us */
.strings = strings_loopback,
};
static struct usb_gadget_strings *loopback_strings[] = {
&stringtab_loop,
NULL,
};
/*-------------------------------------------------------------------------*/
static int loopback_bind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct f_loopback *loop = func_to_loop(f);
int id;
int ret;
/* allocate interface ID(s) */
id = usb_interface_id(c, f);
if (id < 0)
return id;
loopback_intf.bInterfaceNumber = id;
id = usb_string_id(cdev);
if (id < 0)
return id;
strings_loopback[0].id = id;
loopback_intf.iInterface = id;
/* allocate endpoints */
loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc);
if (!loop->in_ep) {
autoconf_fail:
ERROR(cdev, "%s: can't autoconfigure on %s\n",
f->name, cdev->gadget->name);
return -ENODEV;
}
loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc);
if (!loop->out_ep)
goto autoconf_fail;
/* support high speed hardware */
hs_loop_source_desc.bEndpointAddress =
fs_loop_source_desc.bEndpointAddress;
hs_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress;
/* support super speed hardware */
ss_loop_source_desc.bEndpointAddress =
fs_loop_source_desc.bEndpointAddress;
ss_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress;
ret = usb_assign_descriptors(f, fs_loopback_descs, hs_loopback_descs,
ss_loopback_descs, ss_loopback_descs);
if (ret)
return ret;
DBG(cdev, "%s: IN/%s, OUT/%s\n",
f->name, loop->in_ep->name, loop->out_ep->name);
return 0;
}
static void lb_free_func(struct usb_function *f)
{
struct f_lb_opts *opts;
opts = container_of(f->fi, struct f_lb_opts, func_inst);
mutex_lock(&opts->lock);
opts->refcnt--;
mutex_unlock(&opts->lock);
usb_free_all_descriptors(f);
kfree(func_to_loop(f));
}
static void loopback_complete(struct usb_ep *ep, struct usb_request *req)
{
struct f_loopback *loop = ep->driver_data;
struct usb_composite_dev *cdev = loop->function.config->cdev;
int status = req->status;
switch (status) {
case 0: /* normal completion? */
if (ep == loop->out_ep) {
/*
* We received some data from the host so let's
* queue it so host can read the from our in ep
*/
struct usb_request *in_req = req->context;
in_req->zero = (req->actual < req->length);
in_req->length = req->actual;
ep = loop->in_ep;
req = in_req;
} else {
/*
* We have just looped back a bunch of data
* to host. Now let's wait for some more data.
*/
req = req->context;
ep = loop->out_ep;
}
/* queue the buffer back to host or for next bunch of data */
status = usb_ep_queue(ep, req, GFP_ATOMIC);
if (status == 0) {
return;
} else {
ERROR(cdev, "Unable to loop back buffer to %s: %d\n",
ep->name, status);
goto free_req;
}
/* "should never get here" */
default:
ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name,
status, req->actual, req->length);
fallthrough;
/* NOTE: since this driver doesn't maintain an explicit record
* of requests it submitted (just maintains qlen count), we
* rely on the hardware driver to clean up on disconnect or
* endpoint disable.
*/
case -ECONNABORTED: /* hardware forced ep reset */
case -ECONNRESET: /* request dequeued */
case -ESHUTDOWN: /* disconnect from host */
free_req:
usb_ep_free_request(ep == loop->in_ep ?
loop->out_ep : loop->in_ep,
req->context);
free_ep_req(ep, req);
return;
}
}
static void disable_loopback(struct f_loopback *loop)
{
struct usb_composite_dev *cdev;
cdev = loop->function.config->cdev;
disable_endpoints(cdev, loop->in_ep, loop->out_ep, NULL, NULL);
VDBG(cdev, "%s disabled\n", loop->function.name);
}
static inline struct usb_request *lb_alloc_ep_req(struct usb_ep *ep, int len)
{
return alloc_ep_req(ep, len);
}
static int alloc_requests(struct usb_composite_dev *cdev,
struct f_loopback *loop)
{
struct usb_request *in_req, *out_req;
int i;
int result = 0;
/*
* allocate a bunch of read buffers and queue them all at once.
* we buffer at most 'qlen' transfers; We allocate buffers only
* for out transfer and reuse them in IN transfers to implement
* our loopback functionality
*/
for (i = 0; i < loop->qlen && result == 0; i++) {
result = -ENOMEM;
in_req = usb_ep_alloc_request(loop->in_ep, GFP_ATOMIC);
if (!in_req)
goto fail;
out_req = lb_alloc_ep_req(loop->out_ep, loop->buflen);
if (!out_req)
goto fail_in;
in_req->complete = loopback_complete;
out_req->complete = loopback_complete;
in_req->buf = out_req->buf;
/* length will be set in complete routine */
in_req->context = out_req;
out_req->context = in_req;
result = usb_ep_queue(loop->out_ep, out_req, GFP_ATOMIC);
if (result) {
ERROR(cdev, "%s queue req --> %d\n",
loop->out_ep->name, result);
goto fail_out;
}
}
return 0;
fail_out:
free_ep_req(loop->out_ep, out_req);
fail_in:
usb_ep_free_request(loop->in_ep, in_req);
fail:
return result;
}
static int enable_endpoint(struct usb_composite_dev *cdev,
struct f_loopback *loop, struct usb_ep *ep)
{
int result;
result = config_ep_by_speed(cdev->gadget, &(loop->function), ep);
if (result)
goto out;
result = usb_ep_enable(ep);
if (result < 0)
goto out;
ep->driver_data = loop;
result = 0;
out:
return result;
}
static int
enable_loopback(struct usb_composite_dev *cdev, struct f_loopback *loop)
{
int result = 0;
result = enable_endpoint(cdev, loop, loop->in_ep);
if (result)
goto out;
result = enable_endpoint(cdev, loop, loop->out_ep);
if (result)
goto disable_in;
result = alloc_requests(cdev, loop);
if (result)
goto disable_out;
DBG(cdev, "%s enabled\n", loop->function.name);
return 0;
disable_out:
usb_ep_disable(loop->out_ep);
disable_in:
usb_ep_disable(loop->in_ep);
out:
return result;
}
static int loopback_set_alt(struct usb_function *f,
unsigned intf, unsigned alt)
{
struct f_loopback *loop = func_to_loop(f);
struct usb_composite_dev *cdev = f->config->cdev;
/* we know alt is zero */
disable_loopback(loop);
return enable_loopback(cdev, loop);
}
static void loopback_disable(struct usb_function *f)
{
struct f_loopback *loop = func_to_loop(f);
disable_loopback(loop);
}
static struct usb_function *loopback_alloc(struct usb_function_instance *fi)
{
struct f_loopback *loop;
struct f_lb_opts *lb_opts;
loop = kzalloc_obj(*loop);
if (!loop)
return ERR_PTR(-ENOMEM);
lb_opts = container_of(fi, struct f_lb_opts, func_inst);
mutex_lock(&lb_opts->lock);
lb_opts->refcnt++;
mutex_unlock(&lb_opts->lock);
loop->buflen = lb_opts->bulk_buflen;
loop->qlen = lb_opts->qlen;
if (!loop->qlen)
loop->qlen = 32;
loop->function.name = "loopback";
loop->function.bind = loopback_bind;
loop->function.set_alt = loopback_set_alt;
loop->function.disable = loopback_disable;
loop->function.strings = loopback_strings;
loop->function.free_func = lb_free_func;
return &loop->function;
}
static inline struct f_lb_opts *to_f_lb_opts(struct config_item *item)
{
return container_of(to_config_group(item), struct f_lb_opts,
func_inst.group);
}
static void lb_attr_release(struct config_item *item)
{
struct f_lb_opts *lb_opts = to_f_lb_opts(item);
usb_put_function_instance(&lb_opts->func_inst);
}
static const struct configfs_item_operations lb_item_ops = {
.release = lb_attr_release,
};
static ssize_t f_lb_opts_qlen_show(struct config_item *item, char *page)
{
struct f_lb_opts *opts = to_f_lb_opts(item);
int result;
mutex_lock(&opts->lock);
result = sprintf(page, "%d\n", opts->qlen);
mutex_unlock(&opts->lock);
return result;
}
static ssize_t f_lb_opts_qlen_store(struct config_item *item,
const char *page, size_t len)
{
struct f_lb_opts *opts = to_f_lb_opts(item);
int ret;
u32 num;
mutex_lock(&opts->lock);
if (opts->refcnt) {
ret = -EBUSY;
goto end;
}
ret = kstrtou32(page, 0, &num);
if (ret)
goto end;
opts->qlen = num;
ret = len;
end:
mutex_unlock(&opts->lock);
return ret;
}
CONFIGFS_ATTR(f_lb_opts_, qlen);
static ssize_t f_lb_opts_bulk_buflen_show(struct config_item *item, char *page)
{
struct f_lb_opts *opts = to_f_lb_opts(item);
int result;
mutex_lock(&opts->lock);
result = sprintf(page, "%d\n", opts->bulk_buflen);
mutex_unlock(&opts->lock);
return result;
}
static ssize_t f_lb_opts_bulk_buflen_store(struct config_item *item,
const char *page, size_t len)
{
struct f_lb_opts *opts = to_f_lb_opts(item);
int ret;
u32 num;
mutex_lock(&opts->lock);
if (opts->refcnt) {
ret = -EBUSY;
goto end;
}
ret = kstrtou32(page, 0, &num);
if (ret)
goto end;
opts->bulk_buflen = num;
ret = len;
end:
mutex_unlock(&opts->lock);
return ret;
}
CONFIGFS_ATTR(f_lb_opts_, bulk_buflen);
static struct configfs_attribute *lb_attrs[] = {
&f_lb_opts_attr_qlen,
&f_lb_opts_attr_bulk_buflen,
NULL,
};
static const 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_obj(*lb_opts);
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)
{
return usb_function_register(&Loopbackusb_func);
}
void __exit lb_modexit(void)
{
usb_function_unregister(&Loopbackusb_func);
}
MODULE_DESCRIPTION("USB peripheral loopback configuration driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,144 @@
/* SPDX-License-Identifier: GPL-2.0 */
#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_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;
char inquiry_string[INQUIRY_STRING_LEN];
};
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(struct usb_function_instance *fi)
{
return container_of(fi, struct fsg_opts, func_inst);
}
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);
void fsg_common_remove_luns(struct fsg_common *common);
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);
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 */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,491 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* 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.
*/
/* #define VERBOSE_DEBUG */
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/module.h>
#include "u_serial.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 cur_alt;
u8 port_num;
};
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 */
dev_dbg(&cdev->gadget->dev,
"reset obex ttyGS%d control\n", obex->port_num);
} else if (intf == obex->data_id) {
if (alt > 1)
goto fail;
if (obex->port.in->enabled) {
dev_dbg(&cdev->gadget->dev,
"reset obex ttyGS%d\n", obex->port_num);
gserial_disconnect(&obex->port);
}
if (!obex->port.in->desc || !obex->port.out->desc) {
dev_dbg(&cdev->gadget->dev,
"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) {
dev_dbg(&cdev->gadget->dev,
"activate obex ttyGS%d\n", obex->port_num);
gserial_connect(&obex->port, obex->port_num);
}
} else
goto fail;
obex->cur_alt = alt;
return 0;
fail:
return -EINVAL;
}
static int obex_get_alt(struct usb_function *f, unsigned intf)
{
struct f_obex *obex = func_to_obex(f);
return obex->cur_alt;
}
static void obex_disable(struct usb_function *f)
{
struct f_obex *obex = func_to_obex(f);
struct usb_composite_dev *cdev = f->config->cdev;
dev_dbg(&cdev->gadget->dev, "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;
status = usb_function_activate(&g->func);
if (status)
dev_dbg(&cdev->gadget->dev,
"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;
status = usb_function_deactivate(&g->func);
if (status)
dev_dbg(&cdev->gadget->dev,
"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_is_altset_supported(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 = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_out_desc);
if (!ep)
goto fail;
obex->port.out = ep;
/* 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,
NULL);
if (status)
goto fail;
dev_dbg(&cdev->gadget->dev, "obex ttyGS%d: IN/%s OUT/%s\n",
obex->port_num,
obex->port.in->name, obex->port.out->name);
return 0;
fail:
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);
}
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 const struct configfs_item_operations obex_item_ops = {
.release = obex_attr_release,
};
static ssize_t f_obex_port_num_show(struct config_item *item, char *page)
{
return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num);
}
CONFIGFS_ATTR_RO(f_obex_, port_num);
static struct configfs_attribute *acm_attrs[] = {
&f_obex_attr_port_num,
NULL,
};
static const 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_obj(*opts);
if (!opts)
return ERR_PTR(-ENOMEM);
opts->func_inst.free_func_inst = obex_free_inst;
ret = gserial_alloc_line_no_console(&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_obj(*obex);
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;
obex->port.func.bind_deactivated = true;
return &obex->port.func;
}
DECLARE_USB_FUNCTION_INIT(obex, obex_alloc_inst, obex_alloc);
MODULE_AUTHOR("Felipe Balbi");
MODULE_DESCRIPTION("USB CDC OBEX function driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,742 @@
// SPDX-License-Identifier: GPL-2.0
/*
* f_phonet.c -- USB CDC Phonet function
*
* Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
*
* Author: Rémi Denis-Courmont
*/
#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[];
};
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++;
fallthrough;
default:
dev->stats.tx_errors++;
}
dev_kfree_skb_any(skb);
netif_wake_queue(dev);
}
static netdev_tx_t 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 const struct net_device_ops pn_netdev_ops = {
.ndo_open = pn_net_open,
.ndo_stop = pn_net_close,
.ndo_start_xmit = pn_net_xmit,
};
static void pn_net_setup(struct net_device *dev)
{
const u8 addr = PN_MEDIA_USB;
dev->features = 0;
dev->type = ARPHRD_PHONET;
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
dev->mtu = PHONET_DEV_MTU;
dev->min_mtu = PHONET_MIN_MTU;
dev->max_mtu = PHONET_MAX_MTU;
dev->hard_header_len = 1;
dev->addr_len = 1;
dev_addr_set(dev, &addr);
dev->tx_queue_len = 1;
dev->netdev_ops = &pn_netdev_ops;
dev->needs_free_netdev = true;
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 = __dev_alloc_page(gfp_flags | __GFP_NOMEMALLOC);
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 (unlikely(skb_shinfo(skb)->nr_frags >= MAX_SKB_FRAGS)) {
/* Frame count from host exceeds frags[] capacity */
dev_kfree_skb_any(skb);
if (fp->rx.skb == skb)
fp->rx.skb = NULL;
dev->stats.rx_length_errors++;
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 */
skb_put_data(skb, 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++;
fallthrough;
default:
dev->stats.rx_errors++;
break;
}
if (page)
put_page(page);
if (req)
pn_rx_submit(fp, req, GFP_ATOMIC);
}
/*-------------------------------------------------------------------------*/
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);
if (fp->in_ep->enabled)
__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);
}
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 = usb_ep_autoconfig(gadget, &pn_fs_source_desc);
if (!ep)
goto err;
fp->in_ep = ep;
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, 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]);
usb_free_all_descriptors(f);
err:
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);
}
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 const struct configfs_item_operations phonet_item_ops = {
.release = phonet_attr_release,
};
static ssize_t f_phonet_ifname_show(struct config_item *item, char *page)
{
return gether_get_ifname(to_f_phonet_opts(item)->net, page, PAGE_SIZE);
}
CONFIGFS_ATTR_RO(f_phonet_, ifname);
static struct configfs_attribute *phonet_attrs[] = {
&f_phonet_attr_ifname,
NULL,
};
static const 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_obj(*opts);
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;
fp = kzalloc_flex(*fp, out_reqv, phonet_rxq_size);
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", NET_NAME_UNKNOWN,
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_DESCRIPTION("USB CDC Phonet function");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,405 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* 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
*/
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include "u_serial.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->enabled) {
dev_dbg(&cdev->gadget->dev,
"reset generic ttyGS%d\n", gser->port_num);
gserial_disconnect(&gser->port);
}
if (!gser->port.in->desc || !gser->port.out->desc) {
dev_dbg(&cdev->gadget->dev,
"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;
dev_dbg(&cdev->gadget->dev,
"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 = usb_ep_autoconfig(cdev->gadget, &gser_fs_out_desc);
if (!ep)
goto fail;
gser->port.out = ep;
/* 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, gser_ss_function);
if (status)
goto fail;
dev_dbg(&cdev->gadget->dev, "generic ttyGS%d: IN/%s OUT/%s\n",
gser->port_num,
gser->port.in->name, gser->port.out->name);
return 0;
fail:
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);
}
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 const struct configfs_item_operations serial_item_ops = {
.release = serial_attr_release,
};
#ifdef CONFIG_U_SERIAL_CONSOLE
static ssize_t f_serial_console_store(struct config_item *item,
const char *page, size_t count)
{
return gserial_set_console(to_f_serial_opts(item)->port_num,
page, count);
}
static ssize_t f_serial_console_show(struct config_item *item, char *page)
{
return gserial_get_console(to_f_serial_opts(item)->port_num, page);
}
CONFIGFS_ATTR(f_serial_, console);
#endif /* CONFIG_U_SERIAL_CONSOLE */
static ssize_t f_serial_port_num_show(struct config_item *item, char *page)
{
return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num);
}
CONFIGFS_ATTR_RO(f_serial_, port_num);
static struct configfs_attribute *acm_attrs[] = {
#ifdef CONFIG_U_SERIAL_CONSOLE
&f_serial_attr_console,
#endif
&f_serial_attr_port_num,
NULL,
};
static const 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_obj(*opts);
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)
{
struct f_gser *gser = func_to_gser(f);
/* Ensure port is disconnected before unbinding */
gserial_disconnect(&gser->port);
usb_free_all_descriptors(f);
}
static void gser_resume(struct usb_function *f)
{
struct f_gser *gser = func_to_gser(f);
gserial_resume(&gser->port);
}
static void gser_suspend(struct usb_function *f)
{
struct f_gser *gser = func_to_gser(f);
gserial_suspend(&gser->port);
}
static int gser_get_status(struct usb_function *f)
{
return (f->func_wakeup_armed ? USB_INTRF_STAT_FUNC_RW : 0) |
USB_INTRF_STAT_FUNC_RW_CAP;
}
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_obj(*gser);
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;
gser->port.func.resume = gser_resume;
gser->port.func.suspend = gser_suspend;
gser->port.func.get_status = gser_get_status;
return &gser->port.func;
}
DECLARE_USB_FUNCTION_INIT(gser, gser_alloc_inst, gser_alloc);
MODULE_DESCRIPTION("generic USB serial function driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Al Borchers");
MODULE_AUTHOR("David Brownell");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,512 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* f_subset.c -- "CDC Subset" Ethernet link function driver
*
* Copyright (C) 2003-2005,2008 David Brownell
* Copyright (C) 2008 Nokia Corporation
*/
#include <linux/cleanup.h>
#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 *) &ether_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 *) &ether_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 *) &ether_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->enabled) {
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;
struct net_device *net __free(detach_gadget) = NULL;
gether_opts = container_of(f->fi, struct f_gether_opts, func_inst);
scoped_guard(mutex, &gether_opts->lock)
if (gether_opts->bind_count == 0 && !gether_opts->bound) {
if (!device_is_registered(&gether_opts->net->dev)) {
gether_set_gadget(gether_opts->net, cdev->gadget);
status = gether_register_netdev(gether_opts->net);
} else
status = gether_attach_gadget(gether_opts->net, cdev->gadget);
if (status)
return status;
net = gether_opts->net;
}
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)
return status;
subset_data_intf.bInterfaceNumber = status;
/* allocate instance-specific endpoints */
ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_in_desc);
if (!ep)
return -ENODEV;
geth->port.in_ep = ep;
ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_out_desc);
if (!ep)
return -ENODEV;
geth->port.out_ep = ep;
/* 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, ss_eth_function);
if (status)
return status;
/* 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().
*/
gether_opts->bind_count++;
retain_and_null_ptr(net);
DBG(cdev, "CDC Subset: IN/%s OUT/%s\n",
geth->port.in_ep->name, geth->port.out_ep->name);
return 0;
}
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[] = {
&gether_opts_attr_dev_addr,
&gether_opts_attr_host_addr,
&gether_opts_attr_qmult,
&gether_opts_attr_ifname,
NULL,
};
static const 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 (device_is_registered(&opts->net->dev))
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_obj(*opts);
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;
struct f_gether_opts *opts;
opts = container_of(f->fi, struct f_gether_opts, func_inst);
eth = func_to_geth(f);
scoped_guard(mutex, &opts->lock)
opts->refcnt--;
kfree(eth);
}
static void geth_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct f_gether_opts *opts;
opts = container_of(f->fi, struct f_gether_opts, func_inst);
geth_string_defs[0].id = 0;
usb_free_all_descriptors(f);
opts->bind_count--;
if (opts->bind_count == 0 && !opts->bound)
gether_detach_gadget(opts->net);
}
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_obj(*geth);
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_DESCRIPTION("\"CDC Subset\" Ethernet link function driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Brownell");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* f_uvc.h -- USB Video Class Gadget driver
*
* Copyright (C) 2009-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef _F_UVC_H_
#define _F_UVC_H_
struct uvc_device;
void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
void uvc_function_connect(struct uvc_device *uvc);
void uvc_function_disconnect(struct uvc_device *uvc);
#endif /* _F_UVC_H_ */
@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* 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
#define GZERO_SS_BULK_QLEN 1
#define GZERO_SS_ISO_QLEN 8
struct usb_zero_options {
unsigned pattern;
unsigned isoc_interval;
unsigned isoc_maxpacket;
unsigned isoc_mult;
unsigned isoc_maxburst;
unsigned bulk_buflen;
unsigned qlen;
unsigned ss_bulk_qlen;
unsigned ss_iso_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;
unsigned bulk_maxburst;
unsigned bulk_qlen;
unsigned iso_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;
};
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 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 */
@@ -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 */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,202 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* RNDIS Definitions for Remote NDIS
*
* Authors: Benedikt Spranger, Pengutronix
* Robert Schwebel, Pengutronix
*
* 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 {
int 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;
spinlock_t resp_lock;
} rndis_params;
/* RNDIS Message parser and other useless functions */
int rndis_msg_parser(struct rndis_params *params, u8 *buf);
struct rndis_params *rndis_register(void (*resp_avail)(void *v), void *v);
void rndis_deregister(struct rndis_params *params);
int rndis_set_param_dev(struct rndis_params *params, struct net_device *dev,
u16 *cdc_filter);
int rndis_set_param_vendor(struct rndis_params *params, u32 vendorID,
const char *vendorDescr);
int rndis_set_param_medium(struct rndis_params *params, 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(struct rndis_params *params, u32 *length);
void rndis_free_response(struct rndis_params *params, u8 *buf);
void rndis_uninit(struct rndis_params *params);
int rndis_signal_connect(struct rndis_params *params);
int rndis_signal_disconnect(struct rndis_params *params);
int rndis_state(struct rndis_params *params);
extern void rndis_set_host_mac(struct rndis_params *params, const u8 *addr);
#endif /* _LINUX_RNDIS_H */
@@ -0,0 +1,541 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* 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 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/kstrtox.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).
*/
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 = filp->f_mapping->host;
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);
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 (S_ISBLK(inode->i_mode)) {
blksize = bdev_logical_block_size(I_BDEV(inode));
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.
* Sector size is already set to 2048 bytes.
*/
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 = file_path(curlun->filp, 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);
ssize_t fsg_show_inquiry_string(struct fsg_lun *curlun, char *buf)
{
return sprintf(buf, "%s\n", curlun->inquiry_string);
}
EXPORT_SYMBOL_GPL(fsg_show_inquiry_string);
/*
* 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 = kstrtobool(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 = kstrtobool(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 = kstrtobool(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 = kstrtobool(buf, &removable);
if (ret)
return ret;
curlun->removable = removable;
return count;
}
EXPORT_SYMBOL_GPL(fsg_store_removable);
ssize_t fsg_store_inquiry_string(struct fsg_lun *curlun, const char *buf,
size_t count)
{
const size_t len = min(count, sizeof(curlun->inquiry_string));
if (len == 0 || buf[0] == '\n') {
curlun->inquiry_string[0] = 0;
} else {
snprintf(curlun->inquiry_string,
sizeof(curlun->inquiry_string), "%-28s", buf);
if (curlun->inquiry_string[len-1] == '\n')
curlun->inquiry_string[len-1] = ' ';
}
return count;
}
EXPORT_SYMBOL_GPL(fsg_store_inquiry_string);
ssize_t fsg_store_forced_eject(struct fsg_lun *curlun, struct rw_semaphore *filesem,
const char *buf, size_t count)
{
int ret;
/*
* Forcibly detach the backing file from the LUN
* regardless of whether the host has allowed it.
*/
curlun->prevent_medium_removal = 0;
ret = fsg_store_file(curlun, filesem, "", 0);
return ret < 0 ? ret : count;
}
EXPORT_SYMBOL_GPL(fsg_store_forced_eject);
MODULE_DESCRIPTION("Common definitions for mass storage functionality");
MODULE_LICENSE("GPL");
@@ -0,0 +1,225 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef USB_STORAGE_COMMON_H
#define USB_STORAGE_COMMON_H
#include <linux/device.h>
#include <linux/usb/storage.h>
#include <scsi/scsi.h>
#include <linux/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))
/*
* Vendor (8 chars), product (16 chars), release (4 hexadecimal digits) and NUL
* byte
*/
#define INQUIRY_STRING_LEN ((size_t) (8 + 16 + 4 + 1))
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" */
char inquiry_string[INQUIRY_STRING_LEN];
};
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 (US_BULK_MAX_LUN_LIMIT + 1)
enum fsg_buffer_state {
BUF_STATE_SENDING = -2,
BUF_STATE_RECEIVING,
BUF_STATE_EMPTY = 0,
BUF_STATE_FULL
};
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;
struct usb_request *outreq;
};
enum fsg_state {
FSG_STATE_NORMAL,
FSG_STATE_ABORT_BULK_OUT,
FSG_STATE_PROTOCOL_RESET,
FSG_STATE_CONFIG_CHANGE,
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 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_inquiry_string(struct fsg_lun *curlun, 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);
ssize_t fsg_store_inquiry_string(struct fsg_lun *curlun, const char *buf,
size_t count);
ssize_t fsg_store_forced_eject(struct fsg_lun *curlun, struct rw_semaphore *filesem,
const char *buf, size_t count);
#endif /* USB_STORAGE_COMMON_H */
@@ -0,0 +1,164 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __TARGET_USB_GADGET_H__
#define __TARGET_USB_GADGET_H__
#include <linux/kref.h>
#include <linux/spinlock.h>
/* #include <linux/usb/uas.h> */
#include <linux/hashtable.h>
#include <linux/usb/composite.h>
#include <linux/usb/uas.h>
#include <linux/usb/storage.h>
#include <target/target_core_base.h>
#include <target/target_core_fabric.h>
#define USBG_NAMELEN 32
#define fuas_to_gadget(f) (f->function.config->cdev->gadget)
#define UASP_SS_EP_COMP_LOG_STREAMS 5
#define UASP_SS_EP_COMP_NUM_STREAMS (1 << UASP_SS_EP_COMP_LOG_STREAMS)
#define USBG_NUM_CMDS (UASP_SS_EP_COMP_NUM_STREAMS + 1)
enum {
USB_G_STR_INT_UAS = 0,
USB_G_STR_INT_BBB,
};
#define USB_G_ALT_INT_BBB 0
#define USB_G_ALT_INT_UAS 1
#define USB_G_DEFAULT_SESSION_TAGS USBG_NUM_CMDS
enum {
USBG_DELAYED_SET_ALT_IDLE = 0,
USBG_DELAYED_SET_ALT_QUEUED,
USBG_DELAYED_SET_ALT_RUNNING,
};
struct tcm_usbg_nexus {
struct se_session *tvn_se_sess;
};
struct usbg_tpg {
struct mutex tpg_mutex;
/* SAS port target portal group tag for TCM */
u16 tport_tpgt;
/* Pointer back to usbg_tport */
struct usbg_tport *tport;
struct workqueue_struct *workqueue;
/* Returned by usbg_make_tpg() */
struct se_portal_group se_tpg;
u32 gadget_connect;
struct tcm_usbg_nexus *tpg_nexus;
atomic_t tpg_port_count;
struct usb_function_instance *fi;
};
struct usbg_tport {
/* Binary World Wide unique Port Name for SAS Target port */
u64 tport_wwpn;
/* ASCII formatted WWPN for SAS Target port */
char tport_name[USBG_NAMELEN];
/* Returned by usbg_make_tport() */
struct se_wwn tport_wwn;
};
enum uas_state {
UASP_SEND_DATA,
UASP_RECEIVE_DATA,
UASP_SEND_STATUS,
UASP_QUEUE_COMMAND,
};
#define USBG_MAX_CMD 64
struct usbg_cmd {
/* common */
u8 cmd_buf[USBG_MAX_CMD];
u32 data_len;
struct work_struct work;
int unpacked_lun;
struct se_cmd se_cmd;
void *data_buf; /* used if no sg support available */
struct f_uas *fu;
struct kref ref;
struct usb_request *req;
u32 flags;
#define USBG_CMD_PENDING_DATA_WRITE BIT(0)
/* UAS only */
u16 tag;
u16 prio_attr;
struct sense_iu sense_iu;
struct response_iu response_iu;
enum uas_state state;
int tmr_func;
int tmr_rsp;
#define RC_RESPONSE_UNKNOWN 0xff
/* BOT only */
__le32 bot_tag;
unsigned int csw_code;
unsigned is_read:1;
};
struct uas_stream {
struct usb_request *req_in;
struct usb_request *req_out;
struct usb_request *req_status;
struct completion cmd_completion;
struct hlist_node node;
};
struct usbg_cdb {
struct usb_request *req;
void *buf;
};
struct bot_status {
struct usb_request *req;
struct bulk_cs_wrap csw;
};
struct f_uas {
struct usbg_tpg *tpg;
struct usb_function function;
u16 iface;
u32 flags;
#define USBG_ENABLED (1 << 0)
#define USBG_IS_UAS (1 << 1)
#define USBG_USE_STREAMS (1 << 2)
#define USBG_IS_BOT (1 << 3)
#define USBG_BOT_CMD_PEND (1 << 4)
#define USBG_BOT_WEDGED (1 << 5)
struct work_struct delayed_set_alt;
spinlock_t delayed_set_alt_lock; /* protects delayed_set_alt_* */
unsigned int delayed_alt;
unsigned int delayed_set_alt_state;
bool delayed_set_alt_cancel;
struct usbg_cdb cmd[USBG_NUM_CMDS];
struct usb_ep *ep_in;
struct usb_ep *ep_out;
/* UAS */
struct usb_ep *ep_status;
struct usb_ep *ep_cmd;
struct uas_stream stream[USBG_NUM_CMDS];
DECLARE_HASHTABLE(stream_hash, UASP_SS_EP_COMP_LOG_STREAMS);
/* BOT */
struct bot_status bot_status;
struct usb_request *bot_req_in;
struct usb_request *bot_req_out;
};
#endif /* __TARGET_USB_GADGET_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* u_audio.h -- interface to USB gadget "ALSA sound card" utilities
*
* Copyright (C) 2016
* Author: Ruslan Bilovol <ruslan.bilovol@gmail.com>
*/
#ifndef __U_AUDIO_H
#define __U_AUDIO_H
#include <linux/usb/composite.h>
#include "uac_common.h"
/*
* Same maximum frequency deviation on the slower side as in
* sound/usb/endpoint.c. Value is expressed in per-mil deviation.
*/
#define FBACK_SLOW_MAX 250
/*
* Maximum frequency deviation on the faster side, default value for UAC1/2.
* Value is expressed in per-mil deviation.
* UAC2 provides the value as a parameter as it impacts the endpoint required
* bandwidth.
*/
#define FBACK_FAST_MAX 5
/* Feature Unit parameters */
struct uac_fu_params {
int id; /* Feature Unit ID */
bool mute_present; /* mute control enable */
bool volume_present; /* volume control enable */
s16 volume_min; /* min volume in 1/256 dB */
s16 volume_max; /* max volume in 1/256 dB */
s16 volume_res; /* volume resolution in 1/256 dB */
};
struct uac_params {
/* playback */
int p_chmask; /* channel mask */
int p_srates[UAC_MAX_RATES]; /* available rates in Hz (0 terminated list) */
int p_ssize; /* sample size */
struct uac_fu_params p_fu; /* Feature Unit parameters */
/* capture */
int c_chmask; /* channel mask */
int c_srates[UAC_MAX_RATES]; /* available rates in Hz (0 terminated list) */
int c_ssize; /* sample size */
struct uac_fu_params c_fu; /* Feature Unit parameters */
/* rates are dynamic, in uac_rtd_params */
int req_number; /* number of preallocated requests */
int fb_max; /* upper frequency drift feedback limit per-mil */
};
struct g_audio {
struct usb_function func;
struct usb_gadget *gadget;
struct usb_ep *in_ep;
struct usb_ep *out_ep;
/* feedback IN endpoint corresponding to out_ep */
struct usb_ep *in_ep_fback;
/* Max packet size for all in_ep possible speeds */
unsigned int in_ep_maxpsize;
/* Max packet size for all out_ep possible speeds */
unsigned int out_ep_maxpsize;
/* Notify UAC driver about control change */
int (*notify)(struct g_audio *g_audio, int unit_id, int cs);
/* The ALSA Sound Card it represents on the USB-Client side */
struct snd_uac_chip *uac;
struct uac_params params;
};
static inline struct g_audio *func_to_g_audio(struct usb_function *f)
{
return container_of(f, struct g_audio, func);
}
static inline uint num_channels(uint chanmask)
{
uint num = 0;
while (chanmask) {
num += (chanmask & 1);
chanmask >>= 1;
}
return num;
}
/*
* g_audio_setup - initialize one virtual ALSA sound card
* @g_audio: struct with filled params, in_ep_maxpsize, out_ep_maxpsize
* @pcm_name: the id string for a PCM instance of this sound card
* @card_name: name of this soundcard
*
* This sets up the single virtual ALSA sound card that may be exported by a
* gadget driver using this framework.
*
* Context: may sleep
*
* Returns zero on success, or a negative error on failure.
*/
int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
const char *card_name);
void g_audio_cleanup(struct g_audio *g_audio);
int u_audio_start_capture(struct g_audio *g_audio);
void u_audio_stop_capture(struct g_audio *g_audio);
int u_audio_start_playback(struct g_audio *g_audio);
void u_audio_stop_playback(struct g_audio *g_audio);
int u_audio_get_capture_srate(struct g_audio *audio_dev, u32 *val);
int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate);
int u_audio_get_playback_srate(struct g_audio *audio_dev, u32 *val);
int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate);
int u_audio_get_volume(struct g_audio *g_audio, int playback, s16 *val);
int u_audio_set_volume(struct g_audio *g_audio, int playback, s16 val);
int u_audio_get_mute(struct g_audio *g_audio, int playback, int *val);
int u_audio_set_mute(struct g_audio *g_audio, int playback, int val);
void u_audio_suspend(struct g_audio *g_audio);
#endif /* __U_AUDIO_H */
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_ecm.h
*
* Utility definitions for the ecm function
*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef U_ECM_H
#define U_ECM_H
#include <linux/usb/composite.h>
/**
* struct f_ecm_opts - ECM function options
* @func_inst: USB function instance.
* @net: The net_device associated with the ECM function.
* @bound: True if the net_device is shared and pre-registered during the
* legacy composite driver's bind phase (e.g., multi.c). If false,
* the ECM function will register the net_device during its own
* bind phase.
* @bind_count: Tracks the number of configurations the ECM function is
* bound to, preventing double-registration of the @net device.
* @lock: Protects the data from concurrent access by configfs read/write
* and create symlink/remove symlink operations.
* @refcnt: Reference counter for the function instance.
*/
struct f_ecm_opts {
struct usb_function_instance func_inst;
struct net_device *net;
bool bound;
int bind_count;
struct mutex lock;
int refcnt;
};
#endif /* U_ECM_H */
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_eem.h
*
* Utility definitions for the eem function
*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef U_EEM_H
#define U_EEM_H
#include <linux/usb/composite.h>
/**
* struct f_eem_opts - EEM function options
* @func_inst: USB function instance.
* @net: The net_device associated with the EEM function.
* @bound: True if the net_device is shared and pre-registered during the
* legacy composite driver's bind phase (e.g., multi.c). If false,
* the EEM function will register the net_device during its own
* bind phase.
* @bind_count: Tracks the number of configurations the EEM function is
* bound to, preventing double-registration of the @net device.
* @lock: Protects the data from concurrent access by configfs read/write
* and create symlink/remove symlink operations.
* @refcnt: Reference counter for the function instance.
*/
struct f_eem_opts {
struct usb_function_instance func_inst;
struct net_device *net;
bool bound;
int bind_count;
struct mutex lock;
int refcnt;
};
#endif /* U_EEM_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,321 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* 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
*/
#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>
#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 *);
bool is_suspend;
};
#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_attach_gadget - Reparent net_device to the gadget device.
* @net: The network device to reparent.
* @g: The target USB gadget device to parent to.
*
* This function moves the network device to be a child of the USB gadget
* device in the device hierarchy. This is typically done when the function
* is bound to a configuration.
*
* Returns 0 on success, or a negative error code on failure.
*/
int gether_attach_gadget(struct net_device *net, struct usb_gadget *g);
/**
* gether_detach_gadget - Detach net_device from its gadget parent.
* @net: The network device to detach.
*
* This function moves the network device to be a child of the virtual
* devices parent, effectively detaching it from the USB gadget device
* hierarchy. This is typically done when the function is unbound
* from a configuration but the instance is not yet freed.
*/
void gether_detach_gadget(struct net_device *net);
DEFINE_FREE(detach_gadget, struct net_device *, if (_T) gether_detach_gadget(_T))
/**
* 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);
/**
* gether_set_ifname - set an ethernet-over-usb link interface name
* @net: device representing this link
* @name: new interface name
* @len: length of @name
*
* This sets the interface name of this ethernet-over-usb link.
* A single terminating newline, if any, is ignored.
* Returns zero on success, else negative errno.
*/
int gether_set_ifname(struct net_device *net, const char *name, int len);
void gether_cleanup(struct eth_dev *dev);
void gether_suspend(struct gether *link);
void gether_resume(struct gether *link);
/* 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_is_altset_supported(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;
}
/* peak (theoretical) bulk transfer rate in bits-per-second */
static inline unsigned int gether_bitrate(struct usb_gadget *g)
{
if (g->speed >= USB_SPEED_SUPER_PLUS)
return 4250000000U;
if (g->speed == USB_SPEED_SUPER)
return 3750000000U;
else if (g->speed == USB_SPEED_HIGH)
return 13 * 512 * 8 * 1000 * 8;
else
return 19 * 64 * 1 * 1000 * 8;
}
#endif /* __U_ETHER_H */
@@ -0,0 +1,200 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* 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 <andrzejtp2010@gmail.com>
*/
#ifndef __U_ETHER_CONFIGFS_H
#define __U_ETHER_CONFIGFS_H
#define USB_ETHERNET_CONFIGFS_ITEM(_f_) \
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 const struct configfs_item_operations _f_##_item_ops = { \
.release = _f_##_attr_release, \
}
#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(_f_) \
static ssize_t _f_##_opts_dev_addr_show(struct config_item *item, \
char *page) \
{ \
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
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 config_item *item, \
const char *page, size_t len)\
{ \
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
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; \
} \
\
CONFIGFS_ATTR(_f_##_opts_, dev_addr)
#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(_f_) \
static ssize_t _f_##_opts_host_addr_show(struct config_item *item, \
char *page) \
{ \
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
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 config_item *item, \
const char *page, size_t len)\
{ \
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
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; \
} \
\
CONFIGFS_ATTR(_f_##_opts_, host_addr)
#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(_f_) \
static ssize_t _f_##_opts_qmult_show(struct config_item *item, \
char *page) \
{ \
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
unsigned qmult; \
\
mutex_lock(&opts->lock); \
qmult = gether_get_qmult(opts->net); \
mutex_unlock(&opts->lock); \
return sprintf(page, "%d\n", qmult); \
} \
\
static ssize_t _f_##_opts_qmult_store(struct config_item *item, \
const char *page, size_t len)\
{ \
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
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; \
} \
\
CONFIGFS_ATTR(_f_##_opts_, qmult)
#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(_f_) \
static ssize_t _f_##_opts_ifname_show(struct config_item *item, \
char *page) \
{ \
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
int ret; \
\
mutex_lock(&opts->lock); \
ret = gether_get_ifname(opts->net, page, PAGE_SIZE); \
mutex_unlock(&opts->lock); \
\
return ret; \
} \
\
static ssize_t _f_##_opts_ifname_store(struct config_item *item, \
const char *page, size_t len)\
{ \
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
int ret = -EBUSY; \
\
mutex_lock(&opts->lock); \
if (!opts->refcnt) \
ret = gether_set_ifname(opts->net, page, len); \
mutex_unlock(&opts->lock); \
return ret ?: len; \
} \
\
CONFIGFS_ATTR(_f_##_opts_, ifname)
#define USB_ETHER_CONFIGFS_ITEM_ATTR_U8_RW(_f_, _n_) \
static ssize_t _f_##_opts_##_n_##_show(struct config_item *item,\
char *page) \
{ \
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
int ret; \
\
mutex_lock(&opts->lock); \
ret = sprintf(page, "%02x\n", opts->_n_); \
mutex_unlock(&opts->lock); \
\
return ret; \
} \
\
static ssize_t _f_##_opts_##_n_##_store(struct config_item *item,\
const char *page, \
size_t len) \
{ \
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
int ret = -EINVAL; \
u8 val; \
\
mutex_lock(&opts->lock); \
if (sscanf(page, "%02hhx", &val) > 0) { \
opts->_n_ = val; \
ret = len; \
} \
mutex_unlock(&opts->lock); \
\
return ret; \
} \
\
CONFIGFS_ATTR(_f_##_opts_, _n_)
#endif /* __U_ETHER_CONFIGFS_H */
@@ -0,0 +1,301 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_fs.h
*
* Utility definitions for the FunctionFS
*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef U_FFS_H
#define U_FFS_H
#include <linux/usb/composite.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/refcount.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 */
struct f_fs_opts;
struct ffs_dev {
struct ffs_data *ffs_data;
struct f_fs_opts *opts;
struct list_head entry;
char name[41];
bool mounted;
bool desc_ready;
bool single;
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,
/*
* Function is visible to host, but it's not functional. All
* setup requests are stalled and transfers on another endpoints
* are refused. All epfiles, except ep0, are deleted so there
* is no way to perform any operations on them.
*
* This state is set after closing all functionfs files, when
* mount parameter "no_disconnect=1" has been set. Function will
* remain in deactivated state until filesystem is umounted or
* ep0 is opened again. In the second case functionfs state will
* be reset, and it will be ready for descriptors and strings
* writing.
*
* This is useful only when functionfs is composed to gadget
* with another function which can perform some critical
* operations, and it's strongly desired to have this operations
* completed, even after functionfs files closure.
*/
FFS_DEACTIVATED,
/*
* 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 */
refcount_t ref;
/* how many files are opened (EP0 and others) */
int 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
/* For waking up blocked threads when function is enabled. */
wait_queue_head_t wait;
/* 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 user_flags;
#define FFS_MAX_EPS_COUNT 31
u8 eps_addrmap[FFS_MAX_EPS_COUNT];
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;
struct eventfd_ctx *ffs_eventfd;
struct workqueue_struct *io_completion_wq;
bool no_disconnect;
struct work_struct reset_work;
/*
* 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 */
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_gether.h
*
* Utility definitions for the subset function
*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef U_GETHER_H
#define U_GETHER_H
#include <linux/usb/composite.h>
/**
* struct f_gether_opts - subset function options
* @func_inst: USB function instance.
* @net: The net_device associated with the subset function.
* @bound: True if the net_device is shared and pre-registered during the
* legacy composite driver's bind phase (e.g., multi.c). If false,
* the subset function will register the net_device during its own
* bind phase.
* @bind_count: Tracks the number of configurations the subset function is
* bound to, preventing double-registration of the @net device.
* @lock: Protects the data from concurrent access by configfs read/write
* and create symlink/remove symlink operations.
* @refcnt: Reference counter for the function instance.
*/
struct f_gether_opts {
struct usb_function_instance func_inst;
struct net_device *net;
bool bound;
int bind_count;
struct mutex lock;
int refcnt;
};
#endif /* U_GETHER_H */
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_hid.h
*
* Utility definitions for the hid function
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef U_HID_H
#define U_HID_H
#include <linux/usb/composite.h>
struct f_hid_opts {
struct usb_function_instance func_inst;
int minor;
unsigned char subclass;
unsigned char protocol;
unsigned char no_out_endpoint;
unsigned short report_length;
unsigned short report_desc_length;
unsigned char *report_desc;
bool report_desc_alloc;
unsigned char interval;
bool interval_user_set;
/*
* Protect the data form concurrent access by read/write
* and create symlink/remove symlink.
*/
struct mutex lock;
int refcnt;
};
int ghid_setup(struct usb_gadget *g, int count);
void ghid_cleanup(void);
#endif /* U_HID_H */
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_midi.h
*
* Utility definitions for the midi function
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef U_MIDI_H
#define U_MIDI_H
#include <linux/usb/composite.h>
struct f_midi_opts {
struct usb_function_instance func_inst;
int index;
char *id;
char *interface_string;
unsigned int in_ports;
unsigned int out_ports;
unsigned int buflen;
unsigned int qlen;
/*
* Protect the data form concurrent access by read/write
* and create symlink/remove symlink.
*/
struct mutex lock;
int refcnt;
};
#endif /* U_MIDI_H */
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Utility definitions for MIDI 2.0 function
*/
#ifndef U_MIDI2_H
#define U_MIDI2_H
#include <linux/usb/composite.h>
#include <sound/asound.h>
struct f_midi2_opts;
struct f_midi2_ep_opts;
struct f_midi2_block_opts;
/* UMP Function Block info */
struct f_midi2_block_info {
unsigned int direction; /* FB direction: 1-3 */
unsigned int first_group; /* first UMP group: 0-15 */
unsigned int num_groups; /* number of UMP groups: 1-16 */
unsigned int midi1_first_group; /* first UMP group for MIDI 1.0 */
unsigned int midi1_num_groups; /* number of UMP groups for MIDI 1.0 */
unsigned int ui_hint; /* UI-hint: 0-3 */
unsigned int midi_ci_version; /* MIDI-CI version: 0-255 */
unsigned int sysex8_streams; /* number of sysex8 streams: 0-255 */
unsigned int is_midi1; /* MIDI 1.0 port: 0-2 */
bool active; /* FB active flag: bool */
const char *name; /* FB name */
};
/* UMP Endpoint info */
struct f_midi2_ep_info {
unsigned int protocol_caps; /* protocol capabilities: 1-3 */
unsigned int protocol; /* default protocol: 1-2 */
unsigned int manufacturer; /* manufacturer id: 0-0xffffff */
unsigned int family; /* device family id: 0-0xffff */
unsigned int model; /* device model id: 0x-0xffff */
unsigned int sw_revision; /* software revision: 32bit */
const char *ep_name; /* Endpoint name */
const char *product_id; /* Product ID */
};
struct f_midi2_card_info {
bool process_ump; /* process UMP stream: bool */
bool static_block; /* static FBs: bool */
unsigned int req_buf_size; /* request buffer size */
unsigned int num_reqs; /* number of requests */
const char *iface_name; /* interface name */
};
struct f_midi2_block_opts {
struct config_group group;
unsigned int id;
struct f_midi2_block_info info;
struct f_midi2_ep_opts *ep;
};
struct f_midi2_ep_opts {
struct config_group group;
unsigned int index;
struct f_midi2_ep_info info;
struct f_midi2_block_opts *blks[SNDRV_UMP_MAX_BLOCKS];
struct f_midi2_opts *opts;
};
#define MAX_UMP_EPS 4
#define MAX_CABLES 16
struct f_midi2_opts {
struct usb_function_instance func_inst;
struct mutex lock;
int refcnt;
struct f_midi2_card_info info;
unsigned int num_eps;
struct f_midi2_ep_opts *eps[MAX_UMP_EPS];
};
#endif /* U_MIDI2_H */
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_ncm.h
*
* Utility definitions for the ncm function
*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef U_NCM_H
#define U_NCM_H
#include <linux/usb/composite.h>
/**
* struct f_ncm_opts - NCM function options
* @func_inst: USB function instance.
* @net: The net_device associated with the NCM function.
* @bind_count: Tracks the number of configurations the NCM function is
* bound to, preventing double-registration of the @net device.
* @ncm_interf_group: ConfigFS group for NCM interface.
* @ncm_os_desc: USB OS descriptor for NCM.
* @ncm_ext_compat_id: Extended compatibility ID.
* @lock: Protects the data from concurrent access by configfs read/write
* and create symlink/remove symlink operations.
* @refcnt: Reference counter for the function instance.
* @max_segment_size: Maximum segment size.
*/
struct f_ncm_opts {
struct usb_function_instance func_inst;
struct net_device *net;
int bind_count;
struct config_group *ncm_interf_group;
struct usb_os_desc ncm_os_desc;
char ncm_ext_compat_id[16];
struct mutex lock;
int refcnt;
u16 max_segment_size;
};
#endif /* U_NCM_H */
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* u_phonet.h - interface to Phonet
*
* Copyright (C) 2007-2008 by Nokia Corporation
*/
#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);
void gphonet_cleanup(struct net_device *dev);
#endif /* __U_PHONET_H */
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_printer.h
*
* Utility definitions for the printer function
*
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef U_PRINTER_H
#define U_PRINTER_H
#include <linux/usb/composite.h>
struct f_printer_opts {
struct usb_function_instance func_inst;
int minor;
char *pnp_string;
bool pnp_string_allocated;
unsigned q_len;
/*
* Protect the data from concurrent access by read/write
* and create symlink/remove symlink
*/
struct mutex lock;
int refcnt;
};
#endif /* U_PRINTER_H */
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_rndis.h
*
* Utility definitions for the subset function
*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef U_RNDIS_H
#define U_RNDIS_H
#include <linux/usb/composite.h>
/**
* struct f_rndis_opts - RNDIS function options
* @func_inst: USB function instance.
* @vendor_id: Vendor ID.
* @manufacturer: Manufacturer string.
* @net: The net_device associated with the RNDIS function.
* @bind_count: Tracks the number of configurations the RNDIS function is
* bound to, preventing double-registration of the @net device.
* @borrowed_net: True if the net_device is shared and pre-registered during
* the legacy composite driver's bind phase (e.g., multi.c).
* If false, the RNDIS function will register the net_device
* during its own bind phase.
* @rndis_interf_group: ConfigFS group for RNDIS interface.
* @rndis_os_desc: USB OS descriptor for RNDIS.
* @rndis_ext_compat_id: Extended compatibility ID.
* @class: USB class.
* @subclass: USB subclass.
* @protocol: USB protocol.
* @lock: Protects the data from concurrent access by configfs read/write
* and create symlink/remove symlink operations.
* @refcnt: Reference counter for the function instance.
*/
struct f_rndis_opts {
struct usb_function_instance func_inst;
u32 vendor_id;
const char *manufacturer;
struct net_device *net;
int bind_count;
bool borrowed_net;
struct config_group *rndis_interf_group;
struct usb_os_desc rndis_os_desc;
char rndis_ext_compat_id[16];
u8 class;
u8 subclass;
u8 protocol;
struct mutex lock;
int refcnt;
};
void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net);
#endif /* U_RNDIS_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,78 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* u_serial.h - interface to USB gadget "serial port"/TTY utilities
*
* Copyright (C) 2008 David Brownell
* Copyright (C) 2008 by Nokia Corporation
*/
#ifndef __U_SERIAL_H
#define __U_SERIAL_H
#include <linux/usb/composite.h>
#include <linux/usb/cdc.h>
#define MAX_U_SERIAL_PORTS 8
struct f_serial_opts {
struct usb_function_instance func_inst;
u8 port_num;
u8 protocol;
struct mutex lock; /* protect instances */
int instances;
};
/*
* 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_no_console(unsigned char *port_line);
int gserial_alloc_line(unsigned char *port_line);
void gserial_free_line(unsigned char port_line);
#ifdef CONFIG_U_SERIAL_CONSOLE
ssize_t gserial_set_console(unsigned char port_num, const char *page, size_t count);
ssize_t gserial_get_console(unsigned char port_num, char *page);
#endif /* CONFIG_U_SERIAL_CONSOLE */
/* connect/disconnect is handled by individual functions */
int gserial_connect(struct gserial *, u8 port_num);
void gserial_disconnect(struct gserial *);
void gserial_suspend(struct gserial *p);
void gserial_resume(struct gserial *p);
#endif /* __U_SERIAL_H */
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_tcm.h
*
* Utility definitions for the tcm function
*
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx>
*/
#ifndef U_TCM_H
#define U_TCM_H
#include <linux/usb/composite.h>
/**
* @dependent: optional dependent module. Meant for legacy gadget.
* If non-null its refcount will be increased when a tpg is created and
* decreased when tpg is dropped.
* @dep_lock: lock for dependent module operations.
* @ready: true if the dependent module information is set.
* @can_attach: true a function can be bound to gadget
* @has_dep: true if there is a dependent module
*
*/
struct f_tcm_opts {
struct usb_function_instance func_inst;
struct module *dependent;
struct mutex dep_lock;
bool ready;
bool can_attach;
bool has_dep;
/*
* Callbacks to be removed when legacy tcm gadget disappears.
*
* If you use the new function registration interface
* programmatically, you MUST set these callbacks to
* something sensible (e.g. probe/remove the composite).
*/
int (*tcm_register_callback)(struct usb_function_instance *);
void (*tcm_unregister_callback)(struct usb_function_instance *);
};
#endif /* U_TCM_H */
@@ -0,0 +1,71 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_uac1.h - Utility definitions for UAC1 function
*
* Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
*/
#ifndef __U_UAC1_H
#define __U_UAC1_H
#include <linux/usb/composite.h>
#include "uac_common.h"
#define UAC1_OUT_EP_MAX_PACKET_SIZE 200
#define UAC1_DEF_CCHMASK 0x3
#define UAC1_DEF_CSRATE 48000
#define UAC1_DEF_CSSIZE 2
#define UAC1_DEF_PCHMASK 0x3
#define UAC1_DEF_PSRATE 48000
#define UAC1_DEF_PSSIZE 2
#define UAC1_DEF_REQ_NUM 2
#define UAC1_DEF_INT_REQ_NUM 10
#define UAC1_DEF_MUTE_PRESENT 1
#define UAC1_DEF_VOLUME_PRESENT 1
#define UAC1_DEF_MIN_DB (-100*256) /* -100 dB */
#define UAC1_DEF_MAX_DB 0 /* 0 dB */
#define UAC1_DEF_RES_DB (1*256) /* 1 dB */
struct f_uac1_opts {
struct usb_function_instance func_inst;
int c_chmask;
int c_srates[UAC_MAX_RATES];
int c_ssize;
int p_chmask;
int p_srates[UAC_MAX_RATES];
int p_ssize;
bool p_mute_present;
bool p_volume_present;
s16 p_volume_min;
s16 p_volume_max;
s16 p_volume_res;
bool c_mute_present;
bool c_volume_present;
s16 c_volume_min;
s16 c_volume_max;
s16 c_volume_res;
int req_number;
unsigned bound:1;
char function_name[USB_MAX_STRING_LEN];
char p_it_name[USB_MAX_STRING_LEN];
char p_it_ch_name[USB_MAX_STRING_LEN];
char p_ot_name[USB_MAX_STRING_LEN];
char p_fu_vol_name[USB_MAX_STRING_LEN];
char c_it_name[USB_MAX_STRING_LEN];
char c_it_ch_name[USB_MAX_STRING_LEN];
char c_ot_name[USB_MAX_STRING_LEN];
char c_fu_vol_name[USB_MAX_STRING_LEN];
struct mutex lock;
int refcnt;
};
#endif /* __U_UAC1_H */
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* u_uac1.c -- ALSA audio utilities for Gadget stack
*
* Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
* Copyright (C) 2008 Analog Devices, Inc
*/
#include <linux/kernel.h>
#include <linux/module.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_legacy.h"
/*
* This component encapsulates the ALSA devices for USB audio gadget
*/
/*-------------------------------------------------------------------------*/
/*
* 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_obj(*params);
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
*/
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;
ssize_t result;
snd_pcm_sframes_t frames;
try_again:
if (runtime->state == SNDRV_PCM_STATE_XRUN ||
runtime->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);
result = snd_pcm_kernel_write(snd->substream, buf, frames);
if (result != frames) {
ERROR(card, "Playback error: %d\n", (int)result);
goto try_again;
}
return 0;
}
int u_audio_get_playback_channels(struct gaudio *card)
{
return card->playback.channels;
}
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;
struct f_uac1_legacy_opts *opts;
char *fn_play, *fn_cap, *fn_cntl;
opts = container_of(card->func.fi, struct f_uac1_legacy_opts,
func_inst);
fn_play = opts->fn_play;
fn_cap = opts->fn_cap;
fn_cntl = opts->fn_cntl;
/* 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;
}
/*
* 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 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");
return ret;
}
/*
* gaudio_cleanup - remove ALSA device interface
*
* This is called to free all resources allocated by @gaudio_setup().
*/
void gaudio_cleanup(struct gaudio *the_card)
{
if (the_card)
gaudio_close_snd_dev(the_card);
}
@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities
*
* Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
* Copyright (C) 2008 Analog Devices, Inc
*/
#ifndef __U_UAC1_LEGACY_H
#define __U_UAC1_LEGACY_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>
#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p"
#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c"
#define FILE_CONTROL "/dev/snd/controlC0"
#define UAC1_OUT_EP_MAX_PACKET_SIZE 200
#define UAC1_REQ_COUNT 256
#define UAC1_AUDIO_BUF_SIZE 48000
/*
* 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 */
};
struct f_uac1_legacy_opts {
struct usb_function_instance func_inst;
int req_buf_size;
int req_count;
int audio_buf_size;
char *fn_play;
char *fn_cap;
char *fn_cntl;
unsigned bound:1;
unsigned fn_play_alloc:1;
unsigned fn_cap_alloc:1;
unsigned fn_cntl_alloc:1;
struct mutex lock;
int refcnt;
};
int gaudio_setup(struct gaudio *card);
void gaudio_cleanup(struct gaudio *the_card);
size_t u_audio_playback(struct gaudio *card, void *buf, size_t count);
int u_audio_get_playback_channels(struct gaudio *card);
int u_audio_get_playback_rate(struct gaudio *card);
#endif /* __U_UAC1_LEGACY_H */
@@ -0,0 +1,93 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_uac2.h
*
* Utility definitions for UAC2 function
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef U_UAC2_H
#define U_UAC2_H
#include <linux/usb/composite.h>
#include "uac_common.h"
#define UAC2_DEF_PCHMASK 0x3
#define UAC2_DEF_PSRATE 48000
#define UAC2_DEF_PSSIZE 2
#define UAC2_DEF_PHSBINT 0
#define UAC2_DEF_CCHMASK 0x3
#define UAC2_DEF_CSRATE 64000
#define UAC2_DEF_CSSIZE 2
#define UAC2_DEF_CHSBINT 0
#define UAC2_DEF_CSYNC USB_ENDPOINT_SYNC_ASYNC
#define UAC2_DEF_MUTE_PRESENT 1
#define UAC2_DEF_VOLUME_PRESENT 1
#define UAC2_DEF_MIN_DB (-100*256) /* -100 dB */
#define UAC2_DEF_MAX_DB 0 /* 0 dB */
#define UAC2_DEF_RES_DB (1*256) /* 1 dB */
#define UAC2_DEF_REQ_NUM 2
#define UAC2_DEF_INT_REQ_NUM 10
#define UAC2_DEF_P_TERM_TYPE 0x301
/* UAC_OUTPUT_TERMINAL_SPEAKER */
#define UAC2_DEF_C_TERM_TYPE 0x201
/* UAC_INPUT_TERMINAL_MICROPHONE*/
struct f_uac2_opts {
struct usb_function_instance func_inst;
int p_chmask;
int p_srates[UAC_MAX_RATES];
int p_ssize;
u8 p_hs_bint;
int c_chmask;
int c_srates[UAC_MAX_RATES];
int c_ssize;
int c_sync;
u8 c_hs_bint;
bool p_mute_present;
bool p_volume_present;
s16 p_volume_min;
s16 p_volume_max;
s16 p_volume_res;
bool c_mute_present;
bool c_volume_present;
s16 c_volume_min;
s16 c_volume_max;
s16 c_volume_res;
int req_number;
int fb_max;
bool bound;
char function_name[USB_MAX_STRING_LEN];
char if_ctrl_name[USB_MAX_STRING_LEN];
char clksrc_in_name[USB_MAX_STRING_LEN];
char clksrc_out_name[USB_MAX_STRING_LEN];
char p_it_name[USB_MAX_STRING_LEN];
char p_it_ch_name[USB_MAX_STRING_LEN];
char p_ot_name[USB_MAX_STRING_LEN];
char p_fu_vol_name[USB_MAX_STRING_LEN];
char c_it_name[USB_MAX_STRING_LEN];
char c_it_ch_name[USB_MAX_STRING_LEN];
char c_ot_name[USB_MAX_STRING_LEN];
char c_fu_vol_name[USB_MAX_STRING_LEN];
s16 p_terminal_type;
s16 c_terminal_type;
struct mutex lock;
int refcnt;
};
#endif
@@ -0,0 +1,109 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* u_uvc.h
*
* Utility definitions for the uvc function
*
* Copyright (c) 2013-2014 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef U_UVC_H
#define U_UVC_H
#include <linux/mutex.h>
#include <linux/usb/composite.h>
#include <linux/usb/video.h>
#define fi_to_f_uvc_opts(f) container_of(f, struct f_uvc_opts, func_inst)
struct f_uvc_opts {
struct usb_function_instance func_inst;
unsigned int streaming_interval;
unsigned int streaming_maxpacket;
unsigned int streaming_maxburst;
unsigned int control_interface;
unsigned int streaming_interface;
char function_name[32];
unsigned int last_unit_id;
bool enable_interrupt_ep;
/*
* Control descriptors array pointers for full-/high-speed and
* super-speed. They point by default to the uvc_fs_control_cls and
* uvc_ss_control_cls arrays respectively. Legacy gadgets must
* override them in their gadget bind callback.
*/
const struct uvc_descriptor_header * const *fs_control;
const struct uvc_descriptor_header * const *ss_control;
/*
* Streaming descriptors array pointers for full-speed, high-speed and
* super-speed. They will point to the uvc_[fhs]s_streaming_cls arrays
* for configfs-based gadgets. Legacy gadgets must initialize them in
* their gadget bind callback.
*/
const struct uvc_descriptor_header * const *fs_streaming;
const struct uvc_descriptor_header * const *hs_streaming;
const struct uvc_descriptor_header * const *ss_streaming;
/* Default control descriptors for configfs-based gadgets. */
struct uvc_camera_terminal_descriptor uvc_camera_terminal;
struct uvc_processing_unit_descriptor uvc_processing;
struct uvc_output_terminal_descriptor uvc_output_terminal;
/*
* Control descriptors pointers arrays for full-/high-speed and
* super-speed. The first element is a configurable control header
* descriptor, the other elements point to the fixed default control
* descriptors. Used by configfs only, must not be touched by legacy
* gadgets.
*/
struct uvc_descriptor_header *uvc_fs_control_cls[5];
struct uvc_descriptor_header *uvc_ss_control_cls[5];
/*
* Control descriptors for extension units. There could be any number
* of these, including none at all.
*/
struct list_head extension_units;
/*
* Streaming descriptors for full-speed, high-speed and super-speed.
* Used by configfs only, must not be touched by legacy gadgets. The
* arrays are allocated at runtime as the number of descriptors isn't
* known in advance.
*/
struct uvc_descriptor_header **uvc_fs_streaming_cls;
struct uvc_descriptor_header **uvc_hs_streaming_cls;
struct uvc_descriptor_header **uvc_ss_streaming_cls;
/*
* Indexes into the function's string descriptors allowing users to set
* custom descriptions rather than the hard-coded defaults.
*/
u8 iad_index;
u8 vs0_index;
u8 vs1_index;
/*
* Read/write access to configfs attributes is handled by configfs.
*
* This lock protects the descriptors from concurrent access by
* read/write and symlink creation/removal.
*/
struct mutex lock;
int refcnt;
/*
* Only for legacy gadget. Shall be NULL for configfs-composed gadgets,
* which is guaranteed by alloc_inst implementation of f_uvc doing kzalloc.
*/
struct uvcg_streaming_header *header;
};
#endif /* U_UVC_H */
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
*/
#ifndef UAC_COMMON_H
#define UAC_COMMON_H
#define UAC_MAX_RATES 10 /* maximum number of rates configurable by f_uac1/2 */
#endif
@@ -0,0 +1,216 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* uvc_gadget.h -- USB Video Class Gadget driver
*
* Copyright (C) 2009-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef _UVC_GADGET_H_
#define _UVC_GADGET_H_
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/usb/composite.h>
#include <linux/videodev2.h>
#include <linux/wait.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-fh.h>
#include "uvc_queue.h"
struct usb_ep;
struct usb_request;
struct uvc_descriptor_header;
struct uvc_device;
/* ------------------------------------------------------------------------
* Debugging, printing and logging
*/
#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 uvcg_dbg(f, fmt, args...) \
dev_dbg(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args)
#define uvcg_info(f, fmt, args...) \
dev_info(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args)
#define uvcg_warn(f, fmt, args...) \
dev_warn(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args)
#define uvcg_err(f, fmt, args...) \
dev_err(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args)
/* ------------------------------------------------------------------------
* Driver specific constants
*/
#define UVC_MAX_REQUEST_SIZE 64
#define UVC_MAX_EVENTS 4
#define UVCG_REQUEST_HEADER_LEN 12
#define UVCG_REQ_MAX_INT_COUNT 16
#define UVCG_REQ_MAX_ZERO_COUNT (2 * UVCG_REQ_MAX_INT_COUNT)
#define UVCG_STREAMING_MIN_BUFFERS 2
/* ------------------------------------------------------------------------
* Structures
*/
struct uvc_request {
struct usb_request *req;
u8 *req_buffer;
struct uvc_video *video;
struct sg_table sgt;
u8 header[UVCG_REQUEST_HEADER_LEN];
struct uvc_buffer *last_buf;
struct list_head list;
};
struct uvc_video {
struct uvc_device *uvc;
struct usb_ep *ep;
struct work_struct pump;
struct workqueue_struct *async_wq;
struct kthread_worker *kworker;
struct kthread_work hw_submit;
atomic_t queued;
/* Frame parameters */
u8 bpp;
u32 fcc;
unsigned int width;
unsigned int height;
unsigned int imagesize;
unsigned int interval; /* in 100ns units */
struct mutex mutex; /* protects frame parameters */
unsigned int uvc_num_requests;
unsigned int reqs_per_frame;
/* Requests */
bool is_enabled; /* tracks whether video stream is enabled */
unsigned int req_size;
unsigned int max_req_size;
struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
/* USB requests that the video pump thread can encode into */
struct list_head req_free;
/*
* USB requests video pump thread has already encoded into. These are
* ready to be queued to the endpoint.
*/
struct list_head req_ready;
spinlock_t req_lock;
unsigned int req_int_count;
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;
struct completion *vdev_release_done;
struct mutex lock; /* protects func_unbound and func_connected */
bool func_unbound;
bool func_connected;
wait_queue_head_t func_connected_queue;
struct uvcg_streaming_header *header;
/* 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;
struct list_head *extension_units;
} desc;
unsigned int control_intf;
struct usb_ep *interrupt_ep;
struct usb_request *control_req;
void *control_buf;
bool enable_interrupt_ep;
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;
bool is_uvc_app_handle;
};
#define to_uvc_file_handle(handle) \
container_of(handle, struct uvc_file_handle, vfh)
static inline struct uvc_file_handle *file_to_uvc_file_handle(struct file *filp)
{
return container_of(file_to_v4l2_fh(filp), struct uvc_file_handle, vfh);
}
/* ------------------------------------------------------------------------
* Functions
*/
extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
extern void uvc_function_connect(struct uvc_device *uvc);
extern void uvc_function_disconnect(struct uvc_device *uvc);
#endif /* _UVC_GADGET_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,195 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* uvc_configfs.h
*
* Configfs support for the uvc function.
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef UVC_CONFIGFS_H
#define UVC_CONFIGFS_H
#include <linux/configfs.h>
#include "u_uvc.h"
static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item)
{
return container_of(to_config_group(item), struct f_uvc_opts,
func_inst.group);
}
#define UVCG_STREAMING_CONTROL_SIZE 1
DECLARE_UVC_HEADER_DESCRIPTOR(1);
struct uvcg_control_header {
struct config_item item;
struct UVC_HEADER_DESCRIPTOR(1) desc;
unsigned linked;
};
static inline struct uvcg_control_header *to_uvcg_control_header(struct config_item *item)
{
return container_of(item, struct uvcg_control_header, item);
}
struct uvcg_color_matching {
struct config_group group;
struct uvc_color_matching_descriptor desc;
unsigned int refcnt;
};
#define to_uvcg_color_matching(group_ptr) \
container_of(group_ptr, struct uvcg_color_matching, group)
enum uvcg_format_type {
UVCG_UNCOMPRESSED = 0,
UVCG_MJPEG,
UVCG_FRAMEBASED,
};
struct uvcg_format {
struct config_group group;
enum uvcg_format_type type;
unsigned linked;
struct list_head frames;
unsigned num_frames;
__u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE];
struct uvcg_color_matching *color_matching;
};
struct uvcg_format_ptr {
struct uvcg_format *fmt;
struct list_head entry;
};
static inline struct uvcg_format *to_uvcg_format(struct config_item *item)
{
return container_of(to_config_group(item), struct uvcg_format, group);
}
struct uvcg_streaming_header {
struct config_item item;
unsigned linked;
struct list_head formats;
unsigned num_fmt;
/* Must be last --ends in a flexible-array member. */
struct uvc_input_header_descriptor desc;
};
static inline struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item)
{
return container_of(item, struct uvcg_streaming_header, item);
}
struct uvcg_frame_ptr {
struct uvcg_frame *frm;
struct list_head entry;
};
struct uvcg_frame {
struct config_item item;
enum uvcg_format_type fmt_type;
struct {
u8 b_length;
u8 b_descriptor_type;
u8 b_descriptor_subtype;
u8 b_frame_index;
u8 bm_capabilities;
u16 w_width;
u16 w_height;
u32 dw_min_bit_rate;
u32 dw_max_bit_rate;
u32 dw_max_video_frame_buffer_size;
u32 dw_default_frame_interval;
u8 b_frame_interval_type;
u32 dw_bytes_perline;
} __attribute__((packed)) frame;
u32 *dw_frame_interval;
};
static inline struct uvcg_frame *to_uvcg_frame(struct config_item *item)
{
return container_of(item, struct uvcg_frame, item);
}
/* -----------------------------------------------------------------------------
* streaming/uncompressed/<NAME>
*/
struct uvcg_uncompressed {
struct uvcg_format fmt;
struct uvc_format_uncompressed desc;
};
static inline struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item)
{
return container_of(to_uvcg_format(item), struct uvcg_uncompressed, fmt);
}
/* -----------------------------------------------------------------------------
* streaming/mjpeg/<NAME>
*/
struct uvcg_mjpeg {
struct uvcg_format fmt;
struct uvc_format_mjpeg desc;
};
static inline struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item)
{
return container_of(to_uvcg_format(item), struct uvcg_mjpeg, fmt);
}
/* -----------------------------------------------------------------------------
* streaming/framebased/<NAME>
*/
struct uvcg_framebased {
struct uvcg_format fmt;
struct uvc_format_framebased desc;
};
static inline struct uvcg_framebased *to_uvcg_framebased(struct config_item *item)
{
return container_of(to_uvcg_format(item), struct uvcg_framebased, fmt);
}
/* -----------------------------------------------------------------------------
* control/extensions/<NAME>
*/
struct uvcg_extension_unit_descriptor {
u8 bLength;
u8 bDescriptorType;
u8 bDescriptorSubType;
u8 bUnitID;
u8 guidExtensionCode[16];
u8 bNumControls;
u8 bNrInPins;
u8 *baSourceID;
u8 bControlSize;
u8 *bmControls;
u8 iExtension;
} __packed;
struct uvcg_extension {
struct config_item item;
struct list_head list;
u8 string_descriptor_index;
struct uvcg_extension_unit_descriptor desc;
};
static inline struct uvcg_extension *to_uvcg_extension(struct config_item *item)
{
return container_of(item, struct uvcg_extension, item);
}
int uvcg_attach_configfs(struct f_uvc_opts *opts);
#endif /* UVC_CONFIGFS_H */
@@ -0,0 +1,368 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* uvc_queue.c -- USB Video Class driver - Buffers management
*
* Copyright (C) 2005-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#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-dma-sg.h>
#include <media/videobuf2-vmalloc.h>
#include "uvc.h"
#include "uvc_video.h"
/* ------------------------------------------------------------------------
* Video buffers queue management.
*
* Video queues is initialized by uvcg_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,
unsigned int *nbuffers, unsigned int *nplanes,
unsigned int sizes[], struct device *alloc_devs[])
{
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;
if (*nbuffers < UVCG_STREAMING_MIN_BUFFERS)
*nbuffers = UVCG_STREAMING_MIN_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_video *video = container_of(queue, struct uvc_video, queue);
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct uvc_buffer *buf = container_of(vbuf, struct uvc_buffer, buf);
if (vb->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;
if (queue->use_sg) {
buf->sgt = vb2_dma_sg_plane_desc(vb, 0);
buf->sg = buf->sgt->sgl;
} else {
buf->mem = vb2_plane_vaddr(vb, 0);
}
buf->length = vb2_plane_size(vb, 0);
if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
buf->bytesused = 0;
} else {
buf->bytesused = vb2_get_plane_payload(vb, 0);
if (video->reqs_per_frame != 0) {
buf->req_payload_size =
DIV_ROUND_UP(buf->bytesused +
(video->reqs_per_frame * UVCG_REQUEST_HEADER_LEN),
video->reqs_per_frame);
if (buf->req_payload_size > video->req_size)
buf->req_payload_size = video->req_size;
} else {
buf->req_payload_size = video->max_req_size;
}
}
return 0;
}
static void uvc_buffer_queue(struct vb2_buffer *vb)
{
struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct uvc_buffer *buf = container_of(vbuf, 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(vb, VB2_BUF_STATE_ERROR);
}
spin_unlock_irqrestore(&queue->irqlock, flags);
}
static const struct vb2_ops uvc_queue_qops = {
.queue_setup = uvc_queue_setup,
.buf_prepare = uvc_buffer_prepare,
.buf_queue = uvc_buffer_queue,
};
int uvcg_queue_init(struct uvc_video_queue *queue, struct device *dev, enum v4l2_buf_type type,
struct mutex *lock)
{
struct uvc_video *video = container_of(queue, struct uvc_video, queue);
struct usb_composite_dev *cdev = video->uvc->func.config->cdev;
int ret;
queue->queue.type = type;
queue->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
queue->queue.drv_priv = queue;
queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
queue->queue.ops = &uvc_queue_qops;
queue->queue.lock = lock;
if (cdev->gadget->sg_supported) {
queue->queue.mem_ops = &vb2_dma_sg_memops;
queue->use_sg = 1;
} else {
queue->queue.mem_ops = &vb2_vmalloc_memops;
}
queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY
| V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
queue->queue.dev = dev;
ret = vb2_queue_init(&queue->queue);
if (ret)
return ret;
spin_lock_init(&queue->irqlock);
INIT_LIST_HEAD(&queue->irqqueue);
queue->flags = 0;
return 0;
}
/*
* Free the video buffers.
*/
void uvcg_free_buffers(struct uvc_video_queue *queue)
{
vb2_queue_release(&queue->queue);
}
/*
* Allocate the video buffers.
*/
int uvcg_alloc_buffers(struct uvc_video_queue *queue,
struct v4l2_requestbuffers *rb)
{
int ret;
retry:
ret = vb2_reqbufs(&queue->queue, rb);
if (ret < 0 && queue->use_sg) {
uvc_trace(UVC_TRACE_IOCTL,
"failed to alloc buffer with sg enabled, try non-sg mode\n");
queue->use_sg = 0;
queue->queue.mem_ops = &vb2_vmalloc_memops;
goto retry;
}
return ret ? ret : rb->count;
}
int uvcg_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
{
return vb2_querybuf(&queue->queue, buf);
}
int uvcg_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
{
return vb2_qbuf(&queue->queue, NULL, buf);
}
/*
* Dequeue a video buffer. If nonblocking is false, block until a buffer is
* available.
*/
int uvcg_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf,
int nonblocking)
{
return vb2_dqbuf(&queue->queue, buf, nonblocking);
}
/*
* Poll the video queue.
*
* This function implements video queue polling and is intended to be used by
* the device poll handler.
*/
__poll_t uvcg_queue_poll(struct uvc_video_queue *queue, struct file *file,
poll_table *wait)
{
return vb2_poll(&queue->queue, file, wait);
}
int uvcg_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma)
{
return vb2_mmap(&queue->queue, vma);
}
#ifndef CONFIG_MMU
/*
* Get unmapped area.
*
* NO-MMU arch need this function to make mmap() work correctly.
*/
unsigned long uvcg_queue_get_unmapped_area(struct uvc_video_queue *queue,
unsigned long pgoff)
{
return vb2_get_unmapped_area(&queue->queue, 0, 0, pgoff, 0);
}
#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.
*/
void uvcg_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, VB2_BUF_STATE_ERROR);
}
queue->buf_used = 0;
/*
* 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
* uvcg_queue_cancel() instead.
*/
int uvcg_queue_enable(struct uvc_video_queue *queue, int enable)
{
unsigned long flags;
int ret = 0;
if (enable) {
ret = vb2_streamon(&queue->queue, queue->queue.type);
if (ret < 0)
return ret;
queue->sequence = 0;
queue->buf_used = 0;
queue->flags &= ~UVC_QUEUE_DROP_INCOMPLETE;
} else {
ret = vb2_streamoff(&queue->queue, queue->queue.type);
if (ret < 0)
return ret;
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);
}
return ret;
}
/* called with &queue_irqlock held.. */
void uvcg_complete_buffer(struct uvc_video_queue *queue,
struct uvc_buffer *buf)
{
if (queue->flags & UVC_QUEUE_DROP_INCOMPLETE) {
queue->flags &= ~UVC_QUEUE_DROP_INCOMPLETE;
buf->state = UVC_BUF_STATE_ERROR;
vb2_set_plane_payload(&buf->buf.vb2_buf, 0, 0);
vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_ERROR);
return;
}
buf->buf.field = V4L2_FIELD_NONE;
buf->buf.sequence = queue->sequence++;
buf->buf.vb2_buf.timestamp = ktime_get_ns();
vb2_set_plane_payload(&buf->buf.vb2_buf, 0, buf->bytesused);
vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_DONE);
}
struct uvc_buffer *uvcg_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);
return buf;
}
@@ -0,0 +1,103 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _UVC_QUEUE_H_
#define _UVC_QUEUE_H_
#include <linux/list.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <media/videobuf2-v4l2.h>
struct file;
struct mutex;
/* 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_v4l2_buffer buf;
struct list_head queue;
enum uvc_buffer_state state;
void *mem;
struct sg_table *sgt;
struct scatterlist *sg;
unsigned int offset;
unsigned int length;
unsigned int bytesused;
/* req_payload_size: only used with isoc */
unsigned int req_payload_size;
};
#define UVC_QUEUE_DISCONNECTED (1 << 0)
#define UVC_QUEUE_DROP_INCOMPLETE (1 << 1)
struct uvc_video_queue {
struct vb2_queue queue;
unsigned int flags;
__u32 sequence;
unsigned int buf_used;
bool use_sg;
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);
}
int uvcg_queue_init(struct uvc_video_queue *queue, struct device *dev, enum v4l2_buf_type type,
struct mutex *lock);
void uvcg_free_buffers(struct uvc_video_queue *queue);
int uvcg_alloc_buffers(struct uvc_video_queue *queue,
struct v4l2_requestbuffers *rb);
int uvcg_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf);
int uvcg_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf);
int uvcg_dequeue_buffer(struct uvc_video_queue *queue,
struct v4l2_buffer *buf, int nonblocking);
__poll_t uvcg_queue_poll(struct uvc_video_queue *queue,
struct file *file, poll_table *wait);
int uvcg_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma);
#ifndef CONFIG_MMU
unsigned long uvcg_queue_get_unmapped_area(struct uvc_video_queue *queue,
unsigned long pgoff);
#endif /* CONFIG_MMU */
void uvcg_queue_cancel(struct uvc_video_queue *queue, int disconnect);
int uvcg_queue_enable(struct uvc_video_queue *queue, int enable);
void uvcg_complete_buffer(struct uvc_video_queue *queue,
struct uvc_buffer *buf);
struct uvc_buffer *uvcg_queue_head(struct uvc_video_queue *queue);
#endif /* _UVC_QUEUE_H_ */
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
/*
* trace.c - USB UVC Gadget Trace Support
*
* Copyright (C) 2024 Pengutronix e.K.
*
* Author: Michael Grzeschik <m.grzeschik@pengutronix.de>
*/
#define CREATE_TRACE_POINTS
#include "uvc_trace.h"
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* trace.h - USB UVC Gadget Trace Support
*
* Copyright (C) 2024 Pengutronix e.K.
*
* Author: Michael Grzeschik <m.grzeschik@pengutronix.de>
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM uvcg
#if !defined(__UVCG_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define __UVCG_TRACE_H
#include <linux/types.h>
#include <linux/tracepoint.h>
#include <linux/usb/gadget.h>
#include <asm/byteorder.h>
DECLARE_EVENT_CLASS(uvcg_video_req,
TP_PROTO(struct usb_request *req, u32 queued),
TP_ARGS(req, queued),
TP_STRUCT__entry(
__field(struct usb_request *, req)
__field(u32, length)
__field(u32, queued)
),
TP_fast_assign(
__entry->req = req;
__entry->length = req->length;
__entry->queued = queued;
),
TP_printk("req %p length %u queued %u",
__entry->req,
__entry->length,
__entry->queued)
);
DEFINE_EVENT(uvcg_video_req, uvcg_video_complete,
TP_PROTO(struct usb_request *req, u32 queued),
TP_ARGS(req, queued)
);
DEFINE_EVENT(uvcg_video_req, uvcg_video_queue,
TP_PROTO(struct usb_request *req, u32 queued),
TP_ARGS(req, queued)
);
#endif /* __UVCG_TRACE_H */
/* this part has to be here */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE uvc_trace
#include <trace/define_trace.h>
@@ -0,0 +1,747 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* uvc_v4l2.c -- USB Video Class Gadget driver
*
* Copyright (C) 2009-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/usb/g_uvc.h>
#include <linux/usb/uvc.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 "f_uvc.h"
#include "uvc.h"
#include "uvc_queue.h"
#include "uvc_video.h"
#include "uvc_v4l2.h"
#include "uvc_configfs.h"
static const struct uvc_format_desc *to_uvc_format(struct uvcg_format *uformat)
{
char guid[16] = UVC_GUID_FORMAT_MJPEG;
const struct uvc_format_desc *format;
if (uformat->type == UVCG_UNCOMPRESSED) {
struct uvcg_uncompressed *unc;
unc = to_uvcg_uncompressed(&uformat->group.cg_item);
if (!unc)
return ERR_PTR(-EINVAL);
memcpy(guid, unc->desc.guidFormat, sizeof(guid));
} else if (uformat->type == UVCG_FRAMEBASED) {
struct uvcg_framebased *unc;
unc = to_uvcg_framebased(&uformat->group.cg_item);
if (!unc)
return ERR_PTR(-EINVAL);
memcpy(guid, unc->desc.guidFormat, sizeof(guid));
}
format = uvc_format_by_guid(guid);
if (!format)
return ERR_PTR(-EINVAL);
return format;
}
static int uvc_v4l2_get_bytesperline(struct uvcg_format *uformat,
struct uvcg_frame *uframe)
{
struct uvcg_uncompressed *u;
if (uformat->type == UVCG_UNCOMPRESSED) {
u = to_uvcg_uncompressed(&uformat->group.cg_item);
if (!u)
return 0;
return u->desc.bBitsPerPixel * uframe->frame.w_width / 8;
}
return 0;
}
static int uvc_get_frame_size(struct uvcg_format *uformat,
struct uvcg_frame *uframe)
{
unsigned int bpl = uvc_v4l2_get_bytesperline(uformat, uframe);
return bpl ? bpl * uframe->frame.w_height :
uframe->frame.dw_max_video_frame_buffer_size;
}
static struct uvcg_format *find_format_by_index(struct uvc_device *uvc, int index)
{
struct uvcg_format_ptr *format;
struct uvcg_format *uformat = NULL;
int i = 1;
list_for_each_entry(format, &uvc->header->formats, entry) {
if (index == i) {
uformat = format->fmt;
break;
}
i++;
}
return uformat;
}
static struct uvcg_frame *find_frame_by_index(struct uvc_device *uvc,
struct uvcg_format *uformat,
int index)
{
struct uvcg_format_ptr *format;
struct uvcg_frame_ptr *frame;
struct uvcg_frame *uframe = NULL;
list_for_each_entry(format, &uvc->header->formats, entry) {
if (format->fmt->type != uformat->type)
continue;
list_for_each_entry(frame, &format->fmt->frames, entry) {
if (index == frame->frm->frame.b_frame_index) {
uframe = frame->frm;
break;
}
}
}
return uframe;
}
static struct uvcg_format *find_format_by_pix(struct uvc_device *uvc,
u32 pixelformat)
{
struct uvcg_format_ptr *format;
struct uvcg_format *uformat = NULL;
list_for_each_entry(format, &uvc->header->formats, entry) {
const struct uvc_format_desc *fmtdesc = to_uvc_format(format->fmt);
if (IS_ERR(fmtdesc))
continue;
if (fmtdesc->fcc == pixelformat) {
uformat = format->fmt;
break;
}
}
return uformat;
}
static struct uvcg_frame *find_closest_frame_by_size(struct uvc_device *uvc,
struct uvcg_format *uformat,
u16 rw, u16 rh)
{
struct uvc_video *video = &uvc->video;
struct uvcg_format_ptr *format;
struct uvcg_frame_ptr *frame;
struct uvcg_frame *uframe = NULL;
unsigned int d, maxd;
/* Find the closest image size. The distance between image sizes is
* the size in pixels of the non-overlapping regions between the
* requested size and the frame-specified size.
*/
maxd = (unsigned int)-1;
list_for_each_entry(format, &uvc->header->formats, entry) {
if (format->fmt->type != uformat->type)
continue;
list_for_each_entry(frame, &format->fmt->frames, entry) {
u16 w, h;
w = frame->frm->frame.w_width;
h = frame->frm->frame.w_height;
d = min(w, rw) * min(h, rh);
d = w*h + rw*rh - 2*d;
if (d < maxd) {
maxd = d;
uframe = frame->frm;
}
if (maxd == 0)
break;
}
}
if (!uframe)
uvcg_dbg(&video->uvc->func, "Unsupported size %ux%u\n", rw, rh);
return uframe;
}
/* --------------------------------------------------------------------------
* 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);
if (req->length > sizeof(data->data))
req->length = sizeof(data->data);
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 ioctls
*/
static int
uvc_v4l2_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct usb_composite_dev *cdev = uvc->func.config->cdev;
strscpy(cap->driver, "g_uvc", sizeof(cap->driver));
strscpy(cap->card, cdev->gadget->name, sizeof(cap->card));
strscpy(cap->bus_info, dev_name(&cdev->gadget->dev),
sizeof(cap->bus_info));
return 0;
}
static int
uvc_v4l2_get_format(struct file *file, void *fh, struct v4l2_format *fmt)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
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_try_format(struct file *file, void *fh, struct v4l2_format *fmt)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
struct uvcg_format *uformat;
struct uvcg_frame *uframe;
const struct uvc_format_desc *fmtdesc;
u8 *fcc;
if (fmt->type != video->queue.queue.type)
return -EINVAL;
fcc = (u8 *)&fmt->fmt.pix.pixelformat;
uvcg_dbg(&uvc->func, "Trying format 0x%08x (%c%c%c%c): %ux%u\n",
fmt->fmt.pix.pixelformat,
fcc[0], fcc[1], fcc[2], fcc[3],
fmt->fmt.pix.width, fmt->fmt.pix.height);
uformat = find_format_by_pix(uvc, fmt->fmt.pix.pixelformat);
if (!uformat)
return -EINVAL;
uframe = find_closest_frame_by_size(uvc, uformat,
fmt->fmt.pix.width, fmt->fmt.pix.height);
if (!uframe)
return -EINVAL;
if (uformat->type == UVCG_UNCOMPRESSED) {
struct uvcg_uncompressed *u =
to_uvcg_uncompressed(&uformat->group.cg_item);
if (!u)
return 0;
v4l2_fill_pixfmt(&fmt->fmt.pix, fmt->fmt.pix.pixelformat,
uframe->frame.w_width, uframe->frame.w_height);
if (fmt->fmt.pix.sizeimage != (uvc_v4l2_get_bytesperline(uformat, uframe) *
uframe->frame.w_height))
return -EINVAL;
} else {
fmt->fmt.pix.width = uframe->frame.w_width;
fmt->fmt.pix.height = uframe->frame.w_height;
fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(uformat, uframe);
fmt->fmt.pix.sizeimage = uvc_get_frame_size(uformat, uframe);
fmtdesc = to_uvc_format(uformat);
if (IS_ERR(fmtdesc))
return PTR_ERR(fmtdesc);
fmt->fmt.pix.pixelformat = fmtdesc->fcc;
}
fmt->fmt.pix.field = V4L2_FIELD_NONE;
fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
fmt->fmt.pix.priv = 0;
return 0;
}
static int
uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
int ret;
ret = uvc_v4l2_try_format(file, fh, fmt);
if (ret)
return ret;
video->fcc = fmt->fmt.pix.pixelformat;
video->bpp = fmt->fmt.pix.bytesperline * 8 / video->width;
video->width = fmt->fmt.pix.width;
video->height = fmt->fmt.pix.height;
video->imagesize = fmt->fmt.pix.sizeimage;
return ret;
}
static int uvc_v4l2_g_parm(struct file *file, void *fh,
struct v4l2_streamparm *parm)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
struct v4l2_fract timeperframe;
if (!V4L2_TYPE_IS_OUTPUT(parm->type))
return -EINVAL;
/* Return the actual frame period. */
timeperframe.numerator = video->interval;
timeperframe.denominator = 10000000;
v4l2_simplify_fraction(&timeperframe.numerator,
&timeperframe.denominator, 8, 333);
uvcg_dbg(&uvc->func, "Getting frame interval of %u/%u (%u)\n",
timeperframe.numerator, timeperframe.denominator,
video->interval);
parm->parm.output.timeperframe = timeperframe;
parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
return 0;
}
static int uvc_v4l2_s_parm(struct file *file, void *fh,
struct v4l2_streamparm *parm)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
struct v4l2_fract timeperframe;
if (!V4L2_TYPE_IS_OUTPUT(parm->type))
return -EINVAL;
timeperframe = parm->parm.output.timeperframe;
video->interval = v4l2_fraction_to_interval(timeperframe.numerator,
timeperframe.denominator);
uvcg_dbg(&uvc->func, "Setting frame interval to %u/%u (%u)\n",
timeperframe.numerator, timeperframe.denominator,
video->interval);
return 0;
}
static int
uvc_v4l2_enum_frameintervals(struct file *file, void *fh,
struct v4l2_frmivalenum *fival)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvcg_format *uformat = NULL;
struct uvcg_frame *uframe = NULL;
struct uvcg_frame_ptr *frame;
uformat = find_format_by_pix(uvc, fival->pixel_format);
if (!uformat)
return -EINVAL;
list_for_each_entry(frame, &uformat->frames, entry) {
if (frame->frm->frame.w_width == fival->width &&
frame->frm->frame.w_height == fival->height) {
uframe = frame->frm;
break;
}
}
if (!uframe)
return -EINVAL;
if (fival->index >= uframe->frame.b_frame_interval_type)
return -EINVAL;
fival->discrete.numerator =
uframe->dw_frame_interval[fival->index];
/* TODO: handle V4L2_FRMIVAL_TYPE_STEPWISE */
fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
fival->discrete.denominator = 10000000;
v4l2_simplify_fraction(&fival->discrete.numerator,
&fival->discrete.denominator, 8, 333);
return 0;
}
static int
uvc_v4l2_enum_framesizes(struct file *file, void *fh,
struct v4l2_frmsizeenum *fsize)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvcg_format *uformat = NULL;
struct uvcg_frame *uframe = NULL;
uformat = find_format_by_pix(uvc, fsize->pixel_format);
if (!uformat)
return -EINVAL;
if (fsize->index >= uformat->num_frames)
return -EINVAL;
uframe = find_frame_by_index(uvc, uformat, fsize->index + 1);
if (!uframe)
return -EINVAL;
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
fsize->discrete.width = uframe->frame.w_width;
fsize->discrete.height = uframe->frame.w_height;
return 0;
}
static int
uvc_v4l2_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
const struct uvc_format_desc *fmtdesc;
struct uvcg_format *uformat;
if (f->index >= uvc->header->num_fmt)
return -EINVAL;
uformat = find_format_by_index(uvc, f->index + 1);
if (!uformat)
return -EINVAL;
fmtdesc = to_uvc_format(uformat);
if (IS_ERR(fmtdesc))
return PTR_ERR(fmtdesc);
f->pixelformat = fmtdesc->fcc;
return 0;
}
static int
uvc_v4l2_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
if (b->type != video->queue.queue.type)
return -EINVAL;
return uvcg_alloc_buffers(&video->queue, b);
}
static int
uvc_v4l2_querybuf(struct file *file, void *fh, struct v4l2_buffer *b)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
return uvcg_query_buffer(&video->queue, b);
}
static int
uvc_v4l2_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
int ret;
ret = uvcg_queue_buffer(&video->queue, b);
if (ret < 0)
return ret;
if (uvc->state == UVC_STATE_STREAMING)
queue_work(video->async_wq, &video->pump);
return ret;
}
static int
uvc_v4l2_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
return uvcg_dequeue_buffer(&video->queue, b, file->f_flags & O_NONBLOCK);
}
static int
uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
int ret;
if (type != video->queue.queue.type)
return -EINVAL;
/* Enable UVC video. */
ret = uvcg_video_enable(video);
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, 0);
uvc->state = UVC_STATE_STREAMING;
return 0;
}
static int
uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
int ret = 0;
if (type != video->queue.queue.type)
return -EINVAL;
ret = uvcg_video_disable(video);
if (ret < 0)
return ret;
if (uvc->state != UVC_STATE_STREAMING)
return 0;
uvc->state = UVC_STATE_CONNECTED;
uvc_function_setup_continue(uvc, 1);
return 0;
}
static int
uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
const struct v4l2_event_subscription *sub)
{
struct uvc_device *uvc = video_get_drvdata(fh->vdev);
struct uvc_file_handle *handle = to_uvc_file_handle(fh);
int ret;
if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST)
return -EINVAL;
guard(mutex)(&uvc->lock);
if (sub->type == UVC_EVENT_SETUP && uvc->func_connected)
return -EBUSY;
ret = v4l2_event_subscribe(fh, sub, 2, NULL);
if (ret < 0)
return ret;
if (sub->type == UVC_EVENT_SETUP) {
uvc->func_connected = true;
handle->is_uvc_app_handle = true;
uvc_function_connect(uvc);
}
return 0;
}
static void uvc_v4l2_disable(struct uvc_device *uvc)
{
uvc_function_disconnect(uvc);
uvcg_video_disable(&uvc->video);
uvcg_free_buffers(&uvc->video.queue);
scoped_guard(mutex, &uvc->lock)
uvc->func_connected = false;
wake_up_interruptible(&uvc->func_connected_queue);
}
static int
uvc_v4l2_unsubscribe_event(struct v4l2_fh *fh,
const struct v4l2_event_subscription *sub)
{
struct uvc_device *uvc = video_get_drvdata(fh->vdev);
struct uvc_file_handle *handle = to_uvc_file_handle(fh);
int ret;
ret = v4l2_event_unsubscribe(fh, sub);
if (ret < 0)
return ret;
if (sub->type == UVC_EVENT_SETUP && handle->is_uvc_app_handle) {
uvc_v4l2_disable(uvc);
handle->is_uvc_app_handle = false;
}
return 0;
}
static long
uvc_v4l2_ioctl_default(struct file *file, void *fh, bool valid_prio,
unsigned int cmd, void *arg)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
switch (cmd) {
case UVCIOC_SEND_RESPONSE:
return uvc_send_response(uvc, arg);
default:
return -ENOIOCTLCMD;
}
}
const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops = {
.vidioc_querycap = uvc_v4l2_querycap,
.vidioc_try_fmt_vid_out = uvc_v4l2_try_format,
.vidioc_g_fmt_vid_out = uvc_v4l2_get_format,
.vidioc_s_fmt_vid_out = uvc_v4l2_set_format,
.vidioc_enum_frameintervals = uvc_v4l2_enum_frameintervals,
.vidioc_enum_framesizes = uvc_v4l2_enum_framesizes,
.vidioc_enum_fmt_vid_out = uvc_v4l2_enum_format,
.vidioc_reqbufs = uvc_v4l2_reqbufs,
.vidioc_querybuf = uvc_v4l2_querybuf,
.vidioc_qbuf = uvc_v4l2_qbuf,
.vidioc_dqbuf = uvc_v4l2_dqbuf,
.vidioc_streamon = uvc_v4l2_streamon,
.vidioc_streamoff = uvc_v4l2_streamoff,
.vidioc_s_parm = uvc_v4l2_s_parm,
.vidioc_g_parm = uvc_v4l2_g_parm,
.vidioc_subscribe_event = uvc_v4l2_subscribe_event,
.vidioc_unsubscribe_event = uvc_v4l2_unsubscribe_event,
.vidioc_default = uvc_v4l2_ioctl_default,
};
/* --------------------------------------------------------------------------
* V4L2
*/
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_obj(*handle);
if (handle == NULL)
return -ENOMEM;
v4l2_fh_init(&handle->vfh, vdev);
v4l2_fh_add(&handle->vfh, file);
handle->device = &uvc->video;
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 = file_to_uvc_file_handle(file);
struct uvc_video *video = handle->device;
mutex_lock(&video->mutex);
if (handle->is_uvc_app_handle)
uvc_v4l2_disable(uvc);
mutex_unlock(&video->mutex);
v4l2_fh_del(&handle->vfh, file);
v4l2_fh_exit(&handle->vfh);
kfree(handle);
return 0;
}
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 uvcg_queue_mmap(&uvc->video.queue, vma);
}
static __poll_t
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 uvcg_queue_poll(&uvc->video.queue, file, wait);
}
#ifndef CONFIG_MMU
static unsigned long uvcg_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 uvcg_queue_get_unmapped_area(&uvc->video.queue, pgoff);
}
#endif
const struct v4l2_file_operations uvc_v4l2_fops = {
.owner = THIS_MODULE,
.open = uvc_v4l2_open,
.release = uvc_v4l2_release,
.unlocked_ioctl = video_ioctl2,
.mmap = uvc_v4l2_mmap,
.poll = uvc_v4l2_poll,
#ifndef CONFIG_MMU
.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
#endif
};
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* uvc_v4l2.h -- USB Video Class Gadget driver
*
* Copyright (C) 2009-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* http://www.samsung.com
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef __UVC_V4L2_H__
#define __UVC_V4L2_H__
extern const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops;
extern const struct v4l2_file_operations uvc_v4l2_fops;
#endif /* __UVC_V4L2_H__ */
@@ -0,0 +1,843 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* uvc_video.c -- USB Video Class Gadget driver
*
* Copyright (C) 2009-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/video.h>
#include <linux/unaligned.h>
#include <media/v4l2-dev.h>
#include "uvc.h"
#include "uvc_queue.h"
#include "uvc_video.h"
#include "uvc_trace.h"
/* --------------------------------------------------------------------------
* Video codecs
*/
static int
uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf,
u8 *data, int len)
{
struct uvc_device *uvc = container_of(video, struct uvc_device, video);
struct usb_composite_dev *cdev = uvc->func.config->cdev;
struct timespec64 ts = ns_to_timespec64(buf->buf.vb2_buf.timestamp);
int pos = 2;
data[1] = UVC_STREAM_EOH | video->fid;
if (video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE)
data[1] |= UVC_STREAM_ERR;
if (video->queue.buf_used == 0 && ts.tv_sec) {
/* dwClockFrequency is 48 MHz */
u32 pts = ((u64)ts.tv_sec * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC) * 48;
data[1] |= UVC_STREAM_PTS;
put_unaligned_le32(pts, &data[pos]);
pos += 4;
}
if (cdev->gadget->ops->get_frame) {
u32 sof, stc;
sof = usb_gadget_frame_number(cdev->gadget);
ktime_get_ts64(&ts);
stc = ((u64)ts.tv_sec * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC) * 48;
data[1] |= UVC_STREAM_SCR;
put_unaligned_le32(stc, &data[pos]);
put_unaligned_le16(sof, &data[pos+4]);
pos += 6;
}
data[0] = pos;
if (buf->bytesused - video->queue.buf_used <= len - pos)
data[1] |= UVC_STREAM_EOF;
return pos;
}
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_t(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;
struct uvc_request *ureq = req->context;
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_t(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;
list_del(&buf->queue);
video->fid ^= UVC_STREAM_FID;
ureq->last_buf = buf;
video->payload_size = 0;
}
if (video->payload_size == video->max_payload_size ||
video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE ||
buf->bytesused == video->queue.buf_used)
video->payload_size = 0;
}
static void
uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video,
struct uvc_buffer *buf)
{
unsigned int pending = buf->bytesused - video->queue.buf_used;
struct uvc_request *ureq = req->context;
struct scatterlist *sg, *iter;
unsigned int len = buf->req_payload_size;
unsigned int sg_left, part = 0;
unsigned int i;
int header_len;
sg = ureq->sgt.sgl;
sg_init_table(sg, ureq->sgt.nents);
/* Init the header. */
header_len = uvc_video_encode_header(video, buf, ureq->header,
buf->req_payload_size);
sg_set_buf(sg, ureq->header, header_len);
len -= header_len;
if (pending <= len)
len = pending;
req->length = (len == pending) ? len + header_len :
buf->req_payload_size;
/* Init the pending sgs with payload */
sg = sg_next(sg);
for_each_sg(sg, iter, ureq->sgt.nents - 1, i) {
if (!len || !buf->sg || !buf->sg->length)
break;
sg_left = buf->sg->length - buf->offset;
part = min_t(unsigned int, len, sg_left);
sg_set_page(iter, sg_page(buf->sg), part, buf->offset);
if (part == sg_left) {
buf->offset = 0;
buf->sg = sg_next(buf->sg);
} else {
buf->offset += part;
}
len -= part;
}
/* Assign the video data with header. */
req->buf = NULL;
req->sg = ureq->sgt.sgl;
req->num_sgs = i + 1;
req->length -= len;
video->queue.buf_used += req->length - header_len;
if (buf->bytesused == video->queue.buf_used || !buf->sg ||
video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) {
video->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE;
buf->offset = 0;
list_del(&buf->queue);
video->fid ^= UVC_STREAM_FID;
ureq->last_buf = buf;
}
}
static void
uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
struct uvc_buffer *buf)
{
void *mem = req->buf;
struct uvc_request *ureq = req->context;
int len = buf->req_payload_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 = buf->req_payload_size - len;
if (buf->bytesused == video->queue.buf_used ||
video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) {
video->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE;
list_del(&buf->queue);
video->fid ^= UVC_STREAM_FID;
ureq->last_buf = buf;
}
}
/* --------------------------------------------------------------------------
* Request handling
*/
/*
* Callers must take care to hold req_lock when this function may be called
* from multiple threads. For example, when frames are streaming to the host.
*/
static void
uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
{
sg_free_table(&ureq->sgt);
if (ureq->req && ep) {
usb_ep_free_request(ep, ureq->req);
ureq->req = NULL;
}
kfree(ureq->req_buffer);
ureq->req_buffer = NULL;
if (!list_empty(&ureq->list))
list_del_init(&ureq->list);
kfree(ureq);
}
static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
{
int ret;
ret = usb_ep_queue(video->ep, req, GFP_ATOMIC);
if (ret < 0) {
uvcg_err(&video->uvc->func, "Failed to queue request (%d).\n",
ret);
/* If the endpoint is disabled the descriptor may be NULL. */
if (video->ep->desc) {
/* Isochronous endpoints can't be halted. */
if (usb_endpoint_xfer_bulk(video->ep->desc))
usb_ep_set_halt(video->ep);
}
}
atomic_inc(&video->queued);
trace_uvcg_video_queue(req, atomic_read(&video->queued));
return ret;
}
/* This function must be called with video->req_lock held. */
static int uvcg_video_usb_req_queue(struct uvc_video *video,
struct usb_request *req, bool queue_to_ep)
{
bool is_bulk = video->max_payload_size;
struct list_head *list = NULL;
if (!video->is_enabled)
return -ENODEV;
if (queue_to_ep) {
struct uvc_request *ureq = req->context;
/*
* With USB3 handling more requests at a higher speed, we can't
* afford to generate an interrupt for every request. Decide to
* interrupt:
*
* - When no more requests are available in the free queue, as
* this may be our last chance to refill the endpoint's
* request queue.
*
* - When this is request is the last request for the video
* buffer, as we want to start sending the next video buffer
* ASAP in case it doesn't get started already in the next
* iteration of this loop.
*
* - Four times over the length of the requests queue (as
* indicated by video->uvc_num_requests), as a trade-off
* between latency and interrupt load.
*/
if (list_empty(&video->req_free) || ureq->last_buf ||
!(video->req_int_count %
min(DIV_ROUND_UP(video->uvc_num_requests, 4), UVCG_REQ_MAX_INT_COUNT))) {
video->req_int_count = 0;
req->no_interrupt = 0;
} else {
req->no_interrupt = 1;
}
video->req_int_count++;
return uvcg_video_ep_queue(video, req);
}
/*
* If we're not queuing to the ep, for isoc we're queuing
* to the req_ready list, otherwise req_free.
*/
list = is_bulk ? &video->req_free : &video->req_ready;
list_add_tail(&req->list, list);
return 0;
}
static void
uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
{
struct uvc_request *ureq = req->context;
struct uvc_video *video = ureq->video;
struct uvc_video_queue *queue = &video->queue;
struct uvc_buffer *last_buf;
unsigned long flags;
spin_lock_irqsave(&video->req_lock, flags);
atomic_dec(&video->queued);
if (!video->is_enabled) {
/*
* When is_enabled is false, uvcg_video_disable() ensures
* that in-flight uvc_buffers are returned, so we can
* safely call free_request without worrying about
* last_buf.
*/
uvc_video_free_request(ureq, ep);
spin_unlock_irqrestore(&video->req_lock, flags);
return;
}
last_buf = ureq->last_buf;
ureq->last_buf = NULL;
spin_unlock_irqrestore(&video->req_lock, flags);
switch (req->status) {
case 0:
break;
case -EXDEV:
uvcg_dbg(&video->uvc->func, "VS request missed xfer.\n");
if (req->length != 0)
queue->flags |= UVC_QUEUE_DROP_INCOMPLETE;
break;
case -ESHUTDOWN: /* disconnect from host. */
uvcg_dbg(&video->uvc->func, "VS request cancelled.\n");
uvcg_queue_cancel(queue, 1);
break;
default:
uvcg_warn(&video->uvc->func,
"VS request completed with status %d.\n",
req->status);
uvcg_queue_cancel(queue, 0);
}
if (last_buf) {
spin_lock_irqsave(&queue->irqlock, flags);
uvcg_complete_buffer(queue, last_buf);
spin_unlock_irqrestore(&queue->irqlock, flags);
}
spin_lock_irqsave(&video->req_lock, flags);
/*
* Video stream might have been disabled while we were
* processing the current usb_request. So make sure
* we're still streaming before queueing the usb_request
* back to req_free
*/
if (!video->is_enabled) {
uvc_video_free_request(ureq, ep);
spin_unlock_irqrestore(&video->req_lock, flags);
uvcg_queue_cancel(queue, 0);
return;
}
list_add_tail(&req->list, &video->req_free);
/*
* Queue work to the wq as well since it is possible that a
* buffer may not have been completely encoded with the set of
* in-flight usb requests for whih the complete callbacks are
* firing.
* In that case, if we do not queue work to the worker thread,
* the buffer will never be marked as complete - and therefore
* not be returned to userpsace. As a result,
* dequeue -> queue -> dequeue flow of uvc buffers will not
* happen. Since there are is a new free request wake up the pump.
*/
queue_work(video->async_wq, &video->pump);
trace_uvcg_video_complete(req, atomic_read(&video->queued));
spin_unlock_irqrestore(&video->req_lock, flags);
kthread_queue_work(video->kworker, &video->hw_submit);
}
static void uvcg_video_hw_submit(struct kthread_work *work)
{
struct uvc_video *video = container_of(work, struct uvc_video, hw_submit);
bool is_bulk = video->max_payload_size;
unsigned long flags;
struct usb_request *req;
int ret = 0;
while (true) {
if (!video->ep->enabled)
return;
spin_lock_irqsave(&video->req_lock, flags);
/*
* Here we check whether any request is available in the ready
* list. If it is, queue it to the ep and add the current
* usb_request to the req_free list - for video_pump to fill in.
* Otherwise, just use the current usb_request to queue a 0
* length request to the ep. Since we always add to the req_free
* list if we dequeue from the ready list, there will never
* be a situation where the req_free list is completely out of
* requests and cannot recover.
*/
if (!list_empty(&video->req_ready)) {
req = list_first_entry(&video->req_ready,
struct usb_request, list);
} else {
if (list_empty(&video->req_free) ||
(atomic_read(&video->queued) > UVCG_REQ_MAX_ZERO_COUNT)) {
spin_unlock_irqrestore(&video->req_lock, flags);
return;
}
req = list_first_entry(&video->req_free, struct usb_request,
list);
req->length = 0;
}
list_del(&req->list);
/*
* Queue to the endpoint. The actual queueing to ep will
* only happen on one thread - the async_wq for bulk endpoints
* and this thread for isoc endpoints.
*/
ret = uvcg_video_usb_req_queue(video, req, !is_bulk);
if (ret < 0) {
/*
* Endpoint error, but the stream is still enabled.
* Put request back in req_free for it to be cleaned
* up later.
*/
list_add_tail(&req->list, &video->req_free);
/*
* There is a new free request - wake up the pump.
*/
queue_work(video->async_wq, &video->pump);
}
spin_unlock_irqrestore(&video->req_lock, flags);
}
}
static int
uvc_video_free_requests(struct uvc_video *video)
{
struct uvc_request *ureq, *temp;
list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
uvc_video_free_request(ureq, video->ep);
INIT_LIST_HEAD(&video->ureqs);
INIT_LIST_HEAD(&video->req_free);
INIT_LIST_HEAD(&video->req_ready);
return 0;
}
static void
uvc_video_prep_requests(struct uvc_video *video)
{
struct uvc_device *uvc = container_of(video, struct uvc_device, video);
struct usb_composite_dev *cdev = uvc->func.config->cdev;
unsigned int interval_duration;
unsigned int max_req_size, req_size, header_size;
unsigned int nreq;
max_req_size = video->max_req_size;
if (!usb_endpoint_xfer_isoc(video->ep->desc)) {
video->req_size = max_req_size;
video->reqs_per_frame = video->uvc_num_requests =
DIV_ROUND_UP(video->imagesize, max_req_size);
return;
}
interval_duration = 1 << (video->ep->desc->bInterval - 1);
if (cdev->gadget->speed < USB_SPEED_HIGH)
interval_duration *= 10000;
else
interval_duration *= 1250;
nreq = DIV_ROUND_UP(video->interval, interval_duration);
header_size = nreq * UVCG_REQUEST_HEADER_LEN;
req_size = DIV_ROUND_UP(video->imagesize + header_size, nreq);
if (req_size > max_req_size) {
/* The prepared interval length and expected buffer size
* is not possible to stream with the currently configured
* isoc bandwidth. Fallback to the maximum.
*/
req_size = max_req_size;
}
video->req_size = req_size;
/* We need to compensate the amount of requests to be
* allocated with the maximum amount of zero length requests.
* Since it is possible that hw_submit will initially
* enqueue some zero length requests and we then will not be
* able to fully encode one frame.
*/
video->uvc_num_requests = nreq + UVCG_REQ_MAX_ZERO_COUNT;
video->reqs_per_frame = nreq;
}
static int
uvc_video_alloc_requests(struct uvc_video *video)
{
struct uvc_request *ureq;
unsigned int i;
int ret = -ENOMEM;
/*
* calculate in uvc_video_prep_requests
* - video->uvc_num_requests
* - video->req_size
*/
uvc_video_prep_requests(video);
for (i = 0; i < video->uvc_num_requests; i++) {
ureq = kzalloc_obj(struct uvc_request);
if (ureq == NULL)
goto error;
INIT_LIST_HEAD(&ureq->list);
list_add_tail(&ureq->list, &video->ureqs);
ureq->req_buffer = kmalloc(video->req_size, GFP_KERNEL);
if (ureq->req_buffer == NULL)
goto error;
ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
if (ureq->req == NULL)
goto error;
ureq->req->buf = ureq->req_buffer;
ureq->req->length = 0;
ureq->req->complete = uvc_video_complete;
ureq->req->context = ureq;
ureq->video = video;
ureq->last_buf = NULL;
list_add_tail(&ureq->req->list, &video->req_free);
/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
sg_alloc_table(&ureq->sgt,
DIV_ROUND_UP(video->req_size - UVCG_REQUEST_HEADER_LEN,
PAGE_SIZE) + 2, GFP_KERNEL);
}
return 0;
error:
uvc_video_free_requests(video);
return ret;
}
/* --------------------------------------------------------------------------
* Video streaming
*/
/*
* uvcg_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 void uvcg_video_pump(struct work_struct *work)
{
struct uvc_video *video = container_of(work, struct uvc_video, pump);
struct uvc_video_queue *queue = &video->queue;
/* video->max_payload_size is only set when using bulk transfer */
bool is_bulk = video->max_payload_size;
struct usb_request *req = NULL;
struct uvc_buffer *buf;
unsigned long flags;
int ret = 0;
while (true) {
if (!video->ep->enabled)
return;
/*
* Check is_enabled and retrieve the first available USB
* request, protected by the request lock.
*/
spin_lock_irqsave(&video->req_lock, flags);
if (!video->is_enabled || list_empty(&video->req_free)) {
spin_unlock_irqrestore(&video->req_lock, flags);
return;
}
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(&queue->irqlock, flags);
buf = uvcg_queue_head(queue);
if (!buf) {
/*
* Either the queue has been disconnected or no video buffer
* available for bulk transfer. Either way, stop processing
* further.
*/
spin_unlock_irqrestore(&queue->irqlock, flags);
break;
}
video->encode(req, video, buf);
spin_unlock_irqrestore(&queue->irqlock, flags);
spin_lock_irqsave(&video->req_lock, flags);
/* For bulk end points we queue from the worker thread
* since we would preferably not want to wait on requests
* to be ready, in the uvcg_video_complete() handler.
* For isoc endpoints we add the request to the ready list
* and only queue it to the endpoint from the complete handler.
*/
ret = uvcg_video_usb_req_queue(video, req, is_bulk);
spin_unlock_irqrestore(&video->req_lock, flags);
if (ret < 0) {
uvcg_queue_cancel(queue, 0);
break;
}
}
spin_lock_irqsave(&video->req_lock, flags);
if (video->is_enabled)
list_add_tail(&req->list, &video->req_free);
else
uvc_video_free_request(req->context, video->ep);
spin_unlock_irqrestore(&video->req_lock, flags);
}
/*
* Disable the video stream
*/
int
uvcg_video_disable(struct uvc_video *video)
{
unsigned long flags;
struct list_head inflight_bufs;
struct usb_request *req, *temp;
struct uvc_buffer *buf, *btemp;
struct uvc_request *ureq, *utemp;
if (video->ep == NULL) {
uvcg_info(&video->uvc->func,
"Video disable failed, device is uninitialized.\n");
return -ENODEV;
}
INIT_LIST_HEAD(&inflight_bufs);
spin_lock_irqsave(&video->req_lock, flags);
video->is_enabled = false;
/*
* Remove any in-flight buffers from the uvc_requests
* because we want to return them before cancelling the
* queue. This ensures that we aren't stuck waiting for
* all complete callbacks to come through before disabling
* vb2 queue.
*/
list_for_each_entry(ureq, &video->ureqs, list) {
if (ureq->last_buf) {
list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
ureq->last_buf = NULL;
}
}
spin_unlock_irqrestore(&video->req_lock, flags);
cancel_work_sync(&video->pump);
uvcg_queue_cancel(&video->queue, 0);
spin_lock_irqsave(&video->req_lock, flags);
/*
* Remove all uvc_requests from ureqs with list_del_init
* This lets uvc_video_free_request correctly identify
* if the uvc_request is attached to a list or not when freeing
* memory.
*/
list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
list_del_init(&ureq->list);
list_for_each_entry_safe(req, temp, &video->req_free, list) {
list_del(&req->list);
uvc_video_free_request(req->context, video->ep);
}
list_for_each_entry_safe(req, temp, &video->req_ready, list) {
list_del(&req->list);
uvc_video_free_request(req->context, video->ep);
}
INIT_LIST_HEAD(&video->ureqs);
INIT_LIST_HEAD(&video->req_free);
INIT_LIST_HEAD(&video->req_ready);
spin_unlock_irqrestore(&video->req_lock, flags);
/*
* Return all the video buffers before disabling the queue.
*/
spin_lock_irqsave(&video->queue.irqlock, flags);
list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
list_del(&buf->queue);
uvcg_complete_buffer(&video->queue, buf);
}
spin_unlock_irqrestore(&video->queue.irqlock, flags);
uvcg_queue_enable(&video->queue, 0);
return 0;
}
/*
* Enable the video stream.
*/
int uvcg_video_enable(struct uvc_video *video)
{
int ret;
if (video->ep == NULL) {
uvcg_info(&video->uvc->func,
"Video enable failed, device is uninitialized.\n");
return -ENODEV;
}
/*
* Safe to access request related fields without req_lock because
* this is the only thread currently active, and no other
* request handling thread will become active until this function
* returns.
*/
video->is_enabled = true;
if ((ret = uvcg_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 = video->queue.use_sg ?
uvc_video_encode_isoc_sg : uvc_video_encode_isoc;
video->req_int_count = 0;
atomic_set(&video->queued, 0);
kthread_queue_work(video->kworker, &video->hw_submit);
queue_work(video->async_wq, &video->pump);
return ret;
}
/*
* Initialize the UVC video stream.
*/
int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
{
video->is_enabled = false;
INIT_LIST_HEAD(&video->ureqs);
INIT_LIST_HEAD(&video->req_free);
INIT_LIST_HEAD(&video->req_ready);
spin_lock_init(&video->req_lock);
INIT_WORK(&video->pump, uvcg_video_pump);
/* Allocate a work queue for asynchronous video pump handler. */
video->async_wq = alloc_workqueue("uvcgadget", WQ_UNBOUND | WQ_HIGHPRI, 0);
if (!video->async_wq)
return -EINVAL;
/* Allocate a kthread for asynchronous hw submit handler. */
video->kworker = kthread_run_worker(0, "UVCG");
if (IS_ERR(video->kworker)) {
uvcg_err(&video->uvc->func, "failed to create UVCG kworker\n");
return PTR_ERR(video->kworker);
}
kthread_init_work(&video->hw_submit, uvcg_video_hw_submit);
sched_set_fifo(video->kworker->task);
video->uvc = uvc;
video->fcc = V4L2_PIX_FMT_YUYV;
video->bpp = 16;
video->width = 320;
video->height = 240;
video->imagesize = 320 * 240 * 2;
video->interval = 666666;
/* Initialize the video buffers queue. */
return uvcg_queue_init(&video->queue, uvc->v4l2_dev.dev->parent,
V4L2_BUF_TYPE_VIDEO_OUTPUT, &video->mutex);
}
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* uvc_video.h -- USB Video Class Gadget driver
*
* Copyright (C) 2009-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* http://www.samsung.com
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*/
#ifndef __UVC_VIDEO_H__
#define __UVC_VIDEO_H__
struct uvc_video;
int uvcg_video_enable(struct uvc_video *video);
int uvcg_video_disable(struct uvc_video *video);
int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc);
#endif /* __UVC_VIDEO_H__ */