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

Commit ce4ce2cc authored by Gustavo Solaira's avatar Gustavo Solaira Committed by Chris Lew
Browse files

net: qrtr: Add support for USB transport



Add support for USB transport for QRTR. This transport driver
exposes two bulk endpoints and registers tx and rx functions
with qrtr framework for transport of IPC messages to and from
connected device.

Change-Id: Id3f2b5bf3569cfa933edef40bcf13157ff3e2f56
Signed-off-by: default avatarGustavo Solaira <gustavos@codeaurora.org>
Signed-off-by: default avatarAjay Agarwal <ajaya@codeaurora.org>
Signed-off-by: default avatarChris Lew <clew@codeaurora.org>
parent 7da513b7
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -38,4 +38,13 @@ config QRTR_MHI
	  transport will wait for the uplink callback notification before
	  returning to qrtr.

config QRTR_USB
	tristate "USB IPC Router channels"
	depends on USB || (COMPILE_TEST && USB=n)
	help
	  Say Y here to support USB based ipcrouter channels. USB transport
	  is used for connections with other sytems for IPC Router. The USB
	  transport provides bulk endpoints to facilitate sending and receiving
	  IPC Router data packets.

endif # QRTR
+3 −0
Original line number Diff line number Diff line
@@ -6,3 +6,6 @@ obj-$(CONFIG_QRTR_TUN) += qrtr-tun.o
qrtr-tun-y	:= tun.o
obj-$(CONFIG_QRTR_MHI) += qrtr-mhi.o
qrtr-mhi-y	:= mhi.o

obj-$(CONFIG_QRTR_USB) += qrtr-usb.o
qrtr-usb-y      := usb.o

net/qrtr/usb.c

0 → 100644
+311 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2018, The Linux Foundation. All rights reserved. */

#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/usb.h>

#include "qrtr.h"

#define QRTR_VENDOR_ID 0x05c6
#define MAX_DATA_SIZE 16384

enum qrtr_usb_rx_state {
	QRTR_USB_RX_SUSPEND = 0,
	QRTR_USB_RX_RUN,
	QRTR_USB_RX_STOP
};

struct qrtr_usb_dev {
	struct qrtr_endpoint ep;
	struct usb_device *udev;
	struct usb_interface *iface;
	struct usb_anchor submitted;
	struct completion in_compl;
	struct urb *in_urb;
	struct task_struct *rx_thread;
	enum qrtr_usb_rx_state thread_state;
	wait_queue_head_t qrtr_wq;
	unsigned int in_pipe;
	unsigned int out_pipe;
};

static void qcom_usb_qrtr_txn_cb(struct urb *urb)
{
	struct completion *compl = urb->context;

	complete(compl);
}

