Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ad7a3f4e authored by Jack Pham's avatar Jack Pham Committed by Matt Wagantall
Browse files

usb: gadget: Add snapshot of RMNET and QDSS function drivers



Add function drivers supporting RMNET and QDSS over USB peripheral.
This includes the HSIC, SMD, BAM transport functions as well as
changes to android.c to invoke them.

This snapshot is taken as of msm-3.14 commit 3bc54cf86b (Merge "msm:
camera: Add dummy sub module in sensor pipeline")

Signed-off-by: default avatarJack Pham <jackp@codeaurora.org>
parent b6d07e8f
Loading
Loading
Loading
Loading
+222 −0
Original line number Diff line number Diff line
Introduction
============

QUALCOMM MSM Interface (QMI) defines the interface between MSM and
attached Terminal Equipment (TE). RmNet interface is a new logical
device in QMI framework for data services. RmNet in accordance with
QMI architecture defines channels for control and data transfers and
for example it uses Data I/O channel for IP data transfer and control
I/O channel for QMI messaging (functionality similar to AT commands).
RmNet may be used in place of legacy USB modem interface.

Tethered networking is one of the function from MSM which can also be
supported using QMI protocol. There are other standard protocols exists
such as CDC-ECM and Windows proprietary RNDIS. On the host-side system,
the gadget rmnet device looks like a ethernet adapter.

Hardware description
====================

QMI is a messaging protocol to expose various functionalities of MSM
and one of the functionality to be tethered networking which is being
exposed over QMI using RmNet protocol. This usb gadget has one bulk-in,
one bulk-out and one interrupt-in endpoint.

Design:
=======
RmNet function driver design follows two approaches:

Approach 1:
-----------
Single function driver is used to communicate with
Modem(both for data and control). Most of the initial
MSM targets are following this approach.

The main disadvantage with this approach is there are
multiple RmNet drivers for any change in DATA and Control
Layer. There is no re-use in the code.

Approach 2:
-----------
RmNet driver is divided into 3 components

1. USB:
This component has the functionality to deal with composite layer.
Allocates Interfaces, Endpoints, listens to connect/disconnect
interrupts and gives connect/disconnect notifications to DATA and
CONTROL modules.

2. Data:
This component talks to modem to transfer IP data. Usually DATA
and CONTROL go over same channel. However, to achieve higher
data rates new transport channel for DATA may be used.

3. Control:
This component talks to modem to transfer rmnet control data.

Software description
====================
The RmNet suports following data and control transports:
as follows:
	1. SMD Interface
	2. SDIO Interface
	3. BAM Interface
	4. SMD Control Interface

SMD interface uses the Shared memory for the RmNet driver to communicate
with the MSM modem processor.
SDIO interface acts as a link for communication of RmNet driver with the
MDM modem processor.

USB INTERACTION:
----------------

The RmNet function driver binds with the USB using the struct usb_function.
The function is added using the usb_function_add().
Control Transfers: The RmNet handles two Class-specific control
transfers: SEND_ENCAPSULATED_COMMAND and GET_ENCAPSULATED_RESPONSE.
The asynchronous protocol QMI which consists of the QMI requests/responses
is used for handling the transfers between the RmNet and the Host where the
host sends a new QMI request before receiving the response for the current
QMI request.

Control & Data flow:
1. Host issues a SEND_ENCAPSULATED command to send a QMI request.
SMD: If the SMD control channel has enough room to accomodate a QMI request,
it is written into the SMD buffer. Otherwise, append/add that request to
qmi_request queue. A tasklet is scheduled to drain all QMI requests in
qmi_request queue.
SDIO: Add each request in the qmi_request queue and is processed until
the queue is empty.

2. Append/Add QMI response from modem to qmi_response queue.
A notification on an interrupt end point is used to communicate the QMI
response to host.

3. Host issues a GET_ENCAPSULATED command to retrieve the QMI response.
The response from qmi_response queue will be sent to the host.

4. After the connection is fully established data can be sent to
bulk-out endpoint and data can be received from bulk-in endpoint.

5. Host can send QMI requests even after the connection is established.

RmNet gadget driver is completely unaware of QMI/IP protocol. It just
acts as a bridge between the modem and the PC.

All the request/response queues in the driver can be accessed either
from tasklet/workqueue or from interrupt context (either usb or smd/sdio
interrupt handler). Hence a spinlock is used to protect all data/control req
lists.


SMD Interface:
--------------

1. Each QMI request/response can at most be 2048 bytes. Eight 2KB buffers
are allocated using kmalloc for storing maximum of 8 requests/responses.

2. Four 2KB buffers are allocated using kmalloc for data transfers on
each bulk endpoint.

Data structures:
struct qmi_buf		-	Buffer to handle QMI requests & responses
struct rmnet_smd_info	-	Control & Data SMD channel private data
struct rmnet_dev	-	Endpoint and driver specific data

Workqueues:
rmnet_connect_work	-	Called on device connection.
				Opens SMD channels; enables endpoints
rmnet_disconnect_work	-	Called on device disconnection.
				Closes SMD channels.

Tasklets:
rmnet_control_rx_tlet
rmnet_control_tx_tlet	-	Control transfer data reception and transmission
				handler

rmnet_data_rx_tlet
rmnet_data_tx_tlet	-	Data transfer data reception and transmission handler


SMD control interface
----------------------
This function driver implements exchnage of control informtion with
modem over SMD. Uses smd_read/write commands to read or write rmnet
ctrl packets. Exposes a call back function to usb component to write
control packet and at the same time call a call back usb component
callback to send data to usb host.

Data structures and Interfaces are very similar to control interfaces
explained in "SMD Interface"

BAM MUX interface
------------------
BAM Mux interface is very similar to SDIO MUX interface. However there
are differences in the way BAM and SDIO operate but all of the details
are masked by MUX Driver.

Refer to the SDIO interfaces for more information on data structures

SDIO Interface:
---------------

1. Each QMI request/response buffer is allocated depending on the size
of data to be transmitted for the request/response.

2. A 2KB network buffer is allocated for data transfer on bulk-out
endpoint. The SDIO allocates the required buffer for data transfers
on an bulk-in endpoint.

Data structures:
struct rmnet_sdio_qmi_buf      -       Buffer to handle QMI requests/responses.
struct rmnet_dev               -       Endpoint and driver specific data

Workqueues:
rmnet_connect_work             -       Device connection handler. Opens SDIO
                                       channels; enables and allocate buffer for
                                       endpoints
rmnet_disconnect_work          -       Device disconnection handler. Closes
                                       SDIO channels; Frees allocated buffers.
rmnet_control_rx_work          -       Control data reception handler.
rmnet_data_rx_work             -       Network data reception handler.


Two SMD/SDIO channels (control and data) are used as communication channels
between Modem and Apps processor. The driver opens the SMD/SDIO channels
on USB device connection. Data is either read from/written to the channels
as one complete packet.

SMD/SDIO provides a notification whenever the Modem processor completes
read/write of a packet.  Based on these SMD/SDIO notifications all the
pending read/write requests will be handled. Tasklets(SMD)/Workqueues(SDIO)
are used to get the requests done.

There is another variant of rmnet driver called rmnet_smd_sdio which is used
on some boards.  This driver allows the transport (SMD/SDIO) to be chosen
at runtime. This is required because either MDM processor or MODEM processor
is only active at a time for data transfers. As SMD and SDIO interfaces
are different, different endpoint completion handlers are used. This driver
leverage the existing rmnet over smd and rmnet over sdio drivers. The control
messages (QMI) always routed over SDIO. After the control messages exchange,
user space will come to know about the available data transport (SMD/SDIO).
User space notify the same to driver and the corresponding transport is
activated. It is assumed that transport will not change while a USB cable
is connected.

Rmnet over SMD and rmnet over SDIO doesn't expose any of its interfaces to
either kernelspace or userspace. But rmnet over smd/sdio expose a sysfs
interface for userspace to notify the available transport to driver.

The sysfs file can be found at
/sys/class/usb_composite/rmnet_smd_sdio/transport

The below command activates the SMD transport
echo 0 > /sys/class/usb_composite/rmnet_smd_sdio/transport

The below command activates the SDIO transport
echo 1 > /sys/class/usb_composite/rmnet_smd_sdio/transport

-EINVAL is returned if a write is attempted to transport when a USB cable
is not connected.
+27 −0
Original line number Diff line number Diff line
Introduction
============

Gadget serial driver is divided into two parts.
1. f_serial.c : Interacts with USB Gadget Layer
2. u_serial.c : Interacts with TTY Layer

Gadget smd driver adds capability to interact with smd layer in
case modem device is inter-connected with smd interface.

S/W Description
===============
Gadget smd driver is a simple bridge driver between usb serial
gadget and smd abstraction layer. It registers with smd
abstraction layer with  notification call back and provides
USB connect/disconnect call backs usb gadget serial driver.


S/W Control Flow:
=================
USB SMD driver registers w/ SMD driver and provides notification
call back. SMD Driver calls this call back whenever DATA is available
to read, buffer is available to write or modem control signals changed.
Upon receiving notification from SMD driver, USB driver appropriately
schedules read/write works. In case of control singals, USB driver
notifies gadget component with changed control information.
+309 −0
Original line number Diff line number Diff line
@@ -38,6 +38,15 @@

#include "u_fs.h"
#include "f_diag.c"
#include "f_qdss.c"
#include "f_rmnet.c"
#include "u_smd.c"
#include "u_bam.c"
#include "u_rmnet_ctrl_smd.c"
#include "u_rmnet_ctrl_qti.c"
#include "u_ctrl_hsic.c"
#include "u_data_hsic.c"
#include "u_data_ipa.c"
#include "f_mtp.c"
#include "f_accessory.c"
#define USB_ETH_RNDIS y
@@ -773,6 +782,143 @@ static struct android_usb_function acm_function = {
	.attributes	= acm_function_attributes,
};

/*rmnet transport string format(per port):"ctrl0,data0,ctrl1,data1..." */
#define MAX_XPORT_STR_LEN 50
static char rmnet_transports[MAX_XPORT_STR_LEN];

/*rmnet transport name string - "rmnet_hsic[,rmnet_hsusb]" */
static char rmnet_xport_names[MAX_XPORT_STR_LEN];

/*qdss transport string format(per port):"bam [, hsic]" */
static char qdss_transports[MAX_XPORT_STR_LEN];

/*qdss transport name string - "qdss_bam [, qdss_hsic]" */
static char qdss_xport_names[MAX_XPORT_STR_LEN];

/*qdss debug interface setting 0: disable   1:enable */
static bool qdss_debug_intf;

static void rmnet_function_cleanup(struct android_usb_function *f)
{
	frmnet_cleanup();
}

static int rmnet_function_bind_config(struct android_usb_function *f,
					 struct usb_configuration *c)
{
	int i;
	int err = 0;
	char *ctrl_name;
	char *data_name;
	char *tname = NULL;
	char buf[MAX_XPORT_STR_LEN], *b;
	char xport_name_buf[MAX_XPORT_STR_LEN], *tb;
	static int rmnet_initialized, ports;

	if (!rmnet_initialized) {
		rmnet_initialized = 1;
		strlcpy(buf, rmnet_transports, sizeof(buf));
		b = strim(buf);

		strlcpy(xport_name_buf, rmnet_xport_names,
				sizeof(xport_name_buf));
		tb = strim(xport_name_buf);

		while (b) {
			ctrl_name = strsep(&b, ",");
			data_name = strsep(&b, ",");
			if (ctrl_name && data_name) {
				if (tb)
					tname = strsep(&tb, ",");
				err = frmnet_init_port(ctrl_name, data_name,
						tname);
				if (err) {
					pr_err("rmnet: Cannot open ctrl port:"
						"'%s' data port:'%s'\n",
						ctrl_name, data_name);
					goto out;
				}
				ports++;
			}
		}

		err = rmnet_gport_setup();
		if (err) {
			pr_err("rmnet: Cannot setup transports");
			goto out;
		}
	}

	for (i = 0; i < ports; i++) {
		err = frmnet_bind_config(c, i);
		if (err) {
			pr_err("Could not bind rmnet%u config\n", i);
			break;
		}
	}
out:
	return err;
}

static void rmnet_function_unbind_config(struct android_usb_function *f,
						struct usb_configuration *c)
{
	frmnet_unbind_config();
}

static ssize_t rmnet_transports_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%s\n", rmnet_transports);
}

