| /* |
| * f_rmnet_sdio.c -- RmNet SDIO function driver |
| * |
| * Copyright (C) 2003-2005,2008 David Brownell |
| * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger |
| * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) |
| * Copyright (C) 2008 Nokia Corporation |
| * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/err.h> |
| #include <linux/list.h> |
| #include <linux/device.h> |
| #include <linux/workqueue.h> |
| #include <linux/netdevice.h> |
| |
| #include <linux/usb/cdc.h> |
| #include <linux/usb/composite.h> |
| #include <linux/usb/ch9.h> |
| #include <linux/termios.h> |
| #include <linux/debugfs.h> |
| |
| #include <mach/sdio_cmux.h> |
| #include <mach/sdio_dmux.h> |
| |
| #ifdef CONFIG_RMNET_SDIO_CTL_CHANNEL |
| static uint32_t rmnet_sdio_ctl_ch = CONFIG_RMNET_SDIO_CTL_CHANNEL; |
| #else |
| static uint32_t rmnet_sdio_ctl_ch; |
| #endif |
| module_param(rmnet_sdio_ctl_ch, uint, S_IRUGO); |
| MODULE_PARM_DESC(rmnet_sdio_ctl_ch, "RmNet control SDIO channel ID"); |
| |
| #ifdef CONFIG_RMNET_SDIO_DATA_CHANNEL |
| static uint32_t rmnet_sdio_data_ch = CONFIG_RMNET_SDIO_DATA_CHANNEL; |
| #else |
| static uint32_t rmnet_sdio_data_ch; |
| #endif |
| module_param(rmnet_sdio_data_ch, uint, S_IRUGO); |
| MODULE_PARM_DESC(rmnet_sdio_data_ch, "RmNet data SDIO channel ID"); |
| |
| #define ACM_CTRL_DTR (1 << 0) |
| |
| #define SDIO_MUX_HDR 8 |
| #define RMNET_SDIO_NOTIFY_INTERVAL 5 |
| #define RMNET_SDIO_MAX_NFY_SZE sizeof(struct usb_cdc_notification) |
| |
| #define RMNET_SDIO_RX_REQ_MAX 16 |
| #define RMNET_SDIO_RX_REQ_SIZE 2048 |
| #define RMNET_SDIO_TX_REQ_MAX 200 |
| |
| #define TX_PKT_DROP_THRESHOLD 1000 |
| #define RX_PKT_FLOW_CTRL_EN_THRESHOLD 1000 |
| #define RX_PKT_FLOW_CTRL_DISABLE 500 |
| |
| unsigned int sdio_tx_pkt_drop_thld = TX_PKT_DROP_THRESHOLD; |
| module_param(sdio_tx_pkt_drop_thld, uint, S_IRUGO | S_IWUSR); |
| |
| unsigned int sdio_rx_fctrl_en_thld = RX_PKT_FLOW_CTRL_EN_THRESHOLD; |
| module_param(sdio_rx_fctrl_en_thld, uint, S_IRUGO | S_IWUSR); |
| |
| unsigned int sdio_rx_fctrl_dis_thld = RX_PKT_FLOW_CTRL_DISABLE; |
| module_param(sdio_rx_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); |
| |
| /* QMI requests & responses buffer*/ |
| struct rmnet_sdio_qmi_buf { |
| void *buf; |
| int len; |
| struct list_head list; |
| }; |
| |
| struct rmnet_sdio_dev { |
| struct usb_function function; |
| struct usb_composite_dev *cdev; |
| |
| struct usb_ep *epout; |
| struct usb_ep *epin; |
| struct usb_ep *epnotify; |
| struct usb_request *notify_req; |
| |
| u8 ifc_id; |
| /* QMI lists */ |
| struct list_head qmi_req_q; |
| unsigned int qreq_q_len; |
| struct list_head qmi_resp_q; |
| unsigned int qresp_q_len; |
| /* Tx/Rx lists */ |
| struct list_head tx_idle; |
| unsigned int tx_idle_len; |
| struct sk_buff_head tx_skb_queue; |
| struct list_head rx_idle; |
| unsigned int rx_idle_len; |
| struct sk_buff_head rx_skb_queue; |
| |
| spinlock_t lock; |
| atomic_t online; |
| atomic_t notify_count; |
| |
| struct workqueue_struct *wq; |
| struct work_struct disconnect_work; |
| |
| struct work_struct ctl_rx_work; |
| struct work_struct data_rx_work; |
| |
| struct delayed_work sdio_open_work; |
| struct work_struct sdio_close_work; |
| #define RMNET_SDIO_CH_OPEN 1 |
| unsigned long data_ch_status; |
| unsigned long ctrl_ch_status; |
| |
| unsigned int dpkts_pending_atdmux; |
| int cbits_to_modem; |
| struct work_struct set_modem_ctl_bits_work; |
| |
| /* pkt logging dpkt - data pkt; cpkt - control pkt*/ |
| struct dentry *dent; |
| unsigned long dpkt_tolaptop; |
| unsigned long dpkt_tomodem; |
| unsigned long tx_drp_cnt; |
| unsigned long cpkt_tolaptop; |
| unsigned long cpkt_tomodem; |
| }; |
| |
| static struct usb_interface_descriptor rmnet_sdio_interface_desc = { |
| .bLength = USB_DT_INTERFACE_SIZE, |
| .bDescriptorType = USB_DT_INTERFACE, |
| /* .bInterfaceNumber = DYNAMIC */ |
| .bNumEndpoints = 3, |
| .bInterfaceClass = USB_CLASS_VENDOR_SPEC, |
| .bInterfaceSubClass = USB_CLASS_VENDOR_SPEC, |
| .bInterfaceProtocol = USB_CLASS_VENDOR_SPEC, |
| /* .iInterface = DYNAMIC */ |
| }; |
| |
| /* Full speed support */ |
| static struct usb_endpoint_descriptor rmnet_sdio_fs_notify_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_IN, |
| .bmAttributes = USB_ENDPOINT_XFER_INT, |
| .wMaxPacketSize = __constant_cpu_to_le16(RMNET_SDIO_MAX_NFY_SZE), |
| .bInterval = 1 << RMNET_SDIO_NOTIFY_INTERVAL, |
| }; |
| |
| static struct usb_endpoint_descriptor rmnet_sdio_fs_in_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_IN, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = __constant_cpu_to_le16(64), |
| }; |
| |
| static struct usb_endpoint_descriptor rmnet_sdio_fs_out_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_OUT, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = __constant_cpu_to_le16(64), |
| }; |
| |
| static struct usb_descriptor_header *rmnet_sdio_fs_function[] = { |
| (struct usb_descriptor_header *) &rmnet_sdio_interface_desc, |
| (struct usb_descriptor_header *) &rmnet_sdio_fs_notify_desc, |
| (struct usb_descriptor_header *) &rmnet_sdio_fs_in_desc, |
| (struct usb_descriptor_header *) &rmnet_sdio_fs_out_desc, |
| NULL, |
| }; |
| |
| /* High speed support */ |
| static struct usb_endpoint_descriptor rmnet_sdio_hs_notify_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_IN, |
| .bmAttributes = USB_ENDPOINT_XFER_INT, |
| .wMaxPacketSize = __constant_cpu_to_le16(RMNET_SDIO_MAX_NFY_SZE), |
| .bInterval = RMNET_SDIO_NOTIFY_INTERVAL + 4, |
| }; |
| |
| static struct usb_endpoint_descriptor rmnet_sdio_hs_in_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_IN, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = __constant_cpu_to_le16(512), |
| }; |
| |
| static struct usb_endpoint_descriptor rmnet_sdio_hs_out_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_OUT, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = __constant_cpu_to_le16(512), |
| }; |
| |
| static struct usb_descriptor_header *rmnet_sdio_hs_function[] = { |
| (struct usb_descriptor_header *) &rmnet_sdio_interface_desc, |
| (struct usb_descriptor_header *) &rmnet_sdio_hs_notify_desc, |
| (struct usb_descriptor_header *) &rmnet_sdio_hs_in_desc, |
| (struct usb_descriptor_header *) &rmnet_sdio_hs_out_desc, |
| NULL, |
| }; |
| |
| /* String descriptors */ |
| |
| static struct usb_string rmnet_sdio_string_defs[] = { |
| [0].s = "QMI RmNet", |
| { } /* end of list */ |
| }; |
| |
| static struct usb_gadget_strings rmnet_sdio_string_table = { |
| .language = 0x0409, /* en-us */ |
| .strings = rmnet_sdio_string_defs, |
| }; |
| |
| static struct usb_gadget_strings *rmnet_sdio_strings[] = { |
| &rmnet_sdio_string_table, |
| NULL, |
| }; |
| |
| static struct rmnet_sdio_qmi_buf * |
| rmnet_sdio_alloc_qmi(unsigned len, gfp_t kmalloc_flags) |
| |
| { |
| struct rmnet_sdio_qmi_buf *qmi; |
| |
| qmi = kmalloc(sizeof(struct rmnet_sdio_qmi_buf), kmalloc_flags); |
| if (qmi != NULL) { |
| qmi->buf = kmalloc(len, kmalloc_flags); |
| if (qmi->buf == NULL) { |
| kfree(qmi); |
| qmi = NULL; |
| } |
| } |
| |
| return qmi ? qmi : ERR_PTR(-ENOMEM); |
| } |
| |
| static void rmnet_sdio_free_qmi(struct rmnet_sdio_qmi_buf *qmi) |
| { |
| kfree(qmi->buf); |
| kfree(qmi); |
| } |
| /* |
| * Allocate a usb_request and its buffer. Returns a pointer to the |
| * usb_request or a pointer with an error code if there is an error. |
| */ |
| static struct usb_request * |
| rmnet_sdio_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags) |
| { |
| struct usb_request *req; |
| |
| req = usb_ep_alloc_request(ep, kmalloc_flags); |
| |
| if (len && req != NULL) { |
| req->length = len; |
| req->buf = kmalloc(len, kmalloc_flags); |
| if (req->buf == NULL) { |
| usb_ep_free_request(ep, req); |
| req = NULL; |
| } |
| } |
| |
| return req ? req : ERR_PTR(-ENOMEM); |
| } |
| |
| /* |
| * Free a usb_request and its buffer. |
| */ |
| static void rmnet_sdio_free_req(struct usb_ep *ep, struct usb_request *req) |
| { |
| kfree(req->buf); |
| usb_ep_free_request(ep, req); |
| } |
| |
| static void rmnet_sdio_notify_complete(struct usb_ep *ep, |
| struct usb_request *req) |
| { |
| struct rmnet_sdio_dev *dev = req->context; |
| struct usb_composite_dev *cdev = dev->cdev; |
| int status = req->status; |
| |
| switch (status) { |
| case -ECONNRESET: |
| case -ESHUTDOWN: |
| /* connection gone */ |
| atomic_set(&dev->notify_count, 0); |
| break; |
| default: |
| ERROR(cdev, "rmnet notifyep error %d\n", status); |
| /* FALLTHROUGH */ |
| case 0: |
| |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) |
| return; |
| |
| /* handle multiple pending QMI_RESPONSE_AVAILABLE |
| * notifications by resending until we're done |
| */ |
| if (atomic_dec_and_test(&dev->notify_count)) |
| break; |
| |
| status = usb_ep_queue(dev->epnotify, req, GFP_ATOMIC); |
| if (status) { |
| atomic_dec(&dev->notify_count); |
| ERROR(cdev, "rmnet notify ep enq error %d\n", status); |
| } |
| break; |
| } |
| } |
| |
| static void rmnet_sdio_qmi_resp_available(struct rmnet_sdio_dev *dev) |
| { |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct usb_cdc_notification *event; |
| int status; |
| unsigned long flags; |
| |
| /* Response will be sent later */ |
| if (atomic_inc_return(&dev->notify_count) != 1) |
| return; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| |
| if (!atomic_read(&dev->online)) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| return; |
| } |
| |
| event = dev->notify_req->buf; |
| |
| event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS |
| | USB_RECIP_INTERFACE; |
| event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE; |
| event->wValue = cpu_to_le16(0); |
| event->wIndex = cpu_to_le16(dev->ifc_id); |
| event->wLength = cpu_to_le16(0); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| status = usb_ep_queue(dev->epnotify, dev->notify_req, GFP_ATOMIC); |
| if (status < 0) { |
| if (atomic_read(&dev->online)) |
| atomic_dec(&dev->notify_count); |
| ERROR(cdev, "rmnet notify ep enqueue error %d\n", status); |
| } |
| } |
| |
| #define SDIO_MAX_CTRL_PKT_SIZE 4096 |
| static void rmnet_sdio_ctl_receive_cb(void *data, int size, void *priv) |
| { |
| struct rmnet_sdio_dev *dev = priv; |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct rmnet_sdio_qmi_buf *qmi_resp; |
| unsigned long flags; |
| |
| if (!data) { |
| pr_info("%s: cmux_ch close event\n", __func__); |
| if (test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status) && |
| test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { |
| clear_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status); |
| clear_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status); |
| queue_work(dev->wq, &dev->sdio_close_work); |
| } |
| return; |
| } |
| |
| if (!size || !test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) |
| return; |
| |
| |
| if (size > SDIO_MAX_CTRL_PKT_SIZE) { |
| ERROR(cdev, "ctrl pkt size:%d exceeds max pkt size:%d\n", |
| size, SDIO_MAX_CTRL_PKT_SIZE); |
| return; |
| } |
| |
| if (!atomic_read(&dev->online)) { |
| DBG(cdev, "USB disconnected\n"); |
| return; |
| } |
| |
| qmi_resp = rmnet_sdio_alloc_qmi(size, GFP_KERNEL); |
| if (IS_ERR(qmi_resp)) { |
| DBG(cdev, "unable to allocate memory for QMI resp\n"); |
| return; |
| } |
| memcpy(qmi_resp->buf, data, size); |
| qmi_resp->len = size; |
| spin_lock_irqsave(&dev->lock, flags); |
| list_add_tail(&qmi_resp->list, &dev->qmi_resp_q); |
| dev->qresp_q_len++; |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| rmnet_sdio_qmi_resp_available(dev); |
| } |
| |
| static void rmnet_sdio_ctl_write_done(void *data, int size, void *priv) |
| { |
| struct rmnet_sdio_dev *dev = priv; |
| struct usb_composite_dev *cdev = dev->cdev; |
| |
| VDBG(cdev, "rmnet control write done = %d bytes\n", size); |
| } |
| |
| static void rmnet_sdio_sts_callback(int id, void *priv) |
| { |
| struct rmnet_sdio_dev *dev = priv; |
| struct usb_composite_dev *cdev = dev->cdev; |
| |
| DBG(cdev, "rmnet_sdio_sts_callback: id: %d\n", id); |
| } |
| |
| static void rmnet_sdio_control_rx_work(struct work_struct *w) |
| { |
| struct rmnet_sdio_dev *dev = container_of(w, struct rmnet_sdio_dev, |
| ctl_rx_work); |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct rmnet_sdio_qmi_buf *qmi_req; |
| unsigned long flags; |
| int ret; |
| |
| while (1) { |
| spin_lock_irqsave(&dev->lock, flags); |
| if (list_empty(&dev->qmi_req_q)) |
| goto unlock; |
| |
| qmi_req = list_first_entry(&dev->qmi_req_q, |
| struct rmnet_sdio_qmi_buf, list); |
| list_del(&qmi_req->list); |
| dev->qreq_q_len--; |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| ret = sdio_cmux_write(rmnet_sdio_ctl_ch, qmi_req->buf, |
| qmi_req->len); |
| if (ret != qmi_req->len) { |
| ERROR(cdev, "rmnet control SDIO write failed\n"); |
| return; |
| } |
| |
| dev->cpkt_tomodem++; |
| |
| /* |
| * cmux_write API copies the buffer and gives it to sdio_al. |
| * Hence freeing the memory before write is completed. |
| */ |
| rmnet_sdio_free_qmi(qmi_req); |
| } |
| unlock: |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| static void rmnet_sdio_response_complete(struct usb_ep *ep, |
| struct usb_request *req) |
| { |
| struct rmnet_sdio_dev *dev = req->context; |
| struct usb_composite_dev *cdev = dev->cdev; |
| |
| switch (req->status) { |
| case -ECONNRESET: |
| case -ESHUTDOWN: |
| case 0: |
| return; |
| default: |
| INFO(cdev, "rmnet %s response error %d, %d/%d\n", |
| ep->name, req->status, |
| req->actual, req->length); |
| } |
| } |
| |
| static void rmnet_sdio_command_complete(struct usb_ep *ep, |
| struct usb_request *req) |
| { |
| struct rmnet_sdio_dev *dev = req->context; |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct rmnet_sdio_qmi_buf *qmi_req; |
| int len = req->actual; |
| |
| if (req->status < 0) { |
| ERROR(cdev, "rmnet command error %d\n", req->status); |
| return; |
| } |
| |
| /* discard the packet if sdio is not available */ |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) |
| return; |
| |
| qmi_req = rmnet_sdio_alloc_qmi(len, GFP_ATOMIC); |
| if (IS_ERR(qmi_req)) { |
| ERROR(cdev, "unable to allocate memory for QMI req\n"); |
| return; |
| } |
| memcpy(qmi_req->buf, req->buf, len); |
| qmi_req->len = len; |
| spin_lock(&dev->lock); |
| list_add_tail(&qmi_req->list, &dev->qmi_req_q); |
| dev->qreq_q_len++; |
| spin_unlock(&dev->lock); |
| queue_work(dev->wq, &dev->ctl_rx_work); |
| } |
| |
| static int |
| rmnet_sdio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) |
| { |
| struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_dev, |
| function); |
| struct usb_composite_dev *cdev = f->config->cdev; |
| struct usb_request *req = cdev->req; |
| int ret = -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); |
| struct rmnet_sdio_qmi_buf *resp; |
| |
| if (!atomic_read(&dev->online)) |
| return -ENOTCONN; |
| |
| switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { |
| |
| case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) |
| | USB_CDC_SEND_ENCAPSULATED_COMMAND: |
| ret = w_length; |
| req->complete = rmnet_sdio_command_complete; |
| req->context = dev; |
| break; |
| |
| |
| case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) |
| | USB_CDC_GET_ENCAPSULATED_RESPONSE: |
| if (w_value) |
| goto invalid; |
| else { |
| unsigned len; |
| |
| spin_lock(&dev->lock); |
| |
| if (list_empty(&dev->qmi_resp_q)) { |
| INFO(cdev, "qmi resp empty " |
| " req%02x.%02x v%04x i%04x l%d\n", |
| ctrl->bRequestType, ctrl->bRequest, |
| w_value, w_index, w_length); |
| spin_unlock(&dev->lock); |
| goto invalid; |
| } |
| |
| resp = list_first_entry(&dev->qmi_resp_q, |
| struct rmnet_sdio_qmi_buf, list); |
| list_del(&resp->list); |
| dev->qresp_q_len--; |
| spin_unlock(&dev->lock); |
| |
| len = min_t(unsigned, w_length, resp->len); |
| memcpy(req->buf, resp->buf, len); |
| ret = len; |
| req->context = dev; |
| req->complete = rmnet_sdio_response_complete; |
| rmnet_sdio_free_qmi(resp); |
| |
| /* check if its the right place to add */ |
| dev->cpkt_tolaptop++; |
| } |
| break; |
| case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) |
| | USB_CDC_REQ_SET_CONTROL_LINE_STATE: |
| /* This is a workaround for RmNet and is borrowed from the |
| * CDC/ACM standard. The host driver will issue the above ACM |
| * standard request to the RmNet interface in the following |
| * scenario: Once the network adapter is disabled from device |
| * manager, the above request will be sent from the qcusbnet |
| * host driver, with DTR being '0'. Once network adapter is |
| * enabled from device manager (or during enumeration), the |
| * request will be sent with DTR being '1'. |
| */ |
| if (w_value & ACM_CTRL_DTR) |
| dev->cbits_to_modem |= TIOCM_DTR; |
| else |
| dev->cbits_to_modem &= ~TIOCM_DTR; |
| queue_work(dev->wq, &dev->set_modem_ctl_bits_work); |
| |
| ret = 0; |
| |
| break; |
| default: |
| |
| invalid: |
| DBG(cdev, "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 (ret >= 0) { |
| VDBG(cdev, "rmnet req%02x.%02x v%04x i%04x l%d\n", |
| ctrl->bRequestType, ctrl->bRequest, |
| w_value, w_index, w_length); |
| req->zero = (ret < w_length); |
| req->length = ret; |
| ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); |
| if (ret < 0) |
| ERROR(cdev, "rmnet ep0 enqueue err %d\n", ret); |
| } |
| |
| return ret; |
| } |
| |
| static int |
| rmnet_sdio_rx_submit(struct rmnet_sdio_dev *dev, struct usb_request *req, |
| gfp_t gfp_flags) |
| { |
| struct sk_buff *skb; |
| int retval; |
| |
| skb = alloc_skb(RMNET_SDIO_RX_REQ_SIZE + SDIO_MUX_HDR, gfp_flags); |
| if (skb == NULL) |
| return -ENOMEM; |
| skb_reserve(skb, SDIO_MUX_HDR); |
| |
| req->buf = skb->data; |
| req->length = RMNET_SDIO_RX_REQ_SIZE; |
| req->context = skb; |
| |
| retval = usb_ep_queue(dev->epout, req, gfp_flags); |
| if (retval) |
| dev_kfree_skb_any(skb); |
| |
| return retval; |
| } |
| |
| static void rmnet_sdio_start_rx(struct rmnet_sdio_dev *dev) |
| { |
| struct usb_composite_dev *cdev = dev->cdev; |
| int status; |
| struct usb_request *req; |
| unsigned long flags; |
| |
| if (!atomic_read(&dev->online)) { |
| pr_err("%s: USB not connected\n", __func__); |
| return; |
| } |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| while (!list_empty(&dev->rx_idle)) { |
| req = list_first_entry(&dev->rx_idle, struct usb_request, list); |
| list_del(&req->list); |
| dev->rx_idle_len--; |
| |
| spin_unlock_irqrestore(&dev->lock, flags); |
| status = rmnet_sdio_rx_submit(dev, req, GFP_ATOMIC); |
| spin_lock_irqsave(&dev->lock, flags); |
| |
| if (status) { |
| ERROR(cdev, "rmnet data rx enqueue err %d\n", status); |
| list_add_tail(&req->list, &dev->rx_idle); |
| dev->rx_idle_len++; |
| break; |
| } |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| static void rmnet_sdio_start_tx(struct rmnet_sdio_dev *dev) |
| { |
| unsigned long flags; |
| int status; |
| struct sk_buff *skb; |
| struct usb_request *req; |
| struct usb_composite_dev *cdev = dev->cdev; |
| |
| if (!atomic_read(&dev->online)) |
| return; |
| |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) |
| return; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| while (!list_empty(&dev->tx_idle)) { |
| skb = __skb_dequeue(&dev->tx_skb_queue); |
| if (!skb) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| return; |
| } |
| |
| req = list_first_entry(&dev->tx_idle, struct usb_request, list); |
| req->context = skb; |
| req->buf = skb->data; |
| req->length = skb->len; |
| |
| list_del(&req->list); |
| dev->tx_idle_len--; |
| spin_unlock(&dev->lock); |
| status = usb_ep_queue(dev->epin, req, GFP_ATOMIC); |
| spin_lock(&dev->lock); |
| if (status) { |
| /* USB still online, queue requests back */ |
| if (atomic_read(&dev->online)) { |
| ERROR(cdev, "rmnet tx data enqueue err %d\n", |
| status); |
| list_add_tail(&req->list, &dev->tx_idle); |
| dev->tx_idle_len++; |
| __skb_queue_head(&dev->tx_skb_queue, skb); |
| } else { |
| req->buf = 0; |
| rmnet_sdio_free_req(dev->epin, req); |
| dev_kfree_skb_any(skb); |
| } |
| break; |
| } |
| dev->dpkt_tolaptop++; |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| static void rmnet_sdio_data_receive_cb(void *priv, struct sk_buff *skb) |
| { |
| struct rmnet_sdio_dev *dev = priv; |
| unsigned long flags; |
| |
| /* SDIO mux sends NULL SKB when link state changes */ |
| if (!skb) { |
| pr_info("%s: dmux_ch close event\n", __func__); |
| if (test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status) && |
| test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { |
| clear_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status); |
| clear_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status); |
| queue_work(dev->wq, &dev->sdio_close_work); |
| } |
| return; |
| } |
| |
| if (!atomic_read(&dev->online)) { |
| dev_kfree_skb_any(skb); |
| return; |
| } |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| |
| if (dev->tx_skb_queue.qlen > sdio_tx_pkt_drop_thld) { |
| if (printk_ratelimit()) |
| pr_err("%s: tx pkt dropped: tx_drop_cnt:%lu\n", |
| __func__, dev->tx_drp_cnt); |
| dev->tx_drp_cnt++; |
| spin_unlock_irqrestore(&dev->lock, flags); |
| dev_kfree_skb_any(skb); |
| return; |
| } |
| |
| __skb_queue_tail(&dev->tx_skb_queue, skb); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| rmnet_sdio_start_tx(dev); |
| } |
| |
| static void rmnet_sdio_data_write_done(void *priv, struct sk_buff *skb) |
| { |
| struct rmnet_sdio_dev *dev = priv; |
| |
| /* SDIO mux sends NULL SKB when link state changes */ |
| if (!skb) { |
| pr_info("%s: dmux_ch open event\n", __func__); |
| queue_delayed_work(dev->wq, &dev->sdio_open_work, 0); |
| return; |
| } |
| |
| dev_kfree_skb_any(skb); |
| /* this function is called from |
| * sdio mux from spin_lock_irqsave |
| */ |
| spin_lock(&dev->lock); |
| dev->dpkts_pending_atdmux--; |
| |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status) || |
| dev->dpkts_pending_atdmux >= sdio_rx_fctrl_dis_thld) { |
| spin_unlock(&dev->lock); |
| return; |
| } |
| spin_unlock(&dev->lock); |
| |
| rmnet_sdio_start_rx(dev); |
| } |
| |
| static void rmnet_sdio_data_rx_work(struct work_struct *w) |
| { |
| struct rmnet_sdio_dev *dev = container_of(w, struct rmnet_sdio_dev, |
| data_rx_work); |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct sk_buff *skb; |
| int ret; |
| unsigned long flags; |
| |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { |
| pr_info("%s: sdio data ch not open\n", __func__); |
| return; |
| } |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| while ((skb = __skb_dequeue(&dev->rx_skb_queue))) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| ret = msm_sdio_dmux_write(rmnet_sdio_data_ch, skb); |
| spin_lock_irqsave(&dev->lock, flags); |
| if (ret < 0) { |
| ERROR(cdev, "rmnet SDIO data write failed\n"); |
| dev_kfree_skb_any(skb); |
| break; |
| } else { |
| dev->dpkt_tomodem++; |
| dev->dpkts_pending_atdmux++; |
| } |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| static void rmnet_sdio_complete_epout(struct usb_ep *ep, |
| struct usb_request *req) |
| { |
| struct rmnet_sdio_dev *dev = ep->driver_data; |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct sk_buff *skb = req->context; |
| int status = req->status; |
| int queue = 0; |
| |
| switch (status) { |
| case 0: |
| /* successful completion */ |
| skb_put(skb, req->actual); |
| queue = 1; |
| break; |
| case -ECONNRESET: |
| case -ESHUTDOWN: |
| /* connection gone */ |
| dev_kfree_skb_any(skb); |
| req->buf = 0; |
| rmnet_sdio_free_req(ep, req); |
| return; |
| default: |
| /* unexpected failure */ |
| ERROR(cdev, "RMNET %s response error %d, %d/%d\n", |
| ep->name, status, |
| req->actual, req->length); |
| dev_kfree_skb_any(skb); |
| break; |
| } |
| |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { |
| pr_info("%s: sdio data ch not open\n", __func__); |
| dev_kfree_skb_any(skb); |
| req->buf = 0; |
| rmnet_sdio_free_req(ep, req); |
| return; |
| } |
| |
| spin_lock(&dev->lock); |
| if (queue) { |
| __skb_queue_tail(&dev->rx_skb_queue, skb); |
| queue_work(dev->wq, &dev->data_rx_work); |
| } |
| |
| if (dev->dpkts_pending_atdmux >= sdio_rx_fctrl_en_thld) { |
| list_add_tail(&req->list, &dev->rx_idle); |
| dev->rx_idle_len++; |
| spin_unlock(&dev->lock); |
| return; |
| } |
| spin_unlock(&dev->lock); |
| |
| status = rmnet_sdio_rx_submit(dev, req, GFP_ATOMIC); |
| if (status) { |
| ERROR(cdev, "rmnet data rx enqueue err %d\n", status); |
| list_add_tail(&req->list, &dev->rx_idle); |
| dev->rx_idle_len++; |
| } |
| } |
| |
| static void rmnet_sdio_complete_epin(struct usb_ep *ep, struct usb_request *req) |
| { |
| struct rmnet_sdio_dev *dev = ep->driver_data; |
| struct sk_buff *skb = req->context; |
| struct usb_composite_dev *cdev = dev->cdev; |
| int status = req->status; |
| |
| switch (status) { |
| case 0: |
| /* successful completion */ |
| case -ECONNRESET: |
| case -ESHUTDOWN: |
| /* connection gone */ |
| break; |
| default: |
| ERROR(cdev, "rmnet data tx ep error %d\n", status); |
| break; |
| } |
| |
| spin_lock(&dev->lock); |
| list_add_tail(&req->list, &dev->tx_idle); |
| dev->tx_idle_len++; |
| spin_unlock(&dev->lock); |
| dev_kfree_skb_any(skb); |
| |
| rmnet_sdio_start_tx(dev); |
| } |
| |
| static void rmnet_sdio_free_buf(struct rmnet_sdio_dev *dev) |
| { |
| struct rmnet_sdio_qmi_buf *qmi; |
| struct usb_request *req; |
| struct list_head *act, *tmp; |
| struct sk_buff *skb; |
| unsigned long flags; |
| |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| |
| dev->dpkt_tolaptop = 0; |
| dev->dpkt_tomodem = 0; |
| dev->cpkt_tolaptop = 0; |
| dev->cpkt_tomodem = 0; |
| dev->dpkts_pending_atdmux = 0; |
| dev->tx_drp_cnt = 0; |
| |
| /* free all usb requests in tx pool */ |
| list_for_each_safe(act, tmp, &dev->tx_idle) { |
| req = list_entry(act, struct usb_request, list); |
| list_del(&req->list); |
| dev->tx_idle_len--; |
| req->buf = NULL; |
| rmnet_sdio_free_req(dev->epout, req); |
| } |
| |
| /* free all usb requests in rx pool */ |
| list_for_each_safe(act, tmp, &dev->rx_idle) { |
| req = list_entry(act, struct usb_request, list); |
| list_del(&req->list); |
| dev->rx_idle_len--; |
| req->buf = NULL; |
| rmnet_sdio_free_req(dev->epin, req); |
| } |
| |
| /* free all buffers in qmi request pool */ |
| list_for_each_safe(act, tmp, &dev->qmi_req_q) { |
| qmi = list_entry(act, struct rmnet_sdio_qmi_buf, list); |
| list_del(&qmi->list); |
| dev->qreq_q_len--; |
| rmnet_sdio_free_qmi(qmi); |
| } |
| |
| /* free all buffers in qmi request pool */ |
| list_for_each_safe(act, tmp, &dev->qmi_resp_q) { |
| qmi = list_entry(act, struct rmnet_sdio_qmi_buf, list); |
| list_del(&qmi->list); |
| dev->qresp_q_len--; |
| rmnet_sdio_free_qmi(qmi); |
| } |
| |
| while ((skb = __skb_dequeue(&dev->tx_skb_queue))) |
| dev_kfree_skb_any(skb); |
| |
| while ((skb = __skb_dequeue(&dev->rx_skb_queue))) |
| dev_kfree_skb_any(skb); |
| |
| rmnet_sdio_free_req(dev->epnotify, dev->notify_req); |
| |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| static void rmnet_sdio_set_modem_cbits_w(struct work_struct *w) |
| { |
| struct rmnet_sdio_dev *dev; |
| |
| dev = container_of(w, struct rmnet_sdio_dev, set_modem_ctl_bits_work); |
| |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) |
| return; |
| |
| pr_debug("%s: cbits_to_modem:%d\n", |
| __func__, dev->cbits_to_modem); |
| |
| sdio_cmux_tiocmset(rmnet_sdio_ctl_ch, |
| dev->cbits_to_modem, |
| ~dev->cbits_to_modem); |
| } |
| |
| static void rmnet_sdio_disconnect_work(struct work_struct *w) |
| { |
| /* REVISIT: Push all the data to sdio if anythign is pending */ |
| } |
| static void rmnet_sdio_suspend(struct usb_function *f) |
| { |
| struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_dev, |
| function); |
| |
| if (!atomic_read(&dev->online)) |
| return; |
| /* This is a workaround for Windows Host bug during suspend. |
| * Windows 7/xp Hosts are suppose to drop DTR, when Host suspended. |
| * Since it is not beind done, Hence exclusively dropping the DTR |
| * from function driver suspend. |
| */ |
| dev->cbits_to_modem &= ~TIOCM_DTR; |
| queue_work(dev->wq, &dev->set_modem_ctl_bits_work); |
| } |
| static void rmnet_sdio_disable(struct usb_function *f) |
| { |
| struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_dev, |
| function); |
| |
| if (!atomic_read(&dev->online)) |
| return; |
| |
| usb_ep_disable(dev->epnotify); |
| usb_ep_disable(dev->epout); |
| usb_ep_disable(dev->epin); |
| |
| atomic_set(&dev->online, 0); |
| atomic_set(&dev->notify_count, 0); |
| rmnet_sdio_free_buf(dev); |
| |
| /* cleanup work */ |
| queue_work(dev->wq, &dev->disconnect_work); |
| dev->cbits_to_modem = 0; |
| queue_work(dev->wq, &dev->set_modem_ctl_bits_work); |
| } |
| |
| static void rmnet_close_sdio_work(struct work_struct *w) |
| { |
| struct rmnet_sdio_dev *dev; |
| unsigned long flags; |
| struct usb_cdc_notification *event; |
| int status; |
| struct rmnet_sdio_qmi_buf *qmi; |
| struct usb_request *req; |
| struct sk_buff *skb; |
| |
| pr_debug("%s:\n", __func__); |
| |
| dev = container_of(w, struct rmnet_sdio_dev, sdio_close_work); |
| |
| if (!atomic_read(&dev->online)) |
| return; |
| |
| usb_ep_fifo_flush(dev->epnotify); |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| event = dev->notify_req->buf; |
| |
| event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS |
| | USB_RECIP_INTERFACE; |
| event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; |
| event->wValue = cpu_to_le16(0); |
| event->wIndex = cpu_to_le16(dev->ifc_id); |
| event->wLength = cpu_to_le16(0); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| status = usb_ep_queue(dev->epnotify, dev->notify_req, GFP_KERNEL); |
| if (status < 0) { |
| if (!atomic_read(&dev->online)) |
| return; |
| pr_err("%s: rmnet notify ep enqueue error %d\n", |
| __func__, status); |
| } |
| |
| usb_ep_fifo_flush(dev->epout); |
| usb_ep_fifo_flush(dev->epin); |
| cancel_work_sync(&dev->data_rx_work); |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| |
| if (!atomic_read(&dev->online)) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| return; |
| } |
| |
| /* free all usb requests in tx pool */ |
| while (!list_empty(&dev->tx_idle)) { |
| req = list_first_entry(&dev->tx_idle, struct usb_request, list); |
| list_del(&req->list); |
| dev->tx_idle_len--; |
| req->buf = NULL; |
| rmnet_sdio_free_req(dev->epout, req); |
| } |
| |
| /* free all usb requests in rx pool */ |
| while (!list_empty(&dev->rx_idle)) { |
| req = list_first_entry(&dev->rx_idle, struct usb_request, list); |
| list_del(&req->list); |
| dev->rx_idle_len--; |
| req->buf = NULL; |
| rmnet_sdio_free_req(dev->epin, req); |
| } |
| |
| /* free all buffers in qmi request pool */ |
| while (!list_empty(&dev->qmi_req_q)) { |
| qmi = list_first_entry(&dev->qmi_req_q, |
| struct rmnet_sdio_qmi_buf, list); |
| list_del(&qmi->list); |
| dev->qreq_q_len--; |
| rmnet_sdio_free_qmi(qmi); |
| } |
| |
| /* free all buffers in qmi response pool */ |
| while (!list_empty(&dev->qmi_resp_q)) { |
| qmi = list_first_entry(&dev->qmi_resp_q, |
| struct rmnet_sdio_qmi_buf, list); |
| list_del(&qmi->list); |
| dev->qresp_q_len--; |
| rmnet_sdio_free_qmi(qmi); |
| } |
| atomic_set(&dev->notify_count, 0); |
| |
| pr_info("%s: setting notify count to zero\n", __func__); |
| |
| |
| while ((skb = __skb_dequeue(&dev->tx_skb_queue))) |
| dev_kfree_skb_any(skb); |
| |
| while ((skb = __skb_dequeue(&dev->rx_skb_queue))) |
| dev_kfree_skb_any(skb); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| static int rmnet_sdio_start_io(struct rmnet_sdio_dev *dev) |
| { |
| struct usb_request *req; |
| int ret, i; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| if (!atomic_read(&dev->online)) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| return 0; |
| } |
| |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status) || |
| !test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| return 0; |
| } |
| |
| for (i = 0; i < RMNET_SDIO_RX_REQ_MAX; i++) { |
| req = rmnet_sdio_alloc_req(dev->epout, 0, GFP_ATOMIC); |
| if (IS_ERR(req)) { |
| ret = PTR_ERR(req); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| goto free_buf; |
| } |
| req->complete = rmnet_sdio_complete_epout; |
| list_add_tail(&req->list, &dev->rx_idle); |
| dev->rx_idle_len++; |
| } |
| for (i = 0; i < RMNET_SDIO_TX_REQ_MAX; i++) { |
| req = rmnet_sdio_alloc_req(dev->epin, 0, GFP_ATOMIC); |
| if (IS_ERR(req)) { |
| ret = PTR_ERR(req); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| goto free_buf; |
| } |
| req->complete = rmnet_sdio_complete_epin; |
| list_add_tail(&req->list, &dev->tx_idle); |
| dev->tx_idle_len++; |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| /* Queue Rx data requests */ |
| rmnet_sdio_start_rx(dev); |
| |
| return 0; |
| |
| free_buf: |
| rmnet_sdio_free_buf(dev); |
| dev->epout = dev->epin = dev->epnotify = NULL; /* release endpoints */ |
| return ret; |
| } |
| |
| |
| #define RMNET_SDIO_OPEN_RETRY_DELAY msecs_to_jiffies(2000) |
| #define SDIO_SDIO_OPEN_MAX_RETRY 90 |
| static void rmnet_open_sdio_work(struct work_struct *w) |
| { |
| struct rmnet_sdio_dev *dev = |
| container_of(w, struct rmnet_sdio_dev, |
| sdio_open_work.work); |
| struct usb_composite_dev *cdev = dev->cdev; |
| int ret; |
| static int retry_cnt; |
| |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) { |
| /* Control channel for QMI messages */ |
| ret = sdio_cmux_open(rmnet_sdio_ctl_ch, |
| rmnet_sdio_ctl_receive_cb, |
| rmnet_sdio_ctl_write_done, |
| rmnet_sdio_sts_callback, dev); |
| if (!ret) |
| set_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status); |
| } |
| |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { |
| /* Data channel for network packets */ |
| ret = msm_sdio_dmux_open(rmnet_sdio_data_ch, dev, |
| rmnet_sdio_data_receive_cb, |
| rmnet_sdio_data_write_done); |
| if (!ret) |
| set_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status); |
| } |
| |
| if (test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status) && |
| test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) { |
| |
| rmnet_sdio_start_io(dev); |
| |
| /* if usb cable is connected, update DTR status to modem */ |
| if (atomic_read(&dev->online)) |
| queue_work(dev->wq, &dev->set_modem_ctl_bits_work); |
| |
| pr_info("%s: usb rmnet sdio channels are open retry_cnt:%d\n", |
| __func__, retry_cnt); |
| retry_cnt = 0; |
| return; |
| } |
| |
| retry_cnt++; |
| pr_debug("%s: usb rmnet sdio open retry_cnt:%d\n", |
| __func__, retry_cnt); |
| |
| if (retry_cnt > SDIO_SDIO_OPEN_MAX_RETRY) { |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) |
| ERROR(cdev, "Unable to open control SDIO channel\n"); |
| |
| if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) |
| ERROR(cdev, "Unable to open DATA SDIO channel\n"); |
| |
| } else { |
| queue_delayed_work(dev->wq, &dev->sdio_open_work, |
| RMNET_SDIO_OPEN_RETRY_DELAY); |
| } |
| } |
| |
| static int rmnet_sdio_set_alt(struct usb_function *f, |
| unsigned intf, unsigned alt) |
| { |
| struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_dev, |
| function); |
| struct usb_composite_dev *cdev = dev->cdev; |
| int ret = 0; |
| |
| /* Enable epin */ |
| dev->epin->driver_data = dev; |
| ret = config_ep_by_speed(cdev->gadget, f, dev->epin); |
| if (ret) { |
| dev->epin->desc = NULL; |
| ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", |
| dev->epin->name, ret); |
| return ret; |
| } |
| ret = usb_ep_enable(dev->epin); |
| if (ret) { |
| ERROR(cdev, "can't enable %s, result %d\n", |
| dev->epin->name, ret); |
| return ret; |
| } |
| |
| /* Enable epout */ |
| dev->epout->driver_data = dev; |
| ret = config_ep_by_speed(cdev->gadget, f, dev->epout); |
| if (ret) { |
| dev->epout->desc = NULL; |
| ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", |
| dev->epout->name, ret); |
| usb_ep_disable(dev->epin); |
| return ret; |
| } |
| ret = usb_ep_enable(dev->epout); |
| if (ret) { |
| ERROR(cdev, "can't enable %s, result %d\n", |
| dev->epout->name, ret); |
| usb_ep_disable(dev->epin); |
| return ret; |
| } |
| |
| /* Enable epnotify */ |
| ret = config_ep_by_speed(cdev->gadget, f, dev->epnotify); |
| if (ret) { |
| dev->epnotify->desc = NULL; |
| ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", |
| dev->epnotify->name, ret); |
| usb_ep_disable(dev->epin); |
| usb_ep_disable(dev->epout); |
| return ret; |
| } |
| ret = usb_ep_enable(dev->epnotify); |
| if (ret) { |
| ERROR(cdev, "can't enable %s, result %d\n", |
| dev->epnotify->name, ret); |
| usb_ep_disable(dev->epin); |
| usb_ep_disable(dev->epout); |
| return ret; |
| } |
| |
| /* allocate notification */ |
| dev->notify_req = rmnet_sdio_alloc_req(dev->epnotify, |
| RMNET_SDIO_MAX_NFY_SZE, GFP_ATOMIC); |
| |
| if (IS_ERR(dev->notify_req)) { |
| ret = PTR_ERR(dev->notify_req); |
| pr_err("%s: unable to allocate memory for notify ep\n", |
| __func__); |
| return ret; |
| } |
| dev->notify_req->complete = rmnet_sdio_notify_complete; |
| dev->notify_req->context = dev; |
| dev->notify_req->length = RMNET_SDIO_MAX_NFY_SZE; |
| |
| atomic_set(&dev->online, 1); |
| |
| ret = rmnet_sdio_start_io(dev); |
| |
| return ret; |
| |
| } |
| |
| static int rmnet_sdio_bind(struct usb_configuration *c, struct usb_function *f) |
| { |
| struct usb_composite_dev *cdev = c->cdev; |
| struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_dev, |
| function); |
| int id; |
| struct usb_ep *ep; |
| |
| dev->cdev = cdev; |
| |
| /* allocate interface ID */ |
| id = usb_interface_id(c, f); |
| if (id < 0) |
| return id; |
| dev->ifc_id = id; |
| rmnet_sdio_interface_desc.bInterfaceNumber = id; |
| |
| ep = usb_ep_autoconfig(cdev->gadget, &rmnet_sdio_fs_in_desc); |
| if (!ep) |
| goto out; |
| ep->driver_data = cdev; /* claim endpoint */ |
| dev->epin = ep; |
| |
| ep = usb_ep_autoconfig(cdev->gadget, &rmnet_sdio_fs_out_desc); |
| if (!ep) |
| goto out; |
| ep->driver_data = cdev; /* claim endpoint */ |
| dev->epout = ep; |
| |
| ep = usb_ep_autoconfig(cdev->gadget, &rmnet_sdio_fs_notify_desc); |
| if (!ep) |
| goto out; |
| ep->driver_data = cdev; /* claim endpoint */ |
| dev->epnotify = ep; |
| |
| /* support all relevant hardware speeds... we expect that when |
| * hardware is dual speed, all bulk-capable endpoints work at |
| * both speeds |
| */ |
| if (gadget_is_dualspeed(c->cdev->gadget)) { |
| rmnet_sdio_hs_in_desc.bEndpointAddress = |
| rmnet_sdio_fs_in_desc.bEndpointAddress; |
| rmnet_sdio_hs_out_desc.bEndpointAddress = |
| rmnet_sdio_fs_out_desc.bEndpointAddress; |
| rmnet_sdio_hs_notify_desc.bEndpointAddress = |
| rmnet_sdio_fs_notify_desc.bEndpointAddress; |
| } |
| |
| queue_delayed_work(dev->wq, &dev->sdio_open_work, 0); |
| |
| return 0; |
| |
| out: |
| if (dev->epnotify) |
| dev->epnotify->driver_data = NULL; |
| if (dev->epout) |
| dev->epout->driver_data = NULL; |
| if (dev->epin) |
| dev->epin->driver_data = NULL; |
| |
| return -ENODEV; |
| } |
| |
| static void |
| rmnet_sdio_unbind(struct usb_configuration *c, struct usb_function *f) |
| { |
| struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_dev, |
| function); |
| |
| cancel_delayed_work_sync(&dev->sdio_open_work); |
| destroy_workqueue(dev->wq); |
| |
| dev->epout = dev->epin = dev->epnotify = NULL; /* release endpoints */ |
| |
| if (test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { |
| msm_sdio_dmux_close(rmnet_sdio_data_ch); |
| clear_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status); |
| } |
| |
| if (test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) { |
| sdio_cmux_close(rmnet_sdio_ctl_ch); |
| clear_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status); |
| } |
| |
| debugfs_remove_recursive(dev->dent); |
| |
| kfree(dev); |
| } |
| |
| #if defined(CONFIG_DEBUG_FS) |
| static ssize_t rmnet_sdio_read_stats(struct file *file, char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| struct rmnet_sdio_dev *dev = file->private_data; |
| char *buf; |
| unsigned long flags; |
| int ret; |
| |
| buf = kzalloc(sizeof(char) * 1024, GFP_KERNEL); |
| if (!buf) |
| return -ENOMEM; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| ret = scnprintf(buf, PAGE_SIZE, |
| "-*-DATA-*-\n" |
| "dpkts_tohost:%lu epInPool:%u tx_size:%u drp_cnt:%lu\n" |
| "dpkts_tomodem:%lu epOutPool:%u rx_size:%u pending:%u\n" |
| "-*-QMI-*-\n" |
| "cpkts_tomodem:%lu qmi_req_q:%u cbits:%d\n" |
| "cpkts_tolaptop:%lu qmi_resp_q:%u notify_cnt:%d\n" |
| "-*-MISC-*-\n" |
| "data_ch_status: %lu ctrl_ch_status: %lu\n", |
| /* data */ |
| dev->dpkt_tolaptop, dev->tx_idle_len, |
| dev->tx_skb_queue.qlen, dev->tx_drp_cnt, |
| dev->dpkt_tomodem, dev->rx_idle_len, |
| dev->rx_skb_queue.qlen, dev->dpkts_pending_atdmux, |
| /* qmi */ |
| dev->cpkt_tomodem, dev->qreq_q_len, |
| dev->cbits_to_modem, |
| dev->cpkt_tolaptop, dev->qresp_q_len, |
| atomic_read(&dev->notify_count), |
| /* misc */ |
| dev->data_ch_status, dev->ctrl_ch_status); |
| |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); |
| |
| kfree(buf); |
| |
| return ret; |
| } |
| |
| static ssize_t rmnet_sdio_reset_stats(struct file *file, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| struct rmnet_sdio_dev *dev = file->private_data; |
| |
| dev->dpkt_tolaptop = 0; |
| dev->dpkt_tomodem = 0; |
| dev->cpkt_tolaptop = 0; |
| dev->cpkt_tomodem = 0; |
| dev->dpkts_pending_atdmux = 0; |
| dev->tx_drp_cnt = 0; |
| |
| /* TBD: How do we reset skb qlen |
| * it might have side effects |
| */ |
| |
| return count; |
| } |
| |
| static int debug_rmnet_sdio_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = inode->i_private; |
| |
| return 0; |
| } |
| |
| const struct file_operations debug_rmnet_sdio_stats_ops = { |
| .open = debug_rmnet_sdio_open, |
| .read = rmnet_sdio_read_stats, |
| .write = rmnet_sdio_reset_stats, |
| }; |
| |
| static void rmnet_sdio_debugfs_init(struct rmnet_sdio_dev *dev) |
| { |
| dev->dent = debugfs_create_dir("usb_rmnet_sdio", 0); |
| if (IS_ERR(dev->dent)) |
| return; |
| |
| debugfs_create_file("status", 0444, dev->dent, dev, |
| &debug_rmnet_sdio_stats_ops); |
| } |
| #else |
| static void rmnet_sdio_debugfs_init(struct rmnet_sdio_dev *dev) |
| { |
| return; |
| } |
| #endif |
| |
| int rmnet_sdio_function_add(struct usb_configuration *c) |
| { |
| struct rmnet_sdio_dev *dev; |
| int ret; |
| |
| dev = kzalloc(sizeof(*dev), GFP_KERNEL); |
| if (!dev) |
| return -ENOMEM; |
| |
| dev->wq = create_singlethread_workqueue("k_rmnet_work"); |
| if (!dev->wq) { |
| ret = -ENOMEM; |
| goto free_dev; |
| } |
| |
| spin_lock_init(&dev->lock); |
| atomic_set(&dev->notify_count, 0); |
| atomic_set(&dev->online, 0); |
| |
| INIT_WORK(&dev->disconnect_work, rmnet_sdio_disconnect_work); |
| INIT_WORK(&dev->set_modem_ctl_bits_work, rmnet_sdio_set_modem_cbits_w); |
| |
| INIT_WORK(&dev->ctl_rx_work, rmnet_sdio_control_rx_work); |
| INIT_WORK(&dev->data_rx_work, rmnet_sdio_data_rx_work); |
| |
| INIT_DELAYED_WORK(&dev->sdio_open_work, rmnet_open_sdio_work); |
| INIT_WORK(&dev->sdio_close_work, rmnet_close_sdio_work); |
| |
| INIT_LIST_HEAD(&dev->qmi_req_q); |
| INIT_LIST_HEAD(&dev->qmi_resp_q); |
| |
| INIT_LIST_HEAD(&dev->rx_idle); |
| INIT_LIST_HEAD(&dev->tx_idle); |
| skb_queue_head_init(&dev->tx_skb_queue); |
| skb_queue_head_init(&dev->rx_skb_queue); |
| |
| dev->function.name = "rmnet_sdio"; |
| dev->function.strings = rmnet_sdio_strings; |
| dev->function.descriptors = rmnet_sdio_fs_function; |
| dev->function.hs_descriptors = rmnet_sdio_hs_function; |
| dev->function.bind = rmnet_sdio_bind; |
| dev->function.unbind = rmnet_sdio_unbind; |
| dev->function.setup = rmnet_sdio_setup; |
| dev->function.set_alt = rmnet_sdio_set_alt; |
| dev->function.disable = rmnet_sdio_disable; |
| dev->function.suspend = rmnet_sdio_suspend; |
| |
| ret = usb_add_function(c, &dev->function); |
| if (ret) |
| goto free_wq; |
| |
| rmnet_sdio_debugfs_init(dev); |
| |
| return 0; |
| |
| free_wq: |
| destroy_workqueue(dev->wq); |
| free_dev: |
| kfree(dev); |
| |
| return ret; |
| } |