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

Commit ea25f63f authored by Chris Lew's avatar Chris Lew Committed by Gerrit - the friendly Code Review server
Browse files

net: qrtr: Add support for a mhi device transport



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

commit net: qrtr: Add support for a mhi device transport
(89754e5ed45dd).

Change-Id: Ie9276fe6c0846581192aff881e59baa5fecf1ca4
Signed-off-by: default avatarChris Lew <clew@codeaurora.org>
Signed-off-by: default avatarManoharan Vijaya Raghavan <mraghava@codeaurora.org>
parent 7b6c37ad
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -68,4 +68,13 @@ config QRTR_HAVEN
	  Router communication between two virtual machines. The transport
	  uses dynamically shared memory and haven doorbells.

config QRTR_MHI_DEV
	tristate "MHI Device IPC Router channels"
	depends on MSM_MHI_DEV || (COMPILE_TEST && MSM_MHI_DEV=n)
	help
	  Say Y here to support MHI base ipcrouter channels for device
	  endpoint mode.  MHI is the transport used for external modem
	  connections.  This driver enables QRTR to run on the modem device
	  side.

endif # QRTR
+2 −0
Original line number Diff line number Diff line
@@ -7,5 +7,7 @@ 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_MHI_DEV) += qrtr-mhi-dev.o
qrtr-mhi-dev-y	:= mhi_dev.o
obj-$(CONFIG_QRTR_HAVEN) += qrtr-haven.o
qrtr-haven-y	:= haven.o

net/qrtr/mhi_dev.c

0 → 100644
+249 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2019-2021 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/platform_device.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/of.h>
#include <linux/msm_mhi_dev.h>
#include "qrtr.h"

#define QRTR_MAX_PKT_SIZE SZ_32K

/* MHI DEV Enums are defined from Host perspective */
#define QRTR_MHI_DEV_OUT MHI_CLIENT_IPCR_IN
#define QRTR_MHI_DEV_IN MHI_CLIENT_IPCR_OUT

/**
 * struct qrtr_mhi_dev_ep - qrtr mhi device endpoint
 * @ep: endpoint
 * @dev: device from platform bus
 * @out: channel handle from mhi dev
 * @out_tre: complete when channel is ready to send
 * @out_lock: hold when resetting completion variable
 * @in: channel handle from mhi dev
 * @buf_in: buffer to hold incoming data
 * @net_id: subnet id used by qrtr core
 * @rt: realtime option used by qrtr core
 */
struct qrtr_mhi_dev_ep {
	struct qrtr_endpoint ep;
	struct device *dev;
	struct mhi_dev_client *out;
	struct completion out_tre;
	struct mutex out_lock;		/* for out critical sections */
	struct mhi_dev_client *in;
	void *buf_in;

	u32 net_id;
	bool rt;
};

static struct qrtr_mhi_dev_ep *qrtr_mhi_device_endpoint;

static int qrtr_mhi_dev_send(struct qrtr_endpoint *ep, struct sk_buff *skb)
{
	struct qrtr_mhi_dev_ep *qep;
	struct mhi_req req = { 0 };
	int rc;

	qep = container_of(ep, struct qrtr_mhi_dev_ep, ep);
	rc = skb_linearize(skb);
	if (rc) {
		kfree_skb(skb);
		return rc;
	}

	req.chan = QRTR_MHI_DEV_OUT;
	req.client = qep->out;
	req.mode = DMA_SYNC;
	req.buf = skb->data;
	req.len = skb->len;

	do {
		wait_for_completion(&qep->out_tre);

		mutex_lock(&qep->out_lock);
		rc = mhi_dev_write_channel(&req);
		if (rc == 0)
			reinit_completion(&qep->out_tre);
		mutex_unlock(&qep->out_lock);
	} while (!rc);

	if (rc != skb->len) {
		dev_err(qep->dev, "send failed rc:%d len:%d\n", rc, skb->len);
		kfree_skb(skb);
		return rc;
	}

	consume_skb(skb);
	return 0;
}

static void qrtr_mhi_dev_read(struct qrtr_mhi_dev_ep *qep)
{
	struct mhi_req req = { 0 };
	int rc;

	req.chan = QRTR_MHI_DEV_IN;
	req.client = qep->in;
	req.mode = DMA_SYNC;
	req.buf = qep->buf_in;
	req.len = QRTR_MAX_PKT_SIZE;

	rc = mhi_dev_read_channel(&req);
	if (rc < 0) {
		dev_err(qep->dev, "failed to read channel %d\n", rc);
		return;
	}

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

static void qrtr_mhi_dev_event_cb(struct mhi_dev_client_cb_reason *reason)
{
	struct qrtr_mhi_dev_ep *qep;

	qep = qrtr_mhi_device_endpoint;
	if (!qep)
		return;

	if (reason->reason == MHI_DEV_TRE_AVAILABLE) {
		pr_debug("TRE available event for chan %d\n", reason->ch_id);
		if (reason->ch_id == QRTR_MHI_DEV_IN) {
			qrtr_mhi_dev_read(qep);
		} else {
			mutex_lock(&qep->out_lock);
			complete_all(&qep->out_tre);
			mutex_unlock(&qep->out_lock);
		}
	}
}

static int qrtr_mhi_dev_open_channels(struct qrtr_mhi_dev_ep *qep)
{
	int rc;

	/* write channel */
	rc = mhi_dev_open_channel(QRTR_MHI_DEV_OUT, &qep->out,
				  qrtr_mhi_dev_event_cb);
	if (rc < 0)
		return rc;

	/* read channel */
	rc = mhi_dev_open_channel(QRTR_MHI_DEV_IN, &qep->in,
				  qrtr_mhi_dev_event_cb);
	if (rc < 0) {
		mhi_dev_close_channel(qep->out);
		return rc;
	}
	return 0;
}

static void qrtr_mhi_dev_close_channels(struct qrtr_mhi_dev_ep *qep)
{
	int rc;

	rc = mhi_dev_close_channel(qep->out);
	if (rc < 0)
		dev_err(qep->dev, "failed to close out channel %d\n", rc);

	rc = mhi_dev_close_channel(qep->in);
	if (rc < 0)
		dev_err(qep->dev, "failed to close in channel %d\n", rc);
}

static void qrtr_mhi_dev_state_cb(struct mhi_dev_client_cb_data *cb_data)
{
	struct qrtr_mhi_dev_ep *qep;
	int rc;

	if (!cb_data || !cb_data->user_data)
		return;
	qep = cb_data->user_data;

	switch (cb_data->ctrl_info) {
	case MHI_STATE_CONNECTED:
		rc = qrtr_mhi_dev_open_channels(qep);
		if (rc) {
			dev_err(qep->dev, "open failed %d\n", rc);
			return;
		}

		rc = qrtr_endpoint_register(&qep->ep, qep->net_id, qep->rt);
		if (rc) {
			dev_err(qep->dev, "register failed %d\n", rc);
			qrtr_mhi_dev_close_channels(qep);
		}
		break;
	case MHI_STATE_DISCONNECTED:
		qrtr_endpoint_unregister(&qep->ep);
		qrtr_mhi_dev_close_channels(qep);
		break;
	default:
		break;
	}
}

static int qrtr_mhi_dev_probe(struct platform_device *pdev)
{
	struct qrtr_mhi_dev_ep *qep;
	struct device_node *node;
	int rc;

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

	node = pdev->dev.of_node;
	rc = of_property_read_u32(node, "qcom,net-id", &qep->net_id);
	if (rc < 0)
		qep->net_id = QRTR_EP_NET_ID_AUTO;
	qep->rt = of_property_read_bool(node, "qcom,low-latency");

	qep->buf_in = devm_kzalloc(&pdev->dev, QRTR_MAX_PKT_SIZE, GFP_KERNEL);
	if (!qep->buf_in)
		return -ENOMEM;

	qrtr_mhi_device_endpoint = qep;

	mutex_init(&qep->out_lock);
	init_completion(&qep->out_tre);
	qep->ep.xmit = qrtr_mhi_dev_send;
	rc = mhi_register_state_cb(qrtr_mhi_dev_state_cb, qep,
				   QRTR_MHI_DEV_OUT);
	if (rc)
		return rc;

	return 0;
}

static const struct of_device_id qrtr_mhi_dev_match_table[] = {
	{ .compatible = "qcom,qrtr-mhi-dev"},
	{},
};

static struct platform_driver qrtr_mhi_dev_driver = {
	.probe = qrtr_mhi_dev_probe,
	.driver = {
		.name = "qrtr_mhi_dev",
		.of_match_table = qrtr_mhi_dev_match_table,
	},
};
module_platform_driver(qrtr_mhi_dev_driver);

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