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] [day] [month] [year] [list]
Message-ID: <zkhtvquyhgvdqcft6s3jmfjh76hg62mrwn4wj4qqoecrxprq4y@w5zvgp5vbbn2>
Date: Fri, 7 Nov 2025 17:58:18 +0530
From: Manivannan Sadhasivam <mani@...nel.org>
To: Bjorn Andersson <andersson@...nel.org>, 
	Sumit Kumar <sumit.kumar@....qualcomm.com>
Cc: 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 Wed, Nov 05, 2025 at 04:17:41PM -0600, Bjorn Andersson wrote:
> 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.
> 

Though the wording gives an impression like that, this interface is for a MHI
channel that is defined in the MHI spec, so it is perfectly fine to have it
exposed as a sysfs interface to the users. This channel is intented to be used
for MHI protocol testing.

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

Yes, this is mandatory.

> > 
> > 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 ++++++++++++++++++++++++++++++++++++

Create a separate sub-directory 'clients' and move this driver there. Also,
rename it to just 'loopback', but keep the module name as 'mhi_loopback'.

> >  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"

'MHI LOOPBACK client driver'

Use 'LOOPBACK' everywhere.

> > +	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.

Again, this just sounds like this driver exposes a random interface for testing.
You need to properly describe that this driver binds to the MHI LOOPBACK channel
and offers the testing interface to the users.

> > 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);

s/mhi_lb/loopback

> > +
> > +	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);

How the user is supposed to know the size limit if it is just hardcoded in the
driver? You need to expose it as a RO attribute.

Also, 'size' here is the TRE size, not the size of the buffer. So name it as
such.

> > +		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));
> 

Do not print these progress reports in the 'status', just print the end result.
It can be a blocking read.

> 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));

When kzalloc() fails, it will itself print the error log.

> > +		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...
> 

Yes, I do find it quite annoying to see both getting passed on. Use dev_info()
to print the detailed error logs and just return the 'pass' or 'fail' status to
the user via the buffer.

> > +	} 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);

Don't you need to have some lock or sync here?

> > +}
> > +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)

Does this check make sense?

> > +		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);

Move to attribute group as Bjorn suggested.

- Mani

-- 
மணிவண்ணன் சதாசிவம்

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