From: Sumit Kumar <sumit.kumar@oss.qualcomm.com>
To: Manivannan Sadhasivam <mani@kernel.org>,
Jeff Hugo <jeff.hugo@oss.qualcomm.com>
Cc: mhi@lists.linux.dev, linux-arm-msm@vger.kernel.org,
linux-kernel@vger.kernel.org,
Sumit Kumar <sumit.kumar@oss.qualcomm.com>,
Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
Subject: [PATCH v5 1/3] bus: mhi: host: clients: Add loopback driver with sysfs interface
Date: Mon, 17 Aug 2026 15:44:34 +0530 [thread overview]
Message-ID: <20260817-loopback_mhi-v5-1-50efc7360b7f@oss.qualcomm.com> (raw)
In-Reply-To: <20260817-loopback_mhi-v5-0-50efc7360b7f@oss.qualcomm.com>
The MHI specification defines a LOOPBACK channel. The endpoint firmware
echoes back whatever the host sends on this channel. Without a host-side
driver, there is no way to exercise this channel to validate MHI data path
integrity between host and endpoint.
Add a host-side loopback driver that binds to the LOOPBACK channel and
expose a sysfs interface for data path testing. The sysfs interface allows
users to configure TRE buffer size and count, trigger a loopback test, and
read the result.
Co-developed-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
Signed-off-by: Sumit Kumar <sumit.kumar@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-mhi-devices-loopback | 39 +++
MAINTAINERS | 1 +
drivers/bus/mhi/host/Kconfig | 1 +
drivers/bus/mhi/host/Makefile | 1 +
drivers/bus/mhi/host/clients/Kconfig | 17 ++
drivers/bus/mhi/host/clients/Makefile | 2 +
drivers/bus/mhi/host/clients/loopback.c | 289 +++++++++++++++++++++
7 files changed, 350 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-mhi-devices-loopback b/Documentation/ABI/testing/sysfs-bus-mhi-devices-loopback
new file mode 100644
index 0000000000000000000000000000000000000000..1622a146110c2069c8b51e5bdf665c882b853303
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-mhi-devices-loopback
@@ -0,0 +1,39 @@
+What: /sys/bus/mhi/devices/mhi<N>_LOOPBACK/tre_size
+Date: August 2026
+KernelVersion: 7.2
+Contact: mhi@lists.linux.dev
+Description:
+ (RW) Size of each Transfer Ring Element (TRE) buffer in bytes
+ used for the loopback test. Valid range is 1 to the value
+ reported by max_tre_size. Default value is 32 bytes.
+
+What: /sys/bus/mhi/devices/mhi<N>_LOOPBACK/max_tre_size
+Date: August 2026
+KernelVersion: 7.2
+Contact: mhi@lists.linux.dev
+Description:
+ (RO) Maximum allowed TRE size in bytes. Reading this file
+ returns the upper bound for the tre_size attribute.
+
+What: /sys/bus/mhi/devices/mhi<N>_LOOPBACK/num_tre
+Date: August 2026
+KernelVersion: 7.2
+Contact: mhi@lists.linux.dev
+Description:
+ (RW) Number of Transfer Ring Elements (TREs) to use per
+ loopback test. Must be greater than zero and must not exceed
+ the channel ring capacity. Default value is 1.
+
+What: /sys/bus/mhi/devices/mhi<N>_LOOPBACK/start
+Date: August 2026
+KernelVersion: 7.2
+Contact: mhi@lists.linux.dev
+Description:
+ (WO) Write any value to trigger a loopback test. The driver
+ sends random data to the endpoint using the configured tre_size
+ and num_tre parameters, waits for the endpoint to echo it back,
+ and verifies the received data matches what was sent.
+
+ This is a blocking write that returns when the test completes
+ or times out after 5 seconds. The write returns an error code
+ if the test fails or times out.
diff --git a/MAINTAINERS b/MAINTAINERS
index a4b89bd497ad5051c190025838e0e93d257ed061..098b12692140514da52565a3200801f5e24a43b7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17469,6 +17469,7 @@ L: linux-arm-msm@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/mani/mhi.git
F: Documentation/ABI/stable/sysfs-bus-mhi
+F: Documentation/ABI/testing/sysfs-bus-mhi-devices-loopback
F: Documentation/mhi/
F: drivers/bus/mhi/
F: drivers/pci/endpoint/functions/pci-epf-mhi.c
diff --git a/drivers/bus/mhi/host/Kconfig b/drivers/bus/mhi/host/Kconfig
index da5cd0c9fc620ab595e742c422f1a22a2a84c7b9..627c57948235aa52348179ae8b2d0826ebaed01e 100644
--- a/drivers/bus/mhi/host/Kconfig
+++ b/drivers/bus/mhi/host/Kconfig
@@ -29,3 +29,4 @@ config MHI_BUS_PCI_GENERIC
This driver provides MHI PCI controller driver for devices such as
Qualcomm SDX55 based PCIe modems.
+source "drivers/bus/mhi/host/clients/Kconfig"
diff --git a/drivers/bus/mhi/host/Makefile b/drivers/bus/mhi/host/Makefile
index 859c2f38451c669b3d3014c374b2b957c99a1cfe..2a16008aeb38127494782bbff4e1656428d2b776 100644
--- a/drivers/bus/mhi/host/Makefile
+++ b/drivers/bus/mhi/host/Makefile
@@ -4,3 +4,4 @@ mhi-$(CONFIG_MHI_BUS_DEBUG) += debugfs.o
obj-$(CONFIG_MHI_BUS_PCI_GENERIC) += mhi_pci_generic.o
mhi_pci_generic-y += pci_generic.o
+obj-y += clients/
diff --git a/drivers/bus/mhi/host/clients/Kconfig b/drivers/bus/mhi/host/clients/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..312a0b5a8e83b6ad7246453908bd552841a89188
--- /dev/null
+++ b/drivers/bus/mhi/host/clients/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config MHI_BUS_LOOPBACK
+ tristate "MHI LOOPBACK client driver"
+ depends on MHI_BUS
+ help
+ MHI LOOPBACK client driver that binds to the MHI LOOPBACK channel
+ as defined in the MHI specification. The LOOPBACK channel is
+ implemented by MHI-based devices (modems, WLAN) in the field, where
+ the endpoint firmware echoes back whatever the host sends.
+
+ This driver exposes a sysfs interface for testing MHI data path
+ integrity between host and endpoint. Users can configure the TRE
+ size and count, and trigger a loopback test.
+
+ To compile this driver as a module, choose M here. The module
+ will be called mhi_loopback.
diff --git a/drivers/bus/mhi/host/clients/Makefile b/drivers/bus/mhi/host/clients/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3811b6928f42b38f94b1167941cf3b0fe512d32b
--- /dev/null
+++ b/drivers/bus/mhi/host/clients/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_MHI_BUS_LOOPBACK) += mhi_loopback.o
+mhi_loopback-y += loopback.o
diff --git a/drivers/bus/mhi/host/clients/loopback.c b/drivers/bus/mhi/host/clients/loopback.c
new file mode 100644
index 0000000000000000000000000000000000000000..02a952e8e01222cd8cb842c9f9490479df8ea2bd
--- /dev/null
+++ b/drivers/bus/mhi/host/clients/loopback.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/atomic.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/errno.h>
+#include <linux/mhi.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/random.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#define MHI_LOOPBACK_DEFAULT_TRE_SIZE 32
+#define MHI_LOOPBACK_DEFAULT_NUM_TRE 1
+#define MHI_LOOPBACK_TIMEOUT_MS 5000
+#define MHI_LOOPBACK_MAX_TRE_SIZE (SZ_64K - 1)
+
+struct mhi_loopback {
+ struct mhi_device *mdev;
+ /* Serializes the sysfs attributes against a running test */
+ struct mutex lb_mutex;
+ struct completion comp;
+ atomic_t tre_pending;
+ u32 num_tre;
+ u32 tre_size;
+};
+
+static ssize_t tre_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", loopback->tre_size);
+}
+
+static ssize_t tre_size_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(dev);
+ u32 val;
+
+ if (kstrtou32(buf, 0, &val))
+ return -EINVAL;
+
+ if (val == 0 || val > MHI_LOOPBACK_MAX_TRE_SIZE)
+ return -EINVAL;
+
+ guard(mutex)(&loopback->lb_mutex);
+ loopback->tre_size = val;
+
+ return count;
+}
+static DEVICE_ATTR_RW(tre_size);
+
+static ssize_t max_tre_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%u\n", MHI_LOOPBACK_MAX_TRE_SIZE);
+}
+static DEVICE_ATTR_RO(max_tre_size);
+
+static ssize_t num_tre_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", loopback->num_tre);
+}
+
+static ssize_t num_tre_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(dev);
+ u32 val;
+ int el_num;
+
+ if (kstrtou32(buf, 0, &val))
+ return -EINVAL;
+
+ if (val == 0)
+ return -EINVAL;
+
+ guard(mutex)(&loopback->lb_mutex);
+
+ el_num = min(mhi_get_free_desc_count(loopback->mdev, DMA_TO_DEVICE),
+ mhi_get_free_desc_count(loopback->mdev, DMA_FROM_DEVICE));
+ if (val > el_num) {
+ dev_err(dev, "num_tre (%u) exceeds ring capacity (%d)\n", val, el_num);
+ return -EINVAL;
+ }
+
+ loopback->num_tre = val;
+
+ return count;
+}
+static DEVICE_ATTR_RW(num_tre);
+
+static ssize_t start_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(dev);
+ u32 total_size, tre_count, tre_size;
+ int i, ret;
+
+ guard(mutex)(&loopback->lb_mutex);
+
+ tre_size = loopback->tre_size;
+ tre_count = loopback->num_tre;
+ total_size = size_mul(tre_count, tre_size);
+
+ if (total_size > KMALLOC_MAX_SIZE)
+ return -EINVAL;
+
+ if (tre_count > mhi_get_free_desc_count(loopback->mdev, DMA_TO_DEVICE) ||
+ tre_count > mhi_get_free_desc_count(loopback->mdev, DMA_FROM_DEVICE)) {
+ dev_err(dev, "Not enough ring space for %u TREs\n", tre_count);
+ return -ENOSPC;
+ }
+
+ void *recv_buf __free(kfree) = kzalloc(total_size, GFP_KERNEL);
+ if (!recv_buf)
+ return -ENOMEM;
+
+ void *send_buf __free(kfree) = kzalloc(total_size, GFP_KERNEL);
+ if (!send_buf)
+ return -ENOMEM;
+
+ get_random_bytes(send_buf, total_size);
+
+ atomic_set(&loopback->tre_pending, tre_count);
+ reinit_completion(&loopback->comp);
+
+ for (i = 0; i < tre_count; i++) {
+ ret = mhi_queue_buf(loopback->mdev, DMA_FROM_DEVICE,
+ recv_buf + (i * tre_size), tre_size, MHI_EOT);
+ if (ret) {
+ dev_err(dev, "Unable to queue read TRE %d: %d\n", i, ret);
+ if (atomic_sub_and_test(tre_count - i, &loopback->tre_pending))
+ complete(&loopback->comp);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < tre_count - 1; i++) {
+ ret = mhi_queue_buf(loopback->mdev, DMA_TO_DEVICE,
+ send_buf + (i * tre_size), tre_size, MHI_CHAIN);
+ if (ret) {
+ dev_err(dev, "Unable to queue send TRE %d: %d\n", i, ret);
+ return ret;
+ }
+ }
+
+ ret = mhi_queue_buf(loopback->mdev, DMA_TO_DEVICE,
+ send_buf + (i * tre_size), tre_size, MHI_EOT);
+ if (ret) {
+ dev_err(dev, "Unable to queue final TRE: %d\n", ret);
+ return ret;
+ }
+
+ if (!wait_for_completion_timeout(&loopback->comp,
+ msecs_to_jiffies(MHI_LOOPBACK_TIMEOUT_MS))) {
+ dev_err(dev, "Loopback test timed out\n");
+ /* Reset the channel to reclaim the TREs still pointing at the buffers */
+ mhi_unprepare_from_transfer(loopback->mdev);
+ ret = mhi_prepare_for_transfer(loopback->mdev);
+ if (ret)
+ dev_err(dev, "Failed to re-prepare channel for transfers: %d\n", ret);
+
+ return -ETIMEDOUT;
+ }
+
+ if (memcmp(send_buf, recv_buf, total_size)) {
+ dev_err(dev, "Loopback data mismatch\n");
+ return -EIO;
+ }
+
+ return count;
+}
+static DEVICE_ATTR_WO(start);
+
+static void mhi_loopback_dl_callback(struct mhi_device *mhi_dev,
+ struct mhi_result *mhi_res)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(&mhi_dev->dev);
+
+ if (mhi_res->transaction_status && mhi_res->transaction_status != -ENOTCONN)
+ dev_err(&mhi_dev->dev, "DL callback error: status %d\n",
+ mhi_res->transaction_status);
+
+ if (atomic_dec_and_test(&loopback->tre_pending))
+ complete(&loopback->comp);
+}
+
+static void mhi_loopback_ul_callback(struct mhi_device *mhi_dev,
+ struct mhi_result *mhi_res)
+{
+ if (mhi_res->transaction_status && mhi_res->transaction_status != -ENOTCONN)
+ dev_err(&mhi_dev->dev, "UL callback error: status %d\n",
+ mhi_res->transaction_status);
+}
+
+static struct attribute *mhi_loopback_attrs[] = {
+ &dev_attr_tre_size.attr,
+ &dev_attr_max_tre_size.attr,
+ &dev_attr_num_tre.attr,
+ &dev_attr_start.attr,
+ NULL,
+};
+
+static const struct attribute_group mhi_loopback_group = {
+ .attrs = mhi_loopback_attrs,
+};
+
+static int mhi_loopback_probe(struct mhi_device *mhi_dev,
+ const struct mhi_device_id *id)
+{
+ struct mhi_loopback *loopback;
+ int ret;
+
+ loopback = devm_kzalloc(&mhi_dev->dev, sizeof(*loopback), GFP_KERNEL);
+ if (!loopback)
+ return -ENOMEM;
+
+ loopback->mdev = mhi_dev;
+ loopback->tre_size = MHI_LOOPBACK_DEFAULT_TRE_SIZE;
+ loopback->num_tre = MHI_LOOPBACK_DEFAULT_NUM_TRE;
+
+ mutex_init(&loopback->lb_mutex);
+ init_completion(&loopback->comp);
+
+ dev_set_drvdata(&mhi_dev->dev, loopback);
+
+ ret = mhi_prepare_for_transfer(mhi_dev);
+ if (ret) {
+ dev_err(&mhi_dev->dev, "Failed to prepare for transfers: %d\n", ret);
+ return ret;
+ }
+
+ ret = sysfs_create_group(&mhi_dev->dev.kobj, &mhi_loopback_group);
+ if (ret) {
+ dev_err(&mhi_dev->dev, "Failed to create sysfs attributes: %d\n", ret);
+ mhi_unprepare_from_transfer(mhi_dev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mhi_loopback_remove(struct mhi_device *mhi_dev)
+{
+ /* Blocks until any in-progress store() has returned */
+ sysfs_remove_group(&mhi_dev->dev.kobj, &mhi_loopback_group);
+ mhi_unprepare_from_transfer(mhi_dev);
+}
+
+static const struct mhi_device_id mhi_loopback_id_table[] = {
+ { .chan = "LOOPBACK"},
+ {}
+};
+MODULE_DEVICE_TABLE(mhi, mhi_loopback_id_table);
+
+static struct mhi_driver mhi_loopback_driver = {
+ .probe = mhi_loopback_probe,
+ .remove = mhi_loopback_remove,
+ .dl_xfer_cb = mhi_loopback_dl_callback,
+ .ul_xfer_cb = mhi_loopback_ul_callback,
+ .id_table = mhi_loopback_id_table,
+ .driver = {
+ .name = "mhi_loopback",
+ },
+};
+
+module_mhi_driver(mhi_loopback_driver);
+
+MODULE_AUTHOR("Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>");
+MODULE_AUTHOR("Sumit Kumar <sumit.kumar@oss.qualcomm.com>");
+MODULE_DESCRIPTION("MHI Host Loopback Driver");
+MODULE_LICENSE("GPL");
--
2.34.1
next prev parent reply other threads:[~2026-08-17 10:14 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-17 10:14 [PATCH v5 0/3] bus: mhi: Add loopback driver Sumit Kumar
2026-08-17 10:14 ` Sumit Kumar [this message]
2026-08-17 20:14 ` [PATCH v5 1/3] bus: mhi: host: clients: Add loopback driver with sysfs interface Jeff Hugo
2026-08-19 9:46 ` Sumit Kumar
2026-08-18 8:50 ` Uwe Kleine-König
2026-08-19 9:48 ` Sumit Kumar
2026-08-17 10:14 ` [PATCH v5 2/3] bus: mhi: ep: Add mhi_ep_queue_buf() API for raw buffer queuing Sumit Kumar
2026-08-17 10:14 ` [PATCH v5 3/3] bus: mhi: ep: clients: Add loopback driver for data path testing Sumit Kumar
2026-08-18 8:53 ` Uwe Kleine-König
2026-08-19 9:48 ` Sumit Kumar
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260817-loopback_mhi-v5-1-50efc7360b7f@oss.qualcomm.com \
--to=sumit.kumar@oss.qualcomm.com \
--cc=jeff.hugo@oss.qualcomm.com \
--cc=krishna.chundru@oss.qualcomm.com \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mani@kernel.org \
--cc=mhi@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®