staging: usb: gadget: Add Configurable Composite Gadget driver

The Configurable Gadget driver is a composite driver that allows
userspace to change at runtime the list of functions enabled in
its configuration and to configure these functions. It supports
multiple functions: acm, rndis, and mass storage.

It is usually controlled by a daemon that changes the configuration
based on user settings. For example, rndis is enabled when the user
enables sharing the phone data connection.

As an example on how to use it, the following shell commands will
make the gadget disconnect from the host and make it be re-enumerated
as a composite with 1 rndis and 2 acm interfaces, and a different
product id:

echo 0       > /sys/class/ccg_usb/ccg0/enable
echo rndis,acm > /sys/class/ccg_usb/ccg0/functions
echo 2       > /sys/class/ccg_usb/ccg0/f_acm/instances
echo -n 0x2d01 > /sys/module/g_ccg/parameters/idProduct
echo 1       > /sys/class/ccg_usb/ccg0/enable

The driver requires a gadget controller that supports software
control of the D+ pullup and the controller driver must support
disabling the pullup during composite_bind.

Signed-off-by: Mike Lockwood <lockwood@android.com>
Signed-off-by: Benoit Goby <benoit@android.com>
	[import from android.c, implement review comments, remove adb,mtp,ptp,accessory]
Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Mike Lockwood 2012-05-16 12:34:37 +02:00 коммит произвёл Greg Kroah-Hartman
Родитель 3a2300385f
Коммит 332bb43f7c
7 изменённых файлов: 1156 добавлений и 0 удалений

Просмотреть файл

@ -130,4 +130,6 @@ source "drivers/staging/ramster/Kconfig"
source "drivers/staging/ozwpan/Kconfig"
source "drivers/staging/ccg/Kconfig"
endif # STAGING

Просмотреть файл

@ -56,3 +56,4 @@ obj-$(CONFIG_ANDROID) += android/
obj-$(CONFIG_PHONE) += telephony/
obj-$(CONFIG_RAMSTER) += ramster/
obj-$(CONFIG_USB_WPAN_HCD) += ozwpan/
obj-$(CONFIG_USB_G_CCG) += ccg/

Просмотреть файл

@ -0,0 +1,20 @@
if USB_GADGET
config USB_G_CCG
tristate "Configurable Composite Gadget (STAGING)"
depends on STAGING && !USB_ZERO && !USB_ZERO_HNPTEST && !USB_AUDIO && !GADGET_UAC1 && !USB_ETH && !USB_ETH_RNDIS && !USB_ETH_EEM && !USB_G_NCM && !USB_GADGETFS && !USB_FUNCTIONFS && !USB_FUNCTIONFS_ETH && !USB_FUNCTIONFS_RNDIS && !USB_FUNCTIONFS_GENERIC && !USB_FILE_STORAGE && !USB_FILE_STORAGE_TEST && !USB_MASS_STORAGE && !USB_G_SERIAL && !USB_MIDI_GADGET && !USB_G_PRINTER && !USB_CDC_COMPOSITE && !USB_G_NOKIA && !USB_G_ACM_MS && !USB_G_MULTI && !USB_G_MULTI_RNDIS && !USB_G_MULTI_CDC && !USB_G_HID && !USB_G_DBGP && !USB_G_WEBCAM
help
The Configurable Composite Gadget supports multiple USB
functions: acm, mass storage, rndis and FunctionFS.
Each function can be configured and enabled/disabled
dynamically from userspace through a sysfs interface.
In order to compile this (either as a module or built-in),
"USB Gadget Drivers" and anything under it must not be
selected compiled-in in
Device Drivers->USB Support->USB Gadget Support.
However, you can say "M" there, if you do, the
Configurable Composite Gadget can be compiled "M" only
or not at all.
endif # USB_GADGET

Просмотреть файл

@ -0,0 +1,4 @@
g_ccg-y := ccg.o
ccflags-y += -Idrivers/usb/gadget
obj-$(CONFIG_USB_G_CCG) += g_ccg.o

6
drivers/staging/ccg/TODO Normal file
Просмотреть файл

