From ea25f63fe3ebaa3877d7b097f8a5dd271f28a8a3 Mon Sep 17 00:00:00 2001 From: Chris Lew Date: Fri, 28 Jun 2019 19:04:47 -0700 Subject: [PATCH 1/7] 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: Chris Lew Signed-off-by: Manoharan Vijaya Raghavan --- net/qrtr/Kconfig | 9 ++ net/qrtr/Makefile | 2 + net/qrtr/mhi_dev.c | 249 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 net/qrtr/mhi_dev.c diff --git a/net/qrtr/Kconfig b/net/qrtr/Kconfig index 78c078284532..0fde52dbc565 100644 --- a/net/qrtr/Kconfig +++ b/net/qrtr/Kconfig @@ -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 diff --git a/net/qrtr/Makefile b/net/qrtr/Makefile index dfca913847d4..ecb9b5a29c58 100644 --- a/net/qrtr/Makefile +++ b/net/qrtr/Makefile @@ -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 diff --git a/net/qrtr/mhi_dev.c b/net/qrtr/mhi_dev.c new file mode 100644 index 000000000000..4a926b535c83 --- /dev/null +++ b/net/qrtr/mhi_dev.c @@ -0,0 +1,249 @@ +// 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 +#include +#include +#include +#include +#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"); From c5719367783b89b5bb732448abeddc7bfb0891da Mon Sep 17 00:00:00 2001 From: Chris Lew Date: Thu, 3 Jun 2021 11:08:31 +0530 Subject: [PATCH 2/7] net: qrtr: Change the in and out channels MHI's IN channel is QRTR's OUT channel and vice-versa. commit net: qrtr: Change the in and out channels (5c9c165066de0). Change-Id: If16790e629263cfa2787a08326c079b58f42617b Signed-off-by: Chris Lew Signed-off-by: Manoharan Vijaya Raghavan --- net/qrtr/mhi_dev.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/net/qrtr/mhi_dev.c b/net/qrtr/mhi_dev.c index 4a926b535c83..74cc2f6dbfdb 100644 --- a/net/qrtr/mhi_dev.c +++ b/net/qrtr/mhi_dev.c @@ -137,16 +137,16 @@ 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, + rc = mhi_dev_open_channel(QRTR_MHI_DEV_IN, &qep->in, qrtr_mhi_dev_event_cb); if (rc < 0) return rc; /* read channel */ - rc = mhi_dev_open_channel(QRTR_MHI_DEV_IN, &qep->in, + rc = mhi_dev_open_channel(QRTR_MHI_DEV_OUT, &qep->out, qrtr_mhi_dev_event_cb); if (rc < 0) { - mhi_dev_close_channel(qep->out); + mhi_dev_close_channel(qep->in); return rc; } return 0; @@ -156,13 +156,13 @@ 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); + + rc = mhi_dev_close_channel(qep->out); + if (rc < 0) + dev_err(qep->dev, "failed to close out channel %d\n", rc); } static void qrtr_mhi_dev_state_cb(struct mhi_dev_client_cb_data *cb_data) @@ -224,7 +224,7 @@ static int qrtr_mhi_dev_probe(struct platform_device *pdev) 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); + QRTR_MHI_DEV_IN); if (rc) return rc; From 66aeb46272843f97c2847a259cabf8ce6e523217 Mon Sep 17 00:00:00 2001 From: Jay Jayanna Date: Fri, 19 Jul 2019 15:29:31 -0700 Subject: [PATCH 3/7] net: qrtr: Loop on mhi_dev_read_channel until it returns 0 qrtr_mhi_dev_event_cb will be called only once if there is more than one packet at that time. Hence, call mhi_dev_read_channel in a loop until it returns zero. commit net: qrtr: Loop on mhi_dev_read_channel until it returns 0 (1a79f2584ef87). Change-Id: I2f171cb45e66c1c439fcc7d0027c22d0edb4fb19 Signed-off-by: Jay Jayanna Signed-off-by: Manoharan Vijaya Raghavan --- net/qrtr/mhi_dev.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/net/qrtr/mhi_dev.c b/net/qrtr/mhi_dev.c index 74cc2f6dbfdb..f828574ddd6c 100644 --- a/net/qrtr/mhi_dev.c +++ b/net/qrtr/mhi_dev.c @@ -94,6 +94,7 @@ static void qrtr_mhi_dev_read(struct qrtr_mhi_dev_ep *qep) { struct mhi_req req = { 0 }; int rc; + int bytes_read; req.chan = QRTR_MHI_DEV_IN; req.client = qep->in; @@ -101,15 +102,18 @@ static void qrtr_mhi_dev_read(struct qrtr_mhi_dev_ep *qep) 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; - } + do { + bytes_read = mhi_dev_read_channel(&req); + if (bytes_read < 0) { + dev_err(qep->dev, "failed to read channel %d\n", + bytes_read); + return; + } - rc = qrtr_endpoint_post(&qep->ep, req.buf, req.transfer_len); - if (rc == -EINVAL) - dev_err(qep->dev, "invalid ipcrouter packet\n"); + rc = qrtr_endpoint_post(&qep->ep, req.buf, req.transfer_len); + if (rc == -EINVAL) + dev_err(qep->dev, "invalid ipcrouter packet\n"); + } while (bytes_read > 0); } static void qrtr_mhi_dev_event_cb(struct mhi_dev_client_cb_reason *reason) From f1b60eb49631a1b77e01612de1f9ccb354c17777 Mon Sep 17 00:00:00 2001 From: Sricharan R Date: Thu, 31 Oct 2019 23:58:17 +0530 Subject: [PATCH 4/7] net: qrtr: mhi_dev: Only post valid data mhi_dev_read_channel returns the number of bytes read. Do not post a packet to qrtr if the length returned is 0. This prevents an issue where qrtr receives duplicates packets from the mhi_dev transport. commit net: qrtr: mhi_dev: Only post valid data (b4451ceb04138). Change-Id: Ib1a31665730293baa9af61a9ae61bd8bdf6cab6f Signed-off-by: Sricharan R Signed-off-by: Chris Lew Signed-off-by: Manoharan Vijaya Raghavan --- net/qrtr/mhi_dev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/qrtr/mhi_dev.c b/net/qrtr/mhi_dev.c index f828574ddd6c..cb138d5653a5 100644 --- a/net/qrtr/mhi_dev.c +++ b/net/qrtr/mhi_dev.c @@ -109,6 +109,8 @@ static void qrtr_mhi_dev_read(struct qrtr_mhi_dev_ep *qep) bytes_read); return; } + if (bytes_read == 0) + return; rc = qrtr_endpoint_post(&qep->ep, req.buf, req.transfer_len); if (rc == -EINVAL) From 74ac2ceb7f01095f2ce53577d0f336c8b503da03 Mon Sep 17 00:00:00 2001 From: Siddartha Mohanadoss Date: Mon, 28 Oct 2019 14:55:07 -0700 Subject: [PATCH 5/7] msm: mhi_dev: Update release function in MHI After MHI device receives a reset command the internal state machine suspends all activity on the channel and processes the device reset. Clients are notified on the channel close. Existing close function checks if there are pending data to be read but since the device is in the process of cleaning the transfer buffers and the channels are suspended this check is not required. commit msm: mhi_dev: Update release function in MHI (6772e50d7b8fc). Change-Id: Iafc5249762b1468ee2cca4ac141522ce0356d403 Signed-off-by: Siddartha Mohanadoss Signed-off-by: Manoharan Vijaya Raghavan --- net/qrtr/mhi_dev.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/net/qrtr/mhi_dev.c b/net/qrtr/mhi_dev.c index cb138d5653a5..0c45f9ade275 100644 --- a/net/qrtr/mhi_dev.c +++ b/net/qrtr/mhi_dev.c @@ -160,15 +160,9 @@ static int qrtr_mhi_dev_open_channels(struct qrtr_mhi_dev_ep *qep) static void qrtr_mhi_dev_close_channels(struct qrtr_mhi_dev_ep *qep) { - int rc; - rc = mhi_dev_close_channel(qep->in); - if (rc < 0) - dev_err(qep->dev, "failed to close in channel %d\n", rc); - - rc = mhi_dev_close_channel(qep->out); - if (rc < 0) - dev_err(qep->dev, "failed to close out channel %d\n", rc); + mhi_dev_close_channel(qep->in); + mhi_dev_close_channel(qep->out); } static void qrtr_mhi_dev_state_cb(struct mhi_dev_client_cb_data *cb_data) From d7842ded9157853c368045a8ce50f2b245cad31b Mon Sep 17 00:00:00 2001 From: Arun Kumar Neelakantam Date: Fri, 10 Apr 2020 10:40:45 +0530 Subject: [PATCH 6/7] qrtr: mhi-dev: Wait for RX channel to be enumerated Registering only for endpoint RX channel to complete enumeration and starting to open both RX/TX channels leads to race condition on TX channel. Since the host does TX followed by RX init. Wait for endpoint TX channel init to synchronise with host. commit qrtr: mhi-dev: Wait for RX channel to be enumerated (5593a67ad0546). Change-Id: Ied002a89b51f71fa30cb7557f7dce27cfe1fd202 Signed-off-by: Sricharan R Signed-off-by: Arun Kumar Neelakantam Signed-off-by: Manoharan Vijaya Raghavan --- net/qrtr/mhi_dev.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/qrtr/mhi_dev.c b/net/qrtr/mhi_dev.c index 0c45f9ade275..1eb5649eb50b 100644 --- a/net/qrtr/mhi_dev.c +++ b/net/qrtr/mhi_dev.c @@ -223,8 +223,11 @@ static int qrtr_mhi_dev_probe(struct platform_device *pdev) mutex_init(&qep->out_lock); init_completion(&qep->out_tre); qep->ep.xmit = qrtr_mhi_dev_send; + /* HOST init TX first followed by RX, so register for endpoint TX + * which makes both channel ready by checking one channel state. + */ rc = mhi_register_state_cb(qrtr_mhi_dev_state_cb, qep, - QRTR_MHI_DEV_IN); + QRTR_MHI_DEV_OUT); if (rc) return rc; From 2c04602122aeccb1252276f29314afbcce57cd10 Mon Sep 17 00:00:00 2001 From: Arun Prakash Date: Fri, 29 May 2020 20:16:27 +0530 Subject: [PATCH 7/7] net: qrtr: mhi_dev: Handle mhi register error case Handle -EEXIST error case while register state callback with mhi device. If channel is open during registration no callback will issue instead mhi stack return -EEXIST to notify the client. Further state change notification will be informed via registered callback. commit net: qrtr: mhi_dev: Handle mhi register error case (f1eeca90b18b7). Change-Id: Icf60de35d23393a0808ef90058c1108e950d02b9 Signed-off-by: Arun Prakash Signed-off-by: Manoharan Vijaya Raghavan --- net/qrtr/mhi_dev.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/net/qrtr/mhi_dev.c b/net/qrtr/mhi_dev.c index 1eb5649eb50b..3f6091c1656b 100644 --- a/net/qrtr/mhi_dev.c +++ b/net/qrtr/mhi_dev.c @@ -202,6 +202,7 @@ static int qrtr_mhi_dev_probe(struct platform_device *pdev) struct qrtr_mhi_dev_ep *qep; struct device_node *node; int rc; + struct mhi_dev_client_cb_data cb_data; qep = devm_kzalloc(&pdev->dev, sizeof(*qep), GFP_KERNEL); if (!qep) @@ -228,8 +229,21 @@ static int qrtr_mhi_dev_probe(struct platform_device *pdev) */ rc = mhi_register_state_cb(qrtr_mhi_dev_state_cb, qep, QRTR_MHI_DEV_OUT); - if (rc) + if (rc == -EEXIST) { + /** + * MHI stack will return -EEXIST if mhi channel is already + * opend by the host and will not invoke reqistered callback. + * But future state change notification will inform through + * registered callback. + */ + complete_all(&qep->out_tre); + cb_data.user_data = (void *)qep; + cb_data.channel = QRTR_MHI_DEV_OUT; + cb_data.ctrl_info = MHI_STATE_CONNECTED; + qrtr_mhi_dev_state_cb(&cb_data); + } else if (rc) { return rc; + } return 0; }