Linux Documentation
 help / color / mirror / Atom feed
* [PATCH 0/2] docs: iio: update dated triggered buffer example
From: David Lechner @ 2026-05-17 17:00 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Jonathan Cameron, Nuno Sá,
	Andy Shevchenko
  Cc: linux-doc, linux-kernel, linux-iio, David Lechner

Noticed this example was out of date while grepping for something else.
And when I did get_maintainer.pl on it, it didn't match the IIO
subsystem, so we get a bonus patch to fix that too.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
David Lechner (2):
      MAINTAINERS: add match for IIO API docs
      docs: iio: triggered-buffers: use new helpers in example

 Documentation/driver-api/iio/triggered-buffers.rst | 8 ++++----
 MAINTAINERS                                        | 1 +
 2 files changed, 5 insertions(+), 4 deletions(-)
---
base-commit: 8678fb54958893818ddeccd05fea560a4e1fc759
change-id: 20260517-iio-doc-triggered-buffer-update-helpers-ef7e3895c9f4

Best regards,
--  
David Lechner <dlechner@baylibre.com>


^ permalink raw reply

* Re: [PATCH] docs: hwmon: htu31: document debugfs serial_number
From: Guenter Roeck @ 2026-05-17 16:35 UTC (permalink / raw)
  To: Chen-Shi-Hong
  Cc: Jonathan Corbet, Shuah Khan, linux-hwmon, linux-doc, linux-kernel
In-Reply-To: <20260517125320.2196-1-eric039eric@gmail.com>

On Sun, May 17, 2026 at 08:52:21PM +0800, Chen-Shi-Hong wrote:
> Document the debugfs serial_number file exposed by the htu31 driver.
> 
> The driver creates a debugfs entry for the sensor serial number, but
> the documentation currently only describes the sysfs interface.
> 
> Signed-off-by: Chen-Shi-Hong <eric039eric@gmail.com>

Applied.

Thanks,
Guenter

> ---
>  Documentation/hwmon/htu31.rst | 7 +++++++
>  1 file changed, 7 insertions(+)
> 
> diff --git a/Documentation/hwmon/htu31.rst b/Documentation/hwmon/htu31.rst
> index ccde84264643..9ab774dcf65d 100644
> --- a/Documentation/hwmon/htu31.rst
> +++ b/Documentation/hwmon/htu31.rst
> @@ -35,3 +35,10 @@ temp1_input:        temperature input
>  humidity1_input:    humidity input
>  heater_enable:      heater control
>  =================== =================
> +
> +debugfs-Interface
> +-----------------
> +
> +=================== =========================================
> +serial_number:      unique serial number of the sensor
> +=================== =========================================

^ permalink raw reply

* Re: [PATCH] docs: threat-model: add missing closing parenthesis
From: Willy Tarreau @ 2026-05-17 16:04 UTC (permalink / raw)
  To: Baruch Siach; +Cc: Jonathan Corbet, Shuah Khan, workflows, linux-doc
In-Reply-To: <da8ee1e8b4e99261ec11544c4e1a4f81316ae965.1779032501.git.baruch@tkos.co.il>

On Sun, May 17, 2026 at 06:41:41PM +0300, Baruch Siach wrote:
> Fixes: a03ef333fbd6 ("Documentation: security-bugs: explain what is and is not a security bug")
> Signed-off-by: Baruch Siach <baruch@tkos.co.il>

Thank you, and sorry for this mistake!

Obviously: Acked-by: Willy Tarreau <w@1wt.eu>

Willy

> ---
>  Documentation/process/threat-model.rst | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/Documentation/process/threat-model.rst b/Documentation/process/threat-model.rst
> index f177b8d3c1ca..9dd8011dde82 100644
> --- a/Documentation/process/threat-model.rst
> +++ b/Documentation/process/threat-model.rst
> @@ -176,7 +176,7 @@ regular bug:
>    * problems seen only under development simulators, emulators, or combinations
>      that do not exist on real systems at the time of reporting (issues
>      involving tens of millions of threads, tens of thousands of CPUs,
> -    unrealistic CPU frequencies, RAM sizes or disk capacities, network speeds.
> +    unrealistic CPU frequencies, RAM sizes or disk capacities, network speeds).
>  
>    * issues whose reproduction requires hardware modification or emulation,
>      including fake USB devices that pretend to be another one.
> -- 
> 2.53.0

^ permalink raw reply

* Re: [PATCH] docs: fix typo in uniwill-laptop.rst
From: Armin Wolf @ 2026-05-17 16:01 UTC (permalink / raw)
  To: Cheesecake, corbet; +Cc: skhan, platform-driver-x86, linux-doc, linux-kernel
In-Reply-To: <20260516070650.9454-1-cheesecake2960@icloud.com>

Am 16.05.26 um 09:06 schrieb Cheesecake:

> Replace "benifit" with "benefit".

Reviewed-by: Armin Wolf <W_Armin@gmx.de>

> Signed-off-by: Cheesecake <cheesecake2960@icloud.com>
> ---
>   Documentation/wmi/devices/uniwill-laptop.rst | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/Documentation/wmi/devices/uniwill-laptop.rst b/Documentation/wmi/devices/uniwill-laptop.rst
> index e246bf293..65583b239 100644
> --- a/Documentation/wmi/devices/uniwill-laptop.rst
> +++ b/Documentation/wmi/devices/uniwill-laptop.rst
> @@ -189,7 +189,7 @@ Indexed IO
>   
>   Indexed IO with IO ports with a granularity of a single byte can be performed using the ``RIOP``
>   (read) and ``WIOP`` (write) ACPI control methods. Those ACPI methods are unused because they
> -provide no benifit when compared to the native IO port access functions provided by the kernel.
> +provide no benefit when compared to the native IO port access functions provided by the kernel.
>   
>   Special thanks go to github user `pobrn` which developed the
>   `qc71_laptop <https://github.com/pobrn/qc71_laptop>`_ driver on which this driver is partly based.

^ permalink raw reply

* [PATCH] docs: threat-model: add missing closing parenthesis
From: Baruch Siach @ 2026-05-17 15:41 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan
  Cc: workflows, linux-doc, Willy Tarreau, Baruch Siach

Fixes: a03ef333fbd6 ("Documentation: security-bugs: explain what is and is not a security bug")
Signed-off-by: Baruch Siach <baruch@tkos.co.il>
---
 Documentation/process/threat-model.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Documentation/process/threat-model.rst b/Documentation/process/threat-model.rst
index f177b8d3c1ca..9dd8011dde82 100644
--- a/Documentation/process/threat-model.rst
+++ b/Documentation/process/threat-model.rst
@@ -176,7 +176,7 @@ regular bug:
   * problems seen only under development simulators, emulators, or combinations
     that do not exist on real systems at the time of reporting (issues
     involving tens of millions of threads, tens of thousands of CPUs,
-    unrealistic CPU frequencies, RAM sizes or disk capacities, network speeds.
+    unrealistic CPU frequencies, RAM sizes or disk capacities, network speeds).
 
   * issues whose reproduction requires hardware modification or emulation,
     including fake USB devices that pretend to be another one.
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH RFC v4 09/10] Documentation: ABI: testing: add docs for ad9910 sysfs entries
From: Jonathan Cameron @ 2026-05-17 15:45 UTC (permalink / raw)
  To: Rodrigo Alencar via B4 Relay
  Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
	linux-hardening, Lars-Peter Clausen, Michael Hennerich,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva
In-Reply-To: <20260517155843.7f833658@jic23-huawei>

On Sun, 17 May 2026 15:58:43 +0100
Jonathan Cameron <jic23@kernel.org> wrote:

> On Fri, 08 May 2026 18:00:25 +0100
> Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> 
> > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > 
> > Add custom ABI documentation file for the DDS AD9910 with sysfs entries to
> > control Parallel Port, Digital Ramp Generator and OSK parameters.
> > 
> > Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>  
> I'm fine with phase and frequency as defined, but for the scaling it made me wonder.
> For outvoltage0 channels the assumption the value is the peak voltage so if
> we know what input to be modulated by the ramp generator can we express them
> in volts (well milivolts) rather than as a scaling multiplier?
> 
> That seems to me like it fits better with the overall ABI.
> 
> > +What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_offset
> > +KernelVersion:
> > +Contact:	linux-iio@vger.kernel.org
> > +Description:
> > +		For a channel that allows amplitude control through buffers, this
> > +		represents the value for a base amplitude scale. The actual output
> > +		amplitude scale is a result with the sum of this value.
> > +  
> 
> > +
> > +What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_roc  
> 
> Silly question perhaps but can work out how this related to millivolts/sec
> That might make a more intuitive interface than scaling multiplier per sec
> Perhaps the combination with offset makes this impossible though maybe that
> could be a expressed as a voltage offset?  Afterall if the amplitude being
> scaled is 5V then 5 * (offset + scale) = 5 * offset + 5 * scale
>  
See thread on next patch. I think I argued myself out of this.  

> > +KernelVersion:
> > +Contact:	linux-iio@vger.kernel.org
> > +Description:
> > +		Amplitude scale rate of change in 1/s for channels that ramp
> > +		amplitude. This value may be influenced by the channel's
> > +		sampling_frequency setting.  
> 
> 
> 


^ permalink raw reply

* Re: [PATCH RFC v4 10/10] docs: iio: add documentation for ad9910 driver
From: Jonathan Cameron @ 2026-05-17 15:44 UTC (permalink / raw)
  To: David Lechner
  Cc: Rodrigo Alencar, rodrigo.alencar, linux-iio, devicetree,
	linux-kernel, linux-doc, linux-hardening, Lars-Peter Clausen,
	Michael Hennerich, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, Jonathan Corbet,
	Shuah Khan, Kees Cook, Gustavo A. R. Silva
In-Reply-To: <5bce7868-feca-4c54-a14d-ad4bf4072c29@baylibre.com>

On Mon, 11 May 2026 10:23:35 -0500
David Lechner <dlechner@baylibre.com> wrote:

> On 5/11/26 10:02 AM, Rodrigo Alencar wrote:
> > On 26/05/11 09:46AM, David Lechner wrote:  
> >> On 5/10/26 4:30 AM, Rodrigo Alencar wrote:  
> >>> On 26/05/09 06:42PM, David Lechner wrote:  
> >>>> On 5/8/26 12:00 PM, Rodrigo Alencar via B4 Relay wrote:  
> >>>>> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> >>>>>
> >>>>> Add documentation for the AD9910 DDS IIO driver, which describes channels,
> >>>>> DDS modes, attributes and ABI usage examples.  
> >>>
> >>> ...
> >>>  
> >>>>> +       must be a power of 2.
> >>>>> +
> >>>>> +   * - ``frequency_offset``
> >>>>> +     - Hz
> >>>>> +     - Base FTW to which scaled parallel data is added. Range :math:`[0, f_{SYSCLK}/2)`.
> >>>>> +
> >>>>> +   * - ``phase_offset``
> >>>>> +     - rad
> >>>>> +     - Base phase for polar modulation. Lower 8 bits of POW register.
> >>>>> +       Range :math:`[0, 2\pi/256)`.
> >>>>> +
> >>>>> +   * - ``scale_offset``
> >>>>> +     - fractional
> >>>>> +     - Base amplitude for polar modulation. Lower 6 bits of ASF register.
> >>>>> +       Range :math:`[0, 1/256)`.
> >>>>> +  
> >>>>
> >>>> I guess there was some discussion on these attributes. I see some of these in the
> >>>> ad9832 driver in staging, but I'm guessing they are new ABI. It isn't clear to
> >>>> me from the documentation here what they actually do though. I guess they are
> >>>> just basic transformations on the input signal?  
> >>>
> >>> Not sure how the ABI is not clear:
> >>>
> >>> 	For a channel that allows amplitude control through buffers, this
> >>> 	represents the value for a base amplitude scale. The actual output
> >>> 	amplitude scale is a result with the sum of this value.
> >>>
> >>> So yes, it is a basic transformation.  
> >>
> >> I didn't have time to read the ABI docs yet. For scale_offset though,
> >> how is that different from the existing offset attribute?  
> > 
> > I suppose that existing offset ABI is applied to (raw * scale), mostly for
> > voltage channels, here the scale_offset is an offset to the scale itself.  
> 
> 
> Ah, so a very general case would be (raw * (scale + scale_offset)) + offset
> 
> when the scale can change as a function of time and comes from an external
> source.
Ah. Similar question to what I was commenting on.  Though maths is currently wrong
for normal offset application as it is pre scale.


	(raw + offset) * scale is normal case.
This is proposing (I think)

	(raw + offset) * (scale_offset + scale)

Altvoltages are a little odd though in that we are really always kind of dealing
in scales as it's the peak voltage that is the base unit. So they are kind
of always about scale - hence for such a single offset would be shifting the
mid voltage point which I guess is different form scale_offset.

Hmm. Not sure I can draw this but i'll give it a go...

So with no modulation going on and scale = 2.0, Raw 1000. 
(imagine these are sign waves)

  _         _       _ 2V
 / \       / \
/___\_____/___\___  _ 0V
     \   /     \
      \_/       \__ _ -2V

That is sine wave -2/2V swing.

Now if scale or voltage double it get twice as big.

If offset + 100

  _         _       _ 3V
 / \       / \
/   \     /   \   
_____\___/_____\___ _ 0v
      \_/       \__ _ -1V

Scale offset at this point seems straight forward.. Gets more fun when it's modulated.

For now apply a scale offset of -1 and scale becomes 2 - 1 == 1. 
  _         _       _ 1.5V
 / \       / \
/   \     /   \   
_____\___/_____\___ _ 0v
      \_/       \__ _ -0.5V

So for simple case we could just role it into scale. However the fun here
I believe is that _scale is controlled by say a ramp generator.. 

Ok. I can't really draw this.. Lets try with xs

Initial scale 1, ramping to 2 over a couple 1ish cycles, offset 1.  That is
computed sale is going from 2 to 3.
                   Peak 3
____________________________ 3V
    Peak 2.33         xx
______x_______________xx____ 2V 
     xxx             x  x
   xx   x            x
 xx      x          x
x_________x_________x_______ 0V   
           x       x 
            x      x
             x    x
______________x__x__________ -2V            
_______________xx___________ -2.5
                Peak 2.67

Key being really where this starts which is scale_offset = 1 rather than starting
ramp from scale of 0.

Having drawn these I'm even less clear in my head on whether we can move from
expressing that scale_offset and scale_roc in volts  - i.e. not as scales
or not.

Given need for separate control for overall mid point of waveform and the
starting point of scaling I think not.  Ah well.  The challenge will be
how to makes sure folk looking at the ABI can understand the complex
interactions of all these parameters. We may need some extra docs with
better diagrams than above.
              
Jonathan



^ permalink raw reply

* Re: [PATCH RFC v4 07/10] iio: frequency: ad9910: add output shift keying support
From: Jonathan Cameron @ 2026-05-17 15:08 UTC (permalink / raw)
  To: Rodrigo Alencar via B4 Relay
  Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
	linux-hardening, Lars-Peter Clausen, Michael Hennerich,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva
In-Reply-To: <20260508-ad9910-iio-driver-v4-7-d26bfd20ee3d@analog.com>

On Fri, 08 May 2026 18:00:23 +0100
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:

> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> Add OSK channel with amplitude envelope control capabilities:
> - OSK enable/disable via IIO_CHAN_INFO_ENABLE;
> - Amplitude ramp rate control via IIO_CHAN_INFO_SAMP_FREQ;
> - Amplitude scale readback via IIO_CHAN_INFO_SCALE (ASF register);
> - Automatic OSK step size configurable througth the scale_roc extended
>   attribute, which allows for selectable step sizes in nano-units:
> 	- 0: no step, means manual mode (NOT pin controlled)
> 	- 61035: 1/2^14 step, automatic mode (pin controlled)
> 	- 122070: 2/2^14 step, automatic mode (pin controlled)
> 	- 244141: 4/2^14 step, automatic mode (pin controlled)
> 	- 488281: 8/2^14 step, automatic mode (pin controlled)
> 	- 1000000000: 1.0 step, manual mode (pin controlled)
> 
> The ASF register is initialized with a default amplitude ramp rate during
> device setup to ensure valid readback.
> 
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>


>  
> +#define AD9910_OSK_EXT_INFO(_name, _ident) \
> +	AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, osk_attrs)
> +
>  static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = {
>  	AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
>  	{ }
> @@ -1018,6 +1154,12 @@ static const struct iio_chan_spec_ext_info ad9910_drg_ramp_ext_info[] = {
>  	{ }
>  };
>  
> +static const struct iio_chan_spec_ext_info ad9910_osk_ext_info[] = {
> +	AD9910_OSK_EXT_INFO("scale_roc", AD9910_OSK_AUTO_ROC),
> +	AD9910_OSK_EXT_INFO("scale_roc_available", AD9910_OSK_AUTO_ROC_AVAIL),
same questions about whether we can transform these to voltages and hence
not scale (though arguably the main unit of an altvoltage channel is a scale
of a 1V peak amplitude sine wave but meh).

> +	{ }
> +};

^ permalink raw reply

* Re: [PATCH RFC v4 06/10] iio: frequency: ad9910: add RAM mode support
From: Jonathan Cameron @ 2026-05-17 15:06 UTC (permalink / raw)
  To: Rodrigo Alencar via B4 Relay
  Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
	linux-hardening, Lars-Peter Clausen, Michael Hennerich,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva
In-Reply-To: <20260508-ad9910-iio-driver-v4-6-d26bfd20ee3d@analog.com>

On Fri, 08 May 2026 18:00:22 +0100
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:

> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> Add RAM control channel, which includes:
> - RAM data loading via firmware upload interface;
> - Per-profile configuration and DDS core parameter destination as firmware
>   metadata;
> - Profile switching relying on profile channels;
> - Sampling frequency control of the active profile;
> - ram-enable-aware read/write paths that redirect single tone
>   frequency/phase/amplitude access through reg_profile cache when RAM is
>   active;
> 
> When RAM is enabled, the DDS profile parameters (frequency, phase,
> amplitude) for the single tone mode are sourced from a shadow register
> cache (reg_profile[]) since the profile registers are repurposed for RAM
> control.
> 
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>

Minor stuff inline.

> +
> +static enum fw_upload_err ad9910_ram_fwu_write(struct fw_upload *fw_upload,
> +					       const u8 *data, u32 offset,
> +					       u32 size, u32 *written)
> +{
> +	const struct ad9910_ram_fw *fw_data = (const struct ad9910_ram_fw *)data;
> +	struct ad9910_state *st = fw_upload->dd_handle;
> +	int ret, ret2, idx, wcount;
> +	u64 tmp64, backup;
> +
> +	if (offset != 0)
> +		return FW_UPLOAD_ERR_INVALID_SIZE;
> +
> +	guard(mutex)(&st->lock);
> +
> +	if (st->ram_fwu_cancel)
> +		return FW_UPLOAD_ERR_CANCELED;
> +
> +	if (AD9910_RAM_ENABLED(st))
> +		return FW_UPLOAD_ERR_HW_ERROR;
> +
> +	/* copy ram profiles */
> +	for (idx = 0; idx < AD9910_NUM_PROFILES; idx++)
> +		st->reg_profile[idx] = get_unaligned_be64(&fw_data->profiles[idx]) |
> +				       AD9910_PROFILE_RAM_OPEN_MSK;
> +
> +	/* update CFR1 */

Comment is kind of obvious.  Maybe say more or drop it.

> +	ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
> +				  AD9910_CFR1_RAM_PLAYBACK_DEST_MSK |
> +				  AD9910_CFR1_INT_PROFILE_CTL_MSK,
> +				  get_unaligned_be32(&fw_data->cfr1), true);
> +	if (ret)
> +		return FW_UPLOAD_ERR_RW_ERROR;
> +
> +	wcount = get_unaligned_be32(&fw_data->wcount);
> +	if (!wcount) {
> +		*written = size;
> +		return FW_UPLOAD_ERR_NONE; /* nothing else to write */
> +	}
> +
> +	/* ensure profile is selected */

Comment is not adding value unless there is more to say.

> +	ret = ad9910_profile_set(st, st->profile);
> +	if (ret)
> +		return FW_UPLOAD_ERR_HW_ERROR;
> +
> +	/* backup profile register and update it with required address range */
> +	backup = st->reg[AD9910_REG_PROFILE(st->profile)].val64;
> +	tmp64 = AD9910_PROFILE_RAM_STEP_RATE_MSK |
> +		FIELD_PREP(AD9910_PROFILE_RAM_START_ADDR_MSK, 0) |
> +		FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK, wcount - 1);
> +	ret = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), tmp64, true);
> +	if (ret)
> +		return FW_UPLOAD_ERR_RW_ERROR;
> +
> +	/* populate words into tx_buf[1:] */
Another comment that doesn't add value given the code is obviously doing that.
If nothing else to say remove it.
> +	memcpy(&st->tx_buf[1], fw_data->words, wcount * AD9910_RAM_WORD_SIZE);
> +
> +	/* write ram data and restore profile register */
> +	ret = ad9910_spi_write(st, AD9910_REG_RAM,
> +			       wcount * AD9910_RAM_WORD_SIZE, false);
> +	ret2 = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), backup, true);
> +	if (ret || ret2)
> +		return FW_UPLOAD_ERR_RW_ERROR;
> +
> +	*written = size;
> +	return FW_UPLOAD_ERR_NONE;
> +}