@ -0,0 +1,6 @@
TODO:
- change configuration interface from sysfs to configfs
Please send patches to Greg Kroah-Hartmann <gregkh@linuxfoundation.org>,
Andrzej Pietrasiewicz <andrzej.p@samsung.com>, and
Cc: Mike Lockwood <lockwood@android.com>

995
drivers/staging/ccg/ccg.c Normal file
Просмотреть файл

@ -0,0 +1,995 @@
/*
* Configurable Composite Gadget
*
* Initially contributed as "Android Composite Gdaget" by:
*
* Copyright (C) 2008 Google, Inc.
* Author: Mike Lockwood <lockwood@android.com>
* Benoit Goby <benoit@android.com>
*
* Tailoring it to become a generic Configurable Composite Gadget is
*
* Copyright (C) 2012 Samsung Electronics
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/utsname.h>
#include <linux/platform_device.h>
#include <linux/usb/ch9.h>
#include <linux/usb/composite.h>
#include <linux/usb/gadget.h>
#include "gadget_chips.h"
/*
* Kbuild is not very cooperative with respect to linking separately
* compiled library objects into one module. So for now we won't use
* separate compilation ... ensuring init/exit sections work to shrink
* the runtime footprint, and giving us at least some parts of what
* a "gcc --combine ... part1.c part2.c part3.c ... " build would.
*/
#include "../../usb/gadget/usbstring.c"
#include "../../usb/gadget/config.c"
#include "../../usb/gadget/epautoconf.c"
#include "../../usb/gadget/composite.c"
#include "../../usb/gadget/f_mass_storage.c"
#include "../../usb/gadget/u_serial.c"
#include "../../usb/gadget/f_acm.c"
#define USB_ETH_RNDIS y
#include "../../usb/gadget/f_rndis.c"
#include "../../usb/gadget/rndis.c"
#include "../../usb/gadget/u_ether.c"
#include "../../usb/gadget/f_fs.c"
MODULE_AUTHOR("Mike Lockwood");
MODULE_DESCRIPTION("Configurable Composite USB Gadget");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");
static const char longname[] = "Configurable Composite Gadget";
/* Default vendor and product IDs, overridden by userspace */
#define VENDOR_ID 0x1d6b /* Linux Foundation */
#define PRODUCT_ID 0x0107
struct ccg_usb_function {
char *name;
void *config;
struct device *dev;
char *dev_name;
struct device_attribute **attributes;
/* for ccg_dev.enabled_functions */
struct list_head enabled_list;
/* Optional: initialization during gadget bind */
int (*init)(struct ccg_usb_function *, struct usb_composite_dev *);
/* Optional: cleanup during gadget unbind */
void (*cleanup)(struct ccg_usb_function *);
int (*bind_config)(struct ccg_usb_function *,
struct usb_configuration *);
/* Optional: called when the configuration is removed */
void (*unbind_config)(struct ccg_usb_function *,
struct usb_configuration *);
/* Optional: handle ctrl requests before the device is configured */
int (*ctrlrequest)(struct ccg_usb_function *,
struct usb_composite_dev *,
const struct usb_ctrlrequest *);
};
struct ccg_dev {
struct ccg_usb_function **functions;
struct list_head enabled_functions;
struct usb_composite_dev *cdev;
struct device *dev;
bool enabled;
struct mutex mutex;
bool connected;
bool sw_connected;
struct work_struct work;
};
static struct class *ccg_class;
static struct ccg_dev *_ccg_dev;
static int ccg_bind_config(struct usb_configuration *c);
static void ccg_unbind_config(struct usb_configuration *c);
static struct usb_device_descriptor device_desc = {
.bLength = sizeof(device_desc),
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = __constant_cpu_to_le16(0x0200),
.bDeviceClass = USB_CLASS_PER_INTERFACE,
.idVendor = __constant_cpu_to_le16(VENDOR_ID),
.idProduct = __constant_cpu_to_le16(PRODUCT_ID),
.bcdDevice = __constant_cpu_to_le16(0xffff),
.bNumConfigurations = 1,
};
static struct usb_configuration ccg_config_driver = {
.label = "ccg",
.unbind = ccg_unbind_config,
.bConfigurationValue = 1,
.bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
.bMaxPower = 0xFA, /* 500ma */
};
static void ccg_work(struct work_struct *data)
{
struct ccg_dev *dev = container_of(data, struct ccg_dev, work);
struct usb_composite_dev *cdev = dev->cdev;
static char *disconnected[2] = { "USB_STATE=DISCONNECTED", NULL };
static char *connected[2] = { "USB_STATE=CONNECTED", NULL };
static char *configured[2] = { "USB_STATE=CONFIGURED", NULL };
char **uevent_envp = NULL;
unsigned long flags;
spin_lock_irqsave(&cdev->lock, flags);
if (cdev->config)
uevent_envp = configured;
else if (dev->connected != dev->sw_connected)
uevent_envp = dev->connected ? connected : disconnected;
dev->sw_connected = dev->connected;
spin_unlock_irqrestore(&cdev->lock, flags);
if (uevent_envp) {
kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_envp);
pr_info("%s: sent uevent %s\n", __func__, uevent_envp[0]);
} else {
pr_info("%s: did not send uevent (%d %d %p)\n", __func__,
dev->connected, dev->sw_connected, cdev->config);
}
}
/*-------------------------------------------------------------------------*/
/* Supported functions initialization */
#define MAX_ACM_INSTANCES 4
struct acm_function_config {
int instances;
};
static int
acm_function_init(struct ccg_usb_function *f, struct usb_composite_dev *cdev)
{
f->config = kzalloc(sizeof(struct acm_function_config), GFP_KERNEL);
if (!f->config)
return -ENOMEM;
return gserial_setup(cdev->gadget, MAX_ACM_INSTANCES);
}
static void acm_function_cleanup(struct ccg_usb_function *f)
{
gserial_cleanup();
kfree(f->config);
f->config = NULL;
}
static int
acm_function_bind_config(struct ccg_usb_function *f,
struct usb_configuration *c)
{
int i;
int ret = 0;
struct acm_function_config *config = f->config;
for (i = 0; i < config->instances; i++) {
ret = acm_bind_config(c, i);
if (ret) {
pr_err("Could not bind acm%u config\n", i);
break;
}
}
return ret;
}
static ssize_t acm_instances_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ccg_usb_function *f = dev_get_drvdata(dev);
struct acm_function_config *config = f->config;
return sprintf(buf, "%d\n", config->instances);
}
static ssize_t acm_instances_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct ccg_usb_function *f = dev_get_drvdata(dev);
struct acm_function_config *config = f->config;
int value;
int ret = 0;
ret = kstrtoint(buf, 10, &value);
if (ret)
return ret;
if (value > MAX_ACM_INSTANCES)
return -EINVAL;
config->instances = value;
return size;
}
static DEVICE_ATTR(instances, S_IRUGO | S_IWUSR, acm_instances_show,
acm_instances_store);
static struct device_attribute *acm_function_attributes[] = {
&dev_attr_instances,
NULL
};
static struct ccg_usb_function acm_function = {
.name = "acm",
.init = acm_function_init,
.cleanup = acm_function_cleanup,
.bind_config = acm_function_bind_config,
.attributes = acm_function_attributes,
};
struct rndis_function_config {
u8 ethaddr[ETH_ALEN];
u32 vendorID;
char manufacturer[256];
/* "Wireless" RNDIS; auto-detected by Windows */
bool wceis;
};
static int rndis_function_init(struct ccg_usb_function *f,
struct usb_composite_dev *cdev)
{
f->config = kzalloc(sizeof(struct rndis_function_config), GFP_KERNEL);
if (!f->config)
return -ENOMEM;
return 0;
}
static void rndis_function_cleanup(struct ccg_usb_function *f)
{
kfree(f->config);
f->config = NULL;
}
static int rndis_function_bind_config(struct ccg_usb_function *f,
struct usb_configuration *c)
{
int ret;
struct rndis_function_config *rndis = f->config;
if (!rndis) {
pr_err("%s: rndis_pdata\n", __func__);
return -1;
}
pr_info("%s MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", __func__,
rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2],
rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]);
ret = gether_setup_name(c->cdev->gadget, rndis->ethaddr, "rndis");
if (ret) {
pr_err("%s: gether_setup failed\n", __func__);
return ret;
}
if (rndis->wceis) {
/* "Wireless" RNDIS; auto-detected by Windows */
rndis_iad_descriptor.bFunctionClass =
USB_CLASS_WIRELESS_CONTROLLER;
rndis_iad_descriptor.bFunctionSubClass = 0x01;
rndis_iad_descriptor.bFunctionProtocol = 0x03;
rndis_control_intf.bInterfaceClass =
USB_CLASS_WIRELESS_CONTROLLER;
rndis_control_intf.bInterfaceSubClass = 0x01;
rndis_control_intf.bInterfaceProtocol = 0x03;
}
return rndis_bind_config_vendor(c, rndis->ethaddr, rndis->vendorID,
rndis->manufacturer);
}
static void rndis_function_unbind_config(struct ccg_usb_function *f,
struct usb_configuration *c)
{
gether_cleanup();
}
static ssize_t rndis_manufacturer_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ccg_usb_function *f = dev_get_drvdata(dev);
struct rndis_function_config *config = f->config;
return sprintf(buf, "%s\n", config->manufacturer);
}
static ssize_t rndis_manufacturer_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct ccg_usb_function *f = dev_get_drvdata(dev);
struct rndis_function_config *config = f->config;
if (size >= sizeof(config->manufacturer))
return -EINVAL;
memcpy(config->manufacturer, buf, size);
config->manufacturer[size] = 0;
return size;
}
static DEVICE_ATTR(manufacturer, S_IRUGO | S_IWUSR, rndis_manufacturer_show,
rndis_manufacturer_store);
static ssize_t rndis_wceis_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ccg_usb_function *f = dev_get_drvdata(dev);
struct rndis_function_config *config = f->config;
return sprintf(buf, "%d\n", config->wceis);
}
static ssize_t rndis_wceis_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct ccg_usb_function *f = dev_get_drvdata(dev);
struct rndis_function_config *config = f->config;
int value;
int ret;
ret = kstrtoint(buf, 10, &value);
if (ret)
return ret;
config->wceis = value;
return size;
}
static DEVICE_ATTR(wceis, S_IRUGO | S_IWUSR, rndis_wceis_show,
rndis_wceis_store);
static ssize_t rndis_ethaddr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ccg_usb_function *f = dev_get_drvdata(dev);
struct rndis_function_config *rndis = f->config;
return sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n",
rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2],
rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]);
}
static ssize_t rndis_ethaddr_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct ccg_usb_function *f = dev_get_drvdata(dev);
struct rndis_function_config *rndis = f->config;
unsigned char tmp[6];
if (sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
tmp + 0, tmp + 1, tmp + 2, tmp + 3, tmp + 4, tmp + 5) !=
ETH_ALEN)
return -EINVAL;
memcpy(rndis->ethaddr, tmp, ETH_ALEN);
return ETH_ALEN;
}
static DEVICE_ATTR(ethaddr, S_IRUGO | S_IWUSR, rndis_ethaddr_show,
rndis_ethaddr_store);
static ssize_t rndis_vendorID_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ccg_usb_function *f = dev_get_drvdata(dev);
struct rndis_function_config *config = f->config;
return sprintf(buf, "%04x\n", config->vendorID);
}
static ssize_t rndis_vendorID_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct ccg_usb_function *f = dev_get_drvdata(dev);
struct rndis_function_config *config = f->config;
int value;
int ret;
ret = kstrtou32(buf, 16, &value);
if (ret)
return ret;
config->vendorID = value;
return size;
}
static DEVICE_ATTR(vendorID, S_IRUGO | S_IWUSR, rndis_vendorID_show,
rndis_vendorID_store);
static struct device_attribute *rndis_function_attributes[] = {
&dev_attr_manufacturer,
&dev_attr_wceis,
&dev_attr_ethaddr,
&dev_attr_vendorID,
NULL
};
static struct ccg_usb_function rndis_function = {
.name = "rndis",
.init = rndis_function_init,
.cleanup = rndis_function_cleanup,
.bind_config = rndis_function_bind_config,
.unbind_config = rndis_function_unbind_config,
.attributes = rndis_function_attributes,
};
static int mass_storage_function_init(struct ccg_usb_function *f,
struct usb_composite_dev *cdev)
{
struct fsg_config fsg;
struct fsg_common *common;
int err;
memset(&fsg, 0, sizeof fsg);
fsg.nluns = 1;
fsg.luns[0].removable = 1;
fsg.vendor_name = iManufacturer;
fsg.product_name = iProduct;
common = fsg_common_init(NULL, cdev, &fsg);
if (IS_ERR(common))
return PTR_ERR(common);
err = sysfs_create_link(&f->dev->kobj,
&common->luns[0].dev.kobj,
"lun");
if (err) {
fsg_common_put(common);
return err;
}
f->config = common;
return 0;
}
static void mass_storage_function_cleanup(struct ccg_usb_function *f)
{
fsg_common_put(f->config);
f->config = NULL;
}
static int mass_storage_function_bind_config(struct ccg_usb_function *f,
struct usb_configuration *c)
{
struct fsg_common *common = f->config;
return fsg_bind_config(c->cdev, c, common);
}
static struct ccg_usb_function mass_storage_function = {
.name = "mass_storage",
.init = mass_storage_function_init,
.cleanup = mass_storage_function_cleanup,
.bind_config = mass_storage_function_bind_config,
};
static struct ccg_usb_function *supported_functions[] = {
&acm_function,
&rndis_function,
&mass_storage_function,
NULL
};
static int ccg_init_functions(struct ccg_usb_function **functions,
struct usb_composite_dev *cdev)
{
struct ccg_dev *dev = _ccg_dev;
struct ccg_usb_function *f;
struct device_attribute **attrs;
struct device_attribute *attr;
int err;
int index = 0;
for (; (f = *functions++); index++) {
f->dev_name = kasprintf(GFP_KERNEL, "f_%s", f->name);
if (!f->dev_name) {
pr_err("%s: Failed to alloc name %s", __func__,
f->name);
err = -ENOMEM;
goto err_alloc;
}
f->dev = device_create(ccg_class, dev->dev,
MKDEV(0, index), f, f->dev_name);
if (IS_ERR(f->dev)) {
pr_err("%s: Failed to create dev %s", __func__,
f->dev_name);
err = PTR_ERR(f->dev);
f->dev = NULL;
goto err_create;
}
if (f->init) {
err = f->init(f, cdev);
if (err) {
pr_err("%s: Failed to init %s", __func__,
f->name);
goto err_out;
}
}
attrs = f->attributes;
if (attrs) {
while ((attr = *attrs++) && !err)
err = device_create_file(f->dev, attr);
}
if (err) {
pr_err("%s: Failed to create function %s attributes",
__func__, f->name);
goto err_uninit;
}
}
return 0;
err_uninit:
if (f->cleanup)
f->cleanup(f);
err_out:
device_destroy(ccg_class, f->dev->devt);
f->dev = NULL;
err_create:
kfree(f->dev_name);
err_alloc:
return err;
}
static void ccg_cleanup_functions(struct ccg_usb_function **functions)
{
struct ccg_usb_function *f;
while (*functions) {
f = *functions++;
if (f->dev) {
if (f->cleanup)
f->cleanup(f);
device_destroy(ccg_class, f->dev->devt);
kfree(f->dev_name);
}
}
}
static int ccg_bind_enabled_functions(struct ccg_dev *dev,
struct usb_configuration *c)
{
struct ccg_usb_function *f;
int ret;
list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
ret = f->bind_config(f, c);
if (ret) {
pr_err("%s: %s failed", __func__, f->name);
return ret;
}
}
return 0;
}
static void ccg_unbind_enabled_functions(struct ccg_dev *dev,
struct usb_configuration *c)
{
struct ccg_usb_function *f;
list_for_each_entry(f, &dev->enabled_functions, enabled_list)
if (f->unbind_config)
f->unbind_config(f, c);
}
static int ccg_enable_function(struct ccg_dev *dev, char *name)
{
struct ccg_usb_function **functions = dev->functions;
struct ccg_usb_function *f;
while ((f = *functions++)) {
if (!strcmp(name, f->name)) {
list_add_tail(&f->enabled_list,
&dev->enabled_functions);
return 0;
}
}
return -EINVAL;
}
/*-------------------------------------------------------------------------*/
/* /sys/class/ccg_usb/ccg%d/ interface */
static ssize_t
functions_show(struct device *pdev, struct device_attribute *attr, char *buf)
{
struct ccg_dev *dev = dev_get_drvdata(pdev);
struct ccg_usb_function *f;
char *buff = buf;
mutex_lock(&dev->mutex);
list_for_each_entry(f, &dev->enabled_functions, enabled_list)
buff += sprintf(buff, "%s,", f->name);
mutex_unlock(&dev->mutex);
if (buff != buf)
*(buff-1) = '\n';
return buff - buf;
}
static ssize_t
functions_store(struct device *pdev, struct device_attribute *attr,
const char *buff, size_t size)
{
struct ccg_dev *dev = dev_get_drvdata(pdev);
char *name;
char buf[256], *b;
int err;
buff = skip_spaces(buff);
if (!*buff)
return -EINVAL;
mutex_lock(&dev->mutex);
if (dev->enabled) {
mutex_unlock(&dev->mutex);
return -EBUSY;
}
INIT_LIST_HEAD(&dev->enabled_functions);
strlcpy(buf, buff, sizeof(buf));
b = strim(buf);
while (b) {
name = strsep(&b, ",");
if (name) {
err = ccg_enable_function(dev, name);
if (err)
pr_err("ccg_usb: Cannot enable '%s'", name);
}
}
mutex_unlock(&dev->mutex);
return size;
}
static ssize_t enable_show(struct device *pdev, struct device_attribute *attr,
char *buf)
{
struct ccg_dev *dev = dev_get_drvdata(pdev);
return sprintf(buf, "%d\n", dev->enabled);
}
static ssize_t enable_store(struct device *pdev, struct device_attribute *attr,
const char *buff, size_t size)
{
struct ccg_dev *dev = dev_get_drvdata(pdev);
struct usb_composite_dev *cdev = dev->cdev;
int enabled = 0;
mutex_lock(&dev->mutex);
sscanf(buff, "%d", &enabled);
if (enabled && !dev->enabled) {
int ret;
cdev->next_string_id = 0;
/*
* Update values in composite driver's copy of
* device descriptor.
*/
cdev->desc.bDeviceClass = device_desc.bDeviceClass;
cdev->desc.bDeviceSubClass = device_desc.bDeviceSubClass;
cdev->desc.bDeviceProtocol = device_desc.bDeviceProtocol;
cdev->desc.idVendor = idVendor;
cdev->desc.idProduct = idProduct;
cdev->desc.bcdDevice = bcdDevice;
usb_add_config(cdev, &ccg_config_driver, ccg_bind_config);
dev->enabled = true;
ret = usb_gadget_connect(cdev->gadget);
if (ret) {
dev->enabled = false;
usb_remove_config(cdev, &ccg_config_driver);
}
} else if (!enabled && dev->enabled) {
usb_gadget_disconnect(cdev->gadget);
/* Cancel pending control requests */
usb_ep_dequeue(cdev->gadget->ep0, cdev->req);
usb_remove_config(cdev, &ccg_config_driver);
dev->enabled = false;
} else {
pr_err("ccg_usb: already %s\n",
dev->enabled ? "enabled" : "disabled");
}
mutex_unlock(&dev->mutex);
return size;
}
static ssize_t state_show(struct device *pdev, struct device_attribute *attr,
char *buf)
{
struct ccg_dev *dev = dev_get_drvdata(pdev);
struct usb_composite_dev *cdev = dev->cdev;
char *state = "DISCONNECTED";
unsigned long flags;
if (!cdev)
goto out;
spin_lock_irqsave(&cdev->lock, flags);
if (cdev->config)
state = "CONFIGURED";
else if (dev->connected)
state = "CONNECTED";
spin_unlock_irqrestore(&cdev->lock, flags);
out:
return sprintf(buf, "%s\n", state);
}
#define DESCRIPTOR_ATTR(field, format_string) \
static ssize_t \
field ## _show(struct device *dev, struct device_attribute *attr, \
char *buf) \
{ \
return sprintf(buf, format_string, device_desc.field); \
} \
static ssize_t \
field ## _store(struct device *dev, struct device_attribute *attr, \
const char *buf, size_t size) \
{ \
int value; \
if (sscanf(buf, format_string, &value) == 1) { \
device_desc.field = value; \
return size; \
} \
return -1; \
} \
static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store);
DESCRIPTOR_ATTR(bDeviceClass, "%d\n")
DESCRIPTOR_ATTR(bDeviceSubClass, "%d\n")
DESCRIPTOR_ATTR(bDeviceProtocol, "%d\n")
static DEVICE_ATTR(functions, S_IRUGO | S_IWUSR, functions_show,
functions_store);
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store);
static DEVICE_ATTR(state, S_IRUGO, state_show, NULL);
static struct device_attribute *ccg_usb_attributes[] = {
&dev_attr_bDeviceClass,
&dev_attr_bDeviceSubClass,
&dev_attr_bDeviceProtocol,
&dev_attr_functions,
&dev_attr_enable,
&dev_attr_state,
NULL
};
/*-------------------------------------------------------------------------*/
/* Composite driver */
static int ccg_bind_config(struct usb_configuration *c)
{
struct ccg_dev *dev = _ccg_dev;
int ret = 0;
ret = ccg_bind_enabled_functions(dev, c);
if (ret)
return ret;
return 0;
}
static void ccg_unbind_config(struct usb_configuration *c)
{
struct ccg_dev *dev = _ccg_dev;
ccg_unbind_enabled_functions(dev, c);
usb_ep_autoconfig_reset(dev->cdev->gadget);
}
static int ccg_bind(struct usb_composite_dev *cdev)
{
struct ccg_dev *dev = _ccg_dev;
struct usb_gadget *gadget = cdev->gadget;
int gcnum, ret;
/*
* Start disconnected. Userspace will connect the gadget once
* it is done configuring the functions.
*/
usb_gadget_disconnect(gadget);
ret = ccg_init_functions(dev->functions, cdev);
if (ret)
return ret;
gcnum = usb_gadget_controller_number(gadget);
if (gcnum >= 0)
device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum);
else {
pr_warning("%s: controller '%s' not recognized\n",
longname, gadget->name);
device_desc.bcdDevice = __constant_cpu_to_le16(0x9999);
}
usb_gadget_set_selfpowered(gadget);
dev->cdev = cdev;
return 0;
}
static int ccg_usb_unbind(struct usb_composite_dev *cdev)
{
struct ccg_dev *dev = _ccg_dev;
cancel_work_sync(&dev->work);
ccg_cleanup_functions(dev->functions);
return 0;
}
static struct usb_composite_driver ccg_usb_driver = {
.name = "configurable_usb",
.dev = &device_desc,
.unbind = ccg_usb_unbind,
.needs_serial = true,
.iManufacturer = "Linux Foundation",
.iProduct = longname,
.iSerialNumber = "1234567890123456",
};
static int ccg_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *c)
{
struct ccg_dev *dev = _ccg_dev;
struct usb_composite_dev *cdev = get_gadget_data(gadget);
struct usb_request *req = cdev->req;
struct ccg_usb_function *f;
int value = -EOPNOTSUPP;
unsigned long flags;
req->zero = 0;
req->complete = composite_setup_complete;
req->length = 0;
gadget->ep0->driver_data = cdev;
list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
if (f->ctrlrequest) {
value = f->ctrlrequest(f, cdev, c);
if (value >= 0)
break;
}
}
if (value < 0)
value = composite_setup(gadget, c);
spin_lock_irqsave(&cdev->lock, flags);
if (!dev->connected) {
dev->connected = 1;
schedule_work(&dev->work);
} else if (c->bRequest == USB_REQ_SET_CONFIGURATION &&
cdev->config) {
schedule_work(&dev->work);
}
spin_unlock_irqrestore(&cdev->lock, flags);
return value;
}
static void ccg_disconnect(struct usb_gadget *gadget)
{
struct ccg_dev *dev = _ccg_dev;
struct usb_composite_dev *cdev = get_gadget_data(gadget);
unsigned long flags;
composite_disconnect(gadget);
spin_lock_irqsave(&cdev->lock, flags);
dev->connected = 0;
schedule_work(&dev->work);
spin_unlock_irqrestore(&cdev->lock, flags);
}
static int ccg_create_device(struct ccg_dev *dev)
{
struct device_attribute **attrs = ccg_usb_attributes;
struct device_attribute *attr;
int err;
dev->dev = device_create(ccg_class, NULL, MKDEV(0, 0), NULL, "ccg0");
if (IS_ERR(dev->dev))
return PTR_ERR(dev->dev);
dev_set_drvdata(dev->dev, dev);
while ((attr = *attrs++)) {
err = device_create_file(dev->dev, attr);
if (err) {
device_destroy(ccg_class, dev->dev->devt);
return err;
}
}
return 0;
}
static int __init init(void)
{
struct ccg_dev *dev;
int err;
ccg_class = class_create(THIS_MODULE, "ccg_usb");
if (IS_ERR(ccg_class))
return PTR_ERR(ccg_class);
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->functions = supported_functions;
INIT_LIST_HEAD(&dev->enabled_functions);
INIT_WORK(&dev->work, ccg_work);
mutex_init(&dev->mutex);
err = ccg_create_device(dev);
if (err) {
class_destroy(ccg_class);
kfree(dev);
return err;
}
_ccg_dev = dev;
/* Override composite driver functions */
composite_driver.setup = ccg_setup;
composite_driver.disconnect = ccg_disconnect;
err = usb_composite_probe(&ccg_usb_driver, ccg_bind);
if (err) {
class_destroy(ccg_class);
kfree(dev);
}
return err;
}
module_init(init);
static void __exit cleanup(void)
{
usb_composite_unregister(&ccg_usb_driver);
class_destroy(ccg_class);
kfree(_ccg_dev);
_ccg_dev = NULL;
}
module_exit(cleanup);