static ssize_t rmnet_transports_store(
		struct device *device, struct device_attribute *attr,
		const char *buff, size_t size)
{
	strlcpy(rmnet_transports, buff, sizeof(rmnet_transports));

	return size;
}

static ssize_t rmnet_xport_names_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%s\n", rmnet_xport_names);
}

static ssize_t rmnet_xport_names_store(
		struct device *device, struct device_attribute *attr,
		const char *buff, size_t size)
{
	strlcpy(rmnet_xport_names, buff, sizeof(rmnet_xport_names));

	return size;
}

static struct device_attribute dev_attr_rmnet_transports =
					__ATTR(transports, S_IRUGO | S_IWUSR,
						rmnet_transports_show,
						rmnet_transports_store);

static struct device_attribute dev_attr_rmnet_xport_names =
				__ATTR(transport_names, S_IRUGO | S_IWUSR,
				rmnet_xport_names_show,
				rmnet_xport_names_store);

static struct device_attribute *rmnet_function_attributes[] = {
					&dev_attr_rmnet_transports,
					&dev_attr_rmnet_xport_names,
					NULL };

static struct android_usb_function rmnet_function = {
	.name		= "rmnet",
	.cleanup	= rmnet_function_cleanup,
	.bind_config	= rmnet_function_bind_config,
	.unbind_config	= rmnet_function_unbind_config,
	.attributes	= rmnet_function_attributes,
};

