| /* |
| * f_rmnet_smd_sdio.c -- RmNet SMD & 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) 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 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. |
| */ |
| |
| #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/interrupt.h> |
| #include <linux/ratelimit.h> |
| |
| #include <linux/fs.h> |
| #include <linux/miscdevice.h> |
| #include <linux/uaccess.h> |
| #include <asm/ioctls.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/msm_smd.h> |
| #include <mach/sdio_cmux.h> |
| #include <mach/sdio_dmux.h> |
| #include <mach/usb_gadget_xport.h> |
| |
| #ifdef CONFIG_RMNET_SMD_SDIO_CTL_CHANNEL |
| static uint32_t rmnet_mux_sdio_ctl_ch = CONFIG_RMNET_SMD_SDIO_CTL_CHANNEL; |
| #else |
| static uint32_t rmnet_mux_sdio_ctl_ch; |
| #endif |
| module_param(rmnet_mux_sdio_ctl_ch, uint, S_IRUGO); |
| MODULE_PARM_DESC(rmnet_mux_sdio_ctl_ch, "RmNetMUX control SDIO channel ID"); |
| |
| #ifdef CONFIG_RMNET_SMD_SDIO_DATA_CHANNEL |
| static uint32_t rmnet_mux_sdio_data_ch = CONFIG_RMNET_SMD_SDIO_DATA_CHANNEL; |
| #else |
| static uint32_t rmnet_mux_sdio_data_ch; |
| #endif |
| module_param(rmnet_mux_sdio_data_ch, uint, S_IRUGO); |
| MODULE_PARM_DESC(rmnet_mux_sdio_data_ch, "RmNetMUX data SDIO channel ID"); |
| |
| #ifdef CONFIG_RMNET_SDIO_SMD_DATA_CHANNEL |
| static char *rmnet_mux_smd_data_ch = CONFIG_RMNET_SDIO_SMD_DATA_CHANNEL; |
| #else |
| static char *rmnet_mux_smd_data_ch; |
| #endif |
| module_param(rmnet_mux_smd_data_ch, charp, S_IRUGO); |
| MODULE_PARM_DESC(rmnet_mux_smd_data_ch, "RmNetMUX data SMD channel"); |
| |
| #define RMNET_MUX_ACM_CTRL_DTR (1 << 0) |
| |
| #define RMNET_MUX_SDIO_HDR 8 |
| #define RMNET_MUX_SDIO_NOTIFY_INTERVAL 5 |
| #define RMNET_MUX_SDIO_MAX_NFY_SZE sizeof(struct usb_cdc_notification) |
| |
| #define RMNET_MUX_SDIO_RX_REQ_MAX 16 |
| #define RMNET_MUX_SDIO_RX_REQ_SIZE 2048 |
| #define RMNET_MUX_SDIO_TX_REQ_MAX 100 |
| |
| #define RMNET_MUX_SDIO_TX_LIMIT 1000 |
| #define RMNET_MUX_SDIO_RX_ENABLE_LIMIT 1000 |
| #define RMNET_MUX_SDIO_RX_DISABLE_LIMIT 500 |
| |
| static uint32_t mux_sdio_tx_pkt_drop_thld = RMNET_MUX_SDIO_TX_LIMIT; |
| module_param(mux_sdio_tx_pkt_drop_thld, uint, S_IRUGO | S_IWUSR); |
| |
| static uint32_t mux_sdio_rx_fctrl_en_thld = |
| RMNET_MUX_SDIO_RX_ENABLE_LIMIT; |
| module_param(mux_sdio_rx_fctrl_en_thld, uint, S_IRUGO | S_IWUSR); |
| |
| static uint32_t mux_sdio_rx_fctrl_dis_thld = RMNET_MUX_SDIO_RX_DISABLE_LIMIT; |
| module_param(mux_sdio_rx_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); |
| |
| |
| #define RMNET_MUX_SMD_RX_REQ_MAX 8 |
| #define RMNET_MUX_SMD_RX_REQ_SIZE 2048 |
| #define RMNET_MUX_SMD_TX_REQ_MAX 8 |
| #define RMNET_MUX_SMD_TX_REQ_SIZE 2048 |
| #define RMNET_MUX_SMD_TXN_MAX 2048 |
| |
| struct rmnet_mux_ctrl_pkt { |
| void *buf; |
| int len; |
| struct list_head list; |
| }; |
| |
| struct rmnet_mux_ctrl_dev { |
| struct list_head tx_q; |
| wait_queue_head_t tx_wait_q; |
| unsigned long tx_len; |
| |
| struct list_head rx_q; |
| unsigned long rx_len; |
| |
| unsigned long cbits_to_modem; |
| |
| unsigned opened; |
| }; |
| |
| struct rmnet_mux_sdio_dev { |
| /* Tx/Rx lists */ |
| struct list_head tx_idle; |
| struct sk_buff_head tx_skb_queue; |
| struct list_head rx_idle; |
| struct sk_buff_head rx_skb_queue; |
| |
| |
| |
| struct work_struct data_rx_work; |
| |
| struct delayed_work open_work; |
| atomic_t sdio_open; |
| |
| unsigned int dpkts_pending_atdmux; |
| }; |
| |
| /* Data SMD channel */ |
| struct rmnet_mux_smd_info { |
| struct smd_channel *ch; |
| struct tasklet_struct tx_tlet; |
| struct tasklet_struct rx_tlet; |
| #define RMNET_MUX_CH_OPENED 0 |
| unsigned long flags; |
| /* pending rx packet length */ |
| atomic_t rx_pkt; |
| /* wait for smd open event*/ |
| wait_queue_head_t wait; |
| }; |
| |
| struct rmnet_mux_smd_dev { |
| /* Tx/Rx lists */ |
| struct list_head tx_idle; |
| struct list_head rx_idle; |
| struct list_head rx_queue; |
| |
| struct rmnet_mux_smd_info smd_data; |
| }; |
| |
| struct rmnet_mux_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; |
| |
| struct rmnet_mux_smd_dev smd_dev; |
| struct rmnet_mux_sdio_dev sdio_dev; |
| struct rmnet_mux_ctrl_dev ctrl_dev; |
| |
| u8 ifc_id; |
| enum transport_type xport; |
| spinlock_t lock; |
| atomic_t online; |
| atomic_t notify_count; |
| struct workqueue_struct *wq; |
| struct work_struct disconnect_work; |
| |
| /* pkt counters */ |
| unsigned long dpkts_tomsm; |
| unsigned long dpkts_tomdm; |
| unsigned long dpkts_tolaptop; |
| unsigned long tx_drp_cnt; |
| unsigned long cpkts_tolaptop; |
| unsigned long cpkts_tomdm; |
| unsigned long cpkts_drp_cnt; |
| }; |
| |
| static struct rmnet_mux_dev *rmux_dev; |
| |
| static struct usb_interface_descriptor rmnet_mux_interface_desc = { |
| .bLength = USB_DT_INTERFACE_SIZE, |
| .bDescriptorType = USB_DT_INTERFACE, |
| .bNumEndpoints = 3, |
| .bInterfaceClass = USB_CLASS_VENDOR_SPEC, |
| .bInterfaceSubClass = USB_CLASS_VENDOR_SPEC, |
| .bInterfaceProtocol = USB_CLASS_VENDOR_SPEC, |
| }; |
| |
| /* Full speed support */ |
| static struct usb_endpoint_descriptor rmnet_mux_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_MUX_SDIO_MAX_NFY_SZE), |
| .bInterval = 1 << RMNET_MUX_SDIO_NOTIFY_INTERVAL, |
| }; |
| |
| static struct usb_endpoint_descriptor rmnet_mux_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_mux_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_mux_fs_function[] = { |
| (struct usb_descriptor_header *) &rmnet_mux_interface_desc, |
| (struct usb_descriptor_header *) &rmnet_mux_fs_notify_desc, |
| (struct usb_descriptor_header *) &rmnet_mux_fs_in_desc, |
| (struct usb_descriptor_header *) &rmnet_mux_fs_out_desc, |
| NULL, |
| }; |
| |
| /* High speed support */ |
| static struct usb_endpoint_descriptor rmnet_mux_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_MUX_SDIO_MAX_NFY_SZE), |
| .bInterval = RMNET_MUX_SDIO_NOTIFY_INTERVAL + 4, |
| }; |
| |
| static struct usb_endpoint_descriptor rmnet_mux_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_mux_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_mux_hs_function[] = { |
| (struct usb_descriptor_header *) &rmnet_mux_interface_desc, |
| (struct usb_descriptor_header *) &rmnet_mux_hs_notify_desc, |
| (struct usb_descriptor_header *) &rmnet_mux_hs_in_desc, |
| (struct usb_descriptor_header *) &rmnet_mux_hs_out_desc, |
| NULL, |
| }; |
| |
| /* String descriptors */ |
| |
| static struct usb_string rmnet_mux_string_defs[] = { |
| [0].s = "RmNet", |
| { } /* end of list */ |
| }; |
| |
| static struct usb_gadget_strings rmnet_mux_string_table = { |
| .language = 0x0409, /* en-us */ |
| .strings = rmnet_mux_string_defs, |
| }; |
| |
| static struct usb_gadget_strings *rmnet_mux_strings[] = { |
| &rmnet_mux_string_table, |
| NULL, |
| }; |
| |
| static struct rmnet_mux_ctrl_pkt *rmnet_mux_alloc_ctrl_pkt(unsigned len, |
| gfp_t flags) |
| { |
| struct rmnet_mux_ctrl_pkt *cpkt; |
| |
| cpkt = kzalloc(sizeof(struct rmnet_mux_ctrl_pkt), flags); |
| if (!cpkt) |
| return 0; |
| |
| cpkt->buf = kzalloc(len, flags); |
| if (!cpkt->buf) { |
| kfree(cpkt); |
| return 0; |
| } |
| |
| cpkt->len = len; |
| |
| return cpkt; |
| |
| } |
| |
| static void rmnet_mux_free_ctrl_pkt(struct rmnet_mux_ctrl_pkt *cpkt) |
| { |
| kfree(cpkt->buf); |
| kfree(cpkt); |
| } |
| |
| /* |
| * 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_mux_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_mux_free_req(struct usb_ep *ep, struct usb_request *req) |
| { |
| kfree(req->buf); |
| usb_ep_free_request(ep, req); |
| } |
| |
| static int rmnet_mux_sdio_rx_submit(struct rmnet_mux_dev *dev, |
| struct usb_request *req, gfp_t gfp_flags) |
| { |
| struct sk_buff *skb; |
| int retval; |
| |
| skb = alloc_skb(RMNET_MUX_SDIO_RX_REQ_SIZE + RMNET_MUX_SDIO_HDR, |
| gfp_flags); |
| if (skb == NULL) |
| return -ENOMEM; |
| skb_reserve(skb, RMNET_MUX_SDIO_HDR); |
| |
| req->buf = skb->data; |
| req->length = RMNET_MUX_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_mux_sdio_start_rx(struct rmnet_mux_dev *dev) |
| { |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| struct usb_composite_dev *cdev = dev->cdev; |
| int status; |
| struct usb_request *req; |
| struct list_head *pool; |
| unsigned long flags; |
| |
| if (!atomic_read(&dev->online)) { |
| pr_debug("%s: USB not connected\n", __func__); |
| return; |
| } |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| pool = &sdio_dev->rx_idle; |
| while (!list_empty(pool)) { |
| req = list_first_entry(pool, struct usb_request, list); |
| list_del(&req->list); |
| |
| spin_unlock_irqrestore(&dev->lock, flags); |
| status = rmnet_mux_sdio_rx_submit(dev, req, GFP_KERNEL); |
| spin_lock_irqsave(&dev->lock, flags); |
| |
| if (status) { |
| ERROR(cdev, "rmnet_mux data rx enqueue err %d\n", |
| status); |
| list_add_tail(&req->list, &sdio_dev->rx_idle); |
| break; |
| } |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| static void rmnet_mux_sdio_start_tx(struct rmnet_mux_dev *dev) |
| { |
| unsigned long flags; |
| int status; |
| struct sk_buff *skb; |
| struct usb_request *req; |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| struct usb_composite_dev *cdev = dev->cdev; |
| |
| |
| if (!atomic_read(&dev->online)) |
| return; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| while (!list_empty(&sdio_dev->tx_idle)) { |
| skb = __skb_dequeue(&sdio_dev->tx_skb_queue); |
| if (!skb) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| return; |
| } |
| |
| req = list_first_entry(&sdio_dev->tx_idle, |
| struct usb_request, list); |
| req->context = skb; |
| req->buf = skb->data; |
| req->length = skb->len; |
| |
| list_del(&req->list); |
| 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, &sdio_dev->tx_idle); |
| __skb_queue_head(&sdio_dev->tx_skb_queue, skb); |
| } else { |
| req->buf = 0; |
| rmnet_mux_free_req(dev->epin, req); |
| dev_kfree_skb_any(skb); |
| } |
| break; |
| } |
| dev->dpkts_tolaptop++; |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| static void rmnet_mux_sdio_data_receive_cb(void *priv, struct sk_buff *skb) |
| { |
| struct rmnet_mux_dev *dev = priv; |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| unsigned long flags; |
| |
| if (!skb) |
| return; |
| if (!atomic_read(&dev->online)) { |
| dev_kfree_skb_any(skb); |
| return; |
| } |
| spin_lock_irqsave(&dev->lock, flags); |
| if (sdio_dev->tx_skb_queue.qlen > mux_sdio_tx_pkt_drop_thld) { |
| pr_err_ratelimited("%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(&sdio_dev->tx_skb_queue, skb); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| rmnet_mux_sdio_start_tx(dev); |
| } |
| |
| static void rmnet_mux_sdio_data_write_done(void *priv, struct sk_buff *skb) |
| { |
| struct rmnet_mux_dev *dev = priv; |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| |
| if (!skb) |
| return; |
| |
| dev_kfree_skb_any(skb); |
| /* this function is called from |
| * sdio mux from spin_lock_irqsave |
| */ |
| spin_lock(&dev->lock); |
| sdio_dev->dpkts_pending_atdmux--; |
| |
| if (sdio_dev->dpkts_pending_atdmux >= mux_sdio_rx_fctrl_dis_thld) { |
| spin_unlock(&dev->lock); |
| return; |
| } |
| spin_unlock(&dev->lock); |
| |
| rmnet_mux_sdio_start_rx(dev); |
| } |
| |
| static void rmnet_mux_sdio_data_rx_work(struct work_struct *w) |
| { |
| struct rmnet_mux_dev *dev = container_of(w, struct rmnet_mux_dev, |
| sdio_dev.data_rx_work); |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| struct usb_composite_dev *cdev = dev->cdev; |
| |
| struct sk_buff *skb; |
| int ret; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| while ((skb = __skb_dequeue(&sdio_dev->rx_skb_queue))) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| ret = msm_sdio_dmux_write(rmnet_mux_sdio_data_ch, skb); |
| spin_lock_irqsave(&dev->lock, flags); |
| if (ret < 0) { |
| ERROR(cdev, "rmnet_mux SDIO data write failed\n"); |
| dev_kfree_skb_any(skb); |
| } else { |
| dev->dpkts_tomdm++; |
| sdio_dev->dpkts_pending_atdmux++; |
| } |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| static void |
| rmnet_mux_sdio_complete_epout(struct usb_ep *ep, struct usb_request *req) |
| { |
| struct rmnet_mux_dev *dev = ep->driver_data; |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct sk_buff *skb = req->context; |
| int status = req->status; |
| int queue = 0; |
| |
| if (dev->xport == USB_GADGET_XPORT_UNDEF) { |
| dev_kfree_skb_any(skb); |
| req->buf = 0; |
| rmnet_mux_free_req(ep, req); |
| return; |
| } |
| |
| 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_mux_free_req(ep, req); |
| return; |
| default: |
| /* unexpected failure */ |
| ERROR(cdev, "RMNET_MUX %s response error %d, %d/%d\n", |
| ep->name, status, |
| req->actual, req->length); |
| dev_kfree_skb_any(skb); |
| break; |
| } |
| |
| spin_lock(&dev->lock); |
| if (queue) { |
| __skb_queue_tail(&sdio_dev->rx_skb_queue, skb); |
| queue_work(dev->wq, &sdio_dev->data_rx_work); |
| } |
| |
| if (sdio_dev->dpkts_pending_atdmux >= mux_sdio_rx_fctrl_en_thld) { |
| list_add_tail(&req->list, &sdio_dev->rx_idle); |
| spin_unlock(&dev->lock); |
| return; |
| } |
| spin_unlock(&dev->lock); |
| |
| status = rmnet_mux_sdio_rx_submit(dev, req, GFP_ATOMIC); |
| if (status) { |
| ERROR(cdev, "rmnet_mux data rx enqueue err %d\n", status); |
| list_add_tail(&req->list, &sdio_dev->rx_idle); |
| } |
| } |
| |
| static void |
| rmnet_mux_sdio_complete_epin(struct usb_ep *ep, struct usb_request *req) |
| { |
| struct rmnet_mux_dev *dev = ep->driver_data; |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| struct sk_buff *skb = req->context; |
| struct usb_composite_dev *cdev = dev->cdev; |
| int status = req->status; |
| |
| if (dev->xport == USB_GADGET_XPORT_UNDEF) { |
| dev_kfree_skb_any(skb); |
| req->buf = 0; |
| rmnet_mux_free_req(ep, req); |
| return; |
| } |
| |
| switch (status) { |
| case 0: |
| /* successful completion */ |
| case -ECONNRESET: |
| case -ESHUTDOWN: |
| /* connection gone */ |
| break; |
| default: |
| ERROR(cdev, "rmnet_mux data tx ep error %d\n", status); |
| break; |
| } |
| |
| spin_lock(&dev->lock); |
| list_add_tail(&req->list, &sdio_dev->tx_idle); |
| spin_unlock(&dev->lock); |
| dev_kfree_skb_any(skb); |
| |
| rmnet_mux_sdio_start_tx(dev); |
| } |
| |
| static int rmnet_mux_sdio_enable(struct rmnet_mux_dev *dev) |
| { |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| int i; |
| struct usb_request *req; |
| |
| /* |
| * If the memory allocation fails, all the allocated |
| * requests will be freed upon cable disconnect. |
| */ |
| for (i = 0; i < RMNET_MUX_SDIO_RX_REQ_MAX; i++) { |
| req = rmnet_mux_alloc_req(dev->epout, 0, GFP_KERNEL); |
| if (IS_ERR(req)) |
| return PTR_ERR(req); |
| req->complete = rmnet_mux_sdio_complete_epout; |
| list_add_tail(&req->list, &sdio_dev->rx_idle); |
| } |
| for (i = 0; i < RMNET_MUX_SDIO_TX_REQ_MAX; i++) { |
| req = rmnet_mux_alloc_req(dev->epin, 0, GFP_KERNEL); |
| if (IS_ERR(req)) |
| return PTR_ERR(req); |
| req->complete = rmnet_mux_sdio_complete_epin; |
| list_add_tail(&req->list, &sdio_dev->tx_idle); |
| } |
| |
| rmnet_mux_sdio_start_rx(dev); |
| return 0; |
| } |
| |
| static void rmnet_mux_smd_start_rx(struct rmnet_mux_dev *dev) |
| { |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; |
| int status; |
| struct usb_request *req; |
| struct list_head *pool = &smd_dev->rx_idle; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| while (!list_empty(pool)) { |
| req = list_entry(pool->next, struct usb_request, list); |
| list_del(&req->list); |
| |
| spin_unlock_irqrestore(&dev->lock, flags); |
| status = usb_ep_queue(dev->epout, 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, pool); |
| break; |
| } |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| static void rmnet_mux_smd_data_tx_tlet(unsigned long arg) |
| { |
| struct rmnet_mux_dev *dev = (struct rmnet_mux_dev *) arg; |
| struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct usb_request *req; |
| int status; |
| int sz; |
| unsigned long flags; |
| |
| while (1) { |
| if (!atomic_read(&dev->online)) |
| break; |
| sz = smd_cur_packet_size(smd_dev->smd_data.ch); |
| if (sz == 0) |
| break; |
| if (smd_read_avail(smd_dev->smd_data.ch) < sz) |
| break; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| if (list_empty(&smd_dev->tx_idle)) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| DBG(cdev, "rmnet_mux data Tx buffers full\n"); |
| break; |
| } |
| req = list_first_entry(&smd_dev->tx_idle, |
| struct usb_request, list); |
| list_del(&req->list); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| req->length = smd_read(smd_dev->smd_data.ch, req->buf, sz); |
| status = usb_ep_queue(dev->epin, req, GFP_ATOMIC); |
| if (status) { |
| ERROR(cdev, "rmnet tx data enqueue err %d\n", status); |
| spin_lock_irqsave(&dev->lock, flags); |
| list_add_tail(&req->list, &smd_dev->tx_idle); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| break; |
| } |
| dev->dpkts_tolaptop++; |
| } |
| |
| } |
| |
| static void rmnet_mux_smd_data_rx_tlet(unsigned long arg) |
| { |
| struct rmnet_mux_dev *dev = (struct rmnet_mux_dev *) arg; |
| struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct usb_request *req; |
| int ret; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| while (1) { |
| if (!atomic_read(&dev->online)) |
| break; |
| if (list_empty(&smd_dev->rx_queue)) { |
| atomic_set(&smd_dev->smd_data.rx_pkt, 0); |
| break; |
| } |
| req = list_first_entry(&smd_dev->rx_queue, |
| struct usb_request, list); |
| if (smd_write_avail(smd_dev->smd_data.ch) < req->actual) { |
| atomic_set(&smd_dev->smd_data.rx_pkt, req->actual); |
| DBG(cdev, "rmnet_mux SMD data channel full\n"); |
| break; |
| } |
| |
| list_del(&req->list); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| ret = smd_write(smd_dev->smd_data.ch, req->buf, req->actual); |
| spin_lock_irqsave(&dev->lock, flags); |
| if (ret != req->actual) { |
| ERROR(cdev, "rmnet_mux SMD data write failed\n"); |
| break; |
| } |
| dev->dpkts_tomsm++; |
| list_add_tail(&req->list, &smd_dev->rx_idle); |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| /* We have free rx data requests. */ |
| rmnet_mux_smd_start_rx(dev); |
| } |
| |
| /* If SMD has enough room to accommodate a data rx packet, |
| * write into SMD directly. Otherwise enqueue to rx_queue. |
| * We will not write into SMD directly untill rx_queue is |
| * empty to strictly follow the ordering requests. |
| */ |
| static void |
| rmnet_mux_smd_complete_epout(struct usb_ep *ep, struct usb_request *req) |
| { |
| struct rmnet_mux_dev *dev = req->context; |
| struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; |
| struct usb_composite_dev *cdev = dev->cdev; |
| int status = req->status; |
| int ret; |
| |
| if (dev->xport == USB_GADGET_XPORT_UNDEF) { |
| rmnet_mux_free_req(ep, req); |
| return; |
| } |
| |
| switch (status) { |
| case 0: |
| /* normal completion */ |
| break; |
| case -ECONNRESET: |
| case -ESHUTDOWN: |
| /* connection gone */ |
| spin_lock(&dev->lock); |
| list_add_tail(&req->list, &smd_dev->rx_idle); |
| spin_unlock(&dev->lock); |
| return; |
| default: |
| /* unexpected failure */ |
| ERROR(cdev, "RMNET_MUX %s response error %d, %d/%d\n", |
| ep->name, status, |
| req->actual, req->length); |
| spin_lock(&dev->lock); |
| list_add_tail(&req->list, &smd_dev->rx_idle); |
| spin_unlock(&dev->lock); |
| return; |
| } |
| |
| spin_lock(&dev->lock); |
| if (!atomic_read(&smd_dev->smd_data.rx_pkt)) { |
| if (smd_write_avail(smd_dev->smd_data.ch) < req->actual) { |
| atomic_set(&smd_dev->smd_data.rx_pkt, req->actual); |
| goto queue_req; |
| } |
| spin_unlock(&dev->lock); |
| ret = smd_write(smd_dev->smd_data.ch, req->buf, req->actual); |
| /* This should never happen */ |
| if (ret != req->actual) |
| ERROR(cdev, "rmnet_mux data smd write failed\n"); |
| /* Restart Rx */ |
| dev->dpkts_tomsm++; |
| spin_lock(&dev->lock); |
| list_add_tail(&req->list, &smd_dev->rx_idle); |
| spin_unlock(&dev->lock); |
| rmnet_mux_smd_start_rx(dev); |
| return; |
| } |
| queue_req: |
| list_add_tail(&req->list, &smd_dev->rx_queue); |
| spin_unlock(&dev->lock); |
| } |
| |
| static void rmnet_mux_smd_complete_epin(struct usb_ep *ep, |
| struct usb_request *req) |
| { |
| struct rmnet_mux_dev *dev = req->context; |
| struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; |
| struct usb_composite_dev *cdev = dev->cdev; |
| int status = req->status; |
| int schedule = 0; |
| |
| if (dev->xport == USB_GADGET_XPORT_UNDEF) { |
| rmnet_mux_free_req(ep, req); |
| return; |
| } |
| |
| switch (status) { |
| case -ECONNRESET: |
| case -ESHUTDOWN: |
| /* connection gone */ |
| spin_lock(&dev->lock); |
| list_add_tail(&req->list, &smd_dev->tx_idle); |
| spin_unlock(&dev->lock); |
| break; |
| default: |
| ERROR(cdev, "rmnet_mux data tx ep error %d\n", status); |
| /* FALLTHROUGH */ |
| case 0: |
| spin_lock(&dev->lock); |
| if (list_empty(&smd_dev->tx_idle)) |
| schedule = 1; |
| list_add_tail(&req->list, &smd_dev->tx_idle); |
| |
| if (schedule) |
| tasklet_schedule(&smd_dev->smd_data.tx_tlet); |
| spin_unlock(&dev->lock); |
| break; |
| } |
| |
| } |
| |
| |
| static void rmnet_mux_smd_notify(void *priv, unsigned event) |
| { |
| struct rmnet_mux_dev *dev = priv; |
| struct rmnet_mux_smd_info *smd_info = &dev->smd_dev.smd_data; |
| int len = atomic_read(&smd_info->rx_pkt); |
| |
| switch (event) { |
| case SMD_EVENT_DATA: { |
| if (!atomic_read(&dev->online)) |
| break; |
| if (len && (smd_write_avail(smd_info->ch) >= len)) |
| tasklet_schedule(&smd_info->rx_tlet); |
| |
| if (smd_read_avail(smd_info->ch)) |
| tasklet_schedule(&smd_info->tx_tlet); |
| |
| break; |
| } |
| case SMD_EVENT_OPEN: |
| /* usb endpoints are not enabled untill smd channels |
| * are opened. wake up worker thread to continue |
| * connection processing |
| */ |
| set_bit(RMNET_MUX_CH_OPENED, &smd_info->flags); |
| wake_up(&smd_info->wait); |
| break; |
| case SMD_EVENT_CLOSE: |
| /* We will never come here. |
| * reset flags after closing smd channel |
| * */ |
| clear_bit(RMNET_MUX_CH_OPENED, &smd_info->flags); |
| break; |
| } |
| } |
| |
| static int rmnet_mux_smd_enable(struct rmnet_mux_dev *dev) |
| { |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; |
| int i, ret; |
| struct usb_request *req; |
| |
| if (test_bit(RMNET_MUX_CH_OPENED, &smd_dev->smd_data.flags)) |
| goto smd_alloc_req; |
| |
| ret = smd_open(rmnet_mux_smd_data_ch, &smd_dev->smd_data.ch, |
| dev, rmnet_mux_smd_notify); |
| if (ret) { |
| ERROR(cdev, "Unable to open data smd channel\n"); |
| return ret; |
| } |
| |
| wait_event(smd_dev->smd_data.wait, test_bit(RMNET_MUX_CH_OPENED, |
| &smd_dev->smd_data.flags)); |
| |
| /* Allocate bulk in/out requests for data transfer. |
| * If the memory allocation fails, all the allocated |
| * requests will be freed upon cable disconnect. |
| */ |
| smd_alloc_req: |
| for (i = 0; i < RMNET_MUX_SMD_RX_REQ_MAX; i++) { |
| req = rmnet_mux_alloc_req(dev->epout, RMNET_MUX_SMD_RX_REQ_SIZE, |
| GFP_KERNEL); |
| if (IS_ERR(req)) |
| return PTR_ERR(req); |
| req->length = RMNET_MUX_SMD_TXN_MAX; |
| req->context = dev; |
| req->complete = rmnet_mux_smd_complete_epout; |
| list_add_tail(&req->list, &smd_dev->rx_idle); |
| } |
| |
| for (i = 0; i < RMNET_MUX_SMD_TX_REQ_MAX; i++) { |
| req = rmnet_mux_alloc_req(dev->epin, RMNET_MUX_SMD_TX_REQ_SIZE, |
| GFP_KERNEL); |
| if (IS_ERR(req)) |
| return PTR_ERR(req); |
| req->context = dev; |
| req->complete = rmnet_mux_smd_complete_epin; |
| list_add_tail(&req->list, &smd_dev->tx_idle); |
| } |
| |
| rmnet_mux_smd_start_rx(dev); |
| return 0; |
| } |
| |
| static void rmnet_mux_notify_complete(struct usb_ep *ep, |
| struct usb_request *req) |
| { |
| struct rmnet_mux_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_mux notifyep error %d\n", status); |
| /* FALLTHROUGH */ |
| case 0: |
| |
| 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 ctrl_response_available(struct rmnet_mux_dev *dev) |
| { |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct usb_request *req = dev->notify_req; |
| struct usb_cdc_notification *event = req->buf; |
| int status; |
| |
| /* Response will be sent later */ |
| if (atomic_inc_return(&dev->notify_count) != 1) |
| return; |
| |
| 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); |
| |
| status = usb_ep_queue(dev->epnotify, dev->notify_req, GFP_ATOMIC); |
| if (status < 0) { |
| atomic_dec(&dev->notify_count); |
| ERROR(cdev, "rmnet_mux notify ep enqueue error %d\n", status); |
| } |
| } |
| |
| #define MAX_CTRL_PKT_SIZE 4096 |
| |
| static void rmnet_mux_response_complete(struct usb_ep *ep, |
| struct usb_request *req) |
| { |
| struct rmnet_mux_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_mux %s response error %d, %d/%d\n", |
| ep->name, req->status, |
| req->actual, req->length); |
| } |
| } |
| |
| static void rmnet_mux_command_complete(struct usb_ep *ep, |
| struct usb_request *req) |
| { |
| struct rmnet_mux_dev *dev = req->context; |
| struct usb_composite_dev *cdev = dev->cdev; |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| struct rmnet_mux_ctrl_pkt *cpkt; |
| int len = req->actual; |
| |
| if (req->status < 0) { |
| ERROR(cdev, "rmnet_mux command error %d\n", req->status); |
| return; |
| } |
| |
| cpkt = rmnet_mux_alloc_ctrl_pkt(len, GFP_ATOMIC); |
| if (!cpkt) { |
| ERROR(cdev, "unable to allocate memory for ctrl req\n"); |
| return; |
| } |
| |
| spin_lock(&dev->lock); |
| if (!ctrl_dev->opened) { |
| spin_unlock(&dev->lock); |
| rmnet_mux_free_ctrl_pkt(cpkt); |
| dev->cpkts_drp_cnt++; |
| pr_err_ratelimited( |
| "%s: ctrl pkts dropped: cpkts_drp_cnt: %lu\n", |
| __func__, dev->cpkts_drp_cnt); |
| return; |
| } |
| |
| memcpy(cpkt->buf, req->buf, len); |
| |
| list_add_tail(&cpkt->list, &ctrl_dev->tx_q); |
| ctrl_dev->tx_len++; |
| spin_unlock(&dev->lock); |
| |
| /* wakeup read thread */ |
| wake_up(&ctrl_dev->tx_wait_q); |
| } |
| |
| static int |
| rmnet_mux_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) |
| { |
| struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, |
| function); |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| 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_mux_ctrl_pkt *cpkt; |
| |
| 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_mux_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(&ctrl_dev->rx_q)) { |
| DBG(cdev, "ctrl resp queue empty" |
| " %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; |
| |
| } |
| cpkt = list_first_entry(&ctrl_dev->rx_q, |
| struct rmnet_mux_ctrl_pkt, list); |
| list_del(&cpkt->list); |
| ctrl_dev->rx_len--; |
| spin_unlock(&dev->lock); |
| |
| len = min_t(unsigned, w_length, cpkt->len); |
| memcpy(req->buf, cpkt->buf, len); |
| ret = len; |
| req->complete = rmnet_mux_response_complete; |
| req->context = dev; |
| rmnet_mux_free_ctrl_pkt(cpkt); |
| |
| dev->cpkts_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 & RMNET_MUX_ACM_CTRL_DTR) |
| ctrl_dev->cbits_to_modem |= TIOCM_DTR; |
| else |
| ctrl_dev->cbits_to_modem &= ~TIOCM_DTR; |
| |
| 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_mux 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_mux ep0 enqueue err %d\n", ret); |
| } |
| |
| return ret; |
| } |
| |
| static void rmnet_mux_free_buf(struct rmnet_mux_dev *dev) |
| { |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; |
| struct rmnet_mux_ctrl_pkt *cpkt; |
| struct usb_request *req; |
| struct list_head *pool; |
| struct sk_buff *skb; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| /* free all usb requests in SDIO tx pool */ |
| pool = &sdio_dev->tx_idle; |
| while (!list_empty(pool)) { |
| req = list_first_entry(pool, struct usb_request, list); |
| list_del(&req->list); |
| req->buf = NULL; |
| rmnet_mux_free_req(dev->epout, req); |
| } |
| |
| pool = &sdio_dev->rx_idle; |
| /* free all usb requests in SDIO rx pool */ |
| while (!list_empty(pool)) { |
| req = list_first_entry(pool, struct usb_request, list); |
| list_del(&req->list); |
| req->buf = NULL; |
| rmnet_mux_free_req(dev->epin, req); |
| } |
| |
| while ((skb = __skb_dequeue(&sdio_dev->tx_skb_queue))) |
| dev_kfree_skb_any(skb); |
| |
| while ((skb = __skb_dequeue(&sdio_dev->rx_skb_queue))) |
| dev_kfree_skb_any(skb); |
| |
| /* free all usb requests in SMD tx pool */ |
| pool = &smd_dev->tx_idle; |
| while (!list_empty(pool)) { |
| req = list_first_entry(pool, struct usb_request, list); |
| list_del(&req->list); |
| rmnet_mux_free_req(dev->epout, req); |
| } |
| |
| pool = &smd_dev->rx_idle; |
| /* free all usb requests in SMD rx pool */ |
| while (!list_empty(pool)) { |
| req = list_first_entry(pool, struct usb_request, list); |
| list_del(&req->list); |
| rmnet_mux_free_req(dev->epin, req); |
| } |
| |
| /* free all usb requests in SMD rx queue */ |
| pool = &smd_dev->rx_queue; |
| while (!list_empty(pool)) { |
| req = list_first_entry(pool, struct usb_request, list); |
| list_del(&req->list); |
| rmnet_mux_free_req(dev->epin, req); |
| } |
| |
| pool = &ctrl_dev->tx_q; |
| while (!list_empty(pool)) { |
| cpkt = list_first_entry(pool, struct rmnet_mux_ctrl_pkt, list); |
| list_del(&cpkt->list); |
| rmnet_mux_free_ctrl_pkt(cpkt); |
| ctrl_dev->tx_len--; |
| } |
| |
| pool = &ctrl_dev->rx_q; |
| while (!list_empty(pool)) { |
| cpkt = list_first_entry(pool, struct rmnet_mux_ctrl_pkt, list); |
| list_del(&cpkt->list); |
| rmnet_mux_free_ctrl_pkt(cpkt); |
| ctrl_dev->rx_len--; |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| static void rmnet_mux_disconnect_work(struct work_struct *w) |
| { |
| struct rmnet_mux_dev *dev = container_of(w, struct rmnet_mux_dev, |
| disconnect_work); |
| struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| |
| if (dev->xport == USB_GADGET_XPORT_SMD) { |
| tasklet_kill(&smd_dev->smd_data.rx_tlet); |
| tasklet_kill(&smd_dev->smd_data.tx_tlet); |
| } |
| |
| rmnet_mux_free_buf(dev); |
| dev->xport = 0; |
| |
| /* wakeup read thread */ |
| wake_up(&ctrl_dev->tx_wait_q); |
| } |
| |
| static void rmnet_mux_suspend(struct usb_function *f) |
| { |
| struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, |
| function); |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| |
| 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 being done, Hence exclusively dropping the DTR |
| * from function driver suspend. |
| */ |
| ctrl_dev->cbits_to_modem &= ~TIOCM_DTR; |
| } |
| |
| static void rmnet_mux_disable(struct usb_function *f) |
| { |
| struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, |
| function); |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| |
| if (!atomic_read(&dev->online)) |
| return; |
| |
| atomic_set(&dev->online, 0); |
| |
| usb_ep_fifo_flush(dev->epnotify); |
| usb_ep_disable(dev->epnotify); |
| rmnet_mux_free_req(dev->epnotify, dev->notify_req); |
| |
| usb_ep_fifo_flush(dev->epout); |
| usb_ep_disable(dev->epout); |
| |
| usb_ep_fifo_flush(dev->epin); |
| usb_ep_disable(dev->epin); |
| |
| /* cleanup work */ |
| ctrl_dev->cbits_to_modem = 0; |
| queue_work(dev->wq, &dev->disconnect_work); |
| } |
| |
| #define SDIO_OPEN_RETRY_DELAY msecs_to_jiffies(2000) |
| #define SDIO_OPEN_MAX_RETRY 90 |
| static void rmnet_mux_open_sdio_work(struct work_struct *w) |
| { |
| struct rmnet_mux_dev *dev = |
| container_of(w, struct rmnet_mux_dev, sdio_dev.open_work.work); |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| struct usb_composite_dev *cdev = dev->cdev; |
| int ret; |
| static int retry_cnt; |
| |
| /* Data channel for network packets */ |
| ret = msm_sdio_dmux_open(rmnet_mux_sdio_data_ch, dev, |
| rmnet_mux_sdio_data_receive_cb, |
| rmnet_mux_sdio_data_write_done); |
| if (ret) { |
| if (retry_cnt > SDIO_OPEN_MAX_RETRY) { |
| ERROR(cdev, "Unable to open SDIO DATA channel\n"); |
| return; |
| } |
| retry_cnt++; |
| queue_delayed_work(dev->wq, &sdio_dev->open_work, |
| SDIO_OPEN_RETRY_DELAY); |
| return; |
| } |
| |
| |
| atomic_set(&sdio_dev->sdio_open, 1); |
| pr_info("%s: usb rmnet_mux sdio channels are open retry_cnt:%d\n", |
| __func__, retry_cnt); |
| retry_cnt = 0; |
| return; |
| } |
| |
| static int rmnet_mux_set_alt(struct usb_function *f, |
| unsigned intf, unsigned alt) |
| { |
| struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, |
| function); |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| struct usb_composite_dev *cdev = dev->cdev; |
| int ret = 0; |
| |
| /* allocate notification */ |
| dev->notify_req = rmnet_mux_alloc_req(dev->epnotify, |
| RMNET_MUX_SDIO_MAX_NFY_SZE, GFP_ATOMIC); |
| |
| if (IS_ERR(dev->notify_req)) |
| return PTR_ERR(dev->notify_req); |
| |
| dev->notify_req->complete = rmnet_mux_notify_complete; |
| dev->notify_req->context = dev; |
| dev->notify_req->length = RMNET_MUX_SDIO_MAX_NFY_SZE; |
| |
| /* 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; |
| } |
| |
| dev->dpkts_tolaptop = 0; |
| dev->cpkts_tolaptop = 0; |
| dev->cpkts_tomdm = 0; |
| dev->dpkts_tomdm = 0; |
| dev->dpkts_tomsm = 0; |
| dev->tx_drp_cnt = 0; |
| dev->cpkts_drp_cnt = 0; |
| sdio_dev->dpkts_pending_atdmux = 0; |
| atomic_set(&dev->online, 1); |
| |
| return 0; |
| } |
| |
| static ssize_t transport_store( |
| struct device *device, struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| struct rmnet_mux_dev *dev = rmux_dev; |
| int value; |
| enum transport_type given_xport; |
| enum transport_type t; |
| struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| struct list_head *pool; |
| struct sk_buff_head *skb_pool; |
| struct sk_buff *skb; |
| struct usb_request *req; |
| unsigned long flags; |
| |
| if (!atomic_read(&dev->online)) { |
| pr_err("%s: usb cable is not connected\n", __func__); |
| return -EINVAL; |
| } |
| |
| sscanf(buf, "%d", &value); |
| if (value) |
| given_xport = USB_GADGET_XPORT_SDIO; |
| else |
| given_xport = USB_GADGET_XPORT_SMD; |
| |
| if (given_xport == dev->xport) { |
| pr_err("%s: given_xport:%s cur_xport:%s doing nothing\n", |
| __func__, xport_to_str(given_xport), |
| xport_to_str(dev->xport)); |
| return 0; |
| } |
| |
| pr_debug("usb_rmnet_mux: TransportRequested: %s\n", |
| xport_to_str(given_xport)); |
| |
| /* prevent any other pkts to/from usb */ |
| t = dev->xport; |
| dev->xport = USB_GADGET_XPORT_UNDEF; |
| if (t != USB_GADGET_XPORT_UNDEF) { |
| usb_ep_fifo_flush(dev->epin); |
| usb_ep_fifo_flush(dev->epout); |
| } |
| |
| switch (t) { |
| case USB_GADGET_XPORT_SDIO: |
| spin_lock_irqsave(&dev->lock, flags); |
| /* tx_idle */ |
| |
| sdio_dev->dpkts_pending_atdmux = 0; |
| |
| pool = &sdio_dev->tx_idle; |
| while (!list_empty(pool)) { |
| req = list_first_entry(pool, struct usb_request, list); |
| list_del(&req->list); |
| req->buf = NULL; |
| rmnet_mux_free_req(dev->epout, req); |
| } |
| |
| /* rx_idle */ |
| pool = &sdio_dev->rx_idle; |
| /* free all usb requests in SDIO rx pool */ |
| while (!list_empty(pool)) { |
| req = list_first_entry(pool, struct usb_request, list); |
| list_del(&req->list); |
| req->buf = NULL; |
| rmnet_mux_free_req(dev->epin, req); |
| } |
| |
| /* tx_skb_queue */ |
| skb_pool = &sdio_dev->tx_skb_queue; |
| while ((skb = __skb_dequeue(skb_pool))) |
| dev_kfree_skb_any(skb); |
| /* rx_skb_queue */ |
| skb_pool = &sdio_dev->rx_skb_queue; |
| while ((skb = __skb_dequeue(skb_pool))) |
| dev_kfree_skb_any(skb); |
| |
| spin_unlock_irqrestore(&dev->lock, flags); |
| break; |
| case USB_GADGET_XPORT_SMD: |
| /* close smd xport */ |
| tasklet_kill(&smd_dev->smd_data.rx_tlet); |
| tasklet_kill(&smd_dev->smd_data.tx_tlet); |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| /* free all usb requests in SMD tx pool */ |
| pool = &smd_dev->tx_idle; |
| while (!list_empty(pool)) { |
| req = list_first_entry(pool, struct usb_request, list); |
| list_del(&req->list); |
| rmnet_mux_free_req(dev->epout, req); |
| } |
| |
| pool = &smd_dev->rx_idle; |
| /* free all usb requests in SMD rx pool */ |
| while (!list_empty(pool)) { |
| req = list_first_entry(pool, struct usb_request, list); |
| list_del(&req->list); |
| rmnet_mux_free_req(dev->epin, req); |
| } |
| |
| /* free all usb requests in SMD rx queue */ |
| pool = &smd_dev->rx_queue; |
| while (!list_empty(pool)) { |
| req = list_first_entry(pool, struct usb_request, list); |
| list_del(&req->list); |
| rmnet_mux_free_req(dev->epin, req); |
| } |
| |
| spin_unlock_irqrestore(&dev->lock, flags); |
| break; |
| default: |
| pr_debug("%s: undefined xport, do nothing\n", __func__); |
| } |
| |
| dev->xport = given_xport; |
| |
| switch (dev->xport) { |
| case USB_GADGET_XPORT_SDIO: |
| rmnet_mux_sdio_enable(dev); |
| break; |
| case USB_GADGET_XPORT_SMD: |
| rmnet_mux_smd_enable(dev); |
| break; |
| default: |
| /* we should never come here */ |
| pr_err("%s: undefined transport\n", __func__); |
| } |
| |
| return size; |
| } |
| static DEVICE_ATTR(transport, S_IRUGO | S_IWUSR, NULL, transport_store); |
| |
| static int rmnet_mux_bind(struct usb_configuration *c, struct usb_function *f) |
| { |
| struct usb_composite_dev *cdev = c->cdev; |
| struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, |
| function); |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| 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_mux_interface_desc.bInterfaceNumber = id; |
| |
| ep = usb_ep_autoconfig(cdev->gadget, &rmnet_mux_fs_in_desc); |
| if (!ep) |
| goto out; |
| ep->driver_data = cdev; /* claim endpoint */ |
| dev->epin = ep; |
| |
| ep = usb_ep_autoconfig(cdev->gadget, &rmnet_mux_fs_out_desc); |
| if (!ep) |
| goto out; |
| ep->driver_data = cdev; /* claim endpoint */ |
| dev->epout = ep; |
| |
| ep = usb_ep_autoconfig(cdev->gadget, &rmnet_mux_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_mux_hs_in_desc.bEndpointAddress = |
| rmnet_mux_fs_in_desc.bEndpointAddress; |
| rmnet_mux_hs_out_desc.bEndpointAddress = |
| rmnet_mux_fs_out_desc.bEndpointAddress; |
| rmnet_mux_hs_notify_desc.bEndpointAddress = |
| rmnet_mux_fs_notify_desc.bEndpointAddress; |
| } |
| |
| queue_delayed_work(dev->wq, &sdio_dev->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_mux_smd_init(struct rmnet_mux_smd_dev *smd_dev) |
| { |
| struct rmnet_mux_dev *dev = container_of(smd_dev, |
| struct rmnet_mux_dev, smd_dev); |
| |
| atomic_set(&smd_dev->smd_data.rx_pkt, 0); |
| tasklet_init(&smd_dev->smd_data.rx_tlet, rmnet_mux_smd_data_rx_tlet, |
| (unsigned long) dev); |
| tasklet_init(&smd_dev->smd_data.tx_tlet, rmnet_mux_smd_data_tx_tlet, |
| (unsigned long) dev); |
| |
| init_waitqueue_head(&smd_dev->smd_data.wait); |
| |
| INIT_LIST_HEAD(&smd_dev->rx_idle); |
| INIT_LIST_HEAD(&smd_dev->rx_queue); |
| INIT_LIST_HEAD(&smd_dev->tx_idle); |
| } |
| |
| static void rmnet_mux_sdio_init(struct rmnet_mux_sdio_dev *sdio_dev) |
| { |
| INIT_WORK(&sdio_dev->data_rx_work, rmnet_mux_sdio_data_rx_work); |
| |
| INIT_DELAYED_WORK(&sdio_dev->open_work, rmnet_mux_open_sdio_work); |
| |
| INIT_LIST_HEAD(&sdio_dev->rx_idle); |
| INIT_LIST_HEAD(&sdio_dev->tx_idle); |
| skb_queue_head_init(&sdio_dev->tx_skb_queue); |
| skb_queue_head_init(&sdio_dev->rx_skb_queue); |
| } |
| |
| static void |
| rmnet_mux_unbind(struct usb_configuration *c, struct usb_function *f) |
| { |
| struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, |
| function); |
| struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; |
| |
| smd_dev->smd_data.flags = 0; |
| } |
| |
| #if defined(CONFIG_DEBUG_FS) |
| #define DEBUG_BUF_SIZE 1024 |
| static ssize_t rmnet_mux_read_stats(struct file *file, char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| struct rmnet_mux_dev *dev = file->private_data; |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| char *debug_buf; |
| unsigned long flags; |
| int ret; |
| |
| debug_buf = kmalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); |
| if (!debug_buf) |
| return -ENOMEM; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| ret = scnprintf(debug_buf, DEBUG_BUF_SIZE, |
| "dpkts_tomsm: %lu\n" |
| "dpkts_tomdm: %lu\n" |
| "cpkts_tomdm: %lu\n" |
| "dpkts_tolaptop: %lu\n" |
| "cpkts_tolaptop: %lu\n" |
| "cbits_to_modem: %lu\n" |
| "tx skb size: %u\n" |
| "rx_skb_size: %u\n" |
| "dpkts_pending_at_dmux: %u\n" |
| "tx drp cnt: %lu\n" |
| "cpkts_drp_cnt: %lu\n" |
| "cpkt_tx_qlen: %lu\n" |
| "cpkt_rx_qlen_to_modem: %lu\n" |
| "xport: %s\n" |
| "ctr_ch_opened: %d\n", |
| dev->dpkts_tomsm, dev->dpkts_tomdm, |
| dev->cpkts_tomdm, dev->dpkts_tolaptop, |
| dev->cpkts_tolaptop, ctrl_dev->cbits_to_modem, |
| sdio_dev->tx_skb_queue.qlen, |
| sdio_dev->rx_skb_queue.qlen, |
| sdio_dev->dpkts_pending_atdmux, dev->tx_drp_cnt, |
| dev->cpkts_drp_cnt, |
| ctrl_dev->tx_len, ctrl_dev->rx_len, |
| xport_to_str(dev->xport), ctrl_dev->opened); |
| |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| ret = simple_read_from_buffer(ubuf, count, ppos, debug_buf, ret); |
| |
| kfree(debug_buf); |
| |
| return ret; |
| } |
| |
| static ssize_t rmnet_mux_reset_stats(struct file *file, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| struct rmnet_mux_dev *dev = file->private_data; |
| struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; |
| |
| dev->dpkts_tolaptop = 0; |
| dev->cpkts_tolaptop = 0; |
| dev->cpkts_tomdm = 0; |
| dev->dpkts_tomdm = 0; |
| dev->dpkts_tomsm = 0; |
| sdio_dev->dpkts_pending_atdmux = 0; |
| dev->tx_drp_cnt = 0; |
| dev->cpkts_drp_cnt = 0; |
| return count; |
| } |
| |
| static int dbg_rmnet_mux_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = inode->i_private; |
| |
| return 0; |
| } |
| |
| const struct file_operations rmnet_mux_svlte_debug_stats_ops = { |
| .open = dbg_rmnet_mux_open, |
| .read = rmnet_mux_read_stats, |
| .write = rmnet_mux_reset_stats, |
| }; |
| |
| struct dentry *dent_rmnet_mux; |
| |
| static void rmnet_mux_debugfs_init(struct rmnet_mux_dev *dev) |
| { |
| |
| dent_rmnet_mux = debugfs_create_dir("usb_rmnet_mux", 0); |
| if (IS_ERR(dent_rmnet_mux)) |
| return; |
| |
| debugfs_create_file("status", 0444, dent_rmnet_mux, dev, |
| &rmnet_mux_svlte_debug_stats_ops); |
| } |
| #else |
| static void rmnet_mux_debugfs_init(struct rmnet_mux_dev *dev) {} |
| #endif |
| |
| int usb_rmnet_mux_ctrl_open(struct inode *inode, struct file *fp) |
| { |
| struct rmnet_mux_dev *dev = rmux_dev; |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| if (ctrl_dev->opened) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| pr_err("%s: device is already opened\n", __func__); |
| return -EBUSY; |
| } |
| |
| ctrl_dev->opened = 1; |
| fp->private_data = dev; |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| return 0; |
| } |
| |
| |
| int usb_rmnet_mux_ctrl_release(struct inode *inode, struct file *fp) |
| { |
| struct rmnet_mux_dev *dev = fp->private_data; |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| ctrl_dev->opened = 0; |
| fp->private_data = 0; |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| return 0; |
| } |
| |
| ssize_t usb_rmnet_mux_ctrl_read(struct file *fp, |
| char __user *buf, |
| size_t count, |
| loff_t *ppos) |
| { |
| struct rmnet_mux_dev *dev = fp->private_data; |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| struct rmnet_mux_ctrl_pkt *cpkt; |
| unsigned long flags; |
| int ret = 0; |
| |
| ctrl_read: |
| if (!atomic_read(&dev->online)) { |
| pr_debug("%s: USB cable not connected\n", __func__); |
| return -ENODEV; |
| } |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| if (list_empty(&ctrl_dev->tx_q)) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| /* Implement sleep and wakeup here */ |
| ret = wait_event_interruptible(ctrl_dev->tx_wait_q, |
| !list_empty(&ctrl_dev->tx_q) || |
| !atomic_read(&dev->online)); |
| if (ret < 0) |
| return ret; |
| |
| goto ctrl_read; |
| } |
| |
| cpkt = list_first_entry(&ctrl_dev->tx_q, struct rmnet_mux_ctrl_pkt, |
| list); |
| if (cpkt->len > count) { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| pr_err("%s: cpkt size:%d > buf size:%d\n", |
| __func__, cpkt->len, count); |
| return -ENOMEM; |
| } |
| list_del(&cpkt->list); |
| ctrl_dev->tx_len--; |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| count = cpkt->len; |
| |
| ret = copy_to_user(buf, cpkt->buf, count); |
| dev->cpkts_tomdm++; |
| |
| rmnet_mux_free_ctrl_pkt(cpkt); |
| |
| if (ret) |
| return ret; |
| |
| return count; |
| } |
| |
| ssize_t usb_rmnet_mux_ctrl_write(struct file *fp, |
| const char __user *buf, |
| size_t count, |
| loff_t *ppos) |
| { |
| struct rmnet_mux_dev *dev = fp->private_data; |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| struct rmnet_mux_ctrl_pkt *cpkt; |
| unsigned long flags; |
| int ret = 0; |
| |
| if (!atomic_read(&dev->online)) { |
| pr_debug("%s: USB cable not connected\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (!count) { |
| pr_err("%s: zero length ctrl pkt\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (count > MAX_CTRL_PKT_SIZE) { |
| pr_err("%s: max_pkt_size:%d given_pkt_size:%d\n", |
| __func__, MAX_CTRL_PKT_SIZE, count); |
| return -ENOMEM; |
| } |
| |
| cpkt = rmnet_mux_alloc_ctrl_pkt(count, GFP_KERNEL); |
| if (!cpkt) { |
| pr_err("%s: cannot allocate rmnet_mux ctrl pkt\n", __func__); |
| return -ENOMEM; |
| } |
| |
| ret = copy_from_user(cpkt->buf, buf, count); |
| if (ret) { |
| pr_err("%s: copy_from_user failed err:%d\n", |
| __func__, ret); |
| rmnet_mux_free_ctrl_pkt(cpkt); |
| return ret; |
| } |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| ctrl_dev->rx_len++; |
| list_add(&cpkt->list, &ctrl_dev->rx_q); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| ctrl_response_available(dev); |
| |
| return count; |
| } |
| |
| |
| #define RMNET_MUX_CTRL_GET_DTR _IOR(0xFE, 0, int) |
| static long |
| usb_rmnet_mux_ctrl_ioctl(struct file *fp, unsigned c, unsigned long value) |
| { |
| struct rmnet_mux_dev *dev = fp->private_data; |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| unsigned long *temp = (unsigned long *)value; |
| int ret = 0; |
| |
| if (c != RMNET_MUX_CTRL_GET_DTR) |
| return -ENODEV; |
| |
| ret = copy_to_user(temp, |
| &ctrl_dev->cbits_to_modem, |
| sizeof(*temp)); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static const struct file_operations rmnet_mux_ctrl_fops = { |
| .owner = THIS_MODULE, |
| .open = usb_rmnet_mux_ctrl_open, |
| .release = usb_rmnet_mux_ctrl_release, |
| .read = usb_rmnet_mux_ctrl_read, |
| .write = usb_rmnet_mux_ctrl_write, |
| .unlocked_ioctl = usb_rmnet_mux_ctrl_ioctl, |
| }; |
| |
| static struct miscdevice rmnet_mux_ctrl_dev = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "rmnet_mux_ctrl", |
| .fops = &rmnet_mux_ctrl_fops, |
| }; |
| |
| static int rmnet_mux_ctrl_device_init(struct rmnet_mux_dev *dev) |
| { |
| int ret; |
| struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; |
| |
| INIT_LIST_HEAD(&ctrl_dev->tx_q); |
| INIT_LIST_HEAD(&ctrl_dev->rx_q); |
| init_waitqueue_head(&ctrl_dev->tx_wait_q); |
| |
| ret = misc_register(&rmnet_mux_ctrl_dev); |
| if (ret) { |
| pr_err("%s: failed to register misc device\n", __func__); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int rmnet_smd_sdio_function_add(struct usb_configuration *c) |
| { |
| struct rmnet_mux_dev *dev = rmux_dev; |
| |
| if (!dev) |
| return -ENODEV; |
| |
| pr_debug("rmnet_smd_sdio_function_add\n"); |
| |
| dev->function.name = "rmnet_smd_sdio"; |
| dev->function.strings = rmnet_mux_strings; |
| dev->function.descriptors = rmnet_mux_fs_function; |
| dev->function.hs_descriptors = rmnet_mux_hs_function; |
| dev->function.bind = rmnet_mux_bind; |
| dev->function.unbind = rmnet_mux_unbind; |
| dev->function.setup = rmnet_mux_setup; |
| dev->function.set_alt = rmnet_mux_set_alt; |
| dev->function.disable = rmnet_mux_disable; |
| dev->function.suspend = rmnet_mux_suspend; |
| |
| return usb_add_function(c, &dev->function); |
| } |
| |
| static int rmnet_smd_sdio_init(void) |
| { |
| struct rmnet_mux_dev *dev; |
| int ret; |
| |
| dev = kzalloc(sizeof(*dev), GFP_KERNEL); |
| if (!dev) |
| return -ENOMEM; |
| |
| rmux_dev = dev; |
| |
| dev->wq = create_singlethread_workqueue("k_rmnet_mux_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_mux_disconnect_work); |
| rmnet_mux_smd_init(&dev->smd_dev); |
| rmnet_mux_sdio_init(&dev->sdio_dev); |
| |
| ret = rmnet_mux_ctrl_device_init(dev); |
| if (ret) { |
| pr_debug("%s: rmnet_mux_ctrl_device_init failed, err:%d\n", |
| __func__, ret); |
| goto free_wq; |
| } |
| |
| rmnet_mux_debugfs_init(dev); |
| |
| return 0; |
| |
| free_wq: |
| destroy_workqueue(dev->wq); |
| free_dev: |
| kfree(dev); |
| |
| return ret; |
| } |
| |
| static void rmnet_smd_sdio_cleanup(void) |
| { |
| struct rmnet_mux_dev *dev = rmux_dev; |
| struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; |
| |
| debugfs_remove_recursive(dent_rmnet_mux); |
| misc_deregister(&rmnet_mux_ctrl_dev); |
| smd_close(smd_dev->smd_data.ch); |
| destroy_workqueue(dev->wq); |
| kfree(dev); |
| } |