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

Commit 64b25108 authored by Gustavo Solaira's avatar Gustavo Solaira
Browse files

net: qrtr: Add support for a USB device transport



Add a transport that uses the USB device ipc_bridge APIs.
The USB device IPC APIs are used to transfer data from a
device to the host over USB. The modem will generally be
configured as a device and this transport is intended to
be used on the modem controller co-processor.

Change-Id: I8871fef55b266bb4f749e7963bf12e9fe1ae7281
Signed-off-by: default avatarGustavo Solaira <gustavos@codeaurora.org>
parent 1d31fbb5
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -60,4 +60,11 @@ config QRTR_MHI_DEV
	  connections.  This driver enables QRTR to run on the modem device
	  side.

config QRTR_USB_DEV
	tristate "USB Device IPC Router channels"
	depends on USB_CONFIGFS_F_IPC || (COMPILE_TEST && USB_CONFIGFS_F_IPC=n)
	---help---
	  Say Y here to support USB device based ipcrouter channels.
	  This driver enables QRTR to run on the modem device.

endif # QRTR
+3 −0
Original line number Diff line number Diff line
@@ -14,3 +14,6 @@ qrtr-fifo-y := fifo.o

obj-$(CONFIG_QRTR_MHI_DEV) += qrtr-mhi-dev.o
qrtr-mhi-dev-y	:= mhi_dev.o

obj-$(CONFIG_QRTR_USB_DEV) += qrtr-usb-dev.o
qrtr-usb-dev-y      := usb_dev.o

net/qrtr/usb_dev.c

0 → 100644
+187 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2019, 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/ipc_bridge.h>

#include "qrtr.h"

#define IPC_DRIVER_NAME "ipc_bridge"

struct qrtr_usb_dev_ep {
	struct qrtr_endpoint ep;
	struct platform_device *pdev;
	struct task_struct *rx_thread;
};

static struct qrtr_usb_dev_ep *qep;

/* from qrtr to usb */
static int qcom_usb_dev_qrtr_send(struct qrtr_endpoint *ep, struct sk_buff *skb)
{
	struct qrtr_usb_dev_ep *qep =
		container_of(ep, struct qrtr_usb_dev_ep, ep);
	struct ipc_bridge_platform_data *ipc_bridge;
	int rc;

	ipc_bridge = qep->pdev->dev.platform_data;

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

	rc = ipc_bridge->write(qep->pdev, skb->data, skb->len);
	if (rc < 0) {
		dev_err(&qep->pdev->dev, "error writing data %d\n", rc);
	} else if (rc != skb->len) {
		dev_err(&qep->pdev->dev, "wrote partial data, len=%d\n", rc);
		rc = -EIO;
	} else {
		dev_dbg(&qep->pdev->dev, "wrote message with len=%d\n", rc);
		rc = 0;
	}

exit_free_skb:
	if (rc)
		kfree_skb(skb);
	else
		consume_skb(skb);

	return rc;
}

/* from usb to qrtr */
static int qcom_usb_dev_qrtr_rx_thread_fn(void *data)
{
	struct qrtr_usb_dev_ep *qep = data;
	struct ipc_bridge_platform_data *ipc_bridge;
	void *buf;
	int bytes_read;
	int rc = 0;

	ipc_bridge = qep->pdev->dev.platform_data;

	buf = kmalloc(ipc_bridge->max_read_size, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	while (!kthread_should_stop()) {
		bytes_read = ipc_bridge->read(qep->pdev, buf,
					      ipc_bridge->max_read_size);
		if (bytes_read < 0) {
			dev_err(&qep->pdev->dev,
				"error in ipc read operation %d\n", bytes_read);
			break;
		}

		dev_dbg(&qep->pdev->dev, "received message with len=%d\n",
			bytes_read);

		rc = qrtr_endpoint_post(&qep->ep, buf, bytes_read);
		if (rc == -EINVAL)
			dev_err(&qep->pdev->dev,
				"invalid ipcrouter packet\n");
	}

	kfree(buf);
	dev_dbg(&qep->pdev->dev, "leaving rx_thread\n");

	return rc;
}

static int qcom_usb_dev_qrtr_probe(struct platform_device *pdev)
{
	struct ipc_bridge_platform_data *ipc_bridge;
	int rc;

	ipc_bridge = pdev->dev.platform_data;
	if (!ipc_bridge || !ipc_bridge->open || !ipc_bridge->read ||
	    !ipc_bridge->write || !ipc_bridge->close) {
		dev_err(&pdev->dev,
			"ipc_bridge or ipc_bridge->operations is NULL\n");
		rc = -EINVAL;
		goto exit;
	}

	rc = ipc_bridge->open(pdev);
	if (rc) {
		dev_err(&pdev->dev, "channel open failed for %s.%s\n",
			pdev->name, pdev->id);
		goto exit;
	}

	qep = devm_kzalloc(&pdev->dev, sizeof(*qep), GFP_KERNEL);
	if (!qep) {
		rc = -ENOMEM;
		goto exit_close_bridge;
	}

	qep->pdev = pdev;
	qep->ep.xmit = qcom_usb_dev_qrtr_send;

	rc = qrtr_endpoint_register(&qep->ep, QRTR_EP_NID_AUTO, false);
	if (rc) {
		dev_err(&pdev->dev, "failed to register qrtr endpoint\n");
		goto exit_close_bridge;
	}

	qep->rx_thread = kthread_run(qcom_usb_dev_qrtr_rx_thread_fn, qep,
				     "qrtr-usb-dev-rx");
	if (IS_ERR(qep->rx_thread)) {
		dev_err(&qep->pdev->dev, "could not create rx_thread\n");
		rc = PTR_ERR(qep->rx_thread);
		goto exit_qrtr_unregister;
	}

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

	return 0;

exit_qrtr_unregister:
	qrtr_endpoint_unregister(&qep->ep);
exit_close_bridge:
	ipc_bridge->close(pdev);
exit:
	return rc;
}

static int qcom_usb_dev_qrtr_remove(struct platform_device *pdev)
{
	struct ipc_bridge_platform_data *ipc_bridge;

	dev_dbg(&pdev->dev, "removing the platform dev\n");

	kthread_stop(qep->rx_thread);
	qrtr_endpoint_unregister(&qep->ep);

	ipc_bridge = pdev->dev.platform_data;
	if (ipc_bridge && ipc_bridge->close)
		ipc_bridge->close(pdev);

	return 0;
}

static struct platform_driver qcom_usb_dev_qrtr_driver = {
	.probe = qcom_usb_dev_qrtr_probe,
	.remove = qcom_usb_dev_qrtr_remove,
	.driver = {
		.name = IPC_DRIVER_NAME,
		.owner = THIS_MODULE,
	 },
};
module_platform_driver(qcom_usb_dev_qrtr_driver);

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