From cd79304d63d11ebac3c564e1bcea54911cefd1d6 Mon Sep 17 00:00:00 2001 From: Kisang Lee Date: Thu, 17 May 2018 20:47:04 +0900 Subject: [PATCH] [COMMON] usb: otg/functions: add otg and fucntion driver Change-Id: I4f7a54858b8a9ea8483a21bc4953248dcddd543e Signed-off-by: Kisang Lee --- drivers/usb/dwc3/otg.c | 750 +++++++++++++ drivers/usb/dwc3/otg.h | 108 ++ drivers/usb/gadget/function/f_adb.c | 827 ++++++++++++++ drivers/usb/gadget/function/f_dm.c | 501 +++++++++ drivers/usb/gadget/function/f_mtp.c | 1605 +++++++++++++++++++++++++++ drivers/usb/gadget/function/f_mtp.h | 18 + drivers/usb/gadget/function/f_ptp.c | 38 + drivers/usb/gadget/function/f_uts.c | 827 ++++++++++++++ include/linux/f_mtp.h | 23 + include/linux/usb/f_mtp.h | 23 + include/linux/usb/otg-fsm.h | 2 + include/uapi/linux/usb/f_mtp.h | 61 + 12 files changed, 4783 insertions(+) create mode 100644 drivers/usb/dwc3/otg.c create mode 100644 drivers/usb/dwc3/otg.h create mode 100644 drivers/usb/gadget/function/f_adb.c create mode 100644 drivers/usb/gadget/function/f_dm.c create mode 100644 drivers/usb/gadget/function/f_mtp.c create mode 100644 drivers/usb/gadget/function/f_mtp.h create mode 100644 drivers/usb/gadget/function/f_ptp.c create mode 100644 drivers/usb/gadget/function/f_uts.c create mode 100644 include/linux/f_mtp.h create mode 100644 include/linux/usb/f_mtp.h create mode 100644 include/uapi/linux/usb/f_mtp.h diff --git a/drivers/usb/dwc3/otg.c b/drivers/usb/dwc3/otg.c new file mode 100644 index 000000000000..aa21ba4b0802 --- /dev/null +++ b/drivers/usb/dwc3/otg.c @@ -0,0 +1,750 @@ +/** + * otg.c - DesignWare USB3 DRD Controller OTG + * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Authors: Ido Shayevitz + * Anton Tikhomirov + * Minho Lee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * 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 +#include +#include +#include +#include +#include +#ifdef CONFIG_EXYNOS_PD +#include +#endif + +#include "core.h" +#include "otg.h" +#include "io.h" + +/* -------------------------------------------------------------------------- */ +int otg_connection; +static int dwc3_otg_statemachine(struct otg_fsm *fsm) +{ + struct usb_otg *otg = fsm->otg; + enum usb_otg_state prev_state = otg->state; + int ret = 0; + + if (fsm->reset) { + if (otg->state == OTG_STATE_A_HOST) { + otg_drv_vbus(fsm, 0); + otg_start_host(fsm, 0); + } else if (otg->state == OTG_STATE_B_PERIPHERAL) { + otg_start_gadget(fsm, 0); + } + + otg->state = OTG_STATE_UNDEFINED; + goto exit; + } + + switch (otg->state) { + case OTG_STATE_UNDEFINED: + if (fsm->id) + otg->state = OTG_STATE_B_IDLE; + else + otg->state = OTG_STATE_A_IDLE; + break; + case OTG_STATE_B_IDLE: + if (!fsm->id) { + otg->state = OTG_STATE_A_IDLE; + } else if (fsm->b_sess_vld) { + ret = otg_start_gadget(fsm, 1); + if (!ret) + otg->state = OTG_STATE_B_PERIPHERAL; + else + pr_err("OTG SM: cannot start gadget\n"); + } + break; + case OTG_STATE_B_PERIPHERAL: + if (!fsm->id || !fsm->b_sess_vld) { + ret = otg_start_gadget(fsm, 0); + if (!ret) + otg->state = OTG_STATE_B_IDLE; + else + pr_err("OTG SM: cannot stop gadget\n"); + } + break; + case OTG_STATE_A_IDLE: + if (fsm->id) { + otg->state = OTG_STATE_B_IDLE; + } else { + ret = otg_start_host(fsm, 1); + if (!ret) { + otg_drv_vbus(fsm, 1); + otg->state = OTG_STATE_A_HOST; + } else { + pr_err("OTG SM: cannot start host\n"); + } + } + break; + case OTG_STATE_A_HOST: + if (fsm->id) { + otg_drv_vbus(fsm, 0); + ret = otg_start_host(fsm, 0); + if (!ret) + otg->state = OTG_STATE_A_IDLE; + else + pr_err("OTG SM: cannot stop host\n"); + } + break; + default: + pr_err("OTG SM: invalid state\n"); + } + +exit: + if (!ret) + ret = (otg->state != prev_state); + + pr_debug("OTG SM: %s => %s\n", usb_otg_state_string(prev_state), + (ret > 0) ? usb_otg_state_string(otg->state) : "(no change)"); + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +static struct dwc3_ext_otg_ops *dwc3_otg_exynos_rsw_probe(struct dwc3 *dwc) +{ + struct dwc3_ext_otg_ops *ops; + bool ext_otg; + + ext_otg = dwc3_exynos_rsw_available(dwc->dev->parent); + if (!ext_otg) + return NULL; + + /* Allocate and init otg instance */ + ops = devm_kzalloc(dwc->dev, sizeof(struct dwc3_ext_otg_ops), + GFP_KERNEL); + if (!ops) { + dev_err(dwc->dev, "unable to allocate dwc3_ext_otg_ops\n"); + return NULL; + } + + ops->setup = dwc3_exynos_rsw_setup; + ops->exit = dwc3_exynos_rsw_exit; + ops->start = dwc3_exynos_rsw_start; + ops->stop = dwc3_exynos_rsw_stop; + + return ops; +} + +static void dwc3_otg_set_host_mode(struct dwc3_otg *dotg) +{ + struct dwc3 *dwc = dotg->dwc; + u32 reg; + + if (dotg->regs) { + reg = dwc3_readl(dotg->regs, DWC3_OCTL); + reg &= ~DWC3_OTG_OCTL_PERIMODE; + dwc3_writel(dotg->regs, DWC3_OCTL, reg); + } else { + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); + } +} + +static void dwc3_otg_set_peripheral_mode(struct dwc3_otg *dotg) +{ + struct dwc3 *dwc = dotg->dwc; + u32 reg; + + if (dotg->regs) { + reg = dwc3_readl(dotg->regs, DWC3_OCTL); + reg |= DWC3_OTG_OCTL_PERIMODE; + dwc3_writel(dotg->regs, DWC3_OCTL, reg); + } else { + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + } +} + +static void dwc3_otg_drv_vbus(struct otg_fsm *fsm, int on) +{ + struct dwc3_otg *dotg = container_of(fsm, struct dwc3_otg, fsm); + int ret; + + if (IS_ERR(dotg->vbus_reg)) { + dev_err(dotg->dwc->dev, "vbus regulator is not available\n"); + return; + } + + if (on) + ret = regulator_enable(dotg->vbus_reg); + else + ret = regulator_disable(dotg->vbus_reg); + + if (ret) + dev_err(dotg->dwc->dev, "failed to turn Vbus %s\n", + on ? "on" : "off"); +} + +static void dwc3_otg_ldo_control(struct otg_fsm *fsm, int on) +{ +#if defined(OTG_LDO_CONTROL_ENABLED) + struct usb_otg *otg = fsm->otg; + struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg); + struct device *dev = dotg->dwc->dev; + int i; + + dev_info(dev, "Turn %s LDO\n", on ? "on" : "off"); + + if (on) { + for (i = 0; i < dotg->ldos; i++) + s2m_ldo_set_mode(dotg->ldo_num[i], 0x3); + } else { + for (i = 0; i < dotg->ldos; i++) + s2m_ldo_set_mode(dotg->ldo_num[i], 0x1); + } +#endif + + return; +} +static int dwc3_otg_start_host(struct otg_fsm *fsm, int on) +{ + struct usb_otg *otg = fsm->otg; + struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg); + struct dwc3 *dwc = dotg->dwc; + struct device *dev = dotg->dwc->dev; + int ret = 0; + + if (!dotg->dwc->xhci) { + dev_err(dev, "%s: does not have any xhci\n", __func__); + return -EINVAL; + } + + dev_info(dev, "Turn %s host\n", on ? "on" : "off"); + if (on) { + otg_connection = 1; + dwc3_otg_ldo_control(fsm, 1); + pm_runtime_get_sync(dev); + ret = dwc3_phy_setup(dwc); + if (ret) { + dev_err(dwc->dev, "%s: failed to setup phy\n", + __func__); + goto err1; + } + ret = dwc3_core_init(dwc); + if (ret) { + dev_err(dwc->dev, "%s: failed to reinitialize core\n", + __func__); + goto err1; + } + + phy_conn(dwc->usb2_generic_phy, 1); + + /** + * In case there is not a resistance to detect VBUS, + * DP/DM controls by S/W are needed at this point. + */ + if (dwc->is_not_vbus_pad) { + phy_set(dwc->usb2_generic_phy, + SET_DPDM_PULLDOWN, NULL); + phy_set(dwc->usb3_generic_phy, + SET_DPDM_PULLDOWN, NULL); + } + + dwc3_otg_set_host_mode(dotg); + ret = platform_device_add(dwc->xhci); + if (ret) { + dev_err(dev, "%s: cannot add xhci\n", __func__); + goto err2; + } + } else { + otg_connection = 0; + platform_device_del(dwc->xhci); +err2: + phy_conn(dwc->usb2_generic_phy, 0); + + dwc3_core_exit(dwc); +err1: + pm_runtime_put_sync(dev); + dwc3_otg_ldo_control(fsm, 0); + } + + return ret; +} + +static int dwc3_otg_start_gadget(struct otg_fsm *fsm, int on) +{ + struct usb_otg *otg = fsm->otg; + struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg); + struct dwc3 *dwc = dotg->dwc; + struct device *dev = dotg->dwc->dev; + int ret = 0; + + if (!otg->gadget) { + dev_err(dev, "%s does not have any gadget\n", __func__); + return -EINVAL; + } + + dev_info(dev, "Turn %s gadget %s\n", + on ? "on" : "off", otg->gadget->name); + + if (on) { + wake_lock(&dotg->wakelock); + ret = pm_runtime_get_sync(dev); + dev_info(dev, "%s pm_runtime_get_sync = %d\n", + __func__, ret); + ret = dwc3_phy_setup(dwc); + if (ret) { + dev_err(dwc->dev, "%s: failed to setup phy\n", + __func__); + goto err1; + } + + ret = dwc3_core_init(dwc); + if (ret) { + dev_err(dwc->dev, "%s: failed to reinitialize core\n", + __func__); + goto err1; + } + + dwc3_otg_set_peripheral_mode(dotg); + ret = usb_gadget_vbus_connect(otg->gadget); + if (ret) { + dev_err(dwc->dev, "%s: vbus connect failed\n", + __func__); + goto err2; + } + + } else { + if (dwc->is_not_vbus_pad) + dwc3_gadget_disconnect_proc(dwc); + /* avoid missing disconnect interrupt */ + ret = wait_for_completion_timeout(&dwc->disconnect, + msecs_to_jiffies(200)); + if (!ret) { + dev_err(dwc->dev, "%s: disconnect completion timeout\n", + __func__); + return ret; + } + ret = usb_gadget_vbus_disconnect(otg->gadget); + if (ret) + dev_err(dwc->dev, "%s: vbus disconnect failed\n", + __func__); +err2: + dwc3_core_exit(dwc); +err1: + pm_runtime_put_sync(dev); + wake_unlock(&dotg->wakelock); + } + + return ret; +} + +static struct otg_fsm_ops dwc3_otg_fsm_ops = { + .drv_vbus = dwc3_otg_drv_vbus, + .start_host = dwc3_otg_start_host, + .start_gadget = dwc3_otg_start_gadget, +}; + +/* -------------------------------------------------------------------------- */ + +void dwc3_otg_run_sm(struct otg_fsm *fsm) +{ + struct dwc3_otg *dotg = container_of(fsm, struct dwc3_otg, fsm); + int state_changed; + + /* Prevent running SM on early system resume */ + if (!dotg->ready) + return; + + mutex_lock(&fsm->lock); + + do { + state_changed = dwc3_otg_statemachine(fsm); + } while (state_changed > 0); + + mutex_unlock(&fsm->lock); +} + +/* Bind/Unbind the peripheral controller driver */ +static int dwc3_otg_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg); + struct otg_fsm *fsm = &dotg->fsm; + struct device *dev = dotg->dwc->dev; + + if (gadget) { + dev_info(dev, "Binding gadget %s\n", gadget->name); + + otg->gadget = gadget; + } else { + dev_info(dev, "Unbinding gadget\n"); + + mutex_lock(&fsm->lock); + + if (otg->state == OTG_STATE_B_PERIPHERAL) { + /* Reset OTG Statemachine */ + fsm->reset = 1; + dwc3_otg_statemachine(fsm); + fsm->reset = 0; + } + otg->gadget = NULL; + + mutex_unlock(&fsm->lock); + + dwc3_otg_run_sm(fsm); + } + + return 0; +} + +static int dwc3_otg_get_id_state(struct dwc3_otg *dotg) +{ + u32 reg = dwc3_readl(dotg->regs, DWC3_OSTS); + + return !!(reg & DWC3_OTG_OSTS_CONIDSTS); +} + +static int dwc3_otg_get_b_sess_state(struct dwc3_otg *dotg) +{ + u32 reg = dwc3_readl(dotg->regs, DWC3_OSTS); + + return !!(reg & DWC3_OTG_OSTS_BSESVALID); +} + +static irqreturn_t dwc3_otg_interrupt(int irq, void *_dotg) +{ + struct dwc3_otg *dotg = (struct dwc3_otg *)_dotg; + struct otg_fsm *fsm = &dotg->fsm; + u32 oevt, handled_events = 0; + irqreturn_t ret = IRQ_NONE; + + oevt = dwc3_readl(dotg->regs, DWC3_OEVT); + + /* ID */ + if (oevt & DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT) { + fsm->id = dwc3_otg_get_id_state(dotg); + handled_events |= DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT; + } + + /* VBus */ + if (oevt & DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT) { + fsm->b_sess_vld = dwc3_otg_get_b_sess_state(dotg); + handled_events |= DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT; + } + + if (handled_events) { + dwc3_writel(dotg->regs, DWC3_OEVT, handled_events); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +static irqreturn_t dwc3_otg_thread_interrupt(int irq, void *_dotg) +{ + struct dwc3_otg *dotg = (struct dwc3_otg *)_dotg; + + dwc3_otg_run_sm(&dotg->fsm); + + return IRQ_HANDLED; +} + +static void dwc3_otg_enable_irq(struct dwc3_otg *dotg) +{ + /* Enable only connector ID status & VBUS change events */ + dwc3_writel(dotg->regs, DWC3_OEVTEN, + DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT | + DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT); +} + +static void dwc3_otg_disable_irq(struct dwc3_otg *dotg) +{ + dwc3_writel(dotg->regs, DWC3_OEVTEN, 0x0); +} + +static void dwc3_otg_reset(struct dwc3_otg *dotg) +{ + /* + * OCFG[2] - OTG-Version = 0 + * OCFG[1] - HNPCap = 0 + * OCFG[0] - SRPCap = 0 + */ + dwc3_writel(dotg->regs, DWC3_OCFG, 0x0); + + /* + * OCTL[6] - PeriMode = 1 + * OCTL[5] - PrtPwrCtl = 0 + * OCTL[4] - HNPReq = 0 + * OCTL[3] - SesReq = 0 + * OCTL[2] - TermSelDLPulse = 0 + * OCTL[1] - DevSetHNPEn = 0 + * OCTL[0] - HstSetHNPEn = 0 + */ + dwc3_writel(dotg->regs, DWC3_OCTL, DWC3_OTG_OCTL_PERIMODE); + + /* Clear all otg events (interrupts) indications */ + dwc3_writel(dotg->regs, DWC3_OEVT, DWC3_OEVT_CLEAR_ALL); +} + +/* -------------------------------------------------------------------------- */ + +static ssize_t +dwc3_otg_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct usb_otg *otg = &dwc->dotg->otg; + + return snprintf(buf, PAGE_SIZE, "%s\n", + usb_otg_state_string(otg->state)); +} + +static DEVICE_ATTR(state, S_IRUSR | S_IRGRP, + dwc3_otg_show_state, NULL); + +static ssize_t +dwc3_otg_show_b_sess(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct otg_fsm *fsm = &dwc->dotg->fsm; + + return snprintf(buf, PAGE_SIZE, "%d\n", fsm->b_sess_vld); +} + +static ssize_t +dwc3_otg_store_b_sess(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct otg_fsm *fsm = &dwc->dotg->fsm; + int b_sess_vld; + + if (sscanf(buf, "%d", &b_sess_vld) != 1) + return -EINVAL; + + fsm->b_sess_vld = !!b_sess_vld; + + dwc3_otg_run_sm(fsm); + + return n; +} + +static DEVICE_ATTR(b_sess, S_IWUSR | S_IRUSR | S_IRGRP, + dwc3_otg_show_b_sess, dwc3_otg_store_b_sess); + +static ssize_t +dwc3_otg_show_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct otg_fsm *fsm = &dwc->dotg->fsm; + + return snprintf(buf, PAGE_SIZE, "%d\n", fsm->id); +} + +static ssize_t +dwc3_otg_store_id(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct otg_fsm *fsm = &dwc->dotg->fsm; + int id; + + if (sscanf(buf, "%d", &id) != 1) + return -EINVAL; + + fsm->id = !!id; + + dwc3_otg_run_sm(fsm); + + return n; +} + +static DEVICE_ATTR(id, S_IWUSR | S_IRUSR | S_IRGRP, + dwc3_otg_show_id, dwc3_otg_store_id); + +static struct attribute *dwc3_otg_attributes[] = { + &dev_attr_id.attr, + &dev_attr_b_sess.attr, + &dev_attr_state.attr, + NULL +}; + +static const struct attribute_group dwc3_otg_attr_group = { + .attrs = dwc3_otg_attributes, +}; + +/** + * dwc3_otg_start + * @dwc: pointer to our controller context structure + */ +int dwc3_otg_start(struct dwc3 *dwc) +{ + struct dwc3_otg *dotg = dwc->dotg; + struct otg_fsm *fsm = &dotg->fsm; + int ret; + + if (dotg->ext_otg_ops) { + ret = dwc3_ext_otg_start(dotg); + if (ret) { + dev_err(dwc->dev, "failed to start external OTG\n"); + return ret; + } + } else { + dotg->regs = dwc->regs; + + dwc3_otg_reset(dotg); + + dotg->fsm.id = dwc3_otg_get_id_state(dotg); + dotg->fsm.b_sess_vld = dwc3_otg_get_b_sess_state(dotg); + + dotg->irq = platform_get_irq(to_platform_device(dwc->dev), 0); + ret = devm_request_threaded_irq(dwc->dev, dotg->irq, + dwc3_otg_interrupt, + dwc3_otg_thread_interrupt, + IRQF_SHARED, "dwc3-otg", dotg); + if (ret) { + dev_err(dwc->dev, "failed to request irq #%d --> %d\n", + dotg->irq, ret); + return ret; + } + + dwc3_otg_enable_irq(dotg); + } + + dotg->ready = 1; + + dwc3_otg_run_sm(fsm); + + return 0; +} + +/** + * dwc3_otg_stop + * @dwc: pointer to our controller context structure + */ +void dwc3_otg_stop(struct dwc3 *dwc) +{ + struct dwc3_otg *dotg = dwc->dotg; + + if (dotg->ext_otg_ops) { + dwc3_ext_otg_stop(dotg); + } else { + dwc3_otg_disable_irq(dotg); + free_irq(dotg->irq, dotg); + } + + dotg->ready = 0; +} + +/* -------------------------------------------------------------------------- */ +bool otg_is_connect(void) +{ + if (otg_connection) + return true; + else + return false; +} +EXPORT_SYMBOL_GPL(otg_is_connect); + +int dwc3_otg_init(struct dwc3 *dwc) +{ + struct dwc3_otg *dotg; + struct dwc3_ext_otg_ops *ops = NULL; + int ret = 0; + + dev_info(dwc->dev, "%s\n", __func__); + + /* EXYNOS SoCs don't have HW OTG, but support SW OTG. */ + ops = dwc3_otg_exynos_rsw_probe(dwc); + if (!ops) + return 0; + + /* Allocate and init otg instance */ + dotg = devm_kzalloc(dwc->dev, sizeof(struct dwc3_otg), GFP_KERNEL); + if (!dotg) { + dev_err(dwc->dev, "unable to allocate dwc3_otg\n"); + return -ENOMEM; + } + + /* This reference is used by dwc3 modules for checking otg existance */ + dwc->dotg = dotg; + dotg->dwc = dwc; + + ret = of_property_read_u32(dwc->dev->of_node,"ldos", &dotg->ldos); + if (ret < 0) { + dev_err(dwc->dev, "can't get ldo information\n"); + return -EINVAL; + } else { + if (dotg->ldos) { + dev_info(dwc->dev, "have %d LDOs for supporting USB L2 suspend \n", dotg->ldos); + dotg->ldo_num = (int *)devm_kmalloc(dwc->dev, sizeof(int) * (dotg->ldos), + GFP_KERNEL); + ret = of_property_read_u32_array(dwc->dev->of_node, + "ldo_number", dotg->ldo_num, dotg->ldos); + } else { + dev_info(dwc->dev, "don't have LDOs for supporting USB L2 suspend \n"); + } + } + + dotg->ext_otg_ops = ops; + + dotg->otg.set_peripheral = dwc3_otg_set_peripheral; + dotg->otg.set_host = NULL; + + dotg->otg.state = OTG_STATE_UNDEFINED; + + mutex_init(&dotg->fsm.lock); + dotg->fsm.ops = &dwc3_otg_fsm_ops; + dotg->fsm.otg = &dotg->otg; + + dotg->vbus_reg = devm_regulator_get(dwc->dev->parent, "dwc3-vbus"); + if (IS_ERR(dotg->vbus_reg)) + dev_err(dwc->dev, "failed to obtain vbus regulator\n"); + + if (dotg->ext_otg_ops) { + ret = dwc3_ext_otg_setup(dotg); + if (ret) { + dev_err(dwc->dev, "failed to setup OTG\n"); + return ret; + } + } + + wake_lock_init(&dotg->wakelock, WAKE_LOCK_SUSPEND, "dwc3-otg"); + + ret = sysfs_create_group(&dwc->dev->kobj, &dwc3_otg_attr_group); + if (ret) + dev_err(dwc->dev, "failed to create dwc3 otg attributes\n"); + +#ifdef CONFIG_SOC_EXYNOS9810 + register_usb_is_connect(NULL); +#endif + + return 0; +} + +void dwc3_otg_exit(struct dwc3 *dwc) +{ + struct dwc3_otg *dotg = dwc->dotg; + + if (!dotg->ext_otg_ops) + return; + + dwc3_ext_otg_exit(dotg); + + sysfs_remove_group(&dwc->dev->kobj, &dwc3_otg_attr_group); + wake_lock_destroy(&dotg->wakelock); + free_irq(dotg->irq, dotg); + dotg->otg.state = OTG_STATE_UNDEFINED; + kfree(dotg); + dwc->dotg = NULL; +} diff --git a/drivers/usb/dwc3/otg.h b/drivers/usb/dwc3/otg.h new file mode 100644 index 000000000000..d51dafd95d08 --- /dev/null +++ b/drivers/usb/dwc3/otg.h @@ -0,0 +1,108 @@ +/** + * otg.c - DesignWare USB3 DRD Controller OTG + * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Authors: Ido Shayevitz + * Anton Tikhomirov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * 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 __LINUX_USB_DWC3_OTG_H +#define __LINUX_USB_DWC3_OTG_H +#include +#include + +struct dwc3_ext_otg_ops { + int (*setup)(struct device *dev, struct otg_fsm *fsm); + void (*exit)(struct device *dev); + int (*start) (struct device *dev); + void (*stop)(struct device *dev); +}; + +/** + * struct dwc3_otg: OTG driver data. Shared by HCD and DCD. + * @otg: USB OTG Transceiver structure. + * @fsm: OTG Final State Machine. + * @dwc: pointer to our controller context structure. + * @irq: IRQ number assigned for HSUSB controller. + * @regs: ioremapped register base address. + * @wakelock: prevents the system from entering suspend while + * host or peripheral mode is active. + * @vbus_reg: Vbus regulator. + * @ready: is one when OTG is ready for operation. + * @ext_otg_ops: external OTG engine ops. + */ +struct dwc3_otg { + struct usb_otg otg; + struct otg_fsm fsm; + struct dwc3 *dwc; + int irq; + void __iomem *regs; + struct wake_lock wakelock; + + unsigned ready:1; + int otg_connection; + + struct regulator *vbus_reg; + int *ldo_num; + int ldos; + + struct dwc3_ext_otg_ops *ext_otg_ops; +}; + +static inline int dwc3_ext_otg_setup(struct dwc3_otg *dotg) +{ + struct device *dev = dotg->dwc->dev->parent; + + if (!dotg->ext_otg_ops->setup) + return -EOPNOTSUPP; + return dotg->ext_otg_ops->setup(dev, &dotg->fsm); +} + +static inline int dwc3_ext_otg_exit(struct dwc3_otg *dotg) +{ + struct device *dev = dotg->dwc->dev->parent; + + if (!dotg->ext_otg_ops->exit) + return -EOPNOTSUPP; + dotg->ext_otg_ops->exit(dev); + return 0; +} + +static inline int dwc3_ext_otg_start(struct dwc3_otg *dotg) +{ + struct device *dev = dotg->dwc->dev->parent; + + if (!dotg->ext_otg_ops->start) + return -EOPNOTSUPP; + return dotg->ext_otg_ops->start(dev); +} + +static inline int dwc3_ext_otg_stop(struct dwc3_otg *dotg) +{ + struct device *dev = dotg->dwc->dev->parent; + + if (!dotg->ext_otg_ops->stop) + return -EOPNOTSUPP; + dotg->ext_otg_ops->stop(dev); + return 0; +} + +bool dwc3_exynos_rsw_available(struct device *dev); +int dwc3_exynos_rsw_setup(struct device *dev, struct otg_fsm *fsm); +void dwc3_exynos_rsw_exit(struct device *dev); +int dwc3_exynos_rsw_start(struct device *dev); +void dwc3_exynos_rsw_stop(struct device *dev); + +#endif /* __LINUX_USB_DWC3_OTG_H */ diff --git a/drivers/usb/gadget/function/f_adb.c b/drivers/usb/gadget/function/f_adb.c new file mode 100644 index 000000000000..a8829e2ff2ef --- /dev/null +++ b/drivers/usb/gadget/function/f_adb.c @@ -0,0 +1,827 @@ +/* + * Gadget Driver for Android ADB + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../configfs.h" + +#define ADB_BULK_BUFFER_SIZE 4096 +#define MAX_INST_NAME_LEN 40 + +/* number of tx requests to allocate */ +#define TX_REQ_MAX 4 + +static const char adb_shortname[] = "android_adb"; + +struct adb_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + int online; + int error; + + atomic_t read_excl; + atomic_t write_excl; + atomic_t open_excl; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req; + int rx_done; +}; + +struct adb_instance { + const char *name; + struct usb_function_instance func_inst; + struct adb_dev *dev; +}; + +static struct usb_interface_descriptor adb_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0x42, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor adb_superspeed_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(1024), +}; + +static struct usb_endpoint_descriptor adb_superspeed_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(1024), +}; + +static struct usb_ss_ep_comp_descriptor adb_superspeed_bulk_comp_desc = { + .bLength = sizeof adb_superspeed_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor adb_highspeed_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), +}; + +static struct usb_endpoint_descriptor adb_highspeed_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), +}; + +static struct usb_endpoint_descriptor adb_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor adb_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_fullspeed_in_desc, + (struct usb_descriptor_header *) &adb_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_highspeed_in_desc, + (struct usb_descriptor_header *) &adb_highspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *ss_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_superspeed_in_desc, + (struct usb_descriptor_header *) &adb_superspeed_bulk_comp_desc, + (struct usb_descriptor_header *) &adb_superspeed_out_desc, + (struct usb_descriptor_header *) &adb_superspeed_bulk_comp_desc, + NULL, +}; + +#ifndef CONFIG_USB_CONFIGFS_UEVENT +static void adb_ready_callback(void); +static void adb_closed_callback(void); +#endif + +/* temporary variable used between adb_open() and adb_gadget_bind() */ +static struct adb_dev *_adb_dev; + +static inline struct adb_dev *func_to_adb(struct usb_function *f) +{ + return container_of(f, struct adb_dev, function); +} + + +static struct usb_request *adb_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void adb_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int adb_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void adb_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +void adb_req_put(struct adb_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +struct usb_request *adb_req_get(struct adb_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void adb_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct adb_dev *dev = _adb_dev; + + if (req->status != 0) + dev->error = 1; + + adb_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void adb_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct adb_dev *dev = _adb_dev; + + dev->rx_done = 1; + if (req->status != 0 && req->status != -ECONNRESET) + dev->error = 1; + + wake_up(&dev->read_wq); +} + +static int adb_create_bulk_endpoints(struct adb_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for adb ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + req = adb_request_new(dev->ep_out, ADB_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = adb_complete_out; + dev->rx_req = req; + + for (i = 0; i < TX_REQ_MAX; i++) { + req = adb_request_new(dev->ep_in, ADB_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = adb_complete_in; + adb_req_put(dev, &dev->tx_idle, req); + } + + return 0; + +fail: + printk(KERN_ERR "adb_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t adb_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_dev *dev = fp->private_data; + struct usb_request *req; + int r = count, xfer; + int maxp; + int ret; + + pr_debug("adb_read(%d)\n", (int)count); + if (!_adb_dev) + return -ENODEV; + + if (adb_lock(&dev->read_excl)) + return -EBUSY; + + /* we will block until we're online */ + while (!(dev->online || dev->error)) { + pr_debug("adb_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + (dev->online || dev->error)); + if (ret < 0) { + adb_unlock(&dev->read_excl); + return ret; + } + } + if (dev->error) { + r = -EIO; + goto done; + } + + maxp = usb_endpoint_maxp(dev->ep_out->desc); + count = round_up(count, maxp); + + if (count > ADB_BULK_BUFFER_SIZE) + return -EINVAL; + +requeue_req: + /* queue a request */ + req = dev->rx_req; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC); + if (ret < 0) { + pr_debug("adb_read: failed to queue req %p (%d)\n", req, ret); + r = -EIO; + dev->error = 1; + goto done; + } else { + pr_debug("rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + if (ret != -ERESTARTSYS) + dev->error = 1; + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (!dev->error) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + pr_debug("rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + + } else + r = -EIO; + +done: + adb_unlock(&dev->read_excl); + pr_debug("adb_read returning %d\n", r); + return r; +} + +static ssize_t adb_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_dev *dev = fp->private_data; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + if (!_adb_dev) + return -ENODEV; + pr_debug("adb_write(%d)\n", (int)count); + + if (adb_lock(&dev->write_excl)) + return -EBUSY; + + while (count > 0) { + if (dev->error) { + pr_debug("adb_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = adb_req_get(dev, &dev->tx_idle)) || dev->error); + + if (ret < 0) { + r = ret; + break; + } + + if (req != 0) { + if (count > ADB_BULK_BUFFER_SIZE) + xfer = ADB_BULK_BUFFER_SIZE; + else + xfer = count; + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC); + if (ret < 0) { + pr_debug("adb_write: xfer error %d\n", ret); + dev->error = 1; + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + if (req) + adb_req_put(dev, &dev->tx_idle, req); + + adb_unlock(&dev->write_excl); + pr_debug("adb_write returning %d\n", r); + return r; +} + +static int adb_open(struct inode *ip, struct file *fp) +{ + pr_info("adb_open\n"); + if (!_adb_dev) + return -ENODEV; + + if (adb_lock(&_adb_dev->open_excl)) + return -EBUSY; + + fp->private_data = _adb_dev; + + /* clear the error latch */ + _adb_dev->error = 0; + +#ifndef CONFIG_USB_CONFIGFS_UEVENT + adb_ready_callback(); +#endif + + return 0; +} + +static int adb_release(struct inode *ip, struct file *fp) +{ + pr_info("adb_release\n"); + +#ifndef CONFIG_USB_CONFIGFS_UEVENT + adb_closed_callback(); +#endif + + adb_unlock(&_adb_dev->open_excl); + return 0; +} + +/* file operations for ADB device /dev/android_adb */ +static const struct file_operations adb_fops = { + .owner = THIS_MODULE, + .read = adb_read, + .write = adb_write, + .open = adb_open, + .release = adb_release, +}; + +static struct miscdevice adb_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = adb_shortname, + .fops = &adb_fops, +}; + + + + +static int +adb_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct adb_dev *dev = func_to_adb(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "adb_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + adb_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = adb_create_bulk_endpoints(dev, &adb_fullspeed_in_desc, + &adb_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + adb_highspeed_in_desc.bEndpointAddress = + adb_fullspeed_in_desc.bEndpointAddress; + adb_highspeed_out_desc.bEndpointAddress = + adb_fullspeed_out_desc.bEndpointAddress; + } + + /* support super speed hardware */ + if (gadget_is_superspeed(c->cdev->gadget)) { + adb_superspeed_in_desc.bEndpointAddress = + adb_fullspeed_in_desc.bEndpointAddress; + adb_superspeed_out_desc.bEndpointAddress = + adb_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +adb_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_request *req; + + + dev->online = 0; + dev->error = 1; + + wake_up(&dev->read_wq); + + adb_request_free(dev->rx_req, dev->ep_out); + while ((req = adb_req_get(dev, &dev->tx_idle))) + adb_request_free(req, dev->ep_in); +} + +static int adb_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "adb_function_set_alt intf: %d alt: %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_in); + if (ret) + return ret; + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + dev->online = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void adb_function_disable(struct usb_function *f) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "adb_function_disable cdev %p\n", cdev); + dev->online = 0; + dev->error = 1; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +#ifndef CONFIG_USB_CONFIGFS_UEVENT +static int adb_bind_config(struct usb_configuration *c) +{ + struct adb_dev *dev = _adb_dev; + + printk(KERN_INFO "adb_bind_config\n"); + + dev->cdev = c->cdev; + dev->function.name = "adb"; + dev->function.fs_descriptors = fs_adb_descs; + dev->function.hs_descriptors = hs_adb_descs; + dev->function.ss_descriptors = ss_adb_descs; + dev->function.bind = adb_function_bind; + dev->function.unbind = adb_function_unbind; + dev->function.set_alt = adb_function_set_alt; + dev->function.disable = adb_function_disable; + + return usb_add_function(c, &dev->function); +} +#endif + +static int __adb_setup(struct adb_instance *fi_adb) +{ + struct adb_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + if (fi_adb != NULL) + fi_adb->dev = dev; + + spin_lock_init(&dev->lock); + + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->read_excl, 0); + atomic_set(&dev->write_excl, 0); + + INIT_LIST_HEAD(&dev->tx_idle); + + _adb_dev = dev; + + ret = misc_register(&adb_device); + if (ret) + goto err; + + return 0; + +err: + kfree(dev); + printk(KERN_ERR "adb gadget driver failed to initialize\n"); + return ret; +} + +static int adb_setup_configfs(struct adb_instance *fi_adb) +{ + return __adb_setup(fi_adb); +} + +#ifndef CONFIG_USB_CONFIGFS_UEVENT +static int adb_setup(void) +{ + return __adb_setup(NULL); + +} +#endif + +static void adb_cleanup(void) +{ + misc_deregister(&adb_device); + + kfree(_adb_dev); + _adb_dev = NULL; +} + +static struct adb_instance *to_adb_instance(struct config_item *item) +{ + return container_of(to_config_group(item), struct adb_instance, + func_inst.group); +} + +static void adb_attr_release(struct config_item *item) +{ + struct adb_instance *fi_adb = to_adb_instance(item); + usb_put_function_instance(&fi_adb->func_inst); +} + +static struct configfs_item_operations adb_item_ops = { + .release = adb_attr_release, +}; + +static struct config_item_type adb_func_type = { + .ct_item_ops = &adb_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct adb_instance *to_fi_adb(struct usb_function_instance *fi) +{ + return container_of(fi, struct adb_instance, func_inst); +} + +static int adb_set_inst_name(struct usb_function_instance *fi, + const char *name) +{ + struct adb_instance *fi_adb; + char *ptr; + int name_len; + + name_len = strlen(name) + 1; + if (name_len > MAX_INST_NAME_LEN) + return -ENAMETOOLONG; + + ptr = kstrndup(name, name_len, GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + fi_adb = to_fi_adb(fi); + fi_adb->name = ptr; + + return 0; +} + +static void adb_free_inst(struct usb_function_instance *fi) +{ + struct adb_instance *fi_adb; + + fi_adb = to_fi_adb(fi); + kfree(fi_adb->name); + adb_cleanup(); + kfree(fi_adb); +} + +struct usb_function_instance *alloc_inst_adb(bool adb_config) +{ + struct adb_instance *fi_adb; + int ret = 0; + + fi_adb = kzalloc(sizeof(*fi_adb), GFP_KERNEL); + if (!fi_adb) + return ERR_PTR(-ENOMEM); + fi_adb->func_inst.set_inst_name = adb_set_inst_name; + fi_adb->func_inst.free_func_inst = adb_free_inst; + + if (adb_config) { + ret = adb_setup_configfs(fi_adb); + if (ret) { + kfree(fi_adb); + pr_err("Error setting adb\n"); + return ERR_PTR(ret); + } + } else + fi_adb->dev = _adb_dev; + + config_group_init_type_name(&fi_adb->func_inst.group, "", + &adb_func_type); + + return &fi_adb->func_inst; +} +EXPORT_SYMBOL_GPL(alloc_inst_adb); + +static struct usb_function_instance *adb_alloc_inst(void) +{ + return alloc_inst_adb(true); +} + +static void adb_free(struct usb_function *f) +{ + /*NO-OP: no function specific resource allocation in adb_alloc*/ +} + +struct usb_function *function_alloc_adb(struct usb_function_instance *fi, + bool adb_config) +{ + struct adb_instance *fi_adb = to_fi_adb(fi); + struct adb_dev *dev = fi_adb->dev; + + dev->function.name = "adb"; + + dev->function.fs_descriptors = fs_adb_descs; + dev->function.hs_descriptors = hs_adb_descs; + dev->function.ss_descriptors = ss_adb_descs; + + dev->function.bind = adb_function_bind; + dev->function.unbind = adb_function_unbind; + dev->function.set_alt = adb_function_set_alt; + dev->function.disable = adb_function_disable; + dev->function.free_func = adb_free; + + return &dev->function; +} +EXPORT_SYMBOL_GPL(function_alloc_adb); + +static struct usb_function *adb_alloc(struct usb_function_instance *fi) +{ + return function_alloc_adb(fi, true); +} + +DECLARE_USB_FUNCTION_INIT(adb, adb_alloc_inst, adb_alloc); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_dm.c b/drivers/usb/gadget/function/f_dm.c new file mode 100644 index 000000000000..36537dbcf2af --- /dev/null +++ b/drivers/usb/gadget/function/f_dm.c @@ -0,0 +1,501 @@ +/* + * f_dm.c - generic USB serial function driver + * modified from f_serial.c and f_diag.c + * ttyGS* + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 by David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +#include +#include +#include +#include + +#include +#include + +#include "../configfs.h" +#include "u_serial.h" + +#define MAX_INST_NAME_LEN 40 +/* DM_PORT NUM : /dev/ttyGS* port number */ +#define DM_PORT_NUM 1 +/* + * This function packages a simple "generic serial" port with no real + * control mechanisms, just raw data transfer over two bulk endpoints. + * + * Because it's not standardized, this isn't as interoperable as the + * CDC ACM driver. However, for many purposes it's just as functional + * if you can arrange appropriate host side drivers. + */ + +struct dm_descs { + struct usb_endpoint_descriptor *in; + struct usb_endpoint_descriptor *out; +}; + +struct f_dm { + struct gserial port; + u8 data_id; + u8 port_num; + + struct dm_descs fs; + struct dm_descs hs; +}; + +static inline struct f_dm *func_to_dm(struct usb_function *f) +{ + return container_of(f, struct f_dm, port.func); +} + +/*-------------------------------------------------------------------------*/ + +/* interface descriptor: */ +static struct usb_interface_descriptor dm_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = 0x10, + .bInterfaceProtocol = 0x01, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor dm_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor dm_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *dm_fs_function[] = { + (struct usb_descriptor_header *) &dm_interface_desc, + (struct usb_descriptor_header *) &dm_fs_in_desc, + (struct usb_descriptor_header *) &dm_fs_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor dm_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor dm_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; +static struct usb_descriptor_header *dm_hs_function[] = { + (struct usb_descriptor_header *) &dm_interface_desc, + (struct usb_descriptor_header *) &dm_hs_in_desc, + (struct usb_descriptor_header *) &dm_hs_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor dm_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor dm_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor dm_ss_bulk_comp_desc = { + .bLength = sizeof dm_ss_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_descriptor_header *dm_ss_function[] = { + (struct usb_descriptor_header *) &dm_interface_desc, + (struct usb_descriptor_header *) &dm_ss_in_desc, + (struct usb_descriptor_header *) &dm_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &dm_ss_out_desc, + (struct usb_descriptor_header *) &dm_ss_bulk_comp_desc, + NULL, +}; + + +/* string descriptors: */ +#define F_DM_IDX 0 + +static struct usb_string dm_string_defs[] = { + [F_DM_IDX].s = "Samsung Android DM", + { /* ZEROES END LIST */ }, +}; + +static struct usb_gadget_strings dm_string_table = { + .language = 0x0409, /* en-us */ + .strings = dm_string_defs, +}; + +static struct usb_gadget_strings *dm_strings[] = { + &dm_string_table, + NULL, +}; + +struct dm_instance { + struct usb_function_instance func_inst; + const char *name; + struct f_dm *dm; +}; +/*-------------------------------------------------------------------------*/ + +static int dm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_dm *dm = func_to_dm(f); + struct usb_composite_dev *cdev = f->config->cdev; + int status; + + /* we know alt == 0, so this is an activation or a reset */ + + if (dm->port.in->driver_data) { + DBG(cdev, "reset generic ttyGS%d\n", dm->port_num); + gserial_disconnect(&dm->port); + } else { + DBG(cdev, "activate generic ttyGS%d\n", dm->port_num); + } + if (!dm->port.in->desc || !dm->port.out->desc) { + DBG(cdev, "activate dm ttyGS%d\n", dm->port_num); + if (config_ep_by_speed(cdev->gadget, f, + dm->port.in) || + config_ep_by_speed(cdev->gadget, f, + dm->port.out)) { + dm->port.in->desc = NULL; + dm->port.out->desc = NULL; + return -EINVAL; + } + } + + + status = gserial_connect(&dm->port, dm->port_num); + printk(KERN_DEBUG "usb: %s run generic_connect(%d)", __func__, + dm->port_num); + + if (status < 0) { + printk(KERN_DEBUG "fail to activate generic ttyGS%d\n", + dm->port_num); + + return status; + } + + return 0; +} + +static void dm_disable(struct usb_function *f) +{ + struct f_dm *dm = func_to_dm(f); + + printk(KERN_DEBUG "usb: %s generic ttyGS%d deactivated\n", __func__, + dm->port_num); + gserial_disconnect(&dm->port); +} + +/*-------------------------------------------------------------------------*/ + +/* serial function driver setup/binding */ + +static int +dm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_dm *dm = func_to_dm(f); + int status; + struct usb_ep *ep; + + /* maybe allocate device-global string ID */ + if (dm_string_defs[F_DM_IDX].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) + return status; + dm_string_defs[F_DM_IDX].id = status; + } + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + dm->data_id = status; + dm_interface_desc.bInterfaceNumber = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &dm_fs_in_desc); + if (!ep) + goto fail; + dm->port.in = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &dm_fs_out_desc); + if (!ep) + goto fail; + dm->port.out = ep; + ep->driver_data = cdev; /* claim */ + printk(KERN_INFO "[%s] in =0x%p , out =0x%p\n", __func__, + dm->port.in, dm->port.out); + + /* copy descriptors, and track endpoint copies */ + f->fs_descriptors = usb_copy_descriptors(dm_fs_function); + + + + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + dm_hs_in_desc.bEndpointAddress = + dm_fs_in_desc.bEndpointAddress; + dm_hs_out_desc.bEndpointAddress = + dm_fs_out_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(dm_hs_function); + } + if (gadget_is_superspeed(c->cdev->gadget)) { + dm_ss_in_desc.bEndpointAddress = + dm_fs_in_desc.bEndpointAddress; + dm_ss_out_desc.bEndpointAddress = + dm_fs_out_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->ss_descriptors = usb_copy_descriptors(dm_ss_function); + if (!f->ss_descriptors) + goto fail; + } + + printk("usb: [%s] generic ttyGS%d: %s speed IN/%s OUT/%s\n", + __func__, + dm->port_num, + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + dm->port.in->name, dm->port.out->name); + return 0; + +fail: + /* we might as well release our claims on endpoints */ + if (dm->port.out) + dm->port.out->driver_data = NULL; + if (dm->port.in) + dm->port.in->driver_data = NULL; + + printk(KERN_ERR "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +dm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->fs_descriptors); + printk(KERN_DEBUG "usb: %s\n", __func__); +} + +/* + * dm_bind_config - add a generic serial function to a configuration + * @c: the configuration to support the serial instance + * @port_num: /dev/ttyGS* port this interface will use + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gserial_setup() with enough ports to + * handle all the ones it binds. Caller is also responsible + * for calling @gserial_cleanup() before module unload. + */ +int dm_bind_config(struct usb_configuration *c, u8 port_num) +{ + struct f_dm *dm; + int status; + + /* REVISIT might want instance-specific strings to help + * distinguish instances ... + */ + + /* maybe allocate device-global string ID */ + if (dm_string_defs[F_DM_IDX].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) + return status; + dm_string_defs[F_DM_IDX].id = status; + } + + /* allocate and initialize one new instance */ + dm = kzalloc(sizeof *dm, GFP_KERNEL); + if (!dm) + return -ENOMEM; + + dm->port_num = DM_PORT_NUM; + + dm->port.func.name = "dm"; + dm->port.func.strings = dm_strings; + dm->port.func.bind = dm_bind; + dm->port.func.unbind = dm_unbind; + dm->port.func.set_alt = dm_set_alt; + dm->port.func.disable = dm_disable; + + status = usb_add_function(c, &dm->port.func); + if (status) + kfree(dm); + return status; +} +static struct dm_instance *to_dm_instance(struct config_item *item) +{ + return container_of(to_config_group(item), struct dm_instance, + func_inst.group); +} +static void dm_attr_release(struct config_item *item) +{ + struct dm_instance *fi_dm = to_dm_instance(item); + usb_put_function_instance(&fi_dm->func_inst); +} + +static struct configfs_item_operations dm_item_ops = { + .release = dm_attr_release, +}; + +static struct config_item_type dm_func_type = { + .ct_item_ops = &dm_item_ops, + .ct_owner = THIS_MODULE, +}; + + +static struct dm_instance *to_fi_dm(struct usb_function_instance *fi) +{ + return container_of(fi, struct dm_instance, func_inst); +} + +static int dm_set_inst_name(struct usb_function_instance *fi, const char *name) +{ + struct dm_instance *fi_dm; + char *ptr; + int name_len; + + name_len = strlen(name) + 1; + if (name_len > MAX_INST_NAME_LEN) + return -ENAMETOOLONG; + + ptr = kstrndup(name, name_len, GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + fi_dm = to_fi_dm(fi); + fi_dm->name = ptr; + + return 0; +} + +static void dm_free_inst(struct usb_function_instance *fi) +{ + struct dm_instance *fi_dm; + + fi_dm = to_fi_dm(fi); + kfree(fi_dm->name); + kfree(fi_dm); +} + +struct usb_function_instance *alloc_inst_dm(bool dm_config) +{ + struct dm_instance *fi_dm; + + fi_dm = kzalloc(sizeof(*fi_dm), GFP_KERNEL); + if (!fi_dm) + return ERR_PTR(-ENOMEM); + fi_dm->func_inst.set_inst_name = dm_set_inst_name; + fi_dm->func_inst.free_func_inst = dm_free_inst; + + + config_group_init_type_name(&fi_dm->func_inst.group, + "", &dm_func_type); + + return &fi_dm->func_inst; +} +EXPORT_SYMBOL_GPL(alloc_inst_dm); + +static struct usb_function_instance *dm_alloc_inst(void) +{ + return alloc_inst_dm(true); +} + +static void dm_free(struct usb_function *f) +{ + struct f_dm *dm = func_to_dm(f); + + kfree(dm); +} + +struct usb_function *function_alloc_dm(struct usb_function_instance *fi, bool dm_config) +{ + + struct dm_instance *fi_dm = to_fi_dm(fi); + struct f_dm *dm; + int ret; + + /* REVISIT might want instance-specific strings to help + * distinguish instances ... + */ + + /* allocate and initialize one new instance */ + dm = kzalloc(sizeof *dm, GFP_KERNEL); + if (!dm) + return ERR_PTR(-ENOMEM); + + + dm->port_num = DM_PORT_NUM; + + dm->port.func.name = "dm"; + dm->port.func.strings = dm_strings; + dm->port.func.bind = dm_bind; + dm->port.func.unbind = dm_unbind; + dm->port.func.set_alt = dm_set_alt; + dm->port.func.disable = dm_disable; + dm->port.func.free_func = dm_free; + + fi_dm->dm = dm; + + ret = gserial_alloc_line(&dm->port_num); + if (ret) { + kfree(dm); + return ERR_PTR(ret); + } + + return &dm->port.func; +} +EXPORT_SYMBOL_GPL(function_alloc_dm); + +static struct usb_function *dm_alloc(struct usb_function_instance *fi) +{ + return function_alloc_dm(fi, true); +} + +DECLARE_USB_FUNCTION_INIT(dm, dm_alloc_inst, dm_alloc); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_mtp.c b/drivers/usb/gadget/function/f_mtp.c new file mode 100644 index 000000000000..9b8b6f4a2c70 --- /dev/null +++ b/drivers/usb/gadget/function/f_mtp.c @@ -0,0 +1,1605 @@ +/* + * Gadget Function Driver for MTP + * + * Copyright (C) 2010 Google, Inc. + * Author: Mike Lockwood + * + * 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. + * + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "configfs.h" + +#define MTP_BULK_BUFFER_SIZE 16384 +#define INTR_BUFFER_SIZE 28 +#define MAX_INST_NAME_LEN 40 +#define MTP_MAX_FILE_SIZE 0xFFFFFFFFL + +/* String IDs */ +#define INTERFACE_STRING_INDEX 0 + +/* values for mtp_dev.state */ +#define STATE_OFFLINE 0 /* initial state, disconnected */ +#define STATE_READY 1 /* ready for userspace calls */ +#define STATE_BUSY 2 /* processing userspace calls */ +#define STATE_CANCELED 3 /* transaction canceled by host */ +#define STATE_ERROR 4 /* error from completion routine */ + +/* number of tx and rx requests to allocate */ +#define TX_REQ_MAX 4 +#define RX_REQ_MAX 2 +#define INTR_REQ_MAX 5 + +/* ID for Microsoft MTP OS String */ +#define MTP_OS_STRING_ID 0xEE + +/* MTP class reqeusts */ +#define MTP_REQ_CANCEL 0x64 +#define MTP_REQ_GET_EXT_EVENT_DATA 0x65 +#define MTP_REQ_RESET 0x66 +#define MTP_REQ_GET_DEVICE_STATUS 0x67 + +/* constants for device status */ +#define MTP_RESPONSE_OK 0x2001 +#define MTP_RESPONSE_DEVICE_BUSY 0x2019 +#define DRIVER_NAME "mtp" +#if IS_ENABLED(CONFIG_USB_CONFIGFS_UEVENT) +#define DRIVER_NAME_PTP "ptp" +#endif + +static const char mtp_shortname[] = DRIVER_NAME "_usb"; + +struct mtp_dev { + struct usb_function function; +#if IS_ENABLED(CONFIG_USB_CONFIGFS_UEVENT) + struct usb_function function_ptp; +#endif + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + struct usb_ep *ep_intr; + + int state; + + /* synchronize access to our device file */ + atomic_t open_excl; + /* to enforce only one ioctl at a time */ + atomic_t ioctl_excl; + + struct list_head tx_idle; + struct list_head intr_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + wait_queue_head_t intr_wq; + struct usb_request *rx_req[RX_REQ_MAX]; + int rx_done; + + /* for processing MTP_SEND_FILE, MTP_RECEIVE_FILE and + * MTP_SEND_FILE_WITH_HEADER ioctls on a work queue + */ + struct workqueue_struct *wq; + struct work_struct send_file_work; + struct work_struct receive_file_work; + struct file *xfer_file; + loff_t xfer_file_offset; + int64_t xfer_file_length; + unsigned xfer_send_header; + uint16_t xfer_command; + uint32_t xfer_transaction_id; + int xfer_result; +}; + +static struct usb_interface_descriptor mtp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_interface_descriptor ptp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor mtp_ss_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(1024), +}; + +static struct usb_ss_ep_comp_descriptor mtp_ss_in_comp_desc = { + .bLength = sizeof(mtp_ss_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + /* .bMaxBurst = DYNAMIC, */ +}; + +static struct usb_endpoint_descriptor mtp_ss_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(1024), +}; + +static struct usb_ss_ep_comp_descriptor mtp_ss_out_comp_desc = { + .bLength = sizeof(mtp_ss_out_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + /* .bMaxBurst = DYNAMIC, */ +}; + +static struct usb_endpoint_descriptor mtp_highspeed_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), +}; + +static struct usb_endpoint_descriptor mtp_highspeed_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), +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_intr_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(INTR_BUFFER_SIZE), + .bInterval = 6, +}; + +static struct usb_ss_ep_comp_descriptor mtp_intr_ss_comp_desc = { + .bLength = sizeof(mtp_intr_ss_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .wBytesPerInterval = cpu_to_le16(INTR_BUFFER_SIZE), +}; + +static struct usb_descriptor_header *fs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *ss_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_ss_in_desc, + (struct usb_descriptor_header *) &mtp_ss_in_comp_desc, + (struct usb_descriptor_header *) &mtp_ss_out_desc, + (struct usb_descriptor_header *) &mtp_ss_out_comp_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + (struct usb_descriptor_header *) &mtp_intr_ss_comp_desc, + NULL, +}; + +static struct usb_descriptor_header *fs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *ss_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_ss_in_desc, + (struct usb_descriptor_header *) &mtp_ss_in_comp_desc, + (struct usb_descriptor_header *) &mtp_ss_out_desc, + (struct usb_descriptor_header *) &mtp_ss_out_comp_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + (struct usb_descriptor_header *) &mtp_intr_ss_comp_desc, + NULL, +}; + +static struct usb_string mtp_string_defs[] = { + /* Naming interface "MTP" so libmtp will recognize us */ + [INTERFACE_STRING_INDEX].s = "MTP", + { }, /* end of list */ +}; + +static struct usb_gadget_strings mtp_string_table = { + .language = 0x0409, /* en-US */ + .strings = mtp_string_defs, +}; + +static struct usb_gadget_strings *mtp_strings[] = { + &mtp_string_table, + NULL, +}; + +/* Microsoft MTP OS String */ +static u8 mtp_os_string[] = { + 18, /* sizeof(mtp_os_string) */ + USB_DT_STRING, + /* Signature field: "MSFT100" */ + 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, + /* vendor code */ + 1, + /* padding */ + 0 +}; + +/* Microsoft Extended Configuration Descriptor Header Section */ +struct mtp_ext_config_desc_header { + __le32 dwLength; + __u16 bcdVersion; + __le16 wIndex; + __u8 bCount; + __u8 reserved[7]; +}; + +/* Microsoft Extended Configuration Descriptor Function Section */ +struct mtp_ext_config_desc_function { + __u8 bFirstInterfaceNumber; + __u8 bInterfaceCount; + __u8 compatibleID[8]; + __u8 subCompatibleID[8]; + __u8 reserved[6]; +}; + +/* MTP Extended Configuration Descriptor */ +struct { + struct mtp_ext_config_desc_header header; + struct mtp_ext_config_desc_function function; +} mtp_ext_config_desc = { + .header = { + .dwLength = __constant_cpu_to_le32(sizeof(mtp_ext_config_desc)), + .bcdVersion = __constant_cpu_to_le16(0x0100), + .wIndex = __constant_cpu_to_le16(4), + .bCount = 1, + }, + .function = { + .bFirstInterfaceNumber = 0, + .bInterfaceCount = 1, + .compatibleID = { 'M', 'T', 'P' }, + }, +}; + +struct mtp_device_status { + __le16 wLength; + __le16 wCode; +}; + +struct mtp_data_header { + /* length of packet, including this header */ + __le32 length; + /* container type (2 for data packet) */ + __le16 type; + /* MTP command code */ + __le16 command; + /* MTP transaction ID */ + __le32 transaction_id; +}; + +struct mtp_instance { + struct usb_function_instance func_inst; + const char *name; + struct mtp_dev *dev; + char mtp_ext_compat_id[16]; + struct usb_os_desc mtp_os_desc; +}; + +/* temporary variable used between mtp_open() and mtp_gadget_bind() */ +static struct mtp_dev *_mtp_dev; + +static inline struct mtp_dev *func_to_mtp(struct usb_function *f) +{ +#if IS_ENABLED(CONFIG_USB_CONFIGFS_UEVENT) + if (!strcmp(f->name, DRIVER_NAME_PTP)) + return container_of(f, struct mtp_dev, function_ptp); +#endif + return container_of(f, struct mtp_dev, function); +} + +static struct usb_request *mtp_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void mtp_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int mtp_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void mtp_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +static void mtp_req_put(struct mtp_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request +*mtp_req_get(struct mtp_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +/* Make bulk-out requests be divisible by the maxpacket size */ +static void set_read_req_length(struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + unsigned int rem; + + rem = req->length % dev->ep_out->maxpacket; + if (rem > 0) + req->length += dev->ep_out->maxpacket - rem; +} + +static void mtp_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + if (req->status != 0) + dev->state = STATE_ERROR; + + mtp_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void mtp_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + dev->rx_done = 1; + if (req->status != 0) + dev->state = STATE_ERROR; + + wake_up(&dev->read_wq); +} + +static void mtp_complete_intr(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + if (req->status != 0) + dev->state = STATE_ERROR; + + mtp_req_put(dev, &dev->intr_idle, req); + + wake_up(&dev->intr_wq); +} + +static int mtp_create_bulk_endpoints(struct mtp_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc, + struct usb_endpoint_descriptor *intr_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, intr_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_intr got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_intr = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < TX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_in, MTP_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_in; + mtp_req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_out, MTP_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_out; + dev->rx_req[i] = req; + } + for (i = 0; i < INTR_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_intr, INTR_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_intr; + mtp_req_put(dev, &dev->intr_idle, req); + } + + return 0; + +fail: + pr_err("mtp_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t mtp_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + ssize_t r = count; + unsigned xfer; + int ret = 0; + size_t len = 0; + + DBG(cdev, "mtp_read(%zu)\n", count); + + /* we will block until we're online */ + DBG(cdev, "mtp_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + dev->state != STATE_OFFLINE); + if (ret < 0) { + r = ret; + goto done; + } + spin_lock_irq(&dev->lock); + if (dev->ep_out->desc) { + len = usb_ep_align_maybe(cdev->gadget, dev->ep_out, count); + if (len > MTP_BULK_BUFFER_SIZE) { + spin_unlock_irq(&dev->lock); + return -EINVAL; + } + } + + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + +requeue_req: + /* queue a request */ + req = dev->rx_req[0]; + req->length = len; + dev->rx_done = 0; + set_read_req_length(req); + ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + goto done; + } else { + DBG(cdev, "rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (dev->state == STATE_BUSY) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + DBG(cdev, "rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_read returning %zd\n", r); + return r; +} + +static ssize_t mtp_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + ssize_t r = count; + unsigned xfer; + int sendZLP = 0; + int ret; + + DBG(cdev, "mtp_write(%zu)\n", count); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + return -ENODEV; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((count & (dev->ep_in->maxpacket - 1)) == 0) + sendZLP = 1; + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + if (dev->state != STATE_BUSY) { + DBG(cdev, "mtp_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = mtp_req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY)); + if (!req) { + r = ret; + break; + } + + if (count > MTP_BULK_BUFFER_SIZE) + xfer = MTP_BULK_BUFFER_SIZE; + else + xfer = count; + if (xfer && copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "mtp_write: xfer error %d\n", ret); + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + mtp_req_put(dev, &dev->tx_idle, req); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_write returning %zd\n", r); + return r; +} + +/* read from a local file and write to USB */ +static void send_file_work(struct work_struct *data) +{ + struct mtp_dev *dev = container_of(data, struct mtp_dev, + send_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + struct mtp_data_header *header; + struct file *filp; + loff_t offset; + int64_t count; + int xfer, ret, hdr_size; + int r = 0; + int sendZLP = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "send_file_work(%lld %lld)\n", offset, count); + + if (dev->xfer_send_header) { + hdr_size = sizeof(struct mtp_data_header); + count += hdr_size; + } else { + hdr_size = 0; + } + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((count & (dev->ep_in->maxpacket - 1)) == 0) + sendZLP = 1; + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = mtp_req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + break; + } + if (!req) { + r = ret; + break; + } + + if (count > MTP_BULK_BUFFER_SIZE) + xfer = MTP_BULK_BUFFER_SIZE; + else + xfer = count; + + if (hdr_size) { + /* prepend MTP data header */ + header = (struct mtp_data_header *)req->buf; + /* + * set file size with header according to + * MTP Specification v1.0 + */ + header->length = (count > MTP_MAX_FILE_SIZE) ? + MTP_MAX_FILE_SIZE : __cpu_to_le32(count); + header->type = __cpu_to_le16(2); /* data packet */ + header->command = __cpu_to_le16(dev->xfer_command); + header->transaction_id = + __cpu_to_le32(dev->xfer_transaction_id); + } + + ret = vfs_read(filp, req->buf + hdr_size, xfer - hdr_size, + &offset); + if (ret < 0) { + r = ret; + break; + } + xfer = ret + hdr_size; + hdr_size = 0; + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "send_file_work: xfer error %d\n", ret); + dev->state = STATE_ERROR; + r = -EIO; + break; + } + + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + mtp_req_put(dev, &dev->tx_idle, req); + + DBG(cdev, "send_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +/* read from USB and write to a local file */ +static void receive_file_work(struct work_struct *data) +{ + struct mtp_dev *dev = container_of(data, struct mtp_dev, + receive_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *read_req = NULL, *write_req = NULL; + struct file *filp; + loff_t offset; + int64_t count; + int ret, cur_buf = 0; + int r = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "receive_file_work(%lld)\n", count); + + while (count > 0 || write_req) { + if (count > 0) { + /* queue a request */ + read_req = dev->rx_req[cur_buf]; + cur_buf = (cur_buf + 1) % RX_REQ_MAX; + + read_req->length = (count > MTP_BULK_BUFFER_SIZE + ? MTP_BULK_BUFFER_SIZE : count); + dev->rx_done = 0; + + set_read_req_length(read_req); + ret = usb_ep_queue(dev->ep_out, read_req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + } + + if (write_req) { + DBG(cdev, "rx %p %d\n", write_req, write_req->actual); + ret = vfs_write(filp, write_req->buf, write_req->actual, + &offset); + DBG(cdev, "vfs_write %d\n", ret); + if (ret != write_req->actual) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + write_req = NULL; + } + + if (read_req) { + /* wait for our last read to complete */ + ret = wait_event_interruptible(dev->read_wq, + dev->rx_done || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + if (!dev->rx_done) + usb_ep_dequeue(dev->ep_out, read_req); + break; + } + if (read_req->status) { + r = read_req->status; + break; + } + /* if xfer_file_length is 0xFFFFFFFF, then we read until + * we get a zero length packet + */ + if (count != 0xFFFFFFFF) + count -= read_req->actual; + if (read_req->actual < read_req->length) { + /* + * short packet is used to signal EOF for + * sizes > 4 gig + */ + DBG(cdev, "got short packet\n"); + count = 0; + } + + write_req = read_req; + read_req = NULL; + } + } + + DBG(cdev, "receive_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event) +{ + struct usb_request *req = NULL; + int ret; + int length = event->length; + + DBG(dev->cdev, "mtp_send_event(%zu)\n", event->length); + + if (length < 0 || length > INTR_BUFFER_SIZE) + return -EINVAL; + if (dev->state == STATE_OFFLINE) + return -ENODEV; + + ret = wait_event_interruptible_timeout(dev->intr_wq, + (req = mtp_req_get(dev, &dev->intr_idle)), + msecs_to_jiffies(1000)); + if (!req) + return -ETIME; + + if (copy_from_user(req->buf, (void __user *)event->data, length)) { + mtp_req_put(dev, &dev->intr_idle, req); + return -EFAULT; + } + req->length = length; + ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL); + if (ret) + mtp_req_put(dev, &dev->intr_idle, req); + + return ret; +} + +static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value) +{ + struct mtp_dev *dev = fp->private_data; + struct file *filp = NULL; + int ret = -EINVAL; + + if (mtp_lock(&dev->ioctl_excl)) + return -EBUSY; + + switch (code) { + case MTP_SEND_FILE: + case MTP_RECEIVE_FILE: + case MTP_SEND_FILE_WITH_HEADER: + { + struct mtp_file_range mfr; + struct work_struct *work; + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + ret = -ECANCELED; + goto out; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + ret = -ENODEV; + goto out; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) { + ret = -EFAULT; + goto fail; + } + /* hold a reference to the file while we are working with it */ + filp = fget(mfr.fd); + if (!filp) { + ret = -EBADF; + goto fail; + } + + /* write the parameters */ + dev->xfer_file = filp; + dev->xfer_file_offset = mfr.offset; + dev->xfer_file_length = mfr.length; + smp_wmb(); + + if (code == MTP_SEND_FILE_WITH_HEADER) { + work = &dev->send_file_work; + dev->xfer_send_header = 1; + dev->xfer_command = mfr.command; + dev->xfer_transaction_id = mfr.transaction_id; + } else if (code == MTP_SEND_FILE) { + work = &dev->send_file_work; + dev->xfer_send_header = 0; + } else { + work = &dev->receive_file_work; + } + + /* We do the file transfer on a work queue so it will run + * in kernel context, which is necessary for vfs_read and + * vfs_write to use our buffers in the kernel address space. + */ + queue_work(dev->wq, work); + /* wait for operation to complete */ + flush_workqueue(dev->wq); + fput(filp); + + /* read the result */ + smp_rmb(); + ret = dev->xfer_result; + break; + } + case MTP_SEND_EVENT: + { + struct mtp_event event; + /* return here so we don't change dev->state below, + * which would interfere with bulk transfer state. + */ + if (copy_from_user(&event, (void __user *)value, sizeof(event))) + ret = -EFAULT; + else + ret = mtp_send_event(dev, &event); + goto out; + } + } + +fail: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + ret = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); +out: + mtp_unlock(&dev->ioctl_excl); + DBG(dev->cdev, "ioctl returning %d\n", ret); + return ret; +} + +static int mtp_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_open\n"); + if (mtp_lock(&_mtp_dev->open_excl)) + return -EBUSY; + + /* clear any error condition */ + if (_mtp_dev->state != STATE_OFFLINE) + _mtp_dev->state = STATE_READY; + + fp->private_data = _mtp_dev; + return 0; +} + +static int mtp_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_release\n"); + + mtp_unlock(&_mtp_dev->open_excl); + return 0; +} + +/* file operations for /dev/mtp_usb */ +static const struct file_operations mtp_fops = { + .owner = THIS_MODULE, + .read = mtp_read, + .write = mtp_write, + .unlocked_ioctl = mtp_ioctl, + .open = mtp_open, + .release = mtp_release, +}; + +static struct miscdevice mtp_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = mtp_shortname, + .fops = &mtp_fops, +}; + +static int mtp_ctrlrequest(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct mtp_dev *dev = _mtp_dev; + 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); + unsigned long flags; + + VDBG(cdev, "mtp_ctrlrequest " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + /* Handle MTP OS string */ + if (ctrl->bRequestType == + (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) + && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR + && (w_value >> 8) == USB_DT_STRING + && (w_value & 0xFF) == MTP_OS_STRING_ID) { + value = (w_length < sizeof(mtp_os_string) + ? w_length : sizeof(mtp_os_string)); + memcpy(cdev->req->buf, mtp_os_string, value); + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + /* Handle MTP OS descriptor */ + DBG(cdev, "vendor request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (ctrl->bRequest == 1 + && (ctrl->bRequestType & USB_DIR_IN) + && (w_index == 4 || w_index == 5)) { + value = (w_length < sizeof(mtp_ext_config_desc) ? + w_length : sizeof(mtp_ext_config_desc)); + memcpy(cdev->req->buf, &mtp_ext_config_desc, value); + } + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + DBG(cdev, "class request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (ctrl->bRequest == MTP_REQ_CANCEL && w_index == 0 + && w_value == 0) { + DBG(cdev, "MTP_REQ_CANCEL\n"); + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state == STATE_BUSY) { + dev->state = STATE_CANCELED; + wake_up(&dev->read_wq); + wake_up(&dev->write_wq); + } + spin_unlock_irqrestore(&dev->lock, flags); + + /* We need to queue a request to read the remaining + * bytes, but we don't actually need to look at + * the contents. + */ + value = w_length; + } else if (ctrl->bRequest == MTP_REQ_GET_DEVICE_STATUS + && w_index == 0 && w_value == 0) { + struct mtp_device_status *status = cdev->req->buf; + + status->wLength = + __constant_cpu_to_le16(sizeof(*status)); + + DBG(cdev, "MTP_REQ_GET_DEVICE_STATUS\n"); + spin_lock_irqsave(&dev->lock, flags); + /* device status is "busy" until we report + * the cancelation to userspace + */ + if (dev->state == STATE_CANCELED) + status->wCode = + __cpu_to_le16(MTP_RESPONSE_DEVICE_BUSY); + else + status->wCode = + __cpu_to_le16(MTP_RESPONSE_OK); + spin_unlock_irqrestore(&dev->lock, flags); + value = sizeof(*status); + } + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + int rc; + + cdev->req->zero = value < w_length; + cdev->req->length = value; + rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (rc < 0) + ERROR(cdev, "%s: response queue error\n", __func__); + } + return value; +} + +static int +mtp_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct mtp_dev *dev = func_to_mtp(f); + int id; + int ret; + struct mtp_instance *fi_mtp; + + dev->cdev = cdev; + DBG(cdev, "mtp_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + mtp_interface_desc.bInterfaceNumber = id; + + if (mtp_string_defs[INTERFACE_STRING_INDEX].id == 0) { + ret = usb_string_id(c->cdev); + if (ret < 0) + return ret; + mtp_string_defs[INTERFACE_STRING_INDEX].id = ret; + mtp_interface_desc.iInterface = ret; + } + + fi_mtp = container_of(f->fi, struct mtp_instance, func_inst); + + if (cdev->use_os_string) { + f->os_desc_table = kzalloc(sizeof(*f->os_desc_table), + GFP_KERNEL); + if (!f->os_desc_table) + return -ENOMEM; + f->os_desc_n = 1; + f->os_desc_table[0].os_desc = &fi_mtp->mtp_os_desc; + } + + /* allocate endpoints */ + ret = mtp_create_bulk_endpoints(dev, &mtp_fullspeed_in_desc, + &mtp_fullspeed_out_desc, &mtp_intr_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + mtp_highspeed_in_desc.bEndpointAddress = + mtp_fullspeed_in_desc.bEndpointAddress; + mtp_highspeed_out_desc.bEndpointAddress = + mtp_fullspeed_out_desc.bEndpointAddress; + } + /* support super speed hardware */ + if (gadget_is_superspeed(c->cdev->gadget)) { + unsigned max_burst; + + /* Calculate bMaxBurst, we know packet size is 1024 */ + max_burst = min_t(unsigned, MTP_BULK_BUFFER_SIZE / 1024, 15); + mtp_ss_in_desc.bEndpointAddress = + mtp_fullspeed_in_desc.bEndpointAddress; + mtp_ss_in_comp_desc.bMaxBurst = max_burst; + mtp_ss_out_desc.bEndpointAddress = + mtp_fullspeed_out_desc.bEndpointAddress; + mtp_ss_out_comp_desc.bMaxBurst = max_burst; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : + (gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full"), + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +mtp_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_request *req; + int i; + + mtp_string_defs[INTERFACE_STRING_INDEX].id = 0; + while ((req = mtp_req_get(dev, &dev->tx_idle))) + mtp_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + mtp_request_free(dev->rx_req[i], dev->ep_out); + while ((req = mtp_req_get(dev, &dev->intr_idle))) + mtp_request_free(req, dev->ep_intr); + dev->state = STATE_OFFLINE; + kfree(f->os_desc_table); + f->os_desc_n = 0; +} + +static int mtp_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "mtp_function_set_alt intf: %d alt: %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_in); + if (ret) + return ret; + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_intr); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_intr); + if (ret) { + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_in); + return ret; + } + dev->state = STATE_READY; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void mtp_function_disable(struct usb_function *f) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "mtp_function_disable\n"); + dev->state = STATE_OFFLINE; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_intr); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int __mtp_setup(struct mtp_instance *fi_mtp) +{ + struct mtp_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + + if (fi_mtp != NULL) + fi_mtp->dev = dev; + + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + init_waitqueue_head(&dev->intr_wq); + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->ioctl_excl, 0); + INIT_LIST_HEAD(&dev->tx_idle); + INIT_LIST_HEAD(&dev->intr_idle); + + dev->wq = create_singlethread_workqueue("f_mtp"); + if (!dev->wq) { + ret = -ENOMEM; + goto err1; + } + INIT_WORK(&dev->send_file_work, send_file_work); + INIT_WORK(&dev->receive_file_work, receive_file_work); + + _mtp_dev = dev; + + ret = misc_register(&mtp_device); + if (ret) + goto err2; + + return 0; + +err2: + destroy_workqueue(dev->wq); +err1: + _mtp_dev = NULL; + kfree(dev); + printk(KERN_ERR "mtp gadget driver failed to initialize\n"); + return ret; +} + +static int mtp_setup_configfs(struct mtp_instance *fi_mtp) +{ + return __mtp_setup(fi_mtp); +} + + +static void mtp_cleanup(void) +{ + struct mtp_dev *dev = _mtp_dev; + + if (!dev) + return; + + misc_deregister(&mtp_device); + destroy_workqueue(dev->wq); + _mtp_dev = NULL; + kfree(dev); +} + +static struct mtp_instance *to_mtp_instance(struct config_item *item) +{ + return container_of(to_config_group(item), struct mtp_instance, + func_inst.group); +} + +static void mtp_attr_release(struct config_item *item) +{ + struct mtp_instance *fi_mtp = to_mtp_instance(item); + + usb_put_function_instance(&fi_mtp->func_inst); +} + +static struct configfs_item_operations mtp_item_ops = { + .release = mtp_attr_release, +}; + +static struct config_item_type mtp_func_type = { + .ct_item_ops = &mtp_item_ops, + .ct_owner = THIS_MODULE, +}; + + +static struct mtp_instance *to_fi_mtp(struct usb_function_instance *fi) +{ + return container_of(fi, struct mtp_instance, func_inst); +} + +static int mtp_set_inst_name(struct usb_function_instance *fi, const char *name) +{ + struct mtp_instance *fi_mtp; + char *ptr; + int name_len; + + name_len = strlen(name) + 1; + if (name_len > MAX_INST_NAME_LEN) + return -ENAMETOOLONG; + + ptr = kstrndup(name, name_len, GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + fi_mtp = to_fi_mtp(fi); + fi_mtp->name = ptr; + + return 0; +} + +static void mtp_free_inst(struct usb_function_instance *fi) +{ + struct mtp_instance *fi_mtp; + + fi_mtp = to_fi_mtp(fi); + kfree(fi_mtp->name); + mtp_cleanup(); + kfree(fi_mtp); +} + +struct usb_function_instance *alloc_inst_mtp_ptp(bool mtp_config) +{ + struct mtp_instance *fi_mtp; + int ret = 0; + struct usb_os_desc *descs[1]; + char *names[1]; + + fi_mtp = kzalloc(sizeof(*fi_mtp), GFP_KERNEL); + if (!fi_mtp) + return ERR_PTR(-ENOMEM); + fi_mtp->func_inst.set_inst_name = mtp_set_inst_name; + fi_mtp->func_inst.free_func_inst = mtp_free_inst; + + fi_mtp->mtp_os_desc.ext_compat_id = fi_mtp->mtp_ext_compat_id; + INIT_LIST_HEAD(&fi_mtp->mtp_os_desc.ext_prop); + descs[0] = &fi_mtp->mtp_os_desc; + names[0] = "MTP"; + + if (mtp_config) { + ret = mtp_setup_configfs(fi_mtp); + if (ret) { + kfree(fi_mtp); + pr_err("Error setting MTP\n"); + return ERR_PTR(ret); + } + } else + fi_mtp->dev = _mtp_dev; + + config_group_init_type_name(&fi_mtp->func_inst.group, + "", &mtp_func_type); + usb_os_desc_prepare_interf_dir(&fi_mtp->func_inst.group, 1, + descs, names, THIS_MODULE); + + return &fi_mtp->func_inst; +} +EXPORT_SYMBOL_GPL(alloc_inst_mtp_ptp); + +static struct usb_function_instance *mtp_alloc_inst(void) +{ + return alloc_inst_mtp_ptp(true); +} + +static int mtp_ctrlreq_configfs(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + return mtp_ctrlrequest(f->config->cdev, ctrl); +} + +static void mtp_free(struct usb_function *f) +{ + /*NO-OP: no function specific resource allocation in mtp_alloc*/ +} + +struct usb_function *function_alloc_mtp_ptp(struct usb_function_instance *fi, + bool mtp_config) +{ + struct mtp_instance *fi_mtp = to_fi_mtp(fi); + struct mtp_dev *dev; +#ifdef CONFIG_USB_CONFIGFS_UEVENT + struct usb_function *function; +#endif + + /* + * PTP piggybacks on MTP function so make sure we have + * created MTP function before we associate this PTP + * function with a gadget configuration. + */ + if (fi_mtp->dev == NULL) { + pr_err("Error: Create MTP function before linking" + " PTP function with a gadget configuration\n"); + pr_err("\t1: Delete existing PTP function if any\n"); + pr_err("\t2: Create MTP function\n"); + pr_err("\t3: Create and symlink PTP function" + " with a gadget configuration\n"); + return ERR_PTR(-EINVAL); /* Invalid Configuration */ + } + + dev = fi_mtp->dev; +#ifdef CONFIG_USB_CONFIGFS_UEVENT + if (mtp_config) { + function = &dev->function; + function->name = DRIVER_NAME; + function->fs_descriptors = fs_mtp_descs; + function->hs_descriptors = hs_mtp_descs; + function->ss_descriptors = ss_mtp_descs; + } else { + function = &dev->function_ptp; + function->name = DRIVER_NAME_PTP; + function->fs_descriptors = fs_ptp_descs; + function->hs_descriptors = hs_ptp_descs; + function->ss_descriptors = ss_ptp_descs; + } + + function->strings = mtp_strings; + function->bind = mtp_function_bind; + function->unbind = mtp_function_unbind; + function->set_alt = mtp_function_set_alt; + function->disable = mtp_function_disable; + function->setup = mtp_ctrlreq_configfs; + function->free_func = mtp_free; + + return function; +#else + dev->function.name = DRIVER_NAME; + dev->function.strings = mtp_strings; + if (mtp_config) { + dev->function.fs_descriptors = fs_mtp_descs; + dev->function.hs_descriptors = hs_mtp_descs; + dev->function.ss_descriptors = ss_mtp_descs; + } else { + dev->function.fs_descriptors = fs_ptp_descs; + dev->function.hs_descriptors = hs_ptp_descs; + dev->function.ss_descriptors = ss_ptp_descs; + } + dev->function.bind = mtp_function_bind; + dev->function.unbind = mtp_function_unbind; + dev->function.set_alt = mtp_function_set_alt; + dev->function.disable = mtp_function_disable; + dev->function.setup = mtp_ctrlreq_configfs; + dev->function.free_func = mtp_free; + + return &dev->function; +#endif +} +EXPORT_SYMBOL_GPL(function_alloc_mtp_ptp); + +static struct usb_function *mtp_alloc(struct usb_function_instance *fi) +{ + return function_alloc_mtp_ptp(fi, true); +} + +DECLARE_USB_FUNCTION_INIT(mtp, mtp_alloc_inst, mtp_alloc); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_mtp.h b/drivers/usb/gadget/function/f_mtp.h new file mode 100644 index 000000000000..739d99ae07f9 --- /dev/null +++ b/drivers/usb/gadget/function/f_mtp.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2014 Google, Inc. + * Author: Badhri Jagan Sridharan + * + * 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. + * + */ + +extern struct usb_function_instance *alloc_inst_mtp_ptp(bool mtp_config); +extern struct usb_function *function_alloc_mtp_ptp( + struct usb_function_instance *fi, bool mtp_config); diff --git a/drivers/usb/gadget/function/f_ptp.c b/drivers/usb/gadget/function/f_ptp.c new file mode 100644 index 000000000000..efcc5c5a33b4 --- /dev/null +++ b/drivers/usb/gadget/function/f_ptp.c @@ -0,0 +1,38 @@ +/* + * Gadget Function Driver for PTP + * + * Copyright (C) 2014 Google, Inc. + * Author: Badhri Jagan Sridharan + * + * 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 +#include + +#include +#include + +#include "f_mtp.h" + +static struct usb_function_instance *ptp_alloc_inst(void) +{ + return alloc_inst_mtp_ptp(false); +} + +static struct usb_function *ptp_alloc(struct usb_function_instance *fi) +{ + return function_alloc_mtp_ptp(fi, false); +} + +DECLARE_USB_FUNCTION_INIT(ptp, ptp_alloc_inst, ptp_alloc); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Badhri Jagan Sridharan"); diff --git a/drivers/usb/gadget/function/f_uts.c b/drivers/usb/gadget/function/f_uts.c new file mode 100644 index 000000000000..8c342367a839 --- /dev/null +++ b/drivers/usb/gadget/function/f_uts.c @@ -0,0 +1,827 @@ +/* + * f_uts.c -- USB CDC serial (ACM) function driver + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 by David Brownell + * Copyright (C) 2008 by Nokia Corporation + * Copyright (C) 2009 by Samsung Electronics + * Author: Michal Nazarewicz (mina86@mina86.com) + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include + +#include "u_serial.h" + + +/* + * This CDC ACM function support just wraps control functions and + * notifications around the generic serial-over-usb code. + * + * Because CDC ACM is standardized by the USB-IF, many host operating + * systems have drivers for it. Accordingly, ACM is the preferred + * interop solution for serial-port type connections. The control + * models are often not necessary, and in any case don't do much in + * this bare-bones implementation. + * + * Note that even MS-Windows has some support for ACM. However, that + * support is somewhat broken because when you use ACM in a composite + * device, having multiple interfaces confuses the poor OS. It doesn't + * seem to understand CDC Union descriptors. The new "association" + * descriptors (roughly equivalent to CDC Unions) may sometimes help. + */ + +/* UTS_PORT NUM : /dev/ttyGS* port number */ +#define UTS_PORT_NUM 2 + +struct f_uts { + struct gserial port; + u8 ctrl_id, data_id; + u8 port_num; + + u8 pending; + + /* lock is mostly for pending and notify_req ... they get accessed + * by callbacks both from tty (open/close/break) under its spinlock, + * and notify_req.complete() which can't use that lock. + */ + spinlock_t lock; + + struct usb_ep *notify; + struct usb_request *notify_req; + + struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ + + /* SetControlLineState request -- CDC 1.1 section 6.2.14 (INPUT) */ + u16 port_handshake_bits; +#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */ +#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */ + + /* SerialState notification -- CDC 1.1 section 6.3.5 (OUTPUT) */ + u16 serial_state; +#define ACM_CTRL_OVERRUN (1 << 6) +#define ACM_CTRL_PARITY (1 << 5) +#define ACM_CTRL_FRAMING (1 << 4) +#define ACM_CTRL_RI (1 << 3) +#define ACM_CTRL_BRK (1 << 2) +#define ACM_CTRL_DSR (1 << 1) +#define ACM_CTRL_DCD (1 << 0) +}; + +static inline struct f_uts *func_to_uts(struct usb_function *f) +{ + return container_of(f, struct f_uts, port.func); +} + +static inline struct f_uts *port_to_uts(struct gserial *p) +{ + return container_of(p, struct f_uts, port); +} + +/*-------------------------------------------------------------------------*/ + +/* notification endpoint uses smallish and infrequent fixed-size messages */ + +#define GS_NOTIFY_INTERVAL_MS 32 +#define GS_NOTIFY_MAXPACKET 10 /* notification + 2 bytes */ + +/* interface and class descriptors: */ + +static struct usb_interface_assoc_descriptor +uts_iad_descriptor = { + .bLength = sizeof(uts_iad_descriptor), + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + /* .bFirstInterface = DYNAMIC, */ + .bInterfaceCount = 2, // control + data + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, + .bFunctionProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + /* .iFunction = DYNAMIC */ +}; + + +static struct usb_interface_descriptor uts_control_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_interface_descriptor uts_data_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc uts_header_desc = { + .bLength = sizeof(uts_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_call_mgmt_descriptor +uts_call_mgmt_descriptor = { + .bLength = sizeof(uts_call_mgmt_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + .bmCapabilities = 0, + /* .bDataInterface = DYNAMIC */ +}; + +static struct usb_cdc_acm_descriptor uts_descriptor = { + .bLength = sizeof(uts_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + .bmCapabilities = USB_CDC_CAP_LINE, +}; + +static struct usb_cdc_union_desc uts_union_desc = { + .bLength = sizeof(uts_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor uts_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = GS_NOTIFY_INTERVAL_MS, +}; + +static struct usb_endpoint_descriptor uts_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor uts_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *uts_fs_function[] = { + (struct usb_descriptor_header *) &uts_iad_descriptor, + (struct usb_descriptor_header *) &uts_control_interface_desc, + (struct usb_descriptor_header *) &uts_header_desc, + (struct usb_descriptor_header *) &uts_call_mgmt_descriptor, + (struct usb_descriptor_header *) &uts_descriptor, + (struct usb_descriptor_header *) &uts_union_desc, + (struct usb_descriptor_header *) &uts_fs_notify_desc, + (struct usb_descriptor_header *) &uts_data_interface_desc, + (struct usb_descriptor_header *) &uts_fs_in_desc, + (struct usb_descriptor_header *) &uts_fs_out_desc, + NULL, +}; + +/* high speed support: */ +static struct usb_endpoint_descriptor uts_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = USB_MS_TO_HS_INTERVAL(GS_NOTIFY_INTERVAL_MS), +}; + +static struct usb_endpoint_descriptor uts_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor uts_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *uts_hs_function[] = { + (struct usb_descriptor_header *) &uts_iad_descriptor, + (struct usb_descriptor_header *) &uts_control_interface_desc, + (struct usb_descriptor_header *) &uts_header_desc, + (struct usb_descriptor_header *) &uts_call_mgmt_descriptor, + (struct usb_descriptor_header *) &uts_descriptor, + (struct usb_descriptor_header *) &uts_union_desc, + (struct usb_descriptor_header *) &uts_hs_notify_desc, + (struct usb_descriptor_header *) &uts_data_interface_desc, + (struct usb_descriptor_header *) &uts_hs_in_desc, + (struct usb_descriptor_header *) &uts_hs_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor uts_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor uts_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor uts_ss_bulk_comp_desc = { + .bLength = sizeof(uts_ss_bulk_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_descriptor_header *uts_ss_function[] = { + (struct usb_descriptor_header *) &uts_iad_descriptor, + (struct usb_descriptor_header *) &uts_control_interface_desc, + (struct usb_descriptor_header *) &uts_header_desc, + (struct usb_descriptor_header *) &uts_call_mgmt_descriptor, + (struct usb_descriptor_header *) &uts_descriptor, + (struct usb_descriptor_header *) &uts_union_desc, + (struct usb_descriptor_header *) &uts_hs_notify_desc, + (struct usb_descriptor_header *) &uts_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &uts_data_interface_desc, + (struct usb_descriptor_header *) &uts_ss_in_desc, + (struct usb_descriptor_header *) &uts_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &uts_ss_out_desc, + (struct usb_descriptor_header *) &uts_ss_bulk_comp_desc, + NULL, +}; + +/* string descriptors: */ + +#define ACM_CTRL_IDX 0 +#define ACM_DATA_IDX 1 +#define ACM_IAD_IDX 2 + +/* static strings, in UTF-8 */ +static struct usb_string uts_string_defs[] = { + [ACM_CTRL_IDX].s = "CDC Abstract Control Model (ACM2)", + [ACM_DATA_IDX].s = "CDC ACM2 Data", + [ACM_IAD_IDX].s = "CDC Serial", + { } /* end of list */ +}; + +static struct usb_gadget_strings uts_string_table = { + .language = 0x0409, /* en-us */ + .strings = uts_string_defs, +}; + +static struct usb_gadget_strings *uts_strings[] = { + &uts_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +/* ACM control ... data handling is delegated to tty library code. + * The main task of this function is to activate and deactivate + * that code based on device state; track parameters like line + * speed, handshake state, and so on; and issue notifications. + */ + +static void uts_complete_set_line_coding(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_uts *uts = ep->driver_data; + struct usb_composite_dev *cdev = uts->port.func.config->cdev; + + if (req->status != 0) { + dev_dbg(&cdev->gadget->dev, "uts ttyGS%d completion, err %d\n", + uts->port_num, req->status); + return; + } + + /* normal completion */ + if (req->actual != sizeof(uts->port_line_coding)) { + dev_dbg(&cdev->gadget->dev, "uts ttyGS%d short resp, len %d\n", + uts->port_num, req->actual); + usb_ep_set_halt(ep); + } else { + struct usb_cdc_line_coding *value = req->buf; + + /* REVISIT: we currently just remember this data. + * If we change that, (a) validate it first, then + * (b) update whatever hardware needs updating, + * (c) worry about locking. This is information on + * the order of 9600-8-N-1 ... most of which means + * nothing unless we control a real RS232 line. + */ + uts->port_line_coding = *value; + } +} + +static int uts_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_uts *uts = func_to_uts(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + * + * Note CDC spec table 4 lists the ACM request profile. It requires + * encapsulated command support ... we don't handle any, and respond + * to them by stalling. Options include get/set/clear comm features + * (not that useful) and SEND_BREAK. + */ + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + /* SET_LINE_CODING ... just read and save what the host sends */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_LINE_CODING: + if (w_length != sizeof(struct usb_cdc_line_coding) + || w_index != uts->ctrl_id) + goto invalid; + + value = w_length; + cdev->gadget->ep0->driver_data = uts; + req->complete = uts_complete_set_line_coding; + break; + + /* GET_LINE_CODING ... return what host sent, or initial value */ + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_GET_LINE_CODING: + if (w_index != uts->ctrl_id) + goto invalid; + + value = min_t(unsigned int, w_length, + sizeof(struct usb_cdc_line_coding)); + memcpy(req->buf, &uts->port_line_coding, value); + break; + + /* SET_CONTROL_LINE_STATE ... save what the host sent */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + if (w_index != uts->ctrl_id) + goto invalid; + + value = 0; + + /* FIXME we should not allow data to flow until the + * host sets the ACM_CTRL_DTR bit; and when it clears + * that bit, we should return to that no-flow state. + */ + uts->port_handshake_bits = w_value; + break; + + default: +invalid: + dev_vdbg(&cdev->gadget->dev, + "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + dev_dbg(&cdev->gadget->dev, + "uts ttyGS%d req%02x.%02x v%04x i%04x l%d\n", + uts->port_num, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "uts response on ttyGS%d, err %d\n", + uts->port_num, value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int uts_set_alt(struct usb_function *f, unsigned int intf, unsigned int alt) +{ + struct f_uts *uts = func_to_uts(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* we know alt == 0, so this is an activation or a reset */ + + if (intf == uts->ctrl_id) { + dev_vdbg(&cdev->gadget->dev, + "reset uts control interface %d\n", intf); + usb_ep_disable(uts->notify); + + if (!uts->notify->desc) + if (config_ep_by_speed(cdev->gadget, f, uts->notify)) + return -EINVAL; + + usb_ep_enable(uts->notify); + + } else if (intf == uts->data_id) { + if (uts->notify->enabled) { + dev_dbg(&cdev->gadget->dev, + "reset uts ttyGS%d\n", uts->port_num); + gserial_disconnect(&uts->port); + } + if (!uts->port.in->desc || !uts->port.out->desc) { + dev_dbg(&cdev->gadget->dev, + "activate uts ttyGS%d\n", uts->port_num); + if (config_ep_by_speed(cdev->gadget, f, + uts->port.in) || + config_ep_by_speed(cdev->gadget, f, + uts->port.out)) { + uts->port.in->desc = NULL; + uts->port.out->desc = NULL; + return -EINVAL; + } + } + gserial_connect(&uts->port, uts->port_num); + + } else + return -EINVAL; + + return 0; +} + +static void uts_disable(struct usb_function *f) +{ + struct f_uts *uts = func_to_uts(f); + struct usb_composite_dev *cdev = f->config->cdev; + + dev_dbg(&cdev->gadget->dev, "uts ttyGS%d deactivated\n", uts->port_num); + gserial_disconnect(&uts->port); + usb_ep_disable(uts->notify); +} + +/*-------------------------------------------------------------------------*/ + +/** + * uts_cdc_notify - issue CDC notification to host + * @uts: wraps host to be notified + * @type: notification type + * @value: Refer to cdc specs, wValue field. + * @data: data to be sent + * @length: size of data + * Context: irqs blocked, uts->lock held, uts_notify_req non-null + * + * Returns zero on success or a negative errno. + * + * See section 6.3.5 of the CDC 1.1 specification for information + * about the only notification we issue: SerialState change. + */ +static int uts_cdc_notify(struct f_uts *uts, u8 type, u16 value, + void *data, unsigned int length) +{ + struct usb_ep *ep = uts->notify; + struct usb_request *req; + struct usb_cdc_notification *notify; + const unsigned int len = sizeof(*notify) + length; + void *buf; + int status; + + req = uts->notify_req; + uts->notify_req = NULL; + uts->pending = false; + + req->length = len; + notify = req->buf; + buf = notify + 1; + + notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + notify->bNotificationType = type; + notify->wValue = cpu_to_le16(value); + notify->wIndex = cpu_to_le16(uts->ctrl_id); + notify->wLength = cpu_to_le16(length); + memcpy(buf, data, length); + + /* ep_queue() can complete immediately if it fills the fifo... */ + spin_unlock(&uts->lock); + status = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock(&uts->lock); + + if (status < 0) { + ERROR(uts->port.func.config->cdev, + "uts ttyGS%d can't notify serial state, %d\n", + uts->port_num, status); + uts->notify_req = req; + } + + return status; +} + +static int uts_notify_serial_state(struct f_uts *uts) +{ + struct usb_composite_dev *cdev = uts->port.func.config->cdev; + int status; + __le16 serial_state; + + spin_lock(&uts->lock); + if (uts->notify_req) { + dev_dbg(&cdev->gadget->dev, "uts ttyGS%d serial state %04x\n", + uts->port_num, uts->serial_state); + serial_state = cpu_to_le16(uts->serial_state); + status = uts_cdc_notify(uts, USB_CDC_NOTIFY_SERIAL_STATE, + 0, &serial_state, sizeof(uts->serial_state)); + } else { + uts->pending = true; + status = 0; + } + spin_unlock(&uts->lock); + return status; +} + +static void uts_cdc_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_uts *uts = req->context; + u8 doit = false; + + /* on this call path we do NOT hold the port spinlock, + * which is why ACM needs its own spinlock + */ + spin_lock(&uts->lock); + if (req->status != -ESHUTDOWN) + doit = uts->pending; + uts->notify_req = req; + spin_unlock(&uts->lock); + + if (doit) + uts_notify_serial_state(uts); +} + +/* connect == the TTY link is open */ + +static void uts_connect(struct gserial *port) +{ + struct f_uts *uts = port_to_uts(port); + + uts->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD; + uts_notify_serial_state(uts); +} + +static void uts_disconnect(struct gserial *port) +{ + struct f_uts *uts = port_to_uts(port); + + uts->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD); + uts_notify_serial_state(uts); +} + +static int uts_send_break(struct gserial *port, int duration) +{ + struct f_uts *uts = port_to_uts(port); + u16 state; + + state = uts->serial_state; + state &= ~ACM_CTRL_BRK; + if (duration) + state |= ACM_CTRL_BRK; + + uts->serial_state = state; + return uts_notify_serial_state(uts); +} + +/*-------------------------------------------------------------------------*/ + +/* ACM function driver setup/binding */ +static int +uts_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_uts *uts = func_to_uts(f); + struct usb_string *us; + int status; + struct usb_ep *ep; + + /* REVISIT might want instance-specific strings to help + * distinguish instances ... + */ + + /* maybe allocate device-global string IDs, and patch descriptors */ + us = usb_gstrings_attach(cdev, uts_strings, + ARRAY_SIZE(uts_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + uts_control_interface_desc.iInterface = us[ACM_CTRL_IDX].id; + uts_data_interface_desc.iInterface = us[ACM_DATA_IDX].id; + uts_iad_descriptor.iFunction = us[ACM_IAD_IDX].id; + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + uts->ctrl_id = status; + uts_iad_descriptor.bFirstInterface = status; + + uts_control_interface_desc.bInterfaceNumber = status; + uts_union_desc .bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + uts->data_id = status; + + uts_data_interface_desc.bInterfaceNumber = status; + uts_union_desc.bSlaveInterface0 = status; + uts_call_mgmt_descriptor.bDataInterface = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &uts_fs_in_desc); + if (!ep) + goto fail; + uts->port.in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &uts_fs_out_desc); + if (!ep) + goto fail; + uts->port.out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &uts_fs_notify_desc); + if (!ep) + goto fail; + uts->notify = ep; + + /* allocate notification */ + uts->notify_req = gs_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, + GFP_KERNEL); + if (!uts->notify_req) + goto fail; + + uts->notify_req->complete = uts_cdc_notify_complete; + uts->notify_req->context = uts; + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + uts_hs_in_desc.bEndpointAddress = uts_fs_in_desc.bEndpointAddress; + uts_hs_out_desc.bEndpointAddress = uts_fs_out_desc.bEndpointAddress; + uts_hs_notify_desc.bEndpointAddress = + uts_fs_notify_desc.bEndpointAddress; + + uts_ss_in_desc.bEndpointAddress = uts_fs_in_desc.bEndpointAddress; + uts_ss_out_desc.bEndpointAddress = uts_fs_out_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, uts_fs_function, uts_hs_function, + uts_ss_function, NULL); + if (status) + goto fail; + + dev_dbg(&cdev->gadget->dev, + "uts ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n", + uts->port_num, + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + uts->port.in->name, uts->port.out->name, + uts->notify->name); + return 0; + +fail: + if (uts->notify_req) + gs_free_req(uts->notify, uts->notify_req); + + ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status); + + return status; +} + +static void uts_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_uts *uts = func_to_uts(f); + + uts_string_defs[0].id = 0; + usb_free_all_descriptors(f); + if (uts->notify_req) + gs_free_req(uts->notify, uts->notify_req); +} + +static void uts_free_func(struct usb_function *f) +{ + struct f_uts *uts = func_to_uts(f); + + kfree(uts); +} + +static struct usb_function *uts_alloc_func(struct usb_function_instance *fi) +{ + struct f_serial_opts *opts; + struct f_uts *uts; + + uts = kzalloc(sizeof(*uts), GFP_KERNEL); + if (!uts) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&uts->lock); + + uts->port.connect = uts_connect; + uts->port.disconnect = uts_disconnect; + uts->port.send_break = uts_send_break; + + uts->port.func.name = "uts"; + uts->port.func.strings = uts_strings; + /* descriptors are per-instance copies */ + uts->port.func.bind = uts_bind; + uts->port.func.set_alt = uts_set_alt; + uts->port.func.setup = uts_setup; + uts->port.func.disable = uts_disable; + + opts = container_of(fi, struct f_serial_opts, func_inst); + uts->port_num = opts->port_num; + uts->port.func.unbind = uts_unbind; + uts->port.func.free_func = uts_free_func; + + return &uts->port.func; +} + +static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_serial_opts, + func_inst.group); +} + +static void uts_attr_release(struct config_item *item) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations uts_item_ops = { + .release = uts_attr_release, +}; + +static ssize_t f_uts_port_num_show(struct config_item *item, char *page) +{ + return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num); +} + +CONFIGFS_ATTR_RO(f_uts_, port_num); + +static struct configfs_attribute *uts_attrs[] = { + &f_uts_attr_port_num, + NULL, +}; + +static struct config_item_type uts_func_type = { + .ct_item_ops = &uts_item_ops, + .ct_attrs = uts_attrs, + .ct_owner = THIS_MODULE, +}; + +static void uts_free_instance(struct usb_function_instance *fi) +{ + struct f_serial_opts *opts; + + opts = container_of(fi, struct f_serial_opts, func_inst); + gserial_free_line(opts->port_num); + kfree(opts); +} + +static struct usb_function_instance *uts_alloc_instance(void) +{ + struct f_serial_opts *opts; + int ret; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + opts->func_inst.free_func_inst = uts_free_instance; + opts->port_num = UTS_PORT_NUM; + ret = gserial_alloc_line(&opts->port_num); + if (ret) { + kfree(opts); + return ERR_PTR(ret); + } + config_group_init_type_name(&opts->func_inst.group, "", + &uts_func_type); + return &opts->func_inst; +} +DECLARE_USB_FUNCTION_INIT(uts, uts_alloc_instance, uts_alloc_func); +MODULE_LICENSE("GPL"); diff --git a/include/linux/f_mtp.h b/include/linux/f_mtp.h new file mode 100644 index 000000000000..4e8417791bea --- /dev/null +++ b/include/linux/f_mtp.h @@ -0,0 +1,23 @@ +/* + * Gadget Function Driver for MTP + * + * Copyright (C) 2010 Google, Inc. + * Author: Mike Lockwood + * + * 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 __LINUX_USB_F_MTP_H +#define __LINUX_USB_F_MTP_H + +#include + +#endif /* __LINUX_USB_F_MTP_H */ diff --git a/include/linux/usb/f_mtp.h b/include/linux/usb/f_mtp.h new file mode 100644 index 000000000000..4e8417791bea --- /dev/null +++ b/include/linux/usb/f_mtp.h @@ -0,0 +1,23 @@ +/* + * Gadget Function Driver for MTP + * + * Copyright (C) 2010 Google, Inc. + * Author: Mike Lockwood + * + * 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 __LINUX_USB_F_MTP_H +#define __LINUX_USB_F_MTP_H + +#include + +#endif /* __LINUX_USB_F_MTP_H */ diff --git a/include/linux/usb/otg-fsm.h b/include/linux/usb/otg-fsm.h index a0a8f878503c..a893e4b51155 100644 --- a/include/linux/usb/otg-fsm.h +++ b/include/linux/usb/otg-fsm.h @@ -134,6 +134,8 @@ enum otg_fsm_timer { * to a_wait_vfall */ struct otg_fsm { + int reset; + /* Input */ int id; int adp_change; diff --git a/include/uapi/linux/usb/f_mtp.h b/include/uapi/linux/usb/f_mtp.h new file mode 100644 index 000000000000..503291855abd --- /dev/null +++ b/include/uapi/linux/usb/f_mtp.h @@ -0,0 +1,61 @@ +/* + * Gadget Function Driver for MTP + * + * Copyright (C) 2010 Google, Inc. + * Author: Mike Lockwood + * + * 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 _UAPI_LINUX_USB_F_MTP_H +#define _UAPI_LINUX_USB_F_MTP_H + +#include +#include + +struct mtp_file_range { + /* file descriptor for file to transfer */ + int fd; + /* offset in file for start of transfer */ + loff_t offset; + /* number of bytes to transfer */ + int64_t length; + /* MTP command ID for data header, + * used only for MTP_SEND_FILE_WITH_HEADER + */ + uint16_t command; + /* MTP transaction ID for data header, + * used only for MTP_SEND_FILE_WITH_HEADER + */ + uint32_t transaction_id; +}; + +struct mtp_event { + /* size of the event */ + size_t length; + /* event data to send */ + void *data; +}; + +/* Sends the specified file range to the host */ +#define MTP_SEND_FILE _IOW('M', 0, struct mtp_file_range) +/* Receives data from the host and writes it to a file. + * The file is created if it does not exist. + */ +#define MTP_RECEIVE_FILE _IOW('M', 1, struct mtp_file_range) +/* Sends an event to the host via the interrupt endpoint */ +#define MTP_SEND_EVENT _IOW('M', 3, struct mtp_event) +/* Sends the specified file range to the host, + * with a 12 byte MTP data packet header at the beginning. + */ +#define MTP_SEND_FILE_WITH_HEADER _IOW('M', 4, struct mtp_file_range) + +#endif /* _UAPI_LINUX_USB_F_MTP_H */ -- 2.20.1