usb: gadget: MBIM function driver for QCOM chipsets
This driver implements Mobile Broadband Interface Model protocol. Control messages are transferred through QBI module running in user space and communicating with mbim using file operations interface android_mbim. Data is transferred on SPS, Bam-to-Bam. Change-Id: Ifc4b3f7e21b1a0dac377af272f9ae922ec9c7e2e Signed-off-by: Anna Perel <aperel@codeaurora.org>
This commit is contained in:
@@ -67,6 +67,8 @@
|
|||||||
#include "f_rndis.c"
|
#include "f_rndis.c"
|
||||||
#include "rndis.c"
|
#include "rndis.c"
|
||||||
#include "u_ether.c"
|
#include "u_ether.c"
|
||||||
|
#include "u_bam_data.c"
|
||||||
|
#include "f_mbim.c"
|
||||||
|
|
||||||
MODULE_AUTHOR("Mike Lockwood");
|
MODULE_AUTHOR("Mike Lockwood");
|
||||||
MODULE_DESCRIPTION("Android Composite USB Driver");
|
MODULE_DESCRIPTION("Android Composite USB Driver");
|
||||||
@@ -619,6 +621,35 @@ static struct android_usb_function rmnet_function = {
|
|||||||
.attributes = rmnet_function_attributes,
|
.attributes = rmnet_function_attributes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* MBIM - used with BAM */
|
||||||
|
#define MAX_MBIM_INSTANCES 1
|
||||||
|
|
||||||
|
static int mbim_function_init(struct android_usb_function *f,
|
||||||
|
struct usb_composite_dev *cdev)
|
||||||
|
{
|
||||||
|
return mbim_init(MAX_MBIM_INSTANCES);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mbim_function_cleanup(struct android_usb_function *f)
|
||||||
|
{
|
||||||
|
fmbim_cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mbim_function_bind_config(struct android_usb_function *f,
|
||||||
|
struct usb_configuration *c)
|
||||||
|
{
|
||||||
|
return mbim_bind_config(c, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct android_usb_function mbim_function = {
|
||||||
|
.name = "usb_mbim",
|
||||||
|
.cleanup = mbim_function_cleanup,
|
||||||
|
.bind_config = mbim_function_bind_config,
|
||||||
|
.init = mbim_function_init,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/* DIAG */
|
/* DIAG */
|
||||||
static char diag_clients[32]; /*enabled DIAG clients- "diag[,diag_mdm]" */
|
static char diag_clients[32]; /*enabled DIAG clients- "diag[,diag_mdm]" */
|
||||||
static ssize_t clients_store(
|
static ssize_t clients_store(
|
||||||
@@ -1055,6 +1086,7 @@ static struct android_usb_function accessory_function = {
|
|||||||
|
|
||||||
|
|
||||||
static struct android_usb_function *supported_functions[] = {
|
static struct android_usb_function *supported_functions[] = {
|
||||||
|
&mbim_function,
|
||||||
&rmnet_smd_function,
|
&rmnet_smd_function,
|
||||||
&rmnet_sdio_function,
|
&rmnet_sdio_function,
|
||||||
&rmnet_smd_sdio_function,
|
&rmnet_smd_sdio_function,
|
||||||
|
|||||||
1804
drivers/usb/gadget/f_mbim.c
Normal file
1804
drivers/usb/gadget/f_mbim.c
Normal file
File diff suppressed because it is too large
Load Diff
328
drivers/usb/gadget/u_bam_data.c
Normal file
328
drivers/usb/gadget/u_bam_data.c
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
/* Copyright (c) 2012, Code Aurora Forum. 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef pr_fmt
|
||||||
|
#undef pr_fmt
|
||||||
|
#endif
|
||||||
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/bitops.h>
|
||||||
|
#include <linux/usb/gadget.h>
|
||||||
|
|
||||||
|
#include <mach/bam_dmux.h>
|
||||||
|
#include <mach/usb_gadget_xport.h>
|
||||||
|
#include <mach/usb_bam.h>
|
||||||
|
|
||||||
|
#define BAM2BAM_DATA_N_PORTS 1
|
||||||
|
|
||||||
|
static struct workqueue_struct *bam_data_wq;
|
||||||
|
static int n_bam2bam_data_ports;
|
||||||
|
|
||||||
|
#define SPS_PARAMS_SPS_MODE BIT(5)
|
||||||
|
#define SPS_PARAMS_TBE BIT(6)
|
||||||
|
#define MSM_VENDOR_ID BIT(16)
|
||||||
|
|
||||||
|
struct data_port {
|
||||||
|
struct usb_function func;
|
||||||
|
struct usb_ep *in;
|
||||||
|
struct usb_ep *out;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bam_data_ch_info {
|
||||||
|
unsigned long flags;
|
||||||
|
unsigned id;
|
||||||
|
|
||||||
|
struct bam_data_port *port;
|
||||||
|
struct work_struct write_tobam_w;
|
||||||
|
|
||||||
|
struct usb_request *rx_req;
|
||||||
|
struct usb_request *tx_req;
|
||||||
|
|
||||||
|
u8 src_pipe_idx;
|
||||||
|
u8 dst_pipe_idx;
|
||||||
|
u8 connection_idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bam_data_port {
|
||||||
|
unsigned port_num;
|
||||||
|
struct data_port *port_usb;
|
||||||
|
struct bam_data_ch_info data_ch;
|
||||||
|
|
||||||
|
struct work_struct connect_w;
|
||||||
|
struct work_struct disconnect_w;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bam_data_port *bam2bam_data_ports[BAM2BAM_DATA_N_PORTS];
|
||||||
|
|
||||||
|
/*------------data_path----------------------------*/
|
||||||
|
|
||||||
|
static void bam_data_endless_rx_complete(struct usb_ep *ep,
|
||||||
|
struct usb_request *req)
|
||||||
|
{
|
||||||
|
int status = req->status;
|
||||||
|
|
||||||
|
pr_info("status: %d\n", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bam_data_endless_tx_complete(struct usb_ep *ep,
|
||||||
|
struct usb_request *req)
|
||||||
|
{
|
||||||
|
int status = req->status;
|
||||||
|
|
||||||
|
pr_info("status: %d\n", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bam_data_start_endless_rx(struct bam_data_port *port)
|
||||||
|
{
|
||||||
|
struct bam_data_ch_info *d = &port->data_ch;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
status = usb_ep_queue(port->port_usb->out, d->rx_req, GFP_ATOMIC);
|
||||||
|
if (status)
|
||||||
|
pr_err("error enqueuing transfer, %d\n", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bam_data_start_endless_tx(struct bam_data_port *port)
|
||||||
|
{
|
||||||
|
struct bam_data_ch_info *d = &port->data_ch;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
status = usb_ep_queue(port->port_usb->in, d->tx_req, GFP_ATOMIC);
|
||||||
|
if (status)
|
||||||
|
pr_err("error enqueuing transfer, %d\n", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bam2bam_data_disconnect_work(struct work_struct *w)
|
||||||
|
{
|
||||||
|
struct bam_data_port *port =
|
||||||
|
container_of(w, struct bam_data_port, disconnect_w);
|
||||||
|
|
||||||
|
pr_info("Enter");
|
||||||
|
|
||||||
|
/* disable endpoints */
|
||||||
|
if (!port->port_usb || !port->port_usb->out || !port->port_usb->in) {
|
||||||
|
pr_err("port_usb->out/in == NULL. Exit");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
usb_ep_disable(port->port_usb->out);
|
||||||
|
usb_ep_disable(port->port_usb->in);
|
||||||
|
|
||||||
|
port->port_usb->in->driver_data = NULL;
|
||||||
|
port->port_usb->out->driver_data = NULL;
|
||||||
|
|
||||||
|
port->port_usb = 0;
|
||||||
|
|
||||||
|
pr_info("Exit");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bam2bam_data_connect_work(struct work_struct *w)
|
||||||
|
{
|
||||||
|
struct bam_data_port *port = container_of(w, struct bam_data_port,
|
||||||
|
connect_w);
|
||||||
|
struct bam_data_ch_info *d = &port->data_ch;
|
||||||
|
u32 sps_params;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pr_info("Enter");
|
||||||
|
|
||||||
|
ret = usb_bam_connect(d->connection_idx, &d->src_pipe_idx,
|
||||||
|
&d->dst_pipe_idx);
|
||||||
|
d->src_pipe_idx = 11;
|
||||||
|
d->dst_pipe_idx = 10;
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
pr_err("usb_bam_connect failed: err:%d\n", ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!port->port_usb) {
|
||||||
|
pr_err("port_usb is NULL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!port->port_usb->out) {
|
||||||
|
pr_err("port_usb->out (bulk out ep) is NULL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
d->rx_req = usb_ep_alloc_request(port->port_usb->out, GFP_KERNEL);
|
||||||
|
if (!d->rx_req)
|
||||||
|
return;
|
||||||
|
|
||||||
|
d->rx_req->context = port;
|
||||||
|
d->rx_req->complete = bam_data_endless_rx_complete;
|
||||||
|
d->rx_req->length = 0;
|
||||||
|
sps_params = (SPS_PARAMS_SPS_MODE | d->src_pipe_idx |
|
||||||
|
MSM_VENDOR_ID) & ~SPS_PARAMS_TBE;
|
||||||
|
d->rx_req->udc_priv = sps_params;
|
||||||
|
d->tx_req = usb_ep_alloc_request(port->port_usb->in, GFP_KERNEL);
|
||||||
|
if (!d->tx_req)
|
||||||
|
return;
|
||||||
|
|
||||||
|
d->tx_req->context = port;
|
||||||
|
d->tx_req->complete = bam_data_endless_tx_complete;
|
||||||
|
d->tx_req->length = 0;
|
||||||
|
sps_params = (SPS_PARAMS_SPS_MODE | d->dst_pipe_idx |
|
||||||
|
MSM_VENDOR_ID) & ~SPS_PARAMS_TBE;
|
||||||
|
d->tx_req->udc_priv = sps_params;
|
||||||
|
|
||||||
|
/* queue in & out requests */
|
||||||
|
bam_data_start_endless_rx(port);
|
||||||
|
bam_data_start_endless_tx(port);
|
||||||
|
|
||||||
|
pr_info("Done\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bam2bam_data_port_free(int portno)
|
||||||
|
{
|
||||||
|
kfree(bam2bam_data_ports[portno]);
|
||||||
|
bam2bam_data_ports[portno] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bam2bam_data_port_alloc(int portno)
|
||||||
|
{
|
||||||
|
struct bam_data_port *port = NULL;
|
||||||
|
struct bam_data_ch_info *d = NULL;
|
||||||
|
|
||||||
|
port = kzalloc(sizeof(struct bam_data_port), GFP_KERNEL);
|
||||||
|
if (!port)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
port->port_num = portno;
|
||||||
|
|
||||||
|
INIT_WORK(&port->connect_w, bam2bam_data_connect_work);
|
||||||
|
INIT_WORK(&port->disconnect_w, bam2bam_data_disconnect_work);
|
||||||
|
|
||||||
|
/* data ch */
|
||||||
|
d = &port->data_ch;
|
||||||
|
d->port = port;
|
||||||
|
bam2bam_data_ports[portno] = port;
|
||||||
|
|
||||||
|
pr_info("port:%p portno:%d\n", port, portno);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bam_data_disconnect(struct data_port *gr, u8 port_num)
|
||||||
|
{
|
||||||
|
struct bam_data_port *port;
|
||||||
|
struct bam_data_ch_info *d;
|
||||||
|
|
||||||
|
pr_info("dev:%p port#%d\n", gr, port_num);
|
||||||
|
|
||||||
|
if (port_num >= n_bam2bam_data_ports) {
|
||||||
|
pr_err("invalid bam2bam portno#%d\n", port_num);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gr) {
|
||||||
|
pr_err("mbim data port is null\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
port = bam2bam_data_ports[port_num];
|
||||||
|
|
||||||
|
d = &port->data_ch;
|
||||||
|
port->port_usb = gr;
|
||||||
|
|
||||||
|
queue_work(bam_data_wq, &port->disconnect_w);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bam_data_connect(struct data_port *gr, u8 port_num,
|
||||||
|
u8 connection_idx)
|
||||||
|
{
|
||||||
|
struct bam_data_port *port;
|
||||||
|
struct bam_data_ch_info *d;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pr_info("dev:%p port#%d\n", gr, port_num);
|
||||||
|
|
||||||
|
if (port_num >= n_bam2bam_data_ports) {
|
||||||
|
pr_err("invalid portno#%d\n", port_num);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gr) {
|
||||||
|
pr_err("mbim data port is null\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
port = bam2bam_data_ports[port_num];
|
||||||
|
|
||||||
|
d = &port->data_ch;
|
||||||
|
|
||||||
|
ret = usb_ep_enable(gr->in);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("usb_ep_enable failed eptype:IN ep:%p", gr->in);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
gr->in->driver_data = port;
|
||||||
|
|
||||||
|
ret = usb_ep_enable(gr->out);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("usb_ep_enable failed eptype:OUT ep:%p", gr->out);
|
||||||
|
gr->in->driver_data = 0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
gr->out->driver_data = port;
|
||||||
|
|
||||||
|
port->port_usb = gr;
|
||||||
|
|
||||||
|
d->connection_idx = connection_idx;
|
||||||
|
|
||||||
|
queue_work(bam_data_wq, &port->connect_w);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bam_data_setup(unsigned int no_bam2bam_port)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pr_info("requested %d BAM2BAM ports", no_bam2bam_port);
|
||||||
|
|
||||||
|
if (!no_bam2bam_port || no_bam2bam_port > BAM2BAM_DATA_N_PORTS) {
|
||||||
|
pr_err("Invalid num of ports count:%d\n", no_bam2bam_port);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bam_data_wq = alloc_workqueue("k_bam_data",
|
||||||
|
WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
|
||||||
|
if (!bam_data_wq) {
|
||||||
|
pr_err("Failed to create workqueue\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < no_bam2bam_port; i++) {
|
||||||
|
n_bam2bam_data_ports++;
|
||||||
|
ret = bam2bam_data_port_alloc(i);
|
||||||
|
if (ret) {
|
||||||
|
n_bam2bam_data_ports--;
|
||||||
|
pr_err("Failed to alloc port:%d\n", i);
|
||||||
|
goto free_bam_ports;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
free_bam_ports:
|
||||||
|
for (i = 0; i < n_bam2bam_data_ports; i++)
|
||||||
|
bam2bam_data_port_free(i);
|
||||||
|
destroy_workqueue(bam_data_wq);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
#define USB_CDC_DMM_TYPE 0x14
|
#define USB_CDC_DMM_TYPE 0x14
|
||||||
#define USB_CDC_OBEX_TYPE 0x15
|
#define USB_CDC_OBEX_TYPE 0x15
|
||||||
#define USB_CDC_NCM_TYPE 0x1a
|
#define USB_CDC_NCM_TYPE 0x1a
|
||||||
|
#define USB_CDC_MBB_TYPE 0x1b /* mbb_desc */
|
||||||
|
|
||||||
/* "Header Functional Descriptor" from CDC spec 5.2.3.1 */
|
/* "Header Functional Descriptor" from CDC spec 5.2.3.1 */
|
||||||
struct usb_cdc_header_desc {
|
struct usb_cdc_header_desc {
|
||||||
@@ -187,6 +188,21 @@ struct usb_cdc_ncm_desc {
|
|||||||
__le16 bcdNcmVersion;
|
__le16 bcdNcmVersion;
|
||||||
__u8 bmNetworkCapabilities;
|
__u8 bmNetworkCapabilities;
|
||||||
} __attribute__ ((packed));
|
} __attribute__ ((packed));
|
||||||
|
|
||||||
|
/* "MBIM Functional Descriptor" */
|
||||||
|
struct usb_cdc_mbb_desc {
|
||||||
|
__u8 bLength;
|
||||||
|
__u8 bDescriptorType;
|
||||||
|
__u8 bDescriptorSubType;
|
||||||
|
|
||||||
|
__le16 bcdMbbVersion;
|
||||||
|
__le16 wMaxControlMessage;
|
||||||
|
__u8 bNumberFilters;
|
||||||
|
__u8 bMaxFilterSize;
|
||||||
|
__le16 wMaxSegmentSize;
|
||||||
|
__u8 bmNetworkCapabilities;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------*/
|
/*-------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -201,6 +217,7 @@ struct usb_cdc_ncm_desc {
|
|||||||
|
|
||||||
#define USB_CDC_SEND_ENCAPSULATED_COMMAND 0x00
|
#define USB_CDC_SEND_ENCAPSULATED_COMMAND 0x00
|
||||||
#define USB_CDC_GET_ENCAPSULATED_RESPONSE 0x01
|
#define USB_CDC_GET_ENCAPSULATED_RESPONSE 0x01
|
||||||
|
#define USB_CDC_RESET_FUNCTION 0x05
|
||||||
#define USB_CDC_REQ_SET_LINE_CODING 0x20
|
#define USB_CDC_REQ_SET_LINE_CODING 0x20
|
||||||
#define USB_CDC_REQ_GET_LINE_CODING 0x21
|
#define USB_CDC_REQ_GET_LINE_CODING 0x21
|
||||||
#define USB_CDC_REQ_SET_CONTROL_LINE_STATE 0x22
|
#define USB_CDC_REQ_SET_CONTROL_LINE_STATE 0x22
|
||||||
|
|||||||
Reference in New Issue
Block a user