/* DIAG */
static char diag_clients[32];	    /*enabled DIAG clients- "diag[,diag_mdm]" */
static ssize_t clients_store(
@@ -835,6 +981,167 @@ static struct android_usb_function diag_function = {
	.attributes	= diag_function_attributes,
};

/* DEBUG */
static int qdss_function_init(struct android_usb_function *f,
	struct usb_composite_dev *cdev)
{
	return qdss_setup();
}

static void qdss_function_cleanup(struct android_usb_function *f)
{
	qdss_cleanup();
}

static int qdss_init_transports(int *portnum)
{
	char *ts_port;
	char *tname = NULL;
	char buf[MAX_XPORT_STR_LEN], *type;
	char xport_name_buf[MAX_XPORT_STR_LEN], *tn;
	int err = 0;

	strlcpy(buf, qdss_transports, sizeof(buf));
	type = strim(buf);

	strlcpy(xport_name_buf, qdss_xport_names,
			sizeof(xport_name_buf));
	tn = strim(xport_name_buf);

	pr_debug("%s: qdss_debug_intf = %d\n",
		__func__, qdss_debug_intf);

	while (type) {
		ts_port = strsep(&type, ",");
		if (ts_port) {
			if (tn)
				tname = strsep(&tn, ",");

			err = qdss_init_port(
				ts_port,
				tname,
				qdss_debug_intf);

			if (err) {
				pr_err("%s: Cannot open transport port:'%s'\n",
					__func__, ts_port);
				return err;
			}
			(*portnum)++;
		}
	}
	return err;
}

static int qdss_function_bind_config(struct android_usb_function *f,
					struct usb_configuration *c)
{
	int i;
	int err = 0;
	static int qdss_initialized = 0, portsnum;

	if (!qdss_initialized) {
		qdss_initialized = 1;

		err = qdss_init_transports(&portsnum);
		if (err) {
			pr_err("qdss: Cannot init transports");
			goto out;
		}

		err = qdss_gport_setup();
		if (err) {
			pr_err("qdss: Cannot setup transports");
			goto out;
		}
	}

	pr_debug("%s: port number is %d\n", __func__, portsnum);

	for (i = 0; i < portsnum; i++) {
		err = qdss_bind_config(c, i);
		if (err) {
			pr_err("Could not bind qdss%u config\n", i);
			break;
		}
	}
out:
	return err;
}

static ssize_t qdss_transports_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%s\n", qdss_transports);
}

