diff options
Diffstat (limited to 'drivers/usb/gadget')
-rw-r--r-- | drivers/usb/gadget/Makefile | 2 | ||||
-rw-r--r-- | drivers/usb/gadget/android.c | 220 | ||||
-rw-r--r-- | drivers/usb/gadget/composite.c | 23 | ||||
-rw-r--r-- | drivers/usb/gadget/exynos_ss_udc.c | 2 | ||||
-rw-r--r-- | drivers/usb/gadget/exynos_ss_udc.h | 2 | ||||
-rw-r--r-- | drivers/usb/gadget/f_accessory.c | 404 | ||||
-rw-r--r-- | drivers/usb/gadget/f_adb.c | 14 | ||||
-rw-r--r-- | drivers/usb/gadget/f_audio_source.c | 825 | ||||
-rw-r--r-- | drivers/usb/gadget/f_diag.c | 764 | ||||
-rw-r--r-- | drivers/usb/gadget/f_diag.h | 24 | ||||
-rw-r--r-- | drivers/usb/gadget/f_mass_storage.c | 364 | ||||
-rw-r--r-- | drivers/usb/gadget/f_mtp.h | 17 | ||||
-rw-r--r-- | drivers/usb/gadget/f_mtp_samsung.c | 181 | ||||
-rw-r--r-- | drivers/usb/gadget/multi_config.c | 10 | ||||
-rw-r--r-- | drivers/usb/gadget/multi_config.h | 7 | ||||
-rw-r--r-- | drivers/usb/gadget/s3c_udc.h | 4 | ||||
-rw-r--r-- | drivers/usb/gadget/s3c_udc_otg.c | 66 | ||||
-rw-r--r-- | drivers/usb/gadget/s3c_udc_otg_xfer_dma.c | 6 | ||||
-rw-r--r-- | drivers/usb/gadget/storage_common.c | 5 | ||||
-rw-r--r-- | drivers/usb/gadget/u_composite_notifier.c | 40 |
20 files changed, 2910 insertions, 70 deletions
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index bd8b874..cc02362 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -52,6 +52,7 @@ g_nokia-y := nokia.o g_webcam-y := webcam.o g_ncm-y := ncm.o g_android-y := android.o +g_slp-y := slp.o obj-$(CONFIG_USB_ZERO) += g_zero.o obj-$(CONFIG_USB_AUDIO) += g_audio.o @@ -71,6 +72,7 @@ obj-$(CONFIG_USB_G_NOKIA) += g_nokia.o obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o obj-$(CONFIG_USB_G_NCM) += g_ncm.o obj-$(CONFIG_USB_G_ANDROID) += g_android.o +obj-$(CONFIG_USB_G_SLP) += g_slp.o obj-$(CONFIG_USB_ANDROID) += gadget_gbhc/android.o obj-$(CONFIG_USB_ANDROID_ACM) += gadget_gbhc/f_acm.o gadget_gbhc/u_serial.o obj-$(CONFIG_USB_ANDROID_ADB) += gadget_gbhc/f_adb.o diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c index 699cf29..c528f4b 100644 --- a/drivers/usb/gadget/android.c +++ b/drivers/usb/gadget/android.c @@ -45,6 +45,7 @@ #include "epautoconf.c" #include "composite.c" +#include "f_audio_source.c" #include "f_mass_storage.c" #include "u_serial.c" #ifdef CONFIG_USB_DUN_SUPPORT @@ -61,6 +62,7 @@ #define USB_ETH_RNDIS y #include "f_rndis.c" #include "rndis.c" +#include "f_diag.c" #include "f_dm.c" MODULE_AUTHOR("Mike Lockwood"); @@ -95,10 +97,11 @@ struct android_usb_function { int (*init)(struct android_usb_function *, struct usb_composite_dev *); /* Optional: cleanup during gadget unbind */ void (*cleanup)(struct android_usb_function *); - /* Optional: called when the function is added the list of enabled functions */ - void (*enable)(struct android_usb_function *); - /* Optional: called when it is removed */ - void (*disable)(struct android_usb_function *); + /* Optional: called when the function is added the list of + * enabled functions */ + void (*enable)(struct android_usb_function *); + /* Optional: called when it is removed */ + void (*disable)(struct android_usb_function *); int (*bind_config)(struct android_usb_function *, struct usb_configuration *); @@ -117,7 +120,7 @@ struct android_dev { struct device *dev; bool enabled; - int disable_depth; + int disable_depth; struct mutex mutex; bool connected; bool sw_connected; @@ -142,6 +145,7 @@ static char serial_string[256]; #ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE #include "u_ncm.c" +#include "u_composite_notifier.c" #endif #include "u_ether.c" @@ -207,6 +211,14 @@ static void android_work(struct work_struct *data) kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_envp); printk(KERN_DEBUG "usb: %s sent uevent %s\n", __func__, uevent_envp[0]); +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (uevent_envp != connected) + blocking_notifier_call_chain( + &usb_composite_notifier_list, + (unsigned long)dev->connected, + (void *)function_lists_string); +#endif + } else { printk(KERN_DEBUG "usb: %s did not send uevent (%d %d %p)\n", __func__, dev->connected, dev->sw_connected, cdev->config); @@ -217,8 +229,8 @@ static void android_enable(struct android_dev *dev) { struct usb_composite_dev *cdev = dev->cdev; - if (WARN_ON(!dev->disable_depth)) - return; + BUG_ON(!mutex_is_locked(&dev->mutex)); + BUG_ON(!dev->disable_depth); if (--dev->disable_depth == 0) { usb_add_config(cdev, &android_config_driver, @@ -231,6 +243,8 @@ static void android_disable(struct android_dev *dev) { struct usb_composite_dev *cdev = dev->cdev; + BUG_ON(!mutex_is_locked(&dev->mutex)); + if (dev->disable_depth++ == 0) { usb_gadget_disconnect(cdev->gadget); /* Cancel pending control requests */ @@ -249,9 +263,9 @@ struct adb_data { static int adb_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) { - f->config = kzalloc(sizeof(struct adb_data), GFP_KERNEL); - if (!f->config) - return -ENOMEM; + f->config = kzalloc(sizeof(struct adb_data), GFP_KERNEL); + if (!f->config) + return -ENOMEM; return adb_setup(); } @@ -259,7 +273,7 @@ static int adb_function_init(struct android_usb_function *f, struct usb_composit static void adb_function_cleanup(struct android_usb_function *f) { adb_cleanup(); - kfree(f->config); + kfree(f->config); } static int adb_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) @@ -678,6 +692,7 @@ static int mass_storage_function_init(struct android_usb_function *f, int err; #ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE int i; + unsigned int cdfs = 0; #endif config = kzalloc(sizeof(struct mass_storage_function_config), GFP_KERNEL); @@ -685,26 +700,28 @@ static int mass_storage_function_init(struct android_usb_function *f, return -ENOMEM; #ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE - if (android_usb_pdata && android_usb_pdata->nluns != 0) { + if (android_usb_pdata && (android_usb_pdata->nluns != 0 + || android_usb_pdata->cdfs_support != 0)) { /* Some device use sd card or not. * If you want to modify nluns, * please change nluns of standard android USB platform data * Please do not modify nluns directly in this function. * Every model uses same android file. */ - printk(KERN_DEBUG "usb: %s pdata->nluns=%d\n", __func__, - android_usb_pdata->nluns); - config->fsg.nluns = android_usb_pdata->nluns; + printk(KERN_DEBUG "usb: %s pdata->nluns=%d, cdfs = %d\n", + __func__, android_usb_pdata->nluns, + android_usb_pdata->cdfs_support); + cdfs = android_usb_pdata->cdfs_support; + config->fsg.nluns = android_usb_pdata->nluns + cdfs; + for (i = 0; i < android_usb_pdata->nluns; i++) { -#if defined(CONFIG_MACH_M0_CTC) - /*FOR CTC PC-MODEM START*/ - if (2 == i) { - printk(KERN_DEBUG "%s Enable cdfs\n", __func__); - config->fsg.luns[i].ro = 1; - config->fsg.luns[i].cdrom = 1; - } - /*FOR CTC PC-MODEM END*/ -#endif + + config->fsg.luns[i].removable = 1; + config->fsg.luns[i].nofua = 1; + } + + if (cdfs) { + config->fsg.luns[i].cdrom = 1; config->fsg.luns[i].removable = 1; config->fsg.luns[i].nofua = 1; } @@ -732,6 +749,23 @@ static int mass_storage_function_init(struct android_usb_function *f, return err; } } + if (cdfs) { + char luns[4]; + err = snprintf(luns, 4, "lun"); + if (err == 0) { + printk(KERN_ERR "usb: %s cdfs snprintf error\n", + __func__); + kfree(config); + return err; + } + err = sysfs_create_link(&f->dev->kobj, + &common->luns[i].dev.kobj, + luns); + if (err) { + kfree(config); + return err; + } + } } else { #endif /* original mainline code */ @@ -906,6 +940,63 @@ static struct android_usb_function accessory_function = { .ctrlrequest = accessory_function_ctrlrequest, }; +/* DIAG : enabled DIAG clients- "diag[,diag_mdm]" */ +static char diag_clients[32]; +static ssize_t clients_store( + struct device *device, struct device_attribute *attr, + const char *buff, size_t size) +{ + strlcpy(diag_clients, buff, sizeof(diag_clients)); + + return size; +} + +static DEVICE_ATTR(clients, S_IWUSR, NULL, clients_store); +static struct device_attribute *diag_function_attributes[] = { + &dev_attr_clients, NULL }; + +static int diag_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return diag_setup(); +} + +static void diag_function_cleanup(struct android_usb_function *f) +{ + diag_cleanup(); +} + +static int diag_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + char *name; + char buf[32], *b; + int once = 0, err = -1; + int (*notify)(uint32_t, const char *) = NULL; + + strlcpy(buf, diag_clients, sizeof(buf)); + b = strim(buf); + while (b) { + notify = NULL; + name = strsep(&b, ","); + + if (name) { + err = diag_function_add(c, name, notify); + if (err) + pr_err("%s : usb: diag: Cannot open channel '%s\r\n", + __func__, name); + } + } + return err; +} + +static struct android_usb_function diag_function = { + .name = "diag", + .init = diag_function_init, + .cleanup = diag_function_cleanup, + .bind_config = diag_function_bind_config, + .attributes = diag_function_attributes, +}; static int dm_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) @@ -918,6 +1009,68 @@ static struct android_usb_function dm_function = { .bind_config = dm_function_bind_config, }; +static int audio_source_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + struct audio_source_config *config; + + config = kzalloc(sizeof(struct audio_source_config), GFP_KERNEL); + if (!config) + return -ENOMEM; + config->card = -1; + config->device = -1; + f->config = config; + return 0; +} + +static void audio_source_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); +} + +static int audio_source_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct audio_source_config *config = f->config; + + return audio_source_bind_config(c, config); +} + +static void audio_source_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct audio_source_config *config = f->config; + + config->card = -1; + config->device = -1; +} + +static ssize_t audio_source_pcm_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct audio_source_config *config = f->config; + + /* print PCM card and device numbers */ + return sprintf(buf, "%d %d\n", config->card, config->device); +} + +static DEVICE_ATTR(pcm, S_IRUGO | S_IWUSR, audio_source_pcm_show, NULL); + +static struct device_attribute *audio_source_function_attributes[] = { + &dev_attr_pcm, + NULL +}; + +static struct android_usb_function audio_source_function = { + .name = "audio_source", + .init = audio_source_function_init, + .cleanup = audio_source_function_cleanup, + .bind_config = audio_source_function_bind_config, + .unbind_config = audio_source_function_unbind_config, + .attributes = audio_source_function_attributes, +}; + static struct android_usb_function *supported_functions[] = { &adb_function, &acm_function, @@ -929,7 +1082,9 @@ static struct android_usb_function *supported_functions[] = { #endif &mass_storage_function, &accessory_function, + &diag_function, &dm_function, + &audio_source_function, NULL }; @@ -1092,6 +1247,9 @@ functions_store(struct device *pdev, struct device_attribute *attr, printk(KERN_DEBUG "usb: %s buff=%s\n", __func__, buff); strncpy(buf, buff, sizeof(buf)); b = strim(buf); +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + strncpy(function_lists_string, b, sizeof(buf)); +#endif while (b) { name = strsep(&b, ","); @@ -1140,7 +1298,7 @@ static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, { struct android_dev *dev = dev_get_drvdata(pdev); struct usb_composite_dev *cdev = dev->cdev; - struct android_usb_function *f; + struct android_usb_function *f; int enabled = 0; mutex_lock(&dev->mutex); @@ -1273,10 +1431,7 @@ field ## _store(struct device *dev, struct device_attribute *attr, \ const char *buf, size_t size) \ { \ if (size >= sizeof(buffer)) return -EINVAL; \ - if (sscanf(buf, "%s", buffer) == 1) { \ - return size; \ - } \ - return -1; \ + return strlcpy(buffer, buf, sizeof(buffer)); \ } \ static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); @@ -1480,6 +1635,11 @@ static void android_disconnect(struct usb_gadget *gadget) unsigned long flags; composite_disconnect(gadget); + /* accessory HID support can be active while the + accessory function is not actually enabled, + so we need to inform it when we are disconnected. + */ + acc_disconnect(); spin_lock_irqsave(&cdev->lock, flags); dev->connected = 0; @@ -1570,7 +1730,7 @@ static int __init init(void) if (!dev) return -ENOMEM; - dev->disable_depth = 1; + dev->disable_depth = 1; dev->functions = supported_functions; INIT_LIST_HEAD(&dev->enabled_functions); INIT_WORK(&dev->work, android_work); diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 274facf..5f459005 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -678,6 +678,19 @@ int usb_add_config(struct usb_composite_dev *cdev, status = bind(config); if (status < 0) { + while (!list_empty(&config->functions)) { + struct usb_function *f; + + f = list_first_entry(&config->functions, + struct usb_function, list); + list_del(&f->list); + if (f->unbind) { + DBG(cdev, "unbind function '%s'/%p\n", + f->name, f); + f->unbind(config, f); + /* may free memory for "f" */ + } + } list_del(&config->list); config->cdev = NULL; } else { @@ -715,7 +728,7 @@ done: return status; } -static int remove_config(struct usb_composite_dev *cdev, +static int unbind_config(struct usb_composite_dev *cdev, struct usb_configuration *config) { while (!list_empty(&config->functions)) { @@ -730,7 +743,6 @@ static int remove_config(struct usb_composite_dev *cdev, /* may free memory for "f" */ } } - list_del(&config->list); if (config->unbind) { DBG(cdev, "unbind config '%s'/%p\n", config->label, config); config->unbind(config); @@ -751,9 +763,11 @@ int usb_remove_config(struct usb_composite_dev *cdev, if (cdev->config == config) reset_config(cdev); + list_del(&config->list); + spin_unlock_irqrestore(&cdev->lock, flags); - return remove_config(cdev, config); + return unbind_config(cdev, config); } /*-------------------------------------------------------------------------*/ @@ -1340,7 +1354,8 @@ composite_unbind(struct usb_gadget *gadget) struct usb_configuration *c; c = list_first_entry(&cdev->configs, struct usb_configuration, list); - remove_config(cdev, c); + list_del(&c->list); + unbind_config(cdev, c); } if (composite->unbind) composite->unbind(cdev); diff --git a/drivers/usb/gadget/exynos_ss_udc.c b/drivers/usb/gadget/exynos_ss_udc.c index c8001e8..9d24396 100644 --- a/drivers/usb/gadget/exynos_ss_udc.c +++ b/drivers/usb/gadget/exynos_ss_udc.c @@ -255,7 +255,7 @@ static int exynos_ss_udc_issue_epcmd(struct exynos_ss_udc *udc, return res; } -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) void exynos_ss_udc_cable_connect(struct exynos_ss_udc *udc, bool connect) { static int last_connect; diff --git a/drivers/usb/gadget/exynos_ss_udc.h b/drivers/usb/gadget/exynos_ss_udc.h index a413a44..b726bcc 100644 --- a/drivers/usb/gadget/exynos_ss_udc.h +++ b/drivers/usb/gadget/exynos_ss_udc.h @@ -424,7 +424,7 @@ struct exynos_ss_udc { struct wake_lock usbd_wake_lock; }; -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) extern void samsung_cable_check_status(int flag); #endif diff --git a/drivers/usb/gadget/f_accessory.c b/drivers/usb/gadget/f_accessory.c index dfe3e51..6dd53c3 100644 --- a/drivers/usb/gadget/f_accessory.c +++ b/drivers/usb/gadget/f_accessory.c @@ -33,6 +33,8 @@ #include <linux/device.h> #include <linux/miscdevice.h> +#include <linux/hid.h> +#include <linux/hiddev.h> #include <linux/usb.h> #include <linux/usb/ch9.h> #include <linux/usb/f_accessory.h> @@ -40,7 +42,7 @@ #define BULK_BUFFER_SIZE 16384 #define ACC_STRING_SIZE 256 -#define PROTOCOL_VERSION 1 +#define PROTOCOL_VERSION 2 /* String IDs */ #define INTERFACE_STRING_INDEX 0 @@ -49,6 +51,20 @@ #define TX_REQ_MAX 4 #define RX_REQ_MAX 2 +struct acc_hid_dev { + struct list_head list; + struct hid_device *hid; + struct acc_dev *dev; + /* accessory defined ID */ + int id; + /* HID report descriptor */ + u8 *report_desc; + /* length of HID report descriptor */ + int report_desc_len; + /* number of bytes of report_desc we have received so far */ + int report_desc_offset; +}; + struct acc_dev { struct usb_function function; struct usb_composite_dev *cdev; @@ -78,6 +94,8 @@ struct acc_dev { /* set to 1 if we have a pending start request */ int start_requested; + int audio_mode; + /* synchronize access to our device file */ atomic_t open_excl; @@ -87,7 +105,21 @@ struct acc_dev { wait_queue_head_t write_wq; struct usb_request *rx_req[RX_REQ_MAX]; int rx_done; - struct delayed_work work; + + /* delayed work for handling ACCESSORY_START */ + struct delayed_work start_work; + + /* worker for registering and unregistering hid devices */ + struct work_struct hid_work; + + /* list of active HID devices */ + struct list_head hid_list; + + /* list of new HID devices to register */ + struct list_head new_hid_list; + + /* list of dead HID devices to unregister */ + struct list_head dead_hid_list; }; static struct usb_interface_descriptor acc_interface_desc = { @@ -296,7 +328,161 @@ static void acc_complete_set_string(struct usb_ep *ep, struct usb_request *req) } } -static int create_bulk_endpoints(struct acc_dev *dev, +static void acc_complete_set_hid_report_desc(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + struct acc_dev *dev = hid->dev; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_set_hid_report_desc, err %d\n", + req->status); + return; + } + + memcpy(hid->report_desc + hid->report_desc_offset, req->buf, length); + hid->report_desc_offset += length; + if (hid->report_desc_offset == hid->report_desc_len) { + /* After we have received the entire report descriptor + * we schedule work to initialize the HID device + */ + schedule_work(&dev->hid_work); + } +} + +static void acc_complete_send_hid_event(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_send_hid_event, err %d\n", req->status); + return; + } + + hid_report_raw_event(hid->hid, HID_INPUT_REPORT, req->buf, length, 1); +} + +static int acc_hid_parse(struct hid_device *hid) +{ + struct acc_hid_dev *hdev = hid->driver_data; + + hid_parse_report(hid, hdev->report_desc, hdev->report_desc_len); + return 0; +} + +static int acc_hid_start(struct hid_device *hid) +{ + return 0; +} + +static void acc_hid_stop(struct hid_device *hid) +{ +} + +static int acc_hid_open(struct hid_device *hid) +{ + return 0; +} + +static void acc_hid_close(struct hid_device *hid) +{ +} + +static struct hid_ll_driver acc_hid_ll_driver = { + .parse = acc_hid_parse, + .start = acc_hid_start, + .stop = acc_hid_stop, + .open = acc_hid_open, + .close = acc_hid_close, +}; + +static struct acc_hid_dev *acc_hid_new(struct acc_dev *dev, + int id, int desc_len) +{ + struct acc_hid_dev *hdev; + + hdev = kzalloc(sizeof(*hdev), GFP_ATOMIC); + if (!hdev) + return NULL; + hdev->report_desc = kzalloc(desc_len, GFP_ATOMIC); + if (!hdev->report_desc) { + kfree(hdev); + return NULL; + } + hdev->dev = dev; + hdev->id = id; + hdev->report_desc_len = desc_len; + + return hdev; +} + +static struct acc_hid_dev *acc_hid_get(struct list_head *list, int id) +{ + struct acc_hid_dev *hid; + + list_for_each_entry(hid, list, list) { + if (hid->id == id) + return hid; + } + return NULL; +} + +static int acc_register_hid(struct acc_dev *dev, int id, int desc_length) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + /* report descriptor length must be > 0 */ + if (desc_length <= 0) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + /* replace HID if one already exists with this ID */ + hid = acc_hid_get(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get(&dev->new_hid_list, id); + if (hid) + list_move(&hid->list, &dev->dead_hid_list); + + hid = acc_hid_new(dev, id, desc_length); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -ENOMEM; + } + + list_add(&hid->list, &dev->new_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + /* schedule work to register the HID device */ + schedule_work(&dev->hid_work); + return 0; +} + +static int acc_unregister_hid(struct acc_dev *dev, int id) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get(&dev->new_hid_list, id); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -EINVAL; + } + + list_move(&hid->list, &dev->dead_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); + return 0; +} + +static int __init create_bulk_endpoints(struct acc_dev *dev, struct usb_endpoint_descriptor *in_desc, struct usb_endpoint_descriptor *out_desc) { @@ -353,7 +539,7 @@ static int create_bulk_endpoints(struct acc_dev *dev, return 0; fail: - printk(KERN_ERR "acc_bind() could not allocate requests\n"); + pr_err("acc_bind() could not allocate requests\n"); while ((req = req_get(dev, &dev->tx_idle))) acc_request_free(req, dev->ep_in); for (i = 0; i < RX_REQ_MAX; i++) @@ -510,6 +696,8 @@ static long acc_ioctl(struct file *fp, unsigned code, unsigned long value) break; case ACCESSORY_IS_START_REQUESTED: return dev->start_requested; + case ACCESSORY_GET_AUDIO_MODE: + return dev->audio_mode; } if (!src) return -EINVAL; @@ -540,7 +728,7 @@ static int acc_release(struct inode *ip, struct file *fp) return 0; } -/* file operations for /dev/acc_usb */ +/* file operations for /dev/usb_accessory */ static const struct file_operations acc_fops = { .owner = THIS_MODULE, .read = acc_read, @@ -550,23 +738,47 @@ static const struct file_operations acc_fops = { .release = acc_release, }; +static int acc_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hdev); + if (ret) + return ret; + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); +} + static struct miscdevice acc_device = { .minor = MISC_DYNAMIC_MINOR, .name = "usb_accessory", .fops = &acc_fops, }; +static const struct hid_device_id acc_hid_table[] = { + { HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) }, + { } +}; + +static struct hid_driver acc_hid_driver = { + .name = "USB accessory", + .id_table = acc_hid_table, + .probe = acc_hid_probe, +}; static int acc_ctrlrequest(struct usb_composite_dev *cdev, const struct usb_ctrlrequest *ctrl) { struct acc_dev *dev = _acc_dev; int value = -EOPNOTSUPP; + struct acc_hid_dev *hid; + int offset; u8 b_requestType = ctrl->bRequestType; u8 b_request = ctrl->bRequest; u16 w_index = le16_to_cpu(ctrl->wIndex); u16 w_value = le16_to_cpu(ctrl->wValue); u16 w_length = le16_to_cpu(ctrl->wLength); + unsigned long flags; /* printk(KERN_INFO "acc_ctrlrequest " @@ -579,20 +791,56 @@ static int acc_ctrlrequest(struct usb_composite_dev *cdev, if (b_request == ACCESSORY_START) { dev->start_requested = 1; schedule_delayed_work( - &dev->work, msecs_to_jiffies(5)); + &dev->start_work, msecs_to_jiffies(5)); value = 0; } else if (b_request == ACCESSORY_SEND_STRING) { dev->string_index = w_index; cdev->gadget->ep0->driver_data = dev; cdev->req->complete = acc_complete_set_string; value = w_length; + } else if (b_request == ACCESSORY_SET_AUDIO_MODE && + w_index == 0 && w_length == 0) { + dev->audio_mode = w_value; + value = 0; + } else if (b_request == ACCESSORY_REGISTER_HID) { + value = acc_register_hid(dev, w_value, w_index); + } else if (b_request == ACCESSORY_UNREGISTER_HID) { + value = acc_unregister_hid(dev, w_value); + } else if (b_request == ACCESSORY_SET_HID_REPORT_DESC) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->new_hid_list, w_value); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + offset = w_index; + if (offset != hid->report_desc_offset + || offset + w_length > hid->report_desc_len) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_set_hid_report_desc; + value = w_length; + } else if (b_request == ACCESSORY_SEND_HID_EVENT) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->hid_list, w_value); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_send_hid_event; + value = w_length; } } else if (b_requestType == (USB_DIR_IN | USB_TYPE_VENDOR)) { if (b_request == ACCESSORY_GET_PROTOCOL) { *((u16 *)cdev->req->buf) = PROTOCOL_VERSION; value = sizeof(u16); - /* clear any strings left over from a previous session */ + /* clear strings left over from a previous session */ memset(dev->manufacturer, 0, sizeof(dev->manufacturer)); memset(dev->model, 0, sizeof(dev->model)); memset(dev->description, 0, sizeof(dev->description)); @@ -600,6 +848,7 @@ static int acc_ctrlrequest(struct usb_composite_dev *cdev, memset(dev->uri, 0, sizeof(dev->uri)); memset(dev->serial, 0, sizeof(dev->serial)); dev->start_requested = 0; + dev->audio_mode = 0; } } @@ -612,6 +861,7 @@ static int acc_ctrlrequest(struct usb_composite_dev *cdev, __func__); } +err: if (value == -EOPNOTSUPP) VDBG(cdev, "unknown class-specific control req " @@ -631,6 +881,10 @@ acc_function_bind(struct usb_configuration *c, struct usb_function *f) DBG(cdev, "acc_function_bind dev: %p\n", dev); + ret = hid_register_driver(&acc_hid_driver); + if (ret) + return ret; + dev->start_requested = 0; /* allocate interface ID(s) */ @@ -660,6 +914,36 @@ acc_function_bind(struct usb_configuration *c, struct usb_function *f) } static void +kill_all_hid_devices(struct acc_dev *dev) +{ + struct acc_hid_dev *hid; + struct list_head *entry, *temp; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_for_each_safe(entry, temp, &dev->hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); +} + +static void +acc_hid_unbind(struct acc_dev *dev) +{ + hid_unregister_driver(&acc_hid_driver); + kill_all_hid_devices(dev); +} + +static void acc_function_unbind(struct usb_configuration *c, struct usb_function *f) { struct acc_dev *dev = func_to_dev(f); @@ -670,14 +954,104 @@ acc_function_unbind(struct usb_configuration *c, struct usb_function *f) acc_request_free(req, dev->ep_in); for (i = 0; i < RX_REQ_MAX; i++) acc_request_free(dev->rx_req[i], dev->ep_out); + + acc_hid_unbind(dev); } -static void acc_work(struct work_struct *data) +static void acc_start_work(struct work_struct *data) { char *envp[2] = { "ACCESSORY=START", NULL }; kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp); } +static int acc_hid_init(struct acc_hid_dev *hdev) +{ + struct hid_device *hid; + int ret; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + + hid->ll_driver = &acc_hid_ll_driver; + hid->dev.parent = acc_device.this_device; + + hid->bus = BUS_USB; + hid->vendor = HID_ANY_ID; + hid->product = HID_ANY_ID; + hid->driver_data = hdev; + ret = hid_add_device(hid); + if (ret) { + pr_err("can't add hid device: %d\n", ret); + hid_destroy_device(hid); + return ret; + } + + hdev->hid = hid; + return 0; +} + +static void acc_hid_delete(struct acc_hid_dev *hid) +{ + kfree(hid->report_desc); + kfree(hid); +} + +static void acc_hid_work(struct work_struct *data) +{ + struct acc_dev *dev = _acc_dev; + struct list_head *entry, *temp; + struct acc_hid_dev *hid; + struct list_head new_list, dead_list; + unsigned long flags; + + INIT_LIST_HEAD(&new_list); + + spin_lock_irqsave(&dev->lock, flags); + + /* copy hids that are ready for initialization to new_list */ + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (hid->report_desc_offset == hid->report_desc_len) + list_move(&hid->list, &new_list); + } + + if (list_empty(&dev->dead_hid_list)) { + INIT_LIST_HEAD(&dead_list); + } else { + /* move all of dev->dead_hid_list to dead_list */ + dead_list.prev = dev->dead_hid_list.prev; + dead_list.next = dev->dead_hid_list.next; + dead_list.next->prev = &dead_list; + dead_list.prev->next = &dead_list; + INIT_LIST_HEAD(&dev->dead_hid_list); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + /* register new HID devices */ + list_for_each_safe(entry, temp, &new_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (acc_hid_init(hid)) { + pr_err("can't add HID device %p\n", hid); + acc_hid_delete(hid); + } else { + spin_lock_irqsave(&dev->lock, flags); + list_move(&hid->list, &dev->hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + } + } + + /* remove dead HID devices */ + list_for_each_safe(entry, temp, &dead_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + if (hid->hid) + hid_destroy_device(hid->hid); + acc_hid_delete(hid); + } +} + static int acc_function_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { @@ -767,7 +1141,11 @@ static int acc_setup(void) init_waitqueue_head(&dev->write_wq); atomic_set(&dev->open_excl, 0); INIT_LIST_HEAD(&dev->tx_idle); - INIT_DELAYED_WORK(&dev->work, acc_work); + INIT_LIST_HEAD(&dev->hid_list); + INIT_LIST_HEAD(&dev->new_hid_list); + INIT_LIST_HEAD(&dev->dead_hid_list); + INIT_DELAYED_WORK(&dev->start_work, acc_start_work); + INIT_WORK(&dev->hid_work, acc_hid_work); /* _acc_dev must be set before calling usb_gadget_register_driver */ _acc_dev = dev; @@ -780,10 +1158,16 @@ static int acc_setup(void) err: kfree(dev); - printk(KERN_ERR "USB accessory gadget driver failed to initialize\n"); + pr_err("USB accessory gadget driver failed to initialize\n"); return ret; } +static void acc_disconnect(void) +{ + /* unregister all HID devices if USB is disconnected */ + kill_all_hid_devices(_acc_dev); +} + static void acc_cleanup(void) { misc_deregister(&acc_device); diff --git a/drivers/usb/gadget/f_adb.c b/drivers/usb/gadget/f_adb.c index c5d292c..8441e45 100644 --- a/drivers/usb/gadget/f_adb.c +++ b/drivers/usb/gadget/f_adb.c @@ -57,9 +57,6 @@ struct adb_dev { int rx_done; }; -static void adb_ready_callback(void); -static void adb_closed_callback(void); - static struct usb_interface_descriptor adb_interface_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, @@ -148,6 +145,8 @@ static struct usb_descriptor_header *ss_adb_descs[] = { NULL, }; +static void adb_ready_callback(void); +static void adb_closed_callback(void); /* temporary variable used between adb_open() and adb_gadget_bind() */ static struct adb_dev *_adb_dev; @@ -456,7 +455,7 @@ static int adb_open(struct inode *ip, struct file *fp) /* clear the error latch */ _adb_dev->error = 0; - adb_ready_callback(); + adb_ready_callback(); return 0; } @@ -464,7 +463,9 @@ static int adb_open(struct inode *ip, struct file *fp) static int adb_release(struct inode *ip, struct file *fp) { pr_info("adb_release\n"); - adb_closed_callback(); + + adb_closed_callback(); + adb_unlock(&_adb_dev->open_excl); return 0; } @@ -484,6 +485,9 @@ static struct miscdevice adb_device = { .fops = &adb_fops, }; + + + static int adb_function_bind(struct usb_configuration *c, struct usb_function *f) { diff --git a/drivers/usb/gadget/f_audio_source.c b/drivers/usb/gadget/f_audio_source.c new file mode 100644 index 0000000..23a7511 --- /dev/null +++ b/drivers/usb/gadget/f_audio_source.c @@ -0,0 +1,825 @@ +/* + * Gadget Function Driver for USB audio source device + * + * Copyright (C) 2012 Google, Inc. + * + * 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/device.h> +#include <linux/usb/audio.h> +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> + +#define SAMPLE_RATE 44100 +/* Each frame is two 16 bit integers (one per channel) */ +#define BYTES_PER_FRAME 4 +#define FRAMES_PER_MSEC (SAMPLE_RATE / 1000) + +#define IN_EP_MAX_PACKET_SIZE 256 + +/* Number of requests to allocate */ +#define IN_EP_REQ_COUNT 4 + +#define AUDIO_AC_INTERFACE 0 +#define AUDIO_AS_INTERFACE 1 +#define AUDIO_NUM_INTERFACES 2 + +/* B.3.1 Standard AC Interface Descriptor */ +static struct usb_interface_descriptor ac_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, +}; + +DECLARE_UAC_AC_HEADER_DESCRIPTOR(2); + +#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(AUDIO_NUM_INTERFACES) +/* 1 input terminal, 1 output terminal and 1 feature unit */ +#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \ + + UAC_DT_INPUT_TERMINAL_SIZE + UAC_DT_OUTPUT_TERMINAL_SIZE \ + + UAC_DT_FEATURE_UNIT_SIZE(0)) +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct uac1_ac_header_descriptor_2 ac_header_desc = { + .bLength = UAC_DT_AC_HEADER_LENGTH, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_HEADER, + .bcdADC = __constant_cpu_to_le16(0x0100), + .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), + .bInCollection = AUDIO_NUM_INTERFACES, + .baInterfaceNr = { + [0] = AUDIO_AC_INTERFACE, + [1] = AUDIO_AS_INTERFACE, + } +}; + +#define INPUT_TERMINAL_ID 1 +static struct uac_input_terminal_descriptor input_terminal_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = INPUT_TERMINAL_ID, + .wTerminalType = UAC_INPUT_TERMINAL_MICROPHONE, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, +}; + +DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0); + +#define FEATURE_UNIT_ID 2 +static struct uac_feature_unit_descriptor_0 feature_unit_desc = { + .bLength = UAC_DT_FEATURE_UNIT_SIZE(0), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FEATURE_UNIT, + .bUnitID = FEATURE_UNIT_ID, + .bSourceID = INPUT_TERMINAL_ID, + .bControlSize = 2, +}; + +#define OUTPUT_TERMINAL_ID 3 +static struct uac1_output_terminal_descriptor output_terminal_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = OUTPUT_TERMINAL_ID, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = FEATURE_UNIT_ID, + .bSourceID = FEATURE_UNIT_ID, +}; + +/* B.4.1 Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_interface_alt_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_interface_alt_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +/* B.4.2 Class-Specific AS Interface Descriptor */ +static struct uac1_as_header_descriptor as_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = INPUT_TERMINAL_ID, + .bDelay = 1, + .wFormatTag = UAC_FORMAT_TYPE_I_PCM, +}; + +DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); + +static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = { + .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO IN Endpoint Descriptor for highspeed */ +static struct usb_endpoint_descriptor hs_as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_SYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE), + .bInterval = 4, /* poll 1 per millisecond */ +}; + +/* Standard ISO IN Endpoint Descriptor for highspeed */ +static struct usb_endpoint_descriptor fs_as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_SYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE), + .bInterval = 1, /* poll 1 per millisecond */ +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_in_desc = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = __constant_cpu_to_le16(1), +}; + +static struct usb_descriptor_header *hs_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&hs_as_in_ep_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +static struct usb_descriptor_header *fs_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&fs_as_in_ep_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +static struct snd_pcm_hardware audio_hw_info = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = SAMPLE_RATE, + .rate_max = SAMPLE_RATE, + + .buffer_bytes_max = 1024 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 512 * 1024, + .periods_min = 2, + .periods_max = 1024, +}; + +/*-------------------------------------------------------------------------*/ + +struct audio_source_config { + int card; + int device; +}; + +struct audio_dev { + struct usb_function func; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + + struct list_head idle_reqs; + struct usb_ep *in_ep; + struct usb_endpoint_descriptor *in_desc; + + spinlock_t lock; + + /* beginning, end and current position in our buffer */ + void *buffer_start; + void *buffer_end; + void *buffer_pos; + + /* byte size of a "period" */ + unsigned int period; + /* bytes sent since last call to snd_pcm_period_elapsed */ + unsigned int period_offset; + /* time we started playing */ + ktime_t start_time; + /* number of frames sent since start_time */ + s64 frames_sent; +}; + +static inline struct audio_dev *func_to_audio(struct usb_function *f) +{ + return container_of(f, struct audio_dev, func); +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_request *audio_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + req->length = buffer_size; + return req; +} + +static void audio_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static void audio_req_put(struct audio_dev *audio, struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + list_add_tail(&req->list, &audio->idle_reqs); + spin_unlock_irqrestore(&audio->lock, flags); +} + +static struct usb_request *audio_req_get(struct audio_dev *audio) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&audio->lock, flags); + if (list_empty(&audio->idle_reqs)) { + req = 0; + } else { + req = list_first_entry(&audio->idle_reqs, struct usb_request, + list); + list_del(&req->list); + } + spin_unlock_irqrestore(&audio->lock, flags); + return req; +} + +/* send the appropriate number of packets to match our bitrate */ +static void audio_send(struct audio_dev *audio) +{ + struct snd_pcm_runtime *runtime; + struct usb_request *req; + int length, length1, length2, ret; + s64 msecs; + s64 frames; + ktime_t now; + + /* audio->substream will be null if we have been closed */ + if (!audio->substream) + return; + /* audio->buffer_pos will be null if we have been stopped */ + if (!audio->buffer_pos) + return; + + runtime = audio->substream->runtime; + + /* compute number of frames to send */ + now = ktime_get(); + msecs = ktime_to_ns(now) - ktime_to_ns(audio->start_time); + do_div(msecs, 1000000); + frames = msecs * SAMPLE_RATE; + do_div(frames, 1000); + + /* Readjust our frames_sent if we fall too far behind. + * If we get too far behind it is better to drop some frames than + * to keep sending data too fast in an attempt to catch up. + */ + if (frames - audio->frames_sent > 10 * FRAMES_PER_MSEC) + audio->frames_sent = frames - FRAMES_PER_MSEC; + + frames -= audio->frames_sent; + + /* We need to send something to keep the pipeline going */ + if (frames <= 0) + frames = FRAMES_PER_MSEC; + + while (frames > 0) { + req = audio_req_get(audio); + if (!req) + break; + + length = frames_to_bytes(runtime, frames); + if (length > IN_EP_MAX_PACKET_SIZE) + length = IN_EP_MAX_PACKET_SIZE; + + if (audio->buffer_pos + length > audio->buffer_end) + length1 = audio->buffer_end - audio->buffer_pos; + else + length1 = length; + memcpy(req->buf, audio->buffer_pos, length1); + if (length1 < length) { + /* Wrap around and copy remaining length + * at beginning of buffer. + */ + length2 = length - length1; + memcpy(req->buf + length1, audio->buffer_start, + length2); + audio->buffer_pos = audio->buffer_start + length2; + } else { + audio->buffer_pos += length1; + if (audio->buffer_pos >= audio->buffer_end) + audio->buffer_pos = audio->buffer_start; + } + + req->length = length; + ret = usb_ep_queue(audio->in_ep, req, GFP_ATOMIC); + if (ret < 0) { + pr_err("usb_ep_queue failed ret: %d\n", ret); + audio_req_put(audio, req); + break; + } + + frames -= bytes_to_frames(runtime, length); + audio->frames_sent += bytes_to_frames(runtime, length); + } +} + +static void audio_control_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* nothing to do here */ +} + +static void audio_data_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct audio_dev *audio = req->context; + + pr_debug("audio_data_complete req->status %d req->actual %d\n", + req->status, req->actual); + + audio_req_put(audio, req); + + if (!audio->buffer_start) + return; + + audio->period_offset += req->actual; + if (audio->period_offset >= audio->period) { + snd_pcm_period_elapsed(audio->substream); + audio->period_offset = 0; + } + audio_send(audio); +} + +static int audio_set_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + int value = -EOPNOTSUPP; + u16 ep = le16_to_cpu(ctrl->wIndex); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_SET_CUR: + case UAC_SET_MIN: + case UAC_SET_MAX: + case UAC_SET_RES: + value = len; + break; + default: + break; + } + + return value; +} + +static int audio_get_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 *buf = cdev->req->buf; + + pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + if (w_value == UAC_EP_CS_ATTR_SAMPLE_RATE << 8) { + switch (ctrl->bRequest) { + case UAC_GET_CUR: + case UAC_GET_MIN: + case UAC_GET_MAX: + case UAC_GET_RES: + /* return our sample rate */ + buf[0] = (u8)SAMPLE_RATE; + buf[1] = (u8)(SAMPLE_RATE >> 8); + buf[2] = (u8)(SAMPLE_RATE >> 16); + value = 3; + break; + default: + break; + } + } + + return value; +} + +static int +audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything; interface + * activation uses set_alt(). + */ + switch (ctrl->bRequestType) { + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_set_endpoint_req(f, ctrl); + break; + + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_get_endpoint_req(f, ctrl); + break; + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + pr_debug("audio req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + req->complete = audio_control_complete; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + pr_err("audio response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct audio_dev *audio = func_to_audio(f); + + pr_debug("audio_set_alt intf %d, alt %d\n", intf, alt); + usb_ep_enable(audio->in_ep, audio->in_desc); + return 0; +} + +static void audio_disable(struct usb_function *f) +{ + struct audio_dev *audio = func_to_audio(f); + + pr_debug("audio_disable\n"); + usb_ep_disable(audio->in_ep); +} + +/*-------------------------------------------------------------------------*/ + +static void audio_build_desc(struct audio_dev *audio) +{ + u8 *sam_freq; + int rate; + + /* Set channel numbers */ + input_terminal_desc.bNrChannels = 2; + as_type_i_desc.bNrChannels = 2; + + /* Set sample rates */ + rate = SAMPLE_RATE; + sam_freq = as_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); +} + +/* audio function driver setup/binding */ +static int +audio_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct audio_dev *audio = func_to_audio(f); + int status; + struct usb_ep *ep; + struct usb_request *req; + int i; + + audio_build_desc(audio); + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ac_interface_desc.bInterfaceNumber = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + as_interface_alt_0_desc.bInterfaceNumber = status; + as_interface_alt_1_desc.bInterfaceNumber = status; + + status = -ENODEV; + + /* allocate our endpoint */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_as_in_ep_desc); + if (!ep) + goto fail; + audio->in_ep = ep; + ep->driver_data = audio; /* claim */ + + if (gadget_is_dualspeed(c->cdev->gadget)) + hs_as_in_ep_desc.bEndpointAddress = + fs_as_in_ep_desc.bEndpointAddress; + + f->descriptors = fs_audio_desc; + f->hs_descriptors = hs_audio_desc; + + for (i = 0, status = 0; i < IN_EP_REQ_COUNT && status == 0; i++) { + req = audio_request_new(ep, IN_EP_MAX_PACKET_SIZE); + if (req) { + req->context = audio; + req->complete = audio_data_complete; + audio_req_put(audio, req); + } else + status = -ENOMEM; + } + +fail: + return status; +} + +static void +audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct audio_dev *audio = func_to_audio(f); + struct usb_request *req; + + while ((req = audio_req_get(audio))) + audio_request_free(req, audio->in_ep); + + snd_card_free_when_closed(audio->card); + kfree(audio); +} + +static void audio_pcm_playback_start(struct audio_dev *audio) +{ + audio->start_time = ktime_get(); + audio->frames_sent = 0; + audio_send(audio); +} + +static void audio_pcm_playback_stop(struct audio_dev *audio) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + audio->buffer_start = 0; + audio->buffer_end = 0; + audio->buffer_pos = 0; + spin_unlock_irqrestore(&audio->lock, flags); +} + +static int audio_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = substream->private_data; + + runtime->private_data = audio; + runtime->hw = audio_hw_info; + snd_pcm_limit_hw_rates(runtime); + runtime->hw.channels_max = 2; + + audio->substream = substream; + return 0; +} + +static int audio_pcm_close(struct snd_pcm_substream *substream) +{ + struct audio_dev *audio = substream->private_data; + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + audio->substream = NULL; + spin_unlock_irqrestore(&audio->lock, flags); + + return 0; +} + +static int audio_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + + if (rate != SAMPLE_RATE) + return -EINVAL; + if (channels != 2) + return -EINVAL; + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(params)); +} + +static int audio_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int audio_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = runtime->private_data; + + audio->period = snd_pcm_lib_period_bytes(substream); + audio->period_offset = 0; + audio->buffer_start = runtime->dma_area; + audio->buffer_end = audio->buffer_start + + snd_pcm_lib_buffer_bytes(substream); + audio->buffer_pos = audio->buffer_start; + + return 0; +} + +static snd_pcm_uframes_t audio_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = runtime->private_data; + ssize_t bytes = audio->buffer_pos - audio->buffer_start; + + /* return offset of next frame to fill in our buffer */ + return bytes_to_frames(runtime, bytes); +} + +static int audio_pcm_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct audio_dev *audio = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + audio_pcm_playback_start(audio); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + audio_pcm_playback_stop(audio); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static struct snd_pcm_ops audio_playback_ops = { + .open = audio_pcm_open, + .close = audio_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = audio_pcm_hw_params, + .hw_free = audio_pcm_hw_free, + .prepare = audio_pcm_prepare, + .trigger = audio_pcm_playback_trigger, + .pointer = audio_pcm_pointer, +}; + +int audio_source_bind_config(struct usb_configuration *c, + struct audio_source_config *config) +{ + struct audio_dev *audio; + struct snd_card *card; + struct snd_pcm *pcm; + int err; + + config->card = -1; + config->device = -1; + + audio = kzalloc(sizeof *audio, GFP_KERNEL); + if (!audio) + return -ENOMEM; + + audio->func.name = "audio_source"; + + spin_lock_init(&audio->lock); + + audio->func.bind = audio_bind; + audio->func.unbind = audio_unbind; + audio->func.set_alt = audio_set_alt; + audio->func.setup = audio_setup; + audio->func.disable = audio_disable; + audio->in_desc = &fs_as_in_ep_desc; + + INIT_LIST_HEAD(&audio->idle_reqs); + + err = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &card); + if (err) + goto snd_card_fail; + + snd_card_set_dev(card, &c->cdev->gadget->dev); + + err = snd_pcm_new(card, "USB audio source", 0, 1, 0, &pcm); + if (err) + goto pcm_fail; + pcm->private_data = audio; + pcm->info_flags = 0; + audio->pcm = pcm; + + strlcpy(pcm->name, "USB gadget audio", sizeof(pcm->name)); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &audio_playback_ops); + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + NULL, 0, 64 * 1024); + + strlcpy(card->driver, "audio_source", sizeof(card->driver)); + strlcpy(card->shortname, card->driver, sizeof(card->shortname)); + strlcpy(card->longname, "USB accessory audio source", + sizeof(card->longname)); + + err = snd_card_register(card); + if (err) + goto register_fail; + + err = usb_add_function(c, &audio->func); + if (err) + goto add_fail; + + config->card = pcm->card->number; + config->device = pcm->device; + audio->card = card; + return 0; + +add_fail: +register_fail: +pcm_fail: + snd_card_free(audio->card); +snd_card_fail: + kfree(audio); + return err; +} diff --git a/drivers/usb/gadget/f_diag.c b/drivers/usb/gadget/f_diag.c new file mode 100644 index 0000000..2a24649 --- /dev/null +++ b/drivers/usb/gadget/f_diag.c @@ -0,0 +1,764 @@ +/* drivers/usb/gadget/f_diag.c + * Diag Function Device - Route ARM9 and ARM11 DIAG messages + * between HOST and DEVICE. + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland <swetland@google.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/kernel.h> +#include <linux/platform_device.h> + +#include <mach/usbdiag.h> + +#include <linux/usb/composite.h> +#include <linux/usb/gadget.h> +#include <linux/workqueue.h> +#include <linux/debugfs.h> + +static DEFINE_SPINLOCK(ch_lock); +static LIST_HEAD(usb_diag_ch_list); + +static struct usb_interface_descriptor intf_desc = { + .bLength = sizeof intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, +#if defined(CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE) || defined(CONFIG_SLP) + .bInterfaceSubClass = 0x10, + .bInterfaceProtocol = 0x01, +#else + .bInterfaceSubClass = 0xFF, + .bInterfaceProtocol = 0xFF, +#endif +}; + +static struct usb_endpoint_descriptor hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; +static struct usb_endpoint_descriptor fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0, +}; + +static struct usb_descriptor_header *fs_diag_desc[] = { + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &fs_bulk_in_desc, + (struct usb_descriptor_header *) &fs_bulk_out_desc, + NULL, + }; +static struct usb_descriptor_header *hs_diag_desc[] = { + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &hs_bulk_in_desc, + (struct usb_descriptor_header *) &hs_bulk_out_desc, + NULL, +}; + +/** + * struct diag_context - USB diag function driver private structure + * @function: function structure for USB interface + * @out: USB OUT endpoint struct + * @in: USB IN endpoint struct + * @in_desc: USB IN endpoint descriptor struct + * @out_desc: USB OUT endpoint descriptor struct + * @read_pool: List of requests used for Rx (OUT ep) + * @write_pool: List of requests used for Tx (IN ep) + * @config_work: Work item schedule after interface is configured to notify + * CONNECT event to diag char driver and updating product id + * and serial number to MODEM/IMEM. + * @lock: Spinlock to proctect read_pool, write_pool lists + * @cdev: USB composite device struct + * @ch: USB diag channel + * + */ +struct diag_context { + struct usb_function function; + struct usb_ep *out; + struct usb_ep *in; + struct usb_endpoint_descriptor *in_desc; + struct usb_endpoint_descriptor *out_desc; + struct list_head read_pool; + struct list_head write_pool; + struct work_struct config_work; + spinlock_t lock; + unsigned configured; + struct usb_composite_dev *cdev; + int (*update_pid_and_serial_num)(uint32_t, const char *); + struct usb_diag_ch ch; + + /* pkt counters */ + unsigned long dpkts_tolaptop; + unsigned long dpkts_tomodem; + unsigned dpkts_tolaptop_pending; + // zero_pky.patch by jagadish + bool qxdm_ops; +}; + +static inline struct diag_context *func_to_diag(struct usb_function *f) +{ + return container_of(f, struct diag_context, function); +} + +static void usb_config_work_func(struct work_struct *work) +{ + struct diag_context *ctxt = container_of(work, + struct diag_context, config_work); + struct usb_composite_dev *cdev = ctxt->cdev; + struct usb_gadget_strings *table; + struct usb_string *s; + + if (ctxt->ch.notify) + { + ctxt->ch.notify(ctxt->ch.priv, USB_DIAG_CONNECT, NULL); + // zero_pky.patch by jagadish + ctxt->qxdm_ops = 0; + } +} + +static void diag_write_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct diag_context *ctxt = ep->driver_data; + struct diag_request *d_req = req->context; + unsigned long flags; + + ctxt->dpkts_tolaptop_pending--; + + if (!req->status) { + if ((req->length >= ep->maxpacket) && + ((req->length % ep->maxpacket) == 0)) { + ctxt->dpkts_tolaptop_pending++; + req->length = 0; + d_req->actual = req->actual; + d_req->status = req->status; + /* Queue zero length packet */ + usb_ep_queue(ctxt->in, req, GFP_ATOMIC); + return; + } + } + + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->write_pool); + if (req->length != 0) { + d_req->actual = req->actual; + d_req->status = req->status; + } + spin_unlock_irqrestore(&ctxt->lock, flags); + + if (ctxt->ch.notify) { + // zero_pky.patch by jagadish + ctxt->qxdm_ops = 1; + ctxt->ch.notify(ctxt->ch.priv, USB_DIAG_WRITE_DONE, d_req); + } + +} + +static void diag_read_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct diag_context *ctxt = ep->driver_data; + struct diag_request *d_req = req->context; + unsigned long flags; + + d_req->actual = req->actual; + d_req->status = req->status; + + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->read_pool); + spin_unlock_irqrestore(&ctxt->lock, flags); + + ctxt->dpkts_tomodem++; + + if (ctxt->ch.notify) { + // zero_pky.patch by jagadish + ctxt->qxdm_ops = 1; + ctxt->ch.notify(ctxt->ch.priv, USB_DIAG_READ_DONE, d_req); + } + +} + +/** + * usb_diag_open() - Open a diag channel over USB + * @name: Name of the channel + * @priv: Private structure pointer which will be passed in notify() + * @notify: Callback function to receive notifications + * + * This function iterates overs the available channels and returns + * the channel handler if the name matches. The notify callback is called + * for CONNECT, DISCONNECT, READ_DONE and WRITE_DONE events. + * + */ +struct usb_diag_ch *usb_diag_open(const char *name, void *priv, + void (*notify)(void *, unsigned, struct diag_request *)) +{ + struct usb_diag_ch *ch; + struct diag_context *ctxt; + unsigned long flags; + int found = 0; + + spin_lock_irqsave(&ch_lock, flags); + /* Check if we already have a channel with this name */ + list_for_each_entry(ch, &usb_diag_ch_list, list) { + if (!strcmp(name, ch->name)) { + found = 1; + break; + } + } + spin_unlock_irqrestore(&ch_lock, flags); + + if (!found) { + ctxt = kzalloc(sizeof(*ctxt), GFP_KERNEL); + if (!ctxt) + return ERR_PTR(-ENOMEM); + + ch = &ctxt->ch; + } + + ch->name = name; + ch->priv = priv; + ch->notify = notify; + + spin_lock_irqsave(&ch_lock, flags); + list_add_tail(&ch->list, &usb_diag_ch_list); + spin_unlock_irqrestore(&ch_lock, flags); + + return ch; +} +EXPORT_SYMBOL(usb_diag_open); + +/** + * usb_diag_close() - Close a diag channel over USB + * @ch: Channel handler + * + * This function closes the diag channel. + * + */ +void usb_diag_close(struct usb_diag_ch *ch) +{ + struct diag_context *dev = container_of(ch, struct diag_context, ch); + unsigned long flags; + + spin_lock_irqsave(&ch_lock, flags); + ch->priv = NULL; + ch->notify = NULL; + /* Free-up the resources if channel is no more active */ + if (!ch->priv_usb) { + list_del(&ch->list); + kfree(dev); + } + + spin_unlock_irqrestore(&ch_lock, flags); +} +EXPORT_SYMBOL(usb_diag_close); + +/** + * usb_diag_free_req() - Free USB requests + * @ch: Channel handler + * + * This function free read and write USB requests for the interface + * associated with this channel. + * + */ +void usb_diag_free_req(struct usb_diag_ch *ch) +{ + struct diag_context *ctxt = ch->priv_usb; + struct usb_request *req; + struct list_head *act, *tmp; + + if (!ctxt) + return; + + list_for_each_safe(act, tmp, &ctxt->write_pool) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + usb_ep_free_request(ctxt->in, req); + } + + list_for_each_safe(act, tmp, &ctxt->read_pool) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + usb_ep_free_request(ctxt->out, req); + } +} +EXPORT_SYMBOL(usb_diag_free_req); + +/** + * usb_diag_alloc_req() - Allocate USB requests + * @ch: Channel handler + * @n_write: Number of requests for Tx + * @n_read: Number of requests for Rx + * + * This function allocate read and write USB requests for the interface + * associated with this channel. The actual buffer is not allocated. + * The buffer is passed by diag char driver. + * + */ +int usb_diag_alloc_req(struct usb_diag_ch *ch, int n_write, int n_read) +{ + struct diag_context *ctxt = ch->priv_usb; + struct usb_request *req; + int i; + + if (!ctxt) + return -ENODEV; + + for (i = 0; i < n_write; i++) { + req = usb_ep_alloc_request(ctxt->in, GFP_ATOMIC); + if (!req) + goto fail; + req->complete = diag_write_complete; + list_add_tail(&req->list, &ctxt->write_pool); + } + + for (i = 0; i < n_read; i++) { + req = usb_ep_alloc_request(ctxt->out, GFP_ATOMIC); + if (!req) + goto fail; + req->complete = diag_read_complete; + list_add_tail(&req->list, &ctxt->read_pool); + } + + return 0; + +fail: + usb_diag_free_req(ch); + return -ENOMEM; + +} +EXPORT_SYMBOL(usb_diag_alloc_req); + +/** + * usb_diag_read() - Read data from USB diag channel + * @ch: Channel handler + * @d_req: Diag request struct + * + * Enqueue a request on OUT endpoint of the interface corresponding to this + * channel. This function returns proper error code when interface is not + * in configured state, no Rx requests available and ep queue is failed. + * + * This function operates asynchronously. READ_DONE event is notified after + * completion of OUT request. + * + */ +int usb_diag_read(struct usb_diag_ch *ch, struct diag_request *d_req) +{ + struct diag_context *ctxt = ch->priv_usb; + unsigned long flags; + struct usb_request *req; + + if (!ctxt) + return -ENODEV; + + spin_lock_irqsave(&ctxt->lock, flags); + + if (!ctxt->configured) { + spin_unlock_irqrestore(&ctxt->lock, flags); + return -EIO; + } + + if (list_empty(&ctxt->read_pool)) { + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: no requests available\n", __func__); + return -EAGAIN; + } + + req = list_first_entry(&ctxt->read_pool, struct usb_request, list); + list_del(&req->list); + spin_unlock_irqrestore(&ctxt->lock, flags); + + req->buf = d_req->buf; + req->length = d_req->length; + req->context = d_req; + if (usb_ep_queue(ctxt->out, req, GFP_ATOMIC)) { + /* If error add the link to linked list again*/ + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->read_pool); + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: cannot queue" + " read request\n", __func__); + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL(usb_diag_read); + +/** + * usb_diag_write() - Write data from USB diag channel + * @ch: Channel handler + * @d_req: Diag request struct + * + * Enqueue a request on IN endpoint of the interface corresponding to this + * channel. This function returns proper error code when interface is not + * in configured state, no Tx requests available and ep queue is failed. + * + * This function operates asynchronously. WRITE_DONE event is notified after + * completion of IN request. + * + */ +int usb_diag_write(struct usb_diag_ch *ch, struct diag_request *d_req) +{ + struct diag_context *ctxt = ch->priv_usb; + unsigned long flags; + struct usb_request *req = NULL; + + if (!ctxt) + return -ENODEV; + + spin_lock_irqsave(&ctxt->lock, flags); + + if (!ctxt->configured) { + spin_unlock_irqrestore(&ctxt->lock, flags); + return -EIO; + } + + if (list_empty(&ctxt->write_pool)) { + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: no requests available\n", __func__); + return -EAGAIN; + } + + req = list_first_entry(&ctxt->write_pool, struct usb_request, list); + list_del(&req->list); + spin_unlock_irqrestore(&ctxt->lock, flags); + + req->buf = d_req->buf; + req->length = d_req->length; + req->context = d_req; + if (usb_ep_queue(ctxt->in, req, GFP_ATOMIC)) { + /* If error add the link to linked list again*/ + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->write_pool); + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: cannot queue" + " read request\n", __func__); + return -EIO; + } + + ctxt->dpkts_tolaptop++; + ctxt->dpkts_tolaptop_pending++; + + return 0; +} +EXPORT_SYMBOL(usb_diag_write); + +static void diag_function_disable(struct usb_function *f) +{ + struct diag_context *dev = func_to_diag(f); + unsigned long flags; + + DBG(dev->cdev, "diag_function_disable\n"); + + spin_lock_irqsave(&dev->lock, flags); + dev->configured = 0; + spin_unlock_irqrestore(&dev->lock, flags); + + // zero_pky.patch by jagadish + if (dev->ch.notify) { + if (dev->qxdm_ops) + dev->ch.notify(dev->ch.priv, USB_DIAG_QXDM_DISCONNECT, NULL); + else + dev->ch.notify(dev->ch.priv, USB_DIAG_DISCONNECT, NULL); + dev->qxdm_ops = 0; + } + + + usb_ep_disable(dev->in); + dev->in->driver_data = NULL; + + usb_ep_disable(dev->out); + dev->out->driver_data = NULL; + +} + +static int diag_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct diag_context *dev = func_to_diag(f); + struct usb_composite_dev *cdev = f->config->cdev; + unsigned long flags; + int rc = 0; + + dev->in_desc = ep_choose(cdev->gadget, + (struct usb_endpoint_descriptor *)f->hs_descriptors[1], + (struct usb_endpoint_descriptor *)f->descriptors[1]); + dev->out_desc = ep_choose(cdev->gadget, + (struct usb_endpoint_descriptor *)f->hs_descriptors[2], + (struct usb_endpoint_descriptor *)f->descriptors[2]); + dev->in->driver_data = dev; + rc = usb_ep_enable(dev->in, dev->in_desc); + if (rc) { + ERROR(dev->cdev, "can't enable %s, result %d\n", + dev->in->name, rc); + return rc; + } + dev->out->driver_data = dev; + rc = usb_ep_enable(dev->out, dev->out_desc); + if (rc) { + ERROR(dev->cdev, "can't enable %s, result %d\n", + dev->out->name, rc); + usb_ep_disable(dev->in); + return rc; + } + schedule_work(&dev->config_work); + + dev->dpkts_tolaptop = 0; + dev->dpkts_tomodem = 0; + dev->dpkts_tolaptop_pending = 0; + + spin_lock_irqsave(&dev->lock, flags); + dev->configured = 1; + spin_unlock_irqrestore(&dev->lock, flags); + + return rc; +} + +static void diag_function_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + struct diag_context *ctxt = func_to_diag(f); + + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + + usb_free_descriptors(f->descriptors); + ctxt->ch.priv_usb = NULL; +} + +static int diag_function_bind(struct usb_configuration *c, + struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct diag_context *ctxt = func_to_diag(f); + struct usb_ep *ep; + int status = -ENODEV; + + intf_desc.bInterfaceNumber = usb_interface_id(c, f); + + ep = usb_ep_autoconfig(cdev->gadget, &fs_bulk_in_desc); + if (!ep) + goto fail; + ctxt->in = ep; + ep->driver_data = ctxt; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_bulk_out_desc); + if (!ep) + goto fail; + ctxt->out = ep; + ep->driver_data = ctxt; + + /* copy descriptors, and track endpoint copies */ + f->descriptors = usb_copy_descriptors(fs_diag_desc); + if (!f->descriptors) + goto fail; + + if (gadget_is_dualspeed(c->cdev->gadget)) { + hs_bulk_in_desc.bEndpointAddress = + fs_bulk_in_desc.bEndpointAddress; + hs_bulk_out_desc.bEndpointAddress = + fs_bulk_out_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(hs_diag_desc); + } + return 0; +fail: + if (ctxt->out) + ctxt->out->driver_data = NULL; + if (ctxt->in) + ctxt->in->driver_data = NULL; + return status; + +} + +int diag_function_add(struct usb_configuration *c, const char *name, + int (*update_pid)(uint32_t, const char *)) +{ + struct diag_context *dev; + struct usb_diag_ch *_ch; + int found = 0, ret; + + DBG(c->cdev, "diag_function_add\n"); + + list_for_each_entry(_ch, &usb_diag_ch_list, list) { + if (!strcmp(name, _ch->name)) { + found = 1; + break; + } + } + if (!found) { + ERROR(c->cdev, "usb: unable to get diag usb channel\n"); + + return -ENODEV; + } + + dev = container_of(_ch, struct diag_context, ch); + /* claim the channel for this USB interface */ + _ch->priv_usb = dev; + + dev->update_pid_and_serial_num = update_pid; + dev->cdev = c->cdev; + dev->function.name = _ch->name; + dev->function.descriptors = fs_diag_desc; + dev->function.hs_descriptors = hs_diag_desc; + dev->function.bind = diag_function_bind; + dev->function.unbind = diag_function_unbind; + dev->function.set_alt = diag_function_set_alt; + dev->function.disable = diag_function_disable; + spin_lock_init(&dev->lock); + INIT_LIST_HEAD(&dev->read_pool); + INIT_LIST_HEAD(&dev->write_pool); + INIT_WORK(&dev->config_work, usb_config_work_func); + + ret = usb_add_function(c, &dev->function); + if (ret) { + INFO(c->cdev, "usb_add_function failed\n"); + _ch->priv_usb = NULL; + } + + return ret; +} + + +#if defined(CONFIG_DEBUG_FS) +static char debug_buffer[PAGE_SIZE]; + +static ssize_t debug_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char *buf = debug_buffer; + int temp = 0; + struct usb_diag_ch *ch; + + list_for_each_entry(ch, &usb_diag_ch_list, list) { + struct diag_context *ctxt; + + ctxt = ch->priv_usb; + + temp += scnprintf(buf + temp, PAGE_SIZE - temp, + "---Name: %s---\n" + "endpoints: %s, %s\n" + "dpkts_tolaptop: %lu\n" + "dpkts_tomodem: %lu\n" + "pkts_tolaptop_pending: %u\n", + ch->name, + ctxt->in->name, ctxt->out->name, + ctxt->dpkts_tolaptop, + ctxt->dpkts_tomodem, + ctxt->dpkts_tolaptop_pending); + } + + return simple_read_from_buffer(ubuf, count, ppos, buf, temp); +} + +static ssize_t debug_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct usb_diag_ch *ch; + + list_for_each_entry(ch, &usb_diag_ch_list, list) { + struct diag_context *ctxt; + + ctxt = ch->priv_usb; + + ctxt->dpkts_tolaptop = 0; + ctxt->dpkts_tomodem = 0; + ctxt->dpkts_tolaptop_pending = 0; + } + + return count; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations debug_fdiag_ops = { + .open = debug_open, + .read = debug_read_stats, + .write = debug_reset_stats, +}; + +struct dentry *dent_diag; +static void fdiag_debugfs_init(void) +{ + dent_diag = debugfs_create_dir("usb_diag", 0); + if (IS_ERR(dent_diag)) + return; + + debugfs_create_file("status", 0444, dent_diag, 0, &debug_fdiag_ops); +} +#else +static void fdiag_debugfs_init(void) +{ + return; +} +#endif + +static void diag_cleanup(void) +{ + struct diag_context *dev; + struct list_head *act, *tmp; + struct usb_diag_ch *_ch; + unsigned long flags; + + debugfs_remove_recursive(dent_diag); + + list_for_each_safe(act, tmp, &usb_diag_ch_list) { + _ch = list_entry(act, struct usb_diag_ch, list); + dev = container_of(_ch, struct diag_context, ch); + + spin_lock_irqsave(&ch_lock, flags); + /* Free if diagchar is not using the channel anymore */ + if (!_ch->priv) { + list_del(&_ch->list); + kfree(dev); + } + spin_unlock_irqrestore(&ch_lock, flags); + } +} + +static int diag_setup(void) +{ + fdiag_debugfs_init(); + + return 0; +} diff --git a/drivers/usb/gadget/f_diag.h b/drivers/usb/gadget/f_diag.h new file mode 100644 index 0000000..82d9a25 --- /dev/null +++ b/drivers/usb/gadget/f_diag.h @@ -0,0 +1,24 @@ +/* drivers/usb/gadget/f_diag.h + * + * Diag Function Device - Route DIAG frames between SMD and USB + * + * Copyright (C) 2008-2009 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland <swetland@google.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. + * + */ +#ifndef __F_DIAG_H +#define __F_DIAG_H + +int diag_function_add(struct usb_configuration *c, const char *); + +#endif /* __F_DIAG_H */ + diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index 92b7736..ec1d520 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -297,6 +297,11 @@ #include "gadget_chips.h" +#ifdef CONFIG_USB_CDFS_SUPPORT +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE +#define _SUPPORT_MAC_ /* support to recognize CDFS on OSX (MAC PC) */ +#endif +#endif /*------------------------------------------------------------------------*/ @@ -456,6 +461,62 @@ struct fsg_dev { struct usb_ep *bulk_out; }; +#ifdef CONFIG_USB_CDFS_SUPPORT +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE +static int send_message(struct fsg_common *common, char *msg) +{ + char name_buf[120]; + char state_buf[120]; + char *envp[3]; + int env_offset = 0; + struct usb_gadget *gadget = common->gadget; + + DBG(common, "%s called\n", __func__); + printk(KERN_INFO "%s (%s)\n", __func__, msg); + + if (gadget) { + snprintf(name_buf, sizeof(name_buf), + "SWITCH_NAME=USB_MESSAGE"); + envp[env_offset++] = name_buf; + + snprintf(state_buf, sizeof(state_buf), + "SWITCH_STATE=%s", msg); + envp[env_offset++] = state_buf; + + envp[env_offset] = NULL; + + if (!gadget->dev.class) { + gadget->dev.class = class_create(THIS_MODULE, + "usb_msg"); + if (IS_ERR(gadget->dev.class)) + return -1; + } + + DBG(common, "Send cd eject message to daemon\n"); + + kobject_uevent_env(&gadget->dev.kobj, KOBJ_CHANGE, envp); + } + + return 0; +} + +static int do_autorun_check(struct fsg_common *common) +{ + printk(KERN_INFO "%s called\n", __func__); + send_message(common, "autorun"); + + return 0; +} + +static int do_switch_atmode(struct fsg_common *common) +{ + printk(KERN_INFO "%s called\n", __func__); + send_message(common, "Load AT"); + + return 0; +} +#endif /* CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE */ +#endif static inline int __fsg_is_set(struct fsg_common *common, const char *func, unsigned line) { @@ -738,6 +799,243 @@ static int sleep_thread(struct fsg_common *common) return rc; } +#ifdef CONFIG_USB_CDFS_SUPPORT +#ifdef _SUPPORT_MAC_ +static void _lba_to_msf(u8 *buf, int lba) +{ + lba += 150; + buf[0] = (lba / 75) / 60; + buf[1] = (lba / 75) % 60; + buf[2] = lba % 75; +} + +static int _read_toc_raw(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + int msf = common->cmnd[1] & 0x02; + u8 *buf = (u8 *) bh->buf; + + u8 *q; + int len; + + q = buf + 2; + *q++ = 1; /* first session */ + *q++ = 1; /* last session */ + + *q++ = 1; /* session number */ + *q++ = 0x14; /* data track */ + *q++ = 0; /* track number */ + *q++ = 0xa0; /* lead-in */ + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + *q++ = 0; + *q++ = 1; /* first track */ + *q++ = 0x00; /* disk type */ + *q++ = 0x00; + + *q++ = 1; /* session number */ + *q++ = 0x14; /* data track */ + *q++ = 0; /* track number */ + *q++ = 0xa1; + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + *q++ = 0; + *q++ = 1; /* last track */ + *q++ = 0x00; + *q++ = 0x00; + + *q++ = 1; /* session number */ + *q++ = 0x14; /* data track */ + *q++ = 0; /* track number */ + *q++ = 0xa2; /* lead-out */ + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + if (msf) { + *q++ = 0; /* reserved */ + _lba_to_msf(q, curlun->num_sectors); + q += 3; + } else { + put_unaligned_be32(curlun->num_sectors, q); + q += 4; + } + + *q++ = 1; /* session number */ + *q++ = 0x14; /* ADR, control */ + *q++ = 0; /* track number */ + *q++ = 1; /* point */ + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + if (msf) { + *q++ = 0; + _lba_to_msf(q, 0); + q += 3; + } else { + *q++ = 0; + *q++ = 0; + *q++ = 0; + *q++ = 0; + } + + len = q - buf; + put_unaligned_be16(len - 2, buf); + + return len; +} + +static void cd_data_to_raw(u8 *buf, int lba) +{ + /* sync bytes */ + buf[0] = 0x00; + memset(buf + 1, 0xff, 10); + buf[11] = 0x00; + buf += 12; + /* MSF */ + _lba_to_msf(buf, lba); + buf[3] = 0x01; /* mode 1 data */ + buf += 4; + /* data */ + buf += 2048; + /* XXX: ECC not computed */ + memset(buf, 0, 288); +} + +static int do_read_cd(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + u32 lba; + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + loff_t file_offset, file_offset_tmp; + unsigned int amount; + unsigned int partial_page; + ssize_t nread; + + u32 nb_sectors, transfer_request; + + nb_sectors = (common->cmnd[6] << 16) | + (common->cmnd[7] << 8) | common->cmnd[8]; + lba = get_unaligned_be32(&common->cmnd[2]); + + if (nb_sectors == 0) + return 0; + + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + transfer_request = common->cmnd[9]; + if ((transfer_request & 0xf8) == 0xf8) { + file_offset = ((loff_t) lba) << 11; + /* read all data - 2352 byte */ + amount_left = 2352; + } else { + file_offset = ((loff_t) lba) << 9; + /* Carry out the file reads */ + amount_left = common->data_size_from_cmnd; + } + + if (unlikely(amount_left == 0)) + return -EIO; /* No default reply */ + + for (;;) { + + /* Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + * Finally, if we're not at a page boundary, don't read past + * the next page. + * If this means reading 0 then we were asked to read past + * the end of file. */ + amount = min(amount_left, FSG_BUFLEN); + amount = min((loff_t) amount, + curlun->file_length - file_offset); + partial_page = file_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, (unsigned int) PAGE_CACHE_SIZE - + partial_page); + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + /* If we were asked to read past the end of file, + * end with an empty buffer. */ + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + if ((transfer_request & 0xf8) == 0xf8) { + nread = vfs_read(curlun->filp, + ((char __user *)bh->buf)+16, + amount, &file_offset_tmp); + } else { + nread = vfs_read(curlun->filp, + (char __user *)bh->buf, + amount, &file_offset_tmp); + } + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file read: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file read: %d/%u\n", + (int) nread, amount); + nread -= (nread & 511); /* Round down to a block */ + } + file_offset += nread; + amount_left -= nread; + common->residue -= nread; + bh->inreq->length = nread; + bh->state = BUF_STATE_FULL; + + /* If an error occurred, report it and its position */ + if (nread < amount) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + if (amount_left == 0) + break; /* No more left to read */ + + /* Send this buffer and go read some more */ + start_in_transfer(common, bh); + common->next_buffhd_to_fill = bh->next; + } + + if ((transfer_request & 0xf8) == 0xf8) + cd_data_to_raw(bh->buf, lba); + + return -EIO; /* No default reply */ +} +#endif /* _SUPPORT_MAC_ */ +#endif /*-------------------------------------------------------------------------*/ @@ -1397,13 +1695,22 @@ static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh) int msf = common->cmnd[1] & 0x02; int start_track = common->cmnd[6]; u8 *buf = (u8 *)bh->buf; - +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef _SUPPORT_MAC_ + int format = (common->cmnd[9] & 0xC0) >> 6; +#endif +#endif if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ start_track > 1) { curlun->sense_data = SS_INVALID_FIELD_IN_CDB; return -EINVAL; } - +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef _SUPPORT_MAC_ + if (format == 2) + return _read_toc_raw(common, bh); +#endif +#endif memset(buf, 0, 20); buf[1] = (20-2); /* TOC data length */ buf[2] = 1; /* First track number */ @@ -1528,6 +1835,12 @@ static int do_start_stop(struct fsg_common *common) * available for use as soon as it is loaded. */ if (start) { +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (loej) + send_message(common, "Load AT"); +#endif +#endif if (!fsg_lun_is_open(curlun)) { curlun->sense_data = SS_MEDIUM_NOT_PRESENT; return -EINVAL; @@ -1561,6 +1874,12 @@ static int do_start_stop(struct fsg_common *common) up_write(&common->filesem); down_read(&common->filesem); +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + send_message(common, "Load User"); +#endif +#endif + return common->ops && common->ops->post_eject ? min(0, common->ops->post_eject(common, curlun, curlun - common->luns)) @@ -2153,7 +2472,16 @@ static int do_scsi_command(struct fsg_common *common) common->data_size_from_cmnd = get_unaligned_be16(&common->cmnd[7]); reply = check_command(common, 10, DATA_DIR_TO_HOST, +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef _SUPPORT_MAC_ + (0xf<<6) | (1<<1), 1, +#else (7<<6) | (1<<1), 1, +#endif +#else + (7<<6) | (1<<1), 1, +#endif + "READ TOC"); if (reply == 0) reply = do_read_toc(common, bh); @@ -2246,15 +2574,43 @@ static int do_scsi_command(struct fsg_common *common) reply = do_write(common); break; - /* - * Some mandatory commands that we recognize but don't implement. +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + case RELEASE: /* SC_AUTORUN_CHECK0 : 0x17 */ + reply = do_switch_atmode(common); + break; + + case RESERVE: /* SC_AUTORUN_CHECK1 : 0x16 */ + reply = do_autorun_check(common); + break; + +#ifdef _SUPPORT_MAC_ + case READ_CD: + common->data_size_from_cmnd = ((common->cmnd[6] << 16) + | (common->cmnd[7] << 8) + | (common->cmnd[8])) << 9; + reply = check_command(common, 12, DATA_DIR_TO_HOST, + (0xf<<2) | (7<<7), 1, + "READ CD"); + if (reply == 0) + reply = do_read_cd(common); + break; + +#endif /* _SUPPORT_MAC_ */ +#endif +#endif + /* Some mandatory commands that we recognize but don't implement. * They don't mean much in this setting. It's left as an exercise * for anyone interested to implement RESERVE and RELEASE in terms * of Posix locks. */ case FORMAT_UNIT: +#ifndef CONFIG_USB_CDFS_SUPPORT +#ifndef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE case RELEASE: case RESERVE: +#endif +#endif case SEND_DIAGNOSTIC: /* Fall through */ diff --git a/drivers/usb/gadget/f_mtp.h b/drivers/usb/gadget/f_mtp.h index 5892e3f..00a30b7 100644 --- a/drivers/usb/gadget/f_mtp.h +++ b/drivers/usb/gadget/f_mtp.h @@ -31,6 +31,7 @@ #define SEND_RESET_ACK 8 #define SET_ZLP_DATA 9 #define GET_HIGH_FULL_SPEED 10 +#define SEND_FILE_WITH_HEADER 11 #define SIG_SETUP 44 /*PIMA15740-2000 spec*/ @@ -53,4 +54,20 @@ struct usb_mtp_ctrlrequest { struct usb_ctrlrequest setup; }; + +struct usb_container_header { + uint32_t Length;/* the valid size, in BYTES, of the container */ + uint16_t Type;/* Container type */ + uint16_t Code;/* Operation code, response code, or Event code */ + uint32_t TransactionID;/* host generated number */ +}; + +struct read_send_info { + int Fd;/* Media File fd */ + uint64_t Length;/* the valid size, in BYTES, of the container */ + uint16_t Code;/* Operation code, response code, or Event code */ + uint32_t TransactionID;/* host generated number */ +}; + + #endif /* __F_MTP_H */ diff --git a/drivers/usb/gadget/f_mtp_samsung.c b/drivers/usb/gadget/f_mtp_samsung.c index 7f0dec8..304eca3 100644 --- a/drivers/usb/gadget/f_mtp_samsung.c +++ b/drivers/usb/gadget/f_mtp_samsung.c @@ -93,12 +93,12 @@ #endif /*-------------------------------------------------------------------------*/ -#define MTPG_BULK_BUFFER_SIZE 4096 +#define MTPG_BULK_BUFFER_SIZE 32768 #define MTPG_INTR_BUFFER_SIZE 28 /* number of rx and tx requests to allocate */ -#define MTPG_RX_REQ_MAX 4 -#define MTPG_MTPG_TX_REQ_MAX 4 +#define MTPG_RX_REQ_MAX 8 +#define MTPG_MTPG_TX_REQ_MAX 8 #define MTPG_INTR_REQ_MAX 5 /* ID for Microsoft MTP OS String */ @@ -137,13 +137,24 @@ struct mtpg_dev { struct usb_ep *bulk_in; struct usb_ep *bulk_out; struct usb_ep *int_in; + struct usb_request *notify_req; + struct workqueue_struct *wq; + struct work_struct read_send_work; + struct file *read_send_file; + + int64_t read_send_length; + + uint16_t read_send_cmd; + uint32_t read_send_id; + int read_send_result; atomic_t read_excl; atomic_t write_excl; atomic_t ioctl_excl; atomic_t open_excl; atomic_t wintfd_excl; char cancel_io_buf[USB_PTPREQUEST_CANCELIO_SIZE+1]; + int cancel_io; }; /* Global mtpg_dev Structure @@ -437,13 +448,14 @@ static int mtp_send_signal(int value) info.si_int = value; rcu_read_lock(); - if (!current->nsproxy) { + if (!current->nsproxy) { printk(KERN_DEBUG "process has gone\n"); rcu_read_unlock(); return -ENODEV; } t = pid_task(find_vpid(mtp_pid), PIDTYPE_PID); + if (t == NULL) { printk(KERN_DEBUG "no such pid\n"); rcu_read_unlock(); @@ -753,6 +765,108 @@ static ssize_t interrupt_write(struct file *fd, return ret; } +static void read_send_work(struct work_struct *work) +{ + struct mtpg_dev *dev = container_of(work, struct mtpg_dev, + read_send_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + struct usb_container_header *hdr; + struct file *file; + loff_t file_pos = 0; + int64_t count = 0; + int xfer = 0; + int ret = -1; + int hdr_length = 0; + int r = 0; + int ZLP_flag = 0; + + /* read our parameters */ + smp_rmb(); + file = dev->read_send_file; + count = dev->read_send_length; + hdr_length = sizeof(struct usb_container_header); + count += hdr_length; + + printk(KERN_DEBUG "[%s:%d] offset=[%lld]\t leth+hder=[%lld]\n", + __func__, __LINE__, file_pos, count); + + /* Zero Length Packet should be sent if the last trasfer + * size is equals to the max packet size. + */ + if ((count & (dev->bulk_in->maxpacket - 1)) == 0) + ZLP_flag = 1; + + while (count > 0 || ZLP_flag) { + /*Breaking the loop after sending Zero Length Packet*/ + if (count == 0) + ZLP_flag = 0; + + if (dev->cancel_io == 1) { + dev->cancel_io = 0; /*reported to user space*/ + r = -EIO; + printk(KERN_DEBUG "[%s]\t%d ret = %d\n", + __func__, __LINE__, r); + break; + } + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = mtpg_req_get(dev, &dev->tx_idle)) + || dev->error)); + if (ret < 0) { + r = ret; + printk(KERN_DEBUG "[%s]\t%d ret = %d\n", + __func__, __LINE__, r); + break; + } + + if (count > MTPG_BULK_BUFFER_SIZE) + xfer = MTPG_BULK_BUFFER_SIZE; + else + xfer = count; + + if (hdr_length) { + hdr = (struct usb_container_header *)req->buf; + hdr->Length = __cpu_to_le32(count); + hdr->Type = __cpu_to_le16(2); + hdr->Code = __cpu_to_le16(dev->read_send_cmd); + hdr->TransactionID = __cpu_to_le32(dev->read_send_id); + } + + ret = vfs_read(file, req->buf + hdr_length, + xfer - hdr_length, &file_pos); + if (ret < 0) { + r = ret; + break; + } + xfer = ret + hdr_length; + hdr_length = 0; + + req->length = xfer; + ret = usb_ep_queue(dev->bulk_in, req, GFP_KERNEL); + if (ret < 0) { + dev->error = 1; + r = -EIO; + printk(KERN_DEBUG "[%s]\t%d ret = %d\n", + __func__, __LINE__, r); + break; + } + + count -= xfer; + + req = 0; + } + + if (req) + mtpg_req_put(dev, &dev->tx_idle, req); + + DEBUG_MTPB("[%s] \tline = [%d] \t r = [%d]\n", __func__, __LINE__, r); + + dev->read_send_result = r; + smp_wmb(); +} + static long mtpg_ioctl(struct file *fd, unsigned int code, unsigned long arg) { struct mtpg_dev *dev = fd->private_data; @@ -781,7 +895,7 @@ static long mtpg_ioctl(struct file *fd, unsigned int code, unsigned long arg) switch (code) { case MTP_ONLY_ENABLE: printk(KERN_DEBUG "[%s:%d] MTP_ONLY_ENABLE ioctl:\n", - __func__, __LINE__); + __func__, __LINE__); if (dev->cdev && dev->cdev->gadget) { usb_gadget_disconnect(cdev->gadget); printk(KERN_DEBUG "[%s:%d] B4 disconectng gadget\n", @@ -789,7 +903,7 @@ static long mtpg_ioctl(struct file *fd, unsigned int code, unsigned long arg) msleep(20); usb_gadget_connect(cdev->gadget); printk(KERN_DEBUG "[%s:%d] after usb_gadget_connect\n", - __func__, __LINE__); + __func__, __LINE__); } status = 10; printk(KERN_DEBUG "[%s:%d] MTP_ONLY_ENABLE clearing error 0\n", @@ -910,9 +1024,48 @@ static long mtpg_ioctl(struct file *fd, unsigned int code, unsigned long arg) else status = 512; break; + case SEND_FILE_WITH_HEADER: + { + struct read_send_info info; + struct work_struct *work; + struct file *file = NULL; + printk(KERN_DEBUG "[%s]SEND_FILE_WITH_HEADER line=[%d]\n", + __func__, __LINE__); + + if (copy_from_user(&info, (void __user *)arg, sizeof(info))) { + status = -EFAULT; + goto exit; + } + + file = fget(info.Fd); + if (!file) { + status = -EBADF; + printk(KERN_DEBUG "[%s] line=[%d] bad file number\n", + __func__, __LINE__); + goto exit; + } + + dev->read_send_file = file; + dev->read_send_length = info.Length; + smp_wmb(); + + work = &dev->read_send_work; + dev->read_send_cmd = info.Code; + dev->read_send_id = info.TransactionID; + queue_work(dev->wq, work); + /* Wait for the work to be complted on work queue */ + flush_workqueue(dev->wq); + + fput(file); + + smp_rmb(); + status = dev->read_send_result; + break; + } default: status = -ENOTTY; } +exit: return status; } @@ -1138,7 +1291,6 @@ mtpg_function_bind(struct usb_configuration *c, struct usb_function *f) fs_mtpg_out_desc.bEndpointAddress; int_hs_notify_desc.bEndpointAddress = int_fs_notify_desc.bEndpointAddress; - } mtpg->cdev = cdev; @@ -1208,6 +1360,7 @@ static int mtpg_function_set_alt(struct usb_function *f, dev->online = 1; dev->error = 0; dev->read_ready = 1; + dev->cancel_io = 0; /* readers may be blocked waiting for us to go online */ wake_up(&dev->read_wq); @@ -1259,6 +1412,7 @@ mtp_complete_cancel_io(struct usb_ep *ep, struct usb_request *req) memset(dev->cancel_io_buf, 0, USB_PTPREQUEST_CANCELIO_SIZE+1); memcpy(dev->cancel_io_buf, req->buf, USB_PTPREQUEST_CANCELIO_SIZE); + dev->cancel_io = 1; /*Debugging*/ for (i = 0; i < USB_PTPREQUEST_CANCELIO_SIZE; i++) DEBUG_MTPB("[%s]cancel_io_buf[%d]=%x\tline = [%d]\n", @@ -1299,7 +1453,8 @@ static int mtp_ctrlrequest(struct usb_composite_dev *cdev, } return value; } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { - if (ctrl->bRequest == 1 + if ((ctrl->bRequest == 1 || ctrl->bRequest == 0x54 || + ctrl->bRequest == 0x6F || ctrl->bRequest == 0xFE) && (ctrl->bRequestType & USB_DIR_IN) && (w_index == 4 || w_index == 5)) { value = (w_length < sizeof(mtpg_ext_config_desc) ? @@ -1443,6 +1598,14 @@ static int mtp_setup(void) INIT_LIST_HEAD(&mtpg->rx_done); INIT_LIST_HEAD(&mtpg->tx_idle); INIT_LIST_HEAD(&mtpg->intr_idle); + mtpg->wq = create_singlethread_workqueue("mtp_read_send"); + if (!mtpg->wq) { + printk(KERN_ERR "mtpg_dev_alloc work queue creation failed\n"); + rc = -ENOMEM; + goto err_work; + } + + INIT_WORK(&mtpg->read_send_work, read_send_work); /* the_mtpg must be set before calling usb_gadget_register_driver */ the_mtpg = mtpg; @@ -1454,7 +1617,7 @@ static int mtp_setup(void) } return 0; - +err_work: err_misc_register: the_mtpg = NULL; kfree(mtpg); diff --git a/drivers/usb/gadget/multi_config.c b/drivers/usb/gadget/multi_config.c index 2e4f52e..95f2ac3 100644 --- a/drivers/usb/gadget/multi_config.c +++ b/drivers/usb/gadget/multi_config.c @@ -258,3 +258,13 @@ void set_string_mode(u16 w_length) stringMode = OTHER_REQUEST; } } + +/* Description : Get Host OS type + * Return value : type - u16 + * - 0 : MAC PC + * - 1 : Windows and Linux PC + */ +u16 get_host_os_type(void) +{ + return stringMode; +} diff --git a/drivers/usb/gadget/multi_config.h b/drivers/usb/gadget/multi_config.h index 792bbae..cc944ad 100644 --- a/drivers/usb/gadget/multi_config.h +++ b/drivers/usb/gadget/multi_config.h @@ -132,4 +132,11 @@ void set_interface_count(struct usb_configuration *config, * - Windows and Linux PC always request 255 size. */ void set_string_mode(u16 w_length); + +/* Description : Get Host OS type + * Return value : type - u16 + * - 0 : MAC PC + * - 1 : Windows and Linux PC + */ +u16 get_host_os_type(void); #endif /* __MULTI_CONFIG_H */ diff --git a/drivers/usb/gadget/s3c_udc.h b/drivers/usb/gadget/s3c_udc.h index a75f8f2..c6bf986 100644 --- a/drivers/usb/gadget/s3c_udc.h +++ b/drivers/usb/gadget/s3c_udc.h @@ -142,7 +142,11 @@ struct s3c_udc { unsigned req_pending:1, req_std:1, req_config:1; struct wake_lock usbd_wake_lock; struct wake_lock usb_cb_wake_lock; + int softconnect; int udc_enabled; + int is_usb_ready; + struct delayed_work usb_ready_work; + struct mutex mutex; }; extern struct s3c_udc *the_controller; diff --git a/drivers/usb/gadget/s3c_udc_otg.c b/drivers/usb/gadget/s3c_udc_otg.c index 6703973..a4f33cd 100644 --- a/drivers/usb/gadget/s3c_udc_otg.c +++ b/drivers/usb/gadget/s3c_udc_otg.c @@ -218,6 +218,21 @@ udc_proc_read(char *page, char **start, off_t off, int count, #include "s3c_udc_otg_xfer_dma.c" /* +* udc_core_disconnect +* Ask On Connection - Vzw requirement +*/ +static void udc_core_disconect(struct s3c_udc *dev) +{ + u32 uTemp; + + printk(KERN_DEBUG "usb: %s -dev->softconnect=%d\n", + __func__, dev->softconnect); + uTemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + uTemp |= SOFT_DISCONNECT; + __raw_writel(uTemp, dev->regs + S3C_UDC_OTG_DCTL); +} + +/* * udc_disable - disable USB device controller */ static void udc_disable(struct s3c_udc *dev) @@ -307,6 +322,18 @@ int s3c_vbus_enable(struct usb_gadget *gadget, int is_active) { unsigned long flags; struct s3c_udc *dev = container_of(gadget, struct s3c_udc, gadget); + mutex_lock(&dev->mutex); + + if (dev->is_usb_ready) { + printk(KERN_DEBUG "usb: %s, ready u_e: %d, is_active: %d\n", + __func__, dev->udc_enabled, is_active); + } else { /* USB is not ready to enable USB PHY */ + printk(KERN_DEBUG "usb: %s, not ready u_e: %d, is_active: %d\n", + __func__, dev->udc_enabled, is_active); + dev->udc_enabled = is_active; + mutex_unlock(&dev->mutex); + return 0; + } if (dev->udc_enabled != is_active) { dev->udc_enabled = is_active; @@ -324,11 +351,14 @@ int s3c_vbus_enable(struct usb_gadget *gadget, int is_active) wake_lock_timeout(&dev->usbd_wake_lock, HZ * 5); wake_lock_timeout(&dev->usb_cb_wake_lock, HZ * 5); } else { - printk(KERN_DEBUG "usb: %s is_active=%d(udc_enable)\n", - __func__, is_active); + printk(KERN_DEBUG "usb: %s is_active=%d(udc_enable)," + "softconnect=%d\n", + __func__, is_active, dev->softconnect); wake_lock(&dev->usb_cb_wake_lock); udc_reinit(dev); udc_enable(dev); + if (!dev->softconnect) + udc_core_disconect(dev); } } else { printk(KERN_DEBUG "usb: %s, udc_enabled : %d, is_active : %d\n", @@ -336,7 +366,7 @@ int s3c_vbus_enable(struct usb_gadget *gadget, int is_active) } - + mutex_unlock(&dev->mutex); return 0; } @@ -894,6 +924,9 @@ static void s3c_udc_soft_disconnect(void) static int s3c_udc_pullup(struct usb_gadget *gadget, int is_on) { + struct s3c_udc *dev = container_of(gadget, struct s3c_udc, gadget); + dev->softconnect = is_on; + if (is_on) s3c_udc_soft_connect(); else @@ -1157,6 +1190,27 @@ static struct s3c_udc memory = { }, }; +static void usb_ready(struct work_struct *work) +{ + struct s3c_udc *dev = + container_of(work, struct s3c_udc, usb_ready_work.work); + + if (!dev) { + printk(KERN_DEBUG "usb: %s dev is NULL\n", __func__); + return ; + } + + printk(KERN_DEBUG "usb: %s udc_enable=%d\n", + __func__, dev->udc_enabled); + + dev->is_usb_ready = true; + + if (dev->udc_enabled) { + dev->udc_enabled = 0; + s3c_vbus_enable(&dev->gadget, 1); + } +} + /* * probe - binds to the platform device */ @@ -1262,6 +1316,10 @@ static int s3c_udc_probe(struct platform_device *pdev) create_proc_files(); + INIT_DELAYED_WORK(&dev->usb_ready_work, usb_ready); + schedule_delayed_work(&dev->usb_ready_work, msecs_to_jiffies(15000)); + mutex_init(&dev->mutex); + return retval; err_clk: clk_put(dev->clk); @@ -1298,6 +1356,8 @@ static int s3c_udc_remove(struct platform_device *pdev) the_controller = 0; wake_lock_destroy(&dev->usbd_wake_lock); wake_lock_destroy(&dev->usb_cb_wake_lock); + cancel_delayed_work(&dev->usb_ready_work); + mutex_destroy(&dev->mutex); return 0; } diff --git a/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c index 7554772..97fc349 100644 --- a/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c +++ b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c @@ -52,7 +52,7 @@ static u8 test_pkt[TEST_PKT_SIZE] __attribute__((aligned(8))) = { static void s3c_udc_ep_set_stall(struct s3c_ep *ep); -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) u32 cable_connected; void s3c_udc_cable_connect(struct s3c_udc *dev) @@ -559,7 +559,7 @@ static irqreturn_t s3c_udc_irq(int irq, void *_dev) spin_lock(&dev->lock); } -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) s3c_udc_cable_disconnect(dev); #endif } @@ -1346,7 +1346,7 @@ static void s3c_ep0_setup(struct s3c_udc *dev) reset_available = 1; dev->req_config = 1; } -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) s3c_udc_cable_connect(dev); #endif break; diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c index 4dd598c..70a474c 100644 --- a/drivers/usb/gadget/storage_common.c +++ b/drivers/usb/gadget/storage_common.c @@ -205,6 +205,11 @@ struct interrupt_data { /* Length of a SCSI Command Data Block */ #define MAX_COMMAND_SIZE 16 +#if defined(CONFIG_USB_CDFS_SUPPORT) +/* SCSI commands that we recognize */ +#define READ_CD 0xbe +#endif + /* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ #define SS_NO_SENSE 0 #define SS_COMMUNICATION_FAILURE 0x040800 diff --git a/drivers/usb/gadget/u_composite_notifier.c b/drivers/usb/gadget/u_composite_notifier.c new file mode 100644 index 0000000..62ff621 --- /dev/null +++ b/drivers/usb/gadget/u_composite_notifier.c @@ -0,0 +1,40 @@ +/* + * File Name : u_composite_notifier.c + * + * Copyright (C) 2012 Samsung Electronics + * + * 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. + * + */ +static char function_lists_string[256]; + +static BLOCKING_NOTIFIER_HEAD(usb_composite_notifier_list); + +int register_usb_composite_notifier(struct notifier_block *notifier) +{ + int retval; + + retval = blocking_notifier_chain_register( + &usb_composite_notifier_list, notifier); + + return retval; +} +EXPORT_SYMBOL(register_usb_composite_notifier); + +int unregister_usb_composite_notifier(struct notifier_block *notifier) +{ + int retval; + + retval = blocking_notifier_chain_unregister( + &usb_composite_notifier_list, notifier); + + return retval; +} +EXPORT_SYMBOL(unregister_usb_composite_notifier); |