Merge "usb: gadget: Resolve recursive spinlock during remote wakeup" into msm-4.8
diff --git a/Documentation/devicetree/bindings/arm/cache.txt b/Documentation/devicetree/bindings/arm/cache.txt
new file mode 100644
index 0000000..a9594f0
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/cache.txt
@@ -0,0 +1,195 @@
+==========================================
+ARM processors cache binding description
+==========================================
+
+Device tree bindings for ARM processor caches adhere to the cache bindings
+described in [3], in section 3.8 for multi-level and shared caches.
+On ARM based systems most of the cache properties related to cache
+geometry are probeable in HW, hence, unless otherwise stated, the properties
+defined in ePAPR for multi-level and shared caches are to be considered
+optional by default.
+
+On ARM, caches are either architected (directly controlled by the processor
+through coprocessor instructions and tightly coupled with the processor
+implementation) or unarchitected (controlled through a memory mapped
+interface, implemented as a stand-alone IP external to the processor
+implementation).
+
+This document provides the device tree bindings for ARM architected caches.
+
+- ARM architected cache node
+
+ Description: must be a direct child of the cpu node. A system
+ can contain multiple architected cache nodes per cpu node,
+ linked through the next-level-cache phandle. The
+ next-level-cache property in the cpu node points to
+ the first level of architected cache for the CPU.
+ The next-level-cache property in architected cache nodes
+ points to the respective next level of caching in the
+ hierarchy. An architected cache node with an empty or
+ missing next-level-cache property represents the last
+ architected cache level for the CPU.
+ On ARM v7 and v8 architectures, the order in which cache
+ nodes are linked through the next-level-cache phandle must
+ follow the ordering specified in the processors CLIDR (v7)
+ and CLIDR_EL1 (v8) registers, as described in [1][2],
+ implying that a cache node pointed at by a
+ next-level-cache phandle must correspond to a level
+ defined in CLIDR (v7) and CLIDR_EL1 (v8) greater than the
+ one the cache node containing the next-level-cache
+ phandle corresponds to.
+
+ Since on ARM most of the cache properties are probeable in HW the
+ properties described in [3] - section 3.8 multi-level and shared
+ caches - shall be considered optional, with the following properties
+ updates, specific for the ARM architected cache node.
+
+ - compatible
+ Usage: Required
+ Value type: <string>
+ Definition: value shall be "arm,arch-cache".
+
+ - interrupts
+ Usage: Optional
+ Value type: See definition
+ Definition: standard device tree property [3] that defines
+ the interrupt line associated with the cache.
+ The property can be accompanied by an
+ interrupt-names property, as described in [4].
+
+ - power-domain
+ Usage: Optional
+ Value type: phandle
+ Definition: A phandle and power domain specifier as defined by
+ bindings of power controller specified by the
+ phandle [5].
+
+ - qcom,dump-size
+ Usage: Optional
+ Value type: <integer>
+ Definition: The memory size needed to contain a copy of the
+ cache data and associated tag ram.
+ size = nways * nsets * (bytes per cache line +
+ bytes tag ram per line)
+
+Example(dual-cluster big.LITTLE system 32-bit)
+
+ cpus {
+ #size-cells = <0>;
+ #address-cells = <1>;
+
+ cpu@0 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a15";
+ reg = <0x0>;
+ next-level-cache = <&L1_0>;
+
+ L1_0: l1-cache {
+ compatible = "arm,arch-cache";
+ next-level-cache = <&L2_0>;
+ };
+
+ L2_0: l2-cache {
+ compatible = "arm,arch-cache";
+ };
+ };
+
+ cpu@1 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a15";
+ reg = <0x1>;
+ next-level-cache = <&L1_1>;
+
+ L1_1: l1-cache {
+ compatible = "arm,arch-cache";
+ next-level-cache = <&L2_0>;
+ };
+ };
+
+ cpu@2 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a15";
+ reg = <0x2>;
+ next-level-cache = <&L1_2>;
+
+ L1_2: l1-cache {
+ compatible = "arm,arch-cache";
+ next-level-cache = <&L2_0>;
+ };
+ };
+
+ cpu@3 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a15";
+ reg = <0x3>;
+ next-level-cache = <&L1_3>;
+
+ L1_3: l1-cache {
+ compatible = "arm,arch-cache";
+ next-level-cache = <&L2_0>;
+ };
+ };
+
+ cpu@100 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <0x100>;
+ next-level-cache = <&L1_4>;
+
+ L1_4: l1-cache {
+ compatible = "arm,arch-cache";
+ next-level-cache = <&L2_1>;
+ };
+
+ L2_1: l2-cache {
+ compatible = "arm,arch-cache";
+ };
+ };
+
+ cpu@101 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <0x101>;
+ next-level-cache = <&L1_5>;
+
+ L1_5: l1-cache {
+ compatible = "arm,arch-cache";
+ next-level-cache = <&L2_1>;
+ };
+ };
+
+ cpu@102 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <0x102>;
+ next-level-cache = <&L1_6>;
+
+ L1_6: l1-cache {
+ compatible = "arm,arch-cache";
+ next-level-cache = <&L2_1>;
+ };
+ };
+
+ cpu@103 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <0x103>;
+ next-level-cache = <&L1_7>;
+
+ L1_7: l1-cache {
+ compatible = "arm,arch-cache";
+ next-level-cache = <&L2_1>;
+ };
+ };
+ };
+
+[1] ARMv7-AR Reference Manual
+ http://infocenter.arm.com/help/index.jsp
+[2] ARMv8-A Reference Manual
+ http://infocenter.arm.com/help/index.jsp
+[3] ePAPR standard
+ https://www.power.org/documentation/epapr-version-1-1/
+[4] Kernel documentation - resource property bindings
+ Documentation/devicetree/bindings/resource-names.txt
+[5] Kernel documentation - power domain bindings
+ Documentation/devicetree/bindings/power/power_domain.txt
diff --git a/Documentation/devicetree/bindings/usb/msm-ssusb.txt b/Documentation/devicetree/bindings/usb/msm-ssusb.txt
index 1c870ac..e5113aa 100644
--- a/Documentation/devicetree/bindings/usb/msm-ssusb.txt
+++ b/Documentation/devicetree/bindings/usb/msm-ssusb.txt
@@ -60,6 +60,8 @@
should point to external connector device, which provide "USB-HOST"
cable events. A single phandle may be specified if a single connector
device provides both "USB" and "USB-HOST" events.
+- qcom,num-gsi-evt-buffs: If present, specifies number of GSI based hardware accelerated
+ event buffers. 1 event buffer is needed per h/w accelerated endpoint.
Sub nodes:
- Sub node for "DWC3- USB3 controller".
@@ -84,6 +86,7 @@
qcom,dwc-usb3-msm-tx-fifo-size = <29696>;
qcom,usb-dbm = <&dbm_1p4>;
qcom,lpm-to-suspend-delay-ms = <2>;
+ qcom,num-gsi-evt-buffs = <0x2>;
qcom,msm_bus,name = "usb3";
qcom,msm_bus,num_cases = <2>;
diff --git a/arch/arm64/boot/dts/qcom/msmskunk-usb.dtsi b/arch/arm64/boot/dts/qcom/msmskunk-usb.dtsi
index 317deae..74e6172 100644
--- a/arch/arm64/boot/dts/qcom/msmskunk-usb.dtsi
+++ b/arch/arm64/boot/dts/qcom/msmskunk-usb.dtsi
@@ -28,6 +28,7 @@
USB3_GDSC-supply = <&usb30_prim_gdsc>;
qcom,usb-dbm = <&dbm_1p5>;
qcom,dwc-usb3-msm-tx-fifo-size = <21288>;
+ qcom,num-gsi-evt-buffs = <0x3>;
clocks = <&clock_gcc GCC_USB30_PRIM_MASTER_CLK>,
<&clock_gcc GCC_CFG_NOC_USB3_PRIM_AXI_CLK>,
@@ -52,7 +53,6 @@
tx-fifo-resize;
snps,nominal-elastic-buffer;
snps,hird_thresh = <0x10>;
- snps,num-gsi-evt-buffs = <0x3>;
};
};
diff --git a/arch/arm64/boot/dts/qcom/msmskunk.dtsi b/arch/arm64/boot/dts/qcom/msmskunk.dtsi
index 692add7..d001365 100644
--- a/arch/arm64/boot/dts/qcom/msmskunk.dtsi
+++ b/arch/arm64/boot/dts/qcom/msmskunk.dtsi
@@ -798,6 +798,59 @@
status = "ok";
};
+ qcom,msm_fastrpc {
+ compatible = "qcom,msm-fastrpc-compute";
+
+ qcom,msm_fastrpc_compute_cb1 {
+ compatible = "qcom,msm-fastrpc-compute-cb";
+ label = "cdsprpc-smd";
+ iommus = <&apps_smmu 0x1401>,
+ <&apps_smmu 0x1421>;
+ };
+ qcom,msm_fastrpc_compute_cb2 {
+ compatible = "qcom,msm-fastrpc-compute-cb";
+ label = "cdsprpc-smd";
+ iommus = <&apps_smmu 0x1402>,
+ <&apps_smmu 0x1422>;
+ };
+ qcom,msm_fastrpc_compute_cb3 {
+ compatible = "qcom,msm-fastrpc-compute-cb";
+ label = "cdsprpc-smd";
+ iommus = <&apps_smmu 0x1403>,
+ <&apps_smmu 0x1423>;
+ };
+ qcom,msm_fastrpc_compute_cb4 {
+ compatible = "qcom,msm-fastrpc-compute-cb";
+ label = "cdsprpc-smd";
+ iommus = <&apps_smmu 0x1404>,
+ <&apps_smmu 0x1424>;
+ };
+ qcom,msm_fastrpc_compute_cb5 {
+ compatible = "qcom,msm-fastrpc-compute-cb";
+ label = "cdsprpc-smd";
+ iommus = <&apps_smmu 0x1405>,
+ <&apps_smmu 0x1425>;
+ };
+ qcom,msm_fastrpc_compute_cb6 {
+ compatible = "qcom,msm-fastrpc-compute-cb";
+ label = "cdsprpc-smd";
+ iommus = <&apps_smmu 0x1406>,
+ <&apps_smmu 0x1426>;
+ };
+ qcom,msm_fastrpc_compute_cb7 {
+ compatible = "qcom,msm-fastrpc-compute-cb";
+ label = "cdsprpc-smd";
+ iommus = <&apps_smmu 0x1407>,
+ <&apps_smmu 0x1427>;
+ };
+ qcom,msm_fastrpc_compute_cb8 {
+ compatible = "qcom,msm-fastrpc-compute-cb";
+ label = "cdsprpc-smd";
+ iommus = <&apps_smmu 0x1408>,
+ <&apps_smmu 0x1428>;
+ };
+ };
+
qcom,msm-imem@146bf000 {
compatible = "qcom,msm-imem";
reg = <0x146bf000 0x1000>;
diff --git a/arch/arm64/configs/msmskunk-perf_defconfig b/arch/arm64/configs/msmskunk-perf_defconfig
index faf1351..cb1f46e 100644
--- a/arch/arm64/configs/msmskunk-perf_defconfig
+++ b/arch/arm64/configs/msmskunk-perf_defconfig
@@ -243,6 +243,7 @@
# CONFIG_DEVMEM is not set
# CONFIG_DEVKMEM is not set
CONFIG_HW_RANDOM=y
+CONFIG_MSM_ADSPRPC=y
CONFIG_I2C_CHARDEV=y
CONFIG_SPI=y
CONFIG_SPI_QUP=y
diff --git a/arch/arm64/configs/msmskunk_defconfig b/arch/arm64/configs/msmskunk_defconfig
index dde0e84..52d5f5a 100644
--- a/arch/arm64/configs/msmskunk_defconfig
+++ b/arch/arm64/configs/msmskunk_defconfig
@@ -250,6 +250,7 @@
CONFIG_SERIAL_MSM_CONSOLE=y
CONFIG_HVC_DCC=y
CONFIG_HW_RANDOM=y
+CONFIG_MSM_ADSPRPC=y
CONFIG_I2C_CHARDEV=y
CONFIG_SPI=y
CONFIG_SPI_QUP=y
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 7a3d696..8745af1d 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -356,6 +356,9 @@
evt = dwc->ev_buf;
if (evt)
dwc3_free_one_event_buffer(dwc, evt);
+
+ /* free GSI related event buffers */
+ dwc3_notify_event(dwc, DWC3_GSI_EVT_BUF_FREE);
}
/**
@@ -377,6 +380,8 @@
}
dwc->ev_buf = evt;
+ /* alloc GSI related event buffers */
+ dwc3_notify_event(dwc, DWC3_GSI_EVT_BUF_ALLOC);
return 0;
}
@@ -406,6 +411,8 @@
DWC3_GEVNTSIZ_SIZE(evt->length));
dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), 0);
+ /* setup GSI related event buffers */
+ dwc3_notify_event(dwc, DWC3_GSI_EVT_BUF_SETUP);
return 0;
}
@@ -422,6 +429,9 @@
dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0), DWC3_GEVNTSIZ_INTMASK
| DWC3_GEVNTSIZ_SIZE(0));
dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), 0);
+
+ /* cleanup GSI related event buffers */
+ dwc3_notify_event(dwc, DWC3_GSI_EVT_BUF_CLEANUP);
}
static int dwc3_alloc_scratch_buffers(struct dwc3 *dwc)
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index fcbc14e..17a27fe 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -780,6 +780,12 @@
#define DWC3_CONTROLLER_SET_CURRENT_DRAW_EVENT 7
#define DWC3_CONTROLLER_RESTART_USB_SESSION 8
+/* USB GSI event buffer related notification */
+#define DWC3_GSI_EVT_BUF_ALLOC 9
+#define DWC3_GSI_EVT_BUF_SETUP 10
+#define DWC3_GSI_EVT_BUF_CLEANUP 11
+#define DWC3_GSI_EVT_BUF_FREE 12
+
#define MAX_INTR_STATS 10
/**
diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c
index 57db6d9..1dd3f88 100644
--- a/drivers/usb/dwc3/dwc3-msm.c
+++ b/drivers/usb/dwc3/dwc3-msm.c
@@ -117,6 +117,11 @@
#define GSI_IF_STS (QSCRATCH_REG_OFFSET + 0x1A4)
#define GSI_WR_CTRL_STATE_MASK BIT(15)
+#define DWC3_GEVNTCOUNT_EVNTINTRPTMASK (1 << 31)
+#define DWC3_GEVNTADRHI_EVNTADRHI_GSI_EN(n) (n << 22)
+#define DWC3_GEVNTADRHI_EVNTADRHI_GSI_IDX(n) (n << 16)
+#define DWC3_GEVENT_TYPE_GSI 0x3
+
struct dwc3_msm_req_complete {
struct list_head list_item;
struct usb_request *req;
@@ -213,6 +218,8 @@
unsigned int lpm_to_suspend_delay;
bool init;
enum plug_orientation typec_orientation;
+ u32 num_gsi_event_buffers;
+ struct dwc3_event_buffer **gsi_ev_buff;
};
#define USB_HSPHY_3P3_VOL_MIN 3050000 /* uV */
@@ -232,7 +239,7 @@
static void dwc3_pwr_event_handler(struct dwc3_msm *mdwc);
static int dwc3_msm_gadget_vbus_draw(struct dwc3_msm *mdwc, unsigned int mA);
-
+static void dwc3_msm_notify_event(struct dwc3 *dwc, unsigned int event);
/**
*
* Read register with debug info.
@@ -1608,10 +1615,9 @@
static void dwc3_msm_notify_event(struct dwc3 *dwc, unsigned int event)
{
struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent);
+ struct dwc3_event_buffer *evt;
u32 reg;
-
- if (dwc->revision < DWC3_REVISION_230A)
- return;
+ int i;
switch (event) {
case DWC3_CONTROLLER_ERROR_EVENT:
@@ -1696,6 +1702,93 @@
dev_dbg(mdwc->dev, "DWC3_CONTROLLER_RESTART_USB_SESSION received\n");
schedule_work(&mdwc->restart_usb_work);
break;
+ case DWC3_GSI_EVT_BUF_ALLOC:
+ dev_dbg(mdwc->dev, "DWC3_GSI_EVT_BUF_ALLOC\n");
+
+ if (!mdwc->num_gsi_event_buffers)
+ break;
+
+ mdwc->gsi_ev_buff = devm_kzalloc(dwc->dev,
+ sizeof(*dwc->ev_buf) * mdwc->num_gsi_event_buffers,
+ GFP_KERNEL);
+ if (!mdwc->gsi_ev_buff) {
+ dev_err(dwc->dev, "can't allocate gsi_ev_buff\n");
+ break;
+ }
+
+ for (i = 0; i < mdwc->num_gsi_event_buffers; i++) {
+
+ evt = devm_kzalloc(dwc->dev, sizeof(*evt), GFP_KERNEL);
+ if (!evt)
+ break;
+ evt->dwc = dwc;
+ evt->length = DWC3_EVENT_BUFFERS_SIZE;
+ evt->buf = dma_alloc_coherent(dwc->dev,
+ DWC3_EVENT_BUFFERS_SIZE,
+ &evt->dma, GFP_KERNEL);
+ if (!evt->buf) {
+ dev_err(dwc->dev,
+ "can't allocate gsi_evt_buf(%d)\n", i);
+ break;
+ }
+ mdwc->gsi_ev_buff[i] = evt;
+ }
+ break;
+ case DWC3_GSI_EVT_BUF_SETUP:
+ dev_dbg(mdwc->dev, "DWC3_GSI_EVT_BUF_SETUP\n");
+ for (i = 0; i < mdwc->num_gsi_event_buffers; i++) {
+ evt = mdwc->gsi_ev_buff[i];
+ dev_dbg(mdwc->dev, "Evt buf %p dma %08llx length %d\n",
+ evt->buf, (unsigned long long) evt->dma,
+ evt->length);
+ memset(evt->buf, 0, evt->length);
+ evt->lpos = 0;
+ /*
+ * Primary event buffer is programmed with registers
+ * DWC3_GEVNT*(0). Hence use DWC3_GEVNT*(i+1) to
+ * program USB GSI related event buffer with DWC3
+ * controller.
+ */
+ dwc3_writel(dwc->regs, DWC3_GEVNTADRLO((i+1)),
+ lower_32_bits(evt->dma));
+ dwc3_writel(dwc->regs, DWC3_GEVNTADRHI((i+1)),
+ DWC3_GEVNTADRHI_EVNTADRHI_GSI_EN(
+ DWC3_GEVENT_TYPE_GSI) |
+ DWC3_GEVNTADRHI_EVNTADRHI_GSI_IDX((i+1)));
+ dwc3_writel(dwc->regs, DWC3_GEVNTSIZ((i+1)),
+ DWC3_GEVNTCOUNT_EVNTINTRPTMASK |
+ ((evt->length) & 0xffff));
+ dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT((i+1)), 0);
+ }
+ break;
+ case DWC3_GSI_EVT_BUF_CLEANUP:
+ dev_dbg(mdwc->dev, "DWC3_GSI_EVT_BUF_CLEANUP\n");
+ for (i = 0; i < mdwc->num_gsi_event_buffers; i++) {
+ evt = mdwc->gsi_ev_buff[i];
+ evt->lpos = 0;
+ /*
+ * Primary event buffer is programmed with registers
+ * DWC3_GEVNT*(0). Hence use DWC3_GEVNT*(i+1) to
+ * program USB GSI related event buffer with DWC3
+ * controller.
+ */
+ dwc3_writel(dwc->regs, DWC3_GEVNTADRLO((i+1)), 0);
+ dwc3_writel(dwc->regs, DWC3_GEVNTADRHI((i+1)), 0);
+ dwc3_writel(dwc->regs, DWC3_GEVNTSIZ((i+1)),
+ DWC3_GEVNTSIZ_INTMASK |
+ DWC3_GEVNTSIZ_SIZE((i+1)));
+ dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT((i+1)), 0);
+ }
+ break;
+ case DWC3_GSI_EVT_BUF_FREE:
+ dev_dbg(mdwc->dev, "DWC3_GSI_EVT_BUF_FREE\n");
+ for (i = 0; i < mdwc->num_gsi_event_buffers; i++) {
+ evt = mdwc->gsi_ev_buff[i];
+ if (evt)
+ dma_free_coherent(dwc->dev, evt->length,
+ evt->buf, evt->dma);
+ }
+ break;
default:
dev_dbg(mdwc->dev, "unknown dwc3 event\n");
break;
@@ -2878,6 +2971,9 @@
if (cpu_to_affin)
register_cpu_notifier(&mdwc->dwc3_cpu_notifier);
+ ret = of_property_read_u32(node, "qcom,num-gsi-evt-buffs",
+ &mdwc->num_gsi_event_buffers);
+
/*
* Clocks and regulators will not be turned on until the first time
* runtime PM resume is called. This is to allow for booting up with
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 990d77d..3f5fad2 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -862,6 +862,13 @@
dep->name))
return 0;
+ /* Keep GSI ep names with "-gsi" suffix */
+ if (!strnstr(dep->name, "gsi", 10)) {
+ snprintf(dep->name, sizeof(dep->name), "ep%d%s",
+ dep->number >> 1,
+ (dep->number & 1) ? "in" : "out");
+ }
+
spin_lock_irqsave(&dwc->lock, flags);
ret = __dwc3_gadget_ep_disable(dep);
spin_unlock_irqrestore(&dwc->lock, flags);
@@ -2218,11 +2225,22 @@
/* -------------------------------------------------------------------------- */
+#define NUM_GSI_OUT_EPS 1
+#define NUM_GSI_IN_EPS 2
+
static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc,
u8 num, u32 direction)
{
struct dwc3_ep *dep;
- u8 i;
+ u8 i, gsi_ep_count, gsi_ep_index = 0;
+
+ gsi_ep_count = NUM_GSI_OUT_EPS + NUM_GSI_IN_EPS;
+ /* OUT GSI EPs based on direction field */
+ if (gsi_ep_count && !direction)
+ gsi_ep_count = NUM_GSI_OUT_EPS;
+ /* IN GSI EPs */
+ else if (gsi_ep_count && direction)
+ gsi_ep_count = NUM_GSI_IN_EPS;
for (i = 0; i < num; i++) {
u8 epnum = (i << 1) | (direction ? 1 : 0);
@@ -2237,9 +2255,21 @@
dep->regs = dwc->regs + DWC3_DEP_BASE(epnum);
dwc->eps[epnum] = dep;
- snprintf(dep->name, sizeof(dep->name), "ep%d%s", epnum >> 1,
- (epnum & 1) ? "in" : "out");
+ /* Reserve EPs at the end for GSI based on gsi_ep_count */
+ if ((gsi_ep_index < gsi_ep_count) &&
+ (i > (num - 1 - gsi_ep_count))) {
+ gsi_ep_index++;
+ /* For GSI EPs, name eps as "gsi-epin" or "gsi-epout" */
+ snprintf(dep->name, sizeof(dep->name), "%s",
+ (epnum & 1) ? "gsi-epin" : "gsi-epout");
+ /* Set ep type as GSI */
+ dep->endpoint.ep_type = EP_TYPE_GSI;
+ } else {
+ snprintf(dep->name, sizeof(dep->name), "ep%d%s",
+ epnum >> 1, (epnum & 1) ? "in" : "out");
+ }
+ dep->endpoint.ep_num = epnum >> 1;
dep->endpoint.name = dep->name;
spin_lock_init(&dep->lock);
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 87f2b73..987dcef 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -224,6 +224,9 @@
config USB_F_DIAG
tristate
+config USB_F_CDEV
+ tristate
+
# this first set of drivers all depend on bulk-capable hardware.
config USB_CONFIGFS
@@ -518,6 +521,13 @@
Diag function driver enables support for Qualcomm diagnostics
port over USB.
+config USB_CONFIGFS_F_CDEV
+ bool "USB Serial Character function"
+ select USB_F_CDEV
+ depends on USB_CONFIGFS
+ help
+ Generic USB serial character function driver to support DUN/NMEA.
+
choice
tristate "USB Gadget Drivers"
default USB_ETH
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index 459953d..1d774e6 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -56,3 +56,5 @@
obj-$(CONFIG_USB_F_ACC) += usb_f_accessory.o
usb_f_diag-y := f_diag.o
obj-$(CONFIG_USB_F_DIAG) += usb_f_diag.o
+usb_f_cdev-y := f_cdev.o
+obj-$(CONFIG_USB_F_CDEV) += usb_f_cdev.o
diff --git a/drivers/usb/gadget/function/f_cdev.c b/drivers/usb/gadget/function/f_cdev.c
new file mode 100644
index 0000000..920c08a
--- /dev/null
+++ b/drivers/usb/gadget/function/f_cdev.c
@@ -0,0 +1,1844 @@
+/*
+ * Copyright (c) 2011, 2013-2016, The Linux Foundation. All rights reserved.
+ * Linux Foundation chooses to take subject only to the GPLv2 license terms,
+ * and distributes only under these terms.
+ *
+ * This code also borrows from drivers/usb/gadget/u_serial.c, which is
+ * Copyright (C) 2000 - 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2000 Peter Berger (pberger@brimson.com)
+ *
+ * f_cdev_read() API implementation is using borrowed code from
+ * drivers/usb/gadget/legacy/printer.c, which is
+ * Copyright (C) 2003-2005 David Brownell
+ * Copyright (C) 2006 Craig W. Nadler
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 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.
+ */
+
+#ifdef pr_fmt
+#undef pr_fmt
+#endif
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/cdev.h>
+#include <linux/spinlock.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/composite.h>
+#include <linux/module.h>
+#include <asm/ioctls.h>
+#include <asm-generic/termios.h>
+
+#define DEVICE_NAME "at_usb"
+#define MODULE_NAME "msm_usb_bridge"
+#define NUM_INSTANCE 2
+
+#define MAX_CDEV_INST_NAME 15
+#define MAX_CDEV_FUNC_NAME 5
+
+#define BRIDGE_RX_QUEUE_SIZE 8
+#define BRIDGE_RX_BUF_SIZE 2048
+#define BRIDGE_TX_QUEUE_SIZE 8
+#define BRIDGE_TX_BUF_SIZE 2048
+
+#define GS_LOG2_NOTIFY_INTERVAL 5 /* 1 << 5 == 32 msec */
+#define GS_NOTIFY_MAXPACKET 10 /* notification + 2 bytes */
+
+struct cserial {
+ struct usb_function func;
+ struct usb_ep *in;
+ struct usb_ep *out;
+ struct usb_ep *notify;
+ struct usb_request *notify_req;
+ struct usb_cdc_line_coding port_line_coding;
+ u8 pending;
+ u8 q_again;
+ u8 data_id;
+ u16 serial_state;
+ u16 port_handshake_bits;
+ /* control signal callbacks*/
+ unsigned int (*get_dtr)(struct cserial *p);
+ unsigned int (*get_rts)(struct cserial *p);
+
+ /* notification callbacks */
+ void (*connect)(struct cserial *p);
+ void (*disconnect)(struct cserial *p);
+ int (*send_break)(struct cserial *p, int duration);
+ unsigned int (*send_carrier_detect)(struct cserial *p,
+ unsigned int val);
+ unsigned int (*send_ring_indicator)(struct cserial *p,
+ unsigned int val);
+ int (*send_modem_ctrl_bits)(struct cserial *p, int ctrl_bits);
+
+ /* notification changes to modem */
+ void (*notify_modem)(void *port, int ctrl_bits);
+};
+
+struct f_cdev {
+ struct cdev fcdev_cdev;
+ struct device *dev;
+ unsigned int port_num;
+ char name[sizeof(DEVICE_NAME) + 2];
+ int minor;
+
+ spinlock_t port_lock;
+
+ wait_queue_head_t open_wq;
+ wait_queue_head_t read_wq;
+
+ struct list_head read_pool;
+ struct list_head read_queued;
+ struct list_head write_pool;
+
+ /* current active USB RX request */
+ struct usb_request *current_rx_req;
+ /* number of pending bytes */
+ size_t pending_rx_bytes;
+ /* current USB RX buffer */
+ u8 *current_rx_buf;
+
+ struct cserial port_usb;
+
+#define ACM_CTRL_DTR 0x01
+#define ACM_CTRL_RTS 0x02
+#define ACM_CTRL_DCD 0x01
+#define ACM_CTRL_DSR 0x02
+#define ACM_CTRL_BRK 0x04
+#define ACM_CTRL_RI 0x08
+
+ unsigned int cbits_to_modem;
+ bool cbits_updated;
+
+ struct workqueue_struct *fcdev_wq;
+ bool is_connected;
+ bool port_open;
+
+ unsigned long nbytes_from_host;
+ unsigned long nbytes_to_host;
+ unsigned long nbytes_to_port_bridge;
+ unsigned long nbytes_from_port_bridge;
+};
+
+struct f_cdev_opts {
+ struct usb_function_instance func_inst;
+ struct f_cdev *port;
+ char *func_name;
+ u8 port_num;
+};
+
+static int major, minors;
+struct class *fcdev_classp;
+static DEFINE_IDA(chardev_ida);
+static DEFINE_MUTEX(chardev_ida_lock);
+
+static int usb_cser_alloc_chardev_region(void);
+static void usb_cser_chardev_deinit(void);
+static void usb_cser_read_complete(struct usb_ep *ep, struct usb_request *req);
+static int usb_cser_connect(struct f_cdev *port);
+static void usb_cser_disconnect(struct f_cdev *port);
+static struct f_cdev *f_cdev_alloc(char *func_name, int portno);
+static void usb_cser_free_req(struct usb_ep *ep, struct usb_request *req);
+
+static struct usb_interface_descriptor cser_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ /* .bInterfaceNumber = DYNAMIC */
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ /* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc cser_header_desc = {
+ .bLength = sizeof(cser_header_desc),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_HEADER_TYPE,
+ .bcdCDC = cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_call_mgmt_descriptor
+cser_call_mgmt_descriptor = {
+ .bLength = sizeof(cser_call_mgmt_descriptor),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE,
+ .bmCapabilities = 0,
+ /* .bDataInterface = DYNAMIC */
+};
+
+static struct usb_cdc_acm_descriptor cser_descriptor = {
+ .bLength = sizeof(cser_descriptor),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_ACM_TYPE,
+ .bmCapabilities = USB_CDC_CAP_LINE,
+};
+
+static struct usb_cdc_union_desc cser_union_desc = {
+ .bLength = sizeof(cser_union_desc),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_UNION_TYPE,
+ /* .bMasterInterface0 = DYNAMIC */
+ /* .bSlaveInterface0 = DYNAMIC */
+};
+
+/* full speed support: */
+static struct usb_endpoint_descriptor cser_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 = 1 << GS_LOG2_NOTIFY_INTERVAL,
+};
+
+static struct usb_endpoint_descriptor cser_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 cser_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 *cser_fs_function[] = {
+ (struct usb_descriptor_header *) &cser_interface_desc,
+ (struct usb_descriptor_header *) &cser_header_desc,
+ (struct usb_descriptor_header *) &cser_call_mgmt_descriptor,
+ (struct usb_descriptor_header *) &cser_descriptor,
+ (struct usb_descriptor_header *) &cser_union_desc,
+ (struct usb_descriptor_header *) &cser_fs_notify_desc,
+ (struct usb_descriptor_header *) &cser_fs_in_desc,
+ (struct usb_descriptor_header *) &cser_fs_out_desc,
+ NULL,
+};
+
+/* high speed support: */
+static struct usb_endpoint_descriptor cser_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 = GS_LOG2_NOTIFY_INTERVAL+4,
+};
+
+static struct usb_endpoint_descriptor cser_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 cser_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 *cser_hs_function[] = {
+ (struct usb_descriptor_header *) &cser_interface_desc,
+ (struct usb_descriptor_header *) &cser_header_desc,
+ (struct usb_descriptor_header *) &cser_call_mgmt_descriptor,
+ (struct usb_descriptor_header *) &cser_descriptor,
+ (struct usb_descriptor_header *) &cser_union_desc,
+ (struct usb_descriptor_header *) &cser_hs_notify_desc,
+ (struct usb_descriptor_header *) &cser_hs_in_desc,
+ (struct usb_descriptor_header *) &cser_hs_out_desc,
+ NULL,
+};
+
+static struct usb_endpoint_descriptor cser_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 cser_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 cser_ss_bulk_comp_desc = {
+ .bLength = sizeof(cser_ss_bulk_comp_desc),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_endpoint_descriptor cser_ss_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_LOG2_NOTIFY_INTERVAL+4,
+};
+
+static struct usb_ss_ep_comp_descriptor cser_ss_notify_comp_desc = {
+ .bLength = sizeof(cser_ss_notify_comp_desc),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /* the following 2 values can be tweaked if necessary */
+ /* .bMaxBurst = 0, */
+ /* .bmAttributes = 0, */
+ .wBytesPerInterval = cpu_to_le16(GS_NOTIFY_MAXPACKET),
+};
+
+static struct usb_descriptor_header *cser_ss_function[] = {
+ (struct usb_descriptor_header *) &cser_interface_desc,
+ (struct usb_descriptor_header *) &cser_header_desc,
+ (struct usb_descriptor_header *) &cser_call_mgmt_descriptor,
+ (struct usb_descriptor_header *) &cser_descriptor,
+ (struct usb_descriptor_header *) &cser_union_desc,
+ (struct usb_descriptor_header *) &cser_ss_notify_desc,
+ (struct usb_descriptor_header *) &cser_ss_notify_comp_desc,
+ (struct usb_descriptor_header *) &cser_ss_in_desc,
+ (struct usb_descriptor_header *) &cser_ss_bulk_comp_desc,
+ (struct usb_descriptor_header *) &cser_ss_out_desc,
+ (struct usb_descriptor_header *) &cser_ss_bulk_comp_desc,
+ NULL,
+};
+
+/* string descriptors: */
+static struct usb_string cser_string_defs[] = {
+ [0].s = "CDEV Serial",
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings cser_string_table = {
+ .language = 0x0409, /* en-us */
+ .strings = cser_string_defs,
+};
+
+static struct usb_gadget_strings *usb_cser_strings[] = {
+ &cser_string_table,
+ NULL,
+};
+
+static inline struct f_cdev *func_to_port(struct usb_function *f)
+{
+ return container_of(f, struct f_cdev, port_usb.func);
+}
+
+static inline struct f_cdev *cser_to_port(struct cserial *cser)
+{
+ return container_of(cser, struct f_cdev, port_usb);
+}
+
+static unsigned int convert_acm_sigs_to_uart(unsigned int acm_sig)
+{
+ unsigned int uart_sig = 0;
+
+ acm_sig &= (ACM_CTRL_DTR | ACM_CTRL_RTS);
+ if (acm_sig & ACM_CTRL_DTR)
+ uart_sig |= TIOCM_DTR;
+
+ if (acm_sig & ACM_CTRL_RTS)
+ uart_sig |= TIOCM_RTS;
+
+ return uart_sig;
+}
+
+static void port_complete_set_line_coding(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ struct f_cdev *port = ep->driver_data;
+ struct usb_composite_dev *cdev = port->port_usb.func.config->cdev;
+
+ if (req->status != 0) {
+ dev_dbg(&cdev->gadget->dev, "port(%s) completion, err %d\n",
+ port->name, req->status);
+ return;
+ }
+
+ /* normal completion */
+ if (req->actual != sizeof(port->port_usb.port_line_coding)) {
+ dev_dbg(&cdev->gadget->dev, "port(%s) short resp, len %d\n",
+ port->name, req->actual);
+ usb_ep_set_halt(ep);
+ } else {
+ struct usb_cdc_line_coding *value = req->buf;
+
+ port->port_usb.port_line_coding = *value;
+ }
+}
+
+static void usb_cser_free_func(struct usb_function *f)
+{
+ /* Do nothing as cser_alloc() doesn't alloc anything. */
+}
+
+static int
+usb_cser_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct f_cdev *port = func_to_port(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);
+
+ 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))
+ goto invalid;
+
+ value = w_length;
+ cdev->gadget->ep0->driver_data = port;
+ req->complete = port_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:
+ value = min_t(unsigned int, w_length,
+ sizeof(struct usb_cdc_line_coding));
+ memcpy(req->buf, &port->port_usb.port_line_coding, value);
+ break;
+
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_REQ_SET_CONTROL_LINE_STATE:
+
+ value = 0;
+ port->port_usb.port_handshake_bits = w_value;
+ pr_debug("USB_CDC_REQ_SET_CONTROL_LINE_STATE: DTR:%d RST:%d\n",
+ w_value & ACM_CTRL_DTR ? 1 : 0,
+ w_value & ACM_CTRL_RTS ? 1 : 0);
+ if (port->port_usb.notify_modem)
+ port->port_usb.notify_modem(port, w_value);
+
+ break;
+
+ default:
+invalid:
+ dev_dbg(&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,
+ "port(%s) req%02x.%02x v%04x i%04x l%d\n",
+ port->name, 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)
+ pr_err("port response on (%s), err %d\n",
+ port->name, value);
+ }
+
+ /* device either stalls (value < 0) or reports success */
+ return value;
+}
+
+static int usb_cser_set_alt(struct usb_function *f, unsigned int intf,
+ unsigned int alt)
+{
+ struct f_cdev *port = func_to_port(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int rc = 0;
+
+ if (port->port_usb.notify->driver_data) {
+ dev_dbg(&cdev->gadget->dev,
+ "reset port(%s)\n", port->name);
+ usb_ep_disable(port->port_usb.notify);
+ }
+
+ if (!port->port_usb.notify->desc) {
+ if (config_ep_by_speed(cdev->gadget, f,
+ port->port_usb.notify)) {
+ port->port_usb.notify->desc = NULL;
+ return -EINVAL;
+ }
+ }
+
+ rc = usb_ep_enable(port->port_usb.notify);
+ if (rc) {
+ dev_err(&cdev->gadget->dev, "can't enable %s, result %d\n",
+ port->port_usb.notify->name, rc);
+ return rc;
+ }
+ port->port_usb.notify->driver_data = port;
+
+ if (port->port_usb.in->driver_data) {
+ dev_dbg(&cdev->gadget->dev,
+ "reset port(%s)\n", port->name);
+ usb_cser_disconnect(port);
+ }
+ if (!port->port_usb.in->desc || !port->port_usb.out->desc) {
+ dev_dbg(&cdev->gadget->dev,
+ "activate port(%s)\n", port->name);
+ if (config_ep_by_speed(cdev->gadget, f, port->port_usb.in) ||
+ config_ep_by_speed(cdev->gadget, f,
+ port->port_usb.out)) {
+ port->port_usb.in->desc = NULL;
+ port->port_usb.out->desc = NULL;
+ return -EINVAL;
+ }
+ }
+
+ usb_cser_connect(port);
+ return rc;
+}
+
+static void usb_cser_disable(struct usb_function *f)
+{
+ struct f_cdev *port = func_to_port(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+
+ dev_dbg(&cdev->gadget->dev,
+ "port(%s) deactivated\n", port->name);
+
+ usb_cser_disconnect(port);
+ usb_ep_disable(port->port_usb.notify);
+ port->port_usb.notify->driver_data = NULL;
+}
+
+static int usb_cser_notify(struct f_cdev *port, u8 type, u16 value,
+ void *data, unsigned int length)
+{
+ struct usb_ep *ep = port->port_usb.notify;
+ struct usb_request *req;
+ struct usb_cdc_notification *notify;
+ const unsigned int len = sizeof(*notify) + length;
+ void *buf;
+ int status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->is_connected) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ pr_debug("%s: port disconnected\n", __func__);
+ return -ENODEV;
+ }
+
+ req = port->port_usb.notify_req;
+
+ 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(port->port_usb.data_id);
+ notify->wLength = cpu_to_le16(length);
+ /* 2 byte data copy */
+ memcpy(buf, data, length);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ status = usb_ep_queue(ep, req, GFP_ATOMIC);
+ if (status < 0) {
+ pr_err("port %s can't notify serial state, %d\n",
+ port->name, status);
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->port_usb.pending = false;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ }
+
+ return status;
+}
+
+static int port_notify_serial_state(struct cserial *cser)
+{
+ struct f_cdev *port = cser_to_port(cser);
+ int status;
+ unsigned long flags;
+ struct usb_composite_dev *cdev = port->port_usb.func.config->cdev;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->port_usb.pending) {
+ port->port_usb.pending = true;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ dev_dbg(&cdev->gadget->dev, "port %d serial state %04x\n",
+ port->port_num, port->port_usb.serial_state);
+ status = usb_cser_notify(port, USB_CDC_NOTIFY_SERIAL_STATE,
+ 0, &port->port_usb.serial_state,
+ sizeof(port->port_usb.serial_state));
+ spin_lock_irqsave(&port->port_lock, flags);
+ } else {
+ port->port_usb.q_again = true;
+ status = 0;
+ }
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ return status;
+}
+
+static void usb_cser_notify_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_cdev *port = req->context;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->port_usb.pending = false;
+ if (req->status != -ESHUTDOWN && port->port_usb.q_again) {
+ port->port_usb.q_again = false;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ port_notify_serial_state(&port->port_usb);
+ spin_lock_irqsave(&port->port_lock, flags);
+ }
+ spin_unlock_irqrestore(&port->port_lock, flags);
+}
+static void dun_cser_connect(struct cserial *cser)
+{
+ cser->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD;
+ port_notify_serial_state(cser);
+}
+
+unsigned int dun_cser_get_dtr(struct cserial *cser)
+{
+ if (cser->port_handshake_bits & ACM_CTRL_DTR)
+ return 1;
+ else
+ return 0;
+}
+
+unsigned int dun_cser_get_rts(struct cserial *cser)
+{
+ if (cser->port_handshake_bits & ACM_CTRL_RTS)
+ return 1;
+ else
+ return 0;
+}
+
+unsigned int dun_cser_send_carrier_detect(struct cserial *cser,
+ unsigned int yes)
+{
+ u16 state;
+
+ state = cser->serial_state;
+ state &= ~ACM_CTRL_DCD;
+ if (yes)
+ state |= ACM_CTRL_DCD;
+
+ cser->serial_state = state;
+ return port_notify_serial_state(cser);
+}
+
+unsigned int dun_cser_send_ring_indicator(struct cserial *cser,
+ unsigned int yes)
+{
+ u16 state;
+
+ state = cser->serial_state;
+ state &= ~ACM_CTRL_RI;
+ if (yes)
+ state |= ACM_CTRL_RI;
+
+ cser->serial_state = state;
+ return port_notify_serial_state(cser);
+}
+
+static void dun_cser_disconnect(struct cserial *cser)
+{
+ cser->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD);
+ port_notify_serial_state(cser);
+}
+
+static int dun_cser_send_break(struct cserial *cser, int duration)
+{
+ u16 state;
+
+ state = cser->serial_state;
+ state &= ~ACM_CTRL_BRK;
+ if (duration)
+ state |= ACM_CTRL_BRK;
+
+ cser->serial_state = state;
+ return port_notify_serial_state(cser);
+}
+
+static int dun_cser_send_ctrl_bits(struct cserial *cser, int ctrl_bits)
+{
+ cser->serial_state = ctrl_bits;
+ return port_notify_serial_state(cser);
+}
+
+static void usb_cser_free_req(struct usb_ep *ep, struct usb_request *req)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ req = NULL;
+ }
+}
+
+static void usb_cser_free_requests(struct usb_ep *ep, struct list_head *head)
+{
+ struct usb_request *req;
+
+ while (!list_empty(head)) {
+ req = list_entry(head->next, struct usb_request, list);
+ list_del_init(&req->list);
+ usb_cser_free_req(ep, req);
+ }
+}
+
+static struct usb_request *
+usb_cser_alloc_req(struct usb_ep *ep, unsigned int len, gfp_t flags)
+{
+ struct usb_request *req;
+
+ req = usb_ep_alloc_request(ep, flags);
+ if (!req) {
+ pr_err("usb alloc request failed\n");
+ return 0;
+ }
+
+ req->length = len;
+ req->buf = kmalloc(len, flags);
+ if (!req->buf) {
+ pr_err("request buf allocation failed\n");
+ usb_ep_free_request(ep, req);
+ return 0;
+ }
+
+ return req;
+}
+
+static int usb_cser_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct f_cdev *port = func_to_port(f);
+ int status;
+ struct usb_ep *ep;
+
+ if (cser_string_defs[0].id == 0) {
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ cser_string_defs[0].id = status;
+ }
+
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ port->port_usb.data_id = status;
+ cser_interface_desc.bInterfaceNumber = status;
+
+ status = -ENODEV;
+ ep = usb_ep_autoconfig(cdev->gadget, &cser_fs_in_desc);
+ if (!ep)
+ goto fail;
+ port->port_usb.in = ep;
+ ep->driver_data = cdev;
+
+ ep = usb_ep_autoconfig(cdev->gadget, &cser_fs_out_desc);
+ if (!ep)
+ goto fail;
+ port->port_usb.out = ep;
+ ep->driver_data = cdev;
+
+ ep = usb_ep_autoconfig(cdev->gadget, &cser_fs_notify_desc);
+ if (!ep)
+ goto fail;
+ port->port_usb.notify = ep;
+ ep->driver_data = cdev;
+ /* allocate notification */
+ port->port_usb.notify_req = usb_cser_alloc_req(ep,
+ sizeof(struct usb_cdc_notification) + 2, GFP_KERNEL);
+ if (!port->port_usb.notify_req)
+ goto fail;
+
+ port->port_usb.notify_req->complete = usb_cser_notify_complete;
+ port->port_usb.notify_req->context = port;
+
+ cser_hs_in_desc.bEndpointAddress = cser_fs_in_desc.bEndpointAddress;
+ cser_hs_out_desc.bEndpointAddress = cser_fs_out_desc.bEndpointAddress;
+
+ cser_ss_in_desc.bEndpointAddress = cser_fs_in_desc.bEndpointAddress;
+ cser_ss_out_desc.bEndpointAddress = cser_fs_out_desc.bEndpointAddress;
+
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ cser_hs_notify_desc.bEndpointAddress =
+ cser_fs_notify_desc.bEndpointAddress;
+ }
+ if (gadget_is_superspeed(c->cdev->gadget)) {
+ cser_ss_notify_desc.bEndpointAddress =
+ cser_fs_notify_desc.bEndpointAddress;
+ }
+
+ status = usb_assign_descriptors(f, cser_fs_function, cser_hs_function,
+ cser_ss_function, NULL);
+ if (status)
+ goto fail;
+
+ dev_dbg(&cdev->gadget->dev, "usb serial port(%d): %s speed IN/%s OUT/%s\n",
+ port->port_num,
+ gadget_is_superspeed(c->cdev->gadget) ? "super" :
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ port->port_usb.in->name, port->port_usb.out->name);
+ return 0;
+
+fail:
+ if (port->port_usb.notify_req)
+ usb_cser_free_req(port->port_usb.notify,
+ port->port_usb.notify_req);
+
+ if (port->port_usb.notify)
+ port->port_usb.notify->driver_data = NULL;
+ if (port->port_usb.out)
+ port->port_usb.out->driver_data = NULL;
+ if (port->port_usb.in)
+ port->port_usb.in->driver_data = NULL;
+
+ pr_err("%s: can't bind, err %d\n", f->name, status);
+ return status;
+}
+
+static void cser_free_inst(struct usb_function_instance *fi)
+{
+ struct f_cdev_opts *opts;
+
+ opts = container_of(fi, struct f_cdev_opts, func_inst);
+
+ device_destroy(fcdev_classp, MKDEV(major, opts->port->minor));
+ cdev_del(&opts->port->fcdev_cdev);
+ usb_cser_chardev_deinit();
+ kfree(opts->func_name);
+ kfree(opts->port);
+ kfree(opts);
+}
+
+static void usb_cser_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct f_cdev *port = func_to_port(f);
+
+ usb_free_all_descriptors(f);
+ usb_cser_free_req(port->port_usb.notify, port->port_usb.notify_req);
+}
+
+static int usb_cser_alloc_requests(struct usb_ep *ep, struct list_head *head,
+ int num, int size,
+ void (*cb)(struct usb_ep *ep, struct usb_request *))
+{
+ int i;
+ struct usb_request *req;
+
+ pr_debug("ep:%p head:%p num:%d size:%d cb:%p",
+ ep, head, num, size, cb);
+
+ for (i = 0; i < num; i++) {
+ req = usb_cser_alloc_req(ep, size, GFP_ATOMIC);
+ if (!req) {
+ pr_debug("req allocated:%d\n", i);
+ return list_empty(head) ? -ENOMEM : 0;
+ }
+ req->complete = cb;
+ list_add_tail(&req->list, head);
+ }
+
+ return 0;
+}
+
+static void usb_cser_start_rx(struct f_cdev *port)
+{
+ struct list_head *pool;
+ struct usb_ep *ep;
+ unsigned long flags;
+ int ret;
+
+ pr_debug("start RX(USB OUT)\n");
+ if (!port) {
+ pr_err("port is null\n");
+ return;
+ }
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!(port->is_connected && port->port_open)) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ pr_debug("can't start rx.\n");
+ return;
+ }
+
+ pool = &port->read_pool;
+ ep = port->port_usb.out;
+
+ while (!list_empty(pool)) {
+ struct usb_request *req;
+
+ req = list_entry(pool->next, struct usb_request, list);
+ list_del_init(&req->list);
+ req->length = BRIDGE_RX_BUF_SIZE;
+ req->complete = usb_cser_read_complete;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ ret = usb_ep_queue(ep, req, GFP_KERNEL);
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (ret) {
+ pr_err("port(%d):%p usb ep(%s) queue failed\n",
+ port->port_num, port, ep->name);
+ list_add(&req->list, pool);
+ break;
+ }
+ }
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static void usb_cser_read_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_cdev *port = ep->driver_data;
+ unsigned long flags;
+
+ pr_debug("ep:(%p)(%s) port:%p req_status:%d req->actual:%u\n",
+ ep, ep->name, port, req->status, req->actual);
+ if (!port) {
+ pr_err("port is null\n");
+ return;
+ }
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->port_open || req->status || !req->actual) {
+ list_add_tail(&req->list, &port->read_pool);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ return;
+ }
+
+ port->nbytes_from_host += req->actual;
+ list_add_tail(&req->list, &port->read_queued);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ wake_up(&port->read_wq);
+}
+
+static void usb_cser_write_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ unsigned long flags;
+ struct f_cdev *port = ep->driver_data;
+
+ pr_debug("ep:(%p)(%s) port:%p req_stats:%d\n",
+ ep, ep->name, port, req->status);
+
+ if (!port) {
+ pr_err("port is null\n");
+ return;
+ }
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->nbytes_to_host += req->actual;
+ list_add_tail(&req->list, &port->write_pool);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ switch (req->status) {
+ default:
+ pr_debug("unexpected %s status %d\n", ep->name, req->status);
+ /* FALL THROUGH */
+ case 0:
+ /* normal completion */
+ break;
+
+ case -ESHUTDOWN:
+ /* disconnect */
+ pr_debug("%s shutdown\n", ep->name);
+ break;
+ }
+}
+
+static void usb_cser_start_io(struct f_cdev *port)
+{
+ int ret = -ENODEV;
+ unsigned long flags;
+
+ pr_debug("port: %p\n", port);
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->is_connected)
+ goto start_io_out;
+
+ port->current_rx_req = NULL;
+ port->pending_rx_bytes = 0;
+ port->current_rx_buf = NULL;
+
+ ret = usb_cser_alloc_requests(port->port_usb.out,
+ &port->read_pool,
+ BRIDGE_RX_QUEUE_SIZE, BRIDGE_RX_BUF_SIZE,
+ usb_cser_read_complete);
+ if (ret) {
+ pr_err("unable to allocate out requests\n");
+ goto start_io_out;
+ }
+
+ ret = usb_cser_alloc_requests(port->port_usb.in,
+ &port->write_pool,
+ BRIDGE_TX_QUEUE_SIZE, BRIDGE_TX_BUF_SIZE,
+ usb_cser_write_complete);
+ if (ret) {
+ usb_cser_free_requests(port->port_usb.out, &port->read_pool);
+ pr_err("unable to allocate IN requests\n");
+ goto start_io_out;
+ }
+
+start_io_out:
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ if (ret)
+ return;
+
+ usb_cser_start_rx(port);
+}
+
+static void usb_cser_stop_io(struct f_cdev *port)
+{
+ struct usb_ep *in;
+ struct usb_ep *out;
+ unsigned long flags;
+
+ pr_debug("port:%p\n", port);
+
+ in = port->port_usb.in;
+ out = port->port_usb.out;
+
+ /* disable endpoints, aborting down any active I/O */
+ usb_ep_disable(out);
+ out->driver_data = NULL;
+ usb_ep_disable(in);
+ in->driver_data = NULL;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (port->current_rx_req != NULL) {
+ kfree(port->current_rx_req->buf);
+ usb_ep_free_request(out, port->current_rx_req);
+ }
+
+ port->pending_rx_bytes = 0;
+ port->current_rx_buf = NULL;
+ usb_cser_free_requests(out, &port->read_queued);
+ usb_cser_free_requests(out, &port->read_pool);
+ usb_cser_free_requests(in, &port->write_pool);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+int f_cdev_open(struct inode *inode, struct file *file)
+{
+ int ret;
+ unsigned long flags;
+ struct f_cdev *port;
+
+ port = container_of(inode->i_cdev, struct f_cdev, fcdev_cdev);
+ if (!port) {
+ pr_err("Port is NULL.\n");
+ return -EINVAL;
+ }
+
+ if (port && port->port_open) {
+ pr_err("port is already opened.\n");
+ return -EBUSY;
+ }
+
+ file->private_data = port;
+ pr_debug("opening port(%s)(%p)\n", port->name, port);
+ ret = wait_event_interruptible(port->open_wq,
+ port->is_connected);
+ if (ret) {
+ pr_debug("open interrupted.\n");
+ return ret;
+ }
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->port_open = true;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ usb_cser_start_rx(port);
+
+ pr_debug("port(%s)(%p) open is success\n", port->name, port);
+
+ return 0;
+}
+
+int f_cdev_release(struct inode *inode, struct file *file)
+{
+ unsigned long flags;
+ struct f_cdev *port;
+
+ port = file->private_data;
+ if (!port) {
+ pr_err("port is NULL.\n");
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->port_open = false;
+ port->cbits_updated = false;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ pr_debug("port(%s)(%p) is closed.\n", port->name, port);
+
+ return 0;
+}
+
+ssize_t f_cdev_read(struct file *file,
+ char __user *buf,
+ size_t count,
+ loff_t *ppos)
+{
+ unsigned long flags;
+ struct f_cdev *port;
+ struct usb_request *req;
+ struct list_head *pool;
+ struct usb_request *current_rx_req;
+ size_t pending_rx_bytes, bytes_copied = 0, size;
+ u8 *current_rx_buf;
+
+ port = file->private_data;
+ if (!port) {
+ pr_err("port is NULL.\n");
+ return -EINVAL;
+ }
+
+ pr_debug("read on port(%s)(%p) count:%zu\n", port->name, port, count);
+ spin_lock_irqsave(&port->port_lock, flags);
+ current_rx_req = port->current_rx_req;
+ pending_rx_bytes = port->pending_rx_bytes;
+ current_rx_buf = port->current_rx_buf;
+ port->current_rx_req = NULL;
+ port->current_rx_buf = NULL;
+ port->pending_rx_bytes = 0;
+ bytes_copied = 0;
+
+ if (list_empty(&port->read_queued) && !pending_rx_bytes) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ pr_debug("%s(): read_queued list is empty.\n", __func__);
+ goto start_rx;
+ }
+
+ /*
+ * Consider below cases:
+ * 1. If available read buffer size (i.e. count value) is greater than
+ * available data as part of one USB OUT request buffer, then consider
+ * copying multiple USB OUT request buffers until read buffer is filled.
+ * 2. If available read buffer size (i.e. count value) is smaller than
+ * available data as part of one USB OUT request buffer, then copy this
+ * buffer data across multiple read() call until whole USB OUT request
+ * buffer is copied.
+ */
+ while ((pending_rx_bytes || !list_empty(&port->read_queued)) && count) {
+ if (pending_rx_bytes == 0) {
+ pool = &port->read_queued;
+ req = list_first_entry(pool, struct usb_request, list);
+ list_del_init(&req->list);
+ current_rx_req = req;
+ pending_rx_bytes = req->actual;
+ current_rx_buf = req->buf;
+ }
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ size = count;
+ if (size > pending_rx_bytes)
+ size = pending_rx_bytes;
+
+ pr_debug("pending_rx_bytes:%zu count:%zu size:%zu\n",
+ pending_rx_bytes, count, size);
+ size -= copy_to_user(buf, current_rx_buf, size);
+ port->nbytes_to_port_bridge += size;
+ bytes_copied += size;
+ count -= size;
+ buf += size;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->is_connected) {
+ list_add_tail(¤t_rx_req->list, &port->read_pool);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ return -EAGAIN;
+ }
+
+ /*
+ * partial data available, then update pending_rx_bytes,
+ * otherwise add USB request back to read_pool for next data.
+ */
+ if (size < pending_rx_bytes) {
+ pending_rx_bytes -= size;
+ current_rx_buf += size;
+ } else {
+ list_add_tail(¤t_rx_req->list, &port->read_pool);
+ pending_rx_bytes = 0;
+ current_rx_req = NULL;
+ current_rx_buf = NULL;
+ }
+ }
+
+ port->pending_rx_bytes = pending_rx_bytes;
+ port->current_rx_buf = current_rx_buf;
+ port->current_rx_req = current_rx_req;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+start_rx:
+ usb_cser_start_rx(port);
+ return bytes_copied;
+}
+
+ssize_t f_cdev_write(struct file *file,
+ const char __user *buf,
+ size_t count,
+ loff_t *ppos)
+{
+ int ret;
+ unsigned long flags;
+ struct f_cdev *port;
+ struct usb_request *req;
+ struct list_head *pool;
+ unsigned int xfer_size;
+ struct usb_ep *in;
+
+ port = file->private_data;
+ if (!port) {
+ pr_err("port is NULL.\n");
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ pr_debug("write on port(%s)(%p)\n", port->name, port);
+
+ if (!port->is_connected) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ pr_err("%s: cable is disconnected.\n", __func__);
+ return -ENODEV;
+ }
+
+ if (list_empty(&port->write_pool)) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ pr_debug("%s: Request list is empty.\n", __func__);
+ return 0;
+ }
+
+ in = port->port_usb.in;
+ pool = &port->write_pool;
+ req = list_first_entry(pool, struct usb_request, list);
+ list_del_init(&req->list);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ pr_debug("%s: write buf size:%zu\n", __func__, count);
+ if (count > BRIDGE_TX_BUF_SIZE)
+ xfer_size = BRIDGE_TX_BUF_SIZE;
+ else
+ xfer_size = count;
+
+ ret = copy_from_user(req->buf, buf, xfer_size);
+ if (ret) {
+ pr_err("copy_from_user failed: err %d\n", ret);
+ ret = -EFAULT;
+ } else {
+ req->length = xfer_size;
+ ret = usb_ep_queue(in, req, GFP_KERNEL);
+ if (ret) {
+ pr_err("EP QUEUE failed:%d\n", ret);
+ ret = -EIO;
+ goto err_exit;
+ }
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->nbytes_from_port_bridge += req->length;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ }
+
+err_exit:
+ if (ret) {
+ spin_lock_irqsave(&port->port_lock, flags);
+ /* USB cable is connected, add it back otherwise free request */
+ if (port->is_connected)
+ list_add(&req->list, &port->write_pool);
+ else
+ usb_cser_free_req(in, req);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ return ret;
+ }
+
+ return xfer_size;
+}
+
+static unsigned int f_cdev_poll(struct file *file, poll_table *wait)
+{
+ unsigned int mask = 0;
+ struct f_cdev *port;
+ unsigned long flags;
+
+ port = file->private_data;
+ if (port && port->is_connected) {
+ poll_wait(file, &port->read_wq, wait);
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!list_empty(&port->read_queued)) {
+ mask |= POLLIN | POLLRDNORM;
+ pr_debug("sets POLLIN for %s\n", port->name);
+ }
+
+ if (port->cbits_updated) {
+ mask |= POLLPRI;
+ pr_debug("sets POLLPRI for %s\n", port->name);
+ }
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ } else {
+ pr_err("Failed due to NULL device or disconnected.\n");
+ mask = POLLERR;
+ }
+
+ return mask;
+}
+
+static int f_cdev_tiocmget(struct f_cdev *port)
+{
+ struct cserial *cser;
+ unsigned int result = 0;
+
+ if (!port) {
+ pr_err("port is NULL.\n");
+ return -ENODEV;
+ }
+
+ cser = &port->port_usb;
+ if (cser->get_dtr)
+ result |= (cser->get_dtr(cser) ? TIOCM_DTR : 0);
+
+ if (cser->get_rts)
+ result |= (cser->get_rts(cser) ? TIOCM_RTS : 0);
+
+ if (cser->serial_state & TIOCM_CD)
+ result |= TIOCM_CD;
+
+ if (cser->serial_state & TIOCM_RI)
+ result |= TIOCM_RI;
+ return result;
+}
+
+static int f_cdev_tiocmset(struct f_cdev *port,
+ unsigned int set, unsigned int clear)
+{
+ struct cserial *cser;
+ int status = 0;
+
+ if (!port) {
+ pr_err("port is NULL.\n");
+ return -ENODEV;
+ }
+
+ cser = &port->port_usb;
+ if (set & TIOCM_RI) {
+ if (cser->send_ring_indicator) {
+ cser->serial_state |= TIOCM_RI;
+ status = cser->send_ring_indicator(cser, 1);
+ }
+ }
+ if (clear & TIOCM_RI) {
+ if (cser->send_ring_indicator) {
+ cser->serial_state &= ~TIOCM_RI;
+ status = cser->send_ring_indicator(cser, 0);
+ }
+ }
+ if (set & TIOCM_CD) {
+ if (cser->send_carrier_detect) {
+ cser->serial_state |= TIOCM_CD;
+ status = cser->send_carrier_detect(cser, 1);
+ }
+ }
+ if (clear & TIOCM_CD) {
+ if (cser->send_carrier_detect) {
+ cser->serial_state &= ~TIOCM_CD;
+ status = cser->send_carrier_detect(cser, 0);
+ }
+ }
+
+ return status;
+}
+
+static long f_cdev_ioctl(struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ long ret = 0;
+ int i = 0;
+ uint32_t val;
+ struct f_cdev *port;
+
+ port = fp->private_data;
+ if (!port) {
+ pr_err("port is null.\n");
+ return POLLERR;
+ }
+
+ switch (cmd) {
+ case TIOCMBIC:
+ case TIOCMBIS:
+ case TIOCMSET:
+ pr_debug("TIOCMSET on port(%s)%p\n", port->name, port);
+ i = get_user(val, (uint32_t *)arg);
+ if (i) {
+ pr_err("Error getting TIOCMSET value\n");
+ return i;
+ }
+ ret = f_cdev_tiocmset(port, val, ~val);
+ break;
+ case TIOCMGET:
+ pr_debug("TIOCMGET on port(%s)%p\n", port->name, port);
+ ret = f_cdev_tiocmget(port);
+ if (ret >= 0) {
+ ret = put_user(ret, (uint32_t *)arg);
+ port->cbits_updated = false;
+ }
+ break;
+ default:
+ pr_err("Received cmd:%d not supported\n", cmd);
+ ret = -ENOIOCTLCMD;
+ break;
+ }
+
+ return ret;
+}
+
+static void usb_cser_notify_modem(void *fport, int ctrl_bits)
+{
+ int temp;
+ struct f_cdev *port = fport;
+
+ if (!port) {
+ pr_err("port is null\n");
+ return;
+ }
+
+ pr_debug("port(%s): ctrl_bits:%x\n", port->name, ctrl_bits);
+
+ temp = convert_acm_sigs_to_uart(ctrl_bits);
+
+ if (temp == port->cbits_to_modem)
+ return;
+
+ port->cbits_to_modem = temp;
+ port->cbits_updated = true;
+
+ wake_up(&port->read_wq);
+}
+
+int usb_cser_connect(struct f_cdev *port)
+{
+ unsigned long flags;
+ int ret;
+ struct cserial *cser;
+
+ if (!port) {
+ pr_err("port is NULL.\n");
+ return -ENODEV;
+ }
+
+ pr_debug("port(%s) (%p)\n", port->name, port);
+
+ cser = &port->port_usb;
+ cser->notify_modem = usb_cser_notify_modem;
+
+ ret = usb_ep_enable(cser->in);
+ if (ret) {
+ pr_err("usb_ep_enable failed eptype:IN ep:%p, err:%d",
+ cser->in, ret);
+ return ret;
+ }
+ cser->in->driver_data = port;
+
+ ret = usb_ep_enable(cser->out);
+ if (ret) {
+ pr_err("usb_ep_enable failed eptype:OUT ep:%p, err: %d",
+ cser->out, ret);
+ cser->in->driver_data = 0;
+ return ret;
+ }
+ cser->out->driver_data = port;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ cser->pending = false;
+ cser->q_again = false;
+ port->is_connected = true;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ usb_cser_start_io(port);
+ wake_up(&port->open_wq);
+ return 0;
+}
+
+void usb_cser_disconnect(struct f_cdev *port)
+{
+ unsigned long flags;
+
+ usb_cser_stop_io(port);
+
+ /* lower DTR to modem */
+ usb_cser_notify_modem(port, 0);
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->is_connected = false;
+ port->nbytes_from_host = port->nbytes_to_host = 0;
+ port->nbytes_to_port_bridge = 0;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static const struct file_operations f_cdev_fops = {
+ .owner = THIS_MODULE,
+ .open = f_cdev_open,
+ .release = f_cdev_release,
+ .read = f_cdev_read,
+ .write = f_cdev_write,
+ .poll = f_cdev_poll,
+ .unlocked_ioctl = f_cdev_ioctl,
+ .compat_ioctl = f_cdev_ioctl,
+};
+
+static struct f_cdev *f_cdev_alloc(char *func_name, int portno)
+{
+ int ret;
+ dev_t dev;
+ struct device *device;
+ struct f_cdev *port;
+
+ port = kzalloc(sizeof(struct f_cdev), GFP_KERNEL);
+ if (!port) {
+ ret = -ENOMEM;
+ return ERR_PTR(ret);
+ }
+
+ mutex_lock(&chardev_ida_lock);
+ if (idr_is_empty(&chardev_ida.idr)) {
+ ret = usb_cser_alloc_chardev_region();
+ if (ret) {
+ mutex_unlock(&chardev_ida_lock);
+ pr_err("alloc chardev failed\n");
+ goto err_alloc_chardev;
+ }
+ }
+
+ ret = ida_simple_get(&chardev_ida, 0, 0, GFP_KERNEL);
+ if (ret >= NUM_INSTANCE) {
+ ida_simple_remove(&chardev_ida, ret);
+ mutex_unlock(&chardev_ida_lock);
+ ret = -ENODEV;
+ goto err_get_ida;
+ }
+
+ port->port_num = portno;
+ port->minor = ret;
+ mutex_unlock(&chardev_ida_lock);
+
+ snprintf(port->name, sizeof(port->name), "%s%d", DEVICE_NAME, portno);
+ spin_lock_init(&port->port_lock);
+
+ init_waitqueue_head(&port->open_wq);
+ init_waitqueue_head(&port->read_wq);
+ INIT_LIST_HEAD(&port->read_pool);
+ INIT_LIST_HEAD(&port->read_queued);
+ INIT_LIST_HEAD(&port->write_pool);
+
+ port->fcdev_wq = create_singlethread_workqueue(port->name);
+ if (!port->fcdev_wq) {
+ pr_err("Unable to create workqueue fcdev_wq for port:%s\n",
+ port->name);
+ ret = -ENOMEM;
+ goto err_get_ida;
+ }
+
+ /* create char device */
+ cdev_init(&port->fcdev_cdev, &f_cdev_fops);
+ dev = MKDEV(major, port->minor);
+ ret = cdev_add(&port->fcdev_cdev, dev, 1);
+ if (ret) {
+ pr_err("Failed to add cdev for port(%s)\n", port->name);
+ goto err_cdev_add;
+ }
+
+ device = device_create(fcdev_classp, NULL, dev, NULL, port->name);
+ if (IS_ERR(device)) {
+ ret = PTR_ERR(device);
+ goto err_create_dev;
+ }
+
+ pr_info("port_name:%s (%p) portno:(%d)\n",
+ port->name, port, port->port_num);
+ return port;
+
+err_create_dev:
+ cdev_del(&port->fcdev_cdev);
+err_cdev_add:
+ destroy_workqueue(port->fcdev_wq);
+err_get_ida:
+ usb_cser_chardev_deinit();
+err_alloc_chardev:
+ kfree(port);
+
+ return ERR_PTR(ret);
+}
+
+static void usb_cser_chardev_deinit(void)
+{
+
+ if (idr_is_empty(&chardev_ida.idr)) {
+
+ if (major) {
+ unregister_chrdev_region(MKDEV(major, 0), minors);
+ major = minors = 0;
+ }
+
+ if (!IS_ERR_OR_NULL(fcdev_classp))
+ class_destroy(fcdev_classp);
+ }
+}
+
+static int usb_cser_alloc_chardev_region(void)
+{
+ int ret;
+ dev_t dev;
+
+ ret = alloc_chrdev_region(&dev,
+ 0,
+ NUM_INSTANCE,
+ MODULE_NAME);
+ if (ret) {
+ pr_err("alloc_chrdev_region() failed ret:%i\n", ret);
+ return ret;
+ }
+
+ major = MAJOR(dev);
+ minors = NUM_INSTANCE;
+
+ fcdev_classp = class_create(THIS_MODULE, MODULE_NAME);
+ if (IS_ERR(fcdev_classp)) {
+ pr_err("class_create() failed ENOMEM\n");
+ ret = -ENOMEM;
+ }
+
+ return 0;
+}
+
+static inline struct f_cdev_opts *to_f_cdev_opts(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct f_cdev_opts,
+ func_inst.group);
+}
+
+static struct f_cdev_opts *to_fi_cdev_opts(struct usb_function_instance *fi)
+{
+ return container_of(fi, struct f_cdev_opts, func_inst);
+}
+
+static void cserial_attr_release(struct config_item *item)
+{
+ struct f_cdev_opts *opts = to_f_cdev_opts(item);
+
+ usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations cserial_item_ops = {
+ .release = cserial_attr_release,
+};
+
+static ssize_t usb_cser_status_show(struct config_item *item, char *page)
+{
+ struct f_cdev *port = to_f_cdev_opts(item)->port;
+ char *buf;
+ unsigned long flags;
+ int temp = 0;
+ int ret;
+
+ buf = kzalloc(sizeof(char) * 512, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ temp += scnprintf(buf + temp, 512 - temp,
+ "###PORT:%s###\n"
+ "port_no:%d\n"
+ "func:%s\n"
+ "nbytes_to_host: %lu\n"
+ "nbytes_from_host: %lu\n"
+ "nbytes_to_port_bridge: %lu\n"
+ "nbytes_from_port_bridge: %lu\n"
+ "cbits_to_modem: %u\n"
+ "Port Opened: %s\n",
+ port->name,
+ port->port_num,
+ to_f_cdev_opts(item)->func_name,
+ port->nbytes_to_host,
+ port->nbytes_from_host,
+ port->nbytes_to_port_bridge,
+ port->nbytes_from_port_bridge,
+ port->cbits_to_modem,
+ (port->port_open ? "Opened" : "Closed"));
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ ret = scnprintf(page, temp, buf);
+ kfree(buf);
+
+ return ret;
+}
+
+static ssize_t usb_cser_status_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct f_cdev *port = to_f_cdev_opts(item)->port;
+ unsigned long flags;
+ u8 stats;
+
+ if (page == NULL) {
+ pr_err("Invalid buffer");
+ return len;
+ }
+
+ if (kstrtou8(page, 0, &stats) != 0 || stats != 0) {
+ pr_err("(%u)Wrong value. enter 0 to clear.\n", stats);
+ return len;
+ }
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->nbytes_to_host = port->nbytes_from_host = 0;
+ port->nbytes_to_port_bridge = port->nbytes_from_port_bridge = 0;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ return len;
+}
+
+CONFIGFS_ATTR(usb_cser_, status);
+static struct configfs_attribute *cserial_attrs[] = {
+ &usb_cser_attr_status,
+ NULL,
+};
+
+static struct config_item_type cserial_func_type = {
+ .ct_item_ops = &cserial_item_ops,
+ .ct_attrs = cserial_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static int cser_set_inst_name(struct usb_function_instance *f, const char *name)
+{
+ struct f_cdev_opts *opts =
+ container_of(f, struct f_cdev_opts, func_inst);
+ char *ptr, *str;
+ size_t name_len, str_size;
+ int ret;
+ struct f_cdev *port;
+
+ name_len = strlen(name) + 1;
+ if (name_len > MAX_CDEV_INST_NAME)
+ return -ENAMETOOLONG;
+
+ /* expect name as cdev.<func>.<port_num> */
+ str = strnchr(name, strlen(name), '.');
+ if (!str) {
+ pr_err("invalid input (%s)\n", name);
+ return -EINVAL;
+ }
+
+ /* get function name */
+ str_size = name_len - strlen(str);
+ if (str_size > MAX_CDEV_FUNC_NAME)
+ return -ENAMETOOLONG;
+
+ ptr = kstrndup(name, str_size - 1, GFP_KERNEL);
+ if (!ptr) {
+ pr_err("error:%ld\n", PTR_ERR(ptr));
+ return -ENOMEM;
+ }
+
+ opts->func_name = ptr;
+
+ /* get port number */
+ str = strrchr(name, '.');
+ if (!str) {
+ pr_err("err: port number not found\n");
+ return -EINVAL;
+ }
+ pr_debug("str:%s\n", str);
+
+ *str = '\0';
+ str++;
+
+ ret = kstrtou8(str, 0, &opts->port_num);
+ if (ret) {
+ pr_err("erro: not able to get port number\n");
+ return -EINVAL;
+ }
+
+ pr_debug("gser: port_num:%d func_name:%s\n",
+ opts->port_num, opts->func_name);
+
+ port = f_cdev_alloc(opts->func_name, opts->port_num);
+ if (IS_ERR(port)) {
+ pr_err("Failed to create cdev port(%d)\n", opts->port_num);
+ return -ENOMEM;
+ }
+
+ opts->port = port;
+
+ /* For DUN functionality only sets control signal handling */
+ if (!strcmp(opts->func_name, "dun")) {
+ port->port_usb.connect = dun_cser_connect;
+ port->port_usb.get_dtr = dun_cser_get_dtr;
+ port->port_usb.get_rts = dun_cser_get_rts;
+ port->port_usb.send_carrier_detect =
+ dun_cser_send_carrier_detect;
+ port->port_usb.send_ring_indicator =
+ dun_cser_send_ring_indicator;
+ port->port_usb.send_modem_ctrl_bits = dun_cser_send_ctrl_bits;
+ port->port_usb.disconnect = dun_cser_disconnect;
+ port->port_usb.send_break = dun_cser_send_break;
+ }
+
+ return 0;
+}
+
+static struct usb_function_instance *cser_alloc_inst(void)
+{
+ struct f_cdev_opts *opts;
+
+ opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+ if (!opts)
+ return ERR_PTR(-ENOMEM);
+
+ opts->func_inst.free_func_inst = cser_free_inst;
+ opts->func_inst.set_inst_name = cser_set_inst_name;
+
+ config_group_init_type_name(&opts->func_inst.group, "",
+ &cserial_func_type);
+ return &opts->func_inst;
+}
+
+static struct usb_function *cser_alloc(struct usb_function_instance *fi)
+{
+ struct f_cdev_opts *opts = to_fi_cdev_opts(fi);
+ struct f_cdev *port = opts->port;
+
+ port->port_usb.func.name = "cser";
+ port->port_usb.func.strings = usb_cser_strings;
+ port->port_usb.func.bind = usb_cser_bind;
+ port->port_usb.func.unbind = usb_cser_unbind;
+ port->port_usb.func.set_alt = usb_cser_set_alt;
+ port->port_usb.func.disable = usb_cser_disable;
+ port->port_usb.func.setup = usb_cser_setup;
+ port->port_usb.func.free_func = usb_cser_free_func;
+
+ return &port->port_usb.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(cser, cser_alloc_inst, cser_alloc);
+MODULE_DESCRIPTION("USB Serial Character Driver");
+MODULE_LICENSE("GPL v2");