Просмотреть файл

@ -0,0 +1,128 @@
What: /sys/class/ccg_usb
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
The ccg_usb/ class subdirectory belongs to ccg
USB gadget.
What: /sys/class/ccg_usb/ccgX
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
The /sys/class/ccg_usb/ccg{0,1,2,3...} class
subdirectories correspond to each ccg gadget device;
at the time of this writing there is only ccg0 and it
represents the ccg gadget.
What: /sys/class/ccg_usb/ccgX/functions
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
A comma-separated list of USB function names to be activated
in this ccg gadget.
What: /sys/class/ccg_usb/ccgX/enable
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
A flag activating/deactivating the ccg usb gadget.
What: /sys/class/ccg_usb/ccgX/state
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
Configurable usb gadget state:
DISCONNECTED
CONNECTED
CONFIGURED
What: /sys/class/ccg_usb/ccgX/f_acm/
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
The /sys/class/ccg_usb/ccgX/f_acm subdirectory
corresponds to the gadget's USB CDC serial (ACM) function
driver.
What: /sys/class/ccg_usb/ccgX/f_acm/instances
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
Maximum number of the /dev/ttyGS<X> interface the driver uses.
What: /sys/class/ccg_usb/ccgX/f_rndis
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
The /sys/class/ccg_usb/ccgX/f_rndis subdirectory
corresponds to the gadget's RNDIS driver.
What: /sys/class/ccg_usb/ccgX/f_rndis/manufacturer
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
RNDIS Ethernet port manufacturer string.
What: /sys/class/ccg_usb/ccgX/f_rndis/wceis
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
RNDIS Ethernet port wireless flag.
What: /sys/class/ccg_usb/ccgX/f_rndis/ethaddr
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
RNDIS Ethernet port Ethernet address.
What: /sys/class/ccg_usb/ccgX/f_rndis/vendorID
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
RNDIS Ethernet port vendor ID.
What: /sys/class/ccg_usb/ccgX/f_mass_storage
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
The /sys/class/ccg_usb/ccgX/f_mass_storage subdirectory
corresponds to the gadget's USB mass storage driver.
What: /sys/class/ccg_usb/ccgX/f_mass_storage/lun
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
The /sys/class/ccg_usb/ccgX/f_mass_storage/lun
subdirectory corresponds to the gadget's USB mass storage
driver and its underlying storage.
What: /sys/class/ccg_usb/ccgX/f_mass_storage/lun
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
The /sys/class/ccg_usb/ccgX/f_mass_storage/lun
subdirectory corresponds to the gadget's USB mass storage
driver and its underlying storage.
What: /sys/class/ccg_usb/ccgX/f_mass_storage/lun/file
Date: May 2012
KernelVersion: 3.4
Contact: linux-usb@vger.kernel.org
Description:
Gadget's USB mass storage underlying file.