USB: Add USB BAM2BAM related function drivers and relevant glue drivers
This change adds MSM specific USB function drivers and relevant glue drivers providing RMNET, RNDIS, DPL, ECM and MBIM functionality with BAM2BAM IPA mode. This snapshot is taken as of msm-3.18 kernel 'commit 8007444c107a ("Merge pinctrl: qcom: Update gcc_gp1_clk_b_groups for msm8953")'. Signed-off-by: Mayank Rana <mrana@codeaurora.org>
This commit is contained in:
parent
375d7195fc
commit
f845a7f24a
13 changed files with 13295 additions and 0 deletions
2150
drivers/usb/gadget/function/f_mbim.c
Normal file
2150
drivers/usb/gadget/function/f_mbim.c
Normal file
File diff suppressed because it is too large
Load diff
1165
drivers/usb/gadget/function/f_qc_ecm.c
Normal file
1165
drivers/usb/gadget/function/f_qc_ecm.c
Normal file
File diff suppressed because it is too large
Load diff
1421
drivers/usb/gadget/function/f_qc_rndis.c
Normal file
1421
drivers/usb/gadget/function/f_qc_rndis.c
Normal file
File diff suppressed because it is too large
Load diff
1469
drivers/usb/gadget/function/f_rmnet.c
Normal file
1469
drivers/usb/gadget/function/f_rmnet.c
Normal file
File diff suppressed because it is too large
Load diff
2523
drivers/usb/gadget/function/u_bam.c
Normal file
2523
drivers/usb/gadget/function/u_bam.c
Normal file
File diff suppressed because it is too large
Load diff
2113
drivers/usb/gadget/function/u_bam_data.c
Normal file
2113
drivers/usb/gadget/function/u_bam_data.c
Normal file
File diff suppressed because it is too large
Load diff
71
drivers/usb/gadget/function/u_bam_data.h
Normal file
71
drivers/usb/gadget/function/u_bam_data.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
/* Copyright (c) 2013-2015, 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.
|
||||
*/
|
||||
|
||||
#ifndef __U_BAM_DATA_H
|
||||
#define __U_BAM_DATA_H
|
||||
|
||||
#include "usb_gadget_xport.h"
|
||||
|
||||
enum function_type {
|
||||
USB_FUNC_ECM,
|
||||
USB_FUNC_MBIM,
|
||||
USB_FUNC_RNDIS,
|
||||
USB_NUM_FUNCS,
|
||||
};
|
||||
|
||||
#define PORTS_PER_FUNC 1
|
||||
#define BAM2BAM_DATA_N_PORTS (USB_NUM_FUNCS * PORTS_PER_FUNC)
|
||||
|
||||
struct data_port {
|
||||
struct usb_composite_dev *cdev;
|
||||
struct usb_function *func;
|
||||
struct usb_ep *in;
|
||||
int rx_buffer_size;
|
||||
struct usb_ep *out;
|
||||
int ipa_consumer_ep;
|
||||
int ipa_producer_ep;
|
||||
const struct usb_endpoint_descriptor *in_ep_desc_backup;
|
||||
const struct usb_endpoint_descriptor *out_ep_desc_backup;
|
||||
};
|
||||
|
||||
void bam_data_disconnect(struct data_port *gr, enum function_type func,
|
||||
u8 dev_port_num);
|
||||
|
||||
int bam_data_connect(struct data_port *gr, enum transport_type trans,
|
||||
u8 dev_port_num, enum function_type func);
|
||||
|
||||
int bam_data_setup(enum function_type func, unsigned int no_bam2bam_port);
|
||||
|
||||
void bam_data_flush_workqueue(void);
|
||||
|
||||
void bam_data_suspend(struct data_port *port_usb, u8 dev_port_num,
|
||||
enum function_type func, bool remote_wakeup_enabled);
|
||||
|
||||
void bam_data_resume(struct data_port *port_usb, u8 dev_port_num,
|
||||
enum function_type func, bool remote_wakeup_enabled);
|
||||
|
||||
void bam_data_flow_control_enable(bool enable);
|
||||
|
||||
void u_bam_data_set_dl_max_xfer_size(u32 dl_max_transfer_size);
|
||||
|
||||
void u_bam_data_set_ul_max_pkt_num(u8 ul_max_packets_number);
|
||||
|
||||
void u_bam_data_set_ul_max_xfer_size(u32 ul_max_xfer_size);
|
||||
|
||||
void u_bam_data_start_rndis_ipa(void);
|
||||
|
||||
void u_bam_data_stop_rndis_ipa(void);
|
||||
|
||||
void bam_data_start_rx_tx(u8 port_num);
|
||||
|
||||
int u_bam_data_func_to_port(enum function_type func, u8 func_port);
|
||||
#endif /* __U_BAM_DATA_H */
|
837
drivers/usb/gadget/function/u_ctrl_qti.c
Normal file
837
drivers/usb/gadget/function/u_ctrl_qti.c
Normal file
|
@ -0,0 +1,837 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016, 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/wait.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/usb/usb_ctrl_qti.h>
|
||||
|
||||
#include <soc/qcom/bam_dmux.h>
|
||||
|
||||
#include "u_rmnet.h"
|
||||
#include "usb_gadget_xport.h"
|
||||
|
||||
#define RMNET_CTRL_QTI_NAME "rmnet_ctrl"
|
||||
#define DPL_CTRL_QTI_NAME "dpl_ctrl"
|
||||
/*
|
||||
* Use size of gadget's qti control name. Here currently RMNET and DPL
|
||||
* gadget is using QTI as control transport. Hence using RMNET ctrl name
|
||||
* (as it is bigger in size) for QTI_CTRL_NAME_LEN.
|
||||
*/
|
||||
#define QTI_CTRL_NAME_LEN (sizeof(RMNET_CTRL_QTI_NAME)+2)
|
||||
|
||||
struct qti_ctrl_port {
|
||||
void *port_usb;
|
||||
char name[QTI_CTRL_NAME_LEN];
|
||||
struct miscdevice ctrl_device;
|
||||
|
||||
bool is_open;
|
||||
int index;
|
||||
unsigned intf;
|
||||
int ipa_prod_idx;
|
||||
int ipa_cons_idx;
|
||||
enum peripheral_ep_type ep_type;
|
||||
|
||||
atomic_t connected;
|
||||
atomic_t line_state;
|
||||
|
||||
atomic_t open_excl;
|
||||
atomic_t read_excl;
|
||||
atomic_t write_excl;
|
||||
atomic_t ioctl_excl;
|
||||
|
||||
wait_queue_head_t read_wq;
|
||||
|
||||
struct list_head cpkt_req_q;
|
||||
|
||||
spinlock_t lock;
|
||||
enum gadget_type gtype;
|
||||
unsigned host_to_modem;
|
||||
unsigned copied_to_modem;
|
||||
unsigned copied_from_modem;
|
||||
unsigned modem_to_host;
|
||||
unsigned drp_cpkt_cnt;
|
||||
};
|
||||
static struct qti_ctrl_port *ctrl_port[NR_QTI_PORTS];
|
||||
|
||||
static inline int qti_ctrl_lock(atomic_t *excl)
|
||||
{
|
||||
if (atomic_inc_return(excl) == 1) {
|
||||
return 0;
|
||||
} else {
|
||||
atomic_dec(excl);
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void qti_ctrl_unlock(atomic_t *excl)
|
||||
{
|
||||
atomic_dec(excl);
|
||||
}
|
||||
|
||||
static void qti_ctrl_queue_notify(struct qti_ctrl_port *port)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct rmnet_ctrl_pkt *cpkt = NULL;
|
||||
|
||||
pr_debug("%s: Queue empty packet for QTI for port%d",
|
||||
__func__, port->index);
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
if (!port->is_open) {
|
||||
pr_err("%s: rmnet ctrl file handler %p is not open",
|
||||
__func__, port);
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
cpkt = alloc_rmnet_ctrl_pkt(0, GFP_ATOMIC);
|
||||
if (IS_ERR(cpkt)) {
|
||||
pr_err("%s: Unable to allocate reset function pkt\n", __func__);
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
list_add_tail(&cpkt->list, &port->cpkt_req_q);
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
|
||||
pr_debug("%s: Wake up read queue", __func__);
|
||||
wake_up(&port->read_wq);
|
||||
}
|
||||
|
||||
static int gqti_ctrl_send_cpkt_tomodem(u8 portno, void *buf, size_t len)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct qti_ctrl_port *port;
|
||||
struct rmnet_ctrl_pkt *cpkt;
|
||||
|
||||
if (len > MAX_QTI_PKT_SIZE) {
|
||||
pr_err("given pkt size too big:%zu > max_pkt_size:%d\n",
|
||||
len, MAX_QTI_PKT_SIZE);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (portno >= NR_QTI_PORTS) {
|
||||
pr_err("%s: Invalid QTI port %d\n", __func__, portno);
|
||||
return -ENODEV;
|
||||
}
|
||||
port = ctrl_port[portno];
|
||||
|
||||
cpkt = alloc_rmnet_ctrl_pkt(len, GFP_ATOMIC);
|
||||
if (IS_ERR(cpkt)) {
|
||||
pr_err("%s: Unable to allocate ctrl pkt\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memcpy(cpkt->buf, buf, len);
|
||||
cpkt->len = len;
|
||||
|
||||
pr_debug("%s: gtype:%d: Add to cpkt_req_q packet with len = %zu\n",
|
||||
__func__, port->gtype, len);
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
|
||||
/* drop cpkt if port is not open */
|
||||
if (!port->is_open) {
|
||||
pr_debug("rmnet file handler %p(index=%d) is not open",
|
||||
port, port->index);
|
||||
port->drp_cpkt_cnt++;
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
free_rmnet_ctrl_pkt(cpkt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
list_add_tail(&cpkt->list, &port->cpkt_req_q);
|
||||
port->host_to_modem++;
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
|
||||
/* wakeup read thread */
|
||||
pr_debug("%s: Wake up read queue", __func__);
|
||||
wake_up(&port->read_wq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
gqti_ctrl_notify_modem(void *gptr, u8 portno, int val)
|
||||
{
|
||||
struct qti_ctrl_port *port;
|
||||
|
||||
if (portno >= NR_QTI_PORTS) {
|
||||
pr_err("%s: Invalid QTI port %d\n", __func__, portno);
|
||||
return;
|
||||
}
|
||||
port = ctrl_port[portno];
|
||||
|
||||
atomic_set(&port->line_state, val);
|
||||
|
||||
/* send 0 len pkt to qti to notify state change */
|
||||
qti_ctrl_queue_notify(port);
|
||||
}
|
||||
|
||||
int gqti_ctrl_connect(void *gr, u8 port_num, unsigned intf,
|
||||
enum transport_type dxport, enum gadget_type gtype)
|
||||
{
|
||||
struct qti_ctrl_port *port;
|
||||
struct grmnet *g_rmnet = NULL;
|
||||
struct gqdss *g_dpl = NULL;
|
||||
unsigned long flags;
|
||||
|
||||
pr_debug("%s: gtype:%d gadget:%p\n", __func__, gtype, gr);
|
||||
if (port_num >= NR_QTI_PORTS) {
|
||||
pr_err("%s: Invalid QTI port %d\n", __func__, port_num);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
port = ctrl_port[port_num];
|
||||
if (!port) {
|
||||
pr_err("%s: gadget port is null\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
port->gtype = gtype;
|
||||
if (dxport == USB_GADGET_XPORT_BAM_DMUX) {
|
||||
/*
|
||||
* BAM-DMUX data transport is used for RMNET and DPL
|
||||
* on some targets where IPA is not available.
|
||||
* Set endpoint type as BAM-DMUX and interface
|
||||
* id as channel number. This information is
|
||||
* sent to user space via EP_LOOKUP ioctl.
|
||||
*
|
||||
*/
|
||||
|
||||
port->ep_type = DATA_EP_TYPE_BAM_DMUX;
|
||||
port->intf = (gtype == USB_GADGET_RMNET) ?
|
||||
BAM_DMUX_USB_RMNET_0 :
|
||||
BAM_DMUX_USB_DPL;
|
||||
port->ipa_prod_idx = 0;
|
||||
port->ipa_cons_idx = 0;
|
||||
} else {
|
||||
port->ep_type = DATA_EP_TYPE_HSUSB;
|
||||
port->intf = intf;
|
||||
}
|
||||
|
||||
if (gr && port->gtype == USB_GADGET_RMNET) {
|
||||
port->port_usb = gr;
|
||||
g_rmnet = (struct grmnet *)gr;
|
||||
g_rmnet->send_encap_cmd = gqti_ctrl_send_cpkt_tomodem;
|
||||
g_rmnet->notify_modem = gqti_ctrl_notify_modem;
|
||||
} else if (gr && port->gtype == USB_GADGET_DPL) {
|
||||
port->port_usb = gr;
|
||||
g_dpl = (struct gqdss *)gr;
|
||||
g_dpl->send_encap_cmd = gqti_ctrl_send_cpkt_tomodem;
|
||||
g_dpl->notify_modem = gqti_ctrl_notify_modem;
|
||||
atomic_set(&port->line_state, 1);
|
||||
} else {
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
pr_err("%s(): Port is used without gtype.\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
port->host_to_modem = 0;
|
||||
port->copied_to_modem = 0;
|
||||
port->copied_from_modem = 0;
|
||||
port->modem_to_host = 0;
|
||||
port->drp_cpkt_cnt = 0;
|
||||
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
|
||||
atomic_set(&port->connected, 1);
|
||||
wake_up(&port->read_wq);
|
||||
if (port->port_usb && g_rmnet && g_rmnet->connect)
|
||||
g_rmnet->connect(port->port_usb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gqti_ctrl_disconnect(void *gr, u8 port_num)
|
||||
{
|
||||
struct qti_ctrl_port *port;
|
||||
unsigned long flags;
|
||||
struct rmnet_ctrl_pkt *cpkt;
|
||||
struct grmnet *g_rmnet = NULL;
|
||||
struct gqdss *g_dpl = NULL;
|
||||
|
||||
pr_debug("%s: gadget:%p\n", __func__, gr);
|
||||
|
||||
if (port_num >= NR_QTI_PORTS) {
|
||||
pr_err("%s: Invalid QTI port %d\n", __func__, port_num);
|
||||
return;
|
||||
}
|
||||
|
||||
port = ctrl_port[port_num];
|
||||
|
||||
if (!port) {
|
||||
pr_err("%s: gadget port is null\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
atomic_set(&port->connected, 0);
|
||||
atomic_set(&port->line_state, 0);
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
|
||||
/* reset ipa eps to -1 */
|
||||
port->ipa_prod_idx = -1;
|
||||
port->ipa_cons_idx = -1;
|
||||
port->port_usb = NULL;
|
||||
|
||||
if (gr && port->gtype == USB_GADGET_RMNET) {
|
||||
g_rmnet = (struct grmnet *)gr;
|
||||
g_rmnet->send_encap_cmd = NULL;
|
||||
g_rmnet->notify_modem = NULL;
|
||||
} else if (gr && port->gtype == USB_GADGET_DPL) {
|
||||
g_dpl = (struct gqdss *)gr;
|
||||
g_dpl->send_encap_cmd = NULL;
|
||||
g_dpl->notify_modem = NULL;
|
||||
} else {
|
||||
pr_err("%s(): unrecognized gadget type(%d).\n",
|
||||
__func__, port->gtype);
|
||||
}
|
||||
|
||||
while (!list_empty(&port->cpkt_req_q)) {
|
||||
cpkt = list_first_entry(&port->cpkt_req_q,
|
||||
struct rmnet_ctrl_pkt, list);
|
||||
|
||||
list_del(&cpkt->list);
|
||||
free_rmnet_ctrl_pkt(cpkt);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
|
||||
/* send 0 len pkt to qti to notify state change */
|
||||
qti_ctrl_queue_notify(port);
|
||||
}
|
||||
|
||||
void gqti_ctrl_update_ipa_pipes(void *gr, u8 port_num, u32 ipa_prod,
|
||||
u32 ipa_cons)
|
||||
{
|
||||
struct qti_ctrl_port *port;
|
||||
|
||||
if (port_num >= NR_QTI_PORTS) {
|
||||
pr_err("%s: Invalid QTI port %d\n", __func__, port_num);
|
||||
return;
|
||||
}
|
||||
|
||||
port = ctrl_port[port_num];
|
||||
|
||||
port->ipa_prod_idx = ipa_prod;
|
||||
port->ipa_cons_idx = ipa_cons;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static int qti_ctrl_open(struct inode *ip, struct file *fp)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct qti_ctrl_port *port = container_of(fp->private_data,
|
||||
struct qti_ctrl_port,
|
||||
ctrl_device);
|
||||
|
||||
pr_debug("Open rmnet_ctrl_qti device file name=%s(index=%d)\n",
|
||||
port->name, port->index);
|
||||
|
||||
if (qti_ctrl_lock(&port->open_excl)) {
|
||||
pr_err("Already opened\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
port->is_open = true;
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qti_ctrl_release(struct inode *ip, struct file *fp)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct qti_ctrl_port *port = container_of(fp->private_data,
|
||||
struct qti_ctrl_port,
|
||||
ctrl_device);
|
||||
|
||||
pr_debug("Close rmnet control file");
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
port->is_open = false;
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
|
||||
qti_ctrl_unlock(&port->open_excl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
qti_ctrl_read(struct file *fp, char __user *buf, size_t count, loff_t *pos)
|
||||
{
|
||||
struct qti_ctrl_port *port = container_of(fp->private_data,
|
||||
struct qti_ctrl_port,
|
||||
ctrl_device);
|
||||
struct rmnet_ctrl_pkt *cpkt = NULL;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
pr_debug("%s: Enter(%zu)\n", __func__, count);
|
||||
|
||||
if (count > MAX_QTI_PKT_SIZE) {
|
||||
pr_err("Buffer size is too big %zu, should be at most %d\n",
|
||||
count, MAX_QTI_PKT_SIZE);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (qti_ctrl_lock(&port->read_excl)) {
|
||||
pr_err("Previous reading is not finished yet\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* block until a new packet is available */
|
||||
do {
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
if (!list_empty(&port->cpkt_req_q))
|
||||
break;
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
|
||||
pr_debug("%s: Requests list is empty. Wait.\n", __func__);
|
||||
ret = wait_event_interruptible(port->read_wq,
|
||||
!list_empty(&port->cpkt_req_q));
|
||||
if (ret < 0) {
|
||||
pr_debug("Waiting failed\n");
|
||||
qti_ctrl_unlock(&port->read_excl);
|
||||
return -ERESTARTSYS;
|
||||
}
|
||||
} while (1);
|
||||
|
||||
cpkt = list_first_entry(&port->cpkt_req_q, struct rmnet_ctrl_pkt,
|
||||
list);
|
||||
list_del(&cpkt->list);
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
|
||||
if (cpkt->len > count) {
|
||||
pr_err("cpkt size too big:%d > buf size:%zu\n",
|
||||
cpkt->len, count);
|
||||
qti_ctrl_unlock(&port->read_excl);
|
||||
free_rmnet_ctrl_pkt(cpkt);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pr_debug("%s: cpkt size:%d\n", __func__, cpkt->len);
|
||||
|
||||
|
||||
qti_ctrl_unlock(&port->read_excl);
|
||||
|
||||
ret = copy_to_user(buf, cpkt->buf, cpkt->len);
|
||||
if (ret) {
|
||||
pr_err("copy_to_user failed: err %d\n", ret);
|
||||
ret = -EFAULT;
|
||||
} else {
|
||||
pr_debug("%s: copied %d bytes to user\n", __func__, cpkt->len);
|
||||
ret = cpkt->len;
|
||||
port->copied_to_modem++;
|
||||
}
|
||||
|
||||
free_rmnet_ctrl_pkt(cpkt);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
qti_ctrl_write(struct file *fp, const char __user *buf, size_t count,
|
||||
loff_t *pos)
|
||||
{
|
||||
struct qti_ctrl_port *port = container_of(fp->private_data,
|
||||
struct qti_ctrl_port,
|
||||
ctrl_device);
|
||||
void *kbuf;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
struct grmnet *g_rmnet = NULL;
|
||||
|
||||
pr_debug("%s: Enter(%zu) port_index=%d", __func__, count, port->index);
|
||||
|
||||
if (!count) {
|
||||
pr_debug("zero length ctrl pkt\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (count > MAX_QTI_PKT_SIZE) {
|
||||
pr_debug("given pkt size too big:%zu > max_pkt_size:%d\n",
|
||||
count, MAX_QTI_PKT_SIZE);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (qti_ctrl_lock(&port->write_excl)) {
|
||||
pr_err("Previous writing not finished yet\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (!atomic_read(&port->connected)) {
|
||||
pr_debug("USB cable not connected\n");
|
||||
qti_ctrl_unlock(&port->write_excl);
|
||||
return -EPIPE;
|
||||
}
|
||||
|
||||
kbuf = kmalloc(count, GFP_KERNEL);
|
||||
if (!kbuf) {
|
||||
pr_err("failed to allocate ctrl pkt\n");
|
||||
qti_ctrl_unlock(&port->write_excl);
|
||||
return -ENOMEM;
|
||||
}
|
||||
ret = copy_from_user(kbuf, buf, count);
|
||||
if (ret) {
|
||||
pr_err("copy_from_user failed err:%d\n", ret);
|
||||
kfree(kbuf);
|
||||
qti_ctrl_unlock(&port->write_excl);
|
||||
return -EFAULT;
|
||||
}
|
||||
port->copied_from_modem++;
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
if (port && port->port_usb) {
|
||||
if (port->gtype == USB_GADGET_RMNET) {
|
||||
g_rmnet = (struct grmnet *)port->port_usb;
|
||||
} else {
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
pr_err("%s(): unrecognized gadget type(%d).\n",
|
||||
__func__, port->gtype);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (g_rmnet && g_rmnet->send_cpkt_response) {
|
||||
ret = g_rmnet->send_cpkt_response(port->port_usb,
|
||||
kbuf, count);
|
||||
if (ret)
|
||||
pr_err("%d failed to send ctrl packet.\n", ret);
|
||||
port->modem_to_host++;
|
||||
} else {
|
||||
pr_err("send_cpkt_response callback is NULL\n");
|
||||
ret = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
kfree(kbuf);
|
||||
qti_ctrl_unlock(&port->write_excl);
|
||||
|
||||
pr_debug("%s: Exit(%zu)", __func__, count);
|
||||
return (ret) ? ret : count;
|
||||
}
|
||||
|
||||
static long qti_ctrl_ioctl(struct file *fp, unsigned cmd, unsigned long arg)
|
||||
{
|
||||
struct qti_ctrl_port *port = container_of(fp->private_data,
|
||||
struct qti_ctrl_port,
|
||||
ctrl_device);
|
||||
struct grmnet *gr = NULL;
|
||||
struct ep_info info;
|
||||
int val, ret = 0;
|
||||
|
||||
pr_debug("%s: Received command %d for gtype:%d\n",
|
||||
__func__, cmd, port->gtype);
|
||||
|
||||
if (qti_ctrl_lock(&port->ioctl_excl))
|
||||
return -EBUSY;
|
||||
|
||||
switch (cmd) {
|
||||
case QTI_CTRL_MODEM_OFFLINE:
|
||||
if (port && (port->gtype == USB_GADGET_DPL)) {
|
||||
pr_err("%s(): Modem Offline not handled\n", __func__);
|
||||
goto exit_ioctl;
|
||||
}
|
||||
|
||||
if (port && port->port_usb)
|
||||
gr = port->port_usb;
|
||||
|
||||
if (gr && gr->disconnect)
|
||||
gr->disconnect(gr);
|
||||
break;
|
||||
case QTI_CTRL_MODEM_ONLINE:
|
||||
if (port && (port->gtype == USB_GADGET_DPL)) {
|
||||
pr_err("%s(): Modem Online not handled\n", __func__);
|
||||
goto exit_ioctl;
|
||||
}
|
||||
|
||||
if (port && port->port_usb)
|
||||
gr = port->port_usb;
|
||||
|
||||
if (gr && gr->connect)
|
||||
gr->connect(gr);
|
||||
break;
|
||||
case QTI_CTRL_GET_LINE_STATE:
|
||||
val = atomic_read(&port->line_state);
|
||||
ret = copy_to_user((void __user *)arg, &val, sizeof(val));
|
||||
if (ret) {
|
||||
pr_err("copying to user space failed");
|
||||
ret = -EFAULT;
|
||||
}
|
||||
pr_debug("%s: Sent line_state: %d for gtype:%d\n", __func__,
|
||||
atomic_read(&port->line_state), port->gtype);
|
||||
break;
|
||||
case QTI_CTRL_EP_LOOKUP:
|
||||
|
||||
pr_debug("%s(): EP_LOOKUP for gtype:%d\n", __func__,
|
||||
port->gtype);
|
||||
val = atomic_read(&port->connected);
|
||||
if (!val) {
|
||||
pr_err_ratelimited("EP_LOOKUP failed: not connected\n");
|
||||
ret = -EAGAIN;
|
||||
break;
|
||||
}
|
||||
|
||||
if (port->ipa_prod_idx == -1 && port->ipa_cons_idx == -1) {
|
||||
pr_err_ratelimited("EP_LOOKUP ipa pipes not updated\n");
|
||||
ret = -EAGAIN;
|
||||
break;
|
||||
}
|
||||
|
||||
info.ph_ep_info.ep_type = port->ep_type;
|
||||
info.ph_ep_info.peripheral_iface_id = port->intf;
|
||||
info.ipa_ep_pair.cons_pipe_num = port->ipa_cons_idx;
|
||||
info.ipa_ep_pair.prod_pipe_num = port->ipa_prod_idx;
|
||||
|
||||
pr_debug("%s(): gtype:%d ep_type:%d intf:%d\n",
|
||||
__func__, port->gtype, info.ph_ep_info.ep_type,
|
||||
info.ph_ep_info.peripheral_iface_id);
|
||||
|
||||
pr_debug("%s(): ipa_cons_idx:%d ipa_prod_idx:%d\n",
|
||||
__func__, info.ipa_ep_pair.cons_pipe_num,
|
||||
info.ipa_ep_pair.prod_pipe_num);
|
||||
|
||||
ret = copy_to_user((void __user *)arg, &info,
|
||||
sizeof(info));
|
||||
if (ret) {
|
||||
pr_err("copying to user space failed");
|
||||
ret = -EFAULT;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pr_err("wrong parameter");
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
exit_ioctl:
|
||||
qti_ctrl_unlock(&port->ioctl_excl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned int qti_ctrl_poll(struct file *file, poll_table *wait)
|
||||
{
|
||||
struct qti_ctrl_port *port = container_of(file->private_data,
|
||||
struct qti_ctrl_port,
|
||||
ctrl_device);
|
||||
unsigned long flags;
|
||||
unsigned int mask = 0;
|
||||
|
||||
if (!port) {
|
||||
pr_err("%s on a NULL device\n", __func__);
|
||||
return POLLERR;
|
||||
}
|
||||
|
||||
poll_wait(file, &port->read_wq, wait);
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
if (!list_empty(&port->cpkt_req_q)) {
|
||||
mask |= POLLIN | POLLRDNORM;
|
||||
pr_debug("%s sets POLLIN for rmnet_ctrl_qti_port\n", __func__);
|
||||
}
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static int qti_ctrl_read_stats(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct qti_ctrl_port *port = s->private;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NR_QTI_PORTS; i++) {
|
||||
port = ctrl_port[i];
|
||||
if (!port)
|
||||
continue;
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
|
||||
seq_printf(s, "\n#PORT:%d port: %p\n", i, port);
|
||||
seq_printf(s, "name: %s\n", port->name);
|
||||
seq_printf(s, "host_to_modem: %d\n",
|
||||
port->host_to_modem);
|
||||
seq_printf(s, "copied_to_modem: %d\n",
|
||||
port->copied_to_modem);
|
||||
seq_printf(s, "copied_from_modem: %d\n",
|
||||
port->copied_from_modem);
|
||||
seq_printf(s, "modem_to_host: %d\n",
|
||||
port->modem_to_host);
|
||||
seq_printf(s, "cpkt_drp_cnt: %d\n",
|
||||
port->drp_cpkt_cnt);
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qti_ctrl_stats_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, qti_ctrl_read_stats, inode->i_private);
|
||||
}
|
||||
|
||||
static ssize_t qti_ctrl_reset_stats(struct file *file,
|
||||
const char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct seq_file *s = file->private_data;
|
||||
struct qti_ctrl_port *port = s->private;
|
||||
int i;
|
||||
unsigned long flags;
|
||||
|
||||
for (i = 0; i < NR_QTI_PORTS; i++) {
|
||||
port = ctrl_port[i];
|
||||
if (!port)
|
||||
continue;
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
port->host_to_modem = 0;
|
||||
port->copied_to_modem = 0;
|
||||
port->copied_from_modem = 0;
|
||||
port->modem_to_host = 0;
|
||||
port->drp_cpkt_cnt = 0;
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
const struct file_operations qti_ctrl_stats_ops = {
|
||||
.open = qti_ctrl_stats_open,
|
||||
.read = seq_read,
|
||||
.write = qti_ctrl_reset_stats,
|
||||
};
|
||||
|
||||
static struct dentry *qti_ctrl_dent;
|
||||
static void qti_ctrl_debugfs_init(void)
|
||||
{
|
||||
struct dentry *qti_ctrl_dfile;
|
||||
|
||||
qti_ctrl_dent = debugfs_create_dir("usb_qti", 0);
|
||||
if (IS_ERR(qti_ctrl_dent))
|
||||
return;
|
||||
|
||||
qti_ctrl_dfile =
|
||||
debugfs_create_file("status", 0444, qti_ctrl_dent, 0,
|
||||
&qti_ctrl_stats_ops);
|
||||
if (!qti_ctrl_dfile || IS_ERR(qti_ctrl_dfile))
|
||||
debugfs_remove(qti_ctrl_dent);
|
||||
}
|
||||
|
||||
static void qti_ctrl_debugfs_exit(void)
|
||||
{
|
||||
debugfs_remove_recursive(qti_ctrl_dent);
|
||||
}
|
||||
|
||||
/* file operations for rmnet device /dev/rmnet_ctrl */
|
||||
static const struct file_operations qti_ctrl_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = qti_ctrl_open,
|
||||
.release = qti_ctrl_release,
|
||||
.read = qti_ctrl_read,
|
||||
.write = qti_ctrl_write,
|
||||
.unlocked_ioctl = qti_ctrl_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = qti_ctrl_ioctl,
|
||||
#endif
|
||||
.poll = qti_ctrl_poll,
|
||||
};
|
||||
/* file operations for DPL device /dev/dpl_ctrl */
|
||||
static const struct file_operations dpl_qti_ctrl_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = qti_ctrl_open,
|
||||
.release = qti_ctrl_release,
|
||||
.read = qti_ctrl_read,
|
||||
.write = NULL,
|
||||
.unlocked_ioctl = qti_ctrl_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = qti_ctrl_ioctl,
|
||||
#endif
|
||||
.poll = qti_ctrl_poll,
|
||||
};
|
||||
|
||||
int gqti_ctrl_init(void)
|
||||
{
|
||||
int ret, i, sz = QTI_CTRL_NAME_LEN;
|
||||
struct qti_ctrl_port *port = NULL;
|
||||
|
||||
for (i = 0; i < NR_QTI_PORTS; i++) {
|
||||
port = kzalloc(sizeof(struct qti_ctrl_port), GFP_KERNEL);
|
||||
if (!port) {
|
||||
pr_err("Failed to allocate rmnet control device\n");
|
||||
ret = -ENOMEM;
|
||||
goto fail_init;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&port->cpkt_req_q);
|
||||
spin_lock_init(&port->lock);
|
||||
|
||||
atomic_set(&port->open_excl, 0);
|
||||
atomic_set(&port->read_excl, 0);
|
||||
atomic_set(&port->write_excl, 0);
|
||||
atomic_set(&port->ioctl_excl, 0);
|
||||
atomic_set(&port->connected, 0);
|
||||
atomic_set(&port->line_state, 0);
|
||||
|
||||
init_waitqueue_head(&port->read_wq);
|
||||
|
||||
ctrl_port[i] = port;
|
||||
port->index = i;
|
||||
port->ipa_prod_idx = -1;
|
||||
port->ipa_cons_idx = -1;
|
||||
|
||||
if (i == 0)
|
||||
strlcat(port->name, RMNET_CTRL_QTI_NAME, sz);
|
||||
else if (i == DPL_QTI_CTRL_PORT_NO)
|
||||
strlcat(port->name, DPL_CTRL_QTI_NAME, sz);
|
||||
else
|
||||
snprintf(port->name, sz, "%s%d",
|
||||
RMNET_CTRL_QTI_NAME, i);
|
||||
|
||||
port->ctrl_device.name = port->name;
|
||||
if (i == DPL_QTI_CTRL_PORT_NO)
|
||||
port->ctrl_device.fops = &dpl_qti_ctrl_fops;
|
||||
else
|
||||
port->ctrl_device.fops = &qti_ctrl_fops;
|
||||
port->ctrl_device.minor = MISC_DYNAMIC_MINOR;
|
||||
|
||||
ret = misc_register(&port->ctrl_device);
|
||||
if (ret) {
|
||||
pr_err("rmnet control driver failed to register");
|
||||
goto fail_init;
|
||||
}
|
||||
}
|
||||
qti_ctrl_debugfs_init();
|
||||
|
||||
return ret;
|
||||
|
||||
fail_init:
|
||||
for (i--; i >= 0; i--) {
|
||||
misc_deregister(&ctrl_port[i]->ctrl_device);
|
||||
kfree(ctrl_port[i]);
|
||||
ctrl_port[i] = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void gqti_ctrl_cleanup(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NR_QTI_PORTS; i++) {
|
||||
misc_deregister(&ctrl_port[i]->ctrl_device);
|
||||
kfree(ctrl_port[i]);
|
||||
ctrl_port[i] = NULL;
|
||||
}
|
||||
qti_ctrl_debugfs_exit();
|
||||
}
|
877
drivers/usb/gadget/function/u_data_ipa.c
Normal file
877
drivers/usb/gadget/function/u_data_ipa.c
Normal file
|
@ -0,0 +1,877 @@
|
|||
/* Copyright (c) 2014-2016, 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/kernel.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/termios.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/termios.h>
|
||||
#include <linux/usb_bam.h>
|
||||
|
||||
#include "usb_gadget_xport.h"
|
||||
|
||||
#define IPA_N_PORTS 4
|
||||
struct ipa_data_ch_info {
|
||||
struct usb_request *rx_req;
|
||||
struct usb_request *tx_req;
|
||||
unsigned long flags;
|
||||
unsigned id;
|
||||
enum transport_type trans;
|
||||
enum gadget_type gtype;
|
||||
bool is_connected;
|
||||
unsigned port_num;
|
||||
spinlock_t port_lock;
|
||||
|
||||
struct work_struct connect_w;
|
||||
struct work_struct disconnect_w;
|
||||
struct work_struct suspend_w;
|
||||
struct work_struct resume_w;
|
||||
|
||||
u32 src_pipe_idx;
|
||||
u32 dst_pipe_idx;
|
||||
u8 src_connection_idx;
|
||||
u8 dst_connection_idx;
|
||||
enum usb_ctrl usb_bam_type;
|
||||
struct gadget_ipa_port *port_usb;
|
||||
struct usb_bam_connect_ipa_params ipa_params;
|
||||
};
|
||||
|
||||
static int n_ipa_ports;
|
||||
static struct workqueue_struct *ipa_data_wq;
|
||||
struct ipa_data_ch_info *ipa_data_ports[IPA_N_PORTS];
|
||||
/**
|
||||
* ipa_data_endless_complete() - completion callback for endless TX/RX request
|
||||
* @ep: USB endpoint for which this completion happen
|
||||
* @req: USB endless request
|
||||
*
|
||||
* This completion is being called when endless (TX/RX) transfer is terminated
|
||||
* i.e. disconnect or suspend case.
|
||||
*/
|
||||
static void ipa_data_endless_complete(struct usb_ep *ep,
|
||||
struct usb_request *req)
|
||||
{
|
||||
pr_debug("%s: endless complete for(%s) with status: %d\n",
|
||||
__func__, ep->name, req->status);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_start_endless_xfer() - configure USB endpoint and
|
||||
* queue endless TX/RX request
|
||||
* @port: USB IPA data channel information
|
||||
* @in: USB endpoint direction i.e. true: IN(Device TX), false: OUT(Device RX)
|
||||
*
|
||||
* It is being used to queue endless TX/RX request with UDC driver.
|
||||
* It does set required DBM endpoint configuration before queueing endless
|
||||
* TX/RX request.
|
||||
*/
|
||||
static void ipa_data_start_endless_xfer(struct ipa_data_ch_info *port, bool in)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (!port->port_usb) {
|
||||
pr_err("%s(): port_usb is NULL.\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (in) {
|
||||
pr_debug("%s: enqueue endless TX_REQ(IN)\n", __func__);
|
||||
status = usb_ep_queue(port->port_usb->in,
|
||||
port->tx_req, GFP_ATOMIC);
|
||||
if (status)
|
||||
pr_err("error enqueuing endless TX_REQ, %d\n", status);
|
||||
} else {
|
||||
pr_debug("%s: enqueue endless RX_REQ(OUT)\n", __func__);
|
||||
status = usb_ep_queue(port->port_usb->out,
|
||||
port->rx_req, GFP_ATOMIC);
|
||||
if (status)
|
||||
pr_err("error enqueuing endless RX_REQ, %d\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_stop_endless_xfer() - terminate and dequeue endless TX/RX request
|
||||
* @port: USB IPA data channel information
|
||||
* @in: USB endpoint direction i.e. IN - Device TX, OUT - Device RX
|
||||
*
|
||||
* It is being used to terminate and dequeue endless TX/RX request with UDC
|
||||
* driver.
|
||||
*/
|
||||
static void ipa_data_stop_endless_xfer(struct ipa_data_ch_info *port, bool in)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (!port->port_usb) {
|
||||
pr_err("%s(): port_usb is NULL.\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (in) {
|
||||
pr_debug("%s: dequeue endless TX_REQ(IN)\n", __func__);
|
||||
status = usb_ep_dequeue(port->port_usb->in, port->tx_req);
|
||||
if (status)
|
||||
pr_err("error dequeueing endless TX_REQ, %d\n", status);
|
||||
} else {
|
||||
pr_debug("%s: dequeue endless RX_REQ(OUT)\n", __func__);
|
||||
status = usb_ep_dequeue(port->port_usb->out, port->rx_req);
|
||||
if (status)
|
||||
pr_err("error dequeueing endless RX_REQ, %d\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_disconnect_work() - Perform USB IPA BAM disconnect
|
||||
* @w: disconnect work
|
||||
*
|
||||
* It is being schedule from ipa_data_disconnect() API when particular function
|
||||
* is being disable due to USB disconnect or USB composition switch is being
|
||||
* trigger . This API performs disconnect of USB BAM pipe, IPA BAM pipe and also
|
||||
* initiate USB IPA BAM pipe handshake for USB Disconnect sequence. Due to
|
||||
* handshake operation and involvement of SPS related APIs, this functioality
|
||||
* can't be used from atomic context.
|
||||
*/
|
||||
static void ipa_data_disconnect_work(struct work_struct *w)
|
||||
{
|
||||
struct ipa_data_ch_info *port = container_of(w, struct ipa_data_ch_info,
|
||||
disconnect_w);
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
if (!port->is_connected) {
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
pr_debug("Already disconnected.\n");
|
||||
return;
|
||||
}
|
||||
port->is_connected = false;
|
||||
pr_debug("%s(): prod_clnt_hdl:%d cons_clnt_hdl:%d\n", __func__,
|
||||
port->ipa_params.prod_clnt_hdl,
|
||||
port->ipa_params.cons_clnt_hdl);
|
||||
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
ret = usb_bam_disconnect_ipa(port->usb_bam_type, &port->ipa_params);
|
||||
if (ret)
|
||||
pr_err("usb_bam_disconnect_ipa failed: err:%d\n", ret);
|
||||
|
||||
if (port->ipa_params.prod_clnt_hdl)
|
||||
usb_bam_free_fifos(port->usb_bam_type,
|
||||
port->dst_connection_idx);
|
||||
if (port->ipa_params.cons_clnt_hdl)
|
||||
usb_bam_free_fifos(port->usb_bam_type,
|
||||
port->src_connection_idx);
|
||||
|
||||
pr_debug("%s(): disconnect work completed.\n", __func__);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_disconnect() - Restore USB ep operation and disable USB endpoint
|
||||
* @gp: USB gadget IPA Port
|
||||
* @port_num: Port num used by function driver which need to be disable
|
||||
*
|
||||
* It is being called from atomic context from gadget driver when particular
|
||||
* function is being disable due to USB cable disconnect or USB composition
|
||||
* switch is being trigger. This API performs restoring USB endpoint operation
|
||||
* and disable USB endpoint used for accelerated path.
|
||||
*/
|
||||
void ipa_data_disconnect(struct gadget_ipa_port *gp, u8 port_num)
|
||||
{
|
||||
struct ipa_data_ch_info *port;
|
||||
unsigned long flags;
|
||||
struct usb_gadget *gadget = NULL;
|
||||
|
||||
pr_debug("dev:%p port number:%d\n", gp, port_num);
|
||||
if (port_num >= n_ipa_ports) {
|
||||
pr_err("invalid ipa portno#%d\n", port_num);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gp) {
|
||||
pr_err("data port is null\n");
|
||||
return;
|
||||
}
|
||||
|
||||
port = ipa_data_ports[port_num];
|
||||
if (!port) {
|
||||
pr_err("port %u is NULL", port_num);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
if (port->port_usb) {
|
||||
gadget = port->port_usb->cdev->gadget;
|
||||
port->port_usb->ipa_consumer_ep = -1;
|
||||
port->port_usb->ipa_producer_ep = -1;
|
||||
|
||||
if (port->port_usb->in) {
|
||||
/*
|
||||
* Disable endpoints.
|
||||
* Unlocking is needed since disabling the eps might
|
||||
* stop active transfers and therefore the request
|
||||
* complete function will be called, where we try
|
||||
* to obtain the spinlock as well.
|
||||
*/
|
||||
if (gadget_is_dwc3(gadget))
|
||||
msm_ep_unconfig(port->port_usb->in);
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
usb_ep_disable(port->port_usb->in);
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
port->port_usb->in->endless = false;
|
||||
}
|
||||
|
||||
if (port->port_usb->out) {
|
||||
if (gadget_is_dwc3(gadget))
|
||||
msm_ep_unconfig(port->port_usb->out);
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
usb_ep_disable(port->port_usb->out);
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
port->port_usb->out->endless = false;
|
||||
}
|
||||
|
||||
port->port_usb = NULL;
|
||||
}
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
queue_work(ipa_data_wq, &port->disconnect_w);
|
||||
}
|
||||
|
||||
/**
|
||||
* configure_fifo() - Configure USB BAM Pipe's data FIFO
|
||||
* @idx: USB BAM Pipe index
|
||||
* @ep: USB endpoint
|
||||
*
|
||||
* This function configures USB BAM data fifo using fetched pipe configuraion
|
||||
* using provided index value. This function needs to used before starting
|
||||
* endless transfer.
|
||||
*/
|
||||
static void configure_fifo(enum usb_ctrl bam_type, u8 idx, struct usb_ep *ep)
|
||||
{
|
||||
struct u_bam_data_connect_info bam_info;
|
||||
struct sps_mem_buffer data_fifo = {0};
|
||||
|
||||
get_bam2bam_connection_info(bam_type, idx,
|
||||
&bam_info.usb_bam_pipe_idx,
|
||||
NULL, &data_fifo, NULL);
|
||||
msm_data_fifo_config(ep, data_fifo.phys_base, data_fifo.size,
|
||||
bam_info.usb_bam_pipe_idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_connect_work() - Perform USB IPA BAM connect
|
||||
* @w: connect work
|
||||
*
|
||||
* It is being schedule from ipa_data_connect() API when particular function
|
||||
* which is using USB IPA accelerated path. This API performs allocating request
|
||||
* for USB endpoint (tx/rx) for endless purpose, configure USB endpoint to be
|
||||
* used in accelerated path, connect of USB BAM pipe, IPA BAM pipe and also
|
||||
* initiate USB IPA BAM pipe handshake for connect sequence.
|
||||
*/
|
||||
static void ipa_data_connect_work(struct work_struct *w)
|
||||
{
|
||||
struct ipa_data_ch_info *port = container_of(w, struct ipa_data_ch_info,
|
||||
connect_w);
|
||||
struct gadget_ipa_port *gport;
|
||||
struct usb_gadget *gadget = NULL;
|
||||
u32 sps_params;
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
bool is_ipa_disconnected = true;
|
||||
|
||||
pr_debug("%s: Connect workqueue started", __func__);
|
||||
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
|
||||
if (!port->port_usb) {
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
pr_err("%s(): port_usb is NULL.\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
gport = port->port_usb;
|
||||
if (gport && gport->cdev)
|
||||
gadget = gport->cdev->gadget;
|
||||
|
||||
if (!gadget) {
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
pr_err("%s: gport is NULL.\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
gport->ipa_consumer_ep = -1;
|
||||
gport->ipa_producer_ep = -1;
|
||||
if (gport->out) {
|
||||
port->rx_req = usb_ep_alloc_request(gport->out, GFP_ATOMIC);
|
||||
if (!port->rx_req) {
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
pr_err("%s: failed to allocate rx_req\n", __func__);
|
||||
return;
|
||||
}
|
||||
port->rx_req->context = port;
|
||||
port->rx_req->complete = ipa_data_endless_complete;
|
||||
port->rx_req->length = 0;
|
||||
port->rx_req->no_interrupt = 1;
|
||||
}
|
||||
|
||||
if (gport->in) {
|
||||
port->tx_req = usb_ep_alloc_request(gport->in, GFP_ATOMIC);
|
||||
if (!port->tx_req) {
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
pr_err("%s: failed to allocate tx_req\n", __func__);
|
||||
goto free_rx_req;
|
||||
}
|
||||
port->tx_req->context = port;
|
||||
port->tx_req->complete = ipa_data_endless_complete;
|
||||
port->tx_req->length = 0;
|
||||
port->tx_req->no_interrupt = 1;
|
||||
}
|
||||
|
||||
port->is_connected = true;
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
|
||||
/* update IPA Parameteres here. */
|
||||
port->ipa_params.usb_connection_speed = gadget->speed;
|
||||
if (gadget_is_dwc3(gadget))
|
||||
port->ipa_params.reset_pipe_after_lpm =
|
||||
msm_dwc3_reset_ep_after_lpm(gadget);
|
||||
port->ipa_params.skip_ep_cfg = true;
|
||||
port->ipa_params.keep_ipa_awake = true;
|
||||
port->ipa_params.cons_clnt_hdl = -1;
|
||||
port->ipa_params.prod_clnt_hdl = -1;
|
||||
|
||||
|
||||
if (gport->out) {
|
||||
usb_bam_alloc_fifos(port->usb_bam_type,
|
||||
port->src_connection_idx);
|
||||
|
||||
if (gadget_is_dwc3(gadget)) {
|
||||
sps_params = MSM_SPS_MODE | MSM_DISABLE_WB
|
||||
| MSM_PRODUCER | port->src_pipe_idx;
|
||||
port->rx_req->length = 32*1024;
|
||||
port->rx_req->udc_priv = sps_params;
|
||||
configure_fifo(port->usb_bam_type,
|
||||
port->src_connection_idx,
|
||||
port->port_usb->out);
|
||||
ret = msm_ep_config(gport->out, port->rx_req,
|
||||
GFP_ATOMIC);
|
||||
if (ret) {
|
||||
pr_err("msm_ep_config() failed for OUT EP\n");
|
||||
usb_bam_free_fifos(port->usb_bam_type,
|
||||
port->src_connection_idx);
|
||||
goto free_rx_tx_req;
|
||||
}
|
||||
} else {
|
||||
sps_params = (MSM_SPS_MODE | port->src_pipe_idx |
|
||||
MSM_VENDOR_ID) & ~MSM_IS_FINITE_TRANSFER;
|
||||
port->rx_req->udc_priv = sps_params;
|
||||
}
|
||||
}
|
||||
|
||||
if (gport->in) {
|
||||
usb_bam_alloc_fifos(port->usb_bam_type,
|
||||
port->dst_connection_idx);
|
||||
if (gadget_is_dwc3(gadget)) {
|
||||
sps_params = MSM_SPS_MODE | MSM_DISABLE_WB |
|
||||
port->dst_pipe_idx;
|
||||
port->tx_req->length = 32*1024;
|
||||
port->tx_req->udc_priv = sps_params;
|
||||
configure_fifo(port->usb_bam_type,
|
||||
port->dst_connection_idx, gport->in);
|
||||
ret = msm_ep_config(gport->in, port->tx_req,
|
||||
GFP_ATOMIC);
|
||||
if (ret) {
|
||||
pr_err("msm_ep_config() failed for IN EP\n");
|
||||
goto unconfig_msm_ep_out;
|
||||
}
|
||||
} else {
|
||||
sps_params = (MSM_SPS_MODE | port->dst_pipe_idx |
|
||||
MSM_VENDOR_ID) & ~MSM_IS_FINITE_TRANSFER;
|
||||
port->tx_req->udc_priv = sps_params;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform below operations for Tx from Device (OUT transfer)
|
||||
* 1. Connect with pipe of USB BAM with IPA BAM pipe
|
||||
* 2. Update USB Endpoint related information using SPS Param.
|
||||
* 3. Configure USB Endpoint/DBM for the same.
|
||||
* 4. Override USB ep queue functionality for endless transfer.
|
||||
*/
|
||||
if (gport->out) {
|
||||
pr_debug("configure bam ipa connect for USB OUT\n");
|
||||
port->ipa_params.dir = USB_TO_PEER_PERIPHERAL;
|
||||
ret = usb_bam_connect_ipa(port->usb_bam_type,
|
||||
&port->ipa_params);
|
||||
if (ret) {
|
||||
pr_err("usb_bam_connect_ipa out failed err:%d\n", ret);
|
||||
goto unconfig_msm_ep_in;
|
||||
}
|
||||
gadget->bam2bam_func_enabled = true;
|
||||
|
||||
gport->ipa_consumer_ep = port->ipa_params.ipa_cons_ep_idx;
|
||||
is_ipa_disconnected = false;
|
||||
}
|
||||
|
||||
if (gport->in) {
|
||||
pr_debug("configure bam ipa connect for USB IN\n");
|
||||
port->ipa_params.dir = PEER_PERIPHERAL_TO_USB;
|
||||
port->ipa_params.dst_client = IPA_CLIENT_USB_DPL_CONS;
|
||||
ret = usb_bam_connect_ipa(port->usb_bam_type,
|
||||
&port->ipa_params);
|
||||
if (ret) {
|
||||
pr_err("usb_bam_connect_ipa IN failed err:%d\n", ret);
|
||||
goto disconnect_usb_bam_ipa_out;
|
||||
}
|
||||
gadget->bam2bam_func_enabled = true;
|
||||
|
||||
gport->ipa_producer_ep = port->ipa_params.ipa_prod_ep_idx;
|
||||
is_ipa_disconnected = false;
|
||||
}
|
||||
|
||||
pr_debug("ipa_producer_ep:%d ipa_consumer_ep:%d\n",
|
||||
gport->ipa_producer_ep,
|
||||
gport->ipa_consumer_ep);
|
||||
|
||||
gqti_ctrl_update_ipa_pipes(NULL, DPL_QTI_CTRL_PORT_NO,
|
||||
gport->ipa_producer_ep,
|
||||
gport->ipa_consumer_ep);
|
||||
|
||||
pr_debug("src_bam_idx:%d dst_bam_idx:%d\n",
|
||||
port->src_connection_idx, port->dst_connection_idx);
|
||||
|
||||
if (gport->out)
|
||||
ipa_data_start_endless_xfer(port, false);
|
||||
if (gport->in)
|
||||
ipa_data_start_endless_xfer(port, true);
|
||||
|
||||
pr_debug("Connect workqueue done (port %p)", port);
|
||||
return;
|
||||
|
||||
disconnect_usb_bam_ipa_out:
|
||||
if (!is_ipa_disconnected) {
|
||||
usb_bam_disconnect_ipa(port->usb_bam_type, &port->ipa_params);
|
||||
is_ipa_disconnected = true;
|
||||
}
|
||||
unconfig_msm_ep_in:
|
||||
if (gport->in)
|
||||
msm_ep_unconfig(port->port_usb->in);
|
||||
unconfig_msm_ep_out:
|
||||
if (gport->in)
|
||||
usb_bam_free_fifos(port->usb_bam_type,
|
||||
port->dst_connection_idx);
|
||||
if (gport->out) {
|
||||
msm_ep_unconfig(port->port_usb->out);
|
||||
usb_bam_free_fifos(port->usb_bam_type,
|
||||
port->src_connection_idx);
|
||||
}
|
||||
free_rx_tx_req:
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
port->is_connected = false;
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
if (gport->in && port->tx_req)
|
||||
usb_ep_free_request(gport->in, port->tx_req);
|
||||
free_rx_req:
|
||||
if (gport->out && port->rx_req)
|
||||
usb_ep_free_request(gport->out, port->rx_req);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_connect() - Prepare IPA params and enable USB endpoints
|
||||
* @gp: USB IPA gadget port
|
||||
* @port_num: port number used by accelerated function
|
||||
* @src_connection_idx: USB BAM pipe index used as producer
|
||||
* @dst_connection_idx: USB BAM pipe index used as consumer
|
||||
*
|
||||
* It is being called from accelerated function driver (from set_alt()) to
|
||||
* initiate USB BAM IPA connection. This API is enabling accelerated endpoints
|
||||
* and schedule connect_work() which establishes USB IPA BAM communication.
|
||||
*/
|
||||
int ipa_data_connect(struct gadget_ipa_port *gp, u8 port_num,
|
||||
u8 src_connection_idx, u8 dst_connection_idx)
|
||||
{
|
||||
struct ipa_data_ch_info *port;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
pr_debug("dev:%p port#%d src_connection_idx:%d dst_connection_idx:%d\n",
|
||||
gp, port_num, src_connection_idx, dst_connection_idx);
|
||||
|
||||
if (port_num >= n_ipa_ports) {
|
||||
pr_err("invalid portno#%d\n", port_num);
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!gp) {
|
||||
pr_err("gadget port is null\n");
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
|
||||
port = ipa_data_ports[port_num];
|
||||
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
port->port_usb = gp;
|
||||
port->src_connection_idx = src_connection_idx;
|
||||
port->dst_connection_idx = dst_connection_idx;
|
||||
port->usb_bam_type = usb_bam_get_bam_type(gp->cdev->gadget->name);
|
||||
|
||||
port->ipa_params.src_pipe = &(port->src_pipe_idx);
|
||||
port->ipa_params.dst_pipe = &(port->dst_pipe_idx);
|
||||
port->ipa_params.src_idx = src_connection_idx;
|
||||
port->ipa_params.dst_idx = dst_connection_idx;
|
||||
|
||||
/*
|
||||
* Disable Xfer complete and Xfer not ready interrupts by
|
||||
* marking endless flag which is used in UDC driver to enable
|
||||
* these interrupts. with this set, these interrupts for selected
|
||||
* endpoints won't be enabled.
|
||||
*/
|
||||
if (port->port_usb->in) {
|
||||
port->port_usb->in->endless = true;
|
||||
ret = usb_ep_enable(port->port_usb->in);
|
||||
if (ret) {
|
||||
pr_err("usb_ep_enable failed eptype:IN ep:%p",
|
||||
port->port_usb->in);
|
||||
port->port_usb->in->endless = false;
|
||||
goto err_usb_in;
|
||||
}
|
||||
}
|
||||
|
||||
if (port->port_usb->out) {
|
||||
port->port_usb->out->endless = true;
|
||||
ret = usb_ep_enable(port->port_usb->out);
|
||||
if (ret) {
|
||||
pr_err("usb_ep_enable failed eptype:OUT ep:%p",
|
||||
port->port_usb->out);
|
||||
port->port_usb->out->endless = false;
|
||||
goto err_usb_out;
|
||||
}
|
||||
}
|
||||
|
||||
if (!port->port_usb->out && !port->port_usb->in) {
|
||||
pr_err("%s(): No USB endpoint enabled.\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto err_usb_in;
|
||||
}
|
||||
|
||||
queue_work(ipa_data_wq, &port->connect_w);
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
|
||||
return ret;
|
||||
|
||||
err_usb_out:
|
||||
if (port->port_usb->in)
|
||||
port->port_usb->in->endless = false;
|
||||
err_usb_in:
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
err:
|
||||
pr_debug("%s(): failed with error:%d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_start() - Restart USB endless transfer
|
||||
* @param: IPA data channel information
|
||||
* @dir: USB BAM pipe direction
|
||||
*
|
||||
* It is being used to restart USB endless transfer for USB bus resume.
|
||||
* For USB consumer case, it restarts USB endless RX transfer, whereas
|
||||
* for USB producer case, it resets DBM endpoint and restart USB endless
|
||||
* TX transfer.
|
||||
*/
|
||||
static void ipa_data_start(void *param, enum usb_bam_pipe_dir dir)
|
||||
{
|
||||
struct ipa_data_ch_info *port = param;
|
||||
struct usb_gadget *gadget = NULL;
|
||||
|
||||
if (!port || !port->port_usb || !port->port_usb->cdev->gadget) {
|
||||
pr_err("%s:port,cdev or gadget is NULL\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
gadget = port->port_usb->cdev->gadget;
|
||||
if (dir == USB_TO_PEER_PERIPHERAL) {
|
||||
pr_debug("%s(): start endless RX\n", __func__);
|
||||
ipa_data_start_endless_xfer(port, false);
|
||||
} else {
|
||||
pr_debug("%s(): start endless TX\n", __func__);
|
||||
if (msm_dwc3_reset_ep_after_lpm(gadget)) {
|
||||
configure_fifo(port->usb_bam_type,
|
||||
port->dst_connection_idx, port->port_usb->in);
|
||||
}
|
||||
ipa_data_start_endless_xfer(port, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_stop() - Stop endless Tx/Rx transfers
|
||||
* @param: IPA data channel information
|
||||
* @dir: USB BAM pipe direction
|
||||
*
|
||||
* It is being used to stop endless Tx/Rx transfers. It is being used
|
||||
* for USB bus suspend functionality.
|
||||
*/
|
||||
static void ipa_data_stop(void *param, enum usb_bam_pipe_dir dir)
|
||||
{
|
||||
struct ipa_data_ch_info *port = param;
|
||||
struct usb_gadget *gadget = NULL;
|
||||
|
||||
if (!port || !port->port_usb || !port->port_usb->cdev->gadget) {
|
||||
pr_err("%s:port,cdev or gadget is NULL\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
gadget = port->port_usb->cdev->gadget;
|
||||
if (dir == USB_TO_PEER_PERIPHERAL) {
|
||||
pr_debug("%s(): stop endless RX transfer\n", __func__);
|
||||
ipa_data_stop_endless_xfer(port, false);
|
||||
} else {
|
||||
pr_debug("%s(): stop endless TX transfer\n", __func__);
|
||||
ipa_data_stop_endless_xfer(port, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_suspend() - Initiate USB BAM IPA suspend functionality
|
||||
* @gp: Gadget IPA port
|
||||
* @port_num: port number used by function
|
||||
*
|
||||
* It is being used to initiate USB BAM IPA suspend functionality
|
||||
* for USB bus suspend functionality.
|
||||
*/
|
||||
void ipa_data_suspend(struct gadget_ipa_port *gp, u8 port_num)
|
||||
{
|
||||
struct ipa_data_ch_info *port;
|
||||
int ret;
|
||||
|
||||
pr_debug("dev:%p port number:%d\n", gp, port_num);
|
||||
|
||||
if (port_num >= n_ipa_ports) {
|
||||
pr_err("invalid ipa portno#%d\n", port_num);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gp) {
|
||||
pr_err("data port is null\n");
|
||||
return;
|
||||
}
|
||||
|
||||
port = ipa_data_ports[port_num];
|
||||
if (!port) {
|
||||
pr_err("port %u is NULL", port_num);
|
||||
return;
|
||||
}
|
||||
|
||||
pr_debug("%s: suspend started\n", __func__);
|
||||
ret = usb_bam_register_wake_cb(port->usb_bam_type,
|
||||
port->dst_connection_idx, NULL, port);
|
||||
if (ret) {
|
||||
pr_err("%s(): Failed to register BAM wake callback.\n",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
usb_bam_register_start_stop_cbs(port->usb_bam_type,
|
||||
port->dst_connection_idx, ipa_data_start,
|
||||
ipa_data_stop, port);
|
||||
usb_bam_suspend(port->usb_bam_type, &port->ipa_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_resume() - Initiate USB resume functionality
|
||||
* @gp: Gadget IPA port
|
||||
* @port_num: port number used by function
|
||||
*
|
||||
* It is being used to initiate USB resume functionality
|
||||
* for USB bus resume case.
|
||||
*/
|
||||
void ipa_data_resume(struct gadget_ipa_port *gp, u8 port_num)
|
||||
{
|
||||
struct ipa_data_ch_info *port;
|
||||
unsigned long flags;
|
||||
struct usb_gadget *gadget = NULL;
|
||||
int ret;
|
||||
|
||||
pr_debug("dev:%p port number:%d\n", gp, port_num);
|
||||
|
||||
if (port_num >= n_ipa_ports) {
|
||||
pr_err("invalid ipa portno#%d\n", port_num);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gp) {
|
||||
pr_err("data port is null\n");
|
||||
return;
|
||||
}
|
||||
|
||||
port = ipa_data_ports[port_num];
|
||||
if (!port) {
|
||||
pr_err("port %u is NULL", port_num);
|
||||
return;
|
||||
}
|
||||
|
||||
pr_debug("%s: resume started\n", __func__);
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
gadget = port->port_usb->cdev->gadget;
|
||||
if (!gadget) {
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
pr_err("%s(): Gadget is NULL.\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = usb_bam_register_wake_cb(port->usb_bam_type,
|
||||
port->dst_connection_idx, NULL, NULL);
|
||||
if (ret) {
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
pr_err("%s(): Failed to register BAM wake callback.\n",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msm_dwc3_reset_ep_after_lpm(gadget)) {
|
||||
configure_fifo(port->usb_bam_type, port->src_connection_idx,
|
||||
port->port_usb->out);
|
||||
configure_fifo(port->usb_bam_type, port->dst_connection_idx,
|
||||
port->port_usb->in);
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
msm_dwc3_reset_dbm_ep(port->port_usb->in);
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
usb_bam_resume(port->usb_bam_type, &port->ipa_params);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_port_alloc() - Allocate IPA USB Port structure
|
||||
* @portno: port number to be used by particular USB function
|
||||
*
|
||||
* It is being used by USB function driver to allocate IPA data port
|
||||
* for USB IPA data accelerated path.
|
||||
*
|
||||
* Retrun: 0 in case of success, otherwise errno.
|
||||
*/
|
||||
static int ipa_data_port_alloc(int portno)
|
||||
{
|
||||
struct ipa_data_ch_info *port = NULL;
|
||||
|
||||
if (ipa_data_ports[portno] != NULL) {
|
||||
pr_debug("port %d already allocated.\n", portno);
|
||||
return 0;
|
||||
}
|
||||
|
||||
port = kzalloc(sizeof(struct ipa_data_ch_info), GFP_KERNEL);
|
||||
if (!port) {
|
||||
pr_err("no memory to allocate port %d\n", portno);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ipa_data_ports[portno] = port;
|
||||
|
||||
pr_debug("port:%p with portno:%d allocated\n", port, portno);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_data_port_select() - Select particular port for BAM2BAM IPA mode
|
||||
* @portno: port number to be used by particular USB function
|
||||
* @gtype: USB gadget function type
|
||||
*
|
||||
* It is being used by USB function driver to select which BAM2BAM IPA
|
||||
* port particular USB function wants to use.
|
||||
*
|
||||
*/
|
||||
void ipa_data_port_select(int portno, enum gadget_type gtype)
|
||||
{
|
||||
struct ipa_data_ch_info *port = NULL;
|
||||
|
||||
pr_debug("portno:%d\n", portno);
|
||||
|
||||
port = ipa_data_ports[portno];
|
||||
port->port_num = portno;
|
||||
port->is_connected = false;
|
||||
|
||||
spin_lock_init(&port->port_lock);
|
||||
|
||||
if (!work_pending(&port->connect_w))
|
||||
INIT_WORK(&port->connect_w, ipa_data_connect_work);
|
||||
|
||||
if (!work_pending(&port->disconnect_w))
|
||||
INIT_WORK(&port->disconnect_w, ipa_data_disconnect_work);
|
||||
|
||||
port->ipa_params.src_client = IPA_CLIENT_USB_PROD;
|
||||
port->ipa_params.dst_client = IPA_CLIENT_USB_CONS;
|
||||
port->gtype = gtype;
|
||||
};
|
||||
|
||||
/**
|
||||
* ipa_data_setup() - setup BAM2BAM IPA port
|
||||
* @no_ipa_port: total number of BAM2BAM IPA port to support
|
||||
*
|
||||
* Each USB function who wants to use BAM2BAM IPA port would
|
||||
* be counting number of IPA port to use and initialize those
|
||||
* ports at time of bind_config() in android gadget driver.
|
||||
*
|
||||
* Retrun: 0 in case of success, otherwise errno.
|
||||
*/
|
||||
int ipa_data_setup(unsigned int no_ipa_port)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
pr_debug("requested %d IPA BAM ports", no_ipa_port);
|
||||
|
||||
if (!no_ipa_port || no_ipa_port > IPA_N_PORTS) {
|
||||
pr_err("Invalid num of ports count:%d\n", no_ipa_port);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < no_ipa_port; i++) {
|
||||
n_ipa_ports++;
|
||||
ret = ipa_data_port_alloc(i);
|
||||
if (ret) {
|
||||
n_ipa_ports--;
|
||||
pr_err("Failed to alloc port:%d\n", i);
|
||||
goto free_ipa_ports;
|
||||
}
|
||||
}
|
||||
|
||||
pr_debug("n_ipa_ports:%d\n", n_ipa_ports);
|
||||
|
||||
if (ipa_data_wq) {
|
||||
pr_debug("ipa_data_wq is already setup.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ipa_data_wq = alloc_workqueue("k_usb_ipa_data",
|
||||
WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
|
||||
if (!ipa_data_wq) {
|
||||
pr_err("Failed to create workqueue\n");
|
||||
ret = -ENOMEM;
|
||||
goto free_ipa_ports;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_ipa_ports:
|
||||
for (i = 0; i < n_ipa_ports; i++) {
|
||||
kfree(ipa_data_ports[i]);
|
||||
ipa_data_ports[i] = NULL;
|
||||
if (ipa_data_wq) {
|
||||
destroy_workqueue(ipa_data_wq);
|
||||
ipa_data_wq = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
35
drivers/usb/gadget/function/u_data_ipa.h
Normal file
35
drivers/usb/gadget/function/u_data_ipa.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/* Copyright (c) 2014, 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.
|
||||
*/
|
||||
|
||||
#ifndef __U_DATA_IPA_H
|
||||
#define __U_DATA_IPA_H
|
||||
|
||||
#include "usb_gadget_xport.h"
|
||||
|
||||
struct gadget_ipa_port {
|
||||
struct usb_composite_dev *cdev;
|
||||
struct usb_function *func;
|
||||
struct usb_ep *in;
|
||||
struct usb_ep *out;
|
||||
int ipa_consumer_ep;
|
||||
int ipa_producer_ep;
|
||||
};
|
||||
|
||||
void ipa_data_port_select(int portno, enum gadget_type gtype);
|
||||
void ipa_data_disconnect(struct gadget_ipa_port *gp, u8 port_num);
|
||||
int ipa_data_connect(struct gadget_ipa_port *gp, u8 port_num,
|
||||
u8 src_connection_idx, u8 dst_connection_idx);
|
||||
int ipa_data_setup(unsigned int no_ipa_port);
|
||||
void ipa_data_resume(struct gadget_ipa_port *gp, u8 port_num);
|
||||
void ipa_data_suspend(struct gadget_ipa_port *gp, u8 port_num);
|
||||
|
||||
#endif
|
454
drivers/usb/gadget/function/u_qc_ether.c
Normal file
454
drivers/usb/gadget/function/u_qc_ether.c
Normal file
|
@ -0,0 +1,454 @@
|
|||
/*
|
||||
* u_qc_ether.c -- Ethernet-over-USB link layer utilities for Gadget stack
|
||||
*
|
||||
* Copyright (C) 2003-2005,2008 David Brownell
|
||||
* Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
* Copyright (c) 2012-2014, 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
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/* #define VERBOSE_DEBUG */
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/ethtool.h>
|
||||
|
||||
#include "u_ether.h"
|
||||
|
||||
|
||||
/*
|
||||
* This component encapsulates the Ethernet link glue needed to provide
|
||||
* one (!) network link through the USB gadget stack, normally "usb0".
|
||||
*
|
||||
* The control and data models are handled by the function driver which
|
||||
* connects to this code; such as CDC Ethernet (ECM or EEM),
|
||||
* "CDC Subset", or RNDIS. That includes all descriptor and endpoint
|
||||
* management.
|
||||
*
|
||||
* Link level addressing is handled by this component using module
|
||||
* parameters; if no such parameters are provided, random link level
|
||||
* addresses are used. Each end of the link uses one address. The
|
||||
* host end address is exported in various ways, and is often recorded
|
||||
* in configuration databases.
|
||||
*
|
||||
* The driver which assembles each configuration using such a link is
|
||||
* responsible for ensuring that each configuration includes at most one
|
||||
* instance of is network link. (The network layer provides ways for
|
||||
* this single "physical" link to be used by multiple virtual links.)
|
||||
*
|
||||
* This utilities is based on Ethernet-over-USB link layer utilities and
|
||||
* contains MSM specific implementation.
|
||||
*/
|
||||
|
||||
#define UETH__VERSION "29-May-2008"
|
||||
|
||||
struct eth_qc_dev {
|
||||
/* lock is held while accessing port_usb
|
||||
* or updating its backlink port_usb->ioport
|
||||
*/
|
||||
spinlock_t lock;
|
||||
struct qc_gether *port_usb;
|
||||
|
||||
struct net_device *net;
|
||||
struct usb_gadget *gadget;
|
||||
|
||||
unsigned header_len;
|
||||
|
||||
bool zlp;
|
||||
u8 host_mac[ETH_ALEN];
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#undef DBG
|
||||
#undef VDBG
|
||||
#undef ERROR
|
||||
#undef INFO
|
||||
|
||||
#define xprintk(d, level, fmt, args...) \
|
||||
printk(level "%s: " fmt , (d)->net->name , ## args)
|
||||
|
||||
#ifdef DEBUG
|
||||
#undef DEBUG
|
||||
#define DBG(dev, fmt, args...) \
|
||||
xprintk(dev , KERN_DEBUG , fmt , ## args)
|
||||
#else
|
||||
#define DBG(dev, fmt, args...) \
|
||||
do { } while (0)
|
||||
#endif /* DEBUG */
|
||||
|
||||
#ifdef VERBOSE_DEBUG
|
||||
#define VDBG DBG
|
||||
#else
|
||||
#define VDBG(dev, fmt, args...) \
|
||||
do { } while (0)
|
||||
#endif /* DEBUG */
|
||||
|
||||
#define ERROR(dev, fmt, args...) \
|
||||
xprintk(dev , KERN_ERR , fmt , ## args)
|
||||
#define INFO(dev, fmt, args...) \
|
||||
xprintk(dev , KERN_INFO , fmt , ## args)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* NETWORK DRIVER HOOKUP (to the layer above this driver) */
|
||||
static int ueth_qc_change_mtu(struct net_device *net, int new_mtu)
|
||||
{
|
||||
struct eth_qc_dev *dev = netdev_priv(net);
|
||||
unsigned long flags;
|
||||
int status = 0;
|
||||
|
||||
/* don't change MTU on "live" link (peer won't know) */
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
if (dev->port_usb)
|
||||
status = -EBUSY;
|
||||
else if (new_mtu <= ETH_HLEN || new_mtu > ETH_FRAME_LEN)
|
||||
status = -ERANGE;
|
||||
else
|
||||
net->mtu = new_mtu;
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void eth_qc_get_drvinfo(struct net_device *net,
|
||||
struct ethtool_drvinfo *p)
|
||||
{
|
||||
struct eth_qc_dev *dev = netdev_priv(net);
|
||||
|
||||
strlcpy(p->driver, "g_qc_ether", sizeof p->driver);
|
||||
strlcpy(p->version, UETH__VERSION, sizeof p->version);
|
||||
strlcpy(p->fw_version, dev->gadget->name, sizeof p->fw_version);
|
||||
strlcpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof p->bus_info);
|
||||
}
|
||||
|
||||
static const struct ethtool_ops qc_ethtool_ops = {
|
||||
.get_drvinfo = eth_qc_get_drvinfo,
|
||||
.get_link = ethtool_op_get_link,
|
||||
};
|
||||
|
||||
static netdev_tx_t eth_qc_start_xmit(struct sk_buff *skb,
|
||||
struct net_device *net)
|
||||
{
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
static int eth_qc_open(struct net_device *net)
|
||||
{
|
||||
struct eth_qc_dev *dev = netdev_priv(net);
|
||||
struct qc_gether *link;
|
||||
|
||||
DBG(dev, "%s\n", __func__);
|
||||
if (netif_carrier_ok(dev->net)) {
|
||||
/* Force the netif to send the RTM_NEWLINK event
|
||||
* that in use to notify on the USB cable status.
|
||||
*/
|
||||
netif_carrier_off(dev->net);
|
||||
netif_carrier_on(dev->net);
|
||||
netif_wake_queue(dev->net);
|
||||
}
|
||||
|
||||
spin_lock_irq(&dev->lock);
|
||||
link = dev->port_usb;
|
||||
if (link && link->open)
|
||||
link->open(link);
|
||||
spin_unlock_irq(&dev->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eth_qc_stop(struct net_device *net)
|
||||
{
|
||||
struct eth_qc_dev *dev = netdev_priv(net);
|
||||
unsigned long flags;
|
||||
struct qc_gether *link = dev->port_usb;
|
||||
|
||||
VDBG(dev, "%s\n", __func__);
|
||||
netif_stop_queue(net);
|
||||
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
if (dev->port_usb && link->close)
|
||||
link->close(link);
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */
|
||||
static char *qc_dev_addr;
|
||||
module_param(qc_dev_addr, charp, S_IRUGO);
|
||||
MODULE_PARM_DESC(qc_dev_addr, "QC Device Ethernet Address");
|
||||
|
||||
/* this address is invisible to ifconfig */
|
||||
static char *qc_host_addr;
|
||||
module_param(qc_host_addr, charp, S_IRUGO);
|
||||
MODULE_PARM_DESC(qc_host_addr, "QC Host Ethernet Address");
|
||||
|
||||
static int get_qc_ether_addr(const char *str, u8 *dev_addr)
|
||||
{
|
||||
if (str) {
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
unsigned char num;
|
||||
|
||||
if ((*str == '.') || (*str == ':'))
|
||||
str++;
|
||||
num = hex_to_bin(*str++) << 4;
|
||||
num |= hex_to_bin(*str++);
|
||||
dev_addr[i] = num;
|
||||
}
|
||||
if (is_valid_ether_addr(dev_addr))
|
||||
return 0;
|
||||
}
|
||||
random_ether_addr(dev_addr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct net_device_ops eth_qc_netdev_ops = {
|
||||
.ndo_open = eth_qc_open,
|
||||
.ndo_stop = eth_qc_stop,
|
||||
.ndo_start_xmit = eth_qc_start_xmit,
|
||||
.ndo_change_mtu = ueth_qc_change_mtu,
|
||||
.ndo_set_mac_address = eth_mac_addr,
|
||||
.ndo_validate_addr = eth_validate_addr,
|
||||
};
|
||||
|
||||
static struct device_type qc_gadget_type = {
|
||||
.name = "gadget",
|
||||
};
|
||||
|
||||
void gether_qc_get_macs(u8 dev_mac[ETH_ALEN], u8 host_mac[ETH_ALEN])
|
||||
{
|
||||
if (get_qc_ether_addr(qc_dev_addr, dev_mac))
|
||||
pr_debug("using random dev_mac ethernet address\n");
|
||||
if (get_qc_ether_addr(qc_host_addr, host_mac))
|
||||
pr_debug("using random host_mac ethernet address\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* gether_qc_setup - initialize one ethernet-over-usb link
|
||||
* @g: gadget to associated with these links
|
||||
* @ethaddr: NULL, or a buffer in which the ethernet address of the
|
||||
* host side of the link is recorded
|
||||
* Context: may sleep
|
||||
*
|
||||
* This sets up the single network link that may be exported by a
|
||||
* gadget driver using this framework. The link layer addresses are
|
||||
* set up using module parameters.
|
||||
*
|
||||
* Returns negative errno, or zero on success
|
||||
*/
|
||||
int gether_qc_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN])
|
||||
{
|
||||
return gether_qc_setup_name(g, ethaddr, "usb");
|
||||
}
|
||||
|
||||
/**
|
||||
* gether_qc_setup_name - initialize one ethernet-over-usb link
|
||||
* @g: gadget to associated with these links
|
||||
* @ethaddr: NULL, or a buffer in which the ethernet address of the
|
||||
* host side of the link is recorded
|
||||
* @netname: name for network device (for example, "usb")
|
||||
* Context: may sleep
|
||||
*
|
||||
* This sets up the single network link that may be exported by a
|
||||
* gadget driver using this framework. The link layer addresses are
|
||||
* set up using module parameters.
|
||||
*
|
||||
* Returns negative errno, or zero on success
|
||||
*/
|
||||
int gether_qc_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
|
||||
const char *netname)
|
||||
{
|
||||
struct eth_qc_dev *dev;
|
||||
struct net_device *net;
|
||||
int status;
|
||||
|
||||
net = alloc_etherdev(sizeof *dev);
|
||||
if (!net)
|
||||
return -ENOMEM;
|
||||
|
||||
dev = netdev_priv(net);
|
||||
spin_lock_init(&dev->lock);
|
||||
|
||||
/* network device setup */
|
||||
dev->net = net;
|
||||
snprintf(net->name, sizeof(net->name), "%s%%d", netname);
|
||||
|
||||
if (get_qc_ether_addr(qc_dev_addr, net->dev_addr))
|
||||
dev_warn(&g->dev,
|
||||
"using random %s ethernet address\n", "self");
|
||||
if (get_qc_ether_addr(qc_host_addr, dev->host_mac))
|
||||
dev_warn(&g->dev,
|
||||
"using random %s ethernet address\n", "host");
|
||||
|
||||
if (ethaddr)
|
||||
memcpy(ethaddr, dev->host_mac, ETH_ALEN);
|
||||
|
||||
net->netdev_ops = ð_qc_netdev_ops;
|
||||
net->ethtool_ops = &qc_ethtool_ops;
|
||||
|
||||
netif_carrier_off(net);
|
||||
|
||||
dev->gadget = g;
|
||||
SET_NETDEV_DEV(net, &g->dev);
|
||||
SET_NETDEV_DEVTYPE(net, &qc_gadget_type);
|
||||
|
||||
status = register_netdev(net);
|
||||
if (status < 0) {
|
||||
dev_dbg(&g->dev, "register_netdev failed, %d\n", status);
|
||||
free_netdev(net);
|
||||
} else {
|
||||
INFO(dev, "MAC %pM\n", net->dev_addr);
|
||||
INFO(dev, "HOST MAC %pM\n", dev->host_mac);
|
||||
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* gether_qc_cleanup_name - remove Ethernet-over-USB device
|
||||
* @netname: name for network device (for example, "usb")
|
||||
* Context: may sleep
|
||||
*
|
||||
* This is called to free all resources allocated by @gether_qc_setup().
|
||||
*/
|
||||
void gether_qc_cleanup_name(const char *netname)
|
||||
{
|
||||
struct net_device *net_dev;
|
||||
|
||||
/* Extract the eth_qc_dev from the net device */
|
||||
net_dev = dev_get_by_name(&init_net, netname);
|
||||
|
||||
if (net_dev) {
|
||||
dev_put(net_dev);
|
||||
unregister_netdev(net_dev);
|
||||
free_netdev(net_dev);
|
||||
}
|
||||
}
|
||||
|
||||
struct net_device *gether_qc_get_net(const char *netname)
|
||||
{
|
||||
struct net_device *net_dev;
|
||||
|
||||
net_dev = dev_get_by_name(&init_net, netname);
|
||||
if (!net_dev)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
/*
|
||||
* Decrement net_dev refcount as it was incremented in
|
||||
* dev_get_by_name().
|
||||
*/
|
||||
dev_put(net_dev);
|
||||
return net_dev;
|
||||
}
|
||||
/**
|
||||
* gether_qc_connect_name - notify network layer that USB link
|
||||
* is active
|
||||
* @link: the USB link, set up with endpoints, descriptors matching
|
||||
* current device speed, and any framing wrapper(s) set up.
|
||||
* @netname: name for network device (for example, "usb")
|
||||
* Context: irqs blocked
|
||||
* @netif_enable: if true, net interface will be turned on
|
||||
*
|
||||
* This is called to let the network layer know the connection
|
||||
* is active ("carrier detect").
|
||||
*/
|
||||
struct net_device *gether_qc_connect_name(struct qc_gether *link,
|
||||
const char *netname, bool netif_enable)
|
||||
{
|
||||
struct net_device *net_dev;
|
||||
struct eth_qc_dev *dev;
|
||||
|
||||
/* Extract the eth_qc_dev from the net device */
|
||||
net_dev = dev_get_by_name(&init_net, netname);
|
||||
if (!net_dev)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
dev_put(net_dev);
|
||||
dev = netdev_priv(net_dev);
|
||||
|
||||
if (!dev)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
dev->zlp = link->is_zlp_ok;
|
||||
dev->header_len = link->header_len;
|
||||
|
||||
spin_lock(&dev->lock);
|
||||
dev->port_usb = link;
|
||||
link->ioport = dev;
|
||||
if (netif_running(dev->net)) {
|
||||
if (link->open)
|
||||
link->open(link);
|
||||
} else {
|
||||
if (link->close)
|
||||
link->close(link);
|
||||
}
|
||||
spin_unlock(&dev->lock);
|
||||
|
||||
if (netif_enable) {
|
||||
netif_carrier_on(dev->net);
|
||||
if (netif_running(dev->net))
|
||||
netif_wake_queue(dev->net);
|
||||
}
|
||||
|
||||
return dev->net;
|
||||
}
|
||||
|
||||
/**
|
||||
* gether_qc_disconnect_name - notify network layer that USB
|
||||
* link is inactive
|
||||
* @link: the USB link, on which gether_connect() was called
|
||||
* @netname: name for network device (for example, "usb")
|
||||
* Context: irqs blocked
|
||||
*
|
||||
* This is called to let the network layer know the connection
|
||||
* went inactive ("no carrier").
|
||||
*
|
||||
* On return, the state is as if gether_connect() had never been called.
|
||||
*/
|
||||
void gether_qc_disconnect_name(struct qc_gether *link, const char *netname)
|
||||
{
|
||||
struct net_device *net_dev;
|
||||
struct eth_qc_dev *dev;
|
||||
|
||||
/* Extract the eth_qc_dev from the net device */
|
||||
net_dev = dev_get_by_name(&init_net, netname);
|
||||
if (!net_dev)
|
||||
return;
|
||||
|
||||
dev_put(net_dev);
|
||||
dev = netdev_priv(net_dev);
|
||||
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
DBG(dev, "%s\n", __func__);
|
||||
|
||||
netif_stop_queue(dev->net);
|
||||
netif_carrier_off(dev->net);
|
||||
|
||||
spin_lock(&dev->lock);
|
||||
dev->port_usb = NULL;
|
||||
link->ioport = NULL;
|
||||
spin_unlock(&dev->lock);
|
||||
}
|
101
drivers/usb/gadget/function/u_qc_ether.h
Normal file
101
drivers/usb/gadget/function/u_qc_ether.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* u_qc_ether.h -- interface to USB gadget "ethernet link" utilities
|
||||
*
|
||||
* Copyright (C) 2003-2005,2008 David Brownell
|
||||
* Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
* Copyright (c) 2012-2014, 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
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef __U_QC_ETHER_H
|
||||
#define __U_QC_ETHER_H
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/usb/cdc.h>
|
||||
|
||||
#include "gadget_chips.h"
|
||||
|
||||
|
||||
/*
|
||||
* This represents the USB side of an "ethernet" link, managed by a USB
|
||||
* function which provides control and (maybe) framing. Two functions
|
||||
* in different configurations could share the same ethernet link/netdev,
|
||||
* using different host interaction models.
|
||||
*
|
||||
* There is a current limitation that only one instance of this link may
|
||||
* be present in any given configuration. When that's a problem, network
|
||||
* layer facilities can be used to package multiple logical links on this
|
||||
* single "physical" one.
|
||||
*
|
||||
* This function is based on Ethernet-over-USB link layer utilities and
|
||||
* contains MSM specific implementation.
|
||||
*/
|
||||
|
||||
struct qc_gether {
|
||||
struct usb_function func;
|
||||
|
||||
/* updated by gether_{connect,disconnect} */
|
||||
struct eth_qc_dev *ioport;
|
||||
|
||||
/* endpoints handle full and/or high speeds */
|
||||
struct usb_ep *in_ep;
|
||||
struct usb_ep *out_ep;
|
||||
|
||||
bool is_zlp_ok;
|
||||
|
||||
u16 cdc_filter;
|
||||
|
||||
/* hooks for added framing, as needed for RNDIS and EEM. */
|
||||
u32 header_len;
|
||||
|
||||
struct sk_buff *(*wrap)(struct qc_gether *port,
|
||||
struct sk_buff *skb);
|
||||
int (*unwrap)(struct qc_gether *port,
|
||||
struct sk_buff *skb,
|
||||
struct sk_buff_head *list);
|
||||
|
||||
/* called on network open/close */
|
||||
void (*open)(struct qc_gether *);
|
||||
void (*close)(struct qc_gether *);
|
||||
};
|
||||
|
||||
/* netdev setup/teardown as directed by the gadget driver */
|
||||
int gether_qc_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]);
|
||||
void gether_qc_cleanup_name(const char *netname);
|
||||
/* variant of gether_setup that allows customizing network device name */
|
||||
int gether_qc_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
|
||||
const char *netname);
|
||||
|
||||
/* connect/disconnect is handled by individual functions */
|
||||
struct net_device *gether_qc_connect_name(struct qc_gether *link,
|
||||
const char *netname, bool netif_enable);
|
||||
struct net_device *gether_qc_get_net(const char *netname);
|
||||
void gether_qc_disconnect_name(struct qc_gether *link, const char *netname);
|
||||
|
||||
/* each configuration may bind one instance of an ethernet link */
|
||||
int ecm_qc_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
|
||||
char *xport_name);
|
||||
|
||||
int
|
||||
rndis_qc_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
|
||||
u32 vendorID, const char *manufacturer,
|
||||
u8 maxPktPerXfer, u8 pkt_alignment_factor,
|
||||
char *xport_name);
|
||||
|
||||
void gether_qc_get_macs(u8 dev_mac[ETH_ALEN], u8 host_mac[ETH_ALEN]);
|
||||
|
||||
#endif /* __U_QC_ETHER_H */
|
79
drivers/usb/gadget/function/u_rmnet.h
Normal file
79
drivers/usb/gadget/function/u_rmnet.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
/* Copyright (c) 2011-2015, 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.
|
||||
*/
|
||||
|
||||
#ifndef __U_RMNET_H
|
||||
#define __U_RMNET_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/usb/cdc.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
struct rmnet_ctrl_pkt {
|
||||
void *buf;
|
||||
int len;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct grmnet {
|
||||
struct usb_function func;
|
||||
|
||||
struct usb_gadget *gadget;
|
||||
struct usb_ep *in;
|
||||
struct usb_ep *out;
|
||||
|
||||
/* to usb host, aka laptop, windows pc etc. Will
|
||||
* be filled by usb driver of rmnet functionality
|
||||
*/
|
||||
int (*send_cpkt_response)(void *g, void *buf, size_t len);
|
||||
|
||||
/* to modem, and to be filled by driver implementing
|
||||
* control function
|
||||
*/
|
||||
int (*send_encap_cmd)(u8 port_num, void *buf, size_t len);
|
||||
|
||||
void (*notify_modem)(void *g, u8 port_num, int cbits);
|
||||
|
||||
void (*disconnect)(struct grmnet *g);
|
||||
void (*connect)(struct grmnet *g);
|
||||
};
|
||||
|
||||
#define NR_QTI_PORTS (NR_RMNET_PORTS + NR_DPL_PORTS)
|
||||
#define NR_RMNET_PORTS 4
|
||||
#define NR_DPL_PORTS 1
|
||||
|
||||
enum ctrl_client {
|
||||
FRMNET_CTRL_CLIENT,
|
||||
GPS_CTRL_CLIENT,
|
||||
|
||||
NR_CTRL_CLIENTS
|
||||
};
|
||||
|
||||
int gbam_setup(unsigned int no_bam_port);
|
||||
int gbam2bam_setup(unsigned int no_bam2bam_port);
|
||||
void gbam_cleanup(void);
|
||||
int gbam_connect(struct grmnet *gr, u8 port_num,
|
||||
enum transport_type trans, u8 src_connection_idx,
|
||||
u8 dst_connection_idx);
|
||||
void gbam_disconnect(struct grmnet *gr, u8 port_num,
|
||||
enum transport_type trans);
|
||||
void gbam_suspend(struct grmnet *gr, u8 port_num, enum transport_type trans);
|
||||
void gbam_resume(struct grmnet *gr, u8 port_num, enum transport_type trans);
|
||||
int gbam_mbim_setup(void);
|
||||
int gbam_mbim_connect(struct usb_gadget *g, struct usb_ep *in,
|
||||
struct usb_ep *out);
|
||||
void gbam_mbim_disconnect(void);
|
||||
int gsmd_ctrl_connect(struct grmnet *gr, int port_num);
|
||||
void gsmd_ctrl_disconnect(struct grmnet *gr, u8 port_num);
|
||||
int gsmd_ctrl_setup(enum ctrl_client client_num, unsigned int count,
|
||||
u8 *first_port_idx);
|
||||
#endif /* __U_RMNET_H*/
|
Loading…
Add table
Reference in a new issue