static ssize_t qdss_transports_store(
		struct device *device, struct device_attribute *attr,
		const char *buff, size_t size)
{
	strlcpy(qdss_transports, buff, sizeof(qdss_transports));

	return size;
}

static ssize_t qdss_xport_names_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%s\n", qdss_xport_names);
}

static ssize_t qdss_xport_names_store(
		struct device *device, struct device_attribute *attr,
		const char *buff, size_t size)
{
	strlcpy(qdss_xport_names, buff, sizeof(qdss_xport_names));
	return size;
}

static ssize_t qdss_debug_intf_store(
		struct device *device, struct device_attribute *attr,
		const char *buff, size_t size)
{
	strtobool(buff, &qdss_debug_intf);
	return size;
}

static ssize_t qdss_debug_intf_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%d\n", qdss_debug_intf);
}

static struct device_attribute dev_attr_qdss_transports =
					__ATTR(transports, S_IRUGO | S_IWUSR,
						qdss_transports_show,
						qdss_transports_store);

static struct device_attribute dev_attr_qdss_xport_names =
				__ATTR(transport_names, S_IRUGO | S_IWUSR,
				qdss_xport_names_show,
				qdss_xport_names_store);

/* 1(enable)/0(disable) the qdss debug interface */
static struct device_attribute dev_attr_qdss_debug_intf =
				__ATTR(debug_intf, S_IRUGO | S_IWUSR,
				qdss_debug_intf_show,
				qdss_debug_intf_store);

