Linux Documentation
 help / color / mirror / Atom feed
* Re: [PATCH] docs: fix typo in mpo-overview.rst
From: Jonathan Corbet @ 2026-05-16 14:09 UTC (permalink / raw)
  To: Cheesecake, Alex Deucher, Christian König, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Shuah Khan
  Cc: amd-gfx, dri-devel, linux-doc, linux-kernel, Cheesecake
In-Reply-To: <20260516100406.21070-1-cheesecake2960@icloud.com>

Cheesecake <cheesecake2960@icloud.com> writes:

> Replace "transparant" with "transparent"
>
> Signed-off-by: Cheesecake <cheesecake2960@icloud.com>

Patches need a proper signoff with a real name, please.

Thanks,

jo

^ permalink raw reply

* Re: [PATCH v2] docs: kernel-parameters: document scope of irqaffinity= parameter
From: Aaron Tomlin @ 2026-05-16 14:12 UTC (permalink / raw)
  To: Bagas Sanjaya
  Cc: corbet, skhan, tglx, akpm, bp, rdunlap, dave.hansen, feng.tang,
	pawan.kumar.gupta, dapeng1.mi, kees, elver, paulmck, lirongqing,
	bhelgaas, bigeasy, linux-doc, linux-kernel
In-Reply-To: <afMAAL4lB72HiSQI@archie.me>

On Thu, Apr 30, 2026 at 02:08:48PM +0700, Bagas Sanjaya wrote:
> On Tue, Apr 21, 2026 at 11:09:11AM -0400, Aaron Tomlin wrote:
> > -  This can be verified via the debugfs interface
> > -  (/sys/kernel/debug/irq/irqs/48). The dstate field will include
> > +  If the Linux kernel was built with Kconfig CONFIG_GENERIC_IRQ_DEBUGFS
> > +  enabled, this can be verified via the debugfs interface (e.g.,
> > +  /sys/kernel/debug/irq/irqs/48). The dstate field will include
> >    IRQD_IRQ_DISABLED, IRQD_IRQ_MASKED and IRQD_MANAGED_SHUTDOWN.
> > +  A managed IRQ will also include IRQD_AFFINITY_MANAGED. For example:
> 
> Use double-colon syntax (i.e. ``For example::``) for literal code block
> below.
> 
> > +
> > +    # cat /sys/kernel/debug/irq/irqs/87
> 

Hi Bagas,

Thank you for the feedback.

I will resolve this in the next iteration.


Kind regards,
-- 
Aaron Tomlin

^ permalink raw reply

* Re: Re: Re: [PATCH] dcache: add fs.dentry-limit sysctl with negative-first reaper
From: Horst Birthelmer @ 2026-05-16 14:15 UTC (permalink / raw)
  To: Stafford Horne
  Cc: kernel test robot, Horst Birthelmer, Miklos Szeredi,
	Jonathan Corbet, Shuah Khan, Alexander Viro, Christian Brauner,
	Jan Kara, oe-kbuild-all, linux-doc, linux-kernel, linux-fsdevel,
	Horst Birthelmer
In-Reply-To: <aghIDLYW91C4fcd7@antec>

On Sat, May 16, 2026 at 11:33:48AM +0100, Stafford Horne wrote:
> On Sat, May 16, 2026 at 08:55:16AM +0200, Horst Birthelmer wrote:
> > On Fri, May 15, 2026 at 11:09:54PM +0800, kernel test robot wrote:
> > > Hi Horst,
> > > 
> > > kernel test robot noticed the following build errors:
> > > 
> > > [auto build test ERROR on 5d6919055dec134de3c40167a490f33c74c12581]
> > > 
> > > url:    https://github.com/intel-lab-lkp/linux/commits/Horst-Birthelmer/dcache-add-fs-dentry-limit-sysctl-with-negative-first-reaper/20260515-154600
> > > base:   5d6919055dec134de3c40167a490f33c74c12581
> > > patch link:    https://lore.kernel.org/r/20260514-limit-dentries-cache-v1-1-431b9eb0c530%40ddn.com
> > > patch subject: [PATCH] dcache: add fs.dentry-limit sysctl with negative-first reaper
> > > config: openrisc-randconfig-r073-20260515 (https://download.01.org/0day-ci/archive/20260515/202605152333.0pOd2zJR-lkp@intel.com/config)
> > > compiler: or1k-linux-gcc (GCC) 10.5.0
> > > smatch: v0.5.0-9185-gbcc58b9c
> > > reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260515/202605152333.0pOd2zJR-lkp@intel.com/reproduce)
> > > 
> > > If you fix the issue in a separate patch/commit (i.e. not just a new version of
> > > the same patch/commit), kindly add following tags
> > > | Reported-by: kernel test robot <lkp@intel.com>
> > > | Closes: https://lore.kernel.org/oe-kbuild-all/202605152333.0pOd2zJR-lkp@intel.com/
> > > 
> > > All errors (new ones prefixed by >>):
> > > 
> > >    fs/dcache.c: In function 'dentry_limit_worker_fn':
> > > >> fs/dcache.c:1474:7: error: implicit declaration of function 'get_nr_dentry'; did you mean 'retain_dentry'? [-Werror=implicit-function-declaration]
> > >     1474 |  nr = get_nr_dentry();
> > >          |       ^~~~~~~~~~~~~
> > >          |       retain_dentry
> > >    cc1: some warnings being treated as errors
> > > 
> > > 
> > > vim +1474 fs/dcache.c
> > > 
> > ...
> > > 
> > > --
> > > 0-DAY CI Kernel Test Service
> > > https://github.com/intel/lkp-tests/wiki
> > 
> > This is puzzling to me get_nr_dentry() is defined in line 178 in the same file and first used in line 209 
> > and has been there since 2013.
> > 
> > Builds fine applied to tag v7.1-rc3 and to the current master with gcc and clang.
> 
> Hi
> 
> They are protected in:
> 
> #if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS)
> 
> With #endif on line 247.

You are right, of course.
Thank you!

I will send a corrected version.

> 
> In the rand config as least I see:
>  # CONFIG_PROC_FS is not set
> 
> -Stafford
> 

^ permalink raw reply

* Re: [PATCH v10 8/9] platform/chrome: Protect cros_ec_device lifecycle with revocable
From: Tzung-Bi Shih @ 2026-05-16 14:34 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
	Linus Walleij, Benson Leung, linux-kernel, chrome-platform,
	driver-core, linux-doc, linux-gpio, Rafael J. Wysocki,
	Danilo Krummrich, Jonathan Corbet, Shuah Khan, Laurent Pinchart,
	Wolfram Sang, Johan Hovold, Paul E . McKenney
In-Reply-To: <agbZLY0wn85JqTFV@google.com>

On Fri, May 15, 2026 at 08:28:29AM +0000, Tzung-Bi Shih wrote:
> On Thu, May 14, 2026 at 01:02:14PM -0300, Jason Gunthorpe wrote:
> > On Thu, May 14, 2026 at 03:33:55AM +0000, Tzung-Bi Shih wrote:
> > 
> > > > Given you say this is such a bug I think you really should be sending
> > > > a series that is patches 5 through 7 from the other series and a
> > > > simple rwsem instead of misc_deregister_sync() to deal with this bug
> > > > ASAP. No need to complicate a simple bug fix in a driver with all
> > > > these core changes.
> > > 
> > > Apologies for missing this suggestion.
> > > 
> > > For "patches 5 through 7 from the other series" I guess you're referring:
> > > - https://lore.kernel.org/all/20260427134659.95181-6-tzungbi@kernel.org
> > > - https://lore.kernel.org/all/20260427134659.95181-7-tzungbi@kernel.org
> > > - https://lore.kernel.org/all/20260427134659.95181-8-tzungbi@kernel.org
> > 
> > Yes
> > 
> > > Could you provide a bit more detail on the rwsem approach?  I'm not
> > > entirely clear on what data or operations the rwsem would be protecting.
> > 
> > Just put a rwsem, or even scru, inside the driver's fops.
> > 
> > You can refactor that out to a misc or revocable later.
> 
> I see.  Thank you for your suggestion.  I will explore it and send out a
> new version.

https://lore.kernel.org/all/20260516143017.18560-1-tzungbi@kernel.org
is an attempt at it.

^ permalink raw reply

* Re: [PATCH] docs: submitting-patches: Clarify that in English "reviewer" is a person
From: Vlastimil Babka (SUSE) @ 2026-05-16 14:39 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Jonathan Corbet, Shuah Khan, workflows,
	linux-doc, linux-kernel
  Cc: Greg Kroah-Hartman, Andrew Morton, David Hildenbrand,
	Linus Torvalds, Guenter Roeck
In-Reply-To: <20260516123846.63413-2-krzysztof.kozlowski@oss.qualcomm.com>

On 5/16/26 14:38, Krzysztof Kozlowski wrote:
> Common understanding of word "Reviewer" is: a person performing a review
> work [1]. Tools are not persons, thus cannot be reviewers in this term.
> Also tools cannot make statements ("A Reviewed-by tag is a statement of
> opinion"), since making a statement needs some sort of conscious mind.
> 
> Our docs already clearly mark that "Reviewed-by" must come from a
> person:
> 
>  - "By offering my Reviewed-by: tag, I state that:"
> 
>    Usage of first person "I" and word "state"
> 
>  - "A Reviewed-by tag is *a statement of opinion* that the patch is an
>     appropriate modification of the kernel without any remaining serious"
> 
>    Only a person can make a statement of opinion.
> 
>  - "Any interested reviewer (who has done the work) can offer a
>    Reviewed-by"
> 
>    A person can offer a tag thus above does not grant the tool
>    permission to offer a tag.
> 
> However this is not enough and apparently English is not that precise,
> so let's clarify that only a person can state the "Reviewer's statement
> of oversight".
> 
> Link: https://en.wiktionary.org/wiki/reviewer [1]
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Vlastimil Babka <vbabka@kernel.org>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: David Hildenbrand <david@kernel.org>
> Cc: Linus Torvalds <torvalds@linux-foundation.org>
> Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

I agree with the intent that the tag is for people (whether they use a tool
or not to help them). We also don't put "Tested-by: kernel test robot" or
syzkaller on every commit that they test and find no bugs. Review is also
not just about absence of bugs, but agreeing with the larger design and
whether the change makes sense to do in the first place.

So whether that's achieved with this particular wording or differently,

Acked-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>

> 
> ---
> 
> I find it silly to need to describe English, but it seems it is needed.
> 
> https://lore.kernel.org/all/fd3b2ca7-4d64-4c4b-98a3-7d3285fa6826@roeck-us.net/
> ---
>  Documentation/process/submitting-patches.rst | 8 ++++----
>  1 file changed, 4 insertions(+), 4 deletions(-)
> 
> diff --git a/Documentation/process/submitting-patches.rst b/Documentation/process/submitting-patches.rst
> index d7290e208e72..a989de43f3db 100644
> --- a/Documentation/process/submitting-patches.rst
> +++ b/Documentation/process/submitting-patches.rst
> @@ -581,10 +581,10 @@ By offering my Reviewed-by: tag, I state that:
>  
>  A Reviewed-by tag is a statement of opinion that the patch is an
>  appropriate modification of the kernel without any remaining serious
> -technical issues.  Any interested reviewer (who has done the work) can
> -offer a Reviewed-by tag for a patch.  This tag serves to give credit to
> -reviewers and to inform maintainers of the degree of review which has been
> -done on the patch.  Reviewed-by: tags, when supplied by reviewers known to
> +technical issues.  Any interested reviewer (who has done the work and is a
> +person) can offer a Reviewed-by tag for a patch.  This tag serves to give
> +credit to reviewers and to inform maintainers of the degree of review which has
> +been done on the patch.  Reviewed-by: tags, when supplied by reviewers known to
>  understand the subject area and to perform thorough reviews, will normally
>  increase the likelihood of your patch getting into the kernel.
>  


^ permalink raw reply

* Re: [PATCH v11 4/5] platform/chrome: Protect cros_ec_device lifecycle with revocable
From: Tzung-Bi Shih @ 2026-05-16 14:42 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
	Linus Walleij, Benson Leung, linux-kernel, chrome-platform,
	driver-core, linux-doc, linux-gpio, Rafael J. Wysocki,
	Danilo Krummrich, Jonathan Corbet, Shuah Khan, Laurent Pinchart,
	Wolfram Sang, Johan Hovold, Paul E . McKenney
In-Reply-To: <20260514160043.GG787748@nvidia.com>

On Thu, May 14, 2026 at 01:00:43PM -0300, Jason Gunthorpe wrote:
> On Thu, May 14, 2026 at 03:34:12AM +0000, Tzung-Bi Shih wrote:
> 
> > To help me understand, could you elaborate on why the revocable mechanism
> > isn't suitable here?
> 
> Stay within one driver. Create the revokable is probe, consume it
> within that drivers fops/etc, destroy it on remove. Do not randomly
> pass it to other drivers.

In that sense, after applying [1], does the patch make sense to you?

[1] https://lore.kernel.org/all/20260516143017.18560-1-tzungbi@kernel.org

diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/chrome/cros_ec_chardev.c
index 450f79759122..27e52fee59f6 100644
--- a/drivers/platform/chrome/cros_ec_chardev.c
+++ b/drivers/platform/chrome/cros_ec_chardev.c
@@ -23,7 +23,7 @@
 #include <linux/platform_data/cros_ec_proto.h>
 #include <linux/platform_device.h>
 #include <linux/poll.h>
-#include <linux/rwsem.h>
+#include <linux/revocable.h>
 #include <linux/slab.h>
 #include <linux/types.h>
 #include <linux/uaccess.h>
@@ -39,8 +39,7 @@
 struct chardev_pdata {
 	struct miscdevice misc;
 	struct kref kref;
-	struct rw_semaphore ec_dev_sem;
-	struct cros_ec_device *ec_dev;
+	struct revocable ec_rev;
 	u16 cmd_offset;
 	struct blocking_notifier_head subscribers;
 	struct notifier_block relay;
@@ -50,6 +49,7 @@ static void chardev_pdata_release(struct kref *kref)
 {
 	struct chardev_pdata *pdata = container_of(kref, typeof(*pdata), kref);
 
+	revocable_put(&pdata->ec_rev);
 	kfree(pdata);
 }
 
@@ -87,6 +87,7 @@ static int ec_get_version(struct chardev_priv *priv, char *str, int maxlen)
 	};
 	struct ec_response_get_version *resp;
 	struct cros_ec_command *msg;
+	struct cros_ec_device *ec_dev;
 	int ret;
 
 	msg = kzalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL);
@@ -96,13 +97,13 @@ static int ec_get_version(struct chardev_priv *priv, char *str, int maxlen)
 	msg->command = EC_CMD_GET_VERSION + priv->pdata->cmd_offset;
 	msg->insize = sizeof(*resp);
 