>  static int ad9910_probe(struct spi_device *spi)
>  {
>  	static const char * const supplies[] = {
> @@ -1688,7 +1991,21 @@ static int ad9910_probe(struct spi_device *spi)
>  	if (ret)
>  		return dev_err_probe(dev, ret, "device setup failed\n");
>  
> -	return devm_iio_device_register(dev, indio_dev);
> +	ret = devm_iio_device_register(dev, indio_dev);
> +	if (ret)
> +		return ret;
> +
> +	snprintf(st->ram_fwu_name, sizeof(st->ram_fwu_name), "%s:ram",
> +		 dev_name(&indio_dev->dev));
> +	st->ram_fwu = firmware_upload_register(THIS_MODULE, dev, st->ram_fwu_name,
> +					       &ad9910_ram_fwu_ops, st);
> +	if (IS_ERR(st->ram_fwu))
> +		return dev_err_probe(dev, PTR_ERR(st->ram_fwu),
> +				     "failed to register ram upload ops\n");
> +
> +	ad9910_debugfs_init(st, indio_dev);
> +
> +	return devm_add_action_or_reset(dev, ad9910_ram_fwu_unregister, st->ram_fwu);

Why is this only after we've registered the device?  At that point userspace
stuff is exposed, so any risk of a race with this bit not having finished yet?
Perhaps a comment would be useful.


>  }
>  
>  static const struct spi_device_id ad9910_id[] = {
> 


^ permalink raw reply

* Re: [PATCH RFC v4 05/10] iio: frequency: ad9910: add digital ramp generator support
From: Jonathan Cameron @ 2026-05-17 15:01 UTC (permalink / raw)
  To: Rodrigo Alencar via B4 Relay
  Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
	linux-hardening, Lars-Peter Clausen, Michael Hennerich,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva
In-Reply-To: <20260508-ad9910-iio-driver-v4-5-d26bfd20ee3d@analog.com>

On Fri, 08 May 2026 18:00:21 +0100
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:

> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> Add Digital Ramp Generator channels with destination selection (frequency,
> phase, or amplitude) based on attribute writes, dwell mode control,
> configurable upper/lower limits, step size controlled with rate of change
> config, and step rate controlled as sampling frequency.
> 
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
> ---
>  drivers/iio/frequency/ad9910.c | 511 ++++++++++++++++++++++++++++++++++++++++-


>  static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = {
>  	AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
>  	{ }
> @@ -677,6 +939,14 @@ static const struct iio_chan_spec_ext_info ad9910_pp_ext_info[] = {
>  	{ }
>  };
>  
> +static const struct iio_chan_spec_ext_info ad9910_drg_ramp_ext_info[] = {
> +	AD9910_EXT_INFO("dwell_en", AD9910_DRG_DWELL_EN, IIO_SEPARATE),
> +	AD9910_DRG_EXT_INFO("frequency_roc", AD9910_DRG_FREQ_ROC),
> +	AD9910_DRG_EXT_INFO("phase_roc", AD9910_DRG_PHASE_ROC),
> +	AD9910_DRG_EXT_INFO("scale_roc", AD9910_DRG_AMP_ROC),

See abi docs reply. This scale made me wonder if we can make this just roc with it
scaled to be in the base units for altvoltage.

> +	{ }
> +};

^ permalink raw reply

* Re: [PATCH RFC v4 09/10] Documentation: ABI: testing: add docs for ad9910 sysfs entries
From: Jonathan Cameron @ 2026-05-17 14:58 UTC (permalink / raw)
  To: Rodrigo Alencar via B4 Relay
  Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
	linux-hardening, Lars-Peter Clausen, Michael Hennerich,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva
In-Reply-To: <20260508-ad9910-iio-driver-v4-9-d26bfd20ee3d@analog.com>

On Fri, 08 May 2026 18:00:25 +0100
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:

> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> Add custom ABI documentation file for the DDS AD9910 with sysfs entries to
> control Parallel Port, Digital Ramp Generator and OSK parameters.
> 
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
I'm fine with phase and frequency as defined, but for the scaling it made me wonder.
For outvoltage0 channels the assumption the value is the peak voltage so if
we know what input to be modulated by the ramp generator can we express them
in volts (well milivolts) rather than as a scaling multiplier?

That seems to me like it fits better with the overall ABI.

> +What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_offset
> +KernelVersion:
> +Contact:	linux-iio@vger.kernel.org
> +Description:
> +		For a channel that allows amplitude control through buffers, this
> +		represents the value for a base amplitude scale. The actual output
> +		amplitude scale is a result with the sum of this value.
> +

> +
> +What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_roc

Silly question perhaps but can work out how this related to millivolts/sec
That might make a more intuitive interface than scaling multiplier per sec
Perhaps the combination with offset makes this impossible though maybe that
could be a expressed as a voltage offset?  Afterall if the amplitude being
scaled is 5V then 5 * (offset + scale) = 5 * offset + 5 * scale
 
> +KernelVersion:
> +Contact:	linux-iio@vger.kernel.org
> +Description:
> +		Amplitude scale rate of change in 1/s for channels that ramp
> +		amplitude. This value may be influenced by the channel's
> +		sampling_frequency setting.



^ permalink raw reply

* Re: [PATCH RFC v4 03/10] iio: frequency: ad9910: initial driver implementation
From: Jonathan Cameron @ 2026-05-17 14:47 UTC (permalink / raw)
  To: Rodrigo Alencar via B4 Relay
  Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
	linux-hardening, Lars-Peter Clausen, Michael Hennerich,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva
In-Reply-To: <20260508-ad9910-iio-driver-v4-3-d26bfd20ee3d@analog.com>

On Fri, 08 May 2026 18:00:19 +0100
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:

> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> Add the core AD9910 DDS driver infrastructure with single tone mode
> support. This includes SPI register access, profile management via GPIO
> pins, PLL/DAC configuration from firmware properties, and single tone
> frequency/phase/amplitude control through IIO attributes.
> 
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
Hi Rodrigo

A few really minor things from a fresh look through.

Jonathan

> diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
> new file mode 100644
> index 000000000000..c75f2ef178c2
> --- /dev/null
> +++ b/drivers/iio/frequency/ad9910.c

> +
> +static int ad9910_read_raw(struct iio_dev *indio_dev,
> +			   struct iio_chan_spec const *chan,
> +			   int *val, int *val2, long info)
> +{
> +	struct ad9910_state *st = iio_priv(indio_dev);
> +	u64 tmp64;
> +	u32 tmp32;
> +
> +	guard(mutex)(&st->lock);
> +
> +	switch (info) {
> +	case IIO_CHAN_INFO_ENABLE:
> +		switch (chan->channel) {
> +		case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
> +			if (ad9910_sw_powerdown_get(st)) {
> +				*val = 0;
> +			} else {
> +				tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
> +				*val = (tmp32 == st->profile);
> +			}
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_FREQUENCY:
> +		switch (chan->channel) {
> +		case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
> +			tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
> +			tmp64 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
> +					  st->reg[AD9910_REG_PROFILE(tmp32)].val64);
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +		tmp64 *= st->data.sysclk_freq_hz;
> +		*val = tmp64 >> 32;
> +		*val2 = ((tmp64 & GENMASK_ULL(31, 0)) * MICRO) >> 32;

Why in this particular case have this outside the switch / case whereas in others
you do the full maths and set inside? I'd put it inside and not worry about slightly
long lines.

> +		return IIO_VAL_INT_PLUS_MICRO;
> +	case IIO_CHAN_INFO_PHASE:
> +		switch (chan->channel) {
> +		case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
> +			tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
> +			tmp64 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK,
> +					  st->reg[AD9910_REG_PROFILE(tmp32)].val64);
> +			tmp32 = (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16;
> +			*val = tmp32 / MICRO;
> +			*val2 = tmp32 % MICRO;
> +			return IIO_VAL_INT_PLUS_MICRO;
> +		default:
> +			return -EINVAL;
> +		}
> +	case IIO_CHAN_INFO_SCALE:
> +		switch (chan->channel) {
> +		case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
> +			tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
> +			tmp64 = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK,
> +					  st->reg[AD9910_REG_PROFILE(tmp32)].val64);
> +			*val = 0;
> +			*val2 = tmp64 * MICRO >> 14;
> +			return IIO_VAL_INT_PLUS_MICRO;
> +		default:
> +			return -EINVAL;
> +		}
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		switch (chan->channel) {
> +		case AD9910_CHANNEL_PHY:
> +			*val = st->data.sysclk_freq_hz;
> +			return IIO_VAL_INT;
> +		default:
> +			return -EINVAL;
> +		}
> +	default:
> +		return -EINVAL;
> +	}
> +}



> +
> +static int ad9910_setup(struct device *dev, struct ad9910_state *st,
> +			struct reset_control *dev_rst)
> +{
> +	int ret;
> +
> +	ret = reset_control_deassert(dev_rst);
> +	if (ret)
> +		return ret;
No need to sleep at all after bringing device out of reset?

Sashiko has reasonably been asking about this in other drivers. If there
is no period needed or it is so quick as to be irrelevant add a comment here.

> +
> +	ret = ad9910_reg32_write(st, AD9910_REG_CFR1,
> +				 AD9910_CFR1_SDIO_INPUT_ONLY_MSK, false);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_add_action_or_reset(dev, ad9910_sw_powerdown_action, st);
> +	if (ret)
> +		return ret;
> +
> +	ret = ad9910_reg32_write(st, AD9910_REG_CFR2,
> +				 AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK |
> +				 AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK |
> +				 AD9910_CFR2_DRG_NO_DWELL_MSK |
> +				 AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK |
> +				 AD9910_CFR2_SYNC_CLK_EN_MSK |
> +				 AD9910_CFR2_PDCLK_ENABLE_MSK, false);
> +	if (ret)
> +		return ret;
> +
> +	ret = ad9910_cfg_sysclk(st, false);
> +	if (ret)
> +		return ret;
> +
> +	ret = ad9910_set_dac_current(st, false);
> +	if (ret)
> +		return ret;
> +
> +	return ad9910_io_update(st);
> +}


^ permalink raw reply

* Re: [PATCH v13 00/12] ADF41513/ADF41510 PLL frequency synthesizers
From: Jonathan Cameron @ 2026-05-17 14:08 UTC (permalink / raw)
  To: Rodrigo Alencar via B4 Relay
  Cc: rodrigo.alencar, linux-kernel, linux-iio, devicetree, linux-doc,
	David Lechner, Andy Shevchenko, Lars-Peter Clausen,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet, Andrew Morton, Petr Mladek, Steven Rostedt,
	Andy Shevchenko, Rasmus Villemoes, Sergey Senozhatsky, Shuah Khan,
	Krzysztof Kozlowski
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>

On Sun, 17 May 2026 10:13:55 +0100
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:

> This patch series adds support for the Analog Devices ADF41513 and ADF41510
> ultralow noise PLL frequency synthesizers. These devices are designed for
> implementing local oscillators (LOs) in high-frequency applications.
> The ADF41513 covers frequencies from 1 GHz to 26.5 GHz, while the ADF41510
> operates from 1 GHz to 10 GHz.
> 
> Key features supported by this driver:
> - Integer-N and fractional-N operation modes
> - High maximum PFD frequency (250 MHz integer-N, 125 MHz fractional-N)
> - 25-bit fixed modulus or 49-bit variable modulus fractional modes
> - Digital lock detect functionality
> - Phase resync capability for consistent output phase
> - Load Enable vs Reference signal syncronization
> 
> The series includes:
> 1. PLL driver implementation
> 2. Device tree bindings documentation
> 3. IIO ABI documentation
> 
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
I took a brief look and other than the few things you've called out
already and trivial formatting stuff I'd ideally like tweaked, looks
good to me.  Fingers crossed for v14 once others have had a chance
to look at v13.

Thanks,

Jonathan

^ permalink raw reply

* Re: [PATCH v13 08/12] iio: frequency: adf41513: driver implementation
From: Jonathan Cameron @ 2026-05-17 14:05 UTC (permalink / raw)
  To: Rodrigo Alencar via B4 Relay
  Cc: rodrigo.alencar, linux-kernel, linux-iio, devicetree, linux-doc,
	David Lechner, Andy Shevchenko, Lars-Peter Clausen,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet, Andrew Morton, Petr Mladek, Steven Rostedt,
	Andy Shevchenko, Rasmus Villemoes, Sergey Senozhatsky, Shuah Khan
In-Reply-To: <20260517-adf41513-iio-driver-v13-8-bb6e134a360f@analog.com>

On Sun, 17 May 2026 10:14:03 +0100
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:

> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> The driver is based on existing PLL drivers in the IIO subsystem and
> implements the following key features:
> 
> - Integer-N and fractional-N (fixed/variable modulus) synthesis modes;
> - High-resolution frequency calculations using microhertz (µHz) precision
>   to handle sub-Hz resolution across multi-GHz frequency ranges;
> - IIO debugfs interface for direct register access;
> - FW property parsing from devicetree including charge pump settings and
>   reference path configuration;
> - Power management support with suspend/resume callbacks;
> - Lock detect GPIO monitoring.
> 
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
Hi Rodrigo

Took another brief look. Seeing as you'll be doing a v14:

- check the headers don't need updates as I spotted one I think should be there.
- the named initializer for struct spi_device_id thing is just to preempt some 
  future cleanup.
 
> diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile
> index 70d0e0b70e80..53b4d01414d8 100644
> --- a/drivers/iio/frequency/Makefile
> +++ b/drivers/iio/frequency/Makefile
> @@ -5,6 +5,7 @@
>  
>  # When adding new entries keep the list in alphabetical order
>  obj-$(CONFIG_AD9523) += ad9523.o
> +obj-$(CONFIG_ADF41513) += adf41513.o
>  obj-$(CONFIG_ADF4350) += adf4350.o
>  obj-$(CONFIG_ADF4371) += adf4371.o
>  obj-$(CONFIG_ADF4377) += adf4377.o
> diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c
> new file mode 100644
> index 000000000000..20ea7e82818f
> --- /dev/null
> +++ b/drivers/iio/frequency/adf41513.c
> @@ -0,0 +1,1106 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * ADF41513 SPI PLL Frequency Synthesizer driver
> + *
> + * Copyright 2026 Analog Devices Inc.
> + */
> +
Give this another look.
array_size.h is missing for instance
That seems to be the most common one people miss so is my smoke
test for whether includes are probably correct.

> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/log2.h>
> +#include <linux/math64.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/property.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>
> +#include <linux/units.h>


> +
> +static const struct spi_device_id adf41513_id[] = {
> +	{"adf41510", (kernel_ulong_t)&adf41510_chip_info},
	{ "adf41510", (kernel_ulong_t)&adf41510_chip_info  },

Though better still (and this is a recent thing given some of the work Uwe
is doing) - given you are respinning please use named initializers like
you do already for of_device_id.

It might save us a patch in the future.

Thanks,

Jonathan

> +	{"adf41513", (kernel_ulong_t)&adf41513_chip_info},
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(spi, adf41513_id);


^ permalink raw reply

* Re: [PATCH v13 07/12] iio: test: iio-test-format: add test case for decimal format
From: Jonathan Cameron @ 2026-05-17 13:56 UTC (permalink / raw)
  To: Rodrigo Alencar via B4 Relay
  Cc: rodrigo.alencar, linux-kernel, linux-iio, devicetree, linux-doc,
	David Lechner, Andy Shevchenko, Lars-Peter Clausen,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet, Andrew Morton, Petr Mladek, Steven Rostedt,
	Andy Shevchenko, Rasmus Villemoes, Sergey Senozhatsky, Shuah Khan
In-Reply-To: <20260517-adf41513-iio-driver-v13-7-bb6e134a360f@analog.com>

On Sun, 17 May 2026 10:14:02 +0100
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:

> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> Add iio_test_iio_format_value_decimal_64() kunit test case for decimal
> value formatting, exploring different scales types. Also, the same
> iio_val_s64_array_populate() macro used to populate local array is used in
> iio_test_iio_format_value_integer_64().
Sashiko calls out that this needs an update. Doesn't seem to use iio_val_s64_array_populate()

No other comment. Context left so anyone reading this can check for themselves
> 
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
> ---
>  drivers/iio/test/iio-test-format.c | 97 +++++++++++++++++++++++++++++---------
>  1 file changed, 75 insertions(+), 22 deletions(-)
> 
> diff --git a/drivers/iio/test/iio-test-format.c b/drivers/iio/test/iio-test-format.c
> index 872dd8582003..1920dee3bfb0 100644
> --- a/drivers/iio/test/iio-test-format.c
> +++ b/drivers/iio/test/iio-test-format.c
> @@ -200,56 +200,108 @@ static void iio_test_iio_format_value_multiple(struct kunit *test)
>  static void iio_test_iio_format_value_integer_64(struct kunit *test)
>  {
>  	int values[2];
> -	s64 value;
>  	char *buf;
>  	int ret;
>  
>  	buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL);
>  	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
>  
> -	value = 24;
> -	values[0] = lower_32_bits(value);
> -	values[1] = upper_32_bits(value);
> +	iio_val_s64_to_s32s(24, values);
>  	ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
>  	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "24\n");
>  
> -	value = -24;
> -	values[0] = lower_32_bits(value);
> -	values[1] = upper_32_bits(value);
> +	iio_val_s64_to_s32s(-24, values);
>  	ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
>  	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-24\n");
>  
> -	value = 0;
> -	values[0] = lower_32_bits(value);
> -	values[1] = upper_32_bits(value);
> +	iio_val_s64_to_s32s(0, values);
>  	ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
>  	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0\n");
>  
> -	value = UINT_MAX;
> -	values[0] = lower_32_bits(value);
> -	values[1] = upper_32_bits(value);
> +	iio_val_s64_to_s32s(UINT_MAX, values);
>  	ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
>  	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "4294967295\n");
>  
> -	value = -((s64)UINT_MAX);
> -	values[0] = lower_32_bits(value);
> -	values[1] = upper_32_bits(value);
> +	iio_val_s64_to_s32s(-((s64)UINT_MAX), values);
>  	ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
>  	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-4294967295\n");
>  
> -	value = LLONG_MAX;
> -	values[0] = lower_32_bits(value);
> -	values[1] = upper_32_bits(value);
> +	iio_val_s64_to_s32s(LLONG_MAX, values);
>  	ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
>  	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036854775807\n");
>  
> -	value = LLONG_MIN;
> -	values[0] = lower_32_bits(value);
> -	values[1] = upper_32_bits(value);
> +	iio_val_s64_to_s32s(LLONG_MIN, values);
>  	ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
>  	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036854775808\n");
>  }
>  
> +static void iio_test_iio_format_value_decimal_64(struct kunit *test)
> +{
> +	int values[2];
> +	char *buf;
> +	int ret;
> +
> +	buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
> +
> +	/* DECIMAL64_MILLI: positive >= 1, value 1.234 */
> +	iio_val_s64_to_s32s(1234, values);
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1.234\n");
> +
> +	/* DECIMAL64_MICRO: positive >= 1, value 3.141592 */
> +	iio_val_s64_to_s32s(3141592, values);
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "3.141592\n");
> +
> +	/* DECIMAL64_MILLI: positive < 1, value 0.042 */
> +	iio_val_s64_to_s32s(42, values);
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.042\n");
> +
> +	/* DECIMAL64_MILLI: negative <= -1, value -1.234 */
> +	iio_val_s64_to_s32s(-1234, values);
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-1.234\n");
> +
> +	/* DECIMAL64_MILLI: negative > -1, value -0.123 */
> +	iio_val_s64_to_s32s(-123, values);
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-0.123\n");
> +
> +	/* DECIMAL64_MILLI: zero */
> +	iio_val_s64_to_s32s(0, values);
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.000\n");
> +
> +	/* DECIMAL64_NANO: value 1.000000001 */
> +	iio_val_s64_to_s32s(1000000001, values);
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1.000000001\n");
> +
> +	/* DECIMAL64_MICRO: large value using upper 32 bits */
> +	iio_val_s64_to_s32s(5000000000000042LL, values);
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "5000000000.000042\n");
> +
> +	/* limits */
> +	iio_val_s64_to_s32s(LLONG_MAX, values);
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_PICO, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372.036854775807\n");
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036.854775807\n");
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036854.775807\n");
> +
> +	iio_val_s64_to_s32s(LLONG_MIN, values);
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_PICO, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372.036854775808\n");
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036.854775808\n");
> +	ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
> +	IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036854.775808\n");
> +}
> +
>  static struct kunit_case iio_format_test_cases[] = {
>  		KUNIT_CASE(iio_test_iio_format_value_integer),
>  		KUNIT_CASE(iio_test_iio_format_value_fixedpoint),
> @@ -257,6 +309,7 @@ static struct kunit_case iio_format_test_cases[] = {
>  		KUNIT_CASE(iio_test_iio_format_value_fractional_log2),
>  		KUNIT_CASE(iio_test_iio_format_value_multiple),
>  		KUNIT_CASE(iio_test_iio_format_value_integer_64),
> +		KUNIT_CASE(iio_test_iio_format_value_decimal_64),
>  		{ }
>  };
>  
> 


^ permalink raw reply

* Re: [PATCH v13 02/12] iio: kstrtox: add local _parse_integer_limit_init() helper
From: Jonathan Cameron @ 2026-05-17 13:53 UTC (permalink / raw)
  To: Rodrigo Alencar via B4 Relay
  Cc: rodrigo.alencar, linux-kernel, linux-iio, devicetree, linux-doc,
	David Lechner, Andy Shevchenko, Lars-Peter Clausen,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet, Andrew Morton, Petr Mladek, Steven Rostedt,
	Andy Shevchenko, Rasmus Villemoes, Sergey Senozhatsky, Shuah Khan
In-Reply-To: <20260517-adf41513-iio-driver-v13-2-bb6e134a360f@analog.com>

On Sun, 17 May 2026 10:13:57 +0100
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:

> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> Add parsing helper that accepts an initial value for the accumulated
> result when parsing an 64-bit integer. It reuses current implementation
> for _parse_integer_limit(), which now consumes the new function with
> init = 0. The diff algorithm would have the documentation header and
> prototype of _parse_integer_limit() moved around so it is adjusted
> according to guidelines.
> 
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
Sashiko makes a good point on this. It is not currently IIO specific
so the patch title should make that clear.

kstrox: add local_parse_integer_limit_init() helper.

> ---
>  lib/kstrtox.c | 39 ++++++++++++++++++++++++++-------------
>  1 file changed, 26 insertions(+), 13 deletions(-)
> 
> diff --git a/lib/kstrtox.c b/lib/kstrtox.c
> index 97be2a39f537..0705461f51c0 100644
> --- a/lib/kstrtox.c
> +++ b/lib/kstrtox.c
> @@ -39,23 +39,15 @@ const char *_parse_integer_fixup_radix(const char *s, unsigned int *base)
>  	return s;
>  }
>  
> -/*
> - * Convert non-negative integer string representation in explicitly given radix
> - * to an integer. A maximum of max_chars characters will be converted.
> - *
> - * Return number of characters consumed maybe or-ed with overflow bit.
> - * If overflow occurs, result integer (incorrect) is still returned.
> - *
> - * Don't you dare use this function.
> - */
> -noinline
> -unsigned int _parse_integer_limit(const char *s, unsigned int base, unsigned long long *p,
> -				  size_t max_chars)
> +static unsigned int _parse_integer_limit_init(const char *s, unsigned int base,
> +					      unsigned long long init,
> +					      unsigned long long *p,
> +					      size_t max_chars)
>  {
>  	unsigned long long res;
>  	unsigned int rv;
>  
> -	res = 0;
> +	res = init;
>  	rv = 0;
>  	while (max_chars--) {
>  		unsigned int c = *s;
> @@ -87,6 +79,27 @@ unsigned int _parse_integer_limit(const char *s, unsigned int base, unsigned lon
>  	return rv;
>  }
>  
> +/**
> + * _parse_integer_limit() - Convert integer string representation to an integer
> + *			    limiting the number of characters parsed.
> + * @s: The start of the string.
> + * @base: The number base to use.
> + * @p: Where to write the result of the conversion.
> + * @max_chars: Maximum amount of characters to consume.
> + *
> + * Convert non-negative integer string representation in explicitly given radix
> + * to an integer. A maximum of max_chars characters will be converted.
> + *
> + * Return: Number of characters consumed maybe or-ed with overflow bit.
> + *	   If overflow occurs, result integer (incorrect) is still returned.
> + */
> +noinline
> +unsigned int _parse_integer_limit(const char *s, unsigned int base,
> +				  unsigned long long *p, size_t max_chars)
> +{
> +	return _parse_integer_limit_init(s, base, 0, p, max_chars);
> +}
> +
>  noinline
>  unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *p)
>  {
> 


^ permalink raw reply

* [PATCH v3] killswitch: add per-function short-circuit mitigation primitive
From: Sasha Levin @ 2026-05-17 13:48 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-doc, linux-kselftest, bpf, live-patching,
	Greg Kroah-Hartman, Andrew Morton, Jonathan Corbet,
	Mathieu Desnoyers, Joshua Peisach, Florian Weimer, Breno Leitao,
	Anthony Iliopoulos, Michal Hocko, Jiri Olsa, Sasha Levin
In-Reply-To: <20260508195749.1885522-1-sashal@kernel.org>

When a kernel (security) issue goes public, fleets stay exposed until a patched
kernel is built, distributed, and rebooted into.

For many such issues the simplest mitigation is to stop calling the buggy
function. Killswitch provides that. An admin writes:

    echo "engage af_alg_sendmsg -1" \
        > /sys/kernel/security/killswitch/control

After this, af_alg_sendmsg() returns -EPERM on every call without
running its body. The mitigation takes effect immediately, and is dropped on
the next reboot -- by which point a patched kernel is hopefully in place.

A lot of recent kernel issues sit in code paths most installs only have enabled
to support a relative minority of users: AF_ALG, ksmbd, nf_tables, vsock, ax25,
and friends.

For most users, the cost of "this socket family stops working for the day" is
much smaller than the cost of running a known vulnerable kernel until the fix
lands.

Why not an existing facility:

* livepatch needs a built, signed, per-kernel-version module per CVE.
  Under Secure Boot the operator can't sign their own, so they wait
  for the vendor, and only a minority of vendors actually ship
  livepatches. Killswitch covers the days before that module shows
  up.

* fail_function (CONFIG_FUNCTION_ERROR_INJECTION) is disabled in
  most production kernels. Even where enabled, it only works on
  functions pre-annotated with ALLOW_ERROR_INJECTION() in source -
  no help for a freshly-disclosed CVE. The debugfs UI is blocked by
  lockdown=integrity and the override is probabilistic.

* BPF override (bpf_override_return) honors the same
  ALLOW_ERROR_INJECTION() whitelist, and BPF itself is off in many
  production kernels. Even where on, the operator interface is
  "load a verified BPF program," not a one-line write.

* Module blacklist only helps when the bug is in a loadable module.

Killswitch fills the gap: write a symbol to securityfs, function
returns the chosen value until disengage or reboot.

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

Changes since v2:
- Fix LLVM=1 build: gate __noipa__ on __has_attribute() (Breno)
- Admin guide: do-not-engage list, pre-soak workflow, relation to
  livepatch/fail_function/BPF (Michal, Mathieu, Joshua)
- Add CVE-2026-43284 (esp_input) worked example + netns selftest
- Drop unused [reason] token from Kconfig help and cmdline comment
- Commit message: spell out why livepatch / fail_function / BPF
  override / module-blacklist don't cover this window.

 Documentation/admin-guide/index.rst           |   1 +
 Documentation/admin-guide/killswitch.rst      | 229 +++++
 Documentation/admin-guide/tainted-kernels.rst |   8 +
 MAINTAINERS                                   |  11 +
 include/linux/killswitch.h                    |  19 +
 include/linux/panic.h                         |   3 +-
 include/linux/security.h                      |   1 +
 init/Kconfig                                  |   2 +
 kernel/Kconfig.killswitch                     |  31 +
 kernel/Makefile                               |   1 +
 kernel/killswitch.c                           | 863 ++++++++++++++++++
 kernel/panic.c                                |   1 +
 lib/Kconfig.debug                             |  13 +
 lib/Makefile                                  |   1 +
 lib/test_killswitch.c                         |  85 ++
 security/security.c                           |   1 +
 tools/testing/selftests/Makefile              |   1 +
 tools/testing/selftests/killswitch/.gitignore |   1 +
 tools/testing/selftests/killswitch/Makefile   |   8 +
 .../selftests/killswitch/cve_31431_test.c     | 162 ++++
 .../selftests/killswitch/cve_43284_test.c     |  88 ++
 .../selftests/killswitch/killswitch_test.sh   | 254 ++++++
 22 files changed, 1783 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/admin-guide/killswitch.rst
 create mode 100644 include/linux/killswitch.h
 create mode 100644 kernel/Kconfig.killswitch
 create mode 100644 kernel/killswitch.c
 create mode 100644 lib/test_killswitch.c
 create mode 100644 tools/testing/selftests/killswitch/.gitignore
 create mode 100644 tools/testing/selftests/killswitch/Makefile
 create mode 100644 tools/testing/selftests/killswitch/cve_31431_test.c
 create mode 100644 tools/testing/selftests/killswitch/cve_43284_test.c
 create mode 100755 tools/testing/selftests/killswitch/killswitch_test.sh

diff --git a/Documentation/admin-guide/index.rst b/Documentation/admin-guide/index.rst
index cd28dfe91b060..ca37dd70f108d 100644
--- a/Documentation/admin-guide/index.rst
+++ b/Documentation/admin-guide/index.rst
@@ -70,6 +70,7 @@ problems and bugs in particular.
    bug-hunting
    bug-bisect
    tainted-kernels
+   killswitch
    ramoops
    dynamic-debug-howto
    init
diff --git a/Documentation/admin-guide/killswitch.rst b/Documentation/admin-guide/killswitch.rst
new file mode 100644
index 0000000000000..a524cc9ee23ca
--- /dev/null
+++ b/Documentation/admin-guide/killswitch.rst
@@ -0,0 +1,229 @@
+.. SPDX-License-Identifier: GPL-2.0
+..
+.. Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+
+============
+Killswitch
+============
+
+Killswitch lets a privileged operator make a chosen kernel function
+return a fixed value without executing its body, as a temporary
+mitigation for a security bug while a real fix is being prepared.
+
+The function returns the operator-supplied value and nothing else
+runs in its place. There is no allowlist, no return-type check; if
+the kprobe layer accepts the symbol, killswitch engages it. Once
+engaged, the change is in effect on every CPU until ``disengage`` is
+written or the system reboots.
+
+Configuration
+=============
+
+``CONFIG_KILLSWITCH``
+  Enables the feature. Depends on ``SECURITYFS``, ``KPROBES`` (with
+  ftrace support), and ``FUNCTION_ERROR_INJECTION``.
+
+The interface
+=============
+
+::
+
+    /sys/kernel/security/killswitch/
+        engaged                 RO  currently-engaged functions
+        control                 WO  command sink
+        taint                   RO  0 or 1
+        fn/<name>/              per-function directory, created on engage
+            retval              RW  return value
+            hits                RO  per-cpu summed call count
+
+Three commands are accepted by ``control``::
+
+    engage <symbol> <retval>
+    disengage <symbol>
+    disengage_all
+
+Each engage and disengage emits a single ``KERN_WARNING`` line to
+dmesg with the symbol, retval, hit count (on disengage), and the
+operator's identity (uid/auid/sessionid/comm, or ``source=cmdline``).
+
+Engagement is rejected when:
+
+* the symbol is unknown, in a non-traceable section, on the kprobe
+  blacklist, or otherwise refused by ``register_kprobe`` (the error
+  from the kprobe layer is logged and returned to userspace);
+* the symbol is already engaged (``-EBUSY``);
+* the operator does not hold ``CAP_SYS_ADMIN``.
+
+Whatever value the operator writes is what the function returns.
+Writing the wrong type or wrong value lands in the caller as-is.
+
+Boot parameter
+==============
+
+``killswitch=fn1=<val>,fn2=<val>,...``
+
+Parsed early; engagements are applied at the end of kernel init
+once the kprobe subsystem is up. Parse failures emit a warning and
+skip the offending entry; they never panic.
+
+Useful for fleet rollout: when an issue drops, ship the mitigation
+in the bootloader / PXE config and roll the fleet through reboots
+while the real fix is being prepared.
+
+Tainting
+========
+
+The first successful engagement (runtime or boot-time) sets
+``TAINT_KILLSWITCH`` (bit 20, char ``H``). The taint persists across
+``disengage`` until reboot, so an oops on a killswitch-modified
+kernel is identifiable from the banner: ``Tainted: ... H`` tells a
+maintainer to consult ``engaged`` before further triage.
+
+Module unload
+=============
+
+If a module containing an engaged target is unloaded, killswitch
+auto-disengages the entry and emits a ``KERN_WARNING`` so the loss
+of mitigation is visible. Reloading the module does not silently
+re-arm the killswitch; the operator re-engages explicitly.
+
+Choosing the right target
+=========================
+
+A function that *looks* skippable may be relied on by callers for a
+side effect (a lock the caller releases, a refcount the caller
+drops, a scatterlist the caller consumes). The rule of thumb:
+
+  Pick the **highest-level** entry point that contains the bug.
+
+That gives callers no chance to dereference half-initialised state
+from a function whose body was skipped. Two illustrative examples
+from ``crypto/af_alg.c``:
+
+Anti-pattern: ``af_alg_count_tsgl``
+-----------------------------------
+
+``af_alg_count_tsgl()`` returns ``unsigned int`` (the number of TX
+SG entries). Engaging it with retval ``0`` causes the caller in
+``algif_aead.c`` to allocate a 1-entry scatterlist (its
+``if (!entries) entries = 1`` guard) and then walk the *real* TX
+SGL into that undersized destination via ``af_alg_pull_tsgl``,
+producing out-of-bounds writes. **Killswitching here introduces a
+worse bug than the one being mitigated.**
+
+Anti-pattern: ``af_alg_pull_tsgl``
+----------------------------------
+
+``af_alg_pull_tsgl()`` returns ``void``, so any retval is accepted.
+But its caller depends on the per-request SGL being filled in.
+Skipping the body leaves the per-request SGL with NULL pages; the
+next-stage ``memcpy_sglist`` dereferences them and the kernel
+oopses.
+
+Correct pattern: ``af_alg_sendmsg``
+-----------------------------------
+
+``af_alg_sendmsg()`` is the highest-level entry into the AF_ALG
+send path. Engaging it with retval ``-EPERM`` causes every send
+attempt to return -EPERM to userspace; no caller ever sees
+half-initialised state, and any AF_ALG-reachable bug downstream of
+``sendmsg`` is unreachable until the killswitch is disengaged.
+
+The canonical pattern: pick a syscall-handler-shaped function whose
+return value already encodes "this operation didn't happen", and
+let userspace handle the error as it would any other failed
+syscall.
+
+Correct pattern: ``esp_input`` (CVE-2026-43284)
+-----------------------------------------------
+
+The IPsec ESP receive-path bug fixed by ``xfrm: esp: avoid in-place
+decrypt on shared skb frags`` is reachable through ``esp_input()``
+in ``net/ipv4/esp4.c`` (and ``esp6_input()`` for IPv6). Engage these
+with retval ``-EINVAL``:
+
+::
+
+    echo "engage esp_input -22"  > /sys/kernel/security/killswitch/control
+    echo "engage esp6_input -22" > /sys/kernel/security/killswitch/control
+
+Inbound ESP packets are then dropped before decapsulation, neutering
+any bug downstream of the ESP receive path. IPsec tunnels stop
+working; other networking is unaffected.
+
+Do not engage
+=============
+
+Do not killswitch:
+
+* process or memory primitives the rest of the kernel needs to
+  function: ``fork``, ``do_exit``, ``__alloc_pages``, ``kmalloc``,
+  ``schedule``, anything in ``mm/`` reached by every allocation.
+* hot paths in the scheduler, timekeeping, RCU, or interrupt entry.
+* functions invoked from the killswitch path itself (``securityfs``,
+  ``lockdown``, ``audit``, ``kprobe`` registration) -- the system
+  may livelock or refuse to disengage.
+* functions whose return value is read structurally (size, count,
+  pointer-to-allocated-thing) rather than as success/failure.
+  See the AF_ALG anti-patterns above for what goes wrong.
+
+When in doubt, measure first.
+
+Pre-soak before engaging
+========================
+
+If the target's call rate is unknown, attach a counter for a few
+seconds first. With perf::
+
+    perf probe --add 'esp_input'
+    perf stat -a -e probe:esp_input -- sleep 5
+
+Or with bpftrace::
+
+    bpftrace -e 'kprobe:esp_input { @hits = count(); } interval:s:5 { exit(); }'
+
+A target with ten thousand hits per second is not a candidate -- the
+kernel will not survive five seconds with that path returning a
+fixed error.
+
+Relation to other facilities
+============================
+
+* ``CONFIG_FUNCTION_ERROR_INJECTION`` provides the same architecture
+  trampoline (``override_function_with_return``), which killswitch
+  reuses. fail_function is debug-oriented: targets must be
+  pre-annotated with ``ALLOW_ERROR_INJECTION()`` in source, the
+  override is probabilistic, and the interface is on debugfs (blocked
+  under ``lockdown=integrity``). Killswitch is the production cousin:
+  no whitelist, deterministic, securityfs-visible under integrity
+  lockdown, with audit and taint.
+* livepatch can do everything killswitch can and more, at the cost
+  of building, signing, and shipping a kernel module per mitigation.
+  Killswitch is for the window before that module exists.
+* BPF override (``bpf_override_return``) needs a BPF program and
+  ``CONFIG_BPF_KPROBE_OVERRIDE``; appropriate when the policy is
+  conditional, overkill for "always return -EPERM".
+
+Safety notes
+============
+
+* In-flight calls during ``write()`` to ``control`` may run either
+  the original body or the override. The override is ``return X``,
+  which has no preconditions to violate.
+* SMP visibility comes from ``text_poke_bp()``. ``write()`` to
+  ``control`` returns only after every CPU sees the new path.
+* The ftrace ops unregister waits for in-flight pre-handlers, so
+  freeing the engagement attribute on disengage is safe.
+* Inline functions, freed ``__init`` symbols, and anything compiled
+  away cannot be killswitched. ``register_kprobe`` rejects them
+  with whatever error the kprobe layer chooses.
+
+Diagnostics
+===========
+
+Per-call hits are aggregated in a per-cpu counter readable at
+``/sys/kernel/security/killswitch/fn/<name>/hits``. Per-hit logging
+is not provided to avoid log storms on hot paths.
+
+A ``KILLSWITCH`` entry appears in the kernel taint vector once any
+engagement succeeds (also visible as ``H`` in the oops banner).
diff --git a/Documentation/admin-guide/tainted-kernels.rst b/Documentation/admin-guide/tainted-kernels.rst
index 9ead927a37c0f..71a6e3364eddc 100644
--- a/Documentation/admin-guide/tainted-kernels.rst
+++ b/Documentation/admin-guide/tainted-kernels.rst
@@ -102,6 +102,7 @@ Bit  Log  Number  Reason that got the kernel tainted
  17  _/T  131072  kernel was built with the struct randomization plugin
  18  _/N  262144  an in-kernel test has been run
  19  _/J  524288  userspace used a mutating debug operation in fwctl
+ 20  _/H 1048576  killswitch override engaged (function short-circuited)
 ===  ===  ======  ========================================================
 
 Note: The character ``_`` is representing a blank in this table to make reading
@@ -189,3 +190,10 @@ More detailed explanation for tainting
  19) ``J`` if userspace opened /dev/fwctl/* and performed a FWTCL_RPC_DEBUG_WRITE
      to use the devices debugging features. Device debugging features could
      cause the device to malfunction in undefined ways.
+
+ 20) ``H`` if the killswitch primitive (see
+     Documentation/admin-guide/killswitch.rst) has been engaged on at least
+     one function. The kernel is no longer running its source: at least one
+     function has been short-circuited to return a fixed value. The taint
+     persists across ``disengage`` until the next reboot — once the running
+     image has been modified, oops triage must reflect that.
diff --git a/MAINTAINERS b/MAINTAINERS
index b2040011a3865..b4005b61d444f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14350,6 +14350,17 @@ F:	lib/Kconfig.kmsan
 F:	mm/kmsan/
 F:	scripts/Makefile.kmsan
 
+KILLSWITCH (function short-circuit mitigation)
+M:	Sasha Levin <sashal@kernel.org>
+L:	linux-kernel@vger.kernel.org
+S:	Maintained
+F:	Documentation/admin-guide/killswitch.rst
+F:	include/linux/killswitch.h
+F:	kernel/Kconfig.killswitch
+F:	kernel/killswitch.c
+F:	lib/test_killswitch.c
+F:	tools/testing/selftests/killswitch/
+
 KPROBES
 M:	Naveen N Rao <naveen@kernel.org>
 M:	"David S. Miller" <davem@davemloft.net>
diff --git a/include/linux/killswitch.h b/include/linux/killswitch.h
new file mode 100644
index 0000000000000..3fad49e180ddf
--- /dev/null
+++ b/include/linux/killswitch.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+ */
+#ifndef _LINUX_KILLSWITCH_H
+#define _LINUX_KILLSWITCH_H
+
+#ifdef CONFIG_KILLSWITCH
+int killswitch_engage(const char *symbol, long retval);
+int killswitch_disengage(const char *symbol);
+bool killswitch_is_engaged(const char *symbol);
+#else
+static inline int killswitch_engage(const char *symbol, long retval)
+{ return -EOPNOTSUPP; }
+static inline int killswitch_disengage(const char *symbol) { return -EOPNOTSUPP; }
+static inline bool killswitch_is_engaged(const char *symbol) { return false; }
+#endif
+
+#endif /* _LINUX_KILLSWITCH_H */
diff --git a/include/linux/panic.h b/include/linux/panic.h
index f1dd417e54b29..6699261a61f13 100644
--- a/include/linux/panic.h
+++ b/include/linux/panic.h
@@ -88,7 +88,8 @@ static inline void set_arch_panic_timeout(int timeout, int arch_default_timeout)
 #define TAINT_RANDSTRUCT		17
 #define TAINT_TEST			18
 #define TAINT_FWCTL			19
-#define TAINT_FLAGS_COUNT		20
+#define TAINT_KILLSWITCH		20
+#define TAINT_FLAGS_COUNT		21
 #define TAINT_FLAGS_MAX			((1UL << TAINT_FLAGS_COUNT) - 1)
 
 struct taint_flag {
diff --git a/include/linux/security.h b/include/linux/security.h
index 41d7367cf4036..038027c33ba1a 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -146,6 +146,7 @@ enum lockdown_reason {
 	LOCKDOWN_DBG_WRITE_KERNEL,
 	LOCKDOWN_RTAS_ERROR_INJECTION,
 	LOCKDOWN_XEN_USER_ACTIONS,
+	LOCKDOWN_KILLSWITCH,
 	LOCKDOWN_INTEGRITY_MAX,
 	LOCKDOWN_KCORE,
 	LOCKDOWN_KPROBES,
diff --git a/init/Kconfig b/init/Kconfig
index 2937c4d308aec..5368dd4b5c65b 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -2278,6 +2278,8 @@ config ASN1
 
 source "kernel/Kconfig.locks"
 
+source "kernel/Kconfig.killswitch"
+
 config ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE
 	bool
 
diff --git a/kernel/Kconfig.killswitch b/kernel/Kconfig.killswitch
new file mode 100644
index 0000000000000..a33f7ecb2861e
--- /dev/null
+++ b/kernel/Kconfig.killswitch
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Killswitch: per-function short-circuit mitigation primitive.
+#
+# Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+#
+
+config KILLSWITCH
+	bool "Killswitch: short-circuit a kernel function as a CVE mitigation"
+	depends on SECURITYFS
+	depends on KPROBES && HAVE_KPROBES_ON_FTRACE
+	depends on HAVE_FUNCTION_ERROR_INJECTION
+	select FUNCTION_ERROR_INJECTION
+	help
+	  Provide an admin-facing mechanism to make a chosen kernel function
+	  return a fixed value without executing its body, as a temporary
+	  mitigation for a security bug before a real fix is available.
+
+	  Operators write "engage <symbol> <retval>" to
+	  /sys/kernel/security/killswitch/control. The function entry is
+	  redirected via a kprobe whose pre-handler sets the chosen return
+	  value and short-circuits the call. There is no allowlist,
+	  denylist, or return-type validation: if the kprobe layer accepts
+	  the symbol the engagement proceeds, otherwise its error is
+	  returned to userspace.
+
+	  This is *not* livepatch: there is no replacement implementation,
+	  the function simply returns the chosen value. Engaging a killswitch
+	  taints the kernel (TAINT_KILLSWITCH, 'H'). Requires CAP_SYS_ADMIN.
+
+	  If unsure, say N.
diff --git a/kernel/Makefile b/kernel/Makefile
index 6785982013dce..b3e408d9f275e 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -100,6 +100,7 @@ obj-$(CONFIG_GCOV_KERNEL) += gcov/
 obj-$(CONFIG_KCOV) += kcov.o
 obj-$(CONFIG_KPROBES) += kprobes.o
 obj-$(CONFIG_FAIL_FUNCTION) += fail_function.o
+obj-$(CONFIG_KILLSWITCH) += killswitch.o
 obj-$(CONFIG_KGDB) += debug/
 obj-$(CONFIG_DETECT_HUNG_TASK) += hung_task.o
 obj-$(CONFIG_LOCKUP_DETECTOR) += watchdog.o
diff --git a/kernel/killswitch.c b/kernel/killswitch.c
new file mode 100644
index 0000000000000..7f509c62ea748
--- /dev/null
+++ b/kernel/killswitch.c
@@ -0,0 +1,863 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Per-function short-circuit mitigation.
+ *
+ * Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+ *
+ * Engaging a killswitch installs a kprobe at the function's entry
+ * whose pre-handler sets the return register and skips the body via
+ * override_function_with_return().  Operator interface lives at
+ * /sys/kernel/security/killswitch/.
+ */
+
+#include <linux/audit.h>
+#include <linux/capability.h>
+#include <linux/cred.h>
+#include <linux/ctype.h>
+#include <linux/error-injection.h>
+#include <linux/init.h>
+#include <linux/killswitch.h>
+#include <linux/kprobes.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/panic.h>
+#include <linux/percpu.h>
+#include <linux/printk.h>
+#include <linux/sched.h>
+#include <linux/security.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/uidgid.h>
+
+struct ks_attr {
+	struct list_head	list;
+	struct kprobe		kp;
+	/* atomic so a writer racing an in-flight call can't tear the long. */
+	atomic_long_t		retval;
+	/* false once disengaged; per-fn file ops then return -EIDRM. */
+	bool			engaged;
+	unsigned long __percpu	*hits;
+	struct dentry		*dir;
+	/* engaged_list holds one ref; each open per-fn fd holds one. */
+	struct kref		refcnt;
+};
+
+static DEFINE_MUTEX(ks_lock);
+static LIST_HEAD(ks_engaged_list);
+static struct dentry *ks_root_dir;
+static struct dentry *ks_fn_dir;	/* parent for per-fn directories */
+
+/* ------------------------------------------------------------------ *
+ * Pre-handler: the actual override                                   *
+ * ------------------------------------------------------------------ */
+
+static int ks_kprobe_pre_handler(struct kprobe *kp, struct pt_regs *regs)
+{
+	struct ks_attr *attr = container_of(kp, struct ks_attr, kp);
+
+	this_cpu_inc(*attr->hits);
+	regs_set_return_value(regs, (unsigned long)atomic_long_read(&attr->retval));
+	override_function_with_return(regs);
+	return 1;
+}
+NOKPROBE_SYMBOL(ks_kprobe_pre_handler);
+
+/* Defined non-NULL so the kprobe layer keeps the IPMODIFY ops. */
+static void ks_kprobe_post_handler(struct kprobe *kp, struct pt_regs *regs,
+				   unsigned long flags)
+{
+}
+
+/* ------------------------------------------------------------------ *
+ * Attribute lifecycle                                                *
+ * ------------------------------------------------------------------ */
+
+static struct ks_attr *ks_attr_lookup(const char *symbol)
+{
+	struct ks_attr *attr;
+
+	list_for_each_entry(attr, &ks_engaged_list, list)
+		if (!strcmp(attr->kp.symbol_name, symbol))
+			return attr;
+	return NULL;
+}
+
+static unsigned long ks_attr_hits(const struct ks_attr *attr)
+{
+	unsigned long total = 0;
+	int cpu;
+
+	for_each_possible_cpu(cpu)
+		total += *per_cpu_ptr(attr->hits, cpu);
+	return total;
+}
+
+static void ks_attr_destroy(struct ks_attr *attr)
+{
+	if (!attr)
+		return;
+	free_percpu(attr->hits);
+	kfree(attr->kp.symbol_name);
+	kfree(attr);
+}
+
+static void ks_attr_kref_release(struct kref *kref)
+{
+	ks_attr_destroy(container_of(kref, struct ks_attr, refcnt));
+}
+
+static void ks_attr_get(struct ks_attr *attr)
+{
+	kref_get(&attr->refcnt);
+}
+
+static void ks_attr_put(struct ks_attr *attr)
+{
+	kref_put(&attr->refcnt, ks_attr_kref_release);
+}
+
+static struct ks_attr *ks_attr_alloc(const char *symbol)
+{
+	struct ks_attr *attr;
+
+	attr = kzalloc(sizeof(*attr), GFP_KERNEL);
+	if (!attr)
+		return NULL;
+
+	attr->kp.symbol_name = kstrdup(symbol, GFP_KERNEL);
+	if (!attr->kp.symbol_name)
+		goto err;
+
+	attr->hits = alloc_percpu(unsigned long);
+	if (!attr->hits)
+		goto err;
+
+	attr->kp.pre_handler = ks_kprobe_pre_handler;
+	attr->kp.post_handler = ks_kprobe_post_handler;
+	INIT_LIST_HEAD(&attr->list);
+	kref_init(&attr->refcnt);
+	return attr;
+
+err:
+	ks_attr_destroy(attr);
+	return NULL;
+}
+
+/* ------------------------------------------------------------------ *
+ * Securityfs: per-fn attribute files                                 *
+ * ------------------------------------------------------------------ */
+
+/*
+ * Look up by symbol name (the parent dentry's basename) under
+ * ks_lock and confirm attr->dir is the file's parent dentry.  This
+ * binds the fd to the engagement it was opened against and avoids
+ * dereferencing inode->i_private, which a racing disengage may have
+ * freed.  d_parent is stable for the open's lifetime via the file's
+ * dentry reference.
+ */
+static int ks_attr_open(struct inode *inode, struct file *file)
+{
+	struct dentry *parent = file->f_path.dentry->d_parent;
+	const char *name = parent->d_name.name;
+	struct ks_attr *attr;
+
+	mutex_lock(&ks_lock);
+	attr = ks_attr_lookup(name);
+	if (attr && attr->dir == parent)
+		ks_attr_get(attr);
+	else
+		attr = NULL;
+	mutex_unlock(&ks_lock);
+	if (!attr)
+		return -ENOENT;
+	file->private_data = attr;
+	return 0;
+}
+
+static int ks_attr_release(struct inode *inode, struct file *file)
+{
+	ks_attr_put(file->private_data);
+	file->private_data = NULL;
+	return 0;
+}
+
+/* Caller must hold ks_lock. */
+static int ks_attr_check_live(const struct ks_attr *attr)
+{
+	return attr->engaged ? 0 : -EIDRM;
+}
+
+static ssize_t ks_retval_read(struct file *file, char __user *ubuf,
+			      size_t count, loff_t *ppos)
+{
+	struct ks_attr *attr = file->private_data;
+	char buf[32];
+	long val;
+	int ret, len;
+
+	mutex_lock(&ks_lock);
+	ret = ks_attr_check_live(attr);
+	val = atomic_long_read(&attr->retval);
+	mutex_unlock(&ks_lock);
+	if (ret)
+		return ret;
+	len = scnprintf(buf, sizeof(buf), "%ld\n", val);
+	return simple_read_from_buffer(ubuf, count, ppos, buf, len);
+}
+
+static ssize_t ks_retval_write(struct file *file, const char __user *ubuf,
+			       size_t count, loff_t *ppos)
+{
+	struct ks_attr *attr = file->private_data;
+	char buf[32];
+	long val;
+	int ret;
+
+	if (count >= sizeof(buf))
+		return -EINVAL;
+	if (copy_from_user(buf, ubuf, count))
+		return -EFAULT;
+	buf[count] = '\0';
+	strim(buf);
+
+	ret = kstrtol(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	mutex_lock(&ks_lock);
+	ret = ks_attr_check_live(attr);
+	if (!ret)
+		atomic_long_set(&attr->retval, val);
+	mutex_unlock(&ks_lock);
+
+	return ret ? ret : count;
+}
+
+static const struct file_operations ks_retval_fops = {
+	.open		= ks_attr_open,
+	.release	= ks_attr_release,
+	.read		= ks_retval_read,
+	.write	= ks_retval_write,
+	.llseek	= default_llseek,
+};
+
+static ssize_t ks_hits_read(struct file *file, char __user *ubuf,
+			    size_t count, loff_t *ppos)
+{
+	struct ks_attr *attr = file->private_data;
+	char buf[32];
+	unsigned long hits;
+	int ret, len;
+
+	mutex_lock(&ks_lock);
+	ret = ks_attr_check_live(attr);
+	hits = ks_attr_hits(attr);
+	mutex_unlock(&ks_lock);
+	if (ret)
+		return ret;
+	len = scnprintf(buf, sizeof(buf), "%lu\n", hits);
+	return simple_read_from_buffer(ubuf, count, ppos, buf, len);
+}
+
+static const struct file_operations ks_hits_fops = {
+	.open		= ks_attr_open,
+	.release	= ks_attr_release,
+	.read		= ks_hits_read,
+	.llseek		= default_llseek,
+};
+
+static int ks_create_attr_dir(struct ks_attr *attr)
+{
+	struct dentry *d;
+
+	attr->dir = securityfs_create_dir(attr->kp.symbol_name, ks_fn_dir);
+	if (IS_ERR(attr->dir))
+		return PTR_ERR(attr->dir);
+
+	/* ks_attr_open looks the attr up by name; i_private is unused. */
+	d = securityfs_create_file("retval", 0600, attr->dir,
+				   NULL, &ks_retval_fops);
+	if (IS_ERR(d))
+		goto err;
+	d = securityfs_create_file("hits", 0400, attr->dir,
+				   NULL, &ks_hits_fops);
+	if (IS_ERR(d))
+		goto err;
+	return 0;
+err:
+	securityfs_remove(attr->dir);
+	attr->dir = NULL;
+	return PTR_ERR(d);
+}
+
+/* ------------------------------------------------------------------ *
+ * Engage / disengage                                                 *
+ * ------------------------------------------------------------------ */
+
+static int __ks_engage(const char *symbol, long retval, bool from_cmdline)
+{
+	struct ks_attr *attr;
+	int ret;
+
+	if (!symbol || !*symbol)
+		return -EINVAL;
+
+	if (!from_cmdline) {
+		ret = security_locked_down(LOCKDOWN_KILLSWITCH);
+		if (ret)
+			return ret;
+	}
+
+	mutex_lock(&ks_lock);
+
+	if (ks_attr_lookup(symbol)) {
+		ret = -EBUSY;
+		goto out_unlock;
+	}
+
+	attr = ks_attr_alloc(symbol);
+	if (!attr) {
+		ret = -ENOMEM;
+		goto out_unlock;
+	}
+
+	atomic_long_set(&attr->retval, retval);
+
+	ret = register_kprobe(&attr->kp);
+	if (ret) {
+		pr_warn("killswitch: register_kprobe(%s) failed: %d\n",
+			symbol, ret);
+		ks_attr_put(attr);
+		goto out_unlock;
+	}
+
+	ret = ks_create_attr_dir(attr);
+	if (ret) {
+		unregister_kprobe(&attr->kp);
+		ks_attr_put(attr);
+		goto out_unlock;
+	}
+
+	list_add_tail(&attr->list, &ks_engaged_list);
+	attr->engaged = true;
+	add_taint(TAINT_KILLSWITCH, LOCKDEP_STILL_OK);
+
+	if (from_cmdline) {
+		pr_warn("killswitch: engage %s=%ld source=cmdline\n",
+			symbol, retval);
+	} else {
+		pr_warn("killswitch: engage %s=%ld uid=%u auid=%u ses=%u comm=%s\n",
+			symbol, retval,
+			from_kuid(&init_user_ns, current_uid()),
+			from_kuid(&init_user_ns, audit_get_loginuid(current)),
+			audit_get_sessionid(current),
+			current->comm);
+	}
+	ret = 0;
+
+out_unlock:
+	mutex_unlock(&ks_lock);
+	return ret;
+}
+
+int killswitch_engage(const char *symbol, long retval)
+{
+	return __ks_engage(symbol, retval, false);
+}
+
+static int __ks_disengage(const char *symbol)
+{
+	struct ks_attr *attr;
+	unsigned long hits;
+	int ret = 0;
+
+	mutex_lock(&ks_lock);
+	attr = ks_attr_lookup(symbol);
+	if (!attr) {
+		ret = -ENOENT;
+		goto out_unlock;
+	}
+
+	unregister_kprobe(&attr->kp);
+	attr->engaged = false;
+	list_del(&attr->list);
+	hits = ks_attr_hits(attr);
+	securityfs_remove(attr->dir);
+
+	pr_warn("killswitch: disengage %s hits=%lu uid=%u auid=%u ses=%u comm=%s\n",
+		symbol, hits,
+		from_kuid(&init_user_ns, current_uid()),
+		from_kuid(&init_user_ns, audit_get_loginuid(current)),
+		audit_get_sessionid(current),
+		current->comm);
+
+	/* unregister_kprobe() already waited out in-flight pre-handlers. */
+	ks_attr_put(attr);
+
+out_unlock:
+	mutex_unlock(&ks_lock);
+	return ret;
+}
+
+int killswitch_disengage(const char *symbol)
+{
+	return __ks_disengage(symbol);
+}
+
+bool killswitch_is_engaged(const char *symbol)
+{
+	bool engaged;
+
+	mutex_lock(&ks_lock);
+	engaged = ks_attr_lookup(symbol) != NULL;
+	mutex_unlock(&ks_lock);
+	return engaged;
+}
+
+static void ks_disengage_all_locked(void)
+{
+	struct ks_attr *attr, *n;
+
+	list_for_each_entry_safe(attr, n, &ks_engaged_list, list) {
+		unregister_kprobe(&attr->kp);
+		attr->engaged = false;
+		list_del(&attr->list);
+		securityfs_remove(attr->dir);
+		pr_warn("killswitch: disengage %s hits=%lu (disengage_all)\n",
+			attr->kp.symbol_name, ks_attr_hits(attr));
+		ks_attr_put(attr);
+	}
+}
+
+/* ------------------------------------------------------------------ *
+ * Module unload: drop engagements on functions in the going module   *
+ * ------------------------------------------------------------------ */
+
+static int ks_module_notify(struct notifier_block *nb, unsigned long action,
+			    void *data)
+{
+	struct module *mod = data;
+	struct ks_attr *attr, *n;
+
+	if (action != MODULE_STATE_GOING)
+		return NOTIFY_DONE;
+
+	mutex_lock(&ks_lock);
+	list_for_each_entry_safe(attr, n, &ks_engaged_list, list) {
+		if (!attr->kp.addr ||
+		    __module_address((unsigned long)attr->kp.addr) != mod)
+			continue;
+
+		pr_warn("killswitch: %s mitigation lost: module %s unloading; re-engage after reload if still needed\n",
+			attr->kp.symbol_name, mod->name);
+		unregister_kprobe(&attr->kp);
+		attr->engaged = false;
+		list_del(&attr->list);
+		securityfs_remove(attr->dir);
+		ks_attr_put(attr);
+	}
+	mutex_unlock(&ks_lock);
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block ks_module_nb = {
+	.notifier_call = ks_module_notify,
+};
+
+/* ------------------------------------------------------------------ *
+ * Top-level securityfs files: control / engaged / taint              *
+ * ------------------------------------------------------------------ */
+
+static int ks_engaged_show(struct seq_file *m, void *v)
+{
+	struct ks_attr *attr;
+
+	mutex_lock(&ks_lock);
+	list_for_each_entry(attr, &ks_engaged_list, list) {
+		seq_printf(m, "%s retval=%ld hits=%lu\n",
+			   attr->kp.symbol_name,
+			   atomic_long_read(&attr->retval),
+			   ks_attr_hits(attr));
+	}
+	mutex_unlock(&ks_lock);
+	return 0;
+}
+
+static int ks_engaged_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, ks_engaged_show, NULL);
+}
+
+static const struct file_operations ks_engaged_fops = {
+	.open		= ks_engaged_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static ssize_t ks_taint_read(struct file *file, char __user *ubuf,
+			     size_t count, loff_t *ppos)
+{
+	char buf[4];
+	int len;
+
+	len = scnprintf(buf, sizeof(buf), "%d\n",
+			test_taint(TAINT_KILLSWITCH) ? 1 : 0);
+	return simple_read_from_buffer(ubuf, count, ppos, buf, len);
+}
+
+static const struct file_operations ks_taint_fops = {
+	.open	= simple_open,
+	.read	= ks_taint_read,
+	.llseek	= default_llseek,
+};
+
+/*
+ * control: parse one of:
+ *   engage <symbol> <retval>
+ *   disengage <symbol>
+ *   disengage_all
+ */
+static ssize_t ks_control_write(struct file *file, const char __user *ubuf,
+				size_t count, loff_t *ppos)
+{
+	char *buf, *cur, *verb, *sym, *retstr;
+	long retval = 0;
+	int ret;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	if (count == 0 || count > 4096)
+		return -EINVAL;
+
+	buf = memdup_user_nul(ubuf, count);
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+
+	cur = strim(buf);
+	verb = strsep(&cur, " \t\n");
+	if (!verb || !*verb) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (!strcmp(verb, "disengage_all")) {
+		mutex_lock(&ks_lock);
+		ks_disengage_all_locked();
+		mutex_unlock(&ks_lock);
+		ret = count;
+		goto out;
+	}
+
+	sym = strsep(&cur, " \t\n");
+	if (!sym || !*sym) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (!strcmp(verb, "disengage")) {
+		ret = __ks_disengage(sym);
+		ret = ret ? ret : count;
+		goto out;
+	}
+
+	if (strcmp(verb, "engage")) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	retstr = strsep(&cur, " \t\n");
+	if (!retstr || !*retstr) {
+		ret = -EINVAL;
+		goto out;
+	}
+	if (kstrtol(retstr, 0, &retval)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = killswitch_engage(sym, retval);
+	if (!ret)
+		ret = count;
+
+out:
+	kfree(buf);
+	return ret;
+}
+
+static const struct file_operations ks_control_fops = {
+	.open	= simple_open,
+	.write	= ks_control_write,
+	.llseek	= noop_llseek,
+};
+
+/* ------------------------------------------------------------------ *
+ * Boot parameter:                                                    *
+ *   killswitch=fn1=-1,fn2=0,fn3=-22                                  *
+ * ------------------------------------------------------------------ */
+
+#define KS_BOOT_BUF 1024
+static char ks_boot_buf[KS_BOOT_BUF] __initdata;
+static bool ks_boot_present __initdata;
+
+static int __init ks_boot_setup(char *str)
+{
+	if (!str)
+		return 0;
+	strscpy(ks_boot_buf, str, sizeof(ks_boot_buf));
+	ks_boot_present = true;
+	return 1;
+}
+__setup("killswitch=", ks_boot_setup);
+
+static void __init ks_apply_boot_params(void)
+{
+	char *cur, *tok;
+	long retval;
+
+	if (!ks_boot_present)
+		return;
+
+	cur = ks_boot_buf;
+	while ((tok = strsep(&cur, ",")) != NULL) {
+		char *eq, *sym, *retstr;
+
+		if (!*tok)
+			continue;
+		eq = strchr(tok, '=');
+		if (!eq) {
+			pr_warn("killswitch: cmdline missing '=': %s\n", tok);
+			continue;
+		}
+		*eq++ = '\0';
+		sym = tok;
+		retstr = eq;
+
+		if (kstrtol(retstr, 0, &retval)) {
+			pr_warn("killswitch: cmdline bad retval %s=%s\n",
+				sym, retstr);
+			continue;
+		}
+
+		if (__ks_engage(sym, retval, true))
+			pr_warn("killswitch: cmdline engage %s failed\n", sym);
+	}
+}
+
+/* ------------------------------------------------------------------ *
+ * Init                                                               *
+ * ------------------------------------------------------------------ */
+
+static int __init killswitch_init(void)
+{
+	struct dentry *d;
+
+	ks_root_dir = securityfs_create_dir("killswitch", NULL);
+	if (IS_ERR(ks_root_dir))
+		return PTR_ERR(ks_root_dir);
+
+	d = securityfs_create_file("control", 0200, ks_root_dir,
+				   NULL, &ks_control_fops);
+	if (IS_ERR(d))
+		goto err;
+	d = securityfs_create_file("engaged", 0444, ks_root_dir,
+				   NULL, &ks_engaged_fops);
+	if (IS_ERR(d))
+		goto err;
+	d = securityfs_create_file("taint", 0444, ks_root_dir,
+				   NULL, &ks_taint_fops);
+	if (IS_ERR(d))
+		goto err;
+
+	ks_fn_dir = securityfs_create_dir("fn", ks_root_dir);
+	if (IS_ERR(ks_fn_dir)) {
+		d = ks_fn_dir;
+		goto err;
+	}
+
+	register_module_notifier(&ks_module_nb);
+	ks_apply_boot_params();
+
+	pr_info("killswitch: ready (sysfs at /sys/kernel/security/killswitch/)\n");
+	return 0;
+
+err:
+	securityfs_remove(ks_root_dir);
+	return PTR_ERR(d);
+}
+late_initcall(killswitch_init);
+
+/* ------------------------------------------------------------------ *
+ * KUnit tests                                                        *
+ * ------------------------------------------------------------------ */
+
+#if IS_ENABLED(CONFIG_KUNIT)
+#include <kunit/test.h>
+
+/* Non-static so kallsyms resolves them without CONFIG_KALLSYMS_ALL. */
+int ks_kunit_target_int(int x);
+void *ks_kunit_target_ptr(int x);
+
+#if __has_attribute(__noipa__)
+# define KS_KUNIT_NOIPA __attribute__((__noipa__))
+#else
+# define KS_KUNIT_NOIPA noinline __noclone
+#endif
+
+KS_KUNIT_NOIPA int ks_kunit_target_int(int x)
+{
+	return x + 1;
+}
+
+KS_KUNIT_NOIPA void *ks_kunit_target_ptr(int x)
+{
+	return ERR_PTR(-EIO);
+}
+
+static int ks_kunit_init(struct kunit *test)
+{
+	if (security_locked_down(LOCKDOWN_KILLSWITCH))
+		kunit_skip(test, "integrity lockdown blocks killswitch_engage()");
+	return 0;
+}
+
+static int ks_kunit_init_lockdown(struct kunit *test)
+{
+	if (!security_locked_down(LOCKDOWN_KILLSWITCH))
+		kunit_skip(test, "requires lockdown=integrity");
+	return 0;
+}
+
+static void ks_disengage_quiet(const char *sym)
+{
+	if (killswitch_is_engaged(sym))
+		killswitch_disengage(sym);
+}
+
+static void ks_test_engage_int(struct kunit *test)
+{
+	int ret;
+
+	ret = killswitch_engage("ks_kunit_target_int", -EPERM);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, ks_kunit_target_int(7), -EPERM);
+	KUNIT_EXPECT_EQ(test, killswitch_disengage("ks_kunit_target_int"), 0);
+	KUNIT_EXPECT_EQ(test, ks_kunit_target_int(7), 8);
+}
+
+static void ks_test_double_engage(struct kunit *test)
+{
+	KUNIT_ASSERT_EQ(test,
+		killswitch_engage("ks_kunit_target_int", 0), 0);
+	KUNIT_EXPECT_EQ(test,
+		killswitch_engage("ks_kunit_target_int", 0), -EBUSY);
+	ks_disengage_quiet("ks_kunit_target_int");
+}
+
+static void ks_test_disengage_unknown(struct kunit *test)
+{
+	KUNIT_EXPECT_EQ(test,
+		killswitch_disengage("ks_kunit_target_int"), -ENOENT);
+}
+
+static void ks_test_pointer_target(struct kunit *test)
+{
+	long retval = (long)(unsigned long)ERR_PTR(-EACCES);
+
+	KUNIT_ASSERT_EQ(test,
+		killswitch_engage("ks_kunit_target_ptr", retval), 0);
+	KUNIT_EXPECT_TRUE(test, IS_ERR(ks_kunit_target_ptr(0)));
+	KUNIT_EXPECT_EQ(test, PTR_ERR(ks_kunit_target_ptr(0)), -EACCES);
+	ks_disengage_quiet("ks_kunit_target_ptr");
+}
+
+static void ks_test_taint_set(struct kunit *test)
+{
+	KUNIT_ASSERT_EQ(test,
+		killswitch_engage("ks_kunit_target_int", 0), 0);
+	KUNIT_EXPECT_TRUE(test, test_taint(TAINT_KILLSWITCH));
+	ks_disengage_quiet("ks_kunit_target_int");
+	/* taint must persist even after disengage */
+	KUNIT_EXPECT_TRUE(test, test_taint(TAINT_KILLSWITCH));
+}
+
+static void ks_test_hits_counter(struct kunit *test)
+{
+	struct ks_attr *attr;
+	int i;
+
+	KUNIT_ASSERT_EQ(test,
+		killswitch_engage("ks_kunit_target_int", 0), 0);
+
+	for (i = 0; i < 17; i++)
+		(void)ks_kunit_target_int(i);
+
+	mutex_lock(&ks_lock);
+	attr = ks_attr_lookup("ks_kunit_target_int");
+	KUNIT_EXPECT_NOT_NULL(test, attr);
+	if (attr)
+		KUNIT_EXPECT_EQ(test, ks_attr_hits(attr), 17UL);
+	mutex_unlock(&ks_lock);
+
+	ks_disengage_quiet("ks_kunit_target_int");
+}
+
+static struct kunit_case ks_kunit_cases[] = {
+	KUNIT_CASE(ks_test_engage_int),
+	KUNIT_CASE(ks_test_double_engage),
+	KUNIT_CASE(ks_test_disengage_unknown),
+	KUNIT_CASE(ks_test_pointer_target),
+	KUNIT_CASE(ks_test_taint_set),
+	KUNIT_CASE(ks_test_hits_counter),
+	{}
+};
+
+static struct kunit_suite ks_kunit_suite = {
+	.name = "killswitch",
+	.init = ks_kunit_init,
+	.test_cases = ks_kunit_cases,
+};
+
+/*
+ * Lockdown suite. Skipped unless the kernel was booted with
+ * lockdown=integrity (or higher). Run together with
+ * killswitch=ks_kunit_target_int=... on the same cmdline to also
+ * exercise the cmdline-bypass and disengage-under-lockdown paths.
+ */
+static void ks_test_lockdown_runtime_engage(struct kunit *test)
+{
+	KUNIT_EXPECT_EQ(test,
+		killswitch_engage("ks_kunit_target_int", 0), -EPERM);
+}
+
+static void ks_test_lockdown_cmdline_disengage(struct kunit *test)
+{
+	if (!killswitch_is_engaged("ks_kunit_target_int"))
+		kunit_skip(test,
+			   "requires killswitch=ks_kunit_target_int=... on cmdline");
+	KUNIT_EXPECT_EQ(test,
+		killswitch_disengage("ks_kunit_target_int"), 0);
+}
+
+static struct kunit_case ks_kunit_lockdown_cases[] = {
+	KUNIT_CASE(ks_test_lockdown_runtime_engage),
+	KUNIT_CASE(ks_test_lockdown_cmdline_disengage),
+	{}
+};
+
+static struct kunit_suite ks_kunit_lockdown_suite = {
+	.name = "killswitch_lockdown",
+	.init = ks_kunit_init_lockdown,
+	.test_cases = ks_kunit_lockdown_cases,
+};
+
+kunit_test_suites(&ks_kunit_suite, &ks_kunit_lockdown_suite);
+
+#endif /* CONFIG_KUNIT */
+
diff --git a/kernel/panic.c b/kernel/panic.c
index 20feada5319d4..8ee174c7b7dd0 100644
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -825,6 +825,7 @@ const struct taint_flag taint_flags[TAINT_FLAGS_COUNT] = {
 	TAINT_FLAG(RANDSTRUCT,			'T', ' '),
 	TAINT_FLAG(TEST,			'N', ' '),
 	TAINT_FLAG(FWCTL,			'J', ' '),
+	TAINT_FLAG(KILLSWITCH,			'H', ' '),
 };
 
 #undef TAINT_FLAG
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 8ff5adcfe1e0a..5770639c7b0ea 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -3349,6 +3349,19 @@ config TEST_HMM
 
 	  If unsure, say N.
 
+config TEST_KILLSWITCH
+	tristate "Test module for the killswitch mitigation primitive"
+	depends on KILLSWITCH && DEBUG_FS
+	depends on m
+	help
+	  Build a module that exposes a deliberately-vulnerable function
+	  ks_test_vuln() and a debugfs trigger /sys/kernel/debug/test_killswitch/fire.
+	  The killswitch selftest in tools/testing/selftests/killswitch/
+	  uses this to confirm engaging a killswitch suppresses the BUG()
+	  the function would otherwise hit.
+
+	  If unsure, say N.
+
 config TEST_FREE_PAGES
 	tristate "Test freeing pages"
 	help
diff --git a/lib/Makefile b/lib/Makefile
index f33a24bf1c19a..d763225340674 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -100,6 +100,7 @@ obj-$(CONFIG_TEST_MEMCAT_P) += test_memcat_p.o
 obj-$(CONFIG_TEST_OBJAGG) += test_objagg.o
 obj-$(CONFIG_TEST_MEMINIT) += test_meminit.o
 obj-$(CONFIG_TEST_LOCKUP) += test_lockup.o
+obj-$(CONFIG_TEST_KILLSWITCH) += test_killswitch.o
 obj-$(CONFIG_TEST_HMM) += test_hmm.o
 obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o
 obj-$(CONFIG_TEST_REF_TRACKER) += test_ref_tracker.o
diff --git a/lib/test_killswitch.c b/lib/test_killswitch.c
new file mode 100644
index 0000000000000..cc2584ad652ff
--- /dev/null
+++ b/lib/test_killswitch.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test target for the killswitch selftest.  ks_test_vuln() returns
+ * -EBADMSG on a magic input, standing in for "the buggy path runs
+ * and produces a bad outcome".  Engaging killswitch on this function
+ * with retval 0 is the mitigation.
+ *
+ * Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+
+#define KS_TEST_MAGIC	0xC0FFEEL
+
+int ks_test_vuln(long magic);
+
+/*
+ * Returns -EBADMSG on the magic input -- stands in for "the buggy
+ * path runs and produces a bad outcome".  Engaging a killswitch on
+ * this function with retval 0 represents the mitigation: even on
+ * the magic input, callers see success because the body never runs.
+ *
+ * noipa prevents inlining/IPA so the call actually reaches the
+ * kprobe-instrumented entry point.
+ */
+noinline int ks_test_vuln(long magic)
+{
+	if (magic == KS_TEST_MAGIC)
+		return -EBADMSG;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ks_test_vuln);
+
+static struct dentry *ks_test_dir;
+
+static ssize_t ks_test_fire_write(struct file *file, const char __user *ubuf,
+				  size_t count, loff_t *ppos)
+{
+	char buf[32];
+	long magic;
+	int ret;
+
+	if (count == 0 || count >= sizeof(buf))
+		return -EINVAL;
+	if (copy_from_user(buf, ubuf, count))
+		return -EFAULT;
+	buf[count] = '\0';
+
+	ret = kstrtol(strim(buf), 0, &magic);
+	if (ret)
+		return ret;
+
+	ret = ks_test_vuln(magic);
+	return ret ? ret : count;
+}
+
+static const struct file_operations ks_test_fire_fops = {
+	.write	= ks_test_fire_write,
+	.open	= simple_open,
+	.llseek	= noop_llseek,
+};
+
+static int __init test_killswitch_init(void)
+{
+	ks_test_dir = debugfs_create_dir("test_killswitch", NULL);
+	debugfs_create_file("fire", 0200, ks_test_dir, NULL,
+			    &ks_test_fire_fops);
+	pr_info("test_killswitch: loaded (magic=0x%lx)\n", KS_TEST_MAGIC);
+	return 0;
+}
+module_init(test_killswitch_init);
+
+static void __exit test_killswitch_exit(void)
+{
+	debugfs_remove_recursive(ks_test_dir);
+}
+module_exit(test_killswitch_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Deliberately-vulnerable target for killswitch selftest");
diff --git a/security/security.c b/security/security.c
index 4e999f0236516..bf700abc911a9 100644
--- a/security/security.c
+++ b/security/security.c
@@ -62,6 +62,7 @@ const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX + 1] = {
 	[LOCKDOWN_DBG_WRITE_KERNEL] = "use of kgdb/kdb to write kernel RAM",
 	[LOCKDOWN_RTAS_ERROR_INJECTION] = "RTAS error injection",
 	[LOCKDOWN_XEN_USER_ACTIONS] = "Xen guest user action",
+	[LOCKDOWN_KILLSWITCH] = "engaging a killswitch",
 	[LOCKDOWN_INTEGRITY_MAX] = "integrity",
 	[LOCKDOWN_KCORE] = "/proc/kcore access",
 	[LOCKDOWN_KPROBES] = "use of kprobes",
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 6e59b8f63e416..04c3f8c5ff229 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -53,6 +53,7 @@ TARGETS += ipc
 TARGETS += ir
 TARGETS += kcmp
 TARGETS += kexec
+TARGETS += killswitch
 TARGETS += kselftest_harness
 TARGETS += kvm
 TARGETS += landlock
diff --git a/tools/testing/selftests/killswitch/.gitignore b/tools/testing/selftests/killswitch/.gitignore
new file mode 100644
index 0000000000000..cbf204ce18615
--- /dev/null
+++ b/tools/testing/selftests/killswitch/.gitignore
@@ -0,0 +1 @@
+cve_31431_test
diff --git a/tools/testing/selftests/killswitch/Makefile b/tools/testing/selftests/killswitch/Makefile
new file mode 100644
index 0000000000000..ccf41165cb73d
--- /dev/null
+++ b/tools/testing/selftests/killswitch/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+TEST_GEN_PROGS := cve_31431_test cve_43284_test
+TEST_PROGS := killswitch_test.sh
+
+CFLAGS += -O2 -g -std=gnu99 -Wall $(KHDR_INCLUDES)
+
+include ../lib.mk
diff --git a/tools/testing/selftests/killswitch/cve_31431_test.c b/tools/testing/selftests/killswitch/cve_31431_test.c
new file mode 100644
index 0000000000000..1ff817c51d881
--- /dev/null
+++ b/tools/testing/selftests/killswitch/cve_31431_test.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AF_ALG AEAD round-trip prober.  The killswitch selftest uses this
+ * to demonstrate that engaging a killswitch on af_alg_sendmsg
+ * neuters AF_ALG operations (sendmsg returns -EPERM), mitigating
+ * any AF_ALG-reachable bug whose exploit primitive runs from the
+ * send path.
+ *
+ * Exit codes:
+ *   0  AEAD round-trip succeeded (function intact)
+ *   1  AEAD round-trip refused (mitigation engaged)
+ *   2  setup error (no AF_ALG, missing aead/gcm(aes), etc.) -> SKIP
+ *
+ * Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <linux/if_alg.h>
+
+#define KEY_LEN		16
+#define IV_LEN		12
+#define AAD_LEN		16
+#define PT_LEN		64
+#define TAG_LEN		16
+#define EXPECTED_LEN	(AAD_LEN + PT_LEN + TAG_LEN)
+
+#ifndef AF_ALG
+#define AF_ALG		38
+#endif
+#ifndef SOL_ALG
+#define SOL_ALG		279
+#endif
+
+int main(void)
+{
+	struct sockaddr_alg sa = {
+		.salg_family = AF_ALG,
+		.salg_type   = "aead",
+		.salg_name   = "gcm(aes)",
+	};
+	unsigned char key[KEY_LEN] = { 0 };
+	unsigned char iv[IV_LEN]   = { 0 };
+	unsigned char buf[1024]    = { 0 };
+	struct msghdr msg = { 0 };
+	struct iovec iov;
+	struct cmsghdr *cmsg;
+	struct af_alg_iv *aiv;
+	char cbuf[256] = { 0 };
+	int *p_op, *p_assoclen;
+	int sk, opfd;
+	ssize_t n;
+
+	sk = socket(AF_ALG, SOCK_SEQPACKET, 0);
+	if (sk < 0) {
+		fprintf(stderr, "AF_ALG socket: %s -- skip\n", strerror(errno));
+		return 2;
+	}
+	if (bind(sk, (struct sockaddr *)&sa, sizeof(sa))) {
+		fprintf(stderr, "bind aead/gcm(aes): %s -- skip\n",
+			strerror(errno));
+		close(sk);
+		return 2;
+	}
+	if (setsockopt(sk, SOL_ALG, ALG_SET_KEY, key, KEY_LEN)) {
+		fprintf(stderr, "ALG_SET_KEY: %s -- skip\n", strerror(errno));
+		close(sk);
+		return 2;
+	}
+	if (setsockopt(sk, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, TAG_LEN)) {
+		fprintf(stderr, "ALG_SET_AEAD_AUTHSIZE: %s -- skip\n",
+			strerror(errno));
+		close(sk);
+		return 2;
+	}
+
+	opfd = accept(sk, NULL, 0);
+	if (opfd < 0) {
+		fprintf(stderr, "accept: %s -- skip\n", strerror(errno));
+		close(sk);
+		return 2;
+	}
+
+	/* control message: ENCRYPT op + IV + assoclen */
+	msg.msg_control    = cbuf;
+	msg.msg_controllen = CMSG_SPACE(sizeof(int))
+			   + CMSG_SPACE(sizeof(*aiv) + IV_LEN)
+			   + CMSG_SPACE(sizeof(int));
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_level = SOL_ALG;
+	cmsg->cmsg_type  = ALG_SET_OP;
+	cmsg->cmsg_len   = CMSG_LEN(sizeof(int));
+	p_op = (int *)CMSG_DATA(cmsg);
+	*p_op = ALG_OP_ENCRYPT;
+
+	cmsg = CMSG_NXTHDR(&msg, cmsg);
+	cmsg->cmsg_level = SOL_ALG;
+	cmsg->cmsg_type  = ALG_SET_IV;
+	cmsg->cmsg_len   = CMSG_LEN(sizeof(*aiv) + IV_LEN);
+	aiv = (struct af_alg_iv *)CMSG_DATA(cmsg);
+	aiv->ivlen = IV_LEN;
+	memcpy(aiv->iv, iv, IV_LEN);
+
+	cmsg = CMSG_NXTHDR(&msg, cmsg);
+	cmsg->cmsg_level = SOL_ALG;
+	cmsg->cmsg_type  = ALG_SET_AEAD_ASSOCLEN;
+	cmsg->cmsg_len   = CMSG_LEN(sizeof(int));
+	p_assoclen = (int *)CMSG_DATA(cmsg);
+	*p_assoclen = AAD_LEN;
+
+	/* AAD || plaintext */
+	memset(buf, 0xaa, AAD_LEN);
+	memset(buf + AAD_LEN, 0x55, PT_LEN);
+	iov.iov_base = buf;
+	iov.iov_len  = AAD_LEN + PT_LEN;
+	msg.msg_iov    = &iov;
+	msg.msg_iovlen = 1;
+
+	n = sendmsg(opfd, &msg, 0);
+	if (n < 0) {
+		/*
+		 * sendmsg refused: this is exactly the killswitch
+		 * af_alg_sendmsg=-EPERM mitigation outcome.  Distinct
+		 * exit code from setup failure so the test script can
+		 * tell them apart.
+		 */
+		fprintf(stderr, "sendmsg: %s -- mitigation engaged?\n",
+			strerror(errno));
+		close(opfd); close(sk);
+		return 1;
+	}
+
+	/* recv: AAD echoed, plus ciphertext + tag */
+	memset(buf, 0, sizeof(buf));
+	n = read(opfd, buf, EXPECTED_LEN);
+	close(opfd); close(sk);
+
+	if (n == 0) {
+		printf("AEAD returned 0 bytes -- killswitch mitigation engaged\n");
+		return 1;
+	}
+	if (n != EXPECTED_LEN) {
+		fprintf(stderr,
+			"AEAD short read: got %zd, expected %d -- mitigated?\n",
+			n, EXPECTED_LEN);
+		return 1;
+	}
+
+	/* sanity: ciphertext (after AAD) shouldn't equal the plaintext bytes */
+	if (memcmp(buf + AAD_LEN, buf + AAD_LEN + 1, PT_LEN - 1) == 0) {
+		fprintf(stderr, "AEAD output looks unencrypted\n");
+		return 2;
+	}
+
+	printf("AEAD round-trip OK (%zd bytes)\n", n);
+	return 0;
+}
diff --git a/tools/testing/selftests/killswitch/cve_43284_test.c b/tools/testing/selftests/killswitch/cve_43284_test.c
new file mode 100644
index 0000000000000..4771cb0957dc1
--- /dev/null
+++ b/tools/testing/selftests/killswitch/cve_43284_test.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * UDP loopback round-trip prober.  Wrapped by killswitch_test.sh with
+ * an IPsec ESP SA + policy pair on loopback, this demonstrates that
+ * engaging a killswitch on esp_input drops inbound ESP packets before
+ * decapsulation, mitigating CVE-2026-43284 ("Dirty Frag", upstream fix
+ * xfrm: esp: avoid in-place decrypt on shared skb frags).
+ *
+ * The binary itself knows nothing about ESP -- it sends one UDP
+ * datagram to itself and waits up to a second for delivery.
+ *
+ * Exit codes:
+ *   0  UDP round-trip succeeded (no mitigation in effect)
+ *   1  UDP recv timed out (mitigation engaged)
+ *   2  setup error -> SKIP
+ *
+ * Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#define UDP_PORT 53435
+#define PROBE    "ks-43284-probe"
+
+int main(void)
+{
+	struct sockaddr_in addr = {
+		.sin_family      = AF_INET,
+		.sin_port        = htons(UDP_PORT),
+		.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+	};
+	struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
+	char buf[64];
+	int sk;
+	ssize_t n;
+
+	sk = socket(AF_INET, SOCK_DGRAM, 0);
+	if (sk < 0) {
+		fprintf(stderr, "socket: %s -- skip\n", strerror(errno));
+		return 2;
+	}
+	if (bind(sk, (struct sockaddr *)&addr, sizeof(addr))) {
+		fprintf(stderr, "bind: %s -- skip\n", strerror(errno));
+		close(sk);
+		return 2;
+	}
+	if (setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) {
+		fprintf(stderr, "SO_RCVTIMEO: %s -- skip\n", strerror(errno));
+		close(sk);
+		return 2;
+	}
+
+	if (sendto(sk, PROBE, sizeof(PROBE) - 1, 0,
+		   (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+		fprintf(stderr, "sendto: %s -- skip\n", strerror(errno));
+		close(sk);
+		return 2;
+	}
+
+	memset(buf, 0, sizeof(buf));
+	n = recvfrom(sk, buf, sizeof(buf), 0, NULL, NULL);
+	close(sk);
+
+	if (n < 0) {
+		if (errno == EAGAIN || errno == EWOULDBLOCK) {
+			fprintf(stderr,
+				"recvfrom: timeout -- mitigation engaged?\n");
+			return 1;
+		}
+		fprintf(stderr, "recvfrom: %s\n", strerror(errno));
+		return 2;
+	}
+	if (n != (ssize_t)(sizeof(PROBE) - 1) ||
+	    memcmp(buf, PROBE, sizeof(PROBE) - 1)) {
+		fprintf(stderr, "recvfrom: bad payload (%zd bytes)\n", n);
+		return 2;
+	}
+
+	printf("UDP round-trip OK (%zd bytes)\n", n);
+	return 0;
+}
diff --git a/tools/testing/selftests/killswitch/killswitch_test.sh b/tools/testing/selftests/killswitch/killswitch_test.sh
new file mode 100755
index 0000000000000..ea3fd394a984f
--- /dev/null
+++ b/tools/testing/selftests/killswitch/killswitch_test.sh
@@ -0,0 +1,254 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# End-to-end killswitch selftest.  Drives the test_killswitch module
+# through an engage/disengage cycle and confirms each transition
+# behaves as expected.  Also runs the AF_ALG mitigation proof.
+#
+# Requirements (see Documentation/admin-guide/killswitch.rst):
+#   - CONFIG_KILLSWITCH=y
+#   - CONFIG_TEST_KILLSWITCH=m
+#   - run as root (CAP_SYS_ADMIN)
+#
+# Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+#
+
+set -u
+
+KS=/sys/kernel/security/killswitch
+TRIG=/sys/kernel/debug/test_killswitch/fire
+
+NOMOD=0
+SKIP_RC=4
+N=0
+FAIL=0
+
+ksft_pass() { N=$((N+1));    echo "ok $N - $*"; }
+ksft_fail() { N=$((N+1)); FAIL=$((FAIL+1)); echo "not ok $N - $*"; }
+ksft_skip() { echo "ok 1 - SKIP $*"; echo "1..1"; exit $SKIP_RC; }
+
+[[ $EUID -eq 0 ]] || ksft_skip "must be root"
+[[ -d $KS    ]] || ksft_skip "$KS not present (CONFIG_KILLSWITCH disabled?)"
+
+if ! modprobe test_killswitch 2>/dev/null; then
+	NOMOD=1
+fi
+[[ -e $TRIG ]] || ksft_skip "$TRIG missing (test_killswitch.ko not installed?)"
+
+cleanup() {
+	echo "disengage_all" > $KS/control 2>/dev/null || true
+	[[ $NOMOD -eq 0 ]] && rmmod test_killswitch 2>/dev/null || true
+}
+trap cleanup EXIT
+
+# --- pre-engage: bad path runs, write fails with EBADMSG ---
+if echo 0xC0FFEE > $TRIG 2>/dev/null; then
+	ksft_fail "pre-engage: write should have failed (-EBADMSG)"
+else
+	[[ $? -ne 0 ]] && ksft_pass "pre-engage: bad path returns error" \
+	             || ksft_fail "pre-engage: unexpected outcome"
+fi
+
+# --- engage ---
+echo "engage ks_test_vuln 0" > $KS/control
+grep -q "^ks_test_vuln" $KS/engaged \
+	&& ksft_pass "engage: ks_test_vuln in engaged list" \
+	|| ksft_fail "engage: missing from engaged list"
+
+[[ $(cat $KS/taint) == 1 ]] \
+	&& ksft_pass "engage: taint set" \
+	|| ksft_fail "engage: taint not set"
+
+[[ -d $KS/fn/ks_test_vuln ]] \
+	&& ksft_pass "engage: per-fn dir created" \
+	|| ksft_fail "engage: per-fn dir missing"
+
+# --- post-engage: BUG suppressed; write returns successfully ---
+if echo 0xC0FFEE > $TRIG 2>/dev/null; then
+	ksft_pass "post-engage: BUG suppressed, write succeeded"
+else
+	ksft_fail "post-engage: write should succeed"
+fi
+
+[[ $(cat $KS/fn/ks_test_vuln/hits) -ge 1 ]] \
+	&& ksft_pass "post-engage: hits counter incremented" \
+	|| ksft_fail "post-engage: hits counter did not move"
+
+# --- retval rewrite is a plain write (no validation) ---
+echo 7 > $KS/fn/ks_test_vuln/retval
+[[ $(cat $KS/fn/ks_test_vuln/retval) == 7 ]] \
+	&& ksft_pass "retval rewrite round-trips" \
+	|| ksft_fail "retval rewrite failed"
+
+# --- engage on a kprobe-rejected function fails ---
+# warn_thunk_thunk is in /sys/kernel/debug/kprobes/blacklist;
+# register_kprobe() refuses it.
+KP_REJECT=warn_thunk_thunk
+if echo "engage $KP_REJECT 0" > $KS/control 2>/dev/null; then
+	ksft_fail "register_kprobe should have rejected $KP_REJECT"
+	echo "disengage $KP_REJECT" > $KS/control
+else
+	ksft_pass "register_kprobe refuses blacklisted target"
+fi
+
+# --- disengage ---
+echo "disengage ks_test_vuln" > $KS/control
+[[ -z "$(cat $KS/engaged)" ]] \
+	&& ksft_pass "disengage: engaged list empty" \
+	|| ksft_fail "disengage: engaged list not empty"
+
+[[ ! -d $KS/fn/ks_test_vuln ]] \
+	&& ksft_pass "disengage: per-fn dir removed" \
+	|| ksft_fail "disengage: per-fn dir still present"
+
+[[ $(cat $KS/taint) == 1 ]] \
+	&& ksft_pass "disengage: taint persists" \
+	|| ksft_fail "disengage: taint should persist"
+
+# --- post-disengage: bad path active again ---
+if echo 0xC0FFEE > $TRIG 2>/dev/null; then
+	ksft_fail "post-disengage: write should fail again"
+else
+	ksft_pass "post-disengage: bad path active again"
+fi
+
+# ---- CVE-2026-31431 mitigation proof (AF_ALG aead via af_alg_sendmsg) ----
+# Skip the whole block if AF_ALG / AEAD machinery isn't compiled in.
+if [[ -x $(dirname "$0")/cve_31431_test ]]; then
+	CVE=$(dirname "$0")/cve_31431_test
+	$CVE >/dev/null 2>&1 && PRE=$? || PRE=$?
+	if [[ $PRE -eq 0 ]]; then
+		ksft_pass "cve-31431: pre-engage AEAD round-trip OK"
+
+		echo "engage af_alg_sendmsg -1" > $KS/control
+		$CVE >/dev/null 2>&1 && POST=$? || POST=$?
+		if [[ $POST -eq 1 ]]; then
+			ksft_pass "cve-31431: post-engage AEAD refused (mitigated)"
+		else
+			ksft_fail "cve-31431: post-engage exit=$POST (expected 1)"
+		fi
+
+		HITS=$(cat $KS/fn/af_alg_sendmsg/hits 2>/dev/null || echo 0)
+		[[ $HITS -ge 1 ]] && ksft_pass "cve-31431: hits=$HITS recorded" \
+			|| ksft_fail "cve-31431: hits not recorded"
+
+		echo "disengage af_alg_sendmsg" > $KS/control
+		$CVE >/dev/null 2>&1 && POST2=$? || POST2=$?
+		[[ $POST2 -eq 0 ]] && ksft_pass "cve-31431: post-disengage restored" \
+			|| ksft_fail "cve-31431: post-disengage exit=$POST2"
+	elif [[ $PRE -eq 2 ]]; then
+		echo "# SKIP cve-31431 (AF_ALG/AEAD not available)"
+	else
+		ksft_fail "cve-31431: pre-engage exit=$PRE"
+	fi
+fi
+
+# ---- CVE-2026-43284 mitigation proof (IPsec ESP via esp_input) ----
+# Engaging esp_input causes inbound ESP packets to be dropped before
+# decapsulation, neutering any bug downstream of the ESP receive path.
+# Two netns + veth so traffic actually traverses xfrm (single-netns
+# 127.0.0.0/8 traffic short-circuits before xfrm policy lookup).
+NS0=ks-esp-0
+NS1=ks-esp-1
+esp_setup_ok=0
+esp_cleanup() {
+	[[ $esp_setup_ok -eq 1 ]] || return 0
+	ip netns del $NS0 2>/dev/null
+	ip netns del $NS1 2>/dev/null
+}
+trap 'cleanup; esp_cleanup' EXIT
+
+# UDP probe in python3 (always present on Debian/Fedora minimal installs).
+esp_round_trip() {
+	# $1: source netns, $2: dest netns, $3: dest ip, $4: port
+	local tmp rpid rc
+	tmp=$(mktemp)
+	ip netns exec "$2" python3 -c '
+import socket
+r = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+r.bind(("0.0.0.0", '"$4"'))
+r.settimeout(2.0)
+try:
+    d,_ = r.recvfrom(64)
+    print(d.decode(errors="replace"))
+except socket.timeout:
+    print("timeout")
+' > "$tmp" 2>&1 &
+	rpid=$!
+	sleep 0.3
+	ip netns exec "$1" python3 -c '
+import socket
+s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+s.sendto(b"ks-esp-probe", ("'"$3"'", '"$4"'))
+' 2>/dev/null
+	wait $rpid 2>/dev/null
+	rc=1
+	grep -q "ks-esp-probe" "$tmp" && rc=0
+	rm -f "$tmp"
+	return $rc
+}
+
+if command -v ip >/dev/null 2>&1 && command -v python3 >/dev/null 2>&1; then
+	KEY=0x0123456789abcdef0123456789abcdef01234567
+
+	if ip netns add $NS0 2>/dev/null && \
+	   ip netns add $NS1 2>/dev/null && \
+	   ip link add veth0 type veth peer name veth1 2>/dev/null && \
+	   ip link set veth0 netns $NS0 2>/dev/null && \
+	   ip link set veth1 netns $NS1 2>/dev/null && \
+	   ip -n $NS0 addr add 10.99.0.1/24 dev veth0 2>/dev/null && \
+	   ip -n $NS1 addr add 10.99.0.2/24 dev veth1 2>/dev/null && \
+	   ip -n $NS0 link set veth0 up 2>/dev/null && \
+	   ip -n $NS1 link set veth1 up 2>/dev/null && \
+	   ip -n $NS0 link set lo up 2>/dev/null && \
+	   ip -n $NS1 link set lo up 2>/dev/null && \
+	   ip -n $NS0 xfrm state add src 10.99.0.1 dst 10.99.0.2 proto esp \
+		spi 0x1000 mode transport reqid 0x100 \
+		aead 'rfc4106(gcm(aes))' $KEY 128 2>/dev/null && \
+	   ip -n $NS0 xfrm state add src 10.99.0.2 dst 10.99.0.1 proto esp \
+		spi 0x1001 mode transport reqid 0x100 \
+		aead 'rfc4106(gcm(aes))' $KEY 128 2>/dev/null && \
+	   ip -n $NS1 xfrm state add src 10.99.0.1 dst 10.99.0.2 proto esp \
+		spi 0x1000 mode transport reqid 0x100 \
+		aead 'rfc4106(gcm(aes))' $KEY 128 2>/dev/null && \
+	   ip -n $NS1 xfrm state add src 10.99.0.2 dst 10.99.0.1 proto esp \
+		spi 0x1001 mode transport reqid 0x100 \
+		aead 'rfc4106(gcm(aes))' $KEY 128 2>/dev/null && \
+	   ip -n $NS0 xfrm policy add src 10.99.0.1 dst 10.99.0.2 \
+		dir out tmpl src 10.99.0.1 dst 10.99.0.2 proto esp \
+		reqid 0x100 mode transport 2>/dev/null && \
+	   ip -n $NS1 xfrm policy add src 10.99.0.1 dst 10.99.0.2 \
+		dir in tmpl src 10.99.0.1 dst 10.99.0.2 proto esp \
+		reqid 0x100 mode transport 2>/dev/null; then
+		esp_setup_ok=1
+	fi
+
+	if [[ $esp_setup_ok -eq 1 ]] \
+	   && esp_round_trip $NS0 $NS1 10.99.0.2 53435; then
+		ksft_pass "cve-43284: pre-engage ESP round-trip OK"
+
+		echo "engage esp_input -22" > $KS/control
+		if esp_round_trip $NS0 $NS1 10.99.0.2 53435; then
+			ksft_fail "cve-43284: post-engage ESP should have been dropped"
+		else
+			ksft_pass "cve-43284: post-engage ESP refused (mitigated)"
+		fi
+
+		ESP_HITS=$(cat $KS/fn/esp_input/hits 2>/dev/null || echo 0)
+		[[ $ESP_HITS -ge 1 ]] \
+			&& ksft_pass "cve-43284: hits=$ESP_HITS recorded" \
+			|| ksft_fail "cve-43284: hits not recorded"
+
+		echo "disengage esp_input" > $KS/control
+		if esp_round_trip $NS0 $NS1 10.99.0.2 53435; then
+			ksft_pass "cve-43284: post-disengage restored"
+		else
+			ksft_fail "cve-43284: post-disengage ESP still dropped"
+		fi
+	else
+		echo "# SKIP cve-43284 (netns/veth/XFRM/ESP setup failed)"
+	fi
+fi
+
+echo "1..$N"
+exit $((FAIL > 0))
-- 
2.53.0


^ permalink raw reply related

* [PATCH] docs: gpu: fix spelling errors and remove duplicate sentence
From: Elliot Tester @ 2026-05-17 13:41 UTC (permalink / raw)
  To: alexander.deucher, christian.koenig, maarten.lankhorst, mripard,
	tzimmermann, airlied, simona, corbet
  Cc: skhan, amd-gfx, dri-devel, linux-doc, linux-kernel, Elliot Tester

Fix various spelling errors in GPU docs:
- indicies -> indices (userq.rst)
- umap -> unmap (userq.rst)
- pre-empt -> preempt (drm-compute.rst)
- buffer-leaks -> buffer leaks (drm-uapi.rst)
- Additionally to -> In addition to (drm-uapi.rst)
- unpriviledged -> unprivileged (drm-uapi.rst)
- fucntions -> functions (todo.rst)
- varios -> various (todo.rst)
- implementions -> implementations (todo.rst)
- complection -> completion (todo.rst)

Ale remove a duplicated sentance and stray "uff." in the todo.rst, add
missing period after drm_ioctl.c reference, and add missing newline at
end of drm-uapi.rst. Fixing this would make reading the docs just a
little bit easier.

Signed-off-by: Elliot Tester <elliotctester1@gmail.com>
---
 Documentation/gpu/amdgpu/userq.rst |  4 ++--
 Documentation/gpu/drm-compute.rst  |  2 +-
 Documentation/gpu/drm-uapi.rst     | 10 +++++-----
 Documentation/gpu/todo.rst         | 11 +++++------
 4 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/Documentation/gpu/amdgpu/userq.rst b/Documentation/gpu/amdgpu/userq.rst
index 88f54393b..94427e18a 100644
--- a/Documentation/gpu/amdgpu/userq.rst
+++ b/Documentation/gpu/amdgpu/userq.rst
@@ -156,9 +156,9 @@ IOCTL Interfaces
 GPU virtual addresses used for queues and related data (rptrs, wptrs, context
 save areas, etc.) should be validated by the kernel mode driver to prevent the
 user from specifying invalid GPU virtual addresses.  If the user provides
-invalid GPU virtual addresses or doorbell indicies, the IOCTL should return an
+invalid GPU virtual addresses or doorbell indices, the IOCTL should return an
 error message.  These buffers should also be tracked in the kernel driver so
-that if the user attempts to unmap the buffer(s) from the GPUVM, the umap call
+that if the user attempts to unmap the buffer(s) from the GPUVM, the unmap call
 would return an error.
 
 INFO
diff --git a/Documentation/gpu/drm-compute.rst b/Documentation/gpu/drm-compute.rst
index f90c3e63a..35cc8d654 100644
--- a/Documentation/gpu/drm-compute.rst
+++ b/Documentation/gpu/drm-compute.rst
@@ -7,7 +7,7 @@ seconds. (The time let the user wait before he reaches for the power button).
 This means that other techniques need to be used to manage those workloads,
 that cannot use fences.
 
-Some hardware may schedule compute jobs, and have no way to pre-empt them, or
+Some hardware may schedule compute jobs, and have no way to preempt them, or
 have their memory swapped out from them. Or they simply want their workload
 not to be preempted or swapped out at all.
 
diff --git a/Documentation/gpu/drm-uapi.rst b/Documentation/gpu/drm-uapi.rst
index 579e87cb9..0ef498bff 100644
--- a/Documentation/gpu/drm-uapi.rst
+++ b/Documentation/gpu/drm-uapi.rst
@@ -150,10 +150,10 @@ separate render node called renderD<num>. There will be one render node
 per device. No ioctls except PRIME-related ioctls will be allowed on
 this node. Especially GEM_OPEN will be explicitly prohibited. For a
 complete list of driver-independent ioctls that can be used on render
-nodes, see the ioctls marked DRM_RENDER_ALLOW in drm_ioctl.c  Render
-nodes are designed to avoid the buffer-leaks, which occur if clients
+nodes, see the ioctls marked DRM_RENDER_ALLOW in drm_ioctl.c.  Render
+nodes are designed to avoid the buffer leaks, which occur if clients
 guess the flink names or mmap offsets on the legacy interface.
-Additionally to this basic interface, drivers must mark their
+In addition to this basic interface, drivers must mark their
 driver-dependent render-only ioctls as DRM_RENDER_ALLOW so render
 clients can use them. Driver authors must be careful not to allow any
 privileged ioctls on render nodes.
@@ -568,7 +568,7 @@ ENOSPC:
 EPERM/EACCES:
         Returned for an operation that is valid, but needs more privileges.
         E.g. root-only or much more common, DRM master-only operations return
-        this when called by unpriviledged clients. There's no clear
+        this when called by unprivileged clients. There's no clear
         difference between EACCES and EPERM.
 
 ENODEV:
@@ -761,4 +761,4 @@ Stable uAPI events
 From ``drivers/gpu/drm/scheduler/gpu_scheduler_trace.h``
 
 .. kernel-doc::  drivers/gpu/drm/scheduler/gpu_scheduler_trace.h
-   :doc: uAPI trace events
\ No newline at end of file
+   :doc: uAPI trace events
diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst
index bc9f14c8a..b13cd4347 100644
--- a/Documentation/gpu/todo.rst
+++ b/Documentation/gpu/todo.rst
@@ -55,7 +55,7 @@ There are still drivers that use drm_simple_display_pipe. The task here is to
 convert them to use regular atomic helpers. Search for a driver that calls
 drm_simple_display_pipe_init() and inline all helpers from drm_simple_kms_helper.c
 into the driver, such that no simple-KMS interfaces are required. Please also
-rename all inlined fucntions according to driver conventions.
+rename all inlined functions according to driver conventions.
 
 Contact: Thomas Zimmermann, respective driver maintainer
 
@@ -301,7 +301,7 @@ Various hold-ups:
   valid formats for atomic drivers.
 
 - Many drivers subclass drm_framebuffer, we'd need a embedding compatible
-  version of the varios drm_gem_fb_create functions. Maybe called
+  version of the various drm_gem_fb_create functions. Maybe called
   drm_gem_fb_create/_with_dirty/_with_funcs as needed.
 
 Contact: Simona Vetter
@@ -326,10 +326,9 @@ everything after it has done the write-protect/mkwrite trickery:
 
       vma->vm_page_prot = pgprot_wrprotect(vma->vm_page_prot);
 
-- Set the mkwrite and fsync callbacks with similar implementions to the core
+- Set the mkwrite and fsync callbacks with similar implementations to the core
   fbdev defio stuff. These should all work on plain ptes, they don't actually
-  require a struct page.  uff. These should all work on plain ptes, they don't
-  actually require a struct page.
+  require a struct page.
 
 - Track the dirty pages in a separate structure (bitfield with one bit per page
   should work) to avoid clobbering struct page.
@@ -914,7 +913,7 @@ Querying errors from drm_syncobj
 ================================
 
 The drm_syncobj container can be used by driver independent code to signal
-complection of submission.
+completion of submission.
 
 One minor feature still missing is a generic DRM IOCTL to query the error
 status of binary and timeline drm_syncobj.
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH v6 03/11] dt-bindings: mfd: add documentation for S2MU005 PMIC
From: Kaustabh Chakraborty @ 2026-05-17 13:09 UTC (permalink / raw)
  To: Conor Dooley, Kaustabh Chakraborty
  Cc: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
	Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
	Jonathan Corbet, Shuah Khan, Nam Tran,
	Łukasz Lebiedziński, linux-leds, devicetree,
	linux-kernel, linux-pm, linux-samsung-soc, linux-rtc, linux-doc
In-Reply-To: <20260516-esquire-chitchat-0fffa597e2f3@spud>

On 2026-05-16 23:25 +01:00, Conor Dooley wrote:
> On Sat, May 16, 2026 at 02:41:29AM +0530, Kaustabh Chakraborty wrote:
>> On 2026-05-15 18:14 +01:00, Conor Dooley wrote:
>> > On Fri, May 15, 2026 at 04:08:59PM +0530, Kaustabh Chakraborty wrote:
>> >> Samsung's S2MU005 PMIC includes subdevices for a charger, an MUIC (Micro
>> >> USB Interface Controller), and flash and RGB LED controllers.
>> >> 
>> >> Add the compatible and documentation for the S2MU005 PMIC. Also, add an
>> >> example for nodes for supported sub-devices, i.e. MUIC, flash LEDs, and
>> >> RGB LEDs. Charger sub-device uses the node of the parent.
>> >> 
>> >> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
>> >> ---
>> >>  .../bindings/mfd/samsung,s2mu005-pmic.yaml         | 120 +++++++++++++++++++++
>> >>  1 file changed, 120 insertions(+)
>> >> 
>> >> diff --git a/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml b/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
>> >> new file mode 100644
>> >> index 0000000000000..0e6afb7d2017b
>> >> --- /dev/null
>> >> +++ b/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
>> >> @@ -0,0 +1,120 @@
>> >> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
>> >> +%YAML 1.2
>> >> +---
>> >> +$id: http://devicetree.org/schemas/mfd/samsung,s2mu005-pmic.yaml#
>> >> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> >> +
>> >> +title: Samsung S2MU005 Power Management IC
>> >> +
>> >> +maintainers:
>> >> +  - Kaustabh Chakraborty <kauschluss@disroot.org>
>> >> +
>> >> +description: |
>> >> +  The S2MU005 is a companion power management IC which includes subdevices for
>> >> +  a charger controller, an MUIC (Micro USB Interface Controller), and flash and
>> >> +  RGB LED controllers.
>> >> +
>> >> +allOf:
>> >> +  - $ref: /schemas/power/supply/power-supply.yaml#
>> >> +
>> >> +properties:
>> >> +  compatible:
>> >> +    const: samsung,s2mu005-pmic
>> >> +
>> >> +  flash:
>> >> +    $ref: /schemas/leds/samsung,s2mu005-flash.yaml
>> >> +    description:
>> >> +      Child node describing flash LEDs.
>> >> +
>> >> +  interrupts:
>> >> +    maxItems: 1
>> >> +
>> >> +  muic:
>> >> +    $ref: /schemas/extcon/samsung,s2mu005-muic.yaml#
>> >> +    description:
>> >> +      Child node describing MUIC device.
>> >> +
>> >> +  multi-led:
>> >> +    type: object
>> >> +
>> >> +    allOf:
>> >> +      - $ref: /schemas/leds/leds-class-multicolor.yaml#
>> >
>> > Does this need to be an allOf when the other refs are not?
>> 
>> It has it's own properties, that's the reason. This used to be it's own
>> thing in dt-bindings/leds, but I was asked to move it here in prior
>> reviews.
>
> What do you mean by "its own properties"?

I mean, the other schemas (muic, flash) are in their own file, with
compatible, and other properties too.

This one, inherits properties from leds-class-multicolor, AND has a
"compatible" property with it, which is not defined in
leds-class-multicolor. Now if you ask why does the compatible exist,
that's something Krzysztof suggested in previous revisions.

And, Krzysztof had also reviewed this patch, and (similar to the prev
patch) I've missed the trailers, which have been addressed in v7 now.

>> 
>> >> +
>> >> +    properties:
>> >> +      compatible:
>> >> +        const: samsung,s2mu005-rgb
>> >> +
>> >> +    required:
>> >> +      - compatible
>> >> +
>> >> +    unevaluatedProperties: false
>> >> +
>> >> +  reg:
>> >> +    maxItems: 1
>> >
>> > Move this above the child nodes please.
>> 
>> But properties are sorted in lex order?
>
> Typically the binding is sorted in the same order as properties go in
> nodes. Common stuff like reg/clocks/interrupts therefore send up above
> child nodes.

So, do I change this? For one, I don't see the same being followed in
other schemas of samsung in the same dir (not that I'm trying to pose it
as an argument against your suggestion), and this was reviewed by
Krzysztof and is adderssed in v7.

^ permalink raw reply

* [PATCH v6 2/2] hwmon: add AMD Promontory 21 xHCI temperature sensor support
From: Jihong Min @ 2026-05-17 13:04 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Mathias Nyman
  Cc: Guenter Roeck, Jonathan Corbet, Shuah Khan, Mario Limonciello,
	Basavaraj Natikar, linux-usb, linux-hwmon, linux-doc, linux-pci,
	linux-kernel, Jihong Min, Mario Limonciello (AMD),
	Yaroslav Isakov
In-Reply-To: <20260517130407.795157-1-hurryman2212@gmail.com>

Add an auxiliary-bus hwmon driver for the temperature sensor exposed by
AMD Promontory 21 (PROM21) xHCI PCI functions. The driver binds to the
"hwmon" auxiliary device published by the PROM21 xHCI PCI glue and
exposes the sensor as temp1_input under the prom21_xhci hwmon device.

The sensor is accessed through a PROM21 vendor index/data register pair
in the xHCI PCI MMIO BAR. The driver consumes parent-provided MMIO data
from the PROM21 PCI glue instead of inspecting the parent PCI driver's
drvdata. The read path restores the previous vendor index value after
sampling and does not runtime-resume the parent PCI device; reads from a
suspended parent return -ENODATA.

Document the supported device, register access, runtime PM behavior, and
sysfs lookup method. The documentation also records the observation
method used to identify the register pair and derive the conversion
formula.

Assisted-by: Codex:gpt-5.5
Signed-off-by: Jihong Min <hurryman2212@gmail.com>
Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org>
Tested-by: Yaroslav Isakov <yaroslav.isakov@gmail.com>
---
 Documentation/hwmon/index.rst       |   1 +
 Documentation/hwmon/prom21-xhci.rst | 101 ++++++++++++
 drivers/hwmon/Kconfig               |  10 ++
 drivers/hwmon/Makefile              |   1 +
 drivers/hwmon/prom21-xhci.c         | 239 ++++++++++++++++++++++++++++
 5 files changed, 352 insertions(+)
 create mode 100644 Documentation/hwmon/prom21-xhci.rst
 create mode 100644 drivers/hwmon/prom21-xhci.c

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 8b655e5d6b68..324208f1faa2 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -216,6 +216,7 @@ Hardware Monitoring Kernel Drivers
    pmbus
    powerz
    powr1220
+   prom21-xhci
    pt5161l
    pxe1610
    pwm-fan
diff --git a/Documentation/hwmon/prom21-xhci.rst b/Documentation/hwmon/prom21-xhci.rst
new file mode 100644
index 000000000000..7984fb187bd8
--- /dev/null
+++ b/Documentation/hwmon/prom21-xhci.rst
@@ -0,0 +1,101 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver prom21-xhci
+=========================
+
+Supported chips:
+
+  * AMD Promontory 21 (PROM21) xHCI USB host controller
+
+    Prefix: 'prom21_xhci'
+
+    PCI IDs: 1022:43fc, 1022:43fd
+
+Author:
+
+  - Jihong Min <hurryman2212@gmail.com>
+
+Description
+-----------
+
+This driver exposes the temperature sensor in AMD PROM21 xHCI controllers.
+
+The driver binds to an auxiliary device created by the xHCI PCI driver for
+supported controllers. The sensor value is accessed through a vendor-specific
+index/data register pair in the controller's PCI MMIO BAR.
+The auxiliary device is created by the ``xhci-pci-prom21`` PCI glue driver.
+USB host operation is otherwise delegated to the common ``xhci-pci`` code.
+
+PROM21 is an AMD chipset IP used in single-chip or daisy-chained configurations
+to build AMD 6xx/8xx series chipsets. Since the xHCI controllers are
+integrated in PROM21, this temperature can also be used as a monitor for a
+temperature close to the AMD chipset temperature.
+
+Register access
+---------------
+
+The temperature value is read through a vendor-specific index/data register
+pair in the xHCI PCI MMIO BAR. The driver uses the following byte offsets from
+the MMIO BAR base:
+
+======================= =====================================================
+0x3000			Vendor index register
+0x3008			Vendor data register
+======================= =====================================================
+
+The driver saves the current vendor index register value, writes the
+temperature selector ``0x0001e520`` to the vendor index register, reads the
+vendor data register, and restores the previous vendor index value before
+returning. The raw temperature value is the low 8 bits of the vendor data
+register value.
+
+The hwmon core serializes this driver's callbacks, and the driver restores the
+previous index value after each read. This does not provide synchronization
+with firmware, SMM, ACPI AML, or any other user outside this driver.
+
+No public AMD reference is available for the register pair or the raw value.
+The register pair was identified on an X870E system with two PROM21 xHCI
+controllers. One controller was passed through to a Windows VM, and the same
+controller's PCI MMIO BAR was observed from the Linux host while HWiNFO64 was
+reporting the PROM21 xHCI temperature. In the test environment, the reported
+temperature was very stable at idle and the displayed sensor resolution was
+low, which made it possible to look for a consistently repeating MMIO response
+for the same reported temperature. During observation, offset 0x3000 repeatedly
+contained selector ``0x0001e520``. Writing the same selector to offset 0x3000
+from Linux and then reading offset 0x3008 reproduced the same raw value, so the
+offsets are treated as a vendor index/data register pair.
+
+The conversion formula was empirically inferred by matching observed raw
+8-bit values against HWiNFO64's reported PROM21 xHCI temperature for the same
+controller. The observed mapping is:
+
+  temp[C] = raw * 0.9066 - 78.624
+
+Runtime PM
+----------
+
+The driver does not wake the xHCI PCI device for hwmon reads. It reads the
+temperature only when the parent device is already active. A read from a
+suspended device returns ``-ENODATA``. After a successful read, the driver
+drops its active-only runtime PM reference and lets the PM core re-evaluate the
+idle state.
+
+Sysfs entries
+-------------
+
+======================= =====================================================
+temp1_input		Temperature in millidegrees Celsius
+======================= =====================================================
+
+The hwmon device name is ``prom21_xhci``. The sysfs path depends on the hwmon
+device number assigned by the kernel. Userspace can locate the device by
+matching the ``name`` attribute:
+
+.. code-block:: sh
+
+   for hwmon in /sys/class/hwmon/hwmon*; do
+           [ "$(cat "$hwmon/name")" = "prom21_xhci" ] || continue
+           cat "$hwmon/temp1_input"
+   done
+
+If the raw register value is invalid, ``temp1_input`` returns ``-ENODATA``.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 14e4cea48acc..fe0f14e247b5 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -951,6 +951,16 @@ config SENSORS_POWR1220
 	  This driver can also be built as a module. If so, the module
 	  will be called powr1220.
 
+config SENSORS_PROM21_XHCI
+	tristate "AMD Promontory 21 xHCI temperature sensor"
+	depends on USB_XHCI_PCI_PROM21
+	help
+	  If you say yes here you get support for the AMD Promontory 21
+	  (PROM21) xHCI temperature sensor.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called prom21-xhci.
+
 config SENSORS_LAN966X
 	tristate "Microchip LAN966x Hardware Monitoring"
 	depends on SOC_LAN966 || COMPILE_TEST
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 982ee2c6f9de..f833aed890d8 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -196,6 +196,7 @@ obj-$(CONFIG_SENSORS_PC87427)	+= pc87427.o
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
 obj-$(CONFIG_SENSORS_POWERZ)	+= powerz.o
 obj-$(CONFIG_SENSORS_POWR1220)  += powr1220.o
+obj-$(CONFIG_SENSORS_PROM21_XHCI)	+= prom21-xhci.o
 obj-$(CONFIG_SENSORS_PT5161L)	+= pt5161l.o
 obj-$(CONFIG_SENSORS_PWM_FAN)	+= pwm-fan.o
 obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON)	+= qnap-mcu-hwmon.o
diff --git a/drivers/hwmon/prom21-xhci.c b/drivers/hwmon/prom21-xhci.c
new file mode 100644
index 000000000000..d40d0c53ce45
--- /dev/null
+++ b/drivers/hwmon/prom21-xhci.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Promontory 21 xHCI Hwmon Implementation
+ * (only temperature monitoring is supported)
+ *
+ * This can be effectively used as the alternative chipset temperature monitor.
+ *
+ * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_data/usb-xhci-prom21.h>
+#include <linux/pm_runtime.h>
+
+#define PROM21_XHCI_INDEX_OFFSET	0x3000
+#define PROM21_XHCI_DATA_OFFSET		0x3008
+#define PROM21_XHCI_TEMP_SELECTOR	0x0001e520
+
+struct prom21_xhci {
+	struct pci_dev *pdev;
+	struct device *hwmon_dev;
+	void __iomem *regs;
+};
+
+static int prom21_xhci_pm_get(struct prom21_xhci *hwmon)
+{
+	struct device *dev = &hwmon->pdev->dev;
+	int ret;
+
+	/*
+	 * PROM21 temperature register access does not return a valid value while
+	 * the parent xHCI PCI function is suspended. Do not wake the device from
+	 * a hwmon read. On success, hold a usage reference without changing the
+	 * runtime PM state; if runtime PM is disabled, allow the read unless the
+	 * device is still marked suspended.
+	 */
+	ret = pm_runtime_get_if_active(dev);
+	if (ret > 0)
+		return 0;
+
+	if (ret == -EINVAL) {
+		if (pm_runtime_status_suspended(dev))
+			return -ENODATA;
+
+		pm_runtime_get_noresume(dev);
+		return 0;
+	}
+
+	if (!ret)
+		return -ENODATA;
+
+	return ret;
+}
+
+/*
+ * This is not a pure MMIO read. The PROM21 vendor data register is selected
+ * by temporarily writing PROM21_XHCI_TEMP_SELECTOR to the vendor index
+ * register.
+ * The hwmon core already serializes this driver's callbacks, so this driver
+ * does not need an additional private lock. That does not synchronize with
+ * firmware, SMM, ACPI, or other possible users. Keep the sequence short and
+ * restore the previous index before returning.
+ */
+static int prom21_xhci_read_temp_raw_restore_index(struct prom21_xhci *hwmon,
+						   u8 *raw)
+{
+	struct device *dev = &hwmon->pdev->dev;
+	u32 index;
+	u8 data;
+	int ret;
+
+	ret = prom21_xhci_pm_get(hwmon);
+	if (ret)
+		return ret;
+
+	index = readl(hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
+	/* Select the PROM21 temperature register through the vendor index. */
+	writel(PROM21_XHCI_TEMP_SELECTOR,
+	       hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
+	/* Use a 32-bit read for PCI MMIO register access. */
+	data = readl(hwmon->regs + PROM21_XHCI_DATA_OFFSET) & 0xff;
+	/* Restore the previous vendor index register value. */
+	writel(index, hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
+	readl(hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
+
+	/*
+	 * Drop the usage reference taken by prom21_xhci_pm_get(). This is
+	 * enough because the read path never resumes the device; use the normal
+	 * put path so the PM core can re-evaluate idle state after the read.
+	 * Otherwise, a racing xHCI autosuspend attempt can see a nonzero
+	 * runtime PM usage count and skip autosuspend, and a later
+	 * pm_runtime_put_noidle(), which does not check for an idle device,
+	 * would leave the device active.
+	 */
+	pm_runtime_put(dev);
+
+	if (!data)
+		return -ENODATA;
+
+	*raw = data;
+	return 0;
+}
+
+static long prom21_xhci_raw_to_millicelsius(u8 raw)
+{
+	/*
+	 * No public AMD reference is available for this value.
+	 * The scale was derived from observed PROM21 xHCI temperature readings:
+	 *  temp[C] = raw * 0.9066 - 78.624
+	 */
+	return DIV_ROUND_CLOSEST(raw * 9066, 10) - 78624;
+}
+
+static umode_t prom21_xhci_is_visible(const void *drvdata,
+				      enum hwmon_sensor_types type, u32 attr,
+				      int channel)
+{
+	if (type != hwmon_temp)
+		return 0;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		return 0444;
+	default:
+		return 0;
+	}
+}
+
+static int prom21_xhci_read(struct device *dev, enum hwmon_sensor_types type,
+			    u32 attr, int channel, long *val)
+{
+	struct prom21_xhci *hwmon = dev_get_drvdata(dev);
+	u8 raw;
+	int ret;
+
+	if (type != hwmon_temp || attr != hwmon_temp_input)
+		return -EOPNOTSUPP;
+
+	ret = prom21_xhci_read_temp_raw_restore_index(hwmon, &raw);
+	if (ret)
+		return ret;
+
+	*val = prom21_xhci_raw_to_millicelsius(raw);
+	return 0;
+}
+
+static const struct hwmon_ops prom21_xhci_ops = {
+	.is_visible = prom21_xhci_is_visible,
+	.read = prom21_xhci_read,
+};
+
+static const struct hwmon_channel_info *const prom21_xhci_info[] = {
+	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+	NULL,
+};
+
+static const struct hwmon_chip_info prom21_xhci_chip_info = {
+	.ops = &prom21_xhci_ops,
+	.info = prom21_xhci_info,
+};
+
+static int prom21_xhci_probe(struct auxiliary_device *auxdev,
+			     const struct auxiliary_device_id *id)
+{
+	struct device *dev = &auxdev->dev;
+	const struct prom21_xhci_pdata *pdata = dev_get_platdata(dev);
+	struct prom21_xhci *hwmon;
+
+	if (!pdata)
+		return dev_err_probe(dev, -ENODEV,
+				     "platform data unavailable\n");
+
+	if (!pdata->regs ||
+	    pdata->rsrc_len < PROM21_XHCI_DATA_OFFSET + sizeof(u32))
+		return dev_err_probe(dev, -ENODEV, "invalid MMIO resource\n");
+
+	hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
+	if (!hwmon)
+		return -ENOMEM;
+
+	hwmon->pdev = pdata->pdev;
+	hwmon->regs = pdata->regs;
+	auxiliary_set_drvdata(auxdev, hwmon);
+
+	/*
+	 * Parent the hwmon device to the PCI function because the temperature
+	 * value is read from that function's MMIO BAR, and systems may contain
+	 * multiple PROM21 xHCI functions. This lets userspace identify the PCI
+	 * endpoint for each reading. The auxiliary driver still owns the hwmon
+	 * lifetime and unregisters it before HCD teardown.
+	 */
+	hwmon->hwmon_dev =
+		hwmon_device_register_with_info(&pdata->pdev->dev, "prom21_xhci",
+						hwmon, &prom21_xhci_chip_info,
+						NULL);
+	if (IS_ERR(hwmon->hwmon_dev))
+		return PTR_ERR(hwmon->hwmon_dev);
+
+	return 0;
+}
+
+static void prom21_xhci_remove(struct auxiliary_device *auxdev)
+{
+	struct prom21_xhci *hwmon = auxiliary_get_drvdata(auxdev);
+
+	/*
+	 * The PROM21 PCI glue destroys the auxiliary device before HCD teardown.
+	 * Unregister the hwmon device here so sysfs removes the attributes,
+	 * stops new reads, and drains active hwmon callbacks before the xHCI
+	 * MMIO mapping is released.
+	 */
+	hwmon_device_unregister(hwmon->hwmon_dev);
+}
+
+static const struct auxiliary_device_id prom21_xhci_id_table[] = {
+	{ .name = "xhci_pci_prom21.hwmon" },
+	{}
+};
+MODULE_DEVICE_TABLE(auxiliary, prom21_xhci_id_table);
+
+static struct auxiliary_driver prom21_xhci_driver = {
+	.name = "prom21-xhci",
+	.probe = prom21_xhci_probe,
+	.remove = prom21_xhci_remove,
+	.id_table = prom21_xhci_id_table,
+};
+module_auxiliary_driver(prom21_xhci_driver);
+
+MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>");
+MODULE_DESCRIPTION("AMD Promontory 21 xHCI temperature sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.53.0


^ permalink raw reply related

* [PATCH v6 1/2] usb: xhci-pci: add AMD Promontory 21 PCI glue
From: Jihong Min @ 2026-05-17 13:04 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Mathias Nyman
  Cc: Guenter Roeck, Jonathan Corbet, Shuah Khan, Mario Limonciello,
	Basavaraj Natikar, linux-usb, linux-hwmon, linux-doc, linux-pci,
	linux-kernel, Jihong Min, Mario Limonciello (AMD),
	Yaroslav Isakov
In-Reply-To: <20260517130407.795157-1-hurryman2212@gmail.com>

AMD Promontory 21 (PROM21) xHCI controllers use generic xHCI
operation, but the PCI function also exposes optional
controller-specific sensor functionality. Add a small PROM21 PCI glue
driver for AMD 1022:43fc and 1022:43fd controllers.

The driver delegates USB host operation to the common xhci-pci core,
collects the parent-provided MMIO resource data, and creates a "hwmon"
auxiliary device for optional child drivers. Failure to create the
auxiliary device is logged but does not fail the xHCI probe, since the
auxiliary device is only needed for sensor support.

Make the PROM21 PCI glue a hidden Kconfig tristate that follows
USB_XHCI_PCI. This keeps the glue built in with a built-in xhci-pci core
and builds it as a module with a modular xhci-pci core. A built-in
xhci-pci core must not hand PROM21 controllers to a PROM21 glue driver
that is only available as a module, otherwise USB behind those controllers
can be unavailable during initramfs and PROM21 temperature sensor support
may not appear until the controller is rebound after the module loads.

Assisted-by: Codex:gpt-5.5
Signed-off-by: Jihong Min <hurryman2212@gmail.com>
Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org>
Tested-by: Yaroslav Isakov <yaroslav.isakov@gmail.com>
---
 drivers/usb/host/Kconfig                      |   7 +
 drivers/usb/host/Makefile                     |   1 +
 drivers/usb/host/xhci-pci-prom21.c            | 136 ++++++++++++++++++
 drivers/usb/host/xhci-pci.c                   |  11 ++
 drivers/usb/host/xhci-pci.h                   |   3 +
 include/linux/platform_data/usb-xhci-prom21.h |  22 +++
 6 files changed, 180 insertions(+)
 create mode 100644 drivers/usb/host/xhci-pci-prom21.c
 create mode 100644 include/linux/platform_data/usb-xhci-prom21.h

diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 0a277a07cf70..89bf262235e1 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -42,6 +42,13 @@ config USB_XHCI_PCI
 	depends on USB_PCI
 	default y
 
+config USB_XHCI_PCI_PROM21
+	tristate
+	depends on X86
+	depends on USB_XHCI_PCI
+	default USB_XHCI_PCI
+	select AUXILIARY_BUS
+
 config USB_XHCI_PCI_RENESAS
 	tristate "Support for additional Renesas xHCI controller with firmware"
 	depends on USB_XHCI_PCI
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index a07e7ba9cd53..174580c1281a 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_USB_UHCI_HCD)	+= uhci-hcd.o
 obj-$(CONFIG_USB_FHCI_HCD)	+= fhci.o
 obj-$(CONFIG_USB_XHCI_HCD)	+= xhci-hcd.o
 obj-$(CONFIG_USB_XHCI_PCI)	+= xhci-pci.o
+obj-$(CONFIG_USB_XHCI_PCI_PROM21)	+= xhci-pci-prom21.o
 obj-$(CONFIG_USB_XHCI_PCI_RENESAS)	+= xhci-pci-renesas.o
 obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
 obj-$(CONFIG_USB_XHCI_HISTB)	+= xhci-histb.o
diff --git a/drivers/usb/host/xhci-pci-prom21.c b/drivers/usb/host/xhci-pci-prom21.c
new file mode 100644
index 000000000000..be0933ca5c62
--- /dev/null
+++ b/drivers/usb/host/xhci-pci-prom21.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Promontory 21 xHCI host controller PCI Bus Glue.
+ *
+ * This does not add any PROM21-specific USB or xHCI operation. It exists only
+ * to publish an auxiliary device for integrated temperature sensor support.
+ *
+ * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/device/devres.h>
+#include <linux/errno.h>
+#include <linux/idr.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_data/usb-xhci-prom21.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "xhci-pci.h"
+
+struct prom21_xhci_auxdev {
+	struct auxiliary_device *auxdev;
+	struct prom21_xhci_pdata pdata;
+	int id;
+};
+
+static DEFINE_IDA(prom21_xhci_auxdev_ida);
+
+static void prom21_xhci_auxdev_release(struct device *dev, void *res)
+{
+	struct prom21_xhci_auxdev *prom21_auxdev = res;
+
+	auxiliary_device_destroy(prom21_auxdev->auxdev);
+	ida_free(&prom21_xhci_auxdev_ida, prom21_auxdev->id);
+}
+
+static int prom21_xhci_create_auxdev(struct pci_dev *pdev)
+{
+	struct prom21_xhci_auxdev *prom21_auxdev;
+	struct usb_hcd *hcd = pci_get_drvdata(pdev);
+
+	if (!hcd)
+		return -ENODEV;
+
+	prom21_auxdev = devres_alloc(prom21_xhci_auxdev_release,
+				     sizeof(*prom21_auxdev), GFP_KERNEL);
+	if (!prom21_auxdev)
+		return -ENOMEM;
+
+	prom21_auxdev->pdata.pdev = pdev;
+	prom21_auxdev->pdata.regs = hcd->regs;
+	prom21_auxdev->pdata.rsrc_len = hcd->rsrc_len;
+
+	prom21_auxdev->id = ida_alloc(&prom21_xhci_auxdev_ida, GFP_KERNEL);
+	if (prom21_auxdev->id < 0) {
+		int ret = prom21_auxdev->id;
+
+		devres_free(prom21_auxdev);
+		return ret;
+	}
+
+	prom21_auxdev->auxdev = auxiliary_device_create(&pdev->dev,
+							KBUILD_MODNAME, "hwmon",
+							&prom21_auxdev->pdata,
+							prom21_auxdev->id);
+	if (!prom21_auxdev->auxdev) {
+		ida_free(&prom21_xhci_auxdev_ida, prom21_auxdev->id);
+		devres_free(prom21_auxdev);
+		return -ENOMEM;
+	}
+
+	devres_add(&pdev->dev, prom21_auxdev);
+	return 0;
+}
+
+static void prom21_xhci_destroy_auxdev(struct pci_dev *pdev)
+{
+	devres_release(&pdev->dev, prom21_xhci_auxdev_release, NULL, NULL);
+}
+
+static int prom21_xhci_probe(struct pci_dev *dev,
+			     const struct pci_device_id *id)
+{
+	int retval;
+
+	retval = xhci_pci_common_probe(dev, id);
+	if (retval)
+		return retval;
+
+	retval = prom21_xhci_create_auxdev(dev);
+	if (retval) {
+		/*
+		 * The auxiliary device only provides optional temperature sensor
+		 * support. Keep the xHCI controller usable if it fails.
+		 */
+		dev_err(&dev->dev,
+			"failed to create PROM21 hwmon auxiliary device: %d\n",
+			retval);
+	}
+
+	return 0;
+}
+
+static void prom21_xhci_remove(struct pci_dev *dev)
+{
+	prom21_xhci_destroy_auxdev(dev);
+	xhci_pci_remove(dev);
+}
+
+static const struct pci_device_id pci_ids[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD) },
+	{ /* end: all zeroes */ }
+};
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+static struct pci_driver prom21_xhci_driver = {
+	.name = "xhci-pci-prom21",
+	.id_table = pci_ids,
+
+	.probe = prom21_xhci_probe,
+	.remove = prom21_xhci_remove,
+
+	.shutdown = usb_hcd_pci_shutdown,
+	.driver = {
+		.pm = pm_ptr(&usb_hcd_pci_pm_ops),
+	},
+};
+module_pci_driver(prom21_xhci_driver);
+
+MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>");
+MODULE_DESCRIPTION("AMD Promontory 21 xHCI PCI Host Controller Driver");
+MODULE_IMPORT_NS("xhci");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 585b2f3117b0..039c26b241d0 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -696,12 +696,23 @@ static const struct pci_device_id pci_ids_renesas[] = {
 	{ /* end: all zeroes */ }
 };
 
+/* handled by xhci-pci-prom21 if enabled */
+static const struct pci_device_id pci_ids_prom21[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD) },
+	{ /* end: all zeroes */ }
+};
+
 static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
 {
 	if (IS_ENABLED(CONFIG_USB_XHCI_PCI_RENESAS) &&
 			pci_match_id(pci_ids_renesas, dev))
 		return -ENODEV;
 
+	if (IS_ENABLED(CONFIG_USB_XHCI_PCI_PROM21) &&
+	    pci_match_id(pci_ids_prom21, dev))
+		return -ENODEV;
+
 	return xhci_pci_common_probe(dev, id);
 }
 
diff --git a/drivers/usb/host/xhci-pci.h b/drivers/usb/host/xhci-pci.h
index e87c7d9d76b8..11f435f94322 100644
--- a/drivers/usb/host/xhci-pci.h
+++ b/drivers/usb/host/xhci-pci.h
@@ -4,6 +4,9 @@
 #ifndef XHCI_PCI_H
 #define XHCI_PCI_H
 
+#define PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC	0x43fc
+#define PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD	0x43fd
+
 int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id);
 void xhci_pci_remove(struct pci_dev *dev);
 
diff --git a/include/linux/platform_data/usb-xhci-prom21.h b/include/linux/platform_data/usb-xhci-prom21.h
new file mode 100644
index 000000000000..ee672ad452a8
--- /dev/null
+++ b/include/linux/platform_data/usb-xhci-prom21.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AMD Promontory 21 xHCI auxiliary device platform data.
+ *
+ * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com>
+ */
+
+#ifndef _LINUX_PLATFORM_DATA_USB_XHCI_PROM21_H
+#define _LINUX_PLATFORM_DATA_USB_XHCI_PROM21_H
+
+#include <linux/compiler_types.h>
+#include <linux/types.h>
+
+struct pci_dev;
+
+struct prom21_xhci_pdata {
+	struct pci_dev *pdev;
+	void __iomem *regs;
+	resource_size_t rsrc_len;
+};
+
+#endif
-- 
2.53.0


^ permalink raw reply related

* [PATCH v6 0/2] AMD Promontory 21 xHCI temperature sensor support
From: Jihong Min @ 2026-05-17 13:04 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Mathias Nyman
  Cc: Guenter Roeck, Jonathan Corbet, Shuah Khan, Mario Limonciello,
	Basavaraj Natikar, linux-usb, linux-hwmon, linux-doc, linux-pci,
	linux-kernel, Jihong Min

Hi,

This series adds temperature monitoring for AMD Promontory 21 (PROM21)
xHCI PCI functions.

Patch 1 adds a small PROM21-specific xHCI PCI glue driver. USB host
operation is delegated to the common xhci-pci code, while the PROM21 glue
publishes an auxiliary device for optional sensor support.

Patch 2 adds an auxiliary-bus hwmon driver that binds to that auxiliary
device and exposes the PROM21 xHCI temperature value as temp1_input.

The hwmon driver reads the sensor through a vendor index/data register pair
in the xHCI PCI MMIO BAR. It does not wake the parent PCI device for hwmon
reads; if the parent is suspended, the read returns -ENODATA.

Changes in v6:
- Make USB_XHCI_PCI_PROM21 a hidden tristate that follows USB_XHCI_PCI,
  so the PROM21 PCI glue is built in with a built-in xhci-pci core and
  built as a module with a modular xhci-pci core.
- Use an IDA-allocated auxiliary device id instead of encoding the PCI
  domain/BDF into the auxiliary id.
- Use a 32-bit read for the PROM21 vendor data register and mask the low
  byte instead of using readb().

Jihong Min (2):
  usb: xhci-pci: add AMD Promontory 21 PCI glue
  hwmon: add AMD Promontory 21 xHCI temperature sensor support

 Documentation/hwmon/index.rst                 |   1 +
 Documentation/hwmon/prom21-xhci.rst           | 101 ++++++++
 drivers/hwmon/Kconfig                         |  10 +
 drivers/hwmon/Makefile                        |   1 +
 drivers/hwmon/prom21-xhci.c                   | 239 ++++++++++++++++++
 drivers/usb/host/Kconfig                      |   7 +
 drivers/usb/host/Makefile                     |   1 +
 drivers/usb/host/xhci-pci-prom21.c            | 136 ++++++++++
 drivers/usb/host/xhci-pci.c                   |  11 +
 drivers/usb/host/xhci-pci.h                   |   3 +
 include/linux/platform_data/usb-xhci-prom21.h |  22 ++
 11 files changed, 532 insertions(+)
 create mode 100644 Documentation/hwmon/prom21-xhci.rst
 create mode 100644 drivers/hwmon/prom21-xhci.c
 create mode 100644 drivers/usb/host/xhci-pci-prom21.c
 create mode 100644 include/linux/platform_data/usb-xhci-prom21.h

-- 
2.53.0

^ permalink raw reply

* [PATCH] docs: hwmon: htu31: document debugfs serial_number
From: Chen-Shi-Hong @ 2026-05-17 12:52 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Jonathan Corbet, Shuah Khan, linux-hwmon, linux-doc, linux-kernel,
	Chen-Shi-Hong

Document the debugfs serial_number file exposed by the htu31 driver.

The driver creates a debugfs entry for the sensor serial number, but
the documentation currently only describes the sysfs interface.

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

diff --git a/Documentation/hwmon/htu31.rst b/Documentation/hwmon/htu31.rst
index ccde84264643..9ab774dcf65d 100644
--- a/Documentation/hwmon/htu31.rst
+++ b/Documentation/hwmon/htu31.rst
@@ -35,3 +35,10 @@ temp1_input:        temperature input
 humidity1_input:    humidity input
 heater_enable:      heater control
 =================== =================
+
+debugfs-Interface
+-----------------
+
+=================== =========================================
+serial_number:      unique serial number of the sensor
+=================== =========================================
-- 
2.53.0


^ permalink raw reply related

* Re: [RFC PATCH v3 2/3] Documentation: add kconfirm
From: Miguel Ojeda @ 2026-05-17 12:35 UTC (permalink / raw)
  To: Nathan Chancellor
  Cc: Julian Braha, nsc, jani.nikula, akpm, gary, ljs, arnd, gregkh,
	masahiroy, ojeda, corbet, qingfang.deng, yann.prono, demiobenour,
	ej, linux-kernel, rust-for-linux, linux-doc, linux-kbuild
In-Reply-To: <20260517094041.GC3773662@ax162>

On Sun, May 17, 2026 at 11:40 AM Nathan Chancellor <nathan@kernel.org> wrote:
>
> I guess this is kind of a weird/unique situation. I agree that the files
> generated by 'cargo run' should absolutely be contained within the build
> folder; at that point, $(srctree) could be read only and I would
> consider it rude not to respect the user's choice of build directory.
> For 'cargo vendor' however, I am not sure. They are source files and I
> would expect that running 'cargo vendor' would be more considered part
> of preparing the source tree, rather than the build one (so it should
> not be read only).

That would simplify things, yeah. We could always start there and see
if someone needs it.

> At the same time, it might be safer for dependency updates and internal
> consistency that they are confined to the build folder. I guess we would
> only want to remove them with a 'distclean', rather than 'mrproper' or
> 'clean', in that case, to avoid requiring users to constantly run
> 'cargo vendor'. It might be more ergonomic for this to be a Kbuild
> target ('kconfirmvendor'?) so that this could be handled automatically
> based on the user's build command.

Yeah, it is a bit painful to not have the usual Kbuild
variables/infrastructure around... On the other hand, it is a nice
property to know that nothing called via `make` will ever connect (or
need to connect) to the Internet.

Hmm... Perhaps a good middle ground would be having something in the
name that makes it obvious it will connect, e.g. `fetch` like Git? Or,
if people feel strongly about the property mentioned, then something
like an environment variable that needs to be set to allow it (with a
message printed about it if it is not set).

If this were allowed, i.e. if we are OK having things in `make` that
fetch stuff and put it in the build folder (only in certain targets,
of course), then we could actually think about doing more things that
we didn't so far, such as other setup-like targets, e.g. preparing
kernel.org toolchains, setting up a Rust toolchain via `rustup`
(including `bindgen` etc.), and so on and so forth.

> Additionally, can we detect explicitly when dependencies are not
> properly vendored and error with a more helpful error message? The build
> command in patch 1 just throws up its hands when the build fails and
> asks if the dependencies have been set up but if we provided our own
> vendoring build target, we could add some canary that says we vendored
> successfully and if that is not present, error before even running the
> build and say "hey, you need to explicitly run this target before you
> build".

+1, good error messages help a lot. Something like `rustavailable`
that prints which particular thing is missing is great (that one even
tries to warn about some problematic versions testing for bugs --
hopefully we don't need `autoconf`... :).

Cheers,
Miguel

^ permalink raw reply

* Re: [PATCH v11 6/6] docs: iio: adc: ad4691: add driver documentation
From: Jonathan Cameron @ 2026-05-17 12:32 UTC (permalink / raw)
  To: David Lechner
  Cc: radu.sabau, Lars-Peter Clausen, Michael Hennerich, 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,
	linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc
In-Reply-To: <78d4bb1c-4c6d-4781-86ee-458579ac6990@baylibre.com>

On Sat, 16 May 2026 13:18:03 -0500
David Lechner <dlechner@baylibre.com> wrote:

> On 5/15/26 8:31 AM, Radu Sabau via B4 Relay wrote:
> > From: Radu Sabau <radu.sabau@analog.com>
> > 
> > Add RST documentation for the AD4691 family ADC driver covering
> > supported devices, IIO channels, operating modes, oversampling,
> > reference voltage, LDO supply, reset, GP pins, SPI offload support,
> > and buffer data format.
> > 
> > Signed-off-by: Radu Sabau <radu.sabau@analog.com>
FWIW I finished giving the series another look and mostly didn't have
anything to add to David's review!  So subject to further discussion
on the feedback (and maybe a day or two to see if others want to enter
the fray), looking forward to v12. Fingers crossed that's good to go.

Thanks,

Jonathan


^ 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