/* from usb to qrtr */
static int qcom_usb_qrtr_rx_thread_fn(void *data)
{
	struct qrtr_usb_dev *qdev = data;
	struct urb *in_urb = qdev->in_urb;
	void *buf;
	int rc = 0;

	buf = kmalloc(MAX_DATA_SIZE, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	usb_anchor_urb(in_urb, &qdev->submitted);

	usb_fill_bulk_urb(in_urb, qdev->udev, qdev->in_pipe,
			  buf, MAX_DATA_SIZE, qcom_usb_qrtr_txn_cb,
			  &qdev->in_compl);

	while (!kthread_should_stop()) {
		if (qdev->thread_state == QRTR_USB_RX_SUSPEND ||
		    qdev->thread_state == QRTR_USB_RX_STOP) {
			dev_dbg(&qdev->udev->dev,
				"pausing or stopping thread, state=%d\n",
				qdev->thread_state);
			wait_event_interruptible(qdev->qrtr_wq,
						 qdev->thread_state ==
						 QRTR_USB_RX_RUN ||
						 kthread_should_stop());
			continue;
		}

		rc = usb_autopm_get_interface(qdev->iface);
		if (rc) {
			dev_err(&qdev->udev->dev,
				"failed to get autopm, rc=%d\n", rc);
			continue;
		}

		reinit_completion(&qdev->in_compl);

		rc = usb_submit_urb(in_urb, GFP_KERNEL);
		if (rc) {
			usb_autopm_put_interface(qdev->iface);
			dev_dbg(&qdev->udev->dev,
				"could not submit in urb, rc=%d\n", rc);
			/* Give up if the device is disconnected */
			if (rc == -ENODEV)
				break;
			continue;
		}

		wait_for_completion(&qdev->in_compl);
		if (in_urb->status) {
			usb_autopm_put_interface(qdev->iface);
			dev_dbg(&qdev->udev->dev, "URB status %d",
				in_urb->status);
			continue;
		}

		usb_autopm_put_interface(qdev->iface);

		dev_dbg(&qdev->udev->dev, "received message with len=%d\n",
			in_urb->actual_length);

		rc = qrtr_endpoint_post(&qdev->ep, buf, in_urb->actual_length);
		if (rc == -EINVAL)
			dev_err(&qdev->udev->dev,
				"invalid ipcrouter packet\n");
	}

	kfree(buf);
	wait_event_interruptible(qdev->qrtr_wq, kthread_should_stop());
	dev_dbg(&qdev->udev->dev, "leaving rx_thread\n");

	return rc;
}

/* from qrtr to usb */
static int qcom_usb_qrtr_send(struct qrtr_endpoint *ep, struct sk_buff *skb)
{
	struct qrtr_usb_dev *qdev = container_of(ep, struct qrtr_usb_dev, ep);
	struct urb *out_urb = NULL;
	struct completion compl;
	int rc;

	rc = skb_linearize(skb);
	if (rc)
		goto exit_free_skb;

	out_urb = usb_alloc_urb(0, GFP_KERNEL);
	if (!out_urb) {
		rc = -ENOMEM;
		goto exit_free_skb;
	}

	rc = usb_autopm_get_interface(qdev->iface);
	if (rc)
		goto exit_free_urb;

	init_completion(&compl);
	usb_fill_bulk_urb(out_urb, qdev->udev, qdev->out_pipe,
			  skb->data, skb->len, qcom_usb_qrtr_txn_cb, &compl);

	usb_anchor_urb(out_urb, &qdev->submitted);
	rc = usb_submit_urb(out_urb, GFP_KERNEL);
	if (rc) {
		dev_err(&qdev->udev->dev, "could not submit out urb\n");
		usb_unanchor_urb(out_urb);
		goto exit_autopm_put_intf;
	}

	wait_for_completion(&compl);
	rc = out_urb->status;

	dev_dbg(&qdev->udev->dev, "sent message with len=%d, rc=%d\n",
		skb->len, rc);

exit_autopm_put_intf:
	usb_autopm_put_interface(qdev->iface);
exit_free_urb:
	usb_free_urb(out_urb);
exit_free_skb:
	if (rc)
		kfree_skb(skb);
	else
		consume_skb(skb);

	return rc;
}

static int qcom_usb_qrtr_probe(struct usb_interface *interface,
			       const struct usb_device_id *id)
{
	struct qrtr_usb_dev *qdev;
	struct usb_device *udev;
	struct usb_host_interface *intf_desc;
	struct usb_endpoint_descriptor *ep_desc;
	int rc, i;

	udev = usb_get_dev(interface_to_usbdev(interface));
	if (!udev)
		return -ENODEV;

	qdev = devm_kzalloc(&udev->dev, sizeof(*qdev), GFP_KERNEL);
	if (!qdev)
		return -ENOMEM;

	qdev->udev = udev;
	qdev->iface = interface;
	qdev->ep.xmit = qcom_usb_qrtr_send;

	intf_desc = interface->cur_altsetting;
	for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) {
		ep_desc = &intf_desc->endpoint[i].desc;
		if (!qdev->in_pipe && usb_endpoint_is_bulk_in(ep_desc))
			qdev->in_pipe =
			usb_rcvbulkpipe(qdev->udev, ep_desc->bEndpointAddress);
		if (!qdev->out_pipe && usb_endpoint_is_bulk_out(ep_desc))
			qdev->out_pipe =
			usb_sndbulkpipe(qdev->udev, ep_desc->bEndpointAddress);
	}

	if (!qdev->in_pipe || !qdev->out_pipe) {
		dev_err(&qdev->udev->dev, "could not find endpoints\n");
		return -ENODEV;
	}

	qdev->in_urb = usb_alloc_urb(0, GFP_KERNEL);
	if (!qdev->in_urb) {
		dev_err(&qdev->udev->dev, "could not allocate in urb\n");
		return -ENOMEM;
	}

	init_usb_anchor(&qdev->submitted);

	rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO);
	if (rc)
		return rc;

	usb_set_intfdata(interface, qdev);

	init_waitqueue_head(&qdev->qrtr_wq);
	init_completion(&qdev->in_compl);
	qdev->thread_state = QRTR_USB_RX_RUN;
	qdev->rx_thread = kthread_run(qcom_usb_qrtr_rx_thread_fn, qdev,
				      "qrtr-usb-rx");
	if (IS_ERR(qdev->rx_thread)) {
		dev_err(&qdev->udev->dev, "could not create rx_thread\n");
		qrtr_endpoint_unregister(&qdev->ep);
		return PTR_ERR(qdev->rx_thread);
	}

	dev_dbg(&qdev->udev->dev, "QTI USB QRTR driver probed\n");

	return 0;
}

static int qcom_usb_qrtr_suspend(struct usb_interface *intf,
				 pm_message_t message)
{
	struct qrtr_usb_dev *qdev = usb_get_intfdata(intf);

	/* Suspend the thread */
	qdev->thread_state = QRTR_USB_RX_SUSPEND;
	usb_kill_anchored_urbs(&qdev->submitted);

	return 0;
}

static int qcom_usb_qrtr_resume(struct usb_interface *intf)
{
	struct qrtr_usb_dev *qdev = usb_get_intfdata(intf);

	qdev->thread_state = QRTR_USB_RX_RUN;
	wake_up(&qdev->qrtr_wq);

	return 0;
}

static int qcom_usb_qrtr_reset_resume(struct usb_interface *intf)
{
	struct qrtr_usb_dev *qdev = usb_get_intfdata(intf);
	int rc = 0;

	qrtr_endpoint_unregister(&qdev->ep);
	rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO);
	if (rc)
		return rc;

	qdev->thread_state = QRTR_USB_RX_RUN;
	wake_up(&qdev->qrtr_wq);

	return rc;
}

static void qcom_usb_qrtr_disconnect(struct usb_interface *interface)
{
	struct qrtr_usb_dev *qdev = usb_get_intfdata(interface);

	qdev->thread_state = QRTR_USB_RX_STOP;
	usb_kill_anchored_urbs(&qdev->submitted);
	kthread_stop(qdev->rx_thread);

	usb_free_urb(qdev->in_urb);
	qrtr_endpoint_unregister(&qdev->ep);
	usb_set_intfdata(interface, NULL);
	usb_put_dev(qdev->udev);
}

static const struct usb_device_id qcom_usb_qrtr_ids[] = {
	{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x90ef, 3) },
	{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x90f0, 3) },
	{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, qcom_usb_qrtr_ids);

static struct usb_driver qcom_usb_qrtr_driver = {
	.name = "qcom_usb_qrtr",
	.probe = qcom_usb_qrtr_probe,
	.disconnect = qcom_usb_qrtr_disconnect,
	.suspend = qcom_usb_qrtr_suspend,
	.resume = qcom_usb_qrtr_resume,
	.reset_resume = qcom_usb_qrtr_reset_resume,
	.id_table = qcom_usb_qrtr_ids,
	.supports_autosuspend = 1,
};

module_usb_driver(qcom_usb_qrtr_driver);

MODULE_DESCRIPTION("QTI IPC-Router USB interface driver");
MODULE_LICENSE("GPL v2");