static struct device_attribute *qdss_function_attributes[] = {
					&dev_attr_qdss_transports,
					&dev_attr_qdss_xport_names,
					&dev_attr_qdss_debug_intf,
					NULL };

static struct android_usb_function qdss_function = {
	.name		= "qdss",
	.init		= qdss_function_init,
	.cleanup	= qdss_function_cleanup,
	.bind_config	= qdss_function_bind_config,
	.attributes	= qdss_function_attributes,
};

static int
mtp_function_init(struct android_usb_function *f,
		struct usb_composite_dev *cdev)
@@ -1380,7 +1687,9 @@ static struct android_usb_function audio_source_function = {

static struct android_usb_function *supported_functions[] = {
	&ffs_function,
	&rmnet_function,
	&diag_function,
	&qdss_function,
	&acm_function,
	&mtp_function,
	&ptp_function,
+1234 −0

File added.

Preview size limit exceeded, changes collapsed.

+53 −0
Original line number Diff line number Diff line
/*
 * 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 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 _F_QDSS_H
#define _F_QDSS_H

#include <linux/kernel.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>

#define NR_QDSS_PORTS 4

struct gqdss {
	struct usb_function function;
	struct usb_ep *ctrl_out;
	struct usb_ep *ctrl_in;
	struct usb_ep *data;
};

/* struct f_qdss - USB qdss function driver private structure */
struct f_qdss {

	struct gqdss port;
	struct usb_composite_dev *cdev;
	u8 port_num;
	u8 ctrl_iface_id;
	u8 data_iface_id;
	int usb_connected;
	bool debug_inface_enabled;
	struct usb_request *endless_req;
	struct usb_qdss_ch ch;
	struct list_head ctrl_read_pool;
	struct list_head ctrl_write_pool;
	struct work_struct connect_w;
	struct work_struct disconnect_w;
	spinlock_t lock;
	unsigned int data_enabled:1;
	unsigned int ctrl_in_enabled:1;
	unsigned int ctrl_out_enabled:1;
	struct workqueue_struct *wq;
};

#endif
Loading