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

Commit 8228542c authored by qctecmdr Service's avatar qctecmdr Service Committed by Gerrit - the friendly Code Review server
Browse files

Merge "net: qrtr: Add support for USB transport"

parents 2df7f616 d65b144f
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -28,4 +28,10 @@ config QRTR_MHI
	  Say Y here to support MHI based ipcrouter channels.  MHI is the
	  transport used for external modem connections for IPC Router.

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.

endif # QRTR
+3 −0
Original line number Diff line number Diff line
@@ -5,3 +5,6 @@ qrtr-smd-y := smd.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
+322 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2018, 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/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");
		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");
		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);
	int rc = 0;

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

	return rc;
}

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");