-	scoped_guard(rwsem_read, &priv->pdata->ec_dev_sem) {
-		if (!priv->pdata->ec_dev) {
+	revocable_try_access_with_scoped(&priv->pdata->ec_rev, ec_dev) {
+		if (!ec_dev) {
 			ret = -ENODEV;
 			goto exit;
 		}
 
-		ret = cros_ec_cmd_xfer_status(priv->pdata->ec_dev, msg);
+		ret = cros_ec_cmd_xfer_status(ec_dev, msg);
 		if (ret < 0) {
 			snprintf(str, maxlen,
 				 "Unknown EC version, returned error: %d\n",
@@ -136,10 +137,8 @@ static int cros_ec_chardev_mkbp_event(struct notifier_block *nb,
 	unsigned long event_bit;
 	int total_size;
 
-	guard(rwsem_read)(&priv->pdata->ec_dev_sem);
-	if (!priv->pdata->ec_dev)
-		return NOTIFY_DONE;
-	ec_dev = priv->pdata->ec_dev;
+	revocable_try_access_or_return_err(&priv->pdata->ec_rev, ec_dev,
+					   NOTIFY_DONE);
 
 	event_bit = 1 << ec_dev->event_data.event_type;
 	total_size = sizeof(*event) + ec_dev->event_size;
@@ -206,6 +205,7 @@ static int cros_ec_chardev_open(struct inode *inode, struct file *filp)
 	struct miscdevice *mdev = filp->private_data;
 	struct chardev_pdata *pdata = container_of(mdev, typeof(*pdata), misc);
 	struct chardev_priv *priv;
+	struct cros_ec_device *ec_dev;
 	int ret;
 
 	priv = kzalloc_obj(*priv);
@@ -223,11 +223,9 @@ static int cros_ec_chardev_open(struct inode *inode, struct file *filp)
 	ret = blocking_notifier_chain_register(&pdata->subscribers,
 					       &priv->notifier);
 	if (ret) {
-		scoped_guard(rwsem_read, &pdata->ec_dev_sem) {
-			if (pdata->ec_dev)
-				dev_err(pdata->ec_dev->dev,
-					"failed to register event notifier\n");
-		}
+		revocable_try_access_or_skip_scoped(&pdata->ec_rev, ec_dev)
+			dev_err(ec_dev->dev,
+				"failed to register event notifier\n");
 		kref_put(&priv->pdata->kref, chardev_pdata_release);
 		kfree(priv);
 	}
@@ -324,6 +322,7 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev_priv *priv, void __user *a
 {
 	struct cros_ec_command *s_cmd;
 	struct cros_ec_command u_cmd;
+	struct cros_ec_device *ec_dev;
 	long ret;
 
 	if (copy_from_user(&u_cmd, arg, sizeof(u_cmd)))
@@ -351,13 +350,13 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev_priv *priv, void __user *a
 
 	s_cmd->command += priv->pdata->cmd_offset;
 
-	scoped_guard(rwsem_read, &priv->pdata->ec_dev_sem) {
-		if (!priv->pdata->ec_dev) {
+	revocable_try_access_with_scoped(&priv->pdata->ec_rev, ec_dev) {
+		if (!ec_dev) {
 			ret = -ENODEV;
 			goto exit;
 		}
 
-		ret = cros_ec_cmd_xfer(priv->pdata->ec_dev, s_cmd);
+		ret = cros_ec_cmd_xfer(ec_dev, s_cmd);
 		/* Only copy data to userland if data was received. */
 		if (ret < 0)
 			goto exit;
@@ -376,10 +375,7 @@ static long cros_ec_chardev_ioctl_readmem(struct chardev_priv *priv, void __user
 	struct cros_ec_readmem s_mem = { };
 	long num;
 
-	guard(rwsem_read)(&priv->pdata->ec_dev_sem);
-	if (!priv->pdata->ec_dev)
-		return -ENODEV;
-	ec_dev = priv->pdata->ec_dev;
+	revocable_try_access_or_return(&priv->pdata->ec_rev, ec_dev);
 
 	/* Not every platform supports direct reads */
 	if (!ec_dev->cmd_readmem)
@@ -438,25 +434,29 @@ static int cros_ec_chardev_probe(struct platform_device *pdev)
 {
 	struct cros_ec_dev *ec = dev_get_drvdata(pdev->dev.parent);
 	struct cros_ec_platform *ec_platform = dev_get_platdata(ec->dev);
+	struct cros_ec_device *ec_dev = ec->ec_dev;
 	struct chardev_pdata *pdata;
 	int ret;
 
 	pdata = kzalloc_obj(*pdata);
 	if (!pdata)
 		return -ENOMEM;
+	ret = revocable_init(&pdata->ec_rev, ec_dev);
+	if (ret) {
+		kfree(pdata);
+		return ret;
+	}
 
 	platform_set_drvdata(pdev, pdata);
 	kref_init(&pdata->kref);
-	init_rwsem(&pdata->ec_dev_sem);
-	pdata->ec_dev = ec->ec_dev;
 	pdata->cmd_offset = ec->cmd_offset;
 	BLOCKING_INIT_NOTIFIER_HEAD(&pdata->subscribers);
 	pdata->relay.notifier_call = cros_ec_chardev_relay_event;
-	ret = blocking_notifier_chain_register(&pdata->ec_dev->event_notifier,
+	ret = blocking_notifier_chain_register(&ec_dev->event_notifier,
 					       &pdata->relay);
 	if (ret) {
 		dev_err(&pdev->dev, "failed to register event notifier\n");
-		goto err_put_pdata;
+		goto err_revoke_ec_rev;
 	}
 
 	pdata->misc.minor = MISC_DYNAMIC_MINOR;
@@ -472,9 +472,10 @@ static int cros_ec_chardev_probe(struct platform_device *pdev)
 
 	return 0;
 err_unregister_notifier:
-	blocking_notifier_chain_unregister(&pdata->ec_dev->event_notifier,
+	blocking_notifier_chain_unregister(&ec_dev->event_notifier,
 					   &pdata->relay);
-err_put_pdata:
+err_revoke_ec_rev:
+	revocable_revoke(&pdata->ec_rev);
 	kref_put(&pdata->kref, chardev_pdata_release);
 	return ret;
 }
@@ -482,11 +483,12 @@ static int cros_ec_chardev_probe(struct platform_device *pdev)
 static void cros_ec_chardev_remove(struct platform_device *pdev)
 {
 	struct chardev_pdata *pdata = platform_get_drvdata(pdev);
+	struct cros_ec_device *ec_dev;
 
-	blocking_notifier_chain_unregister(&pdata->ec_dev->event_notifier,
-					   &pdata->relay);
-	scoped_guard(rwsem_write, &pdata->ec_dev_sem)
-		pdata->ec_dev = NULL;
+	revocable_try_access_or_skip_scoped(&pdata->ec_rev, ec_dev)
+		blocking_notifier_chain_unregister(&ec_dev->event_notifier,
+						   &pdata->relay);
+	revocable_revoke(&pdata->ec_rev);
 	misc_deregister(&pdata->misc);
 	kref_put(&pdata->kref, chardev_pdata_release);
 }

^ permalink raw reply related

* [PATCH v2] dcache: add fs.dentry-limit sysctl with negative-first reaper
From: Horst Birthelmer @ 2026-05-16 14:52 UTC (permalink / raw)
  To: Miklos Szeredi, Jonathan Corbet, Shuah Khan, Alexander Viro,
	Christian Brauner, Jan Kara
  Cc: linux-doc, linux-kernel, linux-fsdevel, Horst Birthelmer

From: Horst Birthelmer <hbirthelmer@ddn.com>

The dcache only shrinks under memory pressure, which is rarely reached
on machines with ample RAM, so cached negative dentries can accumulate
without bound.  Give administrators a soft cap they can set,
and a background worker that prefers negative dentries when reclaiming.

Two new sysctls under /proc/sys/fs/:

  dentry-limit             -- soft cap on nr_dentry.  0 (default)
                              disables the feature; behaviour is then
                              identical to before.
  dentry-limit-interval-ms -- pacing for the worker while still over
                              the cap.  Default 1000, minimum 1.

When the cap is exceeded, a delayed_work runs in two phases:

  1. iterate_supers() draining only negative dentries from every LRU.
     Positive entries are rotated past so the walk makes progress.
     DCACHE_REFERENCED is ignored here on purpose -- an admin-imposed
     cap should evict even hot negatives before any positive entry.
  2. If still over the cap, iterate_supers() again with the same
     isolate callback the memory-pressure shrinker uses.

Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
---
There was a discussion at LSFMM about servers with too many cached
negative dentries.
That gave me the idea to keep the dentries in general limited
if the system administrator needs it to.

This is somewhat related to [1] where it would address the same
symptoms but in a more unobtrusive way, by just garbage collecting
the negative and then the unused cache entries.

The other effect I have seen regarding this is that FUSE
will not forget inodes (no FORGET call to the FUSE server)
even after the latest reference has been closed until much later.

In a FUSE server that mirrors the kernel cached inodes in user space
because it has to keep a lot of private data for every node
this puts an unnecessarry memory strain on that userspace entity
especially if the memory is limited for its cgroup.

[1]: https://lore.kernel.org/linux-fsdevel/20260331012925.74840-1-raven@themaw.net/
---
Changes in v2:
- get_nr_dentry() was protected by #if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS) 
  fix the location for the reaper code to be inside the #if bracket
- Link to v1: https://lore.kernel.org/r/20260514-limit-dentries-cache-v1-1-431b9eb0c530@ddn.com
---
 Documentation/admin-guide/sysctl/fs.rst |  28 +++++
 fs/dcache.c                             | 207 ++++++++++++++++++++++++++++++++
 2 files changed, 235 insertions(+)

diff --git a/Documentation/admin-guide/sysctl/fs.rst b/Documentation/admin-guide/sysctl/fs.rst
index 9b7f65c3efd8..0229aea45d85 100644
--- a/Documentation/admin-guide/sysctl/fs.rst
+++ b/Documentation/admin-guide/sysctl/fs.rst
@@ -38,6 +38,34 @@ requests.  ``aio-max-nr`` allows you to change the maximum value
 ``aio-max-nr`` does not result in the
 pre-allocation or re-sizing of any kernel data structures.
 
+dentry-limit
+------------
+
+Soft cap on the total number of dentries allocated system-wide (i.e. on
+``nr_dentry`` from ``dentry-state``).  A value of ``0`` (the default)
+disables the feature and the dcache grows or shrinks only under memory
+pressure as before.
+
+When set to a non-zero value, a background worker is woken whenever
+the live dentry count exceeds the limit. The worker walks every
+superblock's LRU and prefers to evict negative dentries first; if it
+cannot get back under the limit using negative entries alone it falls
+back to the same LRU policy used by the memory-pressure shrinker.
+
+The limit is *soft*: allocations never fail because of it, and brief
+overshoots while the worker catches up are expected. Set the cap a
+comfortable margin above your steady-state working set.
+
+dentry-limit-interval-ms
+------------------------
+
+How often, in milliseconds, the ``dentry-limit`` worker re-runs while
+``nr_dentry`` is still above the cap. Defaults to ``1000`` (one
+second); the minimum accepted value is ``1``. Smaller values trim the
+cache more aggressively at the cost of more CPU spent walking LRUs;
+larger values let temporary spikes ride out before any work is done.
+Has no effect when ``dentry-limit`` is ``0``.
+
 dentry-negative
 ----------------------------
 
diff --git a/fs/dcache.c b/fs/dcache.c
index 2c61aeea41f4..196f842845ed 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -145,6 +145,26 @@ static DEFINE_PER_CPU(long, nr_dentry_negative);
 static int dentry_negative_policy;
 
 #if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS)
+/*
+ * Soft cap on the total number of dentries. When non-zero and exceeded,
+ * a background worker prunes unused dentries (preferring negative ones)
+ * until we are back under the limit. Zero (the default) disables the
+ * feature entirely; the fast path in __d_alloc() only pays the cost of
+ * a READ_ONCE and a branch in that case.
+ */
+static unsigned long sysctl_dentry_limit __read_mostly;
+static unsigned int sysctl_dentry_limit_interval_ms __read_mostly = 1000;
+static unsigned long dentry_limit_last_kick;
+
+static void dentry_limit_kick(void);
+
+/* Forward decls: the helpers used by the reaper live further down. */
+static void d_lru_isolate(struct list_lru_one *lru, struct dentry *dentry);
+static void d_lru_shrink_move(struct list_lru_one *lru, struct dentry *dentry,
+			      struct list_head *list);
+static enum lru_status dentry_lru_isolate(struct list_head *item,
+			      struct list_lru_one *lru, void *arg);
+
 /* Statistics gathering. */
 static struct dentry_stat_t dentry_stat = {
 	.age_limit = 45,
@@ -171,6 +191,161 @@ static long get_nr_dentry(void)
 	return sum < 0 ? 0 : sum;
 }
 
+#define DENTRY_LIMIT_BATCH	1024UL
+
+static void dentry_limit_worker_fn(struct work_struct *work);
+static DECLARE_DELAYED_WORK(dentry_limit_work, dentry_limit_worker_fn);
+
+/*
+ * Variant of dentry_lru_isolate() that only frees negative dentries.
+ * DCACHE_REFERENCED is intentionally not honoured here: the whole point
+ * of an admin-imposed cap on negatives is that even frequently-looked-up
+ * negative entries should be evicted before any positive dentry.
+ * Positive entries are rotated to the tail so the walk continues to
+ * make progress without disturbing their LRU position.
+ */
+static enum lru_status dentry_lru_isolate_negative(struct list_head *item,
+		struct list_lru_one *lru, void *arg)
+{
+	struct list_head *freeable = arg;
+	struct dentry *dentry = container_of(item, struct dentry, d_lru);
+
+	if (!spin_trylock(&dentry->d_lock))
+		return LRU_SKIP;
+
+	/* Same handling as dentry_lru_isolate() for in-use entries. */
+	if (dentry->d_lockref.count) {
+		d_lru_isolate(lru, dentry);
+		spin_unlock(&dentry->d_lock);
+		return LRU_REMOVED;
+	}
+
+	if (!d_is_negative(dentry)) {
+		spin_unlock(&dentry->d_lock);
+		return LRU_ROTATE;
+	}
+
+	d_lru_shrink_move(lru, dentry, freeable);
+	spin_unlock(&dentry->d_lock);
+	return LRU_REMOVED;
+}
+
+struct dentry_limit_ctx {
+	long over;		/* remaining dentries to evict */
+	list_lru_walk_cb isolate;
+};
+
+static void dentry_limit_prune_sb(struct super_block *sb, void *arg)
+{
+	struct dentry_limit_ctx *ctx = arg;
+	unsigned long walked = 0;
+	unsigned long budget;
+
+	if (ctx->over <= 0)
+		return;
+
+	/*
+	 * Walk up to one full pass of this superblock's LRU, in
+	 * DENTRY_LIMIT_BATCH-sized chunks. The loop matters mainly for
+	 * phase 1: dentry_lru_isolate_negative() returns LRU_ROTATE for
+	 * positive dentries, which still counts against list_lru_walk()'s
+	 * nr_to_walk. A single batch can therefore finish having freed
+	 * nothing when positives crowd the head of the LRU, and without
+	 * the inner loop the worker would have to wait a full
+	 * dentry-limit-interval-ms before retrying never reaching the
+	 * negatives buried behind a long run of positives.
+	 *
+	 * The budget is snapshot at entry so a filesystem allocating
+	 * dentries faster than we drain them can't keep us spinning here
+	 * forever; freshly added dentries are picked up on the next
+	 * worker invocation.
+	 *
+	 * Phase 2 normally exits much sooner: its isolate callback frees
+	 * any non-referenced dentry, so ctx->over typically hits zero
+	 * inside the first batch. The worst-case over-eviction is one
+	 * batch past the cap, which is within the soft semantics of
+	 * fs.dentry-limit.
+	 */
+	budget = list_lru_count(&sb->s_dentry_lru);
+
+	while (ctx->over > 0 && walked < budget) {
+		LIST_HEAD(dispose);
+		unsigned long nr;
+		long freed;
+
+		nr = min(DENTRY_LIMIT_BATCH, budget - walked);
+		freed = list_lru_walk(&sb->s_dentry_lru, ctx->isolate,
+				      &dispose, nr);
+		shrink_dentry_list(&dispose);
+
+		ctx->over -= freed;
+		walked += nr;
+
+		cond_resched();
+	}
+}
+
+static void dentry_limit_worker_fn(struct work_struct *work)
+{
+	struct dentry_limit_ctx ctx;
+	unsigned long limit = READ_ONCE(sysctl_dentry_limit);
+	unsigned int ms;
+	long nr;
+
+	if (!limit)
+		return;
+
+	nr = get_nr_dentry();
+	if (nr <= (long)limit)
+		return;
+
+	ctx.over = nr - (long)limit;
+
+	/* Phase 1: drain negative dentries across every superblock. */
+	ctx.isolate = dentry_lru_isolate_negative;
+	iterate_supers(dentry_limit_prune_sb, &ctx);
+
+	/* Phase 2: still over? Apply the ordinary LRU policy. */
+	if (ctx.over > 0) {
+		ctx.isolate = dentry_lru_isolate;
+		iterate_supers(dentry_limit_prune_sb, &ctx);
+	}
+
+	/*
+	 * Re-arm while still above the limit. Re-read the sysctls in
+	 * case the admin raised the cap or disabled the feature during
+	 * the walk.
+	 */
+	limit = READ_ONCE(sysctl_dentry_limit);
+	if (!limit || get_nr_dentry() <= (long)limit)
+		return;
+
+	ms = READ_ONCE(sysctl_dentry_limit_interval_ms);
+	queue_delayed_work(system_unbound_wq, &dentry_limit_work,
+			   msecs_to_jiffies(ms));
+}
+
+static void dentry_limit_kick(void)
+{
+	unsigned long limit = READ_ONCE(sysctl_dentry_limit);
+	unsigned long now;
+
+	if (!limit)
+		return;
+	if (delayed_work_pending(&dentry_limit_work))
+		return;
+
+	now = jiffies;
+	if (time_before(now, READ_ONCE(dentry_limit_last_kick) + HZ / 10))
+		return;
+	WRITE_ONCE(dentry_limit_last_kick, now);
+
+	if (get_nr_dentry() <= (long)limit)
+		return;
+
+	queue_delayed_work(system_unbound_wq, &dentry_limit_work, 0);
+}
+
 static long get_nr_dentry_unused(void)
 {
 	int i;
@@ -199,6 +374,20 @@ static int proc_nr_dentry(const struct ctl_table *table, int write, void *buffer
 	return proc_doulongvec_minmax(table, write, buffer, lenp, ppos);
 }
 
+/*
+ * Writing fs.dentry-limit should give prompt feedback to admins
+ * lowering the cap, so kick the worker on every successful write.
+ */
+static int proc_dentry_limit(const struct ctl_table *table, int write,
+			     void *buffer, size_t *lenp, loff_t *ppos)
+{
+	int ret = proc_doulongvec_minmax(table, write, buffer, lenp, ppos);
+
+	if (write && !ret)
+		dentry_limit_kick();
+	return ret;
+}
+
 static const struct ctl_table fs_dcache_sysctls[] = {
 	{
 		.procname	= "dentry-state",
@@ -207,6 +396,21 @@ static const struct ctl_table fs_dcache_sysctls[] = {
 		.mode		= 0444,
 		.proc_handler	= proc_nr_dentry,
 	},
+	{
+		.procname	= "dentry-limit",
+		.data		= &sysctl_dentry_limit,
+		.maxlen		= sizeof(sysctl_dentry_limit),
+		.mode		= 0644,
+		.proc_handler	= proc_dentry_limit,
+	},
+	{
+		.procname	= "dentry-limit-interval-ms",
+		.data		= &sysctl_dentry_limit_interval_ms,
+		.maxlen		= sizeof(sysctl_dentry_limit_interval_ms),
+		.mode		= 0644,
+		.proc_handler	= proc_douintvec_minmax,
+		.extra1		= SYSCTL_ONE,
+	},
 	{
 		.procname	= "dentry-negative",
 		.data		= &dentry_negative_policy,
@@ -1868,6 +2072,9 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
 	}
 
 	this_cpu_inc(nr_dentry);
+#if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS)
+	dentry_limit_kick();
+#endif
 
 	return dentry;
 }

---
base-commit: 6916d5703ddf9a38f1f6c2cc793381a24ee914c6
change-id: 20260513-limit-dentries-cache-63685729672b

Best regards,
-- 
Horst Birthelmer <hbirthelmer@ddn.com>


^ permalink raw reply related

* Re: [PATCH v4 1/2] dt-bindings: trivial-devices: Add Murata D1U74T PSU
From: Guenter Roeck @ 2026-05-16 15:11 UTC (permalink / raw)
  To: Abdurrahman Hussain
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, linux-hwmon, devicetree, linux-kernel, linux-doc
In-Reply-To: <20260514-d1u74t-v4-1-1f1ee7b002ec@nexthop.ai>

On Thu, May 14, 2026 at 08:03:25PM -0700, Abdurrahman Hussain wrote:
> The Murata D1U74T-W series are hot-pluggable 1U AC/DC front-end
> power supplies in the Intel CRPS-185 / OCP M-CRPS form factor.
> Each variant delivers a 12 V main output plus a 12 V standby output
> from a wide AC input (90-264 Vac) or HVDC supply, and includes an
> internal variable-speed cooling fan and on-board voltage, current,
> power, fan-speed, and temperature telemetry.
> 
> The host-side digital interface is a PMBus 1.2 port on I2C.  The
> PSU's other electrical signals (status, alert, current-share) live
> on the CRPS edge connector and are consumed by the chassis
> controller rather than the host SoC, so there are no host-described
> supplies, GPIOs, clocks, or interrupts.  Add the compatible to
> trivial-devices.yaml rather than carrying a standalone binding file.
> 
> Signed-off-by: Abdurrahman Hussain <abdurrahman@nexthop.ai>
> Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

Applied.

Thanks,
Guenter

^ permalink raw reply

* Re: [PATCH v4 2/2] hwmon: (pmbus/d1u74t) Add Murata D1U74T PSU driver
From: Guenter Roeck @ 2026-05-16 15:13 UTC (permalink / raw)
  To: Abdurrahman Hussain
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, linux-hwmon, devicetree, linux-kernel, linux-doc,
	kernel test robot
In-Reply-To: <20260514-d1u74t-v4-2-1f1ee7b002ec@nexthop.ai>

On Thu, May 14, 2026 at 08:03:26PM -0700, Abdurrahman Hussain wrote:
> Add PMBUS driver for Murata D1U74T power supplies.
> 
> Reported-by: kernel test robot <lkp@intel.com>
> Closes: https://lore.kernel.org/oe-kbuild-all/202605122253.zInzmUeX-lkp@intel.com/
> Signed-off-by: Abdurrahman Hussain <abdurrahman@nexthop.ai>

Applied, after dropping the above Reported-by: and Closes: tags.
Those are inappropriate for new drivers.

I also added the missing include files.

Guenter

> ---
>  Documentation/hwmon/d1u74t.rst | 81 +++++++++++++++++++++++++++++++++++++++
>  Documentation/hwmon/index.rst  |  1 +
>  MAINTAINERS                    |  7 ++++
>  drivers/hwmon/pmbus/Kconfig    |  9 +++++
>  drivers/hwmon/pmbus/Makefile   |  1 +
>  drivers/hwmon/pmbus/d1u74t.c   | 86 ++++++++++++++++++++++++++++++++++++++++++
>  6 files changed, 185 insertions(+)
> 
> diff --git a/Documentation/hwmon/d1u74t.rst b/Documentation/hwmon/d1u74t.rst
> new file mode 100644
> index 000000000000..3a9eedbda483
> --- /dev/null
> +++ b/Documentation/hwmon/d1u74t.rst
> @@ -0,0 +1,81 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Kernel driver d1u74t
> +====================
> +
> +Supported chips:
> +
> +  * Murata D1U74T
> +
> +    Prefix: 'd1u74t'
> +
> +    Addresses scanned: -
> +
> +    Datasheet: Publicly available at the Murata website
> +
> +Authors:
> +    Abdurrahman Hussain <abdurrahman@nexthop.ai>
> +
> +
> +Description
> +-----------
> +
> +This driver implements support for Murata D1U74T Power Supply with
> +PMBus support.
> +
> +The driver is a client driver to the core PMBus driver.
> +Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
> +
> +
> +Usage Notes
> +-----------
> +
> +This driver does not auto-detect devices. You will have to instantiate the
> +devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for
> +details.
> +
> +
> +Sysfs entries
> +-------------
> +
> +======================= ======================================================
> +curr1_label		"iin"
> +curr1_input		Measured input current
> +curr1_alarm		Input current alarm
> +curr1_rated_max		Maximum rated input current
> +
> +curr2_label		"iout1"
> +curr2_input		Measured output current
> +curr2_max		Maximum output current
> +curr2_max_alarm		Output current high alarm
> +curr2_crit		Critical high output current
> +curr2_crit_alarm	Output current critical high alarm
> +curr2_rated_max		Maximum rated output current
> +
> +in1_label		"vin"
> +in1_input		Measured input voltage
> +in1_alarm		Input voltage alarm
> +in1_rated_min		Minimum rated input voltage
> +in1_rated_max		Maximum rated input voltage
> +
> +in2_label		"vout1"
> +in2_input		Measured output voltage
> +in2_alarm		Output voltage alarm
> +in2_rated_min		Minimum rated output voltage
> +in2_rated_max		Maximum rated output voltage
> +
> +power1_label		"pin"
> +power1_input		Measured input power
> +power1_alarm		Input power alarm
> +power1_rated_max	Maximum rated input power
> +
> +temp[1-3]_input		Measured temperature
> +temp[1-3]_max		Maximum temperature
> +temp[1-3]_max_alarm	Maximum temperature alarm
> +temp[1-3]_rated_max	Maximum rated temperature
> +
> +fan1_alarm		Fan 1 warning
> +fan1_fault		Fan 1 fault
> +fan1_input		Fan 1 speed in RPM
> +fan1_target		Fan 1 target
> +======================= ======================================================
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 8b655e5d6b68..97b1ef65b1c1 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -60,6 +60,7 @@ Hardware Monitoring Kernel Drivers
>     corsair-psu
>     cros_ec_hwmon
>     crps
> +   d1u74t
>     da9052
>     da9055
>     dell-smm-hwmon
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b2040011a386..3106cf725dfc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18249,6 +18249,13 @@ F:	drivers/mux/
>  F:	include/dt-bindings/mux/
>  F:	include/linux/mux/
>  
> +MURATA D1U74T PSU DRIVER
> +M:	Abdurrahman Hussain <abdurrahman@nexthop.ai>
> +L:	linux-hwmon@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/hwmon/d1u74t.rst
> +F:	drivers/hwmon/pmbus/d1u74t.c
> +
>  MUSB MULTIPOINT HIGH SPEED DUAL-ROLE CONTROLLER
>  M:	Bin Liu <b-liu@ti.com>
>  L:	linux-usb@vger.kernel.org
> diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
> index 8f4bff375ecb..ee93b22d2887 100644
> --- a/drivers/hwmon/pmbus/Kconfig
> +++ b/drivers/hwmon/pmbus/Kconfig
> @@ -113,6 +113,15 @@ config SENSORS_CRPS
>  	  This driver can also be built as a module. If so, the module will
>  	  be called crps.
>  
> +config SENSORS_D1U74T
> +	tristate "Murata D1U74T Power Supply"
> +	help
> +	  If you say yes here you get hardware monitoring support for the Murata
> +	  D1U74T Power Supply.
> +
> +	  This driver can also be built as a module. If so, the module will
> +	  be called d1u74t.
> +
>  config SENSORS_DELTA_AHE50DC_FAN
>  	tristate "Delta AHE-50DC fan control module"
>  	help
> diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
> index 7129b62bc00f..8cf7d3075371 100644
> --- a/drivers/hwmon/pmbus/Makefile
> +++ b/drivers/hwmon/pmbus/Makefile
> @@ -76,3 +76,4 @@ obj-$(CONFIG_SENSORS_XDPE1A2G7B)	+= xdpe1a2g7b.o
>  obj-$(CONFIG_SENSORS_ZL6100)	+= zl6100.o
>  obj-$(CONFIG_SENSORS_PIM4328)	+= pim4328.o
>  obj-$(CONFIG_SENSORS_CRPS)	+= crps.o
> +obj-$(CONFIG_SENSORS_D1U74T)	+= d1u74t.o
> diff --git a/drivers/hwmon/pmbus/d1u74t.c b/drivers/hwmon/pmbus/d1u74t.c
> new file mode 100644
> index 000000000000..286ba492e336
> --- /dev/null
> +++ b/drivers/hwmon/pmbus/d1u74t.c
> @@ -0,0 +1,86 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright 2026 Nexthop Systems.
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/of.h>
> +#include <linux/pmbus.h>
> +
> +#include "pmbus.h"
> +
> +static const struct i2c_device_id d1u74t_id[] = {
> +	{ "d1u74t" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(i2c, d1u74t_id);
> +
> +static struct pmbus_driver_info d1u74t_info = {
> +	.pages = 1,
> +	/* PSU uses default linear data format. */
> +	.func[0] = PMBUS_HAVE_PIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
> +		   PMBUS_HAVE_IIN | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT |
> +		   PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_TEMP |
> +		   PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 |
> +		   PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_FAN12 |
> +		   PMBUS_HAVE_STATUS_FAN12,
> +};
> +
> +static int d1u74t_probe(struct i2c_client *client)
> +{
> +	char buf[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
> +	struct device *dev = &client->dev;
> +	int rc;
> +
> +	rc = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
> +	if (rc < 0)
> +		return dev_err_probe(dev, rc, "Failed to read PMBUS_MFR_ID\n");
> +
> +	if (rc != 9 || strncmp(buf, "Murata-PS", 9)) {
> +		buf[rc] = '\0';
> +		return dev_err_probe(dev, -ENODEV,
> +				     "Unsupported Manufacturer ID '%s'\n",
> +				     buf);
> +	}
> +
> +	rc = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
> +	if (rc < 0)
> +		return dev_err_probe(dev, rc,
> +				     "Failed to read PMBUS_MFR_MODEL\n");
> +
> +	if (rc < 8 || strncmp(buf, "D1U74T-W", 8)) {
> +		buf[rc] = '\0';
> +		return dev_err_probe(dev, -ENODEV, "Model '%s' not supported\n",
> +				     buf);
> +	}
> +
> +	rc = pmbus_do_probe(client, &d1u74t_info);
> +	if (rc)
> +		return dev_err_probe(dev, rc, "Failed to probe\n");
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id d1u74t_of_match[] = {
> +	{
> +		.compatible = "murata,d1u74t",
> +	},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, d1u74t_of_match);
> +
> +static struct i2c_driver d1u74t_driver = {
> +	.driver = {
> +		.name = "d1u74t",
> +		.of_match_table = d1u74t_of_match,
> +	},
> +	.probe = d1u74t_probe,
> +	.id_table = d1u74t_id,
> +};
> +
> +module_i2c_driver(d1u74t_driver);
> +
> +MODULE_AUTHOR("Abdurrahman Hussain");
> +MODULE_DESCRIPTION("PMBus driver for Murata D1U74T-W power supplies");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("PMBUS");

^ permalink raw reply

* Re: [PATCH v2 1/3] Doc: deprecated.rst: add strlcat()
From: Heiko Carstens @ 2026-05-16 15:28 UTC (permalink / raw)
  To: Kees Cook
  Cc: Manuel Ebner, Andy Shevchenko, Jonathan Corbet, Shuah Khan,
	Andy Whitcroft, Joe Perches, Dwaipayan Ray, Lukas Bulwahn,
	Geert Uytterhoeven, David Laight, Randy Dunlap, Jani Nikula,
	open list:DOCUMENTATION PROCESS, open list:DOCUMENTATION,
	open list
In-Reply-To: <202605140931.913048A68B@keescook>

On Thu, May 14, 2026 at 09:31:46AM -0700, Kees Cook wrote:
> On Thu, May 14, 2026 at 06:26:53PM +0200, Manuel Ebner wrote:
> > add strlcat and alternatives
> > 
> > Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
> > ---
> >  Documentation/process/deprecated.rst | 7 +++++++
> >  1 file changed, 7 insertions(+)
> > 
> > diff --git a/Documentation/process/deprecated.rst b/Documentation/process/deprecated.rst
> > index fed56864d036..06e802f4bbfd 100644
> > --- a/Documentation/process/deprecated.rst
> > +++ b/Documentation/process/deprecated.rst
> > @@ -153,6 +153,13 @@ used, and the destinations should be marked with the `__nonstring
> >  attribute to avoid future compiler warnings. For cases still needing
> >  NUL-padding, strtomem_pad() can be used.
> >  
> > +strlcat()
> > +---------
> > +strlcat() must re-scan the destination string from the beginning on each
> > +call (O(n^2) behavior). Alternatives are seq_buf_puts() and seq_buf_printf().
> > +snprintf(), scnprintf() and sysfs_emit() are possible aswell, but the adoption
> > +of the arguments needs to be taken care off.
> > +
> 
> How about just:
> 
> strlcat() must re-scan the destination string from the beginning on each
> call (O(n^2) behavior). Use the seq_buf API or similar instead.

seq_buf API for appending something to e.g. boot_command_line seems to be odd,
since boot_command_line is usually "just there" (depending on architecture and
boot loader).

So if I would remove strlcat() from appending something to boot_command_line I
would end up open-coding strlcat(), including the chance for the usual
off-by-one bugs. Looks like this would be true for nearly all architectures.

Is performance really the only reason to deprecate strlcat()? This seems to be
a bit questionable to me.

^ permalink raw reply

* [PATCH] Documentation: hwmon: lm75: document sysfs interface
From: Chen-Shi-Hong @ 2026-05-16 16:10 UTC (permalink / raw)
  To: linux; +Cc: corbet, skhan, linux-hwmon, linux-doc, linux-kernel,
	Chen-Shi-Hong

Document the sysfs attributes supported by the lm75 driver.

The driver exposes temp1_input, temp1_max, temp1_max_hyst, and the
standard update_interval attribute. Some chips also expose temp1_alarm.

Add a sysfs-Interface section to Documentation/hwmon/lm75.rst to
describe the supported attributes and clarify that temp1_alarm and the
write permissions of update_interval depend on the chip.

Signed-off-by: Chen-Shi-Hong <eric039eric@gmail.com>
---
 Documentation/hwmon/lm75.rst | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/Documentation/hwmon/lm75.rst b/Documentation/hwmon/lm75.rst
index 4269da04508e..cbf737948c1d 100644
--- a/Documentation/hwmon/lm75.rst
+++ b/Documentation/hwmon/lm75.rst
@@ -181,3 +181,21 @@ is supported by this driver, other specific enhancements are not.
 
 The LM77 is not supported, contrary to what we pretended for a long time.
 Both chips are simply not compatible, value encoding differs.
+
+sysfs-Interface
+---------------
+
+================ ============================================
+temp1_input      temperature input
+temp1_max        maximum temperature
+temp1_max_hyst   maximum temperature hysteresis
+================ ============================================
+
+If supported by the chip, the following attribute is also available:
+
+================ ============================================
+temp1_alarm      temperature alarm
+================ ============================================
+
+The standard update_interval attribute is also supported. Its write
+permissions depend on the chip.
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH RESEND bpf-next v10 2/8] bpf: clear list node owner and unlink before drop
From: Kaitao Cheng @ 2026-05-16 16:18 UTC (permalink / raw)
  To: Eduard Zingerman, Alexei Starovoitov
  Cc: bpf, linux-kernel, linux-doc, ast, memxor, corbet, martin.lau,
	daniel, andrii, song, yonghong.song, john.fastabend, kpsingh, sdf,
	haoluo, jolsa, shuah, chengkaitao, skhan, vmalik, linux-kselftest,
	martin.lau, clm, ihor.solodrai, bot+bpf-ci
In-Reply-To: <7fa6794161a8bd4fdbc21dad68e86e9770c873cc.camel@gmail.com>



在 2026/5/16 02:24, Eduard Zingerman 写道:
> On Fri, 2026-05-15 at 12:34 +0800, Kaitao Cheng wrote:
>>
>> 在 2026/5/14 09:50, Alexei Starovoitov 写道:
>>> On Wed May 13, 2026 at 3:53 PM PDT, Eduard Zingerman wrote:
>>>> On Tue, 2026-05-12 at 06:41 +0000, bot+bpf-ci@kernel.org wrote:
>>>>
>>>> [...]
>>>>
>>>>> When a BPF program holds an owning or refcount-acquired reference to
>>>>> one of these nodes (node X), which is structurally supported because
>>>>> __bpf_obj_drop_impl() uses refcount_dec_and_test() and only frees at
>>>>> refcount 0, a concurrent push to a DIFFERENT bpf_list_head becomes a
>>>>> corruption:
>>>>>
>>>>> CPU 0 (bpf_list_head_free, lock released)  CPU 1 (BPF prog, refcount X)
>>>>> -----------------------------------------   ----------------------------
>>>>> (owner of X == NULL, X linked in drain)
>>>>>                                             bpf_list_push_back(other, X)
>>>>>                                               __bpf_list_add: spin_lock()
>>>>>                                               cmpxchg(X->owner, NULL,
>>>>>                                                       POISON) -> OK
>>>>>                                               list_add_tail(&X->list_head,
>>>>>                                                             other_head)
>>>>>                                                 -> overwrites X->next,
>>>>>                                                    X->prev, corrupts
>>>>>                                                    other_head's chain
>>>>>                                                    because X is still
>>>>>                                                    stitched into drain
>>>>> pos = drain.next;      (may be X or neighbor using X's stale next)
>>>>> list_del_init(pos);    reads X->next/prev now pointing into other_head,
>>>>>                        corrupts other_head's list and/or drain
>>>>
>>>>
>>>> Kaitao, this scenario seem plausible, could you please comment on it?
>>>
>>> I think bot is correct.
>>> This patch looks buggy.
>>> It seems to me an optimization that breaks the concurrent logic.
>>> May be just drop this patch and reorder the other one, so that bot
>>> sees nonown suffix logic first.
>>
>> This patch is still necessary because it addresses the problem discussed
>> in this thread:
>> https://lore.kernel.org/all/DH846C0P88QU.16YT12I1LXBZM@etsalapatis.com/
>>
>> The patch does have a bug, however. To fix the issues we are seeing now,
>> I propose the additional changes below and would appreciate feedback.
>>
>> --- a/kernel/bpf/helpers.c
>> +++ b/kernel/bpf/helpers.c
>> @@ -2263,8 +2263,10 @@ void bpf_list_head_free(const struct btf_field *field, void *list_head,
>>         if (!head->next || list_empty(head))
>>                 goto unlock;
>>         list_for_each_safe(pos, n, head) {
>> -               WRITE_ONCE(container_of(pos,
>> -                       struct bpf_list_node_kern, list_head)->owner, NULL);
>> +               struct bpf_list_node_kern *node;
>> +
>> +               node = container_of(pos, struct bpf_list_node_kern, list_head);
>> +               WRITE_ONCE(node->owner, BPF_PTR_POISON);
>>                 list_move_tail(pos, &drain);
>>         }
>>  unlock:
>> @@ -2272,8 +2274,12 @@ void bpf_list_head_free(const struct btf_field *field, void *list_head,
>>         __bpf_spin_unlock_irqrestore(spin_lock);
>>
>>         while (!list_empty(&drain)) {
>> +               struct bpf_list_node_kern *node;
>> +
>>                 pos = drain.next;
>> +               node = container_of(pos, struct bpf_list_node_kern, list_head);
>>                 list_del_init(pos);
>> +               WRITE_ONCE(node->owner, NULL);
> 
> I think this still leaves a short race window open.
> Why does the .owner has field to be NULL?
> Can the logic that implies for it to be NULL be extended to accept
> POISON as well?

Here, before setting owner to NULL, list_del_init() has already been
executed, which means the node no longer belongs to any list. This
should match the semantic meaning of owner == NULL.

Do you mean deleting WRITE_ONCE(node->owner, NULL) and preventing
all subsequent __bpf_list_add() operations on this node?

> 
>>                 /* The contained type can also have resources, including a
>>                  * bpf_list_head which needs to be freed.
>>                  */


>> @@ -2481,6 +2487,14 @@ static int __bpf_list_add(struct bpf_list_node_kern *node,
>>         if (unlikely(!h->next))
>>                 INIT_LIST_HEAD(h);
>> 
>> +       /* bpf_list_head_free() marks nodes being detached with BPF_PTR_POISON
>> +        * before list_del_init().  cmpxchg(NULL, POISON) below would fail with
>> +        * that old value and fall into the generic error path, which wrongly
>> +        * calls __bpf_obj_drop_impl().  Reject POISON up front instead.
>> +        */
>> +       if (READ_ONCE(node->owner) == BPF_PTR_POISON)
>> +               return -EINVAL;
>> +

This code block is not needed; I will remove it.

-- 
Thanks
Kaitao Cheng


^ permalink raw reply

* [PATCH v3] docs: kernel-parameters: document scope of irqaffinity= parameter
From: Aaron Tomlin @ 2026-05-16 16:28 UTC (permalink / raw)
  To: corbet, skhan
  Cc: tglx, akpm, bp, rdunlap, dave.hansen, feng.tang,
	pawan.kumar.gupta, dapeng1.mi, kees, elver, paulmck, lirongqing,
	bhelgaas, bigeasy, bagasdotme, linux-doc, linux-kernel

There is a common misconception that the "irqaffinity=" boot parameter
acts as a global override for all hardware interrupts. In reality, it
only sets the irq_default_affinity mask, which is explicitly ignored
by managed interrupts (e.g., modern multiqueue storage controllers).

This patch updates kernel-parameters.txt to document this limitation,
directs users to "isolcpus=managed_irq" and
Documentation/core-api/irq/managed_irq.rst for further details.
Additionally, it updates managed_irq.rst to provide a debugfs example
demonstrating the IRQD_AFFINITY_MANAGED state flag.

Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
Changes since v2 [1]:
 - Reworded the debugfs explanation to explicitly distinguish between
   active and offline managed IRQ states
 - Clarified that the shutdown flags (IRQD_IRQ_DISABLED,
   IRQD_IRQ_MASKED, etc.) only appear when the CPU is offlined to
   resolve the previous contradiction with the example output
 - Explicitly labeled the debugfs snippet as an "active" managed
   interrupt
 - Used double-colon for literal code block (Bagas Sanjaya)

Changes since v1 [2]:
 - Provided an example of a managed IRQ using CONFIG_GENERIC_IRQ_DEBUGFS
 - Referenced Documentation/core-api/irq/managed_irq.rst

[1]: https://lore.kernel.org/lkml/20260421150911.42404-1-atomlin@atomlin.com/
[2]: https://lore.kernel.org/lkml/20260414200245.1153919-1-atomlin@atomlin.com/
---
 .../admin-guide/kernel-parameters.txt         | 11 ++++
 Documentation/core-api/irq/managed_irq.rst    | 60 ++++++++++++++++++-
 2 files changed, 68 insertions(+), 3 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index cf3807641d89..365c4931700a 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -2726,6 +2726,17 @@ Kernel parameters
 	irqaffinity=	[SMP] Set the default irq affinity mask
 			The argument is a cpu list, as described above.
 
+			Note: This parameter only sets the default affinity
+			for unmanaged interrupts (e.g., legacy single-queue
+			devices or unmanaged pre/post vectors). It is
+			explicitly ignored by managed interrupts, such as
+			those utilised by modern multiqueue storage
+			controllers. To isolate CPUs from managed
+			interrupts, see "isolcpus=managed_irq".
+
+			For further details see:
+			Documentation/core-api/irq/managed_irq.rst
+
 	irqchip.gicv2_force_probe=
 			[ARM,ARM64,EARLY]
 			Format: <bool>
diff --git a/Documentation/core-api/irq/managed_irq.rst b/Documentation/core-api/irq/managed_irq.rst
index 05e295f3c289..649f7f7fb0ac 100644
--- a/Documentation/core-api/irq/managed_irq.rst
+++ b/Documentation/core-api/irq/managed_irq.rst
@@ -80,9 +80,63 @@ The following examples assume a system with 8 CPUs.
     /proc/irq/48/effective_affinity_list:0
     /proc/irq/48/smp_affinity_list:7
 
-  This can be verified via the debugfs interface
-  (/sys/kernel/debug/irq/irqs/48). The dstate field will include
-  IRQD_IRQ_DISABLED, IRQD_IRQ_MASKED and IRQD_MANAGED_SHUTDOWN.
+  If the Linux kernel was built with Kconfig CONFIG_GENERIC_IRQ_DEBUGFS
+  enabled, this can be verified via the debugfs interface (e.g.,
+  /sys/kernel/debug/irq/irqs/48).
+
+  A managed IRQ will always include IRQD_AFFINITY_MANAGED in its dstate.
+  Furthermore, when the associated CPU is offlined, the dstate field will
+  also include IRQD_IRQ_DISABLED, IRQD_IRQ_MASKED, and IRQD_MANAGED_SHUTDOWN,
+  verifying that the interrupt was cleanly shut down rather than migrated.
+
+  For example, an active managed interrupt might look like this::
+
+    # cat /sys/kernel/debug/irq/irqs/87
+    handler:  handle_edge_irq
+    device:   0000:41:00.0
+    status:   0x00000000
+    istate:   0x00004000
+    ddepth:   0
+    wdepth:   0
+    dstate:   0x19601200
+		IRQD_ACTIVATED
+		IRQD_IRQ_STARTED
+		IRQD_SINGLE_TARGET
+		IRQD_AFFINITY_SET
+		IRQD_AFFINITY_MANAGED
+		IRQD_AFFINITY_ON_ACTIVATE
+		IRQD_HANDLE_ENFORCE_IRQCTX
+    node:     0
+    affinity: 3
+    effectiv: 3
+    pending:
+    domain:  IR-PCI-MSIX-0000:41:00.0-12
+     hwirq:   0x8
+     chip:    IR-PCI-MSIX-0000:41:00.0
+      flags:   0x430
+		 IRQCHIP_SKIP_SET_WAKE
+		 IRQCHIP_ONESHOT_SAFE
+
+      address_hi: 0x00000000
+      address_lo: 0xfee00000
+      msg_data:   0x00000008
+     parent:
+	domain:  AMD-IR-3-14
+	 hwirq:   0x41000000
+	 chip:    AMD-IR
+	  flags:   0x0
+	 parent:
+	    domain:  VECTOR
+	     hwirq:   0x57
+	     chip:    APIC
+	      flags:   0x0
+	     Vector:    33
+	     Target:     3
+	     move_in_progress: 0
+	     is_managed:       1
+	     can_reserve:      0
+	     has_reserved:     0
+	     cleanup_pending:  0
 
 - A QEMU instance is booted with "-device virtio-scsi-pci,num_queues=2"
   and the kernel command line includes:
-- 
2.51.0


^ permalink raw reply related

* Re: [PATCH v2 0/7] seg6: add SRv6 Mobile User Plane (RFC 9433) behaviors
From: Andrea Mayer @ 2026-05-16 16:25 UTC (permalink / raw)
  To: Yuya Kusakabe
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Justin Iurman, Shuah Khan, Jonathan Corbet,
	Shuah Khan, linux-kernel, netdev, linux-kselftest, linux-doc,
	stefano.salsano, ahabdels, Andrea Mayer
In-Reply-To: <20260505-seg6-mobile-v2-0-9e8022bdfdb6@gmail.com>

On Tue, 05 May 2026 01:30:10 +0900
Yuya Kusakabe <yuya.kusakabe@gmail.com> wrote:

Hi Yuya,

Thanks for the work. Some comments on the overall design below. I will
reply on the individual patches separately.

> This series adds the in-kernel data path for the SRv6 Mobile User
> Plane (MUP) architecture defined in RFC 9433.  SRv6 MUP integrates
> GTP-U mobile traffic into an SRv6 transport domain by mapping the
> 5-tuple (TEID, QFI, R, U, PDU Session ID) into a single SID, allowing
> operators to replace the GTP-U overlay between the gNB and the
> upstream UPF with native SRv6 forwarding while keeping the radio side
> unchanged.
>
> The series implements the six MUP behaviors that an SRv6 MUP gateway
> typically needs:
>
>   End.MAP         (RFC 9433 Section 6.2) -- swap DA with the next SID
>                                             without consuming the SRH
>   End.M.GTP6.D    (Section 6.3) -- IPv6/GTP-U to SRv6 headend encap
>   End.M.GTP6.D.Di (Section 6.4) -- drop-in mode variant of the above
>                                    (preserves the original outer DA at
>                                    SRH[0] and discards TEID/QFI)
>   End.M.GTP6.E    (Section 6.5) -- SRv6 to IPv6/GTP-U egress encap
>   End.M.GTP4.E    (Section 6.6) -- SRv6 to IPv4/GTP-U egress encap
>   H.M.GTP4.D      (Section 6.7) -- IPv4/GTP-U to SRv6 headend encap

RFC 9433 Section 6 is titled "SRv6 Segment Endpoint Mobility Behaviors",
but Section 6.7 defines H.M.GTP4.D as "SR Policy Headend with tunnel
decapsulation and map to an SRv6 policy". This behavior receives IPv4
packets and is not bound to any SID, so it does not fit the endpoint
model that seg6_local implements. Placing it there required relaxing the
ETH_P_IPV6 guard to accept ETH_P_IP and adding input_family to
seg6_action_desc, for a single behavior that does not share the endpoint
model.

seg6_local is not the natural place for this behavior. The UAPI cannot
be undone once merged, so where it should live needs discussion on the
list before we proceed.

>
> End.Limit (RFC 9433 Section 6.8) is intentionally out of scope.

>
> All behaviors plug into the existing seg6_local lwtunnel framework, so
> they are configurable through the standard "ip route ... encap
> seg6local action ..." interface.

This adds ~2.2k lines to seg6_local.c, bringing it from ~2.7k to ~5k
lines. The behaviors reuse the existing seg6_local infrastructure (SRH
validation, seg6_do_srh_encap, seg6_lookup_nexthop), while the GTP-U
parsing, encapsulation, and PDU Session handling, for example, is new
and self-contained. Given the volume, moving the MUP code into a
separate seg6_mobile.c (say CONFIG_IPV6_SEG6_MUP) would keep seg6_local
focused on the RFC 8986 endpoint framework. There are trade-offs
either way and I think this deserves discussion on the list.

There is significant duplication across the introduced behaviors: for
example the GTP-U parsing, inner protocol dispatch, and NF_HOOK plumbing
are nearly identical each time, and the small differences are already
hiding issues (HMAC validation missing in some, malformed SRH silently
accepted in others, wrong drop reasons). Some of the individual
functions are also well over 100 lines. Splitting them with helpers
would help.
The new code also has some style drift from seg6_local.c (variable
declaration ordering, scope blocks, blank lines) that should be fixed.

I think this patchset should be broken into smaller patchsets, one per
behavior, each with the behavior, its selftest, and any needed helpers as
separate patches. The same approach was used for End.DT4/End.DT6 and
End.DT46. End.M.GTP4.E alone is ~1.2k lines in a single diff.

> No new netlink families are
> introduced -- the new SEG6_LOCAL_MOBILE_* attributes extend
> SEG6_LOCAL_MAX in an add-only way, and the new SEG6_LOCAL_ACTION_*
> values are appended.

The attribute layout and semantics are probably the most sensitive part
of the series and need to be settled before it can go in, since the UAPI
cannot be changed once merged.

The series reuses SEG6_LOCAL_NH6, SEG6_LOCAL_SRH and SEG6_LOCAL_OIF with
semantics that differ from the existing behaviors. NH6 today means
next-hop in End.X/DX6. This series reuses it as DA replacement in
End.MAP and as prefix template in H.M.GTP4.D.
SRH is inserted verbatim in End.B6/B6.Encaps but augmented per-packet in
the mobile behaviors. These attributes have established UAPI semantics
from their existing behaviors. Giving them a different meaning in new
behaviors is a UAPI semantic divergence.

The selftests use OIF on all five GTP behaviors to select a VRF for the
lookup, but that is what TABLE and VRFTABLE are for (End.DT4, End.DT6).
OIF in the existing behaviors means output interface (End.X) or L2
egress device (End.DX2). VRF support is a nice-to-have that can be added
later as a separate optional attribute.

>
> The egress behaviors (End.M.GTP4.E and End.M.GTP6.E) accept an
> optional per-route pdu_type attribute that is the sole control
> for inserting the GTP-U PDU Session Container (3GPP TS 38.415 Section
> 5.5.2).  When pdu_type is set (dl/ul/0..15), every emitted GTP-U
> packet carries the container with that PDU Type and the QFI extracted
> from Args.Mob.Session.  When pdu_type is unset, the egress emits
> a short GTPv1-U header with no container.  pdu_type must be
> configured on egress routes serving 5G N3 traffic; omitting it is
> intended only for LTE-only / S1-U-style deployments where no PDU
> Session Container is exchanged.
>
> The matching iproute2 patch series has been posted to iproute2-next:
> https://lore.kernel.org/netdev/20260505-seg6-mobile-v2-0-93291b7b0134@gmail.com/

The user-facing parameter names and their semantics are defined in the
iproute2 series (where the man page lives), so that is probably the
better place to discuss keyword choices and attribute naming.

> [snip]
>  include/net/dropreason-core.h                      |   40 +

The series introduces six mobile-specific drop reasons. Drop reasons are
visible to userspace via the kfree_skb tracepoint (perf, bpftrace,
dropwatch), so they look like a kind of interface and their names should
match the actual failure.

BAD_SID and BAD_GTPU make sense as mobile-specific reasons. The other
four have issues: NOMEM and MTU_EXCEEDED duplicate existing generic
reasons (NOMEM and PKT_TOO_BIG). INVALID_SRH_SL is used for multiple SRH
validation failures, not just Segments Left (HMAC failure is also
reported as INVALID_SRH_SL). BAD_INNER is used as a catch-all for outer
header pull failures and encapsulation errors, neither of which involves
the inner payload.

We could think about a prep patch introducing SRv6-level drop reasons
(SEG6_INVALID_SRH, SEG6_HMAC, etc.) that both the existing behaviors and
the MUP ones can share.

> [snip]
>  .../selftests/net/srv6_end_m_gtp4_e_test.sh        |  486 ++++
>  .../selftests/net/srv6_end_m_gtp6_d_di_test.sh     |  427 ++++
>  .../selftests/net/srv6_end_m_gtp6_d_test.sh        |  497 ++++
>  .../selftests/net/srv6_end_m_gtp6_e_test.sh        |  402 +++
>  tools/testing/selftests/net/srv6_end_map_test.sh   |  103 +
>  .../testing/selftests/net/srv6_h_m_gtp4_d_test.sh  |  487 ++++

Selftests for the five GTP behaviors heavily depend on python3 and scapy
heredocs embedded in the shell scripts for packet construction and
validation, which adds an external runtime dependency. A statically
compiled C helper would remove it and avoid embedding python heredocs
in shell scripts.
A few cases worth covering: SRH and no-SRH input paths where the
behavior accepts both, missing SRH where the behavior requires
it, malformed SRH, and invalid attribute values.

The five GTP behaviors pass per-packet context to a finish callback through
skb->cb, and recover the lwtstate via skb_dst(skb)->lwtstate.
When nf_hooks_lwtunnel is enabled, an NF_HOOK sits between the input
handler and the finish callback. Netfilter processing at this hook can then
corrupt the cb (IPCB/IP6CB aliases skb->cb) or drop/replace the skb dst, so
the finish callback dereferences a NULL or unrelated dst.
Even if the dst is preserved, the cb may have been modified.

The dst problem is pre-existing from 7a3f5b0de364 ("netfilter: add
netfilter hooks to SRv6 data plane") and affects seg6_iptunnel too. Both
issues need a robust fix before this series can go in. I want to look at
this myself and will Cc you when I do, as the five new behaviors may need
to be adjusted on top.

> [snip]

> Best regards,
> --
> Yuya Kusakabe <yuya.kusakabe@gmail.com>

Ciao,
Andrea

^ permalink raw reply

* Re: [PATCH v2 1/3] Doc: deprecated.rst: add strlcat()
From: David Laight @ 2026-05-16 16:35 UTC (permalink / raw)
  To: Heiko Carstens
  Cc: Kees Cook, Manuel Ebner, Andy Shevchenko, Jonathan Corbet,
	Shuah Khan, Andy Whitcroft, Joe Perches, Dwaipayan Ray,
	Lukas Bulwahn, Geert Uytterhoeven, Randy Dunlap, Jani Nikula,
	open list:DOCUMENTATION PROCESS, open list:DOCUMENTATION,
	open list
In-Reply-To: <20260516152819.14597A76-hca@linux.ibm.com>

On Sat, 16 May 2026 17:28:19 +0200
Heiko Carstens <hca@linux.ibm.com> wrote:

> On Thu, May 14, 2026 at 09:31:46AM -0700, Kees Cook wrote:
> > On Thu, May 14, 2026 at 06:26:53PM +0200, Manuel Ebner wrote:  
> > > add strlcat and alternatives
> > > 
> > > Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
> > > ---
> > >  Documentation/process/deprecated.rst | 7 +++++++
> > >  1 file changed, 7 insertions(+)
> > > 
> > > diff --git a/Documentation/process/deprecated.rst b/Documentation/process/deprecated.rst
> > > index fed56864d036..06e802f4bbfd 100644
> > > --- a/Documentation/process/deprecated.rst
> > > +++ b/Documentation/process/deprecated.rst
> > > @@ -153,6 +153,13 @@ used, and the destinations should be marked with the `__nonstring
> > >  attribute to avoid future compiler warnings. For cases still needing
> > >  NUL-padding, strtomem_pad() can be used.
> > >  
> > > +strlcat()
> > > +---------
> > > +strlcat() must re-scan the destination string from the beginning on each
> > > +call (O(n^2) behavior). Alternatives are seq_buf_puts() and seq_buf_printf().
> > > +snprintf(), scnprintf() and sysfs_emit() are possible aswell, but the adoption
> > > +of the arguments needs to be taken care off.
> > > +  
> > 
> > How about just:
> > 
> > strlcat() must re-scan the destination string from the beginning on each
> > call (O(n^2) behavior). Use the seq_buf API or similar instead.  
> 
> seq_buf API for appending something to e.g. boot_command_line seems to be odd,
> since boot_command_line is usually "just there" (depending on architecture and
> boot loader).

Indeed, but ISTR that code uses strcat() a lot of the time.
The lengths are all known, so memcpy() can be used.

I don't really see why strlcat() should be deprecated.
Clearly there are many cases where there are better ways to do things.
The only problem with strlcat() is that it returns the 'required length'.
So there are some broken uses.
- fs/nfs/flexfilelayout/flexfilelayout.c
- lib/kunit/string-stream.c (although the preceding vsnprintf() looks like the actual bug).
There is also some very strange code in security/selinus/ima.c - but it may be ok.

In reality the return value of strlcat() isn't really much worse that that
of snprintf().

-- David

> 
> So if I would remove strlcat() from appending something to boot_command_line I
> would end up open-coding strlcat(), including the chance for the usual
> off-by-one bugs. Looks like this would be true for nearly all architectures.
> 
> Is performance really the only reason to deprecate strlcat()? This seems to be
> a bit questionable to me.


^ permalink raw reply

* [PATCH v2] Documentation: hwmon: lm75: document sysfs interface
From: Chen-Shi-Hong @ 2026-05-16 16:40 UTC (permalink / raw)
  To: linux; +Cc: corbet, skhan, linux-hwmon, linux-doc, linux-kernel,
	Chen-Shi-Hong
In-Reply-To: <20260516160823.1461-1-eric039eric@gmail.com>

Document the sysfs attributes supported by the lm75 driver.

The driver exposes temp1_input, temp1_max, temp1_max_hyst, and the
standard update_interval attribute. Some chips also expose temp1_alarm,
and temp1_label is available if a label is provided for the device.

Add a sysfs-Interface section to Documentation/hwmon/lm75.rst to
describe the supported attributes and clarify that temp1_alarm,
temp1_label, and the write permissions of update_interval depend on the
chip.

Signed-off-by: Chen-Shi-Hong <eric039eric@gmail.com>
---
 Documentation/hwmon/lm75.rst | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/Documentation/hwmon/lm75.rst b/Documentation/hwmon/lm75.rst
index 4269da04508e..fa8ddcaa0c2b 100644
--- a/Documentation/hwmon/lm75.rst
+++ b/Documentation/hwmon/lm75.rst
@@ -181,3 +181,28 @@ is supported by this driver, other specific enhancements are not.
 
 The LM77 is not supported, contrary to what we pretended for a long time.
 Both chips are simply not compatible, value encoding differs.
+
+sysfs-Interface
+---------------
+
+================ ============================================
+temp1_input      temperature input
+temp1_max        maximum temperature
+temp1_max_hyst   maximum temperature hysteresis
+================ ============================================
+
+If a label is provided for the device, the following attribute is also
+available:
+
+================ ============================================
+temp1_label      temperature channel label
+================ ============================================
+
+If supported by the chip, the following attribute is also available:
+
+================ ============================================
+temp1_alarm      temperature alarm
+================ ============================================
+
+The standard update_interval attribute is also supported. Its write
+permissions depend on the chip.
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH v2] Documentation: hwmon: lm75: document sysfs interface
From: Guenter Roeck @ 2026-05-16 16:48 UTC (permalink / raw)
  To: Chen-Shi-Hong; +Cc: corbet, skhan, linux-hwmon, linux-doc, linux-kernel
In-Reply-To: <20260516164022.1792-1-eric039eric@gmail.com>

On 5/16/26 09:40, Chen-Shi-Hong wrote:
> Document the sysfs attributes supported by the lm75 driver.
> 
> The driver exposes temp1_input, temp1_max, temp1_max_hyst, and the
> standard update_interval attribute. Some chips also expose temp1_alarm,
> and temp1_label is available if a label is provided for the device.
> 
> Add a sysfs-Interface section to Documentation/hwmon/lm75.rst to
> describe the supported attributes and clarify that temp1_alarm,
> temp1_label, and the write permissions of update_interval depend on the
> chip.
> 
> Signed-off-by: Chen-Shi-Hong <eric039eric@gmail.com>

Change log is missing.

Yes, I do see Sashiko's reply, but that is not an excuse for neglecting
to provide a change log.

Guenter

> ---
>   Documentation/hwmon/lm75.rst | 25 +++++++++++++++++++++++++
>   1 file changed, 25 insertions(+)
> 
> diff --git a/Documentation/hwmon/lm75.rst b/Documentation/hwmon/lm75.rst
> index 4269da04508e..fa8ddcaa0c2b 100644
> --- a/Documentation/hwmon/lm75.rst
> +++ b/Documentation/hwmon/lm75.rst
> @@ -181,3 +181,28 @@ is supported by this driver, other specific enhancements are not.
>   
>   The LM77 is not supported, contrary to what we pretended for a long time.
>   Both chips are simply not compatible, value encoding differs.
> +
> +sysfs-Interface
> +---------------
> +
> +================ ============================================
> +temp1_input      temperature input
> +temp1_max        maximum temperature
> +temp1_max_hyst   maximum temperature hysteresis
> +================ ============================================
> +
> +If a label is provided for the device, the following attribute is also
> +available:
> +
> +================ ============================================
> +temp1_label      temperature channel label
> +================ ============================================
> +
> +If supported by the chip, the following attribute is also available:
> +
> +================ ============================================
> +temp1_alarm      temperature alarm
> +================ ============================================
> +
> +The standard update_interval attribute is also supported. Its write
> +permissions depend on the chip.


^ permalink raw reply

* Re: [RFC PATCH v3] bpf: introduce TAINT_UNSAFE_BPF for mutating helpers
From: Aaron Tomlin @ 2026-05-16 17:01 UTC (permalink / raw)
  To: Alexei Starovoitov
  Cc: Steven Rostedt, Jonathan Corbet, Song Liu, KP Singh,
	Matt Bobrowski, Alexei Starovoitov, Daniel Borkmann,
	Andrii Nakryiko, Eduard, Kumar Kartikeya Dwivedi,
	Masami Hiramatsu, Shuah Khan, Jiri Olsa, Martin KaFai Lau,
	Yonghong Song, Mathieu Desnoyers, Randy Dunlap, neelx, sean,
	chjohnst, steve, mproche, nick.lange, open list:DOCUMENTATION,
	LKML, bpf, linux-trace-kernel
In-Reply-To: <CAADnVQLw+_NaOVeaKabuf085wNo_-6MAv8w0EDO3fBz3KCQT5g@mail.gmail.com>

On Wed, May 13, 2026 at 09:35:29AM -0700, Alexei Starovoitov wrote:
> On Wed, May 13, 2026 at 8:23 AM Steven Rostedt <rostedt@goodmis.org> wrote:
> >
> > On Wed, 13 May 2026 08:16:07 -0700
> > Alexei Starovoitov <alexei.starovoitov@gmail.com> wrote:
> >
> > > It's impossible to track all modifications.
> > > See what sched-ext is doing.
> > > What does it modify? Everything.
> >
> > What about just having a list of what BPF programs are loaded, what they
> > may be attached to, and what kfuncs they are calling?
> 
> Ohh. These have been available forever.
> Just bpftool prog, bpftool link, bpftool prog dump xlated

Hi Alexei,

Thank you for sharing.

Kind regards,
-- 
Aaron Tomlin

^ permalink raw reply

* [PATCH v3] Documentation: hwmon: lm75: document sysfs interface
From: Chen-Shi-Hong @ 2026-05-16 17:07 UTC (permalink / raw)
  To: linux; +Cc: corbet, skhan, linux-hwmon, linux-doc, linux-kernel,
	Chen-Shi-Hong
In-Reply-To: <20260516160823.1461-1-eric039eric@gmail.com>

Document the sysfs attributes supported by the lm75 driver.

The driver exposes temp1_input, temp1_max, temp1_max_hyst, and the
standard update_interval attribute. Some chips also expose temp1_alarm,
and temp1_label is available if a label is provided for the device.

Add a sysfs-Interface section to Documentation/hwmon/lm75.rst to
describe the supported attributes and clarify that temp1_alarm,
temp1_label, and the write permissions of update_interval depend on the
chip.

Signed-off-by: Chen-Shi-Hong <eric039eric@gmail.com>
---
Changes in v2:
- Document temp1_label as conditionally available when a device label is
  provided.

Changes in v3:
- Add changelog requested during review.

 Documentation/hwmon/lm75.rst | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/Documentation/hwmon/lm75.rst b/Documentation/hwmon/lm75.rst
index 4269da04508e..fa8ddcaa0c2b 100644
--- a/Documentation/hwmon/lm75.rst
+++ b/Documentation/hwmon/lm75.rst
@@ -181,3 +181,28 @@ is supported by this driver, other specific enhancements are not.
 
 The LM77 is not supported, contrary to what we pretended for a long time.
 Both chips are simply not compatible, value encoding differs.
+
+sysfs-Interface
+---------------
+
+================ ============================================
+temp1_input      temperature input
+temp1_max        maximum temperature
+temp1_max_hyst   maximum temperature hysteresis
+================ ============================================
+
+If a label is provided for the device, the following attribute is also
+available:
+
+================ ============================================
+temp1_label      temperature channel label
+================ ============================================
+
+If supported by the chip, the following attribute is also available:
+
+================ ============================================
+temp1_alarm      temperature alarm
+================ ============================================
+
+The standard update_interval attribute is also supported. Its write
+permissions depend on the chip.
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH] docs: fix typos in design.rst
From: SeongJae Park @ 2026-05-16 17:08 UTC (permalink / raw)
  To: Cheesecake
  Cc: SeongJae Park, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
	damon, linux-mm, linux-doc, linux-kernel
In-Reply-To: <20260516093552.8404-1-cheesecake2960@icloud.com>

Hello Cheesecake,

Thank you for this patch!

For the consistency, let's use 'Docs/mm/damon/design:' as the prefix of the
subject.  E.g., Docs/mm/damon/design: fix three typos

On Sat, 16 May 2026 18:35:37 +0900 Cheesecake <cheesecake2960@icloud.com> wrote:

> L140: "unsinged" -> "unsigned"
> L371: "sampleing" -> "sampling"
> L387: "multipled" -> "multiplied"

Thank you for finding and fixing these!

> 
> Signed-off-by: Cheesecake <cheesecake2960@icloud.com>

Is Cheesecake your real name or known identity?  We don't allow anonymous
contributions [1], and mm community prefer to use real names.

[...]

The file changes look good.

Could you please send v2 of this patch with changed subject and the name (if
Cheesecake is not your real name or known identity)?

[1] https://docs.kernel.org/process/submitting-patches.html#developer-s-certificate-of-origin-1-1


Thanks,
SJ

^ permalink raw reply

* Re: [PATCH v11 2/6] iio: adc: ad4691: add initial driver for AD4691 family
From: David Lechner @ 2026-05-16 17:11 UTC (permalink / raw)
  To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc
In-Reply-To: <20260515-ad4692-multichannel-sar-adc-driver-v11-2-eab27d852ac2@analog.com>

On 5/15/26 8:31 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
> 
> Add support for the Analog Devices AD4691 family of high-speed,
> low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
> AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
> AD4694 (8-ch, 1 MSPS).
> 
> The driver implements a custom regmap layer over raw SPI to handle the
> device's mixed 1/2/3/4-byte register widths and uses the standard IIO
> read_raw/write_raw interface for single-channel reads.
> 
> The chip idles in Autonomous Mode so that single-shot read_raw can use
> the internal oscillator without disturbing the hardware configuration.
> 
> Three voltage supply domains are managed: avdd (required), vio, and a
> reference supply on either the REF pin (ref-supply, external buffer)
> or the REFIN pin (refin-supply, uses the on-chip reference buffer;
> REFBUF_EN is set accordingly). Hardware reset is performed via
> the reset controller framework; a software reset through SPI_CONFIG_A
> is used as fallback when no hardware reset is available.
> 
> Accumulator channel masking for single-shot reads uses ACC_MASK_REG via
> an ADDR_DESCENDING SPI write, which covers both mask bytes in a single
> 16-bit transfer.
> 
> IIO_CHAN_INFO_SAMP_FREQ is exposed as info_mask_separate. The oscillator
> is shared hardware — writing any channel's sampling_frequency attribute
> sets it for all others — but per-channel attributes are used throughout
> the series to avoid an ABI change when per-channel oversampling ratios
> are introduced in a later commit, at which point the effective output
> rate (osc_freq / osr[N]) becomes genuinely per-channel.
> 
> Reviewed-by: David Lechner <dlechner@baylibre.com>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
>  MAINTAINERS              |   1 +
>  drivers/iio/adc/Kconfig  |  12 +
>  drivers/iio/adc/Makefile |   1 +
>  drivers/iio/adc/ad4691.c | 756 +++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 770 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 438ca850fa1c..24e4502b8292 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1490,6 +1490,7 @@ L:	linux-iio@vger.kernel.org
>  S:	Supported
>  W:	https://ez.analog.com/linux-software-drivers
>  F:	Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
> +F:	drivers/iio/adc/ad4691.c
>  
>  ANALOG DEVICES INC AD4695 DRIVER
>  M:	Michael Hennerich <michael.hennerich@analog.com>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 60038ae8dfc4..5e601a87e5f3 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -139,6 +139,18 @@ config AD4170_4
>  	  To compile this driver as a module, choose M here: the module will be
>  	  called ad4170-4.
>  
> +config AD4691
> +	tristate "Analog Devices AD4691 Family ADC Driver"
> +	depends on SPI
> +	depends on REGULATOR || COMPILE_TEST
> +	select REGMAP
> +	help
> +	  Say yes here to build support for Analog Devices AD4691 Family MuxSAR
> +	  SPI analog to digital converters (ADC).
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called ad4691.
> +
>  config AD4695
>  	tristate "Analog Device AD4695 ADC Driver"
>  	depends on SPI
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index c76550415ff1..4ac1ea09d773 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -16,6 +16,7 @@ obj-$(CONFIG_AD4080) += ad4080.o
>  obj-$(CONFIG_AD4130) += ad4130.o
>  obj-$(CONFIG_AD4134) += ad4134.o
>  obj-$(CONFIG_AD4170_4) += ad4170-4.o
> +obj-$(CONFIG_AD4691) += ad4691.o
>  obj-$(CONFIG_AD4695) += ad4695.o
>  obj-$(CONFIG_AD4851) += ad4851.o
>  obj-$(CONFIG_AD7091R) += ad7091r-base.o
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> new file mode 100644
> index 000000000000..ba77e1bfef16
> --- /dev/null
> +++ b/drivers/iio/adc/ad4691.c
> @@ -0,0 +1,756 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2024-2026 Analog Devices, Inc.
> + * Author: Radu Sabau <radu.sabau@analog.com>
> + */
> +#include <linux/array_size.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/delay.h>
> +#include <linux/dev_printk.h>
> +#include <linux/device/devres.h>
> +#include <linux/err.h>
> +#include <linux/limits.h>
> +#include <linux/math.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/reset.h>
> +#include <linux/spi/spi.h>
> +#include <linux/units.h>
> +#include <linux/unaligned.h>
> +
> +#include <linux/iio/iio.h>
> +
> +#define AD4691_VREF_uV_MIN			2400000
> +#define AD4691_VREF_uV_MAX			5250000
> +#define AD4691_VREF_2P5_uV_MAX			2750000
> +#define AD4691_VREF_3P0_uV_MAX			3250000
> +#define AD4691_VREF_3P3_uV_MAX			3750000
> +#define AD4691_VREF_4P096_uV_MAX		4500000
> +
> +#define AD4691_SPI_CONFIG_A_REG			0x000
> +#define AD4691_SW_RESET				(BIT(7) | BIT(0))
> +
> +#define AD4691_STATUS_REG			0x014
> +#define AD4691_CLAMP_STATUS1_REG		0x01A
> +#define AD4691_CLAMP_STATUS2_REG		0x01B
> +#define AD4691_DEVICE_SETUP			0x020
> +#define AD4691_LDO_EN				BIT(4)
> +#define AD4691_REF_CTRL				0x021
> +#define AD4691_REF_CTRL_MASK			GENMASK(4, 2)
> +#define AD4691_REFBUF_EN			BIT(0)
> +#define AD4691_OSC_FREQ_REG			0x023
> +#define AD4691_OSC_FREQ_MASK			GENMASK(3, 0)
> +#define AD4691_STD_SEQ_CONFIG			0x025
> +#define AD4691_SPARE_CONTROL			0x02A
> +
> +#define AD4691_OSC_EN_REG			0x180
> +#define AD4691_STATE_RESET_REG			0x181
> +#define AD4691_STATE_RESET_ALL			0x01
> +#define AD4691_ADC_SETUP			0x182
> +#define AD4691_ADC_MODE_MASK			GENMASK(1, 0)
> +#define AD4691_AUTONOMOUS_MODE			0x02
> +/*
> + * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a
> + * 16-bit BE value to 0x185 auto-decrements to 0x184 for the second byte.
> + */
> +#define AD4691_ACC_MASK_REG			0x185
> +#define AD4691_ACC_DEPTH_IN(n)			(0x186 + (n))
> +#define AD4691_GPIO_MODE1_REG			0x196
> +#define AD4691_GPIO_MODE2_REG			0x197
> +#define AD4691_GPIO_READ			0x1A0
> +#define AD4691_ACC_STATUS_FULL1_REG		0x1B0
> +#define AD4691_ACC_STATUS_FULL2_REG		0x1B1
> +#define AD4691_ACC_STATUS_OVERRUN1_REG		0x1B2
> +#define AD4691_ACC_STATUS_OVERRUN2_REG		0x1B3
> +#define AD4691_ACC_STATUS_SAT1_REG		0x1B4
> +#define AD4691_ACC_STATUS_SAT2_REG		0x1BE
> +#define AD4691_ACC_SAT_OVR_REG(n)		(0x1C0 + (n))
> +#define AD4691_AVG_IN(n)			(0x201 + (2 * (n)))
> +#define AD4691_AVG_STS_IN(n)			(0x222 + (3 * (n)))
> +#define AD4691_ACC_IN(n)			(0x252 + (3 * (n)))
> +#define AD4691_ACC_STS_DATA(n)			(0x283 + (4 * (n)))
> +
> +static const char * const ad4691_supplies[] = { "avdd", "vio" };
> +
> +enum ad4691_ref_ctrl {
> +	AD4691_VREF_2P5   = 0,
> +	AD4691_VREF_3P0   = 1,
> +	AD4691_VREF_3P3   = 2,
> +	AD4691_VREF_4P096 = 3,
> +	AD4691_VREF_5P0   = 4,

nit: when I see explicit values, I expect a gap or something odd.
When don't find any, I wasted my time reading what are the default
implicit values anyway.

> +};
> +
> +struct ad4691_channel_info {
> +	const struct iio_chan_spec *channels __counted_by_ptr(num_channels);
> +	unsigned int num_channels;
> +};
> +
> +struct ad4691_chip_info {
> +	const char *name;
> +	unsigned int max_rate;
> +	const struct ad4691_channel_info *sw_info;
> +};
> +
> +#define AD4691_CHANNEL(ch)						\
> +	{								\
> +		.type = IIO_VOLTAGE,					\
> +		.indexed = 1,						\
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)		\
> +				    | BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
> +		.info_mask_separate_available =				\
> +				      BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),	\
> +		.channel = ch,						\
> +		.scan_index = ch,					\
> +		.scan_type = {						\
> +			.sign = 'u',					\

FYI, this has a new name now, .format.

> +			.realbits = 16,					\
> +			.storagebits = 16,				\
> +		},							\
> +	}
> +
> +static const struct iio_chan_spec ad4691_channels[] = {
> +	AD4691_CHANNEL(0),
> +	AD4691_CHANNEL(1),
> +	AD4691_CHANNEL(2),
> +	AD4691_CHANNEL(3),
> +	AD4691_CHANNEL(4),
> +	AD4691_CHANNEL(5),
> +	AD4691_CHANNEL(6),
> +	AD4691_CHANNEL(7),
> +	AD4691_CHANNEL(8),
> +	AD4691_CHANNEL(9),
> +	AD4691_CHANNEL(10),
> +	AD4691_CHANNEL(11),
> +	AD4691_CHANNEL(12),
> +	AD4691_CHANNEL(13),
> +	AD4691_CHANNEL(14),
> +	AD4691_CHANNEL(15),
> +};
> +
> +static const struct iio_chan_spec ad4693_channels[] = {
> +	AD4691_CHANNEL(0),
> +	AD4691_CHANNEL(1),
> +	AD4691_CHANNEL(2),
> +	AD4691_CHANNEL(3),
> +	AD4691_CHANNEL(4),
> +	AD4691_CHANNEL(5),
> +	AD4691_CHANNEL(6),
> +	AD4691_CHANNEL(7),
> +};
> +
> +/*
> + * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] value.
> + * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support
> + * up to 500 kHz and use index 1 as their highest valid rate.
> + */
> +static const int ad4691_osc_freqs_Hz[] = {
> +	[0x0] = 1000000,
> +	[0x1] = 500000,
> +	[0x2] = 400000,
> +	[0x3] = 250000,
> +	[0x4] = 200000,
> +	[0x5] = 167000,
> +	[0x6] = 133000,
> +	[0x7] = 125000,
> +	[0x8] = 100000,
> +	[0x9] = 50000,
> +	[0xA] = 25000,
> +	[0xB] = 12500,
> +	[0xC] = 10000,
> +	[0xD] = 5000,
> +	[0xE] = 2500,
> +	[0xF] = 1250,
> +};
> +
> +static const struct ad4691_channel_info ad4691_sw_info = {
> +	.channels = ad4691_channels,
> +	.num_channels = ARRAY_SIZE(ad4691_channels),
> +};
> +
> +static const struct ad4691_channel_info ad4693_sw_info = {
> +	.channels = ad4693_channels,
> +	.num_channels = ARRAY_SIZE(ad4693_channels),
> +};
> +
> +static const struct ad4691_chip_info ad4691_chip_info = {
> +	.name = "ad4691",
> +	.max_rate = 500 * HZ_PER_KHZ,
> +	.sw_info = &ad4691_sw_info,
> +};
> +
> +static const struct ad4691_chip_info ad4692_chip_info = {
> +	.name = "ad4692",
> +	.max_rate = 1 * HZ_PER_MHZ,
> +	.sw_info = &ad4691_sw_info,
> +};
> +
> +static const struct ad4691_chip_info ad4693_chip_info = {
> +	.name = "ad4693",
> +	.max_rate = 500 * HZ_PER_KHZ,
> +	.sw_info = &ad4693_sw_info,
> +};
> +
> +static const struct ad4691_chip_info ad4694_chip_info = {
> +	.name = "ad4694",
> +	.max_rate = 1 * HZ_PER_MHZ,
> +	.sw_info = &ad4693_sw_info,
> +};
> +
> +struct ad4691_state {
> +	const struct ad4691_chip_info *info;
> +	struct regmap *regmap;
> +	int vref_uV;
> +	bool refbuf_en;
> +	bool ldo_en;
> +	/*
> +	 * Synchronize access to members of the driver state, and ensure
> +	 * atomicity of consecutive SPI operations.
> +	 */
> +	struct mutex lock;
> +};
> +
> +static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> +	struct spi_device *spi = context;
> +	u8 tx[2], rx[4];
> +	int ret;
> +
> +	/* Set bit 15 to mark the operation as READ. */

Can't we just set read_flag_mask in the regmap config?

> +	put_unaligned_be16(0x8000 | reg, tx);
> +
> +	switch (reg) {
> +	case 0 ... AD4691_OSC_FREQ_REG:
> +	case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1:
> +	case AD4691_ACC_MASK_REG + 1 ... AD4691_ACC_SAT_OVR_REG(15):
> +		ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 1);
> +		if (ret)
> +			return ret;
> +		*val = rx[0];
> +		return 0;
> +	case AD4691_ACC_MASK_REG:
> +	case AD4691_STD_SEQ_CONFIG:
> +	case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
> +		ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 2);
> +		if (ret)
> +			return ret;
> +		*val = get_unaligned_be16(rx);
> +		return 0;
> +	case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
> +	case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
> +		ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 3);
> +		if (ret)
> +			return ret;
> +		*val = get_unaligned_be24(rx);
> +		return 0;
> +	case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
> +		ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 4);
> +		if (ret)
> +			return ret;
> +		*val = get_unaligned_be32(rx);
> +		return 0;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int ad4691_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> +	struct spi_device *spi = context;
> +	u8 tx[4];
> +
> +	put_unaligned_be16(reg, tx);
> +
> +	switch (reg) {
> +	case 0 ... AD4691_OSC_FREQ_REG:
> +	case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1:
> +	case AD4691_ACC_MASK_REG + 1 ... AD4691_GPIO_MODE2_REG:
> +		if (val > U8_MAX)
> +			return -EINVAL;
> +		tx[2] = val;
> +		return spi_write_then_read(spi, tx, 3, NULL, 0);
> +	case AD4691_ACC_MASK_REG:
> +	case AD4691_STD_SEQ_CONFIG:
> +		if (val > U16_MAX)
> +			return -EINVAL;
> +		put_unaligned_be16(val, &tx[2]);
> +		return spi_write_then_read(spi, tx, 4, NULL, 0);
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static bool ad4691_volatile_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case AD4691_STATUS_REG:
> +	case AD4691_CLAMP_STATUS1_REG:
> +	case AD4691_CLAMP_STATUS2_REG:
> +	case AD4691_GPIO_READ:
> +	case AD4691_ACC_STATUS_FULL1_REG ... AD4691_ACC_STATUS_SAT2_REG:
> +	case AD4691_ACC_SAT_OVR_REG(0) ... AD4691_ACC_SAT_OVR_REG(15):
> +		return true;
> +	default:
> +		break;
> +	}
> +
> +	/*
> +	 * Multi-byte registers have non-unit strides; only accept base
> +	 * addresses to prevent debugfs from triggering reads that cross
> +	 * register boundaries.
> +	 */
> +	if (reg >= AD4691_AVG_IN(0) && reg <= AD4691_AVG_IN(15))
> +		return (reg - AD4691_AVG_IN(0)) % 2 == 0;
> +	if (reg >= AD4691_AVG_STS_IN(0) && reg <= AD4691_AVG_STS_IN(15))
> +		return (reg - AD4691_AVG_STS_IN(0)) % 3 == 0;
> +	if (reg >= AD4691_ACC_IN(0) && reg <= AD4691_ACC_IN(15))
> +		return (reg - AD4691_ACC_IN(0)) % 3 == 0;
> +	if (reg >= AD4691_ACC_STS_DATA(0) && reg <= AD4691_ACC_STS_DATA(15))
> +		return (reg - AD4691_ACC_STS_DATA(0)) % 4 == 0;
> +
> +	return false;
> +}
> +
> +static bool ad4691_readable_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case 0 ... AD4691_OSC_FREQ_REG:
> +	case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
> +	case AD4691_STD_SEQ_CONFIG:
> +		return true;
> +	default:
> +		break;
> +	}
> +
> +	/* Multi-byte registers: only accept base addresses (see volatile_reg). */
> +	if (reg >= AD4691_AVG_IN(0) && reg <= AD4691_AVG_IN(15))
> +		return (reg - AD4691_AVG_IN(0)) % 2 == 0;
> +	if (reg >= AD4691_AVG_STS_IN(0) && reg <= AD4691_AVG_STS_IN(15))
> +		return (reg - AD4691_AVG_STS_IN(0)) % 3 == 0;
> +	if (reg >= AD4691_ACC_IN(0) && reg <= AD4691_ACC_IN(15))
> +		return (reg - AD4691_ACC_IN(0)) % 3 == 0;
> +	if (reg >= AD4691_ACC_STS_DATA(0) && reg <= AD4691_ACC_STS_DATA(15))
> +		return (reg - AD4691_ACC_STS_DATA(0)) % 4 == 0;
> +
> +	return false;
> +}
> +
> +static bool ad4691_writeable_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case 0 ... AD4691_OSC_FREQ_REG:
> +	case AD4691_STD_SEQ_CONFIG:
> +	case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +static const struct regmap_config ad4691_regmap_config = {
> +	.reg_bits = 16,
> +	.val_bits = 32,
> +	.reg_read = ad4691_reg_read,
> +	.reg_write = ad4691_reg_write,
> +	.volatile_reg = ad4691_volatile_reg,
> +	.readable_reg = ad4691_readable_reg,
> +	.writeable_reg = ad4691_writeable_reg,
> +	.max_register = AD4691_ACC_STS_DATA(15),
> +	.cache_type = REGCACHE_MAPLE,
> +};
> +
> +/*
> + * Index 0 in ad4691_osc_freqs_Hz is 1 MHz — valid only for AD4692/AD4694
> + * (max_rate == 1 MHz). AD4691/AD4693 cap at 500 kHz so their valid range
> + * starts at index 1.
> + */
> +static unsigned int ad4691_samp_freq_start(const struct ad4691_chip_info *info)
> +{
> +	return (info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
> +}
> +
> +static int ad4691_get_sampling_freq(struct ad4691_state *st, int *val)
> +{
> +	unsigned int reg_val;
> +	int ret;
> +

No mutex lock here? Maybe without OK since it is a read.

> +	/*
> +	 * AD4691_OSC_FREQ_REG is non-volatile and written during
> +	 * ad4691_config(), so regmap returns the cached value here without
> +	 * touching the SPI bus. No lock is needed.
> +	 */
> +	ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	*val = ad4691_osc_freqs_Hz[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)];
> +	return IIO_VAL_INT;
> +}
> +
> +static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	unsigned int start = ad4691_samp_freq_start(st->info);
> +
> +	IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> +	if (IIO_DEV_ACQUIRE_FAILED(claim))
> +		return -EBUSY;
> +
> +	for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
> +		if (ad4691_osc_freqs_Hz[i] != freq)
> +			continue;

mutex lock?

> +		return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
> +					  AD4691_OSC_FREQ_MASK, i);
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int ad4691_read_avail(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     const int **vals, int *type,
> +			     int *length, long mask)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	unsigned int start = ad4691_samp_freq_start(st->info);
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		*vals = &ad4691_osc_freqs_Hz[start];
> +		*type = IIO_VAL_INT;
> +		*length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
> +		return IIO_AVAIL_LIST;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int ad4691_single_shot_read(struct iio_dev *indio_dev,
> +				   struct iio_chan_spec const *chan, int *val)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	unsigned int reg_val, osc_idx, period_us;
> +	int ret;
> +
> +	guard(mutex)(&st->lock);
> +
> +	/* Use AUTONOMOUS mode for single-shot reads. */
> +	ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> +			   AD4691_STATE_RESET_ALL);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> +			   BIT(chan->channel));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
> +			   ~BIT(chan->channel) & GENMASK(15, 0));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
> +	if (ret)
> +		return ret;
> +
> +	osc_idx = FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val);
> +	/* Wait 2 oscillator periods for the conversion to complete. */
> +	period_us = DIV_ROUND_UP(2UL * USEC_PER_SEC, ad4691_osc_freqs_Hz[osc_idx]);
> +	fsleep(period_us);
> +
> +	ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	*val = reg_val;
> +
> +	ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
> +	if (ret)
> +		return ret;
> +
> +	return IIO_VAL_INT;
> +}
> +
> +static int ad4691_read_raw(struct iio_dev *indio_dev,
> +			   struct iio_chan_spec const *chan, int *val,
> +			   int *val2, long info)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +
> +	switch (info) {
> +	case IIO_CHAN_INFO_RAW: {
> +		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> +		if (IIO_DEV_ACQUIRE_FAILED(claim))
> +			return -EBUSY;
> +
> +		return ad4691_single_shot_read(indio_dev, chan, val);
> +	}
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		return ad4691_get_sampling_freq(st, val);
> +	case IIO_CHAN_INFO_SCALE:
> +		*val = st->vref_uV / (MICRO / MILLI);
> +		*val2 = chan->scan_type.realbits;
> +		return IIO_VAL_FRACTIONAL_LOG2;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int ad4691_write_raw(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *chan,
> +			    int val, int val2, long mask)
> +{
> +	switch (mask) {
> +	case IIO_CHAN_INFO_SAMP_FREQ:

Should we aquire direct mode so that we can't change the rate during
buffered read?

> +		return ad4691_set_sampling_freq(indio_dev, val);
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
> +			     unsigned int writeval, unsigned int *readval)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +
> +	guard(mutex)(&st->lock);
> +
> +	if (readval)
> +		return regmap_read(st->regmap, reg, readval);
> +
> +	return regmap_write(st->regmap, reg, writeval);
> +}
> +
> +static const struct iio_info ad4691_info = {
> +	.read_raw = &ad4691_read_raw,
> +	.write_raw = &ad4691_write_raw,
> +	.read_avail = &ad4691_read_avail,
> +	.debugfs_reg_access = &ad4691_reg_access,
> +};
> +
> +static int ad4691_regulator_setup(struct ad4691_state *st)
> +{
> +	struct device *dev = regmap_get_device(st->regmap);
> +	int ret;
> +
> +	ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4691_supplies),
> +					     ad4691_supplies);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to get and enable supplies\n");
> +
> +	/*
> +	 * vdd-supply and ldo-in-supply are mutually exclusive:
> +	 *   vdd-supply present  → external 1.8V VDD; disable internal LDO.
> +	 *   vdd-supply absent   → enable internal LDO fed from ldo-in-supply.
> +	 * Having both simultaneously is strongly inadvisable per the datasheet.
> +	 */
> +	ret = devm_regulator_get_enable_optional(dev, "vdd");
> +	if (ret == -ENODEV) {

After some recent discussions, we've been leaning towards using
if (device_property_present(dev, "vdd-supply") instead of handling
-ENODEV.

I think it reads more nicely in a linear way here:

	if (device_property_present(dev, "vdd-supply") {
		/* do stuff with vdd supply */
	} else if device_property_present(dev, "ldo-in-supply") {
		/* do stuff with ldo-in supply */
	} else {
		return dev_err_probe(dev, -EINVAL, "missing one of vdd-supply, ldo-in-supply\n");
	}

> +		ret = devm_regulator_get_enable(dev, "ldo-in");
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "Failed to get and enable LDO-IN\n");
> +		st->ldo_en = true;
> +	} else if (ret) {
> +		return dev_err_probe(dev, ret, "Failed to get and enable VDD\n");
> +	}
> +
> +	st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "ref");
> +	if (st->vref_uV == -ENODEV) {

similar with ref-supply and refin-supply

> +		st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "refin");
> +		st->refbuf_en = true;
> +	}
> +	if (st->vref_uV < 0)
> +		return dev_err_probe(dev, st->vref_uV,
> +				     "Failed to get reference supply\n");
> +
> +	if (st->vref_uV < AD4691_VREF_uV_MIN || st->vref_uV > AD4691_VREF_uV_MAX)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "vref(%d) must be in the range [%u...%u]\n",
> +				     st->vref_uV, AD4691_VREF_uV_MIN,
> +				     AD4691_VREF_uV_MAX);
> +
> +	return 0;
> +}
> +
> +static int ad4691_reset(struct ad4691_state *st)
> +{
> +	struct device *dev = regmap_get_device(st->regmap);
> +	struct reset_control *rst;
> +
> +	rst = devm_reset_control_get_optional_exclusive(dev, NULL);
> +	if (IS_ERR(rst))
> +		return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n");
> +
> +	if (rst) {
> +		/*
> +		 * Assert the reset line before sleeping to guarantee a proper
> +		 * reset pulse on every probe, including driver reloads where
> +		 * the line may already be deasserted (reset_control_put() does
> +		 * not re-assert on release).
> +		 * devm_reset_control_get_optional_exclusive_deasserted() cannot
> +		 * be used because it deasserts immediately without delay; the
> +		 * datasheet (Table 5) requires a ≥300 µs reset pulse width
> +		 * before deassertion.
> +		 */
> +		reset_control_assert(rst);
> +		fsleep(300);
> +		return reset_control_deassert(rst);
> +	}
> +
> +	/* No hardware reset available, fall back to software reset. */
> +	return regmap_write(st->regmap, AD4691_SPI_CONFIG_A_REG,
> +			    AD4691_SW_RESET);
> +}
> +
> +static int ad4691_config(struct ad4691_state *st)
> +{
> +	struct device *dev = regmap_get_device(st->regmap);
> +	enum ad4691_ref_ctrl ref_val;
> +	unsigned int val;
> +	int ret;
> +
> +	switch (st->vref_uV) {
> +	case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX:
> +		ref_val = AD4691_VREF_2P5;
> +		break;
> +	case AD4691_VREF_2P5_uV_MAX + 1 ... AD4691_VREF_3P0_uV_MAX:
> +		ref_val = AD4691_VREF_3P0;
> +		break;
> +	case AD4691_VREF_3P0_uV_MAX + 1 ... AD4691_VREF_3P3_uV_MAX:
> +		ref_val = AD4691_VREF_3P3;
> +		break;
> +	case AD4691_VREF_3P3_uV_MAX + 1 ... AD4691_VREF_4P096_uV_MAX:
> +		ref_val = AD4691_VREF_4P096;
> +		break;
> +	case AD4691_VREF_4P096_uV_MAX + 1 ... AD4691_VREF_uV_MAX:
> +		ref_val = AD4691_VREF_5P0;
> +		break;
> +	default:
> +		return dev_err_probe(dev, -EINVAL,
> +				     "Unsupported vref voltage: %d uV\n",
> +				     st->vref_uV);
> +	}
> +
> +	val = FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val);
> +	if (st->refbuf_en)
> +		val |= AD4691_REFBUF_EN;
> +
> +	ret = regmap_write(st->regmap, AD4691_REF_CTRL, val);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
> +
> +	ret = regmap_assign_bits(st->regmap, AD4691_DEVICE_SETUP,
> +				 AD4691_LDO_EN, st->ldo_en);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to write DEVICE_SETUP\n");
> +
> +	/*
> +	 * Set the internal oscillator to the highest rate this chip supports.
> +	 * Index 0 (1 MHz) exceeds the 500 kHz max of AD4691/AD4693, so those
> +	 * chips start at index 1 (500 kHz).
> +	 */
> +	ret = regmap_write(st->regmap, AD4691_OSC_FREQ_REG,
> +			   ad4691_samp_freq_start(st->info));
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n");
> +
> +	ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
> +				 AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
> +
> +	return 0;
> +}
> +
> +static int ad4691_probe(struct spi_device *spi)
> +{
> +	struct device *dev = &spi->dev;
> +	struct iio_dev *indio_dev;
> +	struct ad4691_state *st;
> +	int ret;
> +
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	st = iio_priv(indio_dev);
> +	st->info = spi_get_device_match_data(spi);
> +	if (!st->info)
> +		return -ENODEV;

We've recently standardized on not checking return value
of spi_get_device_match_data().

> +
> +	ret = devm_mutex_init(dev, &st->lock);
> +	if (ret)
> +		return ret;
> +
> +	st->regmap = devm_regmap_init(dev, NULL, spi, &ad4691_regmap_config);
> +	if (IS_ERR(st->regmap))
> +		return dev_err_probe(dev, PTR_ERR(st->regmap),
> +				     "Failed to initialize regmap\n");
> +
> +	ret = ad4691_regulator_setup(st);
> +	if (ret)
> +		return ret;
> +
> +	ret = ad4691_reset(st);
> +	if (ret)
> +		return ret;
> +
> +	ret = ad4691_config(st);
> +	if (ret)
> +		return ret;
> +
> +	indio_dev->name = st->info->name;
> +	indio_dev->info = &ad4691_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	indio_dev->channels = st->info->sw_info->channels;
> +	indio_dev->num_channels = st->info->sw_info->num_channels;
> +
> +	return devm_iio_device_register(dev, indio_dev);
> +}
> +
> +static const struct of_device_id ad4691_of_match[] = {
> +	{ .compatible = "adi,ad4691", .data = &ad4691_chip_info },
> +	{ .compatible = "adi,ad4692", .data = &ad4692_chip_info },
> +	{ .compatible = "adi,ad4693", .data = &ad4693_chip_info },
> +	{ .compatible = "adi,ad4694", .data = &ad4694_chip_info },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, ad4691_of_match);
> +
> +static const struct spi_device_id ad4691_id[] = {
> +	{ "ad4691", (kernel_ulong_t)&ad4691_chip_info },
> +	{ "ad4692", (kernel_ulong_t)&ad4692_chip_info },
> +	{ "ad4693", (kernel_ulong_t)&ad4693_chip_info },
> +	{ "ad4694", (kernel_ulong_t)&ad4694_chip_info },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(spi, ad4691_id);
> +
> +static struct spi_driver ad4691_driver = {
> +	.driver = {
> +		.name = "ad4691",
> +		.of_match_table = ad4691_of_match,
> +	},
> +	.probe = ad4691_probe,
> +	.id_table = ad4691_id,
> +};
> +module_spi_driver(ad4691_driver);
> +
> +MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
> +MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
> +MODULE_LICENSE("GPL");
> 


^ permalink raw reply

* Re: [PATCH] docs: fix typo in lenovo-wmi-other.rst
From: Randy Dunlap @ 2026-05-16 17:14 UTC (permalink / raw)
  To: Cheesecake, Mark Pearson, Derek J. Clark, Armin Wolf,
	Jonathan Corbet, Shuah Khan
  Cc: platform-driver-x86, linux-doc, linux-kernel
In-Reply-To: <20260516075020.16745-1-cheesecake2960@icloud.com>



On 5/16/26 12:50 AM, Cheesecake wrote:
> Replace "Minumum" with "Minimum".
> 
> Signed-off-by: Cheesecake <cheesecake2960@icloud.com>
> ---
>  Documentation/wmi/devices/lenovo-wmi-other.rst | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
> index 01d471156..1d0410500 100644
> --- a/Documentation/wmi/devices/lenovo-wmi-other.rst
> +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
> @@ -144,5 +144,5 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
>      [WmiDataId(1), read, Description("Mode.")] uint32 NumOfFans;
>      [WmiDataId(2), read, Description("Fan ID."), WmiSizeIs("NumOfFans")] uint32 FanId[];
>      [WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMaxSpeed[];
> -    [WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[];
> +    [WmiDataId(4), read, Description("Minimum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[];
>    };

This is a well-known misspelling but apparently it's what the hardware/device
reports (or contains), so it should remain as is.

https://lore.kernel.org/platform-driver-x86/BAA4F3A7-E892-4904-95A6-64B177CDA7AD@gmail.com/
and
https://lore.kernel.org/platform-driver-x86/cfd7977e-d612-4e08-a68a-65fed8e164b6@gmx.de/

Looks like we should add a NOTE: there.


-- 
~Randy


^ permalink raw reply

* Re: [PATCH v11 3/6] iio: adc: ad4691: add triggered buffer support
From: David Lechner @ 2026-05-16 17:32 UTC (permalink / raw)
  To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc
In-Reply-To: <20260515-ad4692-multichannel-sar-adc-driver-v11-3-eab27d852ac2@analog.com>

On 5/15/26 8:31 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
> 
> Add buffered capture support using the IIO triggered buffer framework.
> 
> CNV Burst Mode: the GP pin identified by interrupt-names in the device
> tree is configured as DATA_READY output. The IRQ handler stops
> conversions and fires the IIO trigger; the trigger handler executes a
> pre-built SPI message that reads all active channels from the AVG_IN
> accumulator registers and then resets accumulator state and restarts
> conversions for the next cycle.
> 
> Manual Mode: CNV is tied to SPI CS so each transfer simultaneously
> reads the previous result and starts the next conversion (pipelined
> N+1 scheme). At preenable time a pre-built, optimised SPI message of
> N+1 transfers is constructed (N channel reads plus one NOOP to drain
> the pipeline). The trigger handler executes the message in a single
> spi_sync() call and collects the results. An external trigger (e.g.
> iio-trig-hrtimer) is required to drive the trigger at the desired
> sample rate.
> 
> Both modes share the same trigger handler and push a complete scan —
> one big-endian 16-bit (__be16) slot per active channel, densely packed
> in scan_index order, followed by a timestamp.
> 
> The CNV Burst Mode sampling frequency (PWM period) is exposed as a
> buffer-level attribute via IIO_DEVICE_ATTR.
> 
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
>  drivers/iio/adc/Kconfig  |   2 +
>  drivers/iio/adc/ad4691.c | 592 +++++++++++++++++++++++++++++++++++++++++++++--
>  2 files changed, 580 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 5e601a87e5f3..484363458658 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -143,6 +143,8 @@ config AD4691
>  	tristate "Analog Devices AD4691 Family ADC Driver"
>  	depends on SPI
>  	depends on REGULATOR || COMPILE_TEST
> +	select IIO_BUFFER
> +	select IIO_TRIGGERED_BUFFER
>  	select REGMAP
>  	help
>  	  Say yes here to build support for Analog Devices AD4691 Family MuxSAR
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> index ba77e1bfef16..bf27d5f33a49 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -5,24 +5,35 @@
>   */
>  #include <linux/array_size.h>
>  #include <linux/bitfield.h>
> -#include <linux/bitops.h>
> +#include <linux/bitmap.h>
>  #include <linux/cleanup.h>
>  #include <linux/delay.h>
>  #include <linux/dev_printk.h>
>  #include <linux/device/devres.h>
> +#include <linux/dmaengine.h>

Belongs in later patch, I think.

>  #include <linux/err.h>
> +#include <linux/interrupt.h>
> +#include <linux/kstrtox.h>
>  #include <linux/limits.h>
>  #include <linux/math.h>
>  #include <linux/module.h>
>  #include <linux/mod_devicetable.h>
> +#include <linux/property.h>
> +#include <linux/pwm.h>
>  #include <linux/regmap.h>
>  #include <linux/regulator/consumer.h>
>  #include <linux/reset.h>
> +#include <linux/string.h>
>  #include <linux/spi/spi.h>
>  #include <linux/units.h>
>  #include <linux/unaligned.h>
>  
> +#include <linux/iio/buffer.h>
>  #include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/iio/trigger_consumer.h>
>  
>  #define AD4691_VREF_uV_MIN			2400000
>  #define AD4691_VREF_uV_MAX			5250000
> @@ -31,6 +42,9 @@
>  #define AD4691_VREF_3P3_uV_MAX			3750000
>  #define AD4691_VREF_4P096_uV_MAX		4500000
>  
> +#define AD4691_CNV_DUTY_CYCLE_NS		380
> +#define AD4691_CNV_HIGH_TIME_NS			430
> +
>  #define AD4691_SPI_CONFIG_A_REG			0x000
>  #define AD4691_SW_RESET				(BIT(7) | BIT(0))
>  
> @@ -38,6 +52,7 @@
>  #define AD4691_CLAMP_STATUS1_REG		0x01A
>  #define AD4691_CLAMP_STATUS2_REG		0x01B
>  #define AD4691_DEVICE_SETUP			0x020
> +#define AD4691_MANUAL_MODE			BIT(2)
>  #define AD4691_LDO_EN				BIT(4)
>  #define AD4691_REF_CTRL				0x021
>  #define AD4691_REF_CTRL_MASK			GENMASK(4, 2)
> @@ -45,13 +60,18 @@
>  #define AD4691_OSC_FREQ_REG			0x023
>  #define AD4691_OSC_FREQ_MASK			GENMASK(3, 0)
>  #define AD4691_STD_SEQ_CONFIG			0x025
> +#define AD4691_SEQ_ALL_CHANNELS_OFF		0x00
>  #define AD4691_SPARE_CONTROL			0x02A
>  
> +#define AD4691_NOOP				0x00
> +#define AD4691_ADC_CHAN(ch)			((0x10 + (ch)) << 3)
> +
>  #define AD4691_OSC_EN_REG			0x180
>  #define AD4691_STATE_RESET_REG			0x181
>  #define AD4691_STATE_RESET_ALL			0x01
>  #define AD4691_ADC_SETUP			0x182
>  #define AD4691_ADC_MODE_MASK			GENMASK(1, 0)
> +#define AD4691_CNV_BURST_MODE			0x01
>  #define AD4691_AUTONOMOUS_MODE			0x02
>  /*
>   * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a
> @@ -61,6 +81,8 @@
>  #define AD4691_ACC_DEPTH_IN(n)			(0x186 + (n))
>  #define AD4691_GPIO_MODE1_REG			0x196
>  #define AD4691_GPIO_MODE2_REG			0x197
> +#define AD4691_GP_MODE_MASK			GENMASK(3, 0)
> +#define AD4691_GP_MODE_DATA_READY		0x06
>  #define AD4691_GPIO_READ			0x1A0
>  #define AD4691_ACC_STATUS_FULL1_REG		0x1B0
>  #define AD4691_ACC_STATUS_FULL2_REG		0x1B1
> @@ -110,6 +132,7 @@ struct ad4691_chip_info {
>  			.sign = 'u',					\
>  			.realbits = 16,					\
>  			.storagebits = 16,				\
> +			.endianness = IIO_BE,				\
>  		},							\
>  	}
>  
> @@ -130,6 +153,7 @@ static const struct iio_chan_spec ad4691_channels[] = {
>  	AD4691_CHANNEL(13),
>  	AD4691_CHANNEL(14),
>  	AD4691_CHANNEL(15),
> +	IIO_CHAN_SOFT_TIMESTAMP(16),
>  };
>  
>  static const struct iio_chan_spec ad4693_channels[] = {
> @@ -141,6 +165,17 @@ static const struct iio_chan_spec ad4693_channels[] = {
>  	AD4691_CHANNEL(5),
>  	AD4691_CHANNEL(6),
>  	AD4691_CHANNEL(7),
> +	IIO_CHAN_SOFT_TIMESTAMP(8),
> +};
> +
> +static const struct ad4691_channel_info ad4691_sw_info = {
> +	.channels = ad4691_channels,
> +	.num_channels = ARRAY_SIZE(ad4691_channels),
> +};
> +
> +static const struct ad4691_channel_info ad4693_sw_info = {
> +	.channels = ad4693_channels,
> +	.num_channels = ARRAY_SIZE(ad4693_channels),
>  };
>  
>  /*
> @@ -167,15 +202,7 @@ static const int ad4691_osc_freqs_Hz[] = {
>  	[0xF] = 1250,
>  };
>  
> -static const struct ad4691_channel_info ad4691_sw_info = {
> -	.channels = ad4691_channels,
> -	.num_channels = ARRAY_SIZE(ad4691_channels),
> -};
> -
> -static const struct ad4691_channel_info ad4693_sw_info = {
> -	.channels = ad4693_channels,
> -	.num_channels = ARRAY_SIZE(ad4693_channels),
> -};

Would be better to put this in the correct place in the first patch
to avoid noise of moving them.

> +static const char * const ad4691_gp_names[] = { "gp0", "gp1", "gp2", "gp3" };
>  
>  static const struct ad4691_chip_info ad4691_chip_info = {
>  	.name = "ad4691",
> @@ -204,7 +231,14 @@ static const struct ad4691_chip_info ad4694_chip_info = {
>  struct ad4691_state {
>  	const struct ad4691_chip_info *info;
>  	struct regmap *regmap;
> +	struct spi_device *spi;
> +
> +	struct pwm_device *conv_trigger;
> +	int irq;
>  	int vref_uV;
> +	u32 cnv_period_ns;
> +
> +	bool manual_mode;
>  	bool refbuf_en;
>  	bool ldo_en;
>  	/*
> @@ -212,8 +246,56 @@ struct ad4691_state {
>  	 * atomicity of consecutive SPI operations.
>  	 */
>  	struct mutex lock;
> +	/*
> +	 * Per-buffer-enable lifetime resources:
> +	 * Manual Mode - a pre-built SPI message that clocks out N+1
> +	 *		 transfers in one go.
> +	 * CNV Burst Mode - a pre-built SPI message that clocks out 2*N
> +	 *		    transfers in one go.
> +	 */
> +	struct spi_message scan_msg;
> +	/*
> +	 * max 16 + 1 NOOP (manual) or 2*16 + 1 state-reset (CNV burst).
> +	 */
> +	struct spi_transfer scan_xfers[34];
> +	/*
> +	 * CNV burst: 16 AVG_IN addresses = 16.  Manual: 16 channel cmds +
> +	 * 1 NOOP = 17.  Stored as native u16; put_unaligned_be16() fills each
> +	 * slot so the SPI controller (bits_per_word=8) sends bytes MSB-first.
> +	 */
> +	u16 scan_tx[17] __aligned(IIO_DMA_MINALIGN);
> +	/*
> +	 * CNV burst state-reset: 4-byte write [addr_hi, addr_lo,
> +	 * STATE_RESET_ALL, OSC_EN=1]. CS is asserted throughout, so
> +	 * ADDR_DESCENDING writes byte[3]=1 to OSC_EN_REG (0x180) as a
> +	 * deliberate side-write, keeping the oscillator enabled. Shared
> +	 * with the offload path (mutually exclusive at probe).
> +	 */
> +	u8 scan_tx_reset[4] __aligned(IIO_DMA_MINALIGN);
> +	/*
> +	 * Scan buffer: one BE16 slot per active channel, plus timestamp.
> +	 * DMA-aligned because scan_xfers point rx_buf directly into vals[].
> +	 */
> +	IIO_DECLARE_DMA_BUFFER_WITH_TS(__be16, vals, 16);
>  };
>  
> +/*
> + * Configure the given GP pin (0-3) as DATA_READY output.
> + * GP0/GP1 → GPIO_MODE1_REG, GP2/GP3 → GPIO_MODE2_REG.
> + * Even pins occupy bits [3:0], odd pins bits [7:4].
> + */
> +static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num)
> +{
> +	unsigned int bit_off = gp_num % 2;
> +	unsigned int reg_off = gp_num / 2;
> +	unsigned int shift = 4 * bit_off;
> +
> +	return regmap_update_bits(st->regmap,
> +				  AD4691_GPIO_MODE1_REG + reg_off,
> +				  AD4691_GP_MODE_MASK << shift,
> +				  AD4691_GP_MODE_DATA_READY << shift);
> +}
> +
>  static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
>  {
>  	struct spi_device *spi = context;
> @@ -534,13 +616,405 @@ static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
>  	return regmap_write(st->regmap, reg, writeval);
>  }
>  
> -static const struct iio_info ad4691_info = {
> +static int ad4691_set_pwm_freq(struct ad4691_state *st, unsigned int freq)
> +{
> +	if (!freq)
> +		return -EINVAL;
> +
> +	st->cnv_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, freq);
> +	return 0;
> +}
> +
> +static int ad4691_sampling_enable(struct ad4691_state *st, bool enable)
> +{
> +	struct pwm_state conv_state = {
> +		.period     = st->cnv_period_ns,
> +		.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS,
> +		.polarity   = PWM_POLARITY_NORMAL,
> +		.enabled    = enable,
> +	};
> +
> +	return pwm_apply_might_sleep(st->conv_trigger, &conv_state);
> +}
> +
> +/*
> + * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion mode.
> + *
> + * Configures the ADC hardware registers for the mode selected at probe
> + * (CNV_BURST or MANUAL). Called from buffer preenable before starting
> + * sampling. The chip is in AUTONOMOUS mode during idle (for read_raw).
> + */
> +static int ad4691_enter_conversion_mode(struct ad4691_state *st)
> +{
> +	int ret;
> +
> +	if (st->manual_mode)
> +		return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
> +					  AD4691_MANUAL_MODE, AD4691_MANUAL_MODE);
> +
> +	ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
> +				 AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> +			    AD4691_STATE_RESET_ALL);
> +}
> +
> +/*
> + * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode.
> + *
> + * Called from buffer postdisable to restore the chip to the
> + * idle state used by read_raw. Clears the sequencer and resets state.
> + */
> +static int ad4691_exit_conversion_mode(struct ad4691_state *st)
> +{
> +	if (st->manual_mode)
> +		return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
> +					  AD4691_MANUAL_MODE, 0);
> +
> +	return regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
> +				  AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
> +}
> +
> +static int ad4691_manual_buffer_preenable(struct iio_dev *indio_dev)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	unsigned int k, i;
> +	int ret;
> +
> +	memset(st->scan_xfers, 0, sizeof(st->scan_xfers));
> +	memset(st->scan_tx, 0, sizeof(st->scan_tx));
> +
> +	spi_message_init(&st->scan_msg);
> +
> +	k = 0;
> +	iio_for_each_active_channel(indio_dev, i) {
> +		if (i >= indio_dev->num_channels - 1)
> +			break; /* skip soft timestamp */

I don't think timestamp gets set in the scan mask. It is handled separately.

> +		/*
> +		 * Channel-select command occupies the first (high) byte of the
> +		 * 16-bit DIN frame; the second byte is a don't-care zero pad.
> +		 * put_unaligned_be16() writes [cmd, 0x00] in memory so the
> +		 * SPI controller sends the command byte first on the wire.
> +		 */
> +		put_unaligned_be16((u16)(AD4691_ADC_CHAN(i) << 8), &st->scan_tx[k]);
> +		st->scan_xfers[k].tx_buf = &st->scan_tx[k];
> +		/*
> +		 * The pipeline means xfer[0] receives the residual from the
> +		 * previous sequence, not a valid sample. Discard it (rx_buf=NULL)
> +		 * to avoid aliasing vals[0] across two concurrent DMA mappings.
> +		 * xfer[1] (or the NOOP when only one channel is active) writes
> +		 * the real ch[0] result to vals[0]. Subsequent transfers write
> +		 * into vals[k-1] so each result lands at the next dense slot.
> +		 */
> +		st->scan_xfers[k].rx_buf = (k == 0) ? NULL : &st->vals[k - 1];
> +		st->scan_xfers[k].len = sizeof(st->scan_tx[k]);
> +		st->scan_xfers[k].cs_change = 1;
> +		st->scan_xfers[k].cs_change_delay.value = AD4691_CNV_HIGH_TIME_NS;
> +		st->scan_xfers[k].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
> +		spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
> +		k++;
> +	}
> +
> +	/* Final NOOP transfer retrieves the last channel's result. */
> +	st->scan_xfers[k].tx_buf = &st->scan_tx[k]; /* scan_tx[k] == 0 == NOOP */
> +	st->scan_xfers[k].rx_buf = &st->vals[k - 1];
> +	st->scan_xfers[k].len = sizeof(st->scan_tx[k]);
> +	spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
> +
> +	ret = spi_optimize_message(st->spi, &st->scan_msg);
> +	if (ret)
> +		return ret;
> +
> +	ret = ad4691_enter_conversion_mode(st);
> +	if (ret) {
> +		spi_unoptimize_message(&st->scan_msg);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ad4691_manual_buffer_postdisable(struct iio_dev *indio_dev)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	ret = ad4691_exit_conversion_mode(st);
> +	spi_unoptimize_message(&st->scan_msg);
> +	return ret;
> +}
> +
> +static const struct iio_buffer_setup_ops ad4691_manual_buffer_setup_ops = {
> +	.preenable = &ad4691_manual_buffer_preenable,
> +	.postdisable = &ad4691_manual_buffer_postdisable,
> +};
> +
> +static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	unsigned int acc_mask, std_seq_config;
> +	unsigned int k, i;
> +	int ret;
> +
> +	memset(st->scan_xfers, 0, sizeof(st->scan_xfers));
> +	memset(st->scan_tx, 0, sizeof(st->scan_tx));
> +
> +	spi_message_init(&st->scan_msg);
> +
> +	/*
> +	 * Each AVG_IN read needs two transfers: a 2-byte address write phase
> +	 * followed by a 2-byte data read phase. CS toggles between channels
> +	 * (cs_change=1 on the read phase of all but the last channel).
> +	 */
> +	k = 0;
> +	iio_for_each_active_channel(indio_dev, i) {
> +		if (i >= indio_dev->num_channels - 1)
> +			break; /* skip soft timestamp */

I don't think timestamp gets set in the scan mask. It is handled separately.

> +		put_unaligned_be16(0x8000 | AD4691_AVG_IN(i), &st->scan_tx[k]);
> +		st->scan_xfers[2 * k].tx_buf = &st->scan_tx[k];
> +		st->scan_xfers[2 * k].len = sizeof(st->scan_tx[k]);
> +		spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);
> +		st->scan_xfers[2 * k + 1].rx_buf = &st->vals[k];
> +		st->scan_xfers[2 * k + 1].len = sizeof(st->scan_tx[k]);
> +		st->scan_xfers[2 * k + 1].cs_change = 1;
> +		spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg);
> +		k++;
> +	}
> +
> +	/*
> +	 * Append a 4-byte state-reset transfer [addr_hi, addr_lo,
> +	 * STATE_RESET_ALL, OSC_EN=1]. CS is asserted throughout, so
> +	 * ADDR_DESCENDING writes byte[3]=1 to OSC_EN_REG (0x180) as a
> +	 * deliberate side-write, keeping the oscillator enabled.
> +	 * STATE_RESET_ALL starts the next burst; the hardware does not
> +	 * accumulate new conversions until after a STATE_RESET pulse, so
> +	 * no in-progress data is lost.  No cs_change here — CS must
> +	 * deassert normally at end of message to frame the next command.
> +	 */
> +	put_unaligned_be16(AD4691_STATE_RESET_REG, st->scan_tx_reset);
> +	st->scan_tx_reset[2] = AD4691_STATE_RESET_ALL;
> +	st->scan_tx_reset[3] = 1;
> +	st->scan_xfers[2 * k].tx_buf = st->scan_tx_reset;
> +	st->scan_xfers[2 * k].len = sizeof(st->scan_tx_reset);
> +	spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);
> +
> +	ret = spi_optimize_message(st->spi, &st->scan_msg);
> +	if (ret)
> +		return ret;
> +
> +	std_seq_config = bitmap_read(indio_dev->active_scan_mask, 0,
> +				     iio_get_masklength(indio_dev)) & GENMASK(15, 0);
> +	ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, std_seq_config);
> +	if (ret)
> +		goto err_unoptimize;
> +
> +	acc_mask = ~std_seq_config & GENMASK(15, 0);
> +	ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG, acc_mask);
> +	if (ret)
> +		goto err_unoptimize;
> +
> +	ret = ad4691_enter_conversion_mode(st);
> +	if (ret)
> +		goto err_unoptimize;
> +
> +	return 0;
> +
> +err_unoptimize:
> +	spi_unoptimize_message(&st->scan_msg);
> +	return ret;
> +}
> +
> +static int ad4691_cnv_burst_buffer_postenable(struct iio_dev *indio_dev)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	/*
> +	 * Start the PWM and unmask the IRQ here in postenable, not in
> +	 * preenable. The IIO core attaches the trigger poll function between
> +	 * preenable and postenable; enabling sampling or unmasking the IRQ
> +	 * before that point risks a DATA_READY assertion landing before the
> +	 * poll function is registered. iio_trigger_poll() would drop the
> +	 * event, disable_irq_nosync() would fire, and enable_irq() would
> +	 * never be called, leaving the IRQ permanently masked.
> +	 */
> +	ret = ad4691_sampling_enable(st, true);
> +	if (ret)
> +		return ret;
> +
> +	enable_irq(st->irq);
> +	return 0;
> +}
> +
> +static int ad4691_cnv_burst_buffer_predisable(struct iio_dev *indio_dev)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +
> +	disable_irq(st->irq);
> +	return ad4691_sampling_enable(st, false);
> +}
> +
> +static int ad4691_cnv_burst_buffer_postdisable(struct iio_dev *indio_dev)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	ret = ad4691_exit_conversion_mode(st);
> +	spi_unoptimize_message(&st->scan_msg);
> +	return ret;
> +}
> +
> +static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops = {
> +	.preenable = &ad4691_cnv_burst_buffer_preenable,
> +	.postenable = &ad4691_cnv_burst_buffer_postenable,
> +	.predisable = &ad4691_cnv_burst_buffer_predisable,
> +	.postdisable = &ad4691_cnv_burst_buffer_postdisable,
> +};
> +
> +static ssize_t sampling_frequency_show(struct device *dev,
> +				       struct device_attribute *attr,
> +				       char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +
> +	return sysfs_emit(buf, "%lu\n", NSEC_PER_SEC / st->cnv_period_ns);
> +}
> +
> +static ssize_t sampling_frequency_store(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	unsigned int freq;
> +	int ret;
> +
> +	ret = kstrtouint(buf, 10, &freq);
> +	if (ret)
> +		return ret;
> +
> +	IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> +	if (IIO_DEV_ACQUIRE_FAILED(claim))
> +		return -EBUSY;
> +
> +	ret = ad4691_set_pwm_freq(st, freq);
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static IIO_DEVICE_ATTR_RW(sampling_frequency, 0);
> +
> +static const struct iio_dev_attr *ad4691_buffer_attrs[] = {
> +	&iio_dev_attr_sampling_frequency,
> +	NULL
> +};
> +
> +static irqreturn_t ad4691_irq(int irq, void *private)
> +{
> +	struct iio_dev *indio_dev = private;
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +
> +	/*
> +	 * Disable the IRQ before calling iio_trigger_poll(). The IRQ is
> +	 * re-enabled via the trigger .reenable callback, which the IIO core
> +	 * calls inside iio_trigger_notify_done() once use_count reaches zero.
> +	 * Re-enabling here (before notify_done) would race: a DATA_READY
> +	 * between enable_irq() and notify_done() calls iio_trigger_poll()
> +	 * while use_count > 0, dropping the event and permanently masking
> +	 * the IRQ.
> +	 *
> +	 * IRQF_ONESHOT masks the hardware line for the duration of this
> +	 * threaded handler; disable_irq_nosync() keeps the IRQ disabled even
> +	 * after IRQF_ONESHOT unmasks on return.
> +	 */
> +	disable_irq_nosync(st->irq);
> +	iio_trigger_poll(indio_dev->trig);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void ad4691_trigger_reenable(struct iio_trigger *trig)
> +{
> +	struct ad4691_state *st = iio_trigger_get_drvdata(trig);
> +
> +	enable_irq(st->irq);
> +}
> +
> +static const struct iio_trigger_ops ad4691_trigger_ops = {
> +	.reenable = ad4691_trigger_reenable,
> +	.validate_device = iio_trigger_validate_own_device,
> +};
> +
> +static int ad4691_read_scan(struct iio_dev *indio_dev, s64 ts)

return value is ignored, so this can be void.

> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	guard(mutex)(&st->lock);
> +
> +	ret = spi_sync(st->spi, &st->scan_msg);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * rx_buf pointers in scan_xfers point directly into scan.vals, so no
> +	 * copy is needed. The scan_msg already includes a STATE_RESET at the
> +	 * end (appended in preenable), so no explicit reset is needed here.
> +	 */
> +	iio_push_to_buffers_with_ts(indio_dev, st->vals, sizeof(st->vals), ts);
> +	return 0;
> +}
> +
> +static irqreturn_t ad4691_trigger_handler(int irq, void *p)
> +{
> +	struct iio_poll_func *pf = p;
> +	struct iio_dev *indio_dev = pf->indio_dev;
> +
> +	ad4691_read_scan(indio_dev, pf->timestamp);
> +	iio_trigger_notify_done(indio_dev->trig);
> +	return IRQ_HANDLED;
> +}
> +
> +/*
> + * CNV burst mode: only allow our own trigger (driven by DATA_READY IRQ).
> + * Manual mode: external triggers (e.g. iio-trig-hrtimer) must be allowed
> + * because manual mode has no DATA_READY IRQ to fire the internal trigger.
> + * iio_trigger_ops.validate_device = iio_trigger_validate_own_device is
> + * correct in both modes — it prevents other devices from hijacking our
> + * internal trigger; the distinction here is only for iio_info.validate_trigger.
> + */
> +static const struct iio_info ad4691_cnv_burst_info = {
> +	.read_raw = &ad4691_read_raw,
> +	.write_raw = &ad4691_write_raw,
> +	.read_avail = &ad4691_read_avail,
> +	.debugfs_reg_access = &ad4691_reg_access,
> +	.validate_trigger = iio_validate_own_trigger,

Inconsistent use of & on function pointer. (My personal preference
is to ommit & on all function pointers since it is not required.)

> +};
> +
> +static const struct iio_info ad4691_manual_info = {
>  	.read_raw = &ad4691_read_raw,
>  	.write_raw = &ad4691_write_raw,
>  	.read_avail = &ad4691_read_avail,
>  	.debugfs_reg_access = &ad4691_reg_access,
>  };
>  
> +static int ad4691_pwm_setup(struct ad4691_state *st)
> +{
> +	struct device *dev = regmap_get_device(st->regmap);
> +
> +	st->conv_trigger = devm_pwm_get(dev, "cnv");
> +	if (IS_ERR(st->conv_trigger))
> +		return dev_err_probe(dev, PTR_ERR(st->conv_trigger),
> +				     "Failed to get CNV PWM\n");
> +
> +	return ad4691_set_pwm_freq(st, st->info->max_rate);
> +}
> +
>  static int ad4691_regulator_setup(struct ad4691_state *st)
>  {
>  	struct device *dev = regmap_get_device(st->regmap);
> @@ -623,6 +1097,22 @@ static int ad4691_config(struct ad4691_state *st)
>  	unsigned int val;
>  	int ret;
>  
> +	/*
> +	 * Determine buffer conversion mode from DT: if a PWM is provided it
> +	 * drives the CNV pin (CNV_BURST_MODE); otherwise CNV is tied to CS
> +	 * and each SPI transfer triggers a conversion (MANUAL_MODE).
> +	 * Both modes idle in AUTONOMOUS mode so that read_raw can use the
> +	 * internal oscillator without disturbing the hardware configuration.
> +	 */
> +	if (device_property_present(dev, "pwms")) {
> +		st->manual_mode = false;
> +		ret = ad4691_pwm_setup(st);
> +		if (ret)
> +			return ret;
> +	} else {
> +		st->manual_mode = true;
> +	}
> +
>  	switch (st->vref_uV) {
>  	case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX:
>  		ref_val = AD4691_VREF_2P5;
> @@ -676,6 +1166,79 @@ static int ad4691_config(struct ad4691_state *st)
>  	return 0;
>  }
>  
> +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> +					 struct ad4691_state *st)
> +{
> +	struct device *dev = regmap_get_device(st->regmap);
> +	struct iio_trigger *trig;
> +	unsigned int i;
> +	int irq, ret;
> +
> +	indio_dev->channels = st->info->sw_info->channels;
> +	indio_dev->num_channels = st->info->sw_info->num_channels;
> +	indio_dev->info = st->manual_mode ? &ad4691_manual_info : &ad4691_cnv_burst_info;
> +
> +	trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
> +				      iio_device_id(indio_dev));
> +	if (!trig)
> +		return -ENOMEM;
> +
> +	trig->ops = &ad4691_trigger_ops;
> +	iio_trigger_set_drvdata(trig, st);
> +
> +	ret = devm_iio_trigger_register(dev, trig);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "IIO trigger register failed\n");
> +
> +	indio_dev->trig = iio_trigger_get(trig);
> +
> +	if (st->manual_mode)
> +		return devm_iio_triggered_buffer_setup(dev, indio_dev,
> +						       &iio_pollfunc_store_time,
> +						       &ad4691_trigger_handler,
> +						       &ad4691_manual_buffer_setup_ops);
> +
> +	/*
> +	 * The GP pin named in interrupt-names asserts at end-of-conversion.
> +	 * The IRQ handler stops conversions and fires the IIO trigger so
> +	 * the trigger handler can read and push the sample to the buffer.
> +	 * The IRQ is kept disabled until the buffer is enabled.
> +	 */
> +	irq = -ENXIO;
> +	for (i = 0; i < ARRAY_SIZE(ad4691_gp_names); i++) {
> +		irq = fwnode_irq_get_byname(dev_fwnode(dev),
> +					    ad4691_gp_names[i]);
> +		if (irq > 0 || irq == -EPROBE_DEFER)
> +			break;
> +	}
> +	if (irq < 0)
> +		return dev_err_probe(dev, irq, "failed to get GP interrupt\n");
> +
> +	st->irq = irq;
> +
> +	ret = ad4691_gpio_setup(st, i);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * IRQ is kept disabled until the buffer is enabled to prevent
> +	 * spurious DATA_READY events before the SPI message is set up.
> +	 */
> +	ret = devm_request_threaded_irq(dev, irq, NULL,
> +					&ad4691_irq,
> +					IRQF_ONESHOT | IRQF_NO_AUTOEN,
> +					indio_dev->name, indio_dev);
> +	if (ret)
> +		return ret;
> +
> +	return devm_iio_triggered_buffer_setup_ext(dev, indio_dev,
> +						   &iio_pollfunc_store_time,
> +						   &ad4691_trigger_handler,
> +						   IIO_BUFFER_DIRECTION_IN,
> +						   &ad4691_cnv_burst_buffer_setup_ops,
> +						   ad4691_buffer_attrs);
> +}
> +
>  static int ad4691_probe(struct spi_device *spi)
>  {
>  	struct device *dev = &spi->dev;
> @@ -688,6 +1251,7 @@ static int ad4691_probe(struct spi_device *spi)
>  		return -ENOMEM;
>  
>  	st = iio_priv(indio_dev);
> +	st->spi = spi;
>  	st->info = spi_get_device_match_data(spi);
>  	if (!st->info)
>  		return -ENODEV;
> @@ -714,11 +1278,11 @@ static int ad4691_probe(struct spi_device *spi)
>  		return ret;
>  
>  	indio_dev->name = st->info->name;
> -	indio_dev->info = &ad4691_info;
>  	indio_dev->modes = INDIO_DIRECT_MODE;
>  
> -	indio_dev->channels = st->info->sw_info->channels;
> -	indio_dev->num_channels = st->info->sw_info->num_channels;
> +	ret = ad4691_setup_triggered_buffer(indio_dev, st);
> +	if (ret)
> +		return ret;
>  
>  	return devm_iio_device_register(dev, indio_dev);
>  }
> 


^ permalink raw reply

* [PATCH 2/2] docs: maintainers_include: keep the last entry at the end
From: Mauro Carvalho Chehab @ 2026-05-16 17:33 UTC (permalink / raw)
  To: Jonathan Corbet, Linux Doc Mailing List, Mauro Carvalho Chehab
  Cc: Mauro Carvalho Chehab, linux-kernel, Shuah Khan
In-Reply-To: <cover.1778952682.git.mchehab+huawei@kernel.org>

The last maintainer's entry ("THE REST") is meant to be at the
end. Ensure that.

While here, use a case-insensitive sort to avoid placing "iSCSI"
near the end.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 Documentation/sphinx/maintainers_include.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/Documentation/sphinx/maintainers_include.py b/Documentation/sphinx/maintainers_include.py
index b8b7282ebe38..dc9f9e188ffa 100755
--- a/Documentation/sphinx/maintainers_include.py
+++ b/Documentation/sphinx/maintainers_include.py
@@ -284,7 +284,12 @@ class MaintainersInclude(Include):
 
         self.state.document['maintainers_included'] = True
 
-        for name, fields in sorted(maint_parser.maint_entries.items()):
+        # Keep the last entry ("THE REST") in the end
+        entries = list(maint_parser.maint_entries.keys())
+        entries = sorted(entries[:-1], key=str.casefold) + [entries[-1]]
+
+        for name in entries:
+            fields = maint_parser.maint_entries[name]
             output += f"  * - {name}\n"
             tag = "-"
             for field, lines in fields.items():
-- 
2.54.0


^ permalink raw reply related

* [PATCH 0/2] Two small cleanups to maintainers_include
From: Mauro Carvalho Chehab @ 2026-05-16 17:33 UTC (permalink / raw)
  To: Jonathan Corbet, Mauro Carvalho Chehab
  Cc: Mauro Carvalho Chehab, linux-doc, linux-kernel, Shuah Khan

Hi Jon,

This series contain two minor cleanups to maintainers_include.py:

- make it backward compatible with Python < 3.10
  (according with vermin, it should now be backward-compat up
   to 3.6)

- keep "THE REST" at the end.

Mauro Carvalho Chehab (2):
  docs: maintainers_include: restore compatibility with Python 3.6
  docs: maintainers_include: keep the last entry at the end

 Documentation/sphinx/maintainers_include.py | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

-- 
2.54.0


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox