lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <g7yr3psfoyya76wvcgjs24xyyofgkllmdsvworjnfjgc3q3qeq@vjkxyh5oabkd>
Date: Wed, 5 Nov 2025 16:17:41 -0600
From: Bjorn Andersson <andersson@...nel.org>
To: Sumit Kumar <sumit.kumar@....qualcomm.com>
Cc: Manivannan Sadhasivam <mani@...nel.org>, 
	Krishna Chaitanya Chundru <krishna.chundru@....qualcomm.com>, Akhil Vinod <akhil.vinod@....qualcomm.com>, 
	Subramanian Ananthanarayanan <subramanian.ananthanarayanan@....qualcomm.com>, linux-kernel@...r.kernel.org, mhi@...ts.linux.dev, 
	linux-arm-msm@...r.kernel.org, quic_vpernami@...cinc.com
Subject: Re: [PATCH v2 1/3] bus: mhi: host: Add loopback driver with sysfs
 interface

On Tue, Nov 04, 2025 at 11:09:05AM +0530, Sumit Kumar wrote:
> Add loopback driver for MHI host controllers that provides sysfs based
  ^--- Here would be e good place to explain why we want this driver. Per
https://docs.kernel.org/process/submitting-patches.html#describe-your-changes
start your commit message with a description of the problem you're
solving.

> testing interface for data path validation. The driver supports the
> "LOOPBACK" channel and offers configurable test parameters.
> 
> Sysfs interface provides:
> - size: Configure TRE size
> - num_tre: Set number of TREs for chained transfers
> - start: Initiate loopback test
> - status: Read test results

The words "loopback" and "testing" gives clear indications that this
should live in debugfs and not sysfs.

Also, sysfs attribute should be documented in Documentation/ABI/testing/

> 
> Co-developed-by: Krishna Chaitanya Chundru <krishna.chundru@....qualcomm.com>
> Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@....qualcomm.com>
> Signed-off-by: Sumit Kumar <sumit.kumar@....qualcomm.com>
> ---
>  drivers/bus/mhi/host/Kconfig        |   7 +
>  drivers/bus/mhi/host/Makefile       |   1 +
>  drivers/bus/mhi/host/mhi_loopback.c | 347 ++++++++++++++++++++++++++++++++++++
>  3 files changed, 355 insertions(+)
> 
> diff --git a/drivers/bus/mhi/host/Kconfig b/drivers/bus/mhi/host/Kconfig
> index da5cd0c9fc620ab595e742c422f1a22a2a84c7b9..08a39ecb47f585bf39721c101ed5e2ff44bdd5f8 100644
> --- a/drivers/bus/mhi/host/Kconfig
> +++ b/drivers/bus/mhi/host/Kconfig
> @@ -29,3 +29,10 @@ config MHI_BUS_PCI_GENERIC
>  	  This driver provides MHI PCI controller driver for devices such as
>  	  Qualcomm SDX55 based PCIe modems.
>  
> +config MHI_BUS_LOOPBACK
> +	tristate "MHI loopback driver"
> +	depends on MHI_BUS
> +	help
> +	  MHI loopback driver for data path testing. This driver
> +	  provides a mechanism to test MHI data transfer functionality
> +	  by implementing an echo service between host and endpoint.
> diff --git a/drivers/bus/mhi/host/Makefile b/drivers/bus/mhi/host/Makefile
> index 859c2f38451c669b3d3014c374b2b957c99a1cfe..e5d6dccf5a976eaeb827c47924ad0614c9958f8b 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-$(CONFIG_MHI_BUS_LOOPBACK) += mhi_loopback.o
> diff --git a/drivers/bus/mhi/host/mhi_loopback.c b/drivers/bus/mhi/host/mhi_loopback.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..980ace675718a79c97d9b2968ccef04c992a6c20
> --- /dev/null
> +++ b/drivers/bus/mhi/host/mhi_loopback.c
> @@ -0,0 +1,347 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#include <linux/mhi.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/completion.h>
> +#include <linux/string.h>
> +#include <linux/random.h>
> +#include <linux/kernel.h>
> +#include <linux/sysfs.h>
> +#include <linux/types.h>
> +#include <linux/errno.h>
> +#include <linux/mutex.h>
> +#include <linux/atomic.h>
> +#include <linux/cleanup.h>
> +#include <linux/sizes.h>

Keep them sorted, and make sure you need all of them.

> +
> +#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
> +
> +struct mhi_loopback {
> +	struct mhi_device *mdev;
> +	struct mutex lb_mutex;
> +	struct completion comp;
> +	atomic_t num_completions_received;
> +	char result[32];
> +	u32 num_tre;
> +	u32 size;
> +	bool loopback_in_progress;
> +};
> +
> +static ssize_t size_show(struct device *dev,
> +			 struct device_attribute *attr, char *buf)
> +{
> +	struct mhi_loopback *mhi_lb = dev_get_drvdata(dev);
> +
> +	return sysfs_emit(buf, "%u\n", mhi_lb->size);
> +}
> +
> +static ssize_t size_store(struct device *dev,
> +			  struct device_attribute *attr,
> +			  const char *buf, size_t count)
> +{
> +	struct mhi_loopback *mhi_lb = dev_get_drvdata(dev);
> +	u32 val;
> +
> +	if (kstrtou32(buf, 0, &val)) {
> +		dev_err(dev, "Invalid size value\n");
> +		return -EINVAL;
> +	}
> +
> +	if (val == 0 || val > MHI_LOOPBACK_MAX_TRE_SIZE) {
> +		dev_err(dev, "Size must be between 1 and %u bytes\n",
> +			MHI_LOOPBACK_MAX_TRE_SIZE);
> +		return -EINVAL;
> +	}
> +
> +	guard(mutex)(&mhi_lb->lb_mutex);
> +	if (mhi_lb->loopback_in_progress)

The only time loopback_in_progress is true is between the beginning and
end of start_store(), and that entire function is under guard(lb_mutex),
just as here and in num_tre_store().

So at all times loopback_in_progress is true, any other context will
block on getting the mutex, and then it will be reset to false before
the mutex is let go.

In other words, loopback_in_progress is unnecessary.

> +		return -EBUSY;
> +
> +	mhi_lb->size = val;
> +	return count;
> +}
> +static DEVICE_ATTR_RW(size);
> +
> +static ssize_t num_tre_show(struct device *dev,
> +			    struct device_attribute *attr, char *buf)
> +{
> +	struct mhi_loopback *mhi_lb = dev_get_drvdata(dev);
> +
> +	return sysfs_emit(buf, "%u\n", mhi_lb->num_tre);
> +}
> +
> +static ssize_t num_tre_store(struct device *dev,
> +			     struct device_attribute *attr,
> +			     const char *buf, size_t count)
> +{
> +	struct mhi_loopback *mhi_lb = dev_get_drvdata(dev);
> +	u32 val;
> +	int el_num;
> +
> +	if (kstrtou32(buf, 0, &val)) {
> +		dev_err(dev, "Invalid num_tre value\n");
> +		return -EINVAL;
> +	}
> +
> +	if (val == 0) {
> +		dev_err(dev, "Number of TREs cannot be zero\n");
> +		return -EINVAL;
> +	}
> +
> +	guard(mutex)(&mhi_lb->lb_mutex);
> +	if (mhi_lb->loopback_in_progress)
> +		return -EBUSY;
> +
> +	el_num = mhi_get_free_desc_count(mhi_lb->mdev, DMA_TO_DEVICE);

Aren't the descs per-channel in MHI? Given that you have a mutex around
start_store() is this actually a dynamic value?

> +	if (val > el_num) {
> +		dev_err(dev, "num_tre (%u) exceeds ring capacity (%d)\n", val, el_num);
> +		return -EINVAL;
> +	}
> +
> +	mhi_lb->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 *mhi_lb = dev_get_drvdata(dev);
> +	void *send_buf __free(kfree) = NULL;
> +	void *recv_buf __free(kfree) = NULL;
> +	u32 total_size, tre_count, tre_size;
> +	int ret, i;
> +
> +	guard(mutex)(&mhi_lb->lb_mutex);
> +
> +	if (mhi_lb->loopback_in_progress)
> +		return -EBUSY;
> +
> +	atomic_set(&mhi_lb->num_completions_received, 0);
> +	mhi_lb->loopback_in_progress = true;
> +
> +	tre_size = mhi_lb->size;
> +	tre_count = mhi_lb->num_tre;
> +
> +	strscpy(mhi_lb->result, "Loopback started", sizeof(mhi_lb->result));

All assignments to result are static const strings being strscpy'ed into
the buffer, if you made result a const char * instead, you could just
assign the string.

> +
> +	total_size = tre_count * tre_size;
> +
> +	recv_buf = kzalloc(total_size, GFP_KERNEL);
> +	if (!recv_buf) {
> +		strscpy(mhi_lb->result, "Memory allocation failed", sizeof(mhi_lb->result));
> +		mhi_lb->loopback_in_progress = false;
> +		return -ENOMEM;

You're setting loopback_in_progress to false and returning in 7
different places in this function. There seems to be some room for
improvement here.

That said, as I said above, I don't think your code can ever find
loopback_in_progress to be true...

> +	}
> +
> +	send_buf = kzalloc(total_size, GFP_KERNEL);
> +	if (!send_buf) {
> +		strscpy(mhi_lb->result, "Memory allocation failed", sizeof(mhi_lb->result));
> +		mhi_lb->loopback_in_progress = false;
> +		return -ENOMEM;
> +	}
> +
> +	for (i = 0; i < tre_count; i++) {
> +		ret = mhi_queue_buf(mhi_lb->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);
> +			strscpy(mhi_lb->result, "Queue tre failed", sizeof(mhi_lb->result));
> +			mhi_lb->loopback_in_progress = false;
> +			return ret;
> +		}
> +	}
> +
> +	get_random_bytes(send_buf, total_size);
> +
> +	reinit_completion(&mhi_lb->comp);
> +
> +	for (i = 0; i < tre_count - 1; i++) {
> +		ret = mhi_queue_buf(mhi_lb->mdev, DMA_TO_DEVICE, send_buf + (i * tre_size),
> +				    tre_size, MHI_CHAIN);
> +		if (ret) {
> +			dev_err(dev, "Unable to queue send TRE %d (chained): %d\n", i, ret);
> +			strscpy(mhi_lb->result, "Queue send failed", sizeof(mhi_lb->result));
> +			mhi_lb->loopback_in_progress = false;
> +			return ret;
> +		}
> +	}
> +
> +	ret = mhi_queue_buf(mhi_lb->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);
> +		strscpy(mhi_lb->result, "Queue final tre failed", sizeof(mhi_lb->result));
> +		mhi_lb->loopback_in_progress = false;
> +		return ret;
> +	}
> +
> +	if (!wait_for_completion_timeout(&mhi_lb->comp,
> +					 msecs_to_jiffies(MHI_LOOPBACK_TIMEOUT_MS))) {
> +		strscpy(mhi_lb->result, "Loopback timeout", sizeof(mhi_lb->result));
> +		dev_err(dev, "Loopback test timed out\n");
> +		mhi_lb->loopback_in_progress = false;
> +		return -ETIMEDOUT;
> +	}
> +
> +	ret = memcmp(send_buf, recv_buf, total_size);
> +	if (!ret) {
> +		strscpy(mhi_lb->result, "Loopback successful", sizeof(mhi_lb->result));
> +		dev_info(dev, "Loopback test passed\n");

Why both print the test status and log it to the result? Less is more...

> +	} else {
> +		strscpy(mhi_lb->result, "Loopback data mismatch", sizeof(mhi_lb->result));
> +		dev_err(dev, "Loopback test failed\n");
> +		ret = -EIO;
> +	}
> +
> +	mhi_lb->loopback_in_progress = false;
> +	return ret;
> +}
> +
> +static DEVICE_ATTR_WO(start);
> +
> +static ssize_t status_show(struct device *dev,
> +			   struct device_attribute *attr, char *buf)
> +{
> +	struct mhi_loopback *mhi_lb = dev_get_drvdata(dev);
> +
> +	return sysfs_emit(buf, "%s\n", mhi_lb->result);
> +}
> +static DEVICE_ATTR_RO(status);
> +
> +static void mhi_loopback_dl_callback(struct mhi_device *mhi_dev,
> +				     struct mhi_result *mhi_res)
> +{
> +	struct mhi_loopback *mhi_lb = dev_get_drvdata(&mhi_dev->dev);
> +
> +	if (!mhi_res->transaction_status) {
> +		if (atomic_inc_return(&mhi_lb->num_completions_received) >= mhi_lb->num_tre) {
> +			atomic_set(&mhi_lb->num_completions_received, 0);
> +			complete(&mhi_lb->comp);
> +		}
> +	} else {
> +		dev_err(&mhi_dev->dev, "DL callback error: status %d\n",
> +			mhi_res->transaction_status);
> +		atomic_set(&mhi_lb->num_completions_received, 0);
> +		complete(&mhi_lb->comp);
> +	}
> +}
> +
> +static void mhi_loopback_ul_callback(struct mhi_device *mhi_dev,
> +				     struct mhi_result *mhi_res)
> +{
> +}
> +
> +static int mhi_loopback_probe(struct mhi_device *mhi_dev,
> +			      const struct mhi_device_id *id)
> +{
> +	struct mhi_loopback *mhi_lb;
> +	int rc;
> +
> +	mhi_lb = devm_kzalloc(&mhi_dev->dev, sizeof(*mhi_lb), GFP_KERNEL);
> +	if (!mhi_lb)
> +		return -ENOMEM;
> +
> +	mhi_lb->mdev = mhi_dev;
> +
> +	dev_set_drvdata(&mhi_dev->dev, mhi_lb);
> +
> +	mhi_lb->size = MHI_LOOPBACK_DEFAULT_TRE_SIZE;
> +	mhi_lb->num_tre = MHI_LOOPBACK_DEFAULT_NUM_TRE;
> +	mhi_lb->loopback_in_progress = false;

kzalloc() already did that for you.

> +
> +	mutex_init(&mhi_lb->lb_mutex);
> +	strscpy(mhi_lb->result, "Loopback not started", sizeof(mhi_lb->result));
> +
> +	rc = sysfs_create_file(&mhi_dev->dev.kobj, &dev_attr_size.attr);
> +	if (rc) {
> +		dev_err(&mhi_dev->dev, "failed to create size sysfs file\n");
> +		goto out;
> +	}
> +
> +	rc = sysfs_create_file(&mhi_dev->dev.kobj, &dev_attr_num_tre.attr);
> +	if (rc) {
> +		dev_err(&mhi_dev->dev, "failed to create num_tre sysfs file\n");
> +		goto del_size_sysfs;

This is ugly, devm_device_add_group() seems more appropriate. Then
again, I don't think this belongs in sysfs in the first place.

Regards,
Bjorn

> +	}
> +
> +	rc = sysfs_create_file(&mhi_dev->dev.kobj, &dev_attr_start.attr);
> +	if (rc) {
> +		dev_err(&mhi_dev->dev, "failed to create start sysfs file\n");
> +		goto del_num_tre_sysfs;
> +	}
> +
> +	rc = sysfs_create_file(&mhi_dev->dev.kobj, &dev_attr_status.attr);
> +	if (rc) {
> +		dev_err(&mhi_dev->dev, "failed to create status sysfs file\n");
> +		goto del_start_sysfs;
> +	}
> +
> +	rc = mhi_prepare_for_transfer(mhi_lb->mdev);
> +	if (rc) {
> +		dev_err(&mhi_dev->dev, "failed to prepare for transfers\n");
> +		goto del_status_sysfs;
> +	}
> +
> +	init_completion(&mhi_lb->comp);
> +
> +	return 0;
> +
> +del_status_sysfs:
> +	sysfs_remove_file(&mhi_dev->dev.kobj, &dev_attr_status.attr);
> +del_start_sysfs:
> +	sysfs_remove_file(&mhi_dev->dev.kobj, &dev_attr_start.attr);
> +del_num_tre_sysfs:
> +	sysfs_remove_file(&mhi_dev->dev.kobj, &dev_attr_num_tre.attr);
> +del_size_sysfs:
> +	sysfs_remove_file(&mhi_dev->dev.kobj, &dev_attr_size.attr);
> +out:
> +	return rc;
> +}
> +
> +static void mhi_loopback_remove(struct mhi_device *mhi_dev)
> +{
> +	struct mhi_loopback *mhi_lb = dev_get_drvdata(&mhi_dev->dev);
> +
> +	if (mhi_lb)
> +		complete(&mhi_lb->comp);
> +
> +	sysfs_remove_file(&mhi_dev->dev.kobj, &dev_attr_status.attr);
> +	sysfs_remove_file(&mhi_dev->dev.kobj, &dev_attr_start.attr);
> +	sysfs_remove_file(&mhi_dev->dev.kobj, &dev_attr_num_tre.attr);
> +	sysfs_remove_file(&mhi_dev->dev.kobj, &dev_attr_size.attr);
> +	mhi_unprepare_from_transfer(mhi_dev);
> +	dev_set_drvdata(&mhi_dev->dev, NULL);
> +}
> +
> +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@....qualcomm.com>");
> +MODULE_AUTHOR("Sumit Kumar <sumit.kumar@....qualcomm.com>");
> +MODULE_DESCRIPTION("MHI Host Loopback Driver");
> +MODULE_LICENSE("GPL");
> 
> -- 
> 2.34.1
> 
> 

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