2180 lines
54 KiB
C
2180 lines
54 KiB
C
|
|
/*
|
||
|
|
* Copyright (c) 2011, 2013-2021, 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/debugfs.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 4
|
||
|
|
|
||
|
|
#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;
|
||
|
|
struct list_head write_pending;
|
||
|
|
|
||
|
|
/* 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;
|
||
|
|
bool is_suspended;
|
||
|
|
bool pending_state_notify;
|
||
|
|
|
||
|
|
unsigned long nbytes_from_host;
|
||
|
|
unsigned long nbytes_to_host;
|
||
|
|
unsigned long nbytes_to_port_bridge;
|
||
|
|
unsigned long nbytes_from_port_bridge;
|
||
|
|
|
||
|
|
struct dentry *debugfs_root;
|
||
|
|
|
||
|
|
/* To test remote wakeup using debugfs */
|
||
|
|
u8 debugfs_rw_enable;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct f_cdev_opts {
|
||
|
|
struct usb_function_instance func_inst;
|
||
|
|
struct f_cdev *port;
|
||
|
|
char *func_name;
|
||
|
|
u8 port_num;
|
||
|
|
u8 proto;
|
||
|
|
};
|
||
|
|
|
||
|
|
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 void usb_cser_debugfs_exit(struct f_cdev *port);
|
||
|
|
|
||
|
|
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 = USB_SUBCLASS_VENDOR_SPEC,
|
||
|
|
/* .bInterfaceProtocol = DYNAMIC */
|
||
|
|
/* .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_uart_sigs_to_acm(unsigned int uart_sig)
|
||
|
|
{
|
||
|
|
unsigned int acm_sig = 0;
|
||
|
|
|
||
|
|
/* should this needs to be in calling functions ??? */
|
||
|
|
uart_sig &= (TIOCM_RI | TIOCM_CD | TIOCM_DSR);
|
||
|
|
|
||
|
|
if (uart_sig & TIOCM_RI)
|
||
|
|
acm_sig |= ACM_CTRL_RI;
|
||
|
|
if (uart_sig & TIOCM_CD)
|
||
|
|
acm_sig |= ACM_CTRL_DCD;
|
||
|
|
if (uart_sig & TIOCM_DSR)
|
||
|
|
acm_sig |= ACM_CTRL_DSR;
|
||
|
|
|
||
|
|
return acm_sig;
|
||
|
|
}
|
||
|
|
|
||
|
|
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 int port_notify_serial_state(struct cserial *cser);
|
||
|
|
|
||
|
|
static void usb_cser_resume(struct usb_function *f)
|
||
|
|
{
|
||
|
|
struct f_cdev *port = func_to_port(f);
|
||
|
|
unsigned long flags;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
struct usb_request *req, *t;
|
||
|
|
struct usb_ep *in;
|
||
|
|
|
||
|
|
pr_debug("%s\n", __func__);
|
||
|
|
port->is_suspended = false;
|
||
|
|
|
||
|
|
/* process pending state notifications */
|
||
|
|
if (port->pending_state_notify)
|
||
|
|
port_notify_serial_state(&port->port_usb);
|
||
|
|
|
||
|
|
spin_lock_irqsave(&port->port_lock, flags);
|
||
|
|
in = port->port_usb.in;
|
||
|
|
/* process any pending requests */
|
||
|
|
list_for_each_entry_safe(req, t, &port->write_pending, list) {
|
||
|
|
list_del_init(&req->list);
|
||
|
|
spin_unlock_irqrestore(&port->port_lock, flags);
|
||
|
|
|
||
|
|
ret = usb_ep_queue(in, req, GFP_KERNEL);
|
||
|
|
spin_lock_irqsave(&port->port_lock, flags);
|
||
|
|
if (ret) {
|
||
|
|
pr_err("EP QUEUE failed:%d\n", ret);
|
||
|
|
list_add(&req->list, &port->write_pool);
|
||
|
|
} else {
|
||
|
|
port->nbytes_from_port_bridge += req->length;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
spin_unlock_irqrestore(&port->port_lock, flags);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void usb_cser_suspend(struct usb_function *f)
|
||
|
|
{
|
||
|
|
struct f_cdev *port = func_to_port(f);
|
||
|
|
|
||
|
|
pr_debug("%s\n", __func__);
|
||
|
|
port->is_suspended = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int usb_cser_func_suspend(struct usb_function *f, u8 options)
|
||
|
|
{
|
||
|
|
bool func_wakeup_allowed;
|
||
|
|
|
||
|
|
func_wakeup_allowed =
|
||
|
|
((options & FUNC_SUSPEND_OPT_RW_EN_MASK) != 0);
|
||
|
|
|
||
|
|
f->func_wakeup_allowed = func_wakeup_allowed;
|
||
|
|
if (options & FUNC_SUSPEND_OPT_SUSP_MASK) {
|
||
|
|
if (!f->func_is_suspended) {
|
||
|
|
usb_cser_suspend(f);
|
||
|
|
f->func_is_suspended = true;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (f->func_is_suspended) {
|
||
|
|
f->func_is_suspended = false;
|
||
|
|
usb_cser_resume(f);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int usb_cser_get_remote_wakeup_capable(struct usb_function *f,
|
||
|
|
struct usb_gadget *g)
|
||
|
|
{
|
||
|
|
|
||
|
|
return ((g->speed >= USB_SPEED_SUPER && f->func_wakeup_allowed) ||
|
||
|
|
(g->speed < USB_SPEED_SUPER && g->remote_wakeup));
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
static int usb_cser_get_status(struct usb_function *f)
|
||
|
|
{
|
||
|
|
bool remote_wakeup_en_status = f->func_wakeup_allowed ? 1 : 0;
|
||
|
|
|
||
|
|
return (remote_wakeup_en_status << FUNC_WAKEUP_ENABLE_SHIFT) |
|
||
|
|
(1 << FUNC_WAKEUP_CAPABLE_SHIFT);
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
|
||
|
|
|
||
|
|
if (port->is_suspended) {
|
||
|
|
port->pending_state_notify = true;
|
||
|
|
pr_debug("%s: port is suspended\n", __func__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
spin_lock_irqsave(&port->port_lock, flags);
|
||
|
|
if (port->pending_state_notify)
|
||
|
|
port->pending_state_notify = false;
|
||
|
|
|
||
|
|
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;
|
||
|
|
struct f_cdev_opts *opts =
|
||
|
|
container_of(f->fi, struct f_cdev_opts, func_inst);
|
||
|
|
|
||
|
|
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;
|
||
|
|
cser_interface_desc.bInterfaceProtocol = opts->proto;
|
||
|
|
|
||
|
|
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, cser_ss_function);
|
||
|
|
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);
|
||
|
|
|
||
|
|
if (opts->port) {
|
||
|
|
cdev_device_del(&opts->port->fcdev_cdev, &opts->port->dev);
|
||
|
|
mutex_lock(&chardev_ida_lock);
|
||
|
|
ida_simple_remove(&chardev_ida, opts->port->minor);
|
||
|
|
mutex_unlock(&chardev_ida_lock);
|
||
|
|
usb_cser_debugfs_exit(opts->port);
|
||
|
|
put_device(&opts->port->dev);
|
||
|
|
}
|
||
|
|
|
||
|
|
usb_cser_chardev_deinit();
|
||
|
|
kfree(opts->func_name);
|
||
|
|
kfree(opts);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void usb_cser_unbind(struct usb_configuration *c, struct usb_function *f)
|
||
|
|
{
|
||
|
|
struct f_cdev *port = func_to_port(f);
|
||
|
|
|
||
|
|
/* Reset string id */
|
||
|
|
cser_string_defs[0].id = 0;
|
||
|
|
|
||
|
|
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:%pK 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):%pK 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:(%pK)(%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:(%pK)(%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: %pK\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:%pK\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);
|
||
|
|
usb_cser_free_requests(in, &port->write_pending);
|
||
|
|
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);
|
||
|
|
get_device(&port->dev);
|
||
|
|
if (port->port_open) {
|
||
|
|
pr_err("port is already opened.\n");
|
||
|
|
put_device(&port->dev);
|
||
|
|
return -EBUSY;
|
||
|
|
}
|
||
|
|
|
||
|
|
file->private_data = port;
|
||
|
|
pr_debug("opening port(%s)(%pK)\n", port->name, port);
|
||
|
|
ret = wait_event_interruptible(port->open_wq,
|
||
|
|
port->is_connected);
|
||
|
|
if (ret) {
|
||
|
|
pr_debug("open interrupted.\n");
|
||
|
|
put_device(&port->dev);
|
||
|
|
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)(%pK) 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;
|
||
|
|
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)(%pK) is closed.\n", port->name, port);
|
||
|
|
put_device(&port->dev);
|
||
|
|
|
||
|
|
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)(%pK) 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;
|
||
|
|
struct cserial *cser;
|
||
|
|
struct usb_function *func;
|
||
|
|
struct usb_gadget *gadget;
|
||
|
|
|
||
|
|
port = file->private_data;
|
||
|
|
if (!port) {
|
||
|
|
pr_err("port is NULL.\n");
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
cser = &port->port_usb;
|
||
|
|
func = &cser->func;
|
||
|
|
|
||
|
|
spin_lock_irqsave(&port->port_lock, flags);
|
||
|
|
pr_debug("write on port(%s)(%pK)\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;
|
||
|
|
goto err_exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
req->length = xfer_size;
|
||
|
|
req->zero = 1;
|
||
|
|
if (port->is_suspended) {
|
||
|
|
gadget = cser->func.config->cdev->gadget;
|
||
|
|
if (!usb_cser_get_remote_wakeup_capable(func, gadget)) {
|
||
|
|
pr_debug("%s remote-wakeup not capable\n",
|
||
|
|
__func__);
|
||
|
|
ret = -EOPNOTSUPP;
|
||
|
|
goto err_exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
spin_lock_irqsave(&port->port_lock, flags);
|
||
|
|
list_add(&req->list, &port->write_pending);
|
||
|
|
spin_unlock_irqrestore(&port->port_lock, flags);
|
||
|
|
|
||
|
|
if (gadget->speed >= USB_SPEED_SUPER
|
||
|
|
&& func->func_is_suspended)
|
||
|
|
ret = usb_func_wakeup(func);
|
||
|
|
else
|
||
|
|
ret = usb_gadget_wakeup(gadget);
|
||
|
|
|
||
|
|
if (ret < 0 && ret != -EACCES) {
|
||
|
|
pr_err("Remote wakeup failed:%d\n", ret);
|
||
|
|
spin_lock_irqsave(&port->port_lock, flags);
|
||
|
|
req = list_first_entry(&port->write_pending,
|
||
|
|
struct usb_request, list);
|
||
|
|
list_del(&req->list);
|
||
|
|
spin_unlock_irqrestore(&port->port_lock, flags);
|
||
|
|
goto err_exit;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
return xfer_size;
|
||
|
|
|
||
|
|
err_exit:
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
|
||
|
|
if (cser->serial_state & TIOCM_DSR)
|
||
|
|
result |= TIOCM_DSR;
|
||
|
|
|
||
|
|
if (cser->serial_state & TIOCM_CTS)
|
||
|
|
result |= TIOCM_CTS;
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (set & TIOCM_DSR)
|
||
|
|
cser->serial_state |= TIOCM_DSR;
|
||
|
|
|
||
|
|
if (clear & TIOCM_DSR)
|
||
|
|
cser->serial_state &= ~TIOCM_DSR;
|
||
|
|
|
||
|
|
if (set & TIOCM_CTS) {
|
||
|
|
if (cser->send_break) {
|
||
|
|
cser->serial_state |= TIOCM_CTS;
|
||
|
|
status = cser->send_break(cser, 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (clear & TIOCM_CTS) {
|
||
|
|
if (cser->send_break) {
|
||
|
|
cser->serial_state &= ~TIOCM_CTS;
|
||
|
|
status = cser->send_break(cser, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
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)%pK\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)%pK\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;
|
||
|
|
struct cserial *cser;
|
||
|
|
|
||
|
|
cser = &port->port_usb;
|
||
|
|
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;
|
||
|
|
|
||
|
|
/* if DTR is high, update latest modem info to laptop */
|
||
|
|
if (port->cbits_to_modem & TIOCM_DTR) {
|
||
|
|
unsigned int result;
|
||
|
|
unsigned int cbits_to_laptop;
|
||
|
|
|
||
|
|
result = f_cdev_tiocmget(port);
|
||
|
|
cbits_to_laptop = convert_uart_sigs_to_acm(result);
|
||
|
|
if (cser->send_modem_ctrl_bits)
|
||
|
|
cser->send_modem_ctrl_bits(cser, cbits_to_laptop);
|
||
|
|
}
|
||
|
|
|
||
|
|
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) (%pK)\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:%pK, 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:%pK, 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;
|
||
|
|
port->pending_state_notify = false;
|
||
|
|
port->is_suspended = false;
|
||
|
|
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)
|
||
|
|
{
|
||
|
|
struct cserial *cser;
|
||
|
|
unsigned long flags;
|
||
|
|
|
||
|
|
cser = &port->port_usb;
|
||
|
|
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;
|
||
|
|
cser->notify_modem = NULL;
|
||
|
|
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 ssize_t cser_rw_write(struct file *file, const char __user *ubuf,
|
||
|
|
size_t count, loff_t *ppos)
|
||
|
|
{
|
||
|
|
struct seq_file *s = file->private_data;
|
||
|
|
struct f_cdev *port = s->private;
|
||
|
|
u8 input;
|
||
|
|
struct cserial *cser;
|
||
|
|
struct usb_function *func;
|
||
|
|
struct usb_gadget *gadget;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
cser = &port->port_usb;
|
||
|
|
if (!cser) {
|
||
|
|
pr_err("cser is NULL\n");
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!port->is_connected) {
|
||
|
|
pr_debug("port disconnected\n");
|
||
|
|
return -ENODEV;
|
||
|
|
}
|
||
|
|
|
||
|
|
func = &cser->func;
|
||
|
|
if (!func) {
|
||
|
|
pr_err("func is NULL\n");
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ubuf == NULL) {
|
||
|
|
pr_debug("buffer is Null.\n");
|
||
|
|
goto err;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = kstrtou8_from_user(ubuf, count, 0, &input);
|
||
|
|
if (ret) {
|
||
|
|
pr_err("Invalid value. err:%d\n", ret);
|
||
|
|
goto err;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (port->debugfs_rw_enable == !!input) {
|
||
|
|
if (!!input)
|
||
|
|
pr_debug("RW already enabled\n");
|
||
|
|
else
|
||
|
|
pr_debug("RW already disabled\n");
|
||
|
|
goto err;
|
||
|
|
}
|
||
|
|
|
||
|
|
port->debugfs_rw_enable = !!input;
|
||
|
|
if (port->debugfs_rw_enable) {
|
||
|
|
gadget = cser->func.config->cdev->gadget;
|
||
|
|
if (gadget->speed >= USB_SPEED_SUPER &&
|
||
|
|
func->func_is_suspended) {
|
||
|
|
pr_debug("Calling usb_func_wakeup\n");
|
||
|
|
ret = usb_func_wakeup(func);
|
||
|
|
} else {
|
||
|
|
pr_debug("Calling usb_gadget_wakeup");
|
||
|
|
ret = usb_gadget_wakeup(gadget);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((ret == -EBUSY) || (ret == -EAGAIN))
|
||
|
|
pr_debug("RW delayed due to LPM exit.");
|
||
|
|
else if (ret)
|
||
|
|
pr_err("wakeup failed. ret=%d.", ret);
|
||
|
|
} else {
|
||
|
|
pr_debug("RW disabled.");
|
||
|
|
}
|
||
|
|
err:
|
||
|
|
return count;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int usb_cser_rw_show(struct seq_file *s, void *unused)
|
||
|
|
{
|
||
|
|
struct f_cdev *port = s->private;
|
||
|
|
|
||
|
|
if (!port) {
|
||
|
|
pr_err("port is null\n");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
seq_printf(s, "%d\n", port->debugfs_rw_enable);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int debug_cdev_rw_open(struct inode *inode, struct file *f)
|
||
|
|
{
|
||
|
|
return single_open(f, usb_cser_rw_show, inode->i_private);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct file_operations cser_rem_wakeup_fops = {
|
||
|
|
.open = debug_cdev_rw_open,
|
||
|
|
.read = seq_read,
|
||
|
|
.write = cser_rw_write,
|
||
|
|
.owner = THIS_MODULE,
|
||
|
|
.llseek = seq_lseek,
|
||
|
|
.release = seq_release,
|
||
|
|
};
|
||
|
|
|
||
|
|
static void usb_cser_debugfs_init(struct f_cdev *port)
|
||
|
|
{
|
||
|
|
port->debugfs_root = debugfs_create_dir(port->name, NULL);
|
||
|
|
if (IS_ERR(port->debugfs_root))
|
||
|
|
return;
|
||
|
|
|
||
|
|
debugfs_create_file("remote_wakeup", 0600,
|
||
|
|
port->debugfs_root, port, &cser_rem_wakeup_fops);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void usb_cser_debugfs_exit(struct f_cdev *port)
|
||
|
|
{
|
||
|
|
debugfs_remove_recursive(port->debugfs_root);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void cdev_device_release(struct device *dev)
|
||
|
|
{
|
||
|
|
struct f_cdev *port = container_of(dev, struct f_cdev, dev);
|
||
|
|
|
||
|
|
pr_debug("Free cdev port(%d)\n", port->port_num);
|
||
|
|
kfree(port);
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct f_cdev *f_cdev_alloc(char *func_name, int portno)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
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 (ida_is_empty(&chardev_ida)) {
|
||
|
|
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);
|
||
|
|
INIT_LIST_HEAD(&port->write_pending);
|
||
|
|
|
||
|
|
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);
|
||
|
|
device_initialize(&port->dev);
|
||
|
|
port->dev.class = fcdev_classp;
|
||
|
|
port->dev.parent = NULL;
|
||
|
|
port->dev.release = cdev_device_release;
|
||
|
|
port->dev.devt = MKDEV(major, port->minor);
|
||
|
|
dev_set_name(&port->dev, port->name);
|
||
|
|
ret = cdev_device_add(&port->fcdev_cdev, &port->dev);
|
||
|
|
if (ret) {
|
||
|
|
pr_err("Failed to add cdev for port(%s)\n", port->name);
|
||
|
|
goto err_cdev_add;
|
||
|
|
}
|
||
|
|
|
||
|
|
usb_cser_debugfs_init(port);
|
||
|
|
|
||
|
|
pr_info("port_name:%s (%pK) portno:(%d)\n",
|
||
|
|
port->name, port, port->port_num);
|
||
|
|
return port;
|
||
|
|
|
||
|
|
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 (ida_is_empty(&chardev_ida)) {
|
||
|
|
|
||
|
|
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;
|
||
|
|
opts->proto = 0x40;
|
||
|
|
} else {
|
||
|
|
opts->proto = 0x60;
|
||
|
|
}
|
||
|
|
|
||
|
|
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.func_suspend = usb_cser_func_suspend;
|
||
|
|
port->port_usb.func.get_status = usb_cser_get_status;
|
||
|
|
port->port_usb.func.free_func = usb_cser_free_func;
|
||
|
|
port->port_usb.func.resume = usb_cser_resume;
|
||
|
|
port->port_usb.func.suspend = usb_cser_suspend;
|
||
|
|
|
||
|
|
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");
|