* Re: [PATCH] Documentation: PCI: Document decoding of TLP Header in AER messages
From: Mika Westerberg @ 2026-03-23 11:03 UTC (permalink / raw)
To: Lukas Wunner
Cc: Bjorn Helgaas, Jonathan Corbet, linux-pci, linux-doc,
Ilpo Jarvinen, Maciej Grochowski, Kai-Heng Feng
In-Reply-To: <bf826c41b4c1d255c7dcb16e266b52f774d944ed.1774246067.git.lukas@wunner.de>
On Mon, Mar 23, 2026 at 07:52:39AM +0100, Lukas Wunner wrote:
> The prefix/header of the TLP that caused an error is recorded by the Root
> Complex and emitted to the kernel log in raw hex format. Document the
> existence and usage of tlp-tool, which allows decoding the TLP Header
> into human-readable form.
>
> The TLP Header hints at the root cause of an error, yet is often ignored
> because of its seeming opaqueness. Instead, PCIe errors are frequently
> worked around by a change in the kernel without fully understanding the
> actual source of the problem. With more documentation on available tools
> we'll hopefully come up with better solutions.
>
> There are also wireshark dissectors for TLPs, but it seems they expect a
> complete TLP, not just the header, and they cannot grok the hex format
> emitted by the kernel directly. tlp-tool appears to be the most cut and
> dried solution out there.
>
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
> Cc: Maciej Grochowski <mx2pg@pm.me>
Good idea, this is useful.
Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
^ permalink raw reply
* Re: [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support
From: Andy Shevchenko @ 2026-03-23 11:01 UTC (permalink / raw)
To: Rodrigo Alencar
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan
In-Reply-To: <oqspz57p3xgzeq2lvcvhgihu3yeucufdjxurwc3dpuvj6dfu6j@kv23vp6xxud3>
On Mon, Mar 23, 2026 at 10:39:06AM +0000, Rodrigo Alencar wrote:
> On 26/03/18 08:28PM, Andy Shevchenko wrote:
> > On Wed, Mar 18, 2026 at 05:56:03PM +0000, Rodrigo Alencar via B4 Relay wrote:
> >
> > > Add parallel port channel with frequency scale, frequency offset, phase
> > > offset, and amplitude offset extended attributes for configuring the
> > > parallel data path.
...
> > > + case IIO_CHAN_INFO_ENABLE:
> > > + val = !!val;
> >
> > Only used once, why do we need this...
>
> Next patches introduce more channels here, so the additions are easier to review.
Yeah, but shouldn't be better to put this in each FIELD_PREP() as it will
immediately show the correctness and the value range without looking backwards
in the code?
> > > + switch (chan->channel) {
> > > + case AD9910_CHANNEL_PARALLEL_PORT:
> > > + tmp32 = FIELD_PREP(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, val);
> >
> > ...and not just here?
> >
> > > + return ad9910_reg32_update(st, AD9910_REG_CFR2,
> > > + AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
> > > + tmp32, true);
> > > + default:
> > > + return -EINVAL;
> > > + }
>
> --
> Kind regards,
>
> Rodrigo Alencar
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH RFC v2 2/9] iio: frequency: ad9910: initial driver implementation
From: Andy Shevchenko @ 2026-03-23 11:00 UTC (permalink / raw)
To: Rodrigo Alencar
Cc: Jonathan Cameron, Rodrigo Alencar via B4 Relay, rodrigo.alencar,
linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
In-Reply-To: <grjlyz76cmjy7t3ytwycaklauipbqcj7adjr2t7k3axwlayowr@4zbuzehcrpvs>
On Mon, Mar 23, 2026 at 10:34:37AM +0000, Rodrigo Alencar wrote:
> On 26/03/22 04:50PM, Jonathan Cameron wrote:
> > On Wed, 18 Mar 2026 17:56:02 +0000
> > Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
...
> > > +#include <linux/array_size.h>
> > > +#include <linux/bitfield.h>
> > > +#include <linux/clk.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/device.h>
> >
> > Generally can avoid including device.h in favour of more specific
> > headers. There are a few exceptions where we can't such as actual
> > dereferencing of struct device, but I don't recall seeing a case in here.
>
> I understood that the usage of devm_add_action_or_reset() would justify
> the header.
It's in the device/devres.h.
...
> > > + st->data.pll_enabled = device_property_read_bool(dev, "adi,pll-enable");
> > > + if (st->data.pll_enabled) {
> > > + tmp = AD9910_ICP_MAX_uA;
> >
> > Defaulting to max current seems unusual.
Agree.
> > What's the motivation? Normal instinct is go minimum if no other info.
>
> ICP_MAX_uA leads to 111 in the CFR3_ICP field, which is the default value
> when the device resets or when it powers on. I suppose that if we are not
> touching that property, there would be no reason to change that.
I believe we should think different, id est about potential damages or
current drain. I would expect a minimum or hi-impedance (power off) state
of the related part of the device.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* [PATCH v2] Documentation: locking: Document hwspinlock bust() callback
From: Antonio Borneo @ 2026-03-23 10:58 UTC (permalink / raw)
To: Bjorn Andersson, Baolin Wang, Peter Zijlstra, Ingo Molnar,
Will Deacon, Boqun Feng, Waiman Long, Jonathan Corbet, Shuah Khan
Cc: Antonio Borneo, linux-remoteproc, linux-kernel, linux-doc,
linux-stm32
Add the missing callback and the corresponding description in the
relevant chapter.
Signed-off-by: Antonio Borneo <antonio.borneo@foss.st.com>
---
Changes in v2:
- align with review from sashiko.dev:
http://sashiko.dev/#/patchset/20260314170142.18290-1-antonio.borneo%40foss.st.com
- update the number of callbacks;
- report the returned value;
- report it can sleep.
- rebase on v7.0-rc5
- v1 available at:
https://lore.kernel.org/lkml/20260314170142.18290-1-antonio.borneo@foss.st.com/
---
Documentation/locking/hwspinlock.rst | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/Documentation/locking/hwspinlock.rst b/Documentation/locking/hwspinlock.rst
index a737c702a7d1d..a5fff33181c1b 100644
--- a/Documentation/locking/hwspinlock.rst
+++ b/Documentation/locking/hwspinlock.rst
@@ -420,11 +420,12 @@ initialized by the hwspinlock core itself.
Implementation callbacks
========================
-There are three possible callbacks defined in 'struct hwspinlock_ops'::
+There are four possible callbacks defined in 'struct hwspinlock_ops'::
struct hwspinlock_ops {
int (*trylock)(struct hwspinlock *lock);
void (*unlock)(struct hwspinlock *lock);
+ int (*bust)(struct hwspinlock *lock, unsigned int id);
void (*relax)(struct hwspinlock *lock);
};
@@ -436,6 +437,11 @@ return 0 on failure and 1 on success. This callback may **not** sleep.
The ->unlock() callback releases the lock. It always succeed, and it, too,
may **not** sleep.
+The ->bust() callback is optional. It is called by hwspinlock core to bust a
+specific lock when the remote processor 'id' is not responding, e.g. due to a
+firmware crash. It return 0 on success and a negative error code on failure.
+It can sleep.
+
The ->relax() callback is optional. It is called by hwspinlock core while
spinning on a lock, and can be used by the underlying implementation to force
a delay between two successive invocations of ->trylock(). It may **not** sleep.
base-commit: c369299895a591d96745d6492d4888259b004a9e
--
2.34.1
^ permalink raw reply related
* Re: [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support
From: Rodrigo Alencar @ 2026-03-23 10:39 UTC (permalink / raw)
To: Andy Shevchenko, rodrigo.alencar
Cc: linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan
In-Reply-To: <abru0mNtpJSPSJux@ashevche-desk.local>
On 26/03/18 08:28PM, Andy Shevchenko wrote:
> On Wed, Mar 18, 2026 at 05:56:03PM +0000, Rodrigo Alencar via B4 Relay wrote:
>
> > Add parallel port channel with frequency scale, frequency offset, phase
> > offset, and amplitude offset extended attributes for configuring the
> > parallel data path.
...
> > + case IIO_CHAN_INFO_ENABLE:
> > + val = !!val;
>
> Only used once, why do we need this...
Next patches introduce more channels here, so the additions are easier to review.
> > + switch (chan->channel) {
> > + case AD9910_CHANNEL_PARALLEL_PORT:
> > + tmp32 = FIELD_PREP(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, val);
>
> ...and not just here?
>
> > + return ad9910_reg32_update(st, AD9910_REG_CFR2,
> > + AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
> > + tmp32, true);
> > + default:
> > + return -EINVAL;
> > + }
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply
* Re: [PATCH RFC v2 2/9] iio: frequency: ad9910: initial driver implementation
From: Rodrigo Alencar @ 2026-03-23 10:34 UTC (permalink / raw)
To: Jonathan Cameron, Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
In-Reply-To: <20260322165058.6c4b193e@jic23-huawei>
On 26/03/22 04:50PM, Jonathan Cameron wrote:
> On Wed, 18 Mar 2026 17:56:02 +0000
> 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>
...
> > +#include <linux/array_size.h>
> > +#include <linux/bitfield.h>
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
>
> Generally can avoid including device.h in favour of more specific
> headers. There are a few exceptions where we can't such as actual
> dereferencing of struct device, but I don't recall seeing a case in here.
I understood that the usage of devm_add_action_or_reset() would justify
the header.
...
> > +#define AD9910_EXT_INFO(_name, _ident, _shared) { \
> > + .name = _name, \
> > + .read = ad9910_ext_info_read, \
> > + .write = ad9910_ext_info_write, \
> > + .private = _ident, \
> > + .shared = _shared, \
>
> If there are only a few of these, I'd put it long hand rather than
> using a macro. Tends to end up easier to read.
Next patches will leverage the macro as more ext_info attrs will be introduced.
I suppose we can build the foundation for later extension.
> > +}
> > +
> > +static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = {
> > + AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SEPARATE),
> > + AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
> > + { }
> > +};
>
> > +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_FREQUENCY:
> > + switch (chan->channel) {
> > + case AD9910_CHANNEL_SINGLE_TONE:
>
> I haven't read on yet, but if you never have any other cases in here,
> perhaps us an if() as it will reduce indent of the code that follows.
Similar, other channels will be introduced here so additions are easier
to review.
> > + tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
> > + st->reg[AD9910_REG_PROFILE(st->profile)].val64);
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > + tmp64 = (u64)tmp32 * st->data.sysclk_freq_hz;
> > + *val = upper_32_bits(tmp64);
> > + *val2 = upper_32_bits((u64)lower_32_bits(tmp64) * MICRO);
...
> > +
> > +static int ad9910_cfg_sysclk(struct ad9910_state *st, bool update)
> > +{
> > + u32 tmp32, cfr3 = AD9910_CFR3_OPEN_MSK;
> > +
> > + cfr3 |= AD9910_CFR3_VCO_SEL_MSK |
> > + FIELD_PREP(AD9910_CFR3_DRV0_MSK, st->data.refclk_out_drv);
> > +
> > + if (st->data.pll_enabled) {
> > + tmp32 = st->data.pll_charge_pump_current - AD9910_ICP_MIN_uA;
> > + tmp32 = DIV_ROUND_CLOSEST(tmp32, AD9910_ICP_STEP_uA);
> > + cfr3 |= FIELD_PREP(AD9910_CFR3_ICP_MSK, tmp32) |
> > + AD9910_CFR3_PLL_EN_MSK;
> > + } else {
> > + cfr3 |= AD9910_CFR3_ICP_MSK |
>
> For this, be explicit what value you are setting, probably be defining a max value
> that the field can take. Whilst just setting the mask is the same it doesn't
> convey the same meaning to someone reading the code.
This is just the default value from the datasheet, ICP should not really matter
when the PLL is disabled, so removing this should be fine.
>
> > + AD9910_CFR3_REFCLK_DIV_RESETB_MSK |
> > + AD9910_CFR3_PFD_RESET_MSK;
> > + }
> > + st->reg[AD9910_REG_CFR3].val32 = cfr3;
> > +
> > + return ad9910_set_sysclk_freq(st, AD9910_PLL_OUT_MAX_FREQ_HZ, update);
> > +}
> > +
> > +static int ad9910_parse_fw(struct ad9910_state *st)
> > +{
> > + struct device *dev = &st->spi->dev;
> > + u32 tmp;
> > + int ret;
> > +
> > + st->data.pll_enabled = device_property_read_bool(dev, "adi,pll-enable");
> > + if (st->data.pll_enabled) {
> > + tmp = AD9910_ICP_MAX_uA;
>
> Defaulting to max current seems unusual. What's the motivation? Normal instinct is
> go minimum if no other info.
ICP_MAX_uA leads to 111 in the CFR3_ICP field, which is the default value when the
device resets or when it powers on. I suppose that if we are not touching that
property, there would be no reason to change that.
...
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply
* Re: [PATCH v7 1/3] dt-bindings: hwmon: Document the LTC4283 Swap Controller
From: Nuno Sá @ 2026-03-23 10:31 UTC (permalink / raw)
To: Guenter Roeck, nuno.sa
Cc: linux-gpio, linux-hwmon, devicetree, linux-doc, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Shuah Khan,
Linus Walleij, Bartosz Golaszewski
In-Reply-To: <c395fad0-ca24-448a-a77f-ddac1cd9f809@roeck-us.net>
On Mon, 2026-03-16 at 08:59 -0700, Guenter Roeck wrote:
> On Sat, Mar 14, 2026 at 10:52:19AM +0000, Nuno Sá via B4 Relay wrote:
> > From: Nuno Sá <nuno.sa@analog.com>
> >
> > The LTC4283 is a negative voltage hot swap controller that drives an
> > external N-channel MOSFET to allow a board to be safely inserted and
> > removed from a live backplane.
> >
> > Special note for the "adi,vpower-drns-enable" property. It allows to choose
> > between the attenuated MOSFET drain voltage or the attenuated input
> > voltage at the RTNS pin (effectively choosing between input or output
> > power). This is a system level decision not really intended to change at
> > runtime and hence is being added as a Firmware property.
> >
> > Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
> > Signed-off-by: Nuno Sá <nuno.sa@analog.com>
>
> Some AI review feedback inline. Feel free to ignore if wrong, but please let me know
> to help improve it.
>
> Thanks,
> Guenter
>
> > ---
> > .../devicetree/bindings/hwmon/adi,ltc4283.yaml | 272 +++++++++++++++++++++
> > MAINTAINERS | 6 +
> > 2 files changed, 278 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/hwmon/adi,ltc4283.yaml
> > b/Documentation/devicetree/bindings/hwmon/adi,ltc4283.yaml
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..f82fff1ec7e4407ed63d00f8b1281db459d7221b
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/hwmon/adi,ltc4283.yaml
> > @@ -0,0 +1,272 @@
> > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/hwmon/adi,ltc4283.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: LTC4283 Negative Voltage Hot Swap Controller
> > +
> > +maintainers:
> > + - Nuno Sá <nuno.sa@analog.com>
> > +
> > +description: |
> > + The LTC4283 negative voltage hot swap controller drives an external N-channel
> > + MOSFET to allow a board to be safely inserted and removed from a live
> > + backplane.
> > +
> > + https://www.analog.com/media/en/technical-documentation/data-sheets/ltc4283.pdf
> > +
> > +properties:
> > + compatible:
> > + enum:
> > + - adi,ltc4283
> > +
> > + reg:
> > + maxItems: 1
> > +
> > + adi,rsense-nano-ohms:
> > + description: Value of the sense resistor.
> > +
> > + adi,current-limit-sense-microvolt:
> > + description:
> > + The current limit sense voltage of the chip is adjustable between
> > + 15mV and 30mV in 1mV steps. This effectively limits the current
> > + on the load.
> > + minimum: 15000
> > + maximum: 30000
> > + default: 15000
> > +
> > + adi,current-limit-foldback-factor:
> > + description:
> > + Specifies the foldback factor for the current limit. The current limit
> > + can be reduced (folded back) to one of four preset levels. The value
> > + represents the percentage of the current limit sense voltage to use
> > + during foldback. A value of 100 means no foldback.
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + enum: [10, 20, 50, 100]
> > + default: 100
> > +
> > + adi,cooling-delay-ms:
> > + description:
> > + Cooling time to apply after an overcurrent fault, FET bad or
> > + external fault.
> > + enum: [512, 1002, 2005, 4100, 8190, 16400, 32800, 65600]
> > + default: 512
> > +
> > + adi,fet-bad-timer-delay-ms:
> > + description:
> > + FET bad timer delay. After a FET bad status condition is detected,
> > + this timer is started. If the condition persists for the
> > + specified time, the FET is turned off and a fault is logged.
> > + enum: [256, 512, 1002, 2005]
> > + default: 256
> > +
> > + adi,power-good-reset-on-fet:
> > + description:
> > + If set, resets the power good status when the MOSFET is turned off.
> > + Otherwise, it resets when a low output voltage is detected.
> > + type: boolean
> > +
> > + adi,fet-turn-off-disable:
> > + description:
> > + If set, the MOSFET is turned off immediately when a FET fault is detected.
> > + type: boolean
>
> Is there a logic inversion between the property name and its description?
> The property name uses a -disable suffix, but the description says "If set,
> the MOSFET is turned off immediately", which sounds like it is enabling the
> behavior rather than disabling it.
Yes. Misleading description. This has -disable because the default is the -enable case which
indeed is the case when the FET is turned off. Will update the description so that is not
confusing.
> > +
> > + adi,tmr-pull-down-disable:
> > + description: Disables 2uA pull-down current on the TMR pin.
> > + type: boolean
> > +
> > + adi,dvdt-inrush-control-disable:
> > + description:
> > + Enables dV/dt inrush control during startup. In dV/dt mode, the inrush
> > + current is limited by controlling a constant output voltage ramp rate.
> > + If not set, the inrush control mechanism is active current limiting.
> > + type: boolean
>
> Does this description contradict the property name?
> The -disable suffix implies the property turns off the dV/dt inrush control,
> but the description states that setting the property enables it.
>
> [ Non-AI note: It seems to me that the description contradicts itself.
> It first says "_Enables_ ...", then it says "If _not_ set, the inrush control
> mechanism is active current limiting" ]
Yeah same thing as above. I will update this one just by s/Enables/Disables/
>
> > +
> > + adi,fault-log-enable:
> > + description:
> > + If set, enables logging fault registers and ADC data into EEPROM upon a
> > + fault.
> > + type: boolean
> > +
> > + adi,vpower-drns-enable:
> > + description:
> > + If set, enables the attenuated MOSFET drain voltage to be monitored. This
> > + effectively means that the MOSFET power is monitored. If not set, the
> > + attenuated input voltage (and hence input power) is monitored.
> > + type: boolean
> > +
> > + adi,external-fault-fet-off-enable:
> > + description: Turns MOSFET off following an external fault.
> > + type: boolean
> > +
> > + adi,undervoltage-retry-disable:
> > + description: Do not retry to turn on the MOSFET after an undervoltage fault.
> > + type: boolean
> > +
> > + adi,overvoltage-retry-disable:
> > + description: Do not retry to turn on the MOSFET after an overvoltage fault.
> > + type: boolean
> > +
> > + adi,external-fault-retry-enable:
> > + description: Retry to turn on the MOSFET retry after an external fault.
> > + type: boolean
>
> This isn't a bug, but there's a typo in the description where the word
> "retry" is repeated.
ack.
>
> > +
> > + adi,overcurrent-retries:
> > + description: Configures auto-retry following an Overcurrent fault.
> > + $ref: /schemas/types.yaml#/definitions/string
> > + enum: [latch-off, "1", "7", unlimited]
> > + default: latch-off
> > +
> > + adi,fet-bad-retries:
> > + description:
> > + Configures auto-retry following a FET bad fault and a consequent MOSFET
> > + turn off.
> > + $ref: /schemas/types.yaml#/definitions/string
> > + enum: [latch-off, "1", "7", unlimited]
> > + default: latch-off
> > +
> > + adi,pgio1-func:
> > + description: Configures the function of the PGIO1 pin.
> > + $ref: /schemas/types.yaml#/definitions/string
> > + enum: [inverted_power_good, power_good, gpio]
> > + default: inverted_power_good
> > +
> > + adi,pgio2-func:
> > + description: Configures the function of the PGIO2 pin.
> > + $ref: /schemas/types.yaml#/definitions/string
> > + enum: [inverted_power_good, power_good, gpio, active_current_limiting]
> > + default: inverted_power_good
> > +
> > + adi,pgio3-func:
> > + description: Configures the function of the PGIO3 pin.
> > + $ref: /schemas/types.yaml#/definitions/string
> > + enum: [inverted_power_good_input, power_good_input, gpio]
> > + default: inverted_power_good_input
> > +
> > + adi,pgio4-func:
> > + description: Configures the function of the PGIO4 pin.
> > + $ref: /schemas/types.yaml#/definitions/string
> > + enum: [inverted_external_fault, external_fault, gpio]
> > + default: inverted_external_fault
> > +
> > + adi,gpio-on-adio1:
> > + description: If set, the ADIO1 pin is used as a GPIO.
> > + type: boolean
> > +
> > + adi,gpio-on-adio2:
> > + description: If set, the ADIO2 pin is used as a GPIO.
> > + type: boolean
> > +
> > + adi,gpio-on-adio3:
> > + description: If set, the ADIO3 pin is used as a GPIO.
> > + type: boolean
> > +
> > + adi,gpio-on-adio4:
> > + description: If set, the ADIO4 pin is used as a GPIO.
> > + type: boolean
>
> Does this dependency block force a redundant specification of adi,pgio4-func?
> The default for adi,pgio4-func is inverted_external_fault, which means the
> default hardware state already supports external fault features.
> If a device tree legitimately omits adi,pgio4-func to rely on that default,
> will it fail schema validation here since the dependencies keyword strictly
> checks for the literal presence of properties without injecting defaults?
Fair point. I guess it will fail but the alternative is to not have any constrain at all so
maybe worth it to be explicit in here?
- Nuno Sá
>
> > +
> > + gpio-controller: true
> > +
> > + '#gpio-cells':
> > + const: 2
> > +
> > +dependencies:
> > + adi,gpio-on-adio1:
> > + - gpio-controller
> > + - '#gpio-cells'
> > + adi,gpio-on-adio2:
> > + - gpio-controller
> > + - '#gpio-cells'
> > + adi,gpio-on-adio3:
> > + - gpio-controller
> > + - '#gpio-cells'
> > + adi,gpio-on-adio4:
> > + - gpio-controller
> > + - '#gpio-cells'
> > + adi,external-fault-retry-enable:
> > + - adi,pgio4-func
> > + adi,external-fault-fet-off-enable:
> > + - adi,pgio4-func
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - adi,rsense-nano-ohms
> > +
> > +allOf:
> > + - if:
> > + properties:
> > + adi,pgio1-func:
> > + const: gpio
> > + required:
> > + - adi,pgio1-func
> > + then:
> > + required:
> > + - gpio-controller
> > + - '#gpio-cells'
> > +
> > + - if:
> > + properties:
> > + adi,pgio2-func:
> > + const: gpio
> > + required:
> > + - adi,pgio2-func
> > + then:
> > + required:
> > + - gpio-controller
> > + - '#gpio-cells'
> > +
> > + - if:
> > + properties:
> > + adi,pgio3-func:
> > + const: gpio
> > + required:
> > + - adi,pgio3-func
> > + then:
> > + required:
> > + - gpio-controller
> > + - '#gpio-cells'
> > +
> > + - if:
> > + properties:
> > + adi,pgio4-func:
> > + const: gpio
> > + required:
> > + - adi,pgio4-func
> > + then:
> > + properties:
> > + adi,external-fault-retry-enable: false
> > + adi,external-fault-fet-off-enable: false
> > + required:
> > + - gpio-controller
> > + - '#gpio-cells'
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > + - |
> > + i2c {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + swap-controller@15 {
> > + compatible = "adi,ltc4283";
> > + reg = <0x15>;
> > +
> > + adi,rsense-nano-ohms = <500>;
> > + adi,current-limit-sense-microvolt = <25000>;
> > + adi,current-limit-foldback-factor = <10>;
> > + adi,cooling-delay-ms = <8190>;
> > + adi,fet-bad-timer-delay-ms = <512>;
> > +
> > + adi,external-fault-fet-off-enable;
> > + adi,pgio4-func = "external_fault";
> > +
> > + adi,gpio-on-adio1;
> > + adi,pgio1-func = "gpio";
> > + gpio-controller;
> > + #gpio-cells = <2>;
> > + };
> > + };
> > +...
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 830c6f076b0029f0ff1abee148ad0e1905a60e82..13ae2f3db449e5fd3a7d0fbac92aabdc01734ba9 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -15141,6 +15141,12 @@ F: Documentation/devicetree/bindings/hwmon/adi,ltc4282.yaml
> > F: Documentation/hwmon/ltc4282.rst
> > F: drivers/hwmon/ltc4282.c
> >
> > +LTC4283 HARDWARE MONITOR AND GPIO DRIVER
> > +M: Nuno Sá <nuno.sa@analog.com>
> > +L: linux-hwmon@vger.kernel.org
> > +S: Supported
> > +F: Documentation/devicetree/bindings/hwmon/adi,ltc4283.yaml
> > +
> > LTC4286 HARDWARE MONITOR DRIVER
> > M: Delphine CC Chiu <Delphine_CC_Chiu@Wiwynn.com>
> > L: linux-hwmon@vger.kernel.org
> >
> > --
> > 2.51.0
> >
> >
> >
^ permalink raw reply
* Re: [RFC PATCH 1/1] leds: Introduce the multi_max_intensity sysfs attribute
From: Armin Wolf @ 2026-03-23 10:22 UTC (permalink / raw)
To: Jacek Anaszewski, lee, pavel
Cc: corbet, skhan, linux-leds, linux-doc, linux-kernel, wse, pobrn
In-Reply-To: <e43fc3be-7cee-432b-90cb-7fbc4af2fa68@gmail.com>
Am 22.03.26 um 16:53 schrieb Jacek Anaszewski:
> Hi Armin,
>
> On 3/15/26 12:01 AM, Armin Wolf wrote:
>> Some multicolor LEDs support global brightness control in hardware,
>> meaning that the maximum intensity of the color components is not
>> connected to the maximum global brightness. Such LEDs cannot be
>> described properly by the current multicolor LED class interface,
>> because it assumes that the maximum intensity of each color component
>> is described by the maximum global brightness of the LED.
>>
>> Fix this by introducing a new sysfs attribute called
>> "multi_max_intensity" holding the maximum intensity values for the
>> color components of a multicolor LED class device. Drivers can use
>> the new max_intensity field inside struct mc_subled to tell the
>> multicolor LED class code about those values. Intensity values written
>> by userspace applications will be limited to this maximum value.
>>
>> Drivers for multicolor LEDs that do not support global brightness
>> control in hardware might still want to use the maximum global LED
>> brightness supplied via devicetree as the maximum intensity of each
>> individual color component. Such drivers should set max_intensity
>> to LED_USE_MAX_BRIGHTNESS so that the multicolor LED core can act
>
> Since LED_USE_MAX_BRIGHTNESS value is 0, then I propose not to touch
> the drivers which we want to stick to the default max_intensity,
> unless they don't initialize their mc_subled structs to 0, or don't
> use struct initialization list, which in case the property is omitted,
> initializes it to 0.
>
Agree, i will check which drivers do this when sending the v1 revision.
>> accordingly.
>>
>> Compile-tested only.
>>
>> Signed-off-by: Armin Wolf <W_Armin@gmx.de>
>> ---
>> .../ABI/testing/sysfs-class-led-multicolor | 16 +++++++--
>> Documentation/leds/leds-class-multicolor.rst | 21 ++++++++++-
>> drivers/hid/hid-lg-g15.c | 3 ++
>> drivers/hid/hid-playstation.c | 3 ++
>> drivers/leds/flash/leds-mt6360.c | 8 ++---
>> drivers/leds/led-class-multicolor.c | 36 +++++++++++++++++--
>> drivers/leds/leds-blinkm.c | 3 ++
>> drivers/leds/leds-cros_ec.c | 1 +
>> drivers/leds/leds-lp50xx.c | 1 +
>> drivers/leds/leds-lp55xx-common.c | 7 ++--
>> drivers/leds/leds-max77705.c | 1 +
>> drivers/leds/leds-sun50i-a100.c | 3 ++
>> drivers/leds/leds-turris-omnia.c | 1 +
>> drivers/leds/rgb/leds-group-multicolor.c | 1 +
>> drivers/leds/rgb/leds-ktd202x.c | 1 +
>> drivers/leds/rgb/leds-lp5812.c | 1 +
>> drivers/leds/rgb/leds-mt6370-rgb.c | 8 ++---
>> drivers/leds/rgb/leds-ncp5623.c | 4 +--
>> drivers/leds/rgb/leds-pwm-multicolor.c | 1 +
>> drivers/leds/rgb/leds-qcom-lpg.c | 1 +
>> drivers/platform/x86/silicom-platform.c | 30 ++++++++++++++++
>> drivers/platform/x86/uniwill/uniwill-acpi.c | 4 +--
>> include/linux/led-class-multicolor.h | 32 ++++++++++++++++-
>> 23 files changed, 164 insertions(+), 23 deletions(-)
>>
>> diff --git a/Documentation/ABI/testing/sysfs-class-led-multicolor
>> b/Documentation/ABI/testing/sysfs-class-led-multicolor
>> index 16fc827b10cb..fbd95c5003e2 100644
>> --- a/Documentation/ABI/testing/sysfs-class-led-multicolor
>> +++ b/Documentation/ABI/testing/sysfs-class-led-multicolor
>> @@ -17,8 +17,20 @@ KernelVersion: 5.9
>> Contact: Dan Murphy <dmurphy@ti.com>
>> Description: read/write
>> This file contains array of integers. Order of components is
>> - described by the multi_index array. The maximum intensity
>> should
>> - not exceed /sys/class/leds/<led>/max_brightness.
>> + described by the multi_index array.
>> + For additional details please refer to
>> + Documentation/leds/leds-class-multicolor.rst.
>
> Why not to refer to the newly introduced multi_max_intensity file here
> instead?
>
Good point, i copied this from the other descriptions. I will rework this for
the v1 revision.
>> +
>> +What: /sys/class/leds/<led>/multi_max_intensity
>> +Date: March 2026
>> +KernelVersion: 7.1
>> +Contact: Armin Wolf <W_Armin@gmx.de>
>> +Description: read
>> + This file contains an array of integers describing the maximum
>> + intensity value for each intensity component. Writing intensity
>> + values larger than the maximum value of a given component will
>> + result in those values being clamped.
>> For additional details please refer to
>> Documentation/leds/leds-class-multicolor.rst.
>> diff --git a/Documentation/leds/leds-class-multicolor.rst
>> b/Documentation/leds/leds-class-multicolor.rst
>> index c6b47b4093c4..8f42f10078ad 100644
>> --- a/Documentation/leds/leds-class-multicolor.rst
>> +++ b/Documentation/leds/leds-class-multicolor.rst
>> @@ -25,10 +25,14 @@ color name to indexed value.
>> The ``multi_index`` file is an array that contains the string list
>> of the colors as
>> they are defined in each ``multi_*`` array file.
>> -The ``multi_intensity`` is an array that can be read or written to
>> for the
>> +The ``multi_intensity`` file is an array that can be read or written
>> to for the
>> individual color intensities. All elements within this array must
>> be written in
>> order for the color LED intensities to be updated.
>> +The ``multi_max_intensity`` file is an array that contains the
>> maximum intensity
>> +value supported by each color intensity. Intensity values above this
>> will be
>> +automatically clamped into the supported range.
>> +
>> Directory Layout Example
>> ========================
>> .. code-block:: console
>> @@ -38,6 +42,7 @@ Directory Layout Example
>> -r--r--r-- 1 root root 4096 Oct 19 16:16
>> max_brightness
>> -r--r--r-- 1 root root 4096 Oct 19 16:16
>> multi_index
>> -rw-r--r-- 1 root root 4096 Oct 19 16:16
>> multi_intensity
>> + -r--r--r-- 1 root root 4096 OCt 19 16:16
>> multi_max_intensity
>> ..
>> @@ -104,3 +109,17 @@ the color LED group.
>> 128
>> ..
>> +
>> +Writing intensity values larger than the maximum specified in
>> ``multi_max_intensity``
>> +will result in those values being clamped into the supported range.
>> +
>> +.. code-block:: console
>> +
>> + # cat /sys/class/leds/multicolor:status/multi_max_intensity
>> + 255 255 255
>> +
>> + # echo 512 512 512 >
>> /sys/class/leds/multicolor:status/multi_intensity
>> + # cat /sys/class/leds/multicolor:status/multi_intensity
>> + 255 255 255
>> +
>> +..
>> diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c
>> index 1a88bc44ada4..227f50b21c06 100644
>> --- a/drivers/hid/hid-lg-g15.c
>> +++ b/drivers/hid/hid-lg-g15.c
>> @@ -975,14 +975,17 @@ static void lg_g15_setup_led_rgb(struct
>> lg_g15_data *g15, int index)
>> case LED_COLOR_ID_RED:
>> subled_info[i].color_index = LED_COLOR_ID_RED;
>> subled_info[i].intensity = gled->red;
>> + subled_info[i].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> break;
>> case LED_COLOR_ID_GREEN:
>> subled_info[i].color_index = LED_COLOR_ID_GREEN;
>> subled_info[i].intensity = gled->green;
>> + subled_info[i].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> break;
>> case LED_COLOR_ID_BLUE:
>> subled_info[i].color_index = LED_COLOR_ID_BLUE;
>> subled_info[i].intensity = gled->blue;
>> + subled_info[i].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> break;
>> }
>> subled_info[i].channel = i;
>> diff --git a/drivers/hid/hid-playstation.c
>> b/drivers/hid/hid-playstation.c
>> index 3c0db8f93c82..a6de4dd49750 100644
>> --- a/drivers/hid/hid-playstation.c
>> +++ b/drivers/hid/hid-playstation.c
>> @@ -874,8 +874,11 @@ static int ps_lightbar_register(struct ps_device
>> *ps_dev, struct led_classdev_mc
>> return -ENOMEM;
>> mc_led_info[0].color_index = LED_COLOR_ID_RED;
>> + mc_led_info[0].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> mc_led_info[1].color_index = LED_COLOR_ID_GREEN;
>> + mc_led_info[1].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> mc_led_info[2].color_index = LED_COLOR_ID_BLUE;
>> + mc_led_info[2].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> lightbar_mc_dev->subled_info = mc_led_info;
>> lightbar_mc_dev->num_colors = 3;
>> diff --git a/drivers/leds/flash/leds-mt6360.c
>> b/drivers/leds/flash/leds-mt6360.c
>> index 462a902f54e0..f0331cbec6aa 100644
>> --- a/drivers/leds/flash/leds-mt6360.c
>> +++ b/drivers/leds/flash/leds-mt6360.c
>> @@ -100,7 +100,7 @@ static int mt6360_mc_brightness_set(struct
>> led_classdev *lcdev,
>> struct led_classdev_mc *mccdev = lcdev_to_mccdev(lcdev);
>> struct mt6360_led *led = container_of(mccdev, struct
>> mt6360_led, mc);
>> struct mt6360_priv *priv = led->priv;
>> - u32 real_bright, enable_mask = 0, enable = 0;
>> + u32 enable_mask = 0, enable = 0;
>> int i, ret;
>> mutex_lock(&priv->lock);
>> @@ -110,14 +110,13 @@ static int mt6360_mc_brightness_set(struct
>> led_classdev *lcdev,
>> for (i = 0; i < mccdev->num_colors; i++) {
>> struct mc_subled *subled = mccdev->subled_info + i;
>> - real_bright = min(lcdev->max_brightness, subled->brightness);
>> ret = regmap_update_bits(priv->regmap, MT6360_REG_ISNK(i),
>> - MT6360_ISNK_MASK, real_bright);
>> + MT6360_ISNK_MASK, subled->brightness);
>> if (ret)
>> goto out;
>> enable_mask |= MT6360_ISNK_ENMASK(subled->channel);
>> - if (real_bright)
>> + if (subled->brightness)
>> enable |= MT6360_ISNK_ENMASK(subled->channel);
>> }
>> @@ -660,6 +659,7 @@ static int mt6360_init_isnk_properties(struct
>> mt6360_led *led,
>> priv->leds_active |= BIT(reg);
>> sub_led[num_color].color_index = color;
>> sub_led[num_color].channel = reg;
>> + sub_led[num_color].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> num_color++;
>> }
>> diff --git a/drivers/leds/led-class-multicolor.c
>> b/drivers/leds/led-class-multicolor.c
>> index 6b671f3f9c61..72323e825331 100644
>> --- a/drivers/leds/led-class-multicolor.c
>> +++ b/drivers/leds/led-class-multicolor.c
>> @@ -7,6 +7,7 @@
>> #include <linux/init.h>
>> #include <linux/led-class-multicolor.h>
>> #include <linux/math.h>
>> +#include <linux/minmax.h>
>> #include <linux/module.h>
>> #include <linux/slab.h>
>> #include <linux/uaccess.h>
>> @@ -27,6 +28,30 @@ int led_mc_calc_color_components(struct
>> led_classdev_mc *mcled_cdev,
>> }
>> EXPORT_SYMBOL_GPL(led_mc_calc_color_components);
>> +static ssize_t multi_max_intensity_show(struct device *dev,
>> + struct device_attribute *intensity_attr, char *buf)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
>> + unsigned int max_intensity;
>> + int len = 0;
>> + int i;
>> +
>> + for (i = 0; i < mcled_cdev->num_colors; i++) {
>> + max_intensity = mcled_cdev->subled_info[i].max_intensity;
>> + if (max_intensity == LED_USE_MAX_BRIGHTNESS)
>> + max_intensity = led_cdev->max_brightness;
>> +
>> + len += sprintf(buf + len, "%u", max_intensity);
>> + if (i < mcled_cdev->num_colors - 1)
>> + len += sprintf(buf + len, " ");
>> + }
>> +
>> + buf[len++] = '\n';
>> + return len;
>> +}
>> +static DEVICE_ATTR_RO(multi_max_intensity);
>> +
>> static ssize_t multi_intensity_store(struct device *dev,
>> struct device_attribute *intensity_attr,
>> const char *buf, size_t size)
>> @@ -35,6 +60,7 @@ static ssize_t multi_intensity_store(struct device
>> *dev,
>> struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
>> int nrchars, offset = 0;
>> unsigned int intensity_value[LED_COLOR_ID_MAX];
>> + unsigned int max_intensity;
>> int i;
>> ssize_t ret;
>> @@ -56,8 +82,13 @@ static ssize_t multi_intensity_store(struct
>> device *dev,
>> goto err_out;
>> }
>> - for (i = 0; i < mcled_cdev->num_colors; i++)
>> - mcled_cdev->subled_info[i].intensity = intensity_value[i];
>> + for (i = 0; i < mcled_cdev->num_colors; i++) {
>> + max_intensity = mcled_cdev->subled_info[i].max_intensity;
>> + if (max_intensity == LED_USE_MAX_BRIGHTNESS)
>> + max_intensity = led_cdev->max_brightness;
>> +
>> + mcled_cdev->subled_info[i].intensity =
>> min(intensity_value[i], max_intensity);
>> + }
>> if (!test_bit(LED_BLINK_SW, &led_cdev->work_flags))
>> led_set_brightness(led_cdev, led_cdev->brightness);
>> @@ -111,6 +142,7 @@ static ssize_t multi_index_show(struct device *dev,
>> static DEVICE_ATTR_RO(multi_index);
>> static struct attribute *led_multicolor_attrs[] = {
>> + &dev_attr_multi_max_intensity.attr,
>> &dev_attr_multi_intensity.attr,
>> &dev_attr_multi_index.attr,
>> NULL,
>> diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c
>> index 577497b9d426..7d1140767c76 100644
>> --- a/drivers/leds/leds-blinkm.c
>> +++ b/drivers/leds/leds-blinkm.c
>> @@ -694,8 +694,11 @@ static int register_multicolor(struct i2c_client
>> *client, struct blinkm_data *da
>> return -ENOMEM;
>> mc_led_info[RED].color_index = LED_COLOR_ID_RED;
>> + mc_led_info[RED].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> mc_led_info[GREEN].color_index = LED_COLOR_ID_GREEN;
>> + mc_led_info[GREEN].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> mc_led_info[BLUE].color_index = LED_COLOR_ID_BLUE;
>> + mc_led_info[BLUE].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> mc_led->cdev.mcled_cdev.subled_info = mc_led_info;
>> mc_led->cdev.mcled_cdev.num_colors = NUM_LEDS;
>> diff --git a/drivers/leds/leds-cros_ec.c b/drivers/leds/leds-cros_ec.c
>> index bea3cc3fbfd2..b891471812ff 100644
>> --- a/drivers/leds/leds-cros_ec.c
>> +++ b/drivers/leds/leds-cros_ec.c
>> @@ -202,6 +202,7 @@ static int cros_ec_led_probe_one(struct device
>> *dev, struct cros_ec_device *cros
>> continue;
>> subleds[subled].color_index = cros_ec_led_to_linux_id[i];
>> + subleds[subled].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> if (subled == 0)
>> subleds[subled].intensity = 100;
>> subled++;
>> diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c
>> index e2a9c8592953..69c3550f1a31 100644
>> --- a/drivers/leds/leds-lp50xx.c
>> +++ b/drivers/leds/leds-lp50xx.c
>> @@ -525,6 +525,7 @@ static int lp50xx_probe_dt(struct lp50xx *priv)
>> }
>> mc_led_info[multi_index].color_index = color_id;
>> + mc_led_info[multi_index].max_intensity = 255;
>> num_colors++;
>> }
>> diff --git a/drivers/leds/leds-lp55xx-common.c
>> b/drivers/leds/leds-lp55xx-common.c
>> index ea131177de96..f637e74e8780 100644
>> --- a/drivers/leds/leds-lp55xx-common.c
>> +++ b/drivers/leds/leds-lp55xx-common.c
>> @@ -495,10 +495,9 @@ static int lp55xx_init_led(struct lp55xx_led *led,
>> led_cdev->brightness_set_blocking = lp55xx_set_mc_brightness;
>> led->mc_cdev.num_colors = pdata->led_config[chan].num_colors;
>> for (i = 0; i < led->mc_cdev.num_colors; i++) {
>> - mc_led_info[i].color_index =
>> - pdata->led_config[chan].color_id[i];
>> - mc_led_info[i].channel =
>> - pdata->led_config[chan].output_num[i];
>> + mc_led_info[i].color_index =
>> pdata->led_config[chan].color_id[i];
>> + mc_led_info[i].channel =
>> pdata->led_config[chan].output_num[i];
>> + mc_led_info[i].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> }
>> led->mc_cdev.subled_info = mc_led_info;
>> diff --git a/drivers/leds/leds-max77705.c b/drivers/leds/leds-max77705.c
>> index 1e2054c1bf80..bd2c078b42f3 100644
>> --- a/drivers/leds/leds-max77705.c
>> +++ b/drivers/leds/leds-max77705.c
>> @@ -149,6 +149,7 @@ static int max77705_parse_subled(struct device
>> *dev, struct fwnode_handle *np,
>> "failed to parse \"color\" of %pOF\n", np);
>> info->color_index = color;
>> + info->max_intensity = LED_USE_MAX_BRIGHTNESS;
>> return 0;
>> }
>> diff --git a/drivers/leds/leds-sun50i-a100.c
>> b/drivers/leds/leds-sun50i-a100.c
>> index 2c9bd360ab81..6e77cbcb8b54 100644
>> --- a/drivers/leds/leds-sun50i-a100.c
>> +++ b/drivers/leds/leds-sun50i-a100.c
>> @@ -504,10 +504,13 @@ static int sun50i_a100_ledc_probe(struct
>> platform_device *pdev)
>> fwnode_property_read_u32(child, "reg", &led->addr);
>> led->subled_info[0].color_index = LED_COLOR_ID_RED;
>> + led->subled_info[0].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> led->subled_info[0].channel = 0;
>> led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
>> + led->subled_info[1].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> led->subled_info[1].channel = 1;
>> led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
>> + led->subled_info[2].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> led->subled_info[2].channel = 2;
>> led->mc_cdev.num_colors = ARRAY_SIZE(led->subled_info);
>> diff --git a/drivers/leds/leds-turris-omnia.c
>> b/drivers/leds/leds-turris-omnia.c
>> index 25ee5c1eb820..fc41891fe971 100644
>> --- a/drivers/leds/leds-turris-omnia.c
>> +++ b/drivers/leds/leds-turris-omnia.c
>> @@ -233,6 +233,7 @@ static int omnia_led_register(struct i2c_client
>> *client, struct omnia_led *led,
>> /* Initial color is white */
>> for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) {
>> led->subled_info[i].intensity = 255;
>> + led->subled_info[i].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> led->subled_info[i].brightness = 255;
>> led->subled_info[i].channel = i;
>> }
>> diff --git a/drivers/leds/rgb/leds-group-multicolor.c
>> b/drivers/leds/rgb/leds-group-multicolor.c
>> index 548c7dd63ba1..c472ccbd506e 100644
>> --- a/drivers/leds/rgb/leds-group-multicolor.c
>> +++ b/drivers/leds/rgb/leds-group-multicolor.c
>> @@ -111,6 +111,7 @@ static int leds_gmc_probe(struct platform_device
>> *pdev)
>> /* Configure the LED intensity to its maximum */
>> subled[i].intensity = max_brightness;
>> + subled[i].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> }
>> /* Initialise the multicolor's LED class device */
>> diff --git a/drivers/leds/rgb/leds-ktd202x.c
>> b/drivers/leds/rgb/leds-ktd202x.c
>> index e4f0f25a5e45..4b054175b3b5 100644
>> --- a/drivers/leds/rgb/leds-ktd202x.c
>> +++ b/drivers/leds/rgb/leds-ktd202x.c
>> @@ -423,6 +423,7 @@ static int ktd202x_setup_led_rgb(struct ktd202x
>> *chip, struct fwnode_handle *fwn
>> info[i].color_index = mono_color;
>> info[i].channel = reg;
>> info[i].intensity = KTD202X_MAX_BRIGHTNESS;
>> + info[i].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> i++;
>> }
>> diff --git a/drivers/leds/rgb/leds-lp5812.c
>> b/drivers/leds/rgb/leds-lp5812.c
>> index ce6d703641e8..61fa8df493fb 100644
>> --- a/drivers/leds/rgb/leds-lp5812.c
>> +++ b/drivers/leds/rgb/leds-lp5812.c
>> @@ -353,6 +353,7 @@ static int lp5812_init_led(struct lp5812_led
>> *led, struct lp5812_chip *chip, int
>> for (i = 0; i < led->mc_cdev.num_colors; i++) {
>> mc_led_info[i].color_index =
>> chip->led_config[chan].color_id[i];
>> mc_led_info[i].channel = chip->led_config[chan].led_id[i];
>> + mc_led_info[i].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> }
>> led->mc_cdev.subled_info = mc_led_info;
>> diff --git a/drivers/leds/rgb/leds-mt6370-rgb.c
>> b/drivers/leds/rgb/leds-mt6370-rgb.c
>> index c5927d0eb830..ea11ca9d3326 100644
>> --- a/drivers/leds/rgb/leds-mt6370-rgb.c
>> +++ b/drivers/leds/rgb/leds-mt6370-rgb.c
>> @@ -437,10 +437,7 @@ static int mt6370_mc_brightness_set(struct
>> led_classdev *lcdev, enum led_brightn
>> disable = enable;
>> for (i = 0; i < mccdev->num_colors; i++) {
>> - u32 brightness;
>> -
>> subled = mccdev->subled_info + i;
>> - brightness = min(subled->brightness, lcdev->max_brightness);
>> disable &= ~MT6370_CHEN_BIT(subled->channel);
>> if (level == 0) {
>> @@ -453,14 +450,14 @@ static int mt6370_mc_brightness_set(struct
>> led_classdev *lcdev, enum led_brightn
>> continue;
>> }
>> - if (brightness == 0) {
>> + if (subled->brightness == 0) {
>> enable &= ~MT6370_CHEN_BIT(subled->channel);
>> continue;
>> }
>> enable |= MT6370_CHEN_BIT(subled->channel);
>> - ret = mt6370_set_led_brightness(priv, subled->channel,
>> brightness);
>> + ret = mt6370_set_led_brightness(priv, subled->channel,
>> subled->brightness);
>> if (ret)
>> goto out_unlock;
>> }
>> @@ -742,6 +739,7 @@ static int mt6370_assign_multicolor_info(struct
>> device *dev, struct mt6370_led *
>> sub_led[num_color].color_index = color;
>> sub_led[num_color].channel = reg;
>> sub_led[num_color].intensity = 0;
>> + sub_led[num_color].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> num_color++;
>> }
>> diff --git a/drivers/leds/rgb/leds-ncp5623.c
>> b/drivers/leds/rgb/leds-ncp5623.c
>> index 85d6be6fff2b..f2528f06507d 100644
>> --- a/drivers/leds/rgb/leds-ncp5623.c
>> +++ b/drivers/leds/rgb/leds-ncp5623.c
>> @@ -56,8 +56,7 @@ static int ncp5623_brightness_set(struct
>> led_classdev *cdev,
>> for (int i = 0; i < mc_cdev->num_colors; i++) {
>> ret = ncp5623_write(ncp->client,
>> NCP5623_PWM_REG(mc_cdev->subled_info[i].channel),
>> - min(mc_cdev->subled_info[i].intensity,
>> - NCP5623_MAX_BRIGHTNESS));
>> + mc_cdev->subled_info[i].intensity);
>> if (ret)
>> return ret;
>> }
>> @@ -190,6 +189,7 @@ static int ncp5623_probe(struct i2c_client *client)
>> goto release_led_node;
>> subled_info[ncp->mc_dev.num_colors].channel = reg;
>> + subled_info[ncp->mc_dev.num_colors].max_intensity =
>> NCP5623_MAX_BRIGHTNESS;
>> subled_info[ncp->mc_dev.num_colors++].color_index =
>> color_index;
>> }
>> diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c
>> b/drivers/leds/rgb/leds-pwm-multicolor.c
>> index e0d7d3c9215c..578e67dcc881 100644
>> --- a/drivers/leds/rgb/leds-pwm-multicolor.c
>> +++ b/drivers/leds/rgb/leds-pwm-multicolor.c
>> @@ -95,6 +95,7 @@ static int iterate_subleds(struct device *dev,
>> struct pwm_mc_led *priv,
>> }
>> subled[priv->mc_cdev.num_colors].color_index = color;
>> + subled[priv->mc_cdev.num_colors].max_intensity =
>> LED_USE_MAX_BRIGHTNESS;
>> priv->mc_cdev.num_colors++;
>> }
>> diff --git a/drivers/leds/rgb/leds-qcom-lpg.c
>> b/drivers/leds/rgb/leds-qcom-lpg.c
>> index f6061c47f863..390dcbe1cd18 100644
>> --- a/drivers/leds/rgb/leds-qcom-lpg.c
>> +++ b/drivers/leds/rgb/leds-qcom-lpg.c
>> @@ -1411,6 +1411,7 @@ static int lpg_add_led(struct lpg *lpg, struct
>> device_node *np)
>> info[i].color_index = led->channels[i]->color;
>> info[i].intensity = 0;
>> + info[i].max_intensity = LED_USE_MAX_BRIGHTNESS;
>> i++;
>> }
>> diff --git a/drivers/platform/x86/silicom-platform.c
>> b/drivers/platform/x86/silicom-platform.c
>> index 266f7bc5e416..5ed115b7dc01 100644
>> --- a/drivers/platform/x86/silicom-platform.c
>> +++ b/drivers/platform/x86/silicom-platform.c
>> @@ -283,18 +283,21 @@ static struct mc_subled
>> plat_0222_wan_mc_subled_info[] __initdata = {
>> .color_index = LED_COLOR_ID_WHITE,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 7),
>> },
>> {
>> .color_index = LED_COLOR_ID_YELLOW,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 6),
>> },
>> {
>> .color_index = LED_COLOR_ID_RED,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 5),
>> },
>> };
>> @@ -304,18 +307,21 @@ static struct mc_subled
>> plat_0222_sys_mc_subled_info[] __initdata = {
>> .color_index = LED_COLOR_ID_WHITE,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 4),
>> },
>> {
>> .color_index = LED_COLOR_ID_AMBER,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 3),
>> },
>> {
>> .color_index = LED_COLOR_ID_RED,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 2),
>> },
>> };
>> @@ -325,24 +331,28 @@ static struct mc_subled
>> plat_0222_stat1_mc_subled_info[] __initdata = {
>> .color_index = LED_COLOR_ID_RED,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 1),
>> },
>> {
>> .color_index = LED_COLOR_ID_GREEN,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 0),
>> },
>> {
>> .color_index = LED_COLOR_ID_BLUE,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 7),
>> },
>> {
>> .color_index = LED_COLOR_ID_YELLOW,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 6),
>> },
>> };
>> @@ -352,24 +362,28 @@ static struct mc_subled
>> plat_0222_stat2_mc_subled_info[] __initdata = {
>> .color_index = LED_COLOR_ID_RED,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 5),
>> },
>> {
>> .color_index = LED_COLOR_ID_GREEN,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 4),
>> },
>> {
>> .color_index = LED_COLOR_ID_BLUE,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 3),
>> },
>> {
>> .color_index = LED_COLOR_ID_YELLOW,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 2),
>> },
>> };
>> @@ -379,24 +393,28 @@ static struct mc_subled
>> plat_0222_stat3_mc_subled_info[] __initdata = {
>> .color_index = LED_COLOR_ID_RED,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 1),
>> },
>> {
>> .color_index = LED_COLOR_ID_GREEN,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 0),
>> },
>> {
>> .color_index = LED_COLOR_ID_BLUE,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0e, 1),
>> },
>> {
>> .color_index = LED_COLOR_ID_YELLOW,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x0e, 0),
>> },
>> };
>> @@ -497,24 +515,28 @@ static struct mc_subled
>> cordoba_fp_left_mc_subled_info[] __initdata = {
>> .color_index = LED_COLOR_ID_RED,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x08, 6),
>> },
>> {
>> .color_index = LED_COLOR_ID_GREEN,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x08, 5),
>> },
>> {
>> .color_index = LED_COLOR_ID_BLUE,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x09, 7),
>> },
>> {
>> .color_index = LED_COLOR_ID_AMBER,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x09, 4),
>> },
>> };
>> @@ -524,24 +546,28 @@ static struct mc_subled
>> cordoba_fp_center_mc_subled_info[] __initdata = {
>> .color_index = LED_COLOR_ID_RED,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x08, 7),
>> },
>> {
>> .color_index = LED_COLOR_ID_GREEN,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x08, 4),
>> },
>> {
>> .color_index = LED_COLOR_ID_BLUE,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x08, 3),
>> },
>> {
>> .color_index = LED_COLOR_ID_AMBER,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x09, 6),
>> },
>> };
>> @@ -551,24 +577,28 @@ static struct mc_subled
>> cordoba_fp_right_mc_subled_info[] __initdata = {
>> .color_index = LED_COLOR_ID_RED,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x08, 2),
>> },
>> {
>> .color_index = LED_COLOR_ID_GREEN,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x08, 1),
>> },
>> {
>> .color_index = LED_COLOR_ID_BLUE,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x08, 0),
>> },
>> {
>> .color_index = LED_COLOR_ID_AMBER,
>> .brightness = 1,
>> .intensity = 0,
>> + .max_intensity = LED_USE_MAX_BRIGHTNESS,
>> .channel = OFFSET_BIT_TO_CHANNEL(0x09, 5),
>> },
>> };
>> diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c
>> b/drivers/platform/x86/uniwill/uniwill-acpi.c
>> index fee93537aa43..9468a93f7a59 100644
>> --- a/drivers/platform/x86/uniwill/uniwill-acpi.c
>> +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
>> @@ -1082,8 +1082,7 @@ static int uniwill_led_brightness_set(struct
>> led_classdev *led_cdev, enum led_br
>> guard(mutex)(&data->led_lock);
>> for (int i = 0; i < LED_CHANNELS; i++) {
>> - /* Prevent the brightness values from overflowing */
>> - value = min(LED_MAX_BRIGHTNESS,
>> data->led_mc_subled_info[i].brightness);
>> + value = data->led_mc_subled_info[i].brightness;
>> ret = regmap_write(data->regmap,
>> uniwill_led_channel_to_ac_reg[i], value);
>> if (ret < 0)
>> return ret;
>> @@ -1182,6 +1181,7 @@ static int uniwill_led_init(struct uniwill_data
>> *data)
>> return ret;
>> data->led_mc_subled_info[i].intensity = value;
>> + data->led_mc_subled_info[i].max_intensity =
>> LED_USE_MAX_BRIGHTNESS;
>> data->led_mc_subled_info[i].channel = i;
>> }
>> diff --git a/include/linux/led-class-multicolor.h
>> b/include/linux/led-class-multicolor.h
>> index db9f34c6736e..67ac0ac0fd54 100644
>> --- a/include/linux/led-class-multicolor.h
>> +++ b/include/linux/led-class-multicolor.h
>> @@ -9,10 +9,33 @@
>> #include <linux/leds.h>
>> #include <dt-bindings/leds/common.h>
>> +#define LED_USE_MAX_BRIGHTNESS 0
>> +
>> +/**
>> + * struct mc_subled - Color component description.
>> + * @color_index: Color ID.
>> + * @brightness: Scaled intensity.
>> + * @intensity: Current intensity.
>> + * @max_intensity: Maximum supported intensity value.
>> + * @channel: Channel index.
>> + *
>> + * Describes a color component of a multicolor LED. Many multicolor
>> LEDs
>> + * do no support gobal brightness control in hardware, so they use
>> + * the brightness field in connection with
>> led_mc_calc_color_components()
>> + * to perform the intensity scaling in software.
>> + * Such drivers should set max_intensity to LED_USE_MAX_BRIGHTNESS
>
> I'd just mention here that the field needs to be left initialized to 0,
> to let the LED subsystem to adjust it to max_brightness. Note that in
> case of max_brightness we allow for the same - if it is 0, then
> led-class.c adjusts it to legacy LED_FULL (255).
>
OK, do you think that having LED_USE_MAX_BRIGHTNESS makes sense?
>> + * to signal the multicolor LED core that the maximum global
>> brightness of the
>> + * LED class device should be used for limiting incoming intensity
>> values.
>> + *
>> + * Multicolor LEDs that do support global brightness control in
>> hardware
>> + * should instead set max_intensity to the maximum intensity value
>> supported
>> + * by the hardware for a given color component.
>> + */
>> struct mc_subled {
>> unsigned int color_index;
>> unsigned int brightness;
>> unsigned int intensity;
>> + unsigned int max_intensity;
>> unsigned int channel;
>> };
>> @@ -53,7 +76,14 @@ int led_classdev_multicolor_register_ext(struct
>> device *parent,
>> */
>> void led_classdev_multicolor_unregister(struct led_classdev_mc
>> *mcled_cdev);
>> -/* Calculate brightness for the monochrome LED cluster */
>> +/**
>> + * led_mc_calc_color_components() - Calculate brightness for the
>> monochrome LED cluster.
>
> It does not calculate brightness for the whole LED cluster, but for each
> sub-LED individually. Brightness of the whole cluster can be deemed a
> lightness of the multicolor LED, and the blend of the color components
> can be deemed its hue.
>
> In this case I'd document it as "Calculates brightness values for the
> LEDs of the monochrome LED cluster".
>
OK.
Thanks,
Armin Wolf
>> + * @mcled_cdev - Multicolor LED class device of the LED cluster.
>> + * @led_brightness - Global brightness of the LED cluster.
>> + *
>> + * Calculates the brightness values for each color component of a
>> monochrome LED cluster,
>> + * see Documentation/leds/leds-class-multicolor.rst for details.
>> + */
>> int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,
>> enum led_brightness brightness);
>
^ permalink raw reply
* Re: [PATCH v9 4/5] arm64: kexec: Add support for crashkernel CMA reservation
From: Breno Leitao @ 2026-03-23 10:20 UTC (permalink / raw)
To: Jinjie Ruan
Cc: corbet, skhan, catalin.marinas, will, chenhuacai, kernel, maddy,
mpe, npiggin, chleroy, pjw, palmer, aou, alex, tglx, mingo, bp,
dave.hansen, hpa, robh, saravanak, akpm, bhe, vgoyal, dyoung,
rdunlap, peterz, feng.tang, pawan.kumar.gupta, dapeng1.mi, kees,
elver, paulmck, lirongqing, safinaskar, rppt, ardb, jbohac,
cfsworks, osandov, tangyouling, sourabhjain, ritesh.list, eajames,
songshuaishuai, kevin.brodsky, samuel.holland, vishal.moola,
junhui.liu, coxu, liaoyuanhong, fuqiang.wang, x86, linux-doc,
linux-kernel, linux-arm-kernel, loongarch, linuxppc-dev,
linux-riscv, devicetree, kexec
In-Reply-To: <20260323072745.2481719-5-ruanjinjie@huawei.com>
On Mon, Mar 23, 2026 at 03:27:44PM +0800, Jinjie Ruan wrote:
> Commit 35c18f2933c5 ("Add a new optional ",cma" suffix to the
> crashkernel= command line option") and commit ab475510e042 ("kdump:
> implement reserve_crashkernel_cma") added CMA support for kdump
> crashkernel reservation.
>
> Crash kernel memory reservation wastes production resources if too
> large, risks kdump failure if too small, and faces allocation difficulties
> on fragmented systems due to contiguous block constraints. The new
> CMA-based crashkernel reservation scheme splits the "large fixed
> reservation" into a "small fixed region + large CMA dynamic region": the
> CMA memory is available to userspace during normal operation to avoid
> waste, and is reclaimed for kdump upon crash—saving memory while
> improving reliability.
>
> So extend crashkernel CMA reservation support to arm64. The following
> changes are made to enable CMA reservation:
>
> - Parse and obtain the CMA reservation size along with other crashkernel
> parameters.
> - Call reserve_crashkernel_cma() to allocate the CMA region for kdump.
> - Include the CMA-reserved ranges for kdump kernel to use.
> - Exclude the CMA-reserved ranges from the crash kernel memory to
> prevent them from being exported through /proc/vmcore, which is already
> done in the crash core.
>
> Update kernel-parameters.txt to document CMA support for crashkernel on
> arm64 architecture.
>
> Acked-by: Rob Herring (Arm) <robh@kernel.org>
> Acked-by: Baoquan He <bhe@redhat.com>
> Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> Acked-by: Ard Biesheuvel <ardb@kernel.org>
> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
> ---
> v7:
> - Correct the inclusion of CMA-reserved ranges for kdump
> kernel in of/kexec.
> v3:
> - Add Acked-by.
> v2:
> - Free cmem in prepare_elf_headers()
> - Add the mtivation.
> ---
> Documentation/admin-guide/kernel-parameters.txt | 2 +-
> arch/arm64/kernel/machine_kexec_file.c | 2 +-
> arch/arm64/mm/init.c | 5 +++--
> drivers/of/fdt.c | 9 +++++----
> drivers/of/kexec.c | 9 +++++++++
> 5 files changed, 19 insertions(+), 8 deletions(-)
>
> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> index cb850e5290c2..afb3112510f7 100644
> --- a/Documentation/admin-guide/kernel-parameters.txt
> +++ b/Documentation/admin-guide/kernel-parameters.txt
> @@ -1121,7 +1121,7 @@ Kernel parameters
> It will be ignored when crashkernel=X,high is not used
> or memory reserved is below 4G.
> crashkernel=size[KMG],cma
> - [KNL, X86, ppc] Reserve additional crash kernel memory from
> + [KNL, X86, ARM64, PPC] Reserve additional crash kernel memory from
> CMA. This reservation is usable by the first system's
> userspace memory and kernel movable allocations (memory
> balloon, zswap). Pages allocated from this memory range
> diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
> index c338506a580b..cc577d77df00 100644
> --- a/arch/arm64/kernel/machine_kexec_file.c
> +++ b/arch/arm64/kernel/machine_kexec_file.c
> @@ -42,7 +42,7 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
> #ifdef CONFIG_CRASH_DUMP
> unsigned int arch_get_system_nr_ranges(void)
> {
> - unsigned int nr_ranges = 2; /* for exclusion of crashkernel region */
> + unsigned int nr_ranges = 2 + crashk_cma_cnt; /* for exclusion of crashkernel region */
You update arch_get_system_nr_ranges() to account for CMA ranges, but
prepare_elf_headers() in the same file (line 51) still has the
hardcoded:
nr_ranges = 2; /* for exclusion of crashkernel region */
and does not exclude CMA ranges from cmem. If the generic crash core
handles CMA exclusion from vmcore, then shouldn't
arch_get_system_nr_ranges() also not need this change?
^ permalink raw reply
* Re: [PATCH v7 2/3] hwmon: ltc4283: Add support for the LTC4283 Swap Controller
From: Nuno Sá @ 2026-03-23 10:21 UTC (permalink / raw)
To: Guenter Roeck, Nuno Sá
Cc: linux-gpio, linux-hwmon, devicetree, linux-doc, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Shuah Khan,
Linus Walleij, Bartosz Golaszewski
In-Reply-To: <69e4e158-bd91-4df6-906c-a64f10408ae9@roeck-us.net>
On Tue, 2026-03-17 at 18:17 -0700, Guenter Roeck wrote:
> On Sat, Mar 14, 2026 at 10:52:20AM +0000, Nuno Sá wrote:
> > From: Nuno Sá <nuno.sa@analog.com>
> >
> > Support the LTC4283 Hot Swap Controller. The device features programmable
> > current limit with foldback and independently adjustable inrush current to
> > optimize the MOSFET safe operating area (SOA). The SOA timer limits MOSFET
> > temperature rise for reliable protection against overstresses.
> >
> > An I2C interface and onboard ADC allow monitoring of board current,
> > voltage, power, energy, and fault status.
> >
> > Signed-off-by: Nuno Sá <nuno.sa@analog.com>
> > ---
> > Documentation/hwmon/index.rst | 1 +
> > Documentation/hwmon/ltc4283.rst | 266 ++++++
> > MAINTAINERS | 1 +
> > drivers/hwmon/Kconfig | 12 +
> > drivers/hwmon/Makefile | 1 +
> > drivers/hwmon/ltc4283.c | 1787 +++++++++++++++++++++++++++++++++++++++
> > 6 files changed, 2068 insertions(+)
> >
> > diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> > index 559c32344cd38a9b5bf01a0b7570213f5ae1ab8b..eab95152abee0ae87acd9d01dab005200d80e924 100644
> > --- a/Documentation/hwmon/index.rst
> > +++ b/Documentation/hwmon/index.rst
> > @@ -144,6 +144,7 @@ Hardware Monitoring Kernel Drivers
> > ltc4260
> > ltc4261
> > ltc4282
> > + ltc4283
> > ltc4286
> > macsmc-hwmon
> > max127
> > diff --git a/Documentation/hwmon/ltc4283.rst b/Documentation/hwmon/ltc4283.rst
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..ba88445e45f4c2918a3c7f99a4b54efe69d4d048
> > --- /dev/null
> > +++ b/Documentation/hwmon/ltc4283.rst
> > @@ -0,0 +1,266 @@
> > +.. SPDX-License-Identifier: GPL-2.0-only
> > +
> > +Kernel drivers ltc4283
> > +==========================================
> > +
> > +Supported chips:
> > +
> > + * Analog Devices LTC4283
> > +
> > + Prefix: 'ltc4283'
> > +
> > + Addresses scanned: -
> > +
> > + Datasheet:
> > +
> > + https://www.analog.com/media/en/technical-documentation/data-sheets/ltc4283.pdf
> > +
> > +Author: Nuno Sá <nuno.sa@analog.com>
> > +
> > +Description
> > +___________
> > +
> > +The LTC4283 negative voltage hot swap controller drives an external N-channel
> > +MOSFET to allow a board to be safely inserted and removed from a live backplane.
> > +The device features programmable current limit with foldback and independently
> > +adjustable inrush current to optimize the MOSFET safe operating area (SOA). The
> > +SOA timer limits MOSFET temperature rise for reliable protection against
> > +overstresses. An I2C interface and onboard gear-shift ADC allow monitoring of
> > +board current, voltage, power, energy, and fault status. Additional features
> > +respond to input UV/OV, interrupt the host when a fault has occurred, notify
> > +when output power is good, detect insertion of a board, turn off the MOSFET
> > +if an external supply monitor fails to indicate power good within a timeout
> > +period, and auto-reboot after a programmable delay following a host commanded
> > +turn-off.
> > +
> > +Sysfs entries
> > +_____________
> > +
> > +The following attributes are supported. Limits are read-write and all the other
> > +attributes are read-only. Note that the VADIOx channels might not be available
> > +if the ADIO pins are used as GPIOs (naturally also affects the respective
> > +differential channels).
> > +
> > +======================= ==========================================
> > +in0_lcrit_alarm Critical Undervoltage alarm
> > +in0_crit_alarm Critical Overvoltage alarm
> > +in0_label Channel label (VIN)
> > +
> > +in1_input Output voltage (mV).
> > +in1_min Undervoltage threshold
> > +in1_max Overvoltage threshold
> > +in1_lowest Lowest measured voltage
> > +in1_highest Highest measured voltage
> > +in1_reset_history Write 1 to reset history.
> > +in1_min_alarm Undervoltage alarm
> > +in1_max_alarm Overvoltage alarm
> > +in1_label Channel label (VPWR)
> > +
> > +in2_input Output voltage (mV).
> > +in2_min Undervoltage threshold
> > +in2_max Overvoltage threshold
> > +in2_lowest Lowest measured voltage
> > +in2_highest Highest measured voltage
> > +in2_reset_history Write 1 to reset history.
> > +in2_min_alarm Undervoltage alarm
> > +in2_max_alarm Overvoltage alarm
> > +in2_enable Enable/Disable monitoring.
> > +in2_label Channel label (VADI1)
> > +
> > +in3_input Output voltage (mV).
> > +in3_min Undervoltage threshold
> > +in3_max Overvoltage threshold
> > +in3_lowest Lowest measured voltage
> > +in3_highest Highest measured voltage
> > +in3_reset_history Write 1 to reset history.
> > +in3_min_alarm Undervoltage alarm
> > +in3_max_alarm Overvoltage alarm
> > +in3_enable Enable/Disable monitoring.
> > +in3_label Channel label (VADI2)
> > +
> > +in4_input Output voltage (mV).
> > +in4_min Undervoltage threshold
> > +in4_max Overvoltage threshold
> > +in4_lowest Lowest measured voltage
> > +in4_highest Highest measured voltage
> > +in4_reset_history Write 1 to reset history.
> > +in4_min_alarm Undervoltage alarm
> > +in4_max_alarm Overvoltage alarm
> > +in4_enable Enable/Disable monitoring.
> > +in4_label Channel label (VADI3)
> > +
> > +in5_input Output voltage (mV).
> > +in5_min Undervoltage threshold
> > +in5_max Overvoltage threshold
> > +in5_lowest Lowest measured voltage
> > +in5_highest Highest measured voltage
> > +in5_reset_history Write 1 to reset history.
> > +in5_min_alarm Undervoltage alarm
> > +in5_max_alarm Overvoltage alarm
> > +in5_enable Enable/Disable monitoring.
> > +in5_label Channel label (VADI4)
> > +
> > +in6_input Output voltage (mV).
> > +in6_min Undervoltage threshold
> > +in6_max Overvoltage threshold
> > +in6_lowest Lowest measured voltage
> > +in6_highest Highest measured voltage
> > +in6_reset_history Write 1 to reset history.
> > +in6_min_alarm Undervoltage alarm
> > +in6_max_alarm Overvoltage alarm
> > +in6_enable Enable/Disable monitoring.
> > +in6_label Channel label (VADIO1)
> > +
> > +in7_input Output voltage (mV).
> > +in7_min Undervoltage threshold
> > +in7_max Overvoltage threshold
> > +in7_lowest Lowest measured voltage
> > +in7_highest Highest measured voltage
> > +in7_reset_history Write 1 to reset history.
> > +in7_min_alarm Undervoltage alarm
> > +in7_max_alarm Overvoltage alarm
> > +in7_enable Enable/Disable monitoring.
> > +in7_label Channel label (VADIO2)
> > +
> > +in8_input Output voltage (mV).
> > +in8_min Undervoltage threshold
> > +in8_max Overvoltage threshold
> > +in8_lowest Lowest measured voltage
> > +in8_highest Highest measured voltage
> > +in8_reset_history Write 1 to reset history.
> > +in8_min_alarm Undervoltage alarm
> > +in8_max_alarm Overvoltage alarm
> > +in8_enable Enable/Disable monitoring.
> > +in8_label Channel label (VADIO3)
> > +
> > +in9_input Output voltage (mV).
> > +in9_min Undervoltage threshold
> > +in9_max Overvoltage threshold
> > +in9_lowest Lowest measured voltage
> > +in9_highest Highest measured voltage
> > +in9_reset_history Write 1 to reset history.
> > +in9_min_alarm Undervoltage alarm
> > +in9_max_alarm Overvoltage alarm
> > +in9_enable Enable/Disable monitoring.
> > +in9_label Channel label (VADIO4)
> > +
> > +in10_input Output voltage (mV).
> > +in10_min Undervoltage threshold
> > +in10_max Overvoltage threshold
> > +in10_lowest Lowest measured voltage
> > +in10_highest Highest measured voltage
> > +in10_reset_history Write 1 to reset history.
> > +in10_min_alarm Undervoltage alarm
> > +in10_max_alarm Overvoltage alarm
> > +in10_enable Enable/Disable monitoring.
> > +in10_label Channel label (DRNS)
> > +
> > +in11_input Output voltage (mV).
> > +in11_min Undervoltage threshold
> > +in11_max Overvoltage threshold
> > +in11_lowest Lowest measured voltage
> > +in11_highest Highest measured voltage
> > +in11_reset_history Write 1 to reset history.
> > + Also clears fet bad and short fault logs.
> > +in11_min_alarm Undervoltage alarm
> > +in11_max_alarm Overvoltage alarm
> > +in11_enable Enable/Disable monitoring
> > +in11_fault Failure in the MOSFET. Either bad or shorted FET.
> > +in11_label Channel label (DRAIN)
> > +
> > +in12_input Output voltage (mV).
> > +in12_min Undervoltage threshold
> > +in12_max Overvoltage threshold
> > +in12_lowest Lowest measured voltage
> > +in12_highest Highest measured voltage
> > +in12_reset_history Write 1 to reset history.
> > +in12_min_alarm Undervoltage alarm
> > +in12_max_alarm Overvoltage alarm
> > +in12_enable Enable/Disable monitoring.
> > +in12_label Channel label (ADIN2-ADIN1)
> > +
> > +in13_input Output voltage (mV).
> > +in13_min Undervoltage threshold
> > +in13_max Overvoltage threshold
> > +in13_lowest Lowest measured voltage
> > +in13_highest Highest measured voltage
> > +in13_reset_history Write 1 to reset history.
> > +in13_min_alarm Undervoltage alarm
> > +in13_max_alarm Overvoltage alarm
> > +in13_enable Enable/Disable monitoring.
> > +in13_label Channel label (ADIN4-ADIN3)
> > +
> > +in14_input Output voltage (mV).
> > +in14_min Undervoltage threshold
> > +in14_max Overvoltage threshold
> > +in14_lowest Lowest measured voltage
> > +in14_highest Highest measured voltage
> > +in14_reset_history Write 1 to reset history.
> > +in14_min_alarm Undervoltage alarm
> > +in14_max_alarm Overvoltage alarm
> > +in14_enable Enable/Disable monitoring.
> > +in14_label Channel label (ADIO2-ADIO1)
> > +
> > +in15_input Output voltage (mV).
> > +in15_min Undervoltage threshold
> > +in15_max Overvoltage threshold
> > +in15_lowest Lowest measured voltage
> > +in15_highest Highest measured voltage
> > +in15_reset_history Write 1 to reset history.
> > +in15_min_alarm Undervoltage alarm
> > +in15_max_alarm Overvoltage alarm
> > +in15_enable Enable/Disable monitoring.
> > +in15_label Channel label (ADIO4-ADIO3)
> > +
> > +curr1_input Sense current (mA)
> > +curr1_min Undercurrent threshold
> > +curr1_max Overcurrent threshold
> > +curr1_lowest Lowest measured current
> > +curr1_highest Highest measured current
> > +curr1_reset_history Write 1 to reset curr1 history.
> > + Also clears overcurrent fault logs.
> > +curr1_min_alarm Undercurrent alarm
> > +curr1_max_alarm Overcurrent alarm
> > +curr1_crit_alarm Critical Overcurrent alarm
> > +curr1_label Channel label (ISENSE)
> > +
> > +power1_input Power (in uW)
> > +power1_min Low power threshold
> > +power1_max High power threshold
> > +power1_input_lowest Historical minimum power use
> > +power1_input_highest Historical maximum power use
> > +power1_reset_history Write 1 to reset power1 history.
> > + Also clears power fault logs.
> > +power1_min_alarm Low power alarm
> > +power1_max_alarm High power alarm
> > +power1_label Channel label (Power)
> > +
> > +energy1_input Measured energy over time (in microJoule)
> > +energy1_enable Enable/Disable Energy accumulation
> > +======================= ==========================================
> > +
> > +DebugFs entries
> > +_______________
> > +
> > +The chip also has a fault log register where failures can be logged. Hence,
> > +as these are logging events, we give access to them in debugfs. Note that
> > +even if some failure is detected in these logs, it does necessarily mean
> > +that the failure is still present. As mentioned in the proper Sysfs entries,
> > +these logs can be cleared by writing in the proper reset_history attribute.
> > +
> > +.. warning:: The debugfs interface is subject to change without notice
> > + and is only available when the kernel is compiled with
> > + ``CONFIG_DEBUG_FS`` defined.
> > +
> > +``/sys/kernel/debug/i2c/i2c-[X]/[X]-addr/``
> > +contains the following attributes:
> > +
> > +======================= ==========================================
> > +power1_failed_fault_log Set to 1 by a power1 fault occurring.
> > +power1_good_input_fault_log Set to 1 by a power1 good input fault occurring at PGIO3.
> > +in11_fet_short_fault_log Set to 1 when a FET-short fault occurs.
> > +in11_fet_bad_fault_log Set to 1 when a FET-BAD fault occurs.
> > +in0_lcrit_fault_log Set to 1 by a VIN undervoltage fault occurring.
> > +in0_crit_fault_log Set to 1 by a VIN overvoltage fault occurring.
> > +curr1_crit_fault_log Set to 1 by an overcurrent fault occurring.
> > +======================= ==========================================
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 13ae2f3db449e5fd3a7d0fbac92aabdc01734ba9..38d22cf622b7982e157b19e3ecbf30a80dfbb04d 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -15146,6 +15146,7 @@ M: Nuno Sá <nuno.sa@analog.com>
> > L: linux-hwmon@vger.kernel.org
> > S: Supported
> > F: Documentation/devicetree/bindings/hwmon/adi,ltc4283.yaml
> > +F: drivers/hwmon/ltc4283.c
> >
> > LTC4286 HARDWARE MONITOR DRIVER
> > M: Delphine CC Chiu <Delphine_CC_Chiu@Wiwynn.com>
> > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> > index fb77baeeba27b9ec51142040beccc953df0a1e3e..99ab14726d6b559a2f8bc2af5b79ecd9d0b80b87 100644
> > --- a/drivers/hwmon/Kconfig
> > +++ b/drivers/hwmon/Kconfig
> > @@ -1157,6 +1157,18 @@ config SENSORS_LTC4282
> > This driver can also be built as a module. If so, the module will
> > be called ltc4282.
> >
> > +config SENSORS_LTC4283
> > + tristate "Analog Devices LTC4283"
> > + depends on I2C
> > + select REGMAP_I2C
> > + select AUXILIARY_BUS
> > + help
> > + If you say yes here you get support for Analog Devices LTC4283
> > + Negative Voltage Hot Swap Controller I2C interface.
> > +
> > + This driver can also be built as a module. If so, the module will
> > + be called ltc4283.
> > +
> > config SENSORS_LTQ_CPUTEMP
> > bool "Lantiq cpu temperature sensor driver"
> > depends on SOC_XWAY
> > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> > index 556e86d277b195d8c98798012f0069c78a2f37dc..cb77938dbe07ddb0c2db69f27ade2794139c2d01 100644
> > --- a/drivers/hwmon/Makefile
> > +++ b/drivers/hwmon/Makefile
> > @@ -147,6 +147,7 @@ obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o
> > obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o
> > obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o
> > obj-$(CONFIG_SENSORS_LTC4282) += ltc4282.o
> > +obj-$(CONFIG_SENSORS_LTC4283) += ltc4283.o
> > obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
> > obj-$(CONFIG_SENSORS_MACSMC_HWMON) += macsmc-hwmon.o
> > obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
> > diff --git a/drivers/hwmon/ltc4283.c b/drivers/hwmon/ltc4283.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..2066ff5d568fbaa75c66d1f75087f7eb8cfe83a2
> > --- /dev/null
> > +++ b/drivers/hwmon/ltc4283.c
> > @@ -0,0 +1,1787 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Analog Devices LTC4283 I2C Negative Voltage Hot Swap Controller (HWMON)
> > + *
> > + * Copyright 2025 Analog Devices Inc.
> > + */
> > +#include <linux/auxiliary_bus.h>
> > +#include <linux/bitfield.h>
> > +#include <linux/bitmap.h>
> > +#include <linux/bitops.h>
> > +#include <linux/bits.h>
> > +
> > +#include <linux/debugfs.h>
> > +#include <linux/device.h>
> > +#include <linux/device/devres.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/i2c.h>
> > +#include <linux/math.h>
> > +#include <linux/math64.h>
> > +#include <linux/minmax.h>
> > +#include <linux/module.h>
> > +
> > +#include <linux/mod_devicetable.h>
> > +#include <linux/overflow.h>
> > +#include <linux/property.h>
> > +#include <linux/regmap.h>
> > +#include <linux/unaligned.h>
> > +#include <linux/units.h>
> > +
> > +#define LTC4283_SYSTEM_STATUS 0x00
> > +#define LTC4283_FAULT_STATUS 0x03
> > +#define LTC4283_OV_MASK BIT(0)
> > +#define LTC4283_UV_MASK BIT(1)
> > +#define LTC4283_OC_MASK BIT(2)
> > +#define LTC4283_FET_BAD_MASK BIT(3)
> > +#define LTC4283_FET_SHORT_MASK BIT(6)
> > +#define LTC4283_FAULT_LOG 0x04
> > +#define LTC4283_OV_FAULT_MASK BIT(0)
> > +#define LTC4283_UV_FAULT_MASK BIT(1)
> > +#define LTC4283_OC_FAULT_MASK BIT(2)
> > +#define LTC4283_FET_BAD_FAULT_MASK BIT(3)
> > +#define LTC4283_PGI_FAULT_MASK BIT(4)
> > +#define LTC4283_PWR_FAIL_FAULT_MASK BIT(5)
> > +#define LTC4283_FET_SHORT_FAULT_MASK BIT(6)
> > +#define LTC4283_ADC_ALM_LOG_1 0x05
> > +#define LTC4283_POWER_LOW_ALM BIT(0)
> > +#define LTC4283_POWER_HIGH_ALM BIT(1)
> > +#define LTC4283_SENSE_LOW_ALM BIT(4)
> > +#define LTC4283_SENSE_HIGH_ALM BIT(5)
> > +#define LTC4283_ADC_ALM_LOG_2 0x06
> > +#define LTC4283_ADC_ALM_LOG_3 0x07
> > +#define LTC4283_ADC_ALM_LOG_4 0x08
> > +#define LTC4283_ADC_ALM_LOG_5 0x09
> > +#define LTC4283_CONTROL_1 0x0a
> > +#define LTC4283_RW_PAGE_MASK BIT(0)
> > +#define LTC4283_PIGIO2_ACLB_MASK BIT(2)
> > +#define LTC4283_PWRGD_RST_CTRL_MASK BIT(3)
> > +#define LTC4283_FET_BAD_OFF_MASK BIT(4)
> > +#define LTC4283_THERM_TMR_MASK BIT(5)
> > +#define LTC4283_DVDT_MASK BIT(6)
> > +#define LTC4283_CONTROL_2 0x0b
> > +#define LTC4283_OV_RETRY_MASK BIT(0)
> > +#define LTC4283_UV_RETRY_MASK BIT(1)
> > +#define LTC4283_OC_RETRY_MASK GENMASK(3, 2)
> > +#define LTC4283_FET_BAD_RETRY_MASK GENMASK(5, 4)
> > +#define LTC4283_EXT_FAULT_RETRY_MASK BIT(7)
> > +#define LTC4283_RESERVED_OC 0x0c
> > +#define LTC4283_CONFIG_1 0x0d
> > +#define LTC4283_FB_MASK GENMASK(3, 2)
> > +#define LTC4283_ILIM_MASK GENMASK(7, 4)
> > +#define LTC4283_CONFIG_2 0x0e
> > +#define LTC4283_COOLING_DL_MASK GENMASK(3, 1)
> > +#define LTC4283_FTBD_DL_MASK GENMASK(5, 4)
> > +#define LTC4283_CONFIG_3 0x0f
> > +#define LTC4283_VPWR_DRNS_MASK BIT(6)
> > +#define LTC4283_EXTFLT_TURN_OFF_MASK BIT(7)
> > +#define LTC4283_PGIO_CONFIG 0x10
> > +#define LTC4283_PGIO1_CFG_MASK GENMASK(1, 0)
> > +#define LTC4283_PGIO2_CFG_MASK GENMASK(3, 2)
> > +#define LTC4283_PGIO3_CFG_MASK GENMASK(5, 4)
> > +#define LTC4283_PGIO4_CFG_MASK GENMASK(7, 6)
> > +#define LTC4283_PGIO_CONFIG_2 0x11
> > +#define LTC4283_ADC_MASK GENMASK(2, 0)
> > +#define LTC4283_ADC_SELECT(c) (0x13 + (c) / 8)
> > +#define LTC4283_ADC_SELECT_MASK(c) BIT((c) % 8)
> > +#define LTC4283_SENSE_MIN_TH 0x1b
> > +#define LTC4283_SENSE_MAX_TH 0x1c
> > +#define LTC4283_VPWR_MIN_TH 0x1d
> > +#define LTC4283_VPWR_MAX_TH 0x1e
> > +#define LTC4283_POWER_MIN_TH 0x1f
> > +#define LTC4283_POWER_MAX_TH 0x20
> > +#define LTC4283_ADC_2_MIN_TH(c) (0x21 + (c) * 2)
> > +#define LTC4283_ADC_2_MAX_TH(c) (0x22 + (c) * 2)
> > +#define LTC4283_ADC_2_MIN_TH_DIFF(c) (0x39 + (c) * 2)
> > +#define LTC4283_ADC_2_MAX_TH_DIFF(c) (0x3a + (c) * 2)
> > +#define LTC4283_SENSE 0x41
> > +#define LTC4283_SENSE_MIN 0x42
> > +#define LTC4283_SENSE_MAX 0x43
> > +#define LTC4283_VPWR 0x44
> > +#define LTC4283_VPWR_MIN 0x45
> > +#define LTC4283_VPWR_MAX 0x46
> > +#define LTC4283_POWER 0x47
> > +#define LTC4283_POWER_MIN 0x48
> > +#define LTC4283_POWER_MAX 0x49
> > +#define LTC4283_RESERVED_68 0x68
> > +#define LTC4283_RESERVED_6D 0x6D
> > +/* get channels from ADC 2 */
> > +#define LTC4283_ADC_2(c) (0x4a + (c) * 3)
> > +#define LTC4283_ADC_2_MIN(c) (0x4b + (c) * 3)
> > +#define LTC4283_ADC_2_MAX(c) (0x4c + (c) * 3)
> > +#define LTC4283_ADC_2_DIFF(c) (0x6e + (c) * 3)
> > +#define LTC4283_ADC_2_MIN_DIFF(c) (0x6f + (c) * 3)
> > +#define LTC4283_ADC_2_MAX_DIFF(c) (0x70 + (c) * 3)
> > +#define LTC4283_ENERGY 0x7a
> > +#define LTC4283_METER_CONTROL 0x84
> > +#define LTC4283_INTEGRATE_I_MASK BIT(0)
> > +#define LTC4283_METER_HALT_MASK BIT(6)
> > +#define LTC4283_RESERVED_86 0x86
> > +#define LTC4283_RESERVED_8F 0x8F
> > +#define LTC4283_FAULT_LOG_CTRL 0x90
> > +#define LTC4283_FAULT_LOG_EN_MASK BIT(7)
> > +#define LTC4283_RESERVED_91 0x91
> > +#define LTC4283_RESERVED_A1 0xA1
> > +#define LTC4283_RESERVED_A3 0xA3
> > +#define LTC4283_RESERVED_AC 0xAC
> > +#define LTC4283_POWER_PLAY_MSB 0xE7
> > +#define LTC4283_POWER_PLAY_LSB 0xE8
> > +#define LTC4283_RESERVED_F1 0xF1
> > +#define LTC4283_RESERVED_FF 0xFF
> > +
> > +/* also applies for differential channels */
> > +#define LTC4283_ADC1_FS_uV 32768
> > +#define LTC4283_ADC2_FS_mV 2048
> > +#define LTC4283_TCONV_uS 64103
> > +#define LTC4283_VILIM_MIN_uV 15000
> > +#define LTC4283_VILIM_MAX_uV 30000
> > +#define LTC4283_VILIM_RANGE \
> > + (LTC4283_VILIM_MAX_uV - LTC4283_VILIM_MIN_uV + 1)
> > +
> > +#define LTC4283_PGIO_FUNC_GPIO 2
> > +#define LTC4283_PGIO2_FUNC_ACLB 3
> > +
> > +/* voltage channels */
> > +enum {
> > + LTC4283_CHAN_VIN,
> > + LTC4283_CHAN_VPWR,
> > + LTC4283_CHAN_ADI_1,
> > + LTC4283_CHAN_ADI_2,
> > + LTC4283_CHAN_ADI_3,
> > + LTC4283_CHAN_ADI_4,
> > + LTC4283_CHAN_ADIO_1,
> > + LTC4283_CHAN_ADIO_2,
> > + LTC4283_CHAN_ADIO_3,
> > + LTC4283_CHAN_ADIO_4,
> > + LTC4283_CHAN_DRNS,
> > + LTC4283_CHAN_DRAIN,
> > + /* differential channels */
> > + LTC4283_CHAN_ADIN12,
> > + LTC4283_CHAN_ADIN34,
> > + LTC4283_CHAN_ADIO12,
> > + LTC4283_CHAN_ADIO34,
> > + LTC4283_CHAN_MAX
> > +};
> > +
> > +/* Just for ease of use on the regmap */
> > +#define LTC4283_ADIO34_MAX \
> > + LTC4283_ADC_2_MAX_DIFF(LTC4283_CHAN_ADIO34 - LTC4283_CHAN_ADIN12)
> > +
> > +struct ltc4283_hwmon {
> > + struct regmap *map;
> > + struct i2c_client *client;
> > + unsigned long gpio_mask;
> > + unsigned long ch_enable_mask;
> > + /* in microwatt */
> > + long power_max;
> > + /* in millivolt */
> > + u32 vsense_max;
> > + /* in tenths of microohm*/
> > + u32 rsense;
> > + bool energy_en;
> > + bool ext_fault;
> > +};
> > +
> > +static int ltc4283_read_voltage_word(const struct ltc4283_hwmon *st,
> > + u32 reg, u32 fs, long *val)
> > +{
> > + unsigned int __raw;
> > + int ret;
> > +
> > + ret = regmap_read(st->map, reg, &__raw);
> > + if (ret)
> > + return ret;
> > +
> > + *val = DIV_ROUND_CLOSEST(__raw * fs, BIT(16));
> > + return 0;
> > +}
> > +
> > +static int ltc4283_read_voltage_byte(const struct ltc4283_hwmon *st,
> > + u32 reg, u32 fs, long *val)
> > +{
> > + int ret;
> > + u32 in;
> > +
> > + ret = regmap_read(st->map, reg, &in);
> > + if (ret)
> > + return ret;
> > +
> > + *val = DIV_ROUND_CLOSEST(in * fs, BIT(8));
> > + return 0;
> > +}
> > +
> > +static u32 ltc4283_in_reg(u32 attr, u32 channel)
> > +{
> > + switch (attr) {
> > + case hwmon_in_input:
> > + if (channel == LTC4283_CHAN_VPWR)
> > + return LTC4283_VPWR;
> > + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> > + return LTC4283_ADC_2(channel - LTC4283_CHAN_ADI_1);
> > + return LTC4283_ADC_2_DIFF(channel - LTC4283_CHAN_ADIN12);
> > + case hwmon_in_highest:
> > + if (channel == LTC4283_CHAN_VPWR)
> > + return LTC4283_VPWR_MAX;
> > + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> > + return LTC4283_ADC_2_MAX(channel - LTC4283_CHAN_ADI_1);
> > + return LTC4283_ADC_2_MAX_DIFF(channel - LTC4283_CHAN_ADIN12);
> > + case hwmon_in_lowest:
> > + if (channel == LTC4283_CHAN_VPWR)
> > + return LTC4283_VPWR_MIN;
> > + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> > + return LTC4283_ADC_2_MIN(channel - LTC4283_CHAN_ADI_1);
> > + return LTC4283_ADC_2_MIN_DIFF(channel - LTC4283_CHAN_ADIN12);
> > + case hwmon_in_max:
> > + if (channel == LTC4283_CHAN_VPWR)
> > + return LTC4283_VPWR_MAX_TH;
> > + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> > + return LTC4283_ADC_2_MAX_TH(channel - LTC4283_CHAN_ADI_1);
> > + return LTC4283_ADC_2_MAX_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> > + default:
> > + if (channel == LTC4283_CHAN_VPWR)
> > + return LTC4283_VPWR_MIN_TH;
> > + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> > + return LTC4283_ADC_2_MIN_TH(channel - LTC4283_CHAN_ADI_1);
> > + return LTC4283_ADC_2_MIN_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> > + }
> > +}
> > +
> > +static int ltc4283_read_in_vals(const struct ltc4283_hwmon *st,
> > + u32 attr, u32 channel, long *val)
> > +{
> > + u32 reg = ltc4283_in_reg(attr, channel);
> > + int ret;
> > +
> > + if (channel < LTC4283_CHAN_ADIN12) {
> > + if (attr != hwmon_in_max && attr != hwmon_in_min)
> > + return ltc4283_read_voltage_word(st, reg,
> > + LTC4283_ADC2_FS_mV,
> > + val);
> > +
> > + return ltc4283_read_voltage_byte(st, reg,
> > + LTC4283_ADC2_FS_mV, val);
> > + }
> > +
> > + if (attr != hwmon_in_max && attr != hwmon_in_min)
> > + ret = ltc4283_read_voltage_word(st, reg,
> > + LTC4283_ADC1_FS_uV, val);
> > + else
> > + ret = ltc4283_read_voltage_byte(st, reg,
> > + LTC4283_ADC1_FS_uV, val);
> > + if (ret)
> > + return ret;
> > +
> > + *val = DIV_ROUND_CLOSEST(*val, MILLI);
> > + return 0;
> > +}
> > +
> > +static int ltc4283_read_alarm(struct ltc4283_hwmon *st, u32 reg,
> > + u32 mask, long *val)
> > +{
> > + u32 alarm;
> > + int ret;
> > +
> > + ret = regmap_read(st->map, reg, &alarm);
> > + if (ret)
> > + return ret;
> > +
> > + *val = !!(alarm & mask);
> > +
> > + /* If not status/fault logs, clear the alarm after reading it. */
> > + if (reg != LTC4283_FAULT_STATUS && reg != LTC4283_FAULT_LOG)
> > + return regmap_clear_bits(st->map, reg, mask);
> > +
> > + return 0;
> > +}
> > +
> > +static int ltc4283_read_in_alarm(struct ltc4283_hwmon *st, u32 channel,
> > + bool max_alm, long *val)
> > +{
> > + if (channel == LTC4283_VPWR)
> > + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> > + BIT(2 + max_alm), val);
> > +
> > + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_ADI_4) {
> > + u32 bit = (channel - LTC4283_CHAN_ADI_1) * 2;
> > + /*
> > + * Lower channels go to higher bits. We also want to go +1 down
> > + * in the min_alarm case.
> > + */
> > + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_2,
> > + BIT(7 - bit - !max_alm), val);
> > + }
> > +
> > + if (channel >= LTC4283_CHAN_ADIO_1 && channel <= LTC4283_CHAN_ADIO_4) {
> > + u32 bit = (channel - LTC4283_CHAN_ADIO_1) * 2;
> > +
> > + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_3,
> > + BIT(7 - bit - !max_alm), val);
> > + }
> > +
> > + if (channel >= LTC4283_CHAN_ADIN12 && channel <= LTC4283_CHAN_ADIN34) {
> > + u32 bit = (channel - LTC4283_CHAN_ADIN12) * 2;
> > +
> > + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_5,
> > + BIT(7 - bit - !max_alm), val);
> > + }
> > +
> > + if (channel == LTC4283_CHAN_DRNS)
> > + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_4,
> > + BIT(6 + max_alm), val);
> > +
> > + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_4, BIT(4 + max_alm),
> > + val);
> > +}
> > +
> > +static int ltc4283_read_in(struct ltc4283_hwmon *st, u32 attr, u32 channel,
> > + long *val)
> > +{
> > + switch (attr) {
> > + case hwmon_in_input:
> > + if (!test_bit(channel, &st->ch_enable_mask))
> > + return -ENODATA;
> > +
> > + return ltc4283_read_in_vals(st, attr, channel, val);
> > + case hwmon_in_highest:
> > + case hwmon_in_lowest:
> > + case hwmon_in_max:
> > + case hwmon_in_min:
> > + return ltc4283_read_in_vals(st, attr, channel, val);
> > + case hwmon_in_max_alarm:
> > + return ltc4283_read_in_alarm(st, channel, true, val);
> > + case hwmon_in_min_alarm:
> > + return ltc4283_read_in_alarm(st, channel, false, val);
> > + case hwmon_in_crit_alarm:
> > + return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> > + LTC4283_OV_MASK, val);
> > + case hwmon_in_lcrit_alarm:
> > + return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> > + LTC4283_UV_MASK, val);
> > + case hwmon_in_fault:
> > + /*
> > + * We report failure if we detect either a fer_bad or a
> > + * fet_short in the status register.
> > + */
> > + return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> > + LTC4283_FET_BAD_MASK | LTC4283_FET_SHORT_MASK, val);
> > + case hwmon_in_enable:
> > + *val = test_bit(channel, &st->ch_enable_mask);
> > + return 0;
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > + return 0;
> > +}
> > +
> > +static int ltc4283_read_current_word(const struct ltc4283_hwmon *st, u32 reg,
> > + long *val)
> > +{
> > + u64 temp = (u64)LTC4283_ADC1_FS_uV * DECA * MILLI;
> > + unsigned int __raw;
> > + int ret;
> > +
> > + ret = regmap_read(st->map, reg, &__raw);
> > + if (ret)
> > + return ret;
> > +
> > + *val = DIV64_U64_ROUND_CLOSEST(__raw * temp,
> > + BIT_ULL(16) * st->rsense);
> > +
> > + return 0;
> > +}
> > +
> > +static int ltc4283_read_current_byte(const struct ltc4283_hwmon *st, u32 reg,
> > + long *val)
> > +{
> > + u64 temp = (u64)LTC4283_ADC1_FS_uV * DECA * MILLI;
> > + u32 curr;
> > + int ret;
> > +
> > + ret = regmap_read(st->map, reg, &curr);
> > + if (ret)
> > + return ret;
> > +
> > + *val = DIV_ROUND_CLOSEST_ULL(curr * temp, BIT(8) * st->rsense);
> > + return 0;
> > +}
> > +
> > +static int ltc4283_read_curr(struct ltc4283_hwmon *st, u32 attr, long *val)
> > +{
> > + switch (attr) {
> > + case hwmon_curr_input:
> > + return ltc4283_read_current_word(st, LTC4283_SENSE, val);
> > + case hwmon_curr_highest:
> > + return ltc4283_read_current_word(st, LTC4283_SENSE_MAX, val);
> > + case hwmon_curr_lowest:
> > + return ltc4283_read_current_word(st, LTC4283_SENSE_MIN, val);
> > + case hwmon_curr_max:
> > + return ltc4283_read_current_byte(st, LTC4283_SENSE_MAX_TH, val);
> > + case hwmon_curr_min:
> > + return ltc4283_read_current_byte(st, LTC4283_SENSE_MIN_TH, val);
> > + case hwmon_curr_max_alarm:
> > + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> > + LTC4283_SENSE_HIGH_ALM, val);
> > + case hwmon_curr_min_alarm:
> > + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> > + LTC4283_SENSE_LOW_ALM, val);
> > + case hwmon_curr_crit_alarm:
> > + return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> > + LTC4283_OC_MASK, val);
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +}
> > +
> > +static int ltc4283_read_power_word(const struct ltc4283_hwmon *st,
> > + u32 reg, long *val)
> > +{
> > + u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> > + unsigned int __raw;
> > + int ret;
> > +
> > + ret = regmap_read(st->map, reg, &__raw);
> > + if (ret)
> > + return ret;
> > +
> > + /*
> > + * Power is given by:
> > + * P = CODE(16b) * 32.768mV * 2.048V / (2^16 * Rsense)
> > + */
> > + *val = DIV64_U64_ROUND_CLOSEST(temp * __raw, BIT_ULL(16) * st->rsense);
> > +
> > + return 0;
> > +}
> > +
> > +static int ltc4283_read_power_byte(const struct ltc4283_hwmon *st,
> > + u32 reg, long *val)
> > +{
> > + u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> > + u32 power;
> > + int ret;
> > +
> > + ret = regmap_read(st->map, reg, &power);
> > + if (ret)
> > + return ret;
> > +
> > + *val = DIV_ROUND_CLOSEST_ULL(power * temp, BIT(8) * st->rsense);
> > +
> > + return 0;
> > +}
> > +
> > +static int ltc4283_read_power(struct ltc4283_hwmon *st, u32 attr, long *val)
> > +{
> > + switch (attr) {
> > + case hwmon_power_input:
> > + return ltc4283_read_power_word(st, LTC4283_POWER, val);
> > + case hwmon_power_input_highest:
> > + return ltc4283_read_power_word(st, LTC4283_POWER_MAX, val);
> > + case hwmon_power_input_lowest:
> > + return ltc4283_read_power_word(st, LTC4283_POWER_MIN, val);
> > + case hwmon_power_max_alarm:
> > + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> > + LTC4283_POWER_HIGH_ALM, val);
> > + case hwmon_power_min_alarm:
> > + return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> > + LTC4283_POWER_LOW_ALM, val);
> > + case hwmon_power_max:
> > + return ltc4283_read_power_byte(st, LTC4283_POWER_MAX_TH, val);
> > + case hwmon_power_min:
> > + return ltc4283_read_power_byte(st, LTC4283_POWER_MIN_TH, val);
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +}
> > +
> > +static int ltc4283_read_energy(struct ltc4283_hwmon *st, u32 attr, s64 *val)
> > +{
> > + u64 temp = LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV, energy, temp_2;
> > + u8 raw[8] = {};
> > + int ret;
> > +
> > + if (!st->energy_en)
> > + return -ENODATA;
> > +
> > + ret = i2c_smbus_read_i2c_block_data(st->client, LTC4283_ENERGY, 6, raw);
> > + if (ret < 0)
> > + return ret;
> > + if (ret != 6)
> > + return -EIO;
> > +
> > + energy = get_unaligned_be64(raw) >> 16;
> > +
> > + /*
> > + * The formula for energy is given by:
> > + * E = CODE(48b) * 32.768mV * 2.048V * Tconv / 2^24 * Rsense
> > + *
> > + * As Rsense can have tenths of micro-ohm resolution, we need to
> > + * multiply by DECA to get microjoule.
> > + */
> > + if (check_mul_overflow(temp * LTC4283_TCONV_uS, energy, &temp_2)) {
> > + /*
> > + * We multiply again by 1000 to make sure that we don't get 0
> > + * in the following division which could happen for big rsense
> > + * values. OTOH, we then divide energy first by 1000 so that
> > + * we do not overflow u64 again for very small rsense values.
> > + * We add 100 factor for proper conversion to microjoule.
> > + */
> > + temp_2 = DIV64_U64_ROUND_CLOSEST(temp * LTC4283_TCONV_uS * MILLI,
> > + BIT_ULL(24) * st->rsense);
> > + energy = DIV_ROUND_CLOSEST_ULL(energy, MILLI * CENTI) * temp_2;
> > + } else {
> > + /* Put rsense back into nanoohm so we get microjoule. */
> > + energy = DIV64_U64_ROUND_CLOSEST(temp_2, BIT_ULL(24) * st->rsense * CENTI);
> > + }
> > +
> > + *val = energy;
> > + return 0;
> > +}
> > +
> > +static int ltc4283_read(struct device *dev, enum hwmon_sensor_types type,
> > + u32 attr, int channel, long *val)
> > +{
> > + struct ltc4283_hwmon *st = dev_get_drvdata(dev);
> > +
> > + switch (type) {
> > + case hwmon_in:
> > + return ltc4283_read_in(st, attr, channel, val);
> > + case hwmon_curr:
> > + return ltc4283_read_curr(st, attr, val);
> > + case hwmon_power:
> > + return ltc4283_read_power(st, attr, val);
> > + case hwmon_energy:
> > + *val = st->energy_en;
> > + return 0;
> > + case hwmon_energy64:
> > + return ltc4283_read_energy(st, attr, (s64 *)val);
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +}
> > +
> > +static int ltc4283_write_power_byte(const struct ltc4283_hwmon *st, u32 reg,
> > + long val)
> > +{
> > + u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> > + u32 __raw;
> > +
> > + clamp_val(val, 0, st->power_max);
>
> Result of clamp_val() is ignored.
>
> > + __raw = DIV64_U64_ROUND_CLOSEST(val * BIT_ULL(8) * st->rsense, temp);
> > +
> > + return regmap_write(st->map, reg, __raw);
> > +}
> > +
> > +static int ltc4283_write_power_word(const struct ltc4283_hwmon *st,
> > + u32 reg, long val)
> > +{
> > + u64 temp = st->rsense * BIT_ULL(16), temp_2;
> > + u16 __raw;
> > +
> > + if (check_mul_overflow(val, temp, &temp_2)) {
> > + temp = DIV_ROUND_CLOSEST_ULL(temp, DECA * MILLI);
> > + __raw = DIV_ROUND_CLOSEST_ULL(temp * val, LTC4283_ADC1_FS_uV *
> > LTC4283_ADC2_FS_mV);
> > + } else {
> > + temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> > + __raw = DIV64_U64_ROUND_CLOSEST(temp_2, temp);
> > + }
> > +
> > + return regmap_write(st->map, reg, __raw);
> > +}
> > +
> > +static int ltc4283_reset_power_hist(struct ltc4283_hwmon *st)
> > +{
> > + int ret;
> > +
> > + ret = ltc4283_write_power_word(st, LTC4283_POWER_MIN, st->power_max);
> > + if (ret)
> > + return ret;
> > +
> > + ret = ltc4283_write_power_word(st, LTC4283_POWER_MAX, 0);
> > + if (ret)
> > + return ret;
> > +
> > + /* Clear possible power faults. */
> > + return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> > + LTC4283_PWR_FAIL_FAULT_MASK | LTC4283_PGI_FAULT_MASK);
> > +}
> > +
> > +static int ltc4283_write_power(struct ltc4283_hwmon *st, u32 attr, long val)
> > +{
> > + switch (attr) {
> > + case hwmon_power_max:
> > + return ltc4283_write_power_byte(st, LTC4283_POWER_MAX_TH, val);
> > + case hwmon_power_min:
> > + return ltc4283_write_power_byte(st, LTC4283_POWER_MIN_TH, val);
> > + case hwmon_power_reset_history:
> > + return ltc4283_reset_power_hist(st);
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +}
> > +
> > +static int ltc4283_write_in_history(struct ltc4283_hwmon *st, u32 reg,
> > + long lowest, u32 fs)
> > +{
> > + u32 __raw;
> > + int ret;
> > +
> > + __raw = DIV_ROUND_CLOSEST(BIT(16) * lowest, fs);
> > + if (__raw == BIT(16))
> > + __raw = U16_MAX;
> > +
> > + ret = regmap_write(st->map, reg, __raw);
> > + if (ret)
> > + return ret;
> > +
> > + return regmap_write(st->map, reg + 1, 0);
> > +}
> > +
> > +static int ltc4283_write_in_byte(const struct ltc4283_hwmon *st,
> > + u32 reg, u32 fs, long val)
> > +{
> > + u32 __raw;
> > +
> > + val = clamp_val(val, 0, fs);
> > + __raw = DIV_ROUND_CLOSEST(val * BIT(8), fs);
> > + if (__raw == BIT(8))
> > + __raw = U8_MAX;
> > +
> > + return regmap_write(st->map, reg, __raw);
> > +}
> > +
> > +static int ltc4283_reset_in_hist(struct ltc4283_hwmon *st, u32 channel)
> > +{
> > + u32 reg, fs;
> > + int ret;
> > +
> > + /*
> > + * Make sure to clear possible under/over voltage faults. Otherwise the
> > + * chip won't latch on again.
> > + */
> > + if (channel == LTC4283_CHAN_VIN)
> > + return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> > + LTC4283_OV_FAULT_MASK | LTC4283_UV_FAULT_MASK);
> > +
> > + if (channel == LTC4283_CHAN_VPWR)
> > + return ltc4283_write_in_history(st, LTC4283_VPWR_MIN,
> > + LTC4283_ADC2_FS_mV,
> > + LTC4283_ADC2_FS_mV);
> > +
> > + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN) {
> > + fs = LTC4283_ADC2_FS_mV;
> > + reg = LTC4283_ADC_2_MIN(channel - LTC4283_CHAN_ADI_1);
> > + } else {
> > + fs = LTC4283_ADC1_FS_uV;
> > + reg = LTC4283_ADC_2_MIN_DIFF(channel - LTC4283_CHAN_ADIN12);
> > + }
> > +
> > + ret = ltc4283_write_in_history(st, reg, fs, fs);
> > + if (ret)
> > + return ret;
> > + if (channel != LTC4283_CHAN_DRAIN)
> > + return 0;
> > +
> > + /* Then, let's also clear possible fet faults. Same as above. */
> > + return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> > + LTC4283_FET_BAD_FAULT_MASK | LTC4283_FET_SHORT_FAULT_MASK);
> > +}
> > +
> > +static int ltc4283_write_in_en(struct ltc4283_hwmon *st, u32 channel, bool en)
> > +{
> > + unsigned int bit, adc_idx = channel - LTC4283_CHAN_ADI_1;
> > + unsigned int reg = LTC4283_ADC_SELECT(adc_idx);
> > + int ret;
> > +
> > + bit = LTC4283_ADC_SELECT_MASK(adc_idx);
> > + if (channel > LTC4283_CHAN_DRAIN)
> > + /* Account for two reserved fields after DRAIN. */
> > + bit <<= 2;
> > +
> > + if (en)
> > + ret = regmap_set_bits(st->map, reg, bit);
> > + else
> > + ret = regmap_clear_bits(st->map, reg, bit);
> > + if (ret)
> > + return ret;
> > +
> > + __assign_bit(channel, &st->ch_enable_mask, en);
> > + return 0;
> > +}
> > +
> > +static int ltc4283_write_minmax(struct ltc4283_hwmon *st, long val,
> > + u32 channel, bool is_max)
> > +{
> > + u32 reg;
> > +
> > + if (channel == LTC4283_CHAN_VPWR) {
> > + if (is_max)
> > + return ltc4283_write_in_byte(st, LTC4283_VPWR_MAX_TH,
> > + LTC4283_ADC2_FS_mV, val);
> > +
> > + return ltc4283_write_in_byte(st, LTC4283_VPWR_MIN_TH,
> > + LTC4283_ADC2_FS_mV, val);
> > + }
> > +
> > + if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN) {
> > + if (is_max) {
> > + reg = LTC4283_ADC_2_MAX_TH(channel - LTC4283_CHAN_ADI_1);
> > + return ltc4283_write_in_byte(st, reg,
> > + LTC4283_ADC2_FS_mV, val);
> > + }
> > +
> > + reg = LTC4283_ADC_2_MIN_TH(channel - LTC4283_CHAN_ADI_1);
> > + return ltc4283_write_in_byte(st, reg, LTC4283_ADC2_FS_mV, val);
> > + }
> > +
> > + if (is_max) {
> > + reg = LTC4283_ADC_2_MAX_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> > + return ltc4283_write_in_byte(st, reg, LTC4283_ADC1_FS_uV,
> > + val * MILLI);
> > + }
> > +
> > + reg = LTC4283_ADC_2_MIN_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> > + return ltc4283_write_in_byte(st, reg, LTC4283_ADC1_FS_uV, val * MILLI);
> > +}
> > +
> > +static int ltc4283_write_in(struct ltc4283_hwmon *st, u32 attr, long val,
> > + int channel)
> > +{
> > + switch (attr) {
> > + case hwmon_in_max:
> > + return ltc4283_write_minmax(st, val, channel, true);
> > + case hwmon_in_min:
> > + return ltc4283_write_minmax(st, val, channel, false);
> > + case hwmon_in_reset_history:
> > + return ltc4283_reset_in_hist(st, channel);
> > + case hwmon_in_enable:
> > + return ltc4283_write_in_en(st, channel, !!val);
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +}
> > +
> > +static int ltc4283_write_curr_byte(const struct ltc4283_hwmon *st,
> > + u32 reg, long val)
> > +{
> > + u32 temp = LTC4283_ADC1_FS_uV * DECA * MILLI;
> > + u32 reg_val, isense_max;
> > +
> > + isense_max = DIV_ROUND_CLOSEST(st->vsense_max * MICRO * DECA, st->rsense);
> > + clamp_val(val, 0, isense_max);
>
> Result ignored.
Ups!
>
> > + reg_val = DIV_ROUND_CLOSEST_ULL(val * BIT_ULL(8) * st->rsense, temp);
> > +
> > + return regmap_write(st->map, reg, reg_val);
> > +}
> > +
> > +static int ltc4283_write_curr_history(struct ltc4283_hwmon *st)
> > +{
> > + int ret;
> > +
> > + ret = ltc4283_write_in_history(st, LTC4283_SENSE_MIN,
> > + st->vsense_max * MILLI,
> > + LTC4283_ADC1_FS_uV);
> > + if (ret)
> > + return ret;
> > +
> > + /* Now, let's also clear possible overcurrent logs. */
> > + return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> > + LTC4283_OC_FAULT_MASK);
> > +}
> > +
> > +static int ltc4283_write_curr(struct ltc4283_hwmon *st, u32 attr, long val)
> > +{
> > + switch (attr) {
> > + case hwmon_curr_max:
> > + return ltc4283_write_curr_byte(st, LTC4283_SENSE_MAX_TH, val);
> > + case hwmon_curr_min:
> > + return ltc4283_write_curr_byte(st, LTC4283_SENSE_MIN_TH, val);
> > + case hwmon_curr_reset_history:
> > + return ltc4283_write_curr_history(st);
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +}
> > +
> > +static int ltc4283_energy_enable_set(struct ltc4283_hwmon *st, long val)
> > +{
> > + int ret;
> > +
> > + /* Setting the bit halts the meter. */
> > + val = !!val;
> > + ret = regmap_update_bits(st->map, LTC4283_METER_CONTROL,
> > + LTC4283_METER_HALT_MASK,
> > + FIELD_PREP(LTC4283_METER_HALT_MASK, !val));
> > + if (ret)
> > + return ret;
> > +
> > + st->energy_en = val;
> > +
> > + return 0;
> > +}
> > +
> > +static int ltc4283_write(struct device *dev, enum hwmon_sensor_types type,
> > + u32 attr, int channel, long val)
> > +{
> > + struct ltc4283_hwmon *st = dev_get_drvdata(dev);
> > +
> > + switch (type) {
> > + case hwmon_power:
> > + return ltc4283_write_power(st, attr, val);
> > + case hwmon_in:
> > + return ltc4283_write_in(st, attr, val, channel);
> > + case hwmon_curr:
> > + return ltc4283_write_curr(st, attr, val);
> > + case hwmon_energy:
> > + return ltc4283_energy_enable_set(st, val);
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +}
> > +
> > +static umode_t ltc4283_in_is_visible(const struct ltc4283_hwmon *st,
> > + u32 attr, int channel)
> > +{
> > + /* If ADIO is set as a GPIO, don´t make it visible. */
> > + if (channel >= LTC4283_CHAN_ADIO_1 && channel <= LTC4283_CHAN_ADIO_4) {
> > + /* ADIOX pins come at index 0 in the gpio mask. */
> > + channel -= LTC4283_CHAN_ADIO_1;
> > + if (test_bit(channel, &st->gpio_mask))
> > + return 0;
> > + }
> > +
> > + /* Also take care of differential channels. */
> > + if (channel >= LTC4283_CHAN_ADIO12 && channel <= LTC4283_CHAN_ADIO34) {
> > + channel -= LTC4283_CHAN_ADIO12;
> > + /* If one channel in the pair is used, make it invisible. */
> > + if (test_bit(channel * 2, &st->gpio_mask) ||
> > + test_bit(channel * 2 + 1, &st->gpio_mask))
> > + return 0;
> > + }
> > +
> > + switch (attr) {
> > + case hwmon_in_input:
> > + case hwmon_in_highest:
> > + case hwmon_in_lowest:
> > + case hwmon_in_max_alarm:
> > + case hwmon_in_min_alarm:
> > + case hwmon_in_label:
> > + case hwmon_in_lcrit_alarm:
> > + case hwmon_in_crit_alarm:
> > + case hwmon_in_fault:
> > + return 0444;
> > + case hwmon_in_max:
> > + case hwmon_in_min:
> > + case hwmon_in_enable:
> > + return 0644;
> > + case hwmon_in_reset_history:
> > + return 0200;
> > + default:
> > + return 0;
> > + }
> > +}
> > +
> > +static umode_t ltc4283_curr_is_visible(u32 attr)
> > +{
> > + switch (attr) {
> > + case hwmon_curr_input:
> > + case hwmon_curr_highest:
> > + case hwmon_curr_lowest:
> > + case hwmon_curr_max_alarm:
> > + case hwmon_curr_min_alarm:
> > + case hwmon_curr_crit_alarm:
> > + case hwmon_curr_label:
> > + return 0444;
> > + case hwmon_curr_max:
> > + case hwmon_curr_min:
> > + return 0644;
> > + case hwmon_curr_reset_history:
> > + return 0200;
> > + default:
> > + return 0;
> > + }
> > +}
> > +
> > +static umode_t ltc4283_power_is_visible(u32 attr)
> > +{
> > + switch (attr) {
> > + case hwmon_power_input:
> > + case hwmon_power_input_highest:
> > + case hwmon_power_input_lowest:
> > + case hwmon_power_label:
> > + case hwmon_power_max_alarm:
> > + case hwmon_power_min_alarm:
> > + return 0444;
> > + case hwmon_power_max:
> > + case hwmon_power_min:
> > + return 0644;
> > + case hwmon_power_reset_history:
> > + return 0200;
> > + default:
> > + return 0;
> > + }
> > +}
> > +
> > +static umode_t ltc4283_is_visible(const void *data,
> > + enum hwmon_sensor_types type,
> > + u32 attr, int channel)
> > +{
> > + switch (type) {
> > + case hwmon_in:
> > + return ltc4283_in_is_visible(data, attr, channel);
> > + case hwmon_curr:
> > + return ltc4283_curr_is_visible(attr);
> > + case hwmon_power:
> > + return ltc4283_power_is_visible(attr);
> > + case hwmon_energy:
> > + /* hwmon_energy_enable */
> > + return 0644;
> > + case hwmon_energy64:
> > + /* hwmon_energy_input */
> > + return 0444;
> > + default:
> > + return 0;
> > + }
> > +}
> > +
> > +static const char * const ltc4283_in_strs[] = {
> > + "VIN", "VPWR", "VADI1", "VADI2", "VADI3", "VADI4", "VADIO1", "VADIO2",
> > + "VADIO3", "VADIO4", "DRNS", "DRAIN", "ADIN2-ADIN1", "ADIN4-ADIN3",
> > + "ADIO2-ADIO1", "ADIO4-ADIO3"
> > +};
> > +
> > +static int ltc4283_read_labels(struct device *dev,
> > + enum hwmon_sensor_types type,
> > + u32 attr, int channel, const char **str)
> > +{
> > + switch (type) {
> > + case hwmon_in:
> > + *str = ltc4283_in_strs[channel];
> > + return 0;
> > + case hwmon_curr:
> > + *str = "ISENSE";
> > + return 0;
> > + case hwmon_power:
> > + *str = "Power";
> > + return 0;
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +}
> > +
> > +/*
> > + * Set max limits for ISENSE and Power as that depends on the max voltage on
> > + * rsense that is defined in ILIM_ADJUST. This is specially important for power
> > + * because for some rsense and vfsout values, if we allow the default raw 255
> > + * value, that would overflow long in 32bit archs when reading back the max
> > + * power limit.
> > + */
> > +static int ltc4283_set_max_limits(struct ltc4283_hwmon *st, struct device *dev)
> > +{
> > + u32 temp = st->vsense_max * DECA * MICRO;
> > + int ret;
> > +
> > + ret = ltc4283_write_in_byte(st, LTC4283_SENSE_MAX_TH, LTC4283_ADC1_FS_uV,
> > + st->vsense_max * MILLI);
> > + if (ret)
> > + return ret;
> > +
> > + /* Power is given by ISENSE * Vout. */
> > + st->power_max = DIV_ROUND_CLOSEST(temp, st->rsense) * LTC4283_ADC2_FS_mV;
> > + return ltc4283_write_power_byte(st, LTC4283_POWER_MAX_TH, st->power_max);
> > +}
> > +
> > +static int ltc4283_parse_array_prop(const struct ltc4283_hwmon *st,
> > + struct device *dev, const char *prop,
> > + const u32 *vals, u32 n_vals)
> > +{
> > + u32 prop_val;
> > + int ret;
> > + u32 i;
> > +
> > + ret = device_property_read_u32(dev, prop, &prop_val);
> > + if (ret)
> > + return n_vals;
> > +
> > + for (i = 0; i < n_vals; i++) {
> > + if (prop_val != vals[i])
> > + continue;
> > +
> > + return i;
> > + }
> > +
> > + return dev_err_probe(dev, -EINVAL,
> > + "Invalid %s property value %u, expected one of: %*ph\n",
> > + prop, prop_val, n_vals, vals);
> > +}
> > +
> > +static int ltc4283_get_defaults(struct ltc4283_hwmon *st)
> > +{
> > + u32 reg_val, ilm_adjust, c;
> > + int ret;
> > +
> > + ret = regmap_read(st->map, LTC4283_METER_CONTROL, ®_val);
> > + if (ret)
> > + return ret;
> > +
> > + st->energy_en = !FIELD_GET(LTC4283_METER_HALT_MASK, reg_val);
> > +
> > + ret = regmap_read(st->map, LTC4283_CONFIG_1, ®_val);
> > + if (ret)
> > + return ret;
> > +
> > + ilm_adjust = FIELD_GET(LTC4283_ILIM_MASK, reg_val);
> > + st->vsense_max = LTC4283_VILIM_MIN_uV / MILLI + ilm_adjust;
> > +
> > + /* VPWR and VIN are always enabled */
> > + __set_bit(LTC4283_CHAN_VIN, &st->ch_enable_mask);
> > + __set_bit(LTC4283_CHAN_VPWR, &st->ch_enable_mask);
> > + for (c = LTC4283_CHAN_ADI_1; c < LTC4283_CHAN_MAX; c++) {
> > + u32 chan = c - LTC4283_CHAN_ADI_1, bit;
> > +
> > + ret = regmap_read(st->map, LTC4283_ADC_SELECT(chan), ®_val);
> > + if (ret)
> > + return ret;
> > +
> > + bit = LTC4283_ADC_SELECT_MASK(chan);
> > + if (c > LTC4283_CHAN_DRAIN)
> > + /* account for two reserved fields after DRAIN */
> > + bit <<= 2;
> > +
> > + if (!(bit & reg_val))
> > + continue;
> > +
> > + __set_bit(c, &st->ch_enable_mask);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static const char * const ltc4283_pgio1_funcs[] = {
> > + "inverted_power_good", "power_good", "gpio"
> > +};
> > +
> > +static const char * const ltc4283_pgio2_funcs[] = {
> > + "inverted_power_good", "power_good", "gpio", "active_current_limiting"
> > +};
> > +
> > +static const char * const ltc4283_pgio3_funcs[] = {
> > + "inverted_power_good_input", "power_good_input", "gpio"
> > +};
> > +
> > +static const char * const ltc4283_pgio4_funcs[] = {
> > + "inverted_external_fault", "external_fault", "gpio"
> > +};
> > +
> > +enum {
> > + LTC4283_PIN_ADIO1,
> > + LTC4283_PIN_ADIO2,
> > + LTC4283_PIN_ADIO3,
> > + LTC4283_PIN_ADIO4,
> > + LTC4283_PIN_PGIO1,
> > + LTC4283_PIN_PGIO2,
> > + LTC4283_PIN_PGIO3,
> > + LTC4283_PIN_PGIO4,
> > +};
> > +
> > +static int ltc4283_pgio_config(struct ltc4283_hwmon *st, struct device *dev)
> > +{
> > + int ret, func;
> > +
> > + func = device_property_match_property_string(dev, "adi,pgio1-func",
> > + ltc4283_pgio1_funcs,
> > + ARRAY_SIZE(ltc4283_pgio1_funcs));
> > + if (func < 0 && func != -EINVAL)
>
> != or == ?
>
> for all -EINVAL checks. At the very least,
>
> > + return dev_err_probe(dev, func,
> > + "Invalid adi,pgio1-func property\n");
>
> The "Invalid" messages are confusing. Why say it is invalid when it isn't ?
> And why _only_ accept -EINVAL errors ? That seems odd.
It's a bit weird but -EINVAL is what is used by FW for missing properties. So if the property
is not given at all, we ignore. But we do wanna catch other error codes. Tiny improvement but I'm
aware that it's also a very common pattern to just ignore any error for optional properties so I
can, also, just do that.
>
> > + if (func >= 0) {
> > + if (func == LTC4283_PGIO_FUNC_GPIO) {
> > + __set_bit(LTC4283_PIN_PGIO1, &st->gpio_mask);
> > + /* If GPIO, default to an input pin. */
> > + func++;
> > + }
> > +
> > + ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> > + LTC4283_PGIO1_CFG_MASK,
> > + FIELD_PREP(LTC4283_PGIO1_CFG_MASK, func));
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + func = device_property_match_property_string(dev, "adi,pgio2-func",
> > + ltc4283_pgio2_funcs,
> > + ARRAY_SIZE(ltc4283_pgio2_funcs));
> > +
> > + if (func < 0 && func != -EINVAL)
> > + return dev_err_probe(dev, func,
> > + "Invalid adi,pgio2-func property\n");
> > + if (func >= 0) {
> > + if (func != LTC4283_PGIO2_FUNC_ACLB) {
> > + if (func == LTC4283_PGIO_FUNC_GPIO) {
> > + __set_bit(LTC4283_PIN_PGIO2, &st->gpio_mask);
> > + func++;
> > + }
> > +
> > + ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> > + LTC4283_PGIO2_CFG_MASK,
> > + FIELD_PREP(LTC4283_PGIO2_CFG_MASK, func));
> > + } else {
> > + ret = regmap_set_bits(st->map, LTC4283_CONTROL_1,
> > + LTC4283_PIGIO2_ACLB_MASK);
> > + }
> > +
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + func = device_property_match_property_string(dev, "adi,pgio3-func",
> > + ltc4283_pgio3_funcs,
> > + ARRAY_SIZE(ltc4283_pgio3_funcs));
> > +
> > + if (func < 0 && func != -EINVAL)
> > + return dev_err_probe(dev, func,
> > + "Invalid adi,pgio3-func property\n");
> > + if (func >= 0) {
> > + if (func == LTC4283_PGIO_FUNC_GPIO) {
> > + __set_bit(LTC4283_PIN_PGIO3, &st->gpio_mask);
> > + func++;
> > + }
> > +
> > + ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> > + LTC4283_PGIO3_CFG_MASK,
> > + FIELD_PREP(LTC4283_PGIO3_CFG_MASK, func));
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + func = device_property_match_property_string(dev, "adi,pgio4-func",
> > + ltc4283_pgio4_funcs,
> > + ARRAY_SIZE(ltc4283_pgio4_funcs));
> > +
> > + if (func < 0 && func != -EINVAL)
>
> The usual -EINVAL question.
>
> > + return dev_err_probe(dev, func,
> > + "Invalid adi,pgio4-func property\n");
> > + if (func >= 0) {
> > + if (func == LTC4283_PGIO_FUNC_GPIO) {
> > + __set_bit(LTC4283_PIN_PGIO4, &st->gpio_mask);
> > + func++;
> > + } else {
> > + st->ext_fault = true;
> > + }
> > +
> > + ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> > + LTC4283_PGIO4_CFG_MASK,
> > + FIELD_PREP(LTC4283_PGIO4_CFG_MASK, func));
> > + if (ret)
> > + return ret;
> > + }
>
> Shouldn't this set the default value (inverted_external_fault) if the
> property is not provided (i.e., set st->ext_fault and write func = 0
> into LTC4283_PGIO4_CFG_MASK) ?
I guess this would be a wider question about what to do for all properties not provided? Maybe
(given there's an EEPROM) we should just assume whatever are in the registers is to be used? (When
checking your comments, I started to wonder about this for boolean properties).
But naturally, at the very least, I need to include st->ext_fault in ltc4283_get_defaults().
>
> > +
> > + return 0;
> > +}
> > +
> > +static int ltc4283_adio_config(struct ltc4283_hwmon *st, struct device *dev,
> > + const char *prop, u32 pin)
> > +{
> > + u32 adc_idx;
> > + int ret;
> > +
> > + if (!device_property_read_bool(dev, prop))
> > + return 0;
> > +
> > + adc_idx = LTC4283_CHAN_ADIO_1 - LTC4283_CHAN_ADI_1 + pin;
> > + ret = regmap_clear_bits(st->map, LTC4283_ADC_SELECT(adc_idx),
> > + LTC4283_ADC_SELECT_MASK(adc_idx));
> > + if (ret)
> > + return ret;
> > +
> > + __set_bit(pin, &st->gpio_mask);
> > + return 0;
> > +}
> > +
> > +static int ltc4283_pin_config(struct ltc4283_hwmon *st, struct device *dev)
> > +{
> > + int ret;
> > +
> > + ret = ltc4283_pgio_config(st, dev);
> > + if (ret)
> > + return ret;
> > +
> > + ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio1", LTC4283_PIN_ADIO1);
> > + if (ret)
> > + return ret;
> > +
> > + ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio2", LTC4283_PIN_ADIO2);
> > + if (ret)
> > + return ret;
> > +
> > + ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio3", LTC4283_PIN_ADIO3);
> > + if (ret)
> > + return ret;
> > +
> > + return ltc4283_adio_config(st, dev, "adi,gpio-on-adio4", LTC4283_PIN_ADIO4);
> > +}
> > +
> > +static const char * const ltc4283_oc_fet_retry[] = {
> > + "latch-off", "1", "7", "unlimited"
> > +};
> > +
> > +static const u32 ltc4283_fb_factor[] = {
> > + 100, 50, 20, 10
> > +};
> > +
> > +static const u32 ltc4283_cooling_dl[] = {
> > + 512, 1002, 2005, 4100, 8190, 16400, 32800, 65600
> > +};
> > +
> > +static const u32 ltc4283_fet_bad_delay[] = {
> > + 256, 512, 1002, 2005
> > +};
> > +
> > +static int ltc4283_setup(struct ltc4283_hwmon *st, struct device *dev)
> > +{
> > + u32 val, chan;
> > + int ret;
> > +
> > + /* The part has an eeprom so let's get the needed defaults from it */
> > + ret = ltc4283_get_defaults(st);
> > + if (ret)
> > + return ret;
> > +
> > + /*
> > + * Default to 1 micro ohm so we can probe without FW properties. Note
> > + * the below division expects rsense in nano ohms.
> > + */
> > + st->rsense = 1 * MILLI;
> > + ret = device_property_read_u32(dev, "adi,rsense-nano-ohms",
> > + &st->rsense);
> > + if (!ret) {
> > + if (st->rsense < CENTI)
> > + return dev_err_probe(dev, -EINVAL,
> > + "adi,rsense-nano-ohms too small (< %lu)\n",
> > + CENTI);
> > + }
> > +
> > + /*
> > + * The resolution for rsense is tenths of micro (eg: 62.5 uOhm) which
> > + * means we need nano in the bindings. However, to make things easier to
> > + * handle (with respect to overflows) we divide it by 100 as we don't
> > + * really need the last two digits.
> > + */
> > + st->rsense /= CENTI;
> > +
> > + ret = device_property_read_u32(dev, "adi,current-limit-sense-microvolt",
> > + &st->vsense_max);
> > + if (!ret) {
> > + u32 reg_val;
> > +
> > + if (!in_range(st->vsense_max, LTC4283_VILIM_MIN_uV,
> > + LTC4283_VILIM_RANGE)) {
> > + return dev_err_probe(dev, -EINVAL,
> > + "adi,current-limit-sense-microvolt (%u) out of
> > range [%u %u]\n",
> > + st->vsense_max, LTC4283_VILIM_MIN_uV,
> > + LTC4283_VILIM_MAX_uV);
> > + }
> > +
> > + st->vsense_max /= MILLI;
> > + reg_val = FIELD_PREP(LTC4283_ILIM_MASK,
> > + st->vsense_max - LTC4283_VILIM_MIN_uV / MILLI);
> > + ret = regmap_update_bits(st->map, LTC4283_CONFIG_1,
> > + LTC4283_ILIM_MASK, reg_val);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + ret = ltc4283_parse_array_prop(st, dev, "adi,current-limit-foldback-factor",
> > + ltc4283_fb_factor, ARRAY_SIZE(ltc4283_fb_factor));
> > + if (ret < 0)
> > + return ret;
> > + if (ret < ARRAY_SIZE(ltc4283_fb_factor)) {
> > + ret = regmap_update_bits(st->map, LTC4283_CONFIG_1, LTC4283_FB_MASK,
> > + FIELD_PREP(LTC4283_FB_MASK, ret));
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + ret = ltc4283_parse_array_prop(st, dev, "adi,cooling-delay-ms",
> > + ltc4283_cooling_dl, ARRAY_SIZE(ltc4283_cooling_dl));
> > + if (ret < 0)
> > + return ret;
> > + if (ret < ARRAY_SIZE(ltc4283_cooling_dl)) {
> > + ret = regmap_update_bits(st->map, LTC4283_CONFIG_2, LTC4283_COOLING_DL_MASK,
> > + FIELD_PREP(LTC4283_COOLING_DL_MASK, ret));
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + ret = ltc4283_parse_array_prop(st, dev, "adi,fet-bad-timer-delay-ms",
> > + ltc4283_fet_bad_delay,
> > ARRAY_SIZE(ltc4283_fet_bad_delay));
> > + if (ret < 0)
> > + return ret;
>
> This (and other attributes if missing) have a default and should not blindly
> return an error.
Maybe I'm missing your point but that helper will return ARRAY_SIZE(ltc4283_fet_bad_delay) - same
for other properties using the helper - in case the device_property_read_u32() returns an error.
That is why in here we explicitly check for negative error code.
>
> > + if (ret < ARRAY_SIZE(ltc4283_fet_bad_delay)) {
> > + ret = regmap_update_bits(st->map, LTC4283_CONFIG_2, LTC4283_FTBD_DL_MASK,
> > + FIELD_PREP(LTC4283_FTBD_DL_MASK, ret));
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + ret = ltc4283_set_max_limits(st, dev);
> > + if (ret)
> > + return ret;
> > +
> > + ret = ltc4283_pin_config(st, dev);
> > + if (ret)
> > + return ret;
> > +
> > + if (device_property_read_bool(dev, "adi,power-good-reset-on-fet")) {
> > + ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> > + LTC4283_PWRGD_RST_CTRL_MASK);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + if (device_property_read_bool(dev, "adi,fet-turn-off-disable")) {
> > + ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> > + LTC4283_FET_BAD_OFF_MASK);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + if (device_property_read_bool(dev, "adi,tmr-pull-down-disable")) {
> > + ret = regmap_set_bits(st->map, LTC4283_CONTROL_1,
> > + LTC4283_THERM_TMR_MASK);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + if (device_property_read_bool(dev, "adi,dvdt-inrush-control-disable")) {
> > + ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> > + LTC4283_DVDT_MASK);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + if (device_property_read_bool(dev, "adi,undervoltage-retry-disable")) {
> > + ret = regmap_clear_bits(st->map, LTC4283_CONTROL_2,
> > + LTC4283_UV_RETRY_MASK);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + if (device_property_read_bool(dev, "adi,overvoltage-retry-disable")) {
> > + ret = regmap_clear_bits(st->map, LTC4283_CONTROL_2,
> > + LTC4283_OV_RETRY_MASK);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + if (device_property_read_bool(dev, "adi,external-fault-retry-enable")) {
> > + if (!st->ext_fault)
> > + return dev_err_probe(dev, -EINVAL,
> > + "adi,external-fault-retry-enable set but PGIO4 not
> > configured\n");
> > + ret = regmap_set_bits(st->map, LTC4283_CONTROL_2,
> > + LTC4283_EXT_FAULT_RETRY_MASK);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + if (device_property_read_bool(dev, "adi,fault-log-enable")) {
> > + ret = regmap_set_bits(st->map, LTC4283_FAULT_LOG_CTRL,
> > + LTC4283_FAULT_LOG_EN_MASK);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + ret = device_property_match_property_string(dev, "adi,overcurrent-retries",
> > + ltc4283_oc_fet_retry,
> > + ARRAY_SIZE(ltc4283_oc_fet_retry));
> > + /* We still want to catch when an invalid string is given. */
> > + if (ret != -EINVAL)
>
> Is that supposed to be "== -EINVAL" ?
>
> Because otherwise it returns immediately (even if there is no error)
> all the time.
Sure! Did not hit this because I do have these properties in my DT. Missing a ret < 0 before
>
> > + return dev_err_probe(dev, ret,
> > + "adi,overcurrent-retries invalid value\n");
> > + if (ret >= 0) {
> > + ret = regmap_update_bits(st->map, LTC4283_CONTROL_2,
> > + LTC4283_OC_RETRY_MASK,
> > + FIELD_PREP(LTC4283_OC_RETRY_MASK, ret));
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + ret = device_property_match_property_string(dev, "adi,fet-bad-retries",
> > + ltc4283_oc_fet_retry,
> > + ARRAY_SIZE(ltc4283_oc_fet_retry));
> > + if (ret != -EINVAL)
>
> Same here.
>
> > + return dev_err_probe(dev, ret,
> > + "adi,fet-bad-retries invalid value\n");
> > + if (ret >= 0) {
> > + ret = regmap_update_bits(st->map, LTC4283_CONTROL_2,
> > + LTC4283_FET_BAD_RETRY_MASK,
> > + FIELD_PREP(LTC4283_FET_BAD_RETRY_MASK, ret));
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + if (device_property_read_bool(dev, "adi,external-fault-fet-off-enable")) {
> > + if (!st->ext_fault)
> > + return dev_err_probe(dev, -EINVAL,
> > + "adi,external-fault-fet-off-enable set but PGIO4
> > not configured\n");
> > + ret = regmap_set_bits(st->map, LTC4283_CONFIG_3,
> > + LTC4283_EXTFLT_TURN_OFF_MASK);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + if (device_property_read_bool(dev, "adi,vpower-drns-enable")) {
> > + __clear_bit(LTC4283_CHAN_DRAIN, &st->ch_enable_mask);
>
> That kind of conflicts with the devicetree description, which says that the
> _drain_ voltage should be monitored if this property is enabled.
>
Hmm I messed up here during revisions. Before I was only dealing with the adi,vpower-drns-enable
true case and when I changed to also deal with the lack of the flag, I clearly messed up.
Anyways, main idea is to remove the DRNS (attenuated drain voltage) from ADC2 if we already have
it in VPWR.
- Nuno Sá
> > + chan = LTC4283_CHAN_DRAIN - LTC4283_CHAN_ADI_1;
> > + val = 1;
> > + } else {
> > + __clear_bit(LTC4283_CHAN_DRNS, &st->ch_enable_mask);
> > + chan = LTC4283_CHAN_DRNS - LTC4283_CHAN_ADI_1;
> > + val = 0;
> > + }
> > + /*
> > + * Then, let's by default disable the channel from the ADC2 that is
> > + * already being monitored by the VPWR channel. One can still enable it
> > + * later on if needed.
> > + */
> > + ret = regmap_clear_bits(st->map, LTC4283_ADC_SELECT(chan),
> > + LTC4283_ADC_SELECT_MASK(chan));
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_update_bits(st->map, LTC4283_CONFIG_3,
> > + LTC4283_VPWR_DRNS_MASK,
> > + FIELD_PREP(LTC4283_VPWR_DRNS_MASK, val));
> > + if (ret)
> > + return ret;
> > +
> > + /* Make sure the ADC has 12bit resolution since we're assuming that. */
> > + ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG_2,
> > + LTC4283_ADC_MASK,
> > + FIELD_PREP(LTC4283_ADC_MASK, 3));
> > + if (ret)
> > + return ret;
> > +
> > + /* Energy reads (which are 6 byte block reads) rely on page access */
> > + ret = regmap_set_bits(st->map, LTC4283_CONTROL_1, LTC4283_RW_PAGE_MASK);
> > + if (ret)
> > + return ret;
> > +
> > + /*
> > + * Make sure we are integrating power as we only support reporting
> > + * consumed energy.
> > + */
> > + return regmap_clear_bits(st->map, LTC4283_METER_CONTROL,
> > + LTC4283_INTEGRATE_I_MASK);
> > +}
> > +
> > +static const struct hwmon_channel_info * const ltc4283_info[] = {
> > + HWMON_CHANNEL_INFO(in,
> > + HWMON_I_LCRIT_ALARM | HWMON_I_CRIT_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_MAX_ALARM | HWMON_I_RESET_HISTORY |
> > + HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_FAULT | HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL,
> > + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> > + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> > + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> > + HWMON_I_ENABLE | HWMON_I_LABEL),
> > + HWMON_CHANNEL_INFO(curr,
> > + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST |
> > + HWMON_C_MAX | HWMON_C_MIN | HWMON_C_MIN_ALARM |
> > + HWMON_C_MAX_ALARM | HWMON_C_CRIT_ALARM |
> > + HWMON_C_RESET_HISTORY | HWMON_C_LABEL),
> > + HWMON_CHANNEL_INFO(power,
> > + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST |
> > + HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN |
> > + HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM |
> > + HWMON_P_RESET_HISTORY | HWMON_P_LABEL),
> > + HWMON_CHANNEL_INFO(energy,
> > + HWMON_E_ENABLE),
> > + HWMON_CHANNEL_INFO(energy64,
> > + HWMON_E_INPUT),
> > + NULL
> > +};
> > +
> > +static const struct hwmon_ops ltc4283_ops = {
> > + .read = ltc4283_read,
> > + .write = ltc4283_write,
> > + .is_visible = ltc4283_is_visible,
> > + .read_string = ltc4283_read_labels,
> > +};
> > +
> > +static const struct hwmon_chip_info ltc4283_chip_info = {
> > + .ops = <c4283_ops,
> > + .info = ltc4283_info,
> > +};
> > +
> > +static int ltc4283_show_fault_log(void *arg, u64 *val, u32 mask)
> > +{
> > + struct ltc4283_hwmon *st = arg;
> > + long alarm;
> > + int ret;
> > +
> > + ret = ltc4283_read_alarm(st, LTC4283_FAULT_LOG, mask, &alarm);
> > + if (ret)
> > + return ret;
> > +
> > + *val = alarm;
> > +
> > + return 0;
> > +}
> > +
> > +static int ltc4283_show_in0_lcrit_fault_log(void *arg, u64 *val)
> > +{
> > + return ltc4283_show_fault_log(arg, val, LTC4283_UV_FAULT_MASK);
> > +}
> > +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_in0_lcrit_fault_log,
> > + ltc4283_show_in0_lcrit_fault_log, NULL, "%llu\n");
> > +
> > +static int ltc4283_show_in0_crit_fault_log(void *arg, u64 *val)
> > +{
> > + return ltc4283_show_fault_log(arg, val, LTC4283_OV_FAULT_MASK);
> > +}
> > +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_in0_crit_fault_log,
> > + ltc4283_show_in0_crit_fault_log, NULL, "%llu\n");
> > +
> > +static int ltc4283_show_fet_bad_fault_log(void *arg, u64 *val)
> > +{
> > + return ltc4283_show_fault_log(arg, val, LTC4283_FET_BAD_FAULT_MASK);
> > +}
> > +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_fet_bad_fault_log,
> > + ltc4283_show_fet_bad_fault_log, NULL, "%llu\n");
> > +
> > +static int ltc4283_show_fet_short_fault_log(void *arg, u64 *val)
> > +{
> > + return ltc4283_show_fault_log(arg, val, LTC4283_FET_SHORT_FAULT_MASK);
> > +}
> > +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_fet_short_fault_log,
> > + ltc4283_show_fet_short_fault_log, NULL, "%llu\n");
> > +
> > +static int ltc4283_show_curr1_crit_fault_log(void *arg, u64 *val)
> > +{
> > + return ltc4283_show_fault_log(arg, val, LTC4283_OC_FAULT_MASK);
> > +}
> > +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_curr1_crit_fault_log,
> > + ltc4283_show_curr1_crit_fault_log, NULL, "%llu\n");
> > +
> > +static int ltc4283_show_power1_failed_fault_log(void *arg, u64 *val)
> > +{
> > + return ltc4283_show_fault_log(arg, val, LTC4283_PWR_FAIL_FAULT_MASK);
> > +}
> > +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_power1_failed_fault_log,
> > + ltc4283_show_power1_failed_fault_log, NULL, "%llu\n");
> > +
> > +static int ltc4283_show_power1_good_input_fault_log(void *arg, u64 *val)
> > +{
> > + return ltc4283_show_fault_log(arg, val, LTC4283_PGI_FAULT_MASK);
> > +}
> > +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_power1_good_input_fault_log,
> > + ltc4283_show_power1_good_input_fault_log, NULL, "%llu\n");
> > +
> > +static void ltc4283_debugfs_init(struct ltc4283_hwmon *st, struct i2c_client *i2c)
> > +{
> > + debugfs_create_file_unsafe("in0_crit_fault_log", 0400, i2c->debugfs, st,
> > + <c4283_in0_crit_fault_log);
> > + debugfs_create_file_unsafe("in0_lcrit_fault_log", 0400, i2c->debugfs, st,
> > + <c4283_in0_lcrit_fault_log);
> > + debugfs_create_file_unsafe("in0_fet_bad_fault_log", 0400, i2c->debugfs, st,
> > + <c4283_fet_bad_fault_log);
> > + debugfs_create_file_unsafe("in0_fet_short_fault_log", 0400, i2c->debugfs, st,
> > + <c4283_fet_short_fault_log);
> > + debugfs_create_file_unsafe("curr1_crit_fault_log", 0400, i2c->debugfs, st,
> > + <c4283_curr1_crit_fault_log);
> > + debugfs_create_file_unsafe("power1_failed_fault_log", 0400, i2c->debugfs, st,
> > + <c4283_power1_failed_fault_log);
> > + debugfs_create_file_unsafe("power1_good_input_fault_log", 0400, i2c->debugfs,
> > + st, <c4283_power1_good_input_fault_log);
> > +}
> > +
> > +static bool ltc4283_is_word_reg(unsigned int reg)
> > +{
> > + return reg >= LTC4283_SENSE && reg <= LTC4283_ADIO34_MAX;
> > +}
> > +
> > +static int ltc4283_reg_read(void *context, unsigned int reg, unsigned int *val)
> > +{
> > + struct i2c_client *client = context;
> > + int ret;
> > +
> > + if (ltc4283_is_word_reg(reg))
> > + ret = i2c_smbus_read_word_swapped(client, reg);
> > + else
> > + ret = i2c_smbus_read_byte_data(client, reg);
> > +
> > + if (ret < 0)
> > + return ret;
> > +
> > + *val = ret;
> > + return 0;
> > +}
> > +
> > +static int ltc4283_reg_write(void *context, unsigned int reg, unsigned int val)
> > +{
> > + struct i2c_client *client = context;
> > +
> > + if (ltc4283_is_word_reg(reg))
> > + return i2c_smbus_write_word_swapped(client, reg, val);
> > +
> > + return i2c_smbus_write_byte_data(client, reg, val);
> > +}
> > +
> > +static const struct regmap_bus ltc4283_regmap_bus = {
> > + .reg_read = ltc4283_reg_read,
> > + .reg_write = ltc4283_reg_write,
> > +};
> > +
> > +static bool ltc4283_writable_reg(struct device *dev, unsigned int reg)
> > +{
> > + switch (reg) {
> > + case LTC4283_SYSTEM_STATUS ... LTC4283_FAULT_STATUS:
> > + return false;
> > + case LTC4283_RESERVED_OC:
> > + return false;
> > + case LTC4283_RESERVED_86 ... LTC4283_RESERVED_8F:
> > + return false;
> > + case LTC4283_RESERVED_91 ... LTC4283_RESERVED_A1:
> > + return false;
> > + case LTC4283_RESERVED_A3:
> > + return false;
> > + case LTC4283_RESERVED_AC:
> > + return false;
> > + case LTC4283_POWER_PLAY_MSB ... LTC4283_POWER_PLAY_LSB:
> > + return false;
> > + case LTC4283_RESERVED_F1 ... LTC4283_RESERVED_FF:
> > + return false;
> > + default:
> > + return true;
> > + }
> > +}
> > +
> > +static const struct regmap_config ltc4283_regmap_config = {
> > + .reg_bits = 8,
> > + .val_bits = 16,
> > + .max_register = 0xFF,
> > + .writeable_reg = ltc4283_writable_reg,
> > +};
> > +
> > +static int ltc4283_probe(struct i2c_client *client)
> > +{
> > + struct device *dev = &client->dev, *hwmon;
> > + struct auxiliary_device *adev;
> > + struct ltc4283_hwmon *st;
> > + int ret;
> > +
> > + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
> > + if (!st)
> > + return -ENOMEM;
> > +
> > + if (!i2c_check_functionality(client->adapter,
> > + I2C_FUNC_SMBUS_BYTE_DATA |
> > + I2C_FUNC_SMBUS_WORD_DATA |
> > + I2C_FUNC_SMBUS_READ_I2C_BLOCK))
> > + return -EOPNOTSUPP;
> > +
> > + st->client = client;
> > + st->map = devm_regmap_init(dev, <c4283_regmap_bus, client,
> > + <c4283_regmap_config);
> > + if (IS_ERR(st->map))
> > + return dev_err_probe(dev, PTR_ERR(st->map),
> > + "Failed to create regmap\n");
> > +
> > + ret = ltc4283_setup(st, dev);
> > + if (ret)
> > + return ret;
> > +
> > + hwmon = devm_hwmon_device_register_with_info(dev, "ltc4283", st,
> > + <c4283_chip_info, NULL);
> > +
> > + if (IS_ERR(hwmon))
> > + return PTR_ERR(hwmon);
> > +
> > + ltc4283_debugfs_init(st, client);
> > +
> > + if (!st->gpio_mask)
> > + return 0;
> > +
> > + adev = devm_auxiliary_device_create(dev, "gpio", &st->gpio_mask);
> > + if (!adev)
> > + return dev_err_probe(dev, -ENODEV, "Failed to add GPIO device\n");
> > +
> > + return 0;
> > +}
> > +
> > +static const struct of_device_id ltc4283_of_match[] = {
> > + { .compatible = "adi,ltc4283" },
> > + { }
> > +};
> > +
> > +static const struct i2c_device_id ltc4283_i2c_id[] = {
> > + { "ltc4283" },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(i2c, ltc4283_i2c_id);
> > +
> > +static struct i2c_driver ltc4283_driver = {
> > + .driver = {
> > + .name = "ltc4283",
> > + .of_match_table = ltc4283_of_match,
> > + },
> > + .probe = ltc4283_probe,
> > + .id_table = ltc4283_i2c_id,
> > +};
> > +module_i2c_driver(ltc4283_driver);
> > +
> > +MODULE_AUTHOR("Nuno Sá <nuno.sa@analog.com>");
> > +MODULE_DESCRIPTION("LTC4283 Hot Swap Controller driver");
> > +MODULE_LICENSE("GPL");
^ permalink raw reply
* Re: [PATCH v6 14/40] arm_mpam: resctrl: Add boilerplate cpuhp and domain allocation
From: Ben Horgan @ 2026-03-23 10:13 UTC (permalink / raw)
To: Gavin Shan
Cc: amitsinght, baisheng.gao, baolin.wang, carl, dave.martin, david,
dfustini, fenghuay, james.morse, jonathan.cameron, kobak,
lcherian, linux-arm-kernel, linux-kernel, peternewman,
punit.agrawal, quic_jiles, reinette.chatre, rohit.mathew, scott,
sdonthineni, tan.shaopeng, xhao, catalin.marinas, will, corbet,
maz, oupton, joey.gouly, suzuki.poulose, kvmarm, zengheng4,
linux-doc, Shaopeng Tan
In-Reply-To: <98708bfe-7930-49d5-b383-42b7b2d6759d@redhat.com>
Hi Gavin,
On 3/23/26 06:31, Gavin Shan wrote:
> Hi Ben,
>
> On 3/14/26 12:45 AM, Ben Horgan wrote:
>> From: James Morse <james.morse@arm.com>
>>
>> resctrl has its own data structures to describe its resources. We can't use
>> these directly as we play tricks with the 'MBA' resource, picking the MPAM
>> controls or monitors that best apply. We may export the same component as
>> both L3 and MBA.
>>
>> Add mpam_resctrl_res[] as the array of class->resctrl mappings we are
>> exporting, and add the cpuhp hooks that allocated and free the resctrl
>> domain structures. Only the mpam control feature are considered here and
>> monitor support will be added later.
>>
>> While we're here, plumb in a few other obvious things.
>>
>> CONFIG_ARM_CPU_RESCTRL is used to allow this code to be built even though
>> it can't yet be linked against resctrl.
>>
>> Tested-by: Gavin Shan <gshan@redhat.com>
>> Tested-by: Shaopeng Tan <tan.shaopeng@jp.fujitsu.com>
>> Tested-by: Peter Newman <peternewman@google.com>
>> Tested-by: Zeng Heng <zengheng4@huawei.com>
>> Tested-by: Punit Agrawal <punit.agrawal@oss.qualcomm.com>
>> Reviewed-by: Zeng Heng <zengheng4@huawei.com>
>> Reviewed-by: Shaopeng Tan <tan.shaopeng@jp.fujitsu.com>
>> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
>> Signed-off-by: James Morse <james.morse@arm.com>
>> Signed-off-by: Ben Horgan <ben.horgan@arm.com>
>> ---
>> Changes since rfc:
>> Domain list is an rcu list
>> Add synchronize_rcu() to free the deleted element
>> Code flow simplification (Jonathan)
>>
>> Changes since v2:
>> Iterate over mpam_resctrl_dom directly (Jonathan)
>> Code flow clarification
>> Comment tidying
>> Remove power of 2 check as no longer creates holes in rmid indices
>> Remove unused type argument
>> add macro helper for_each_mpam_resctrl_control
>>
>> Changes since v3:
>> Add and use mpam_resctrl_online_domain_hdr()
>> mpam_resctrl_alloc_domain() error paths (Reinette)
>> rebase on x86/cache changes rdt_mon_domain becomes rdt_l3_mon_domain
>> etc
>>
>> Changes since v4:
>> Set rid in domain_hdr
>> Use rescctrl_res.alloc_capable to determine if alloc_capable as the
>> decision may depend on the resctrl mount options (cdp)
>> Squash in arm_mpam: resctrl: Sort the order of the domain lists
>> Move out monitor/counter changes to a separate patch
>> Commit message update
>>
>> Changes since v5:
>> Use r->alloc_capable when resource specific
>> Move offline_ctrl_domain label and cleanup to monitor boilerplate patch
>> ---
>> drivers/resctrl/Makefile | 1 +
>> drivers/resctrl/mpam_devices.c | 12 ++
>> drivers/resctrl/mpam_internal.h | 21 +++
>> drivers/resctrl/mpam_resctrl.c | 324 ++++++++++++++++++++++++++++++++
>> include/linux/arm_mpam.h | 3 +
>> 5 files changed, 361 insertions(+)
>> create mode 100644 drivers/resctrl/mpam_resctrl.c
>>
>
> With the following two comments addressed. I don't think none of them are critical
> given the fact that this series has been respinned to v6 and may be ready for Linux
> v7.1. If there is still a chance for another respin, they may be worthy to be addressed.
Thanks for the review. Fingers crossed, we will make Linux v7.1.
>
> Reviewed-by: Gavin Shan <gshan@redhat.com>
>
>> diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
>> index 898199dcf80d..40beaf999582 100644
>> --- a/drivers/resctrl/Makefile
>> +++ b/drivers/resctrl/Makefile
>> @@ -1,4 +1,5 @@
>> obj-$(CONFIG_ARM64_MPAM_DRIVER) += mpam.o
>> mpam-y += mpam_devices.o
>> +mpam-$(CONFIG_ARM_CPU_RESCTRL) += mpam_resctrl.o
>> ccflags-$(CONFIG_ARM64_MPAM_DRIVER_DEBUG) += -DDEBUG
>> diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
>> index d50461d6ff3f..0e525539b7e2 100644
>> --- a/drivers/resctrl/mpam_devices.c
>> +++ b/drivers/resctrl/mpam_devices.c
>> @@ -1612,6 +1612,9 @@ static int mpam_cpu_online(unsigned int cpu)
>> mpam_reprogram_msc(msc);
>> }
>> + if (mpam_is_enabled())
>> + return mpam_resctrl_online_cpu(cpu);
>> +
>> return 0;
>> }
>> @@ -1655,6 +1658,9 @@ static int mpam_cpu_offline(unsigned int cpu)
>> {
>> struct mpam_msc *msc;
>> + if (mpam_is_enabled())
>> + mpam_resctrl_offline_cpu(cpu);
>> +
>> guard(srcu)(&mpam_srcu);
>> list_for_each_entry_srcu(msc, &mpam_all_msc, all_msc_list,
>> srcu_read_lock_held(&mpam_srcu)) {
>> @@ -2500,6 +2506,12 @@ static void mpam_enable_once(void)
>> mutex_unlock(&mpam_list_lock);
>> cpus_read_unlock();
>> + if (!err) {
>> + err = mpam_resctrl_setup();
>> + if (err)
>> + pr_err("Failed to initialise resctrl: %d\n", err);
>> + }
>> +
>> if (err) {
>> mpam_disable_reason = "Failed to enable.";
>> schedule_work(&mpam_broken_work);
>> diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
>> index 4632985bcca6..28ac501e1ac3 100644
>> --- a/drivers/resctrl/mpam_internal.h
>> +++ b/drivers/resctrl/mpam_internal.h
>> @@ -12,6 +12,7 @@
>> #include <linux/jump_label.h>
>> #include <linux/llist.h>
>> #include <linux/mutex.h>
>> +#include <linux/resctrl.h>
>> #include <linux/spinlock.h>
>> #include <linux/srcu.h>
>> #include <linux/types.h>
>> @@ -337,6 +338,16 @@ struct mpam_msc_ris {
>> struct mpam_garbage garbage;
>> };
>> +struct mpam_resctrl_dom {
>> + struct mpam_component *ctrl_comp;
>> + struct rdt_ctrl_domain resctrl_ctrl_dom;
>> +};
>> +
>> +struct mpam_resctrl_res {
>> + struct mpam_class *class;
>> + struct rdt_resource resctrl_res;
>> +};
>> +
>> static inline int mpam_alloc_csu_mon(struct mpam_class *class)
>> {
>> struct mpam_props *cprops = &class->props;
>> @@ -391,6 +402,16 @@ void mpam_msmon_reset_mbwu(struct mpam_component *comp, struct mon_cfg *ctx);
>> int mpam_get_cpumask_from_cache_id(unsigned long cache_id, u32 cache_level,
>> cpumask_t *affinity);
>> +#ifdef CONFIG_RESCTRL_FS
>> +int mpam_resctrl_setup(void);
>> +int mpam_resctrl_online_cpu(unsigned int cpu);
>> +void mpam_resctrl_offline_cpu(unsigned int cpu);
>> +#else
>> +static inline int mpam_resctrl_setup(void) { return 0; }
>> +static inline int mpam_resctrl_online_cpu(unsigned int cpu) { return 0; }
>> +static inline void mpam_resctrl_offline_cpu(unsigned int cpu) { }
>> +#endif /* CONFIG_RESCTRL_FS */
>> +
>> /*
>> * MPAM MSCs have the following register layout. See:
>> * Arm Memory System Resource Partitioning and Monitoring (MPAM) System
>> diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
>> new file mode 100644
>> index 000000000000..e698b534e3db
>> --- /dev/null
>> +++ b/drivers/resctrl/mpam_resctrl.c
>> @@ -0,0 +1,324 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +// Copyright (C) 2025 Arm Ltd.
>> +
>> +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
>> +
>> +#include <linux/arm_mpam.h>
>> +#include <linux/cacheinfo.h>
>> +#include <linux/cpu.h>
>> +#include <linux/cpumask.h>
>> +#include <linux/errno.h>
>> +#include <linux/list.h>
>> +#include <linux/printk.h>
>> +#include <linux/rculist.h>
>> +#include <linux/resctrl.h>
>> +#include <linux/slab.h>
>> +#include <linux/types.h>
>> +
>> +#include <asm/mpam.h>
>> +
>> +#include "mpam_internal.h"
>> +
>> +/*
>> + * The classes we've picked to map to resctrl resources, wrapped
>> + * in with their resctrl structure.
>> + * Class pointer may be NULL.
>> + */
>> +static struct mpam_resctrl_res mpam_resctrl_controls[RDT_NUM_RESOURCES];
>> +
>> +#define for_each_mpam_resctrl_control(res, rid) \
>> + for (rid = 0, res = &mpam_resctrl_controls[rid]; \
>> + rid < RDT_NUM_RESOURCES; \
>> + rid++, res = &mpam_resctrl_controls[rid])
>> +
>> +/* The lock for modifying resctrl's domain lists from cpuhp callbacks. */
>> +static DEFINE_MUTEX(domain_list_lock);
>> +
>> +bool resctrl_arch_alloc_capable(void)
>> +{
>> + struct mpam_resctrl_res *res;
>> + enum resctrl_res_level rid;
>> +
>> + for_each_mpam_resctrl_control(res, rid) {
>> + if (res->resctrl_res.alloc_capable)
>> + return true;
>> + }
>> +
>> + return false;
>> +}
>> +
>> +/*
>> + * MSC may raise an error interrupt if it sees an out or range partid/pmg,
>> + * and go on to truncate the value. Regardless of what the hardware supports,
>> + * only the system wide safe value is safe to use.
>> + */
>> +u32 resctrl_arch_get_num_closid(struct rdt_resource *ignored)
>> +{
>> + return mpam_partid_max + 1;
>> +}
>> +
>> +struct rdt_resource *resctrl_arch_get_resource(enum resctrl_res_level l)
>> +{
>> + if (l >= RDT_NUM_RESOURCES)
>> + return NULL;
>> +
>> + return &mpam_resctrl_controls[l].resctrl_res;
>> +}
>> +
>> +static int mpam_resctrl_control_init(struct mpam_resctrl_res *res)
>> +{
>> + /* TODO: initialise the resctrl resources */
>> +
>> + return 0;
>> +}
>> +
>> +static int mpam_resctrl_pick_domain_id(int cpu, struct mpam_component *comp)
>> +{
>> + struct mpam_class *class = comp->class;
>> +
>> + if (class->type == MPAM_CLASS_CACHE)
>> + return comp->comp_id;
>> +
>> + /* TODO: repaint domain ids to match the L3 domain ids */
>> + /* Otherwise, expose the ID used by the firmware table code. */
>> + return comp->comp_id;
>> +}
>> +
>> +static void mpam_resctrl_domain_hdr_init(int cpu, struct mpam_component *comp,
>> + enum resctrl_res_level rid,
>> + struct rdt_domain_hdr *hdr)
>> +{
>> + lockdep_assert_cpus_held();
>> +
>> + INIT_LIST_HEAD(&hdr->list);
>> + hdr->id = mpam_resctrl_pick_domain_id(cpu, comp);
>> + hdr->rid = rid;
>> + cpumask_set_cpu(cpu, &hdr->cpu_mask);
>> +}
>> +
>> +static void mpam_resctrl_online_domain_hdr(unsigned int cpu,
>> + struct rdt_domain_hdr *hdr)
>> +{
>> + lockdep_assert_cpus_held();
>> +
>> + cpumask_set_cpu(cpu, &hdr->cpu_mask);
>> +}
>> +
>> +/**
>> + * mpam_resctrl_offline_domain_hdr() - Update the domain header to remove a CPU.
>> + * @cpu: The CPU to remove from the domain.
>> + * @hdr: The domain's header.
>> + *
>> + * Removes @cpu from the header mask. If this was the last CPU in the domain,
>> + * the domain header is removed from its parent list and true is returned,
>> + * indicating the parent structure can be freed.
>> + * If there are other CPUs in the domain, returns false.
>> + */
>> +static bool mpam_resctrl_offline_domain_hdr(unsigned int cpu,
>> + struct rdt_domain_hdr *hdr)
>> +{
>> + lockdep_assert_held(&domain_list_lock);
>> +
>> + cpumask_clear_cpu(cpu, &hdr->cpu_mask);
>> + if (cpumask_empty(&hdr->cpu_mask)) {
>> + list_del_rcu(&hdr->list);
>> + synchronize_rcu();
>> + return true;
>> + }
>> +
>> + return false;
>> +}
>> +
>> +static void mpam_resctrl_domain_insert(struct list_head *list,
>> + struct rdt_domain_hdr *new)
>> +{
>> + struct rdt_domain_hdr *err;
>> + struct list_head *pos = NULL;
>> +
>> + lockdep_assert_held(&domain_list_lock);
>> +
>> + err = resctrl_find_domain(list, new->id, &pos);
>> + if (WARN_ON_ONCE(err))
>> + return;
>> +
>> + list_add_tail_rcu(&new->list, pos);
>> +}
>> +
>> +static struct mpam_resctrl_dom *
>> +mpam_resctrl_alloc_domain(unsigned int cpu, struct mpam_resctrl_res *res)
>> +{
>> + int err;
>> + struct mpam_resctrl_dom *dom;
>> + struct rdt_ctrl_domain *ctrl_d;
>> + struct mpam_class *class = res->class;
>> + struct mpam_component *comp_iter, *ctrl_comp;
>> + struct rdt_resource *r = &res->resctrl_res;
>> +
>> + lockdep_assert_held(&domain_list_lock);
>> +
>> + ctrl_comp = NULL;
>> + guard(srcu)(&mpam_srcu);
>> + list_for_each_entry_srcu(comp_iter, &class->components, class_list,
>> + srcu_read_lock_held(&mpam_srcu)) {
>> + if (cpumask_test_cpu(cpu, &comp_iter->affinity)) {
>> + ctrl_comp = comp_iter;
>> + break;
>> + }
>> + }
>> +
>> + /* class has no component for this CPU */
>> + if (WARN_ON_ONCE(!ctrl_comp))
>> + return ERR_PTR(-EINVAL);
>> +
>> + dom = kzalloc_node(sizeof(*dom), GFP_KERNEL, cpu_to_node(cpu));
>> + if (!dom)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + if (r->alloc_capable) {
>> + dom->ctrl_comp = ctrl_comp;
>> +
>> + ctrl_d = &dom->resctrl_ctrl_dom;
>> + mpam_resctrl_domain_hdr_init(cpu, ctrl_comp, r->rid, &ctrl_d->hdr);
>> + ctrl_d->hdr.type = RESCTRL_CTRL_DOMAIN;
>> + err = resctrl_online_ctrl_domain(r, ctrl_d);
>> + if (err)
>> + goto free_domain;
>> +
>> + mpam_resctrl_domain_insert(&r->ctrl_domains, &ctrl_d->hdr);
>> + } else {
>> + pr_debug("Skipped control domain online - no controls\n");
>> + }
>> + return dom;
>> +
>
> Even though we will never support "r->alloc_capable == false", it's worthy to maintain
> the consistence in the code level here, meaning @dom needs to be released with a proper
> error number returned.
>
> if (r->alloc_capable) {
> :
> } else {
> pr_debug("Skipped control domain online - no controls\n");
> err = -EINVAL;
> goto free_domain;
> }
>
> Alternatively, the check can be done before locating the component from its calss.
>
>
> lockdep_assert_held(&domain_list_lock);
>
> if (!r->alloc_capable) {
> pr_debug("Skipped control domain online - no controls\n");
> return ERR_PTR(-EINVAL);
> }
>
> ctrl_comp = NULL;
Once monitor support is added later in the series this is no longer an
error path as monitor only platforms are valid. In order to avoid adding
a change and then reverting it later in the series I would like to keep
this as it is.
>
>> +free_domain:
>> + kfree(dom);
>> + dom = ERR_PTR(err);
>> +
>> + return dom;
>> +}
>> +
>> +static struct mpam_resctrl_dom *
>> +mpam_resctrl_get_domain_from_cpu(int cpu, struct mpam_resctrl_res *res)
>> +{
>> + struct mpam_resctrl_dom *dom;
>> + struct rdt_resource *r = &res->resctrl_res;
>> +
>> + lockdep_assert_cpus_held();
>> +
>> + list_for_each_entry_rcu(dom, &r->ctrl_domains, resctrl_ctrl_dom.hdr.list) {
>> + if (cpumask_test_cpu(cpu, &dom->ctrl_comp->affinity))
>> + return dom;
>> + }
>> +
>> + return NULL;
>> +}
>> +
>> +int mpam_resctrl_online_cpu(unsigned int cpu)
>> +{
>> + struct mpam_resctrl_res *res;
>> + enum resctrl_res_level rid;
>> +
>> + guard(mutex)(&domain_list_lock);
>> + for_each_mpam_resctrl_control(res, rid) {
>> + struct mpam_resctrl_dom *dom;
>> + struct rdt_resource *r = &res->resctrl_res;
>> +
>> + if (!res->class)
>> + continue; // dummy_resource;
>> +
>> + dom = mpam_resctrl_get_domain_from_cpu(cpu, res);
>> + if (!dom) {
>> + dom = mpam_resctrl_alloc_domain(cpu, res);
>> + } else {
>> + if (r->alloc_capable) {
>> + struct rdt_ctrl_domain *ctrl_d = &dom->resctrl_ctrl_dom;
>> +
>> + mpam_resctrl_online_domain_hdr(cpu, &ctrl_d->hdr);
>> + }
>> + }
>> + if (IS_ERR(dom))
>> + return PTR_ERR(dom);
>> + }
>> +
>
> I think the "if (IS_ERR(dom))" check can be moved after "dom = mpam_resctrl_alloc_domain(cpu, res)"
> because it seems the only path where an erroneous domain can be returned.
>
> dom = mpam_resctrl_get_domain_from_cpu(cpu, res);
> if (!dom) {
> dom = mpam_resctrl_alloc_domain(cpu, res);
> if (IS_ERR(dom))
> return PTR_ERR(dom);
> } else {
> ...
> }
Yes, that would be clearer. I'll make this change if a respin becomes needed.
>
>> + resctrl_online_cpu(cpu);
>> +
>> + return 0;
>> +}
>> +
>> +void mpam_resctrl_offline_cpu(unsigned int cpu)
>> +{
>> + struct mpam_resctrl_res *res;
>> + enum resctrl_res_level rid;
>> +
>> + resctrl_offline_cpu(cpu);
>> +
>> + guard(mutex)(&domain_list_lock);
>> + for_each_mpam_resctrl_control(res, rid) {
>> + struct mpam_resctrl_dom *dom;
>> + struct rdt_ctrl_domain *ctrl_d;
>> + bool ctrl_dom_empty;
>> + struct rdt_resource *r = &res->resctrl_res;
>> +
>> + if (!res->class)
>> + continue; // dummy resource
>> +
>> + dom = mpam_resctrl_get_domain_from_cpu(cpu, res);
>> + if (WARN_ON_ONCE(!dom))
>> + continue;
>> +
>> + if (r->alloc_capable) {
>> + ctrl_d = &dom->resctrl_ctrl_dom;
>> + ctrl_dom_empty = mpam_resctrl_offline_domain_hdr(cpu, &ctrl_d->hdr);
>> + if (ctrl_dom_empty)
>> + resctrl_offline_ctrl_domain(&res->resctrl_res, ctrl_d);
>> + } else {
>> + ctrl_dom_empty = true;
>> + }
>> +
>> + if (ctrl_dom_empty)
>> + kfree(dom);
>> + }
>> +}
>> +
>> +int mpam_resctrl_setup(void)
>> +{
>> + int err = 0;
>> + struct mpam_resctrl_res *res;
>> + enum resctrl_res_level rid;
>> +
>> + cpus_read_lock();
>> + for_each_mpam_resctrl_control(res, rid) {
>> + INIT_LIST_HEAD_RCU(&res->resctrl_res.ctrl_domains);
>> + res->resctrl_res.rid = rid;
>> + }
>> +
>> + /* TODO: pick MPAM classes to map to resctrl resources */
>> +
>> + /* Initialise the resctrl structures from the classes */
>> + for_each_mpam_resctrl_control(res, rid) {
>> + if (!res->class)
>> + continue; // dummy resource
>> +
>> + err = mpam_resctrl_control_init(res);
>> + if (err) {
>> + pr_debug("Failed to initialise rid %u\n", rid);
>> + break;
>> + }
>> + }
>> + cpus_read_unlock();
>> +
>> + if (err) {
>> + pr_debug("Internal error %d - resctrl not supported\n", err);
>> + return err;
>> + }
>> +
>> + if (!resctrl_arch_alloc_capable()) {
>> + pr_debug("No alloc(%u) found - resctrl not supported\n",
>> + resctrl_arch_alloc_capable());
>> + return -EOPNOTSUPP;
>> + }
>> +
>> + /* TODO: call resctrl_init() */
>> +
>> + return 0;
>> +}
>> diff --git a/include/linux/arm_mpam.h b/include/linux/arm_mpam.h
>> index 7f00c5285a32..2c7d1413a401 100644
>> --- a/include/linux/arm_mpam.h
>> +++ b/include/linux/arm_mpam.h
>> @@ -49,6 +49,9 @@ static inline int mpam_ris_create(struct mpam_msc *msc, u8 ris_idx,
>> }
>> #endif
>> +bool resctrl_arch_alloc_capable(void);
>> +bool resctrl_arch_mon_capable(void);
>> +
>> /**
>> * mpam_register_requestor() - Register a requestor with the MPAM driver
>> * @partid_max: The maximum PARTID value the requestor can generate.
>
> Thanks,
> Gavin
>
^ permalink raw reply
* Re: [PATCH v5 00/21] Virtual Swap Space
From: Kairui Song @ 2026-03-23 10:08 UTC (permalink / raw)
To: Nhat Pham
Cc: Liam.Howlett, akpm, apopple, axelrasmussen, baohua, baolin.wang,
bhe, byungchul, cgroups, chengming.zhou, chrisl, corbet, david,
dev.jain, gourry, hannes, hughd, jannh, joshua.hahnjy, lance.yang,
lenb, linux-doc, linux-kernel, linux-mm, linux-pm,
lorenzo.stoakes, matthew.brost, mhocko, muchun.song, npache,
pavel, peterx, peterz, pfalcato, rafael, rakie.kim,
roman.gushchin, rppt, ryan.roberts, shakeel.butt, shikemeng,
surenb, tglx, vbabka, weixugc, ying.huang, yosry.ahmed, yuanchu,
zhengqi.arch, ziy, kernel-team, riel
In-Reply-To: <20260320192735.748051-1-nphamcs@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 8359 bytes --]
On Sat, Mar 21, 2026 at 3:29 AM Nhat Pham <nphamcs@gmail.com> wrote:
> This patch series is based on 6.19. There are a couple more
> swap-related changes in mainline that I would need to coordinate
> with, but I still want to send this out as an update for the
> regressions reported by Kairui Song in [15]. It's probably easier
> to just build this thing rather than dig through that series of
> emails to get the fix patch :)
>
> Changelog:
> * v4 -> v5:
> * Fix a deadlock in memcg1_swapout (reported by syzbot [16]).
> * Replace VM_WARN_ON(!spin_is_locked()) with lockdep_assert_held(),
> and use guard(rcu) in vswap_cpu_dead
> (reported by Peter Zijlstra [17]).
> * v3 -> v4:
> * Fix poor swap free batching behavior to alleviate a regression
> (reported by Kairui Song).
I tested the v5 (including the batched-free hotfix) and am still
seeing significant regressions in both sequential and concurrent swap
workloads
Thanks for the update as I can see It's a lot of thoughtful work.
Actually I did run some tests already with your previously posted
hotfix based on v3. I didn't update the result because very
unfortunately, I still see a major performance regression even with a
very simple setup.
BTW there seems a simpler way to reproduce that, just use memhog:
sudo mkswap /dev/pmem0; sudo swapon /dev/pmem0; time memhog 48G; sudo swapoff -a
Before:
(I'm using fish shell on that test machine so this is fish time format):
________________________________________________________
Executed in 20.80 secs fish external
usr time 5.14 secs 0.00 millis 5.14 secs
sys time 15.65 secs 1.17 millis 15.65 secs
________________________________________________________
Executed in 21.69 secs fish external
usr time 5.31 secs 725.00 micros 5.31 secs
sys time 16.36 secs 579.00 micros 16.36 secs
________________________________________________________
Executed in 21.86 secs fish external
usr time 5.39 secs 1.02 millis 5.39 secs
sys time 16.46 secs 0.27 millis 16.46 secs
After:
________________________________________________________
Executed in 30.77 secs fish external
usr time 5.16 secs 767.00 micros 5.16 secs
sys time 25.59 secs 580.00 micros 25.59 secs
________________________________________________________
Executed in 37.47 secs fish external
usr time 5.48 secs 0.00 micros 5.48 secs
sys time 31.98 secs 674.00 micros 31.98 secs
________________________________________________________
Executed in 31.34 secs fish external
usr time 5.22 secs 0.00 millis 5.22 secs
sys time 26.09 secs 1.30 millis 26.09 secs
It's obviously a lot slower.
pmem may seem rare but SSDs are good at sequential, and memhog uses
the same filled page and backend like ZRAM has extremely low overhead
for same filled pages. Results with ZRAM are very similar, and many
production workloads have massive amounts of samefill memory.
For example on the Android phone I'm using right now at this moment:
# cat /sys/block/zram0/mm_stat
4283899904 1317373036 1370259456 0 1475977216 116457 1991851
87273 1793760
~450M of samefill page in ZRAM, we may see more on some server
workload. And I'm seeing similar memhog results with ZRAM, pmem is
just easier to setup and less noisy. also simulates high speed
storage.
I also ran the previous usemem matrix, which seems better than V3 but
still pretty bad:
Test: usemem --init-time -O -n 1 56G, 16G mem, 48G swap, avgs of 8 run.
Before:
Throughput (Sum): 528.98 MB/s Throughput (Mean): 526.113333 MB/s Free
Latency: 3037932.888889
After:
Throughput (Sum): 453.74 MB/s Throughput (Mean): 454.875000 MB/s Free
Latency: 5001144.500000 (~10%, 64% slower)
I'm not sure why our results differ so much — perhaps different LRU
settings, memory pressure ratios, or THP/mTHP configs? Here's my exact
config in the attachment. Also includes the full log and info, with
all debug options disabled for close to production. I ran it 8 times
and just attached the first result log, it's all similar anyway, my
test framework reboot the machine after each test run to reduce any
potential noise.
And the above tests are only about sequential performance, concurrent
ones seem worse:
Test: usemem --init-time -O -R -n 32 622M, 16G mem, 48G swap, avgs of 8 run.
Before:
Throughput (Sum): 5467.51 MB/s Throughput (Mean): 170.04 MB/s Free
Latency: 28648.65
After:
Throughput (Sum): 4914.86 MB/s Throughput (Mean): 152.74 MB/s Free
Latency: 67789.81 (~10%, 230% slower)
And I double checked I'm testing your latest V5 commit here:
commit 9114ebedb82089ebd3519854964c73d3959b10c0 (HEAD -> upstream/vswap)
Author: Nhat Pham <nphamcs@gmail.com>
Date: Fri Mar 20 12:27:35 2026 -0700
vswap: batch contiguous vswap free calls
In vswap_free(), we release and reacquire the cluster lock for every
single entry, even for non-disk-swap backends where the lock drop is
unnecessary. Batch consecutive free operations to avoid this overhead.
Signed-off-by: Nhat Pham <nphamcs@gmail.com>
The two kernels being tested:
/boot/vmlinuz-6.19.0.orig-g05f7e89ab973
/boot/vmlinuz-6.19.0.ptch-g9114ebedb820
These tests above are done with an EPYC 7K62, I also setup an Intel
8255C with fresh installed upstream Fedora, and using Fedora's kernel
config. So far the result matches, the gap seems smaller but still
>20% slower for many cases, so this is a common problem:
3 test run on 8255C using fresh installed Fedora and Fedora kernel config:
taskset -c 3 /usr/local/bin/usemem --init-time -O -n 1 112G
(That's a two nodes large machine so I pin the thread on CPU 3 for stability)
Before:
135291469824 bytes / 124326887 usecs = 1062687 KB/s
2157355 usecs to free memory
135291469824 bytes / 123930024 usecs = 1066090 KB/s
2244083 usecs to free memory
135291469824 bytes / 123484528 usecs = 1069936 KB/s
2268364 usecs to free memory
After:
135291469824 bytes / 127073712 usecs = 1039716 KB/s
3050394 usecs to free memory
135291469824 bytes / 130724757 usecs = 1010677 KB/s
3064270 usecs to free memory
135291469824 bytes / 127248347 usecs = 1038289 KB/s
3035986 usecs to free memory
And beside these known cases, my main concern is still that a
mandatory virtual layer seems just wrong, it changes how swap work in
many ways. Storage folks have been trying to bypass the kernel for
decades, as abstraction layers come with overhead — that's common
knowledge. Swap lives right at the intersection of storage and mm and
has to stay inside the kernel, so we really want the kernel path to be
as flat and direct as possible.
I'm also worried this risks undoing all the recent and upcoming work
for reducing memory usage and performance. We've been trying to shrink
per-entry overhead (I'm already feeling nervous over the current
8-byte per-entry cost, and hope soon we'll get down to <1–3 bytes).
The series mentions 24 bytes of overhead, but when I account for the
reverse mapping, it looks >32 bytes per entry.
The intermediate large XArray layer also worries me as the swap space
is now very large. The virtual size could grow with no limit. e.g. a 1
TB swap space would be a 4 layers radix tree, increasing global
contention (int(1024 * 1024 / 2) >> 6 >> 6 >> 6 == 2) and vswap could
be even larger if fragmentation happens. That's the exact problem the
old sub-address_space design for SWAP was created to solve. We only
eliminated that complexity a few months ago, and this approach seems
like it would have to bring a similar structure back to reduce
contention.
And for swapoff support: minor anonymous faults during busy periods
are indeed critical for some workloads, and being able to swapoff
cleanly is still very useful both for performance and troubleshooting.
You will need to touch many things to solve a minor fault.
For reference, I've been exploring an approach that keeps the virtual
layer runtime-optional, which avoids these overheads for workloads
that don't need virtualization:
https://lore.kernel.org/linux-mm/20260220-swap-table-p4-v1-0-104795d19815@tencent.com/
[-- Attachment #2: config-n-log.tar --]
[-- Type: application/tar, Size: 665600 bytes --]
^ permalink raw reply
* Re: [PATCH v2 0/1] docs: examples of pages affected by C API signature overflow
From: Rito Rhymes @ 2026-03-23 9:57 UTC (permalink / raw)
To: Jonathan Corbet, Rito Rhymes, linux-doc; +Cc: Shuah Khan, linux-kernel, rdunlap
In-Reply-To: <874im7eh6e.fsf@trenco.lwn.net>
Thank you for the feedback.
I'll put some examples in the changelogs with versioned URLs and
reroll them without a cover letter.
> The cover letter tells reviewers what the series as a whole does
Admittedly, I knew including a cover letter for a single patch was
non-standard. Given the quantity of examples I aimed to include,
I didn't think it acceptable to include it all in the changelog, but I
knew having the examples was important for testing, so I repurposed the
cover letter as the vehicle for including them. I'll lay off that.
> In this case, some examples of the problem being solved are certainly
> warranted, though perhaps not so many as given here.
I'll scale it down to just a few for the rerolls in the patchlogs.
FWIW my rationale was clear:
"Is this issue a significant enough pattern to justify the risk of
regressions and the review bandwidth required to address it?"
^ I thought it was important to preempt that question given the size
of this repo, assuming layout issues aren't exactly top of mind and
that the potential changes may have systemic effects to consider.
I did an audit for each of the layout fixes and resolved that if I
couldn't find 10 instances of an issue, it would not be worthwhile or
defensible to push. As a first-time contributor here, I figured showing
a concrete list of examples I found was preferable to a take-my-word-
for-it description of each issue's scale while showing only 1 or 2
examples.
> The names of HTML files are perhaps not ideal; since you're
> talking about docs.kernel.org in particular, you could give URLs
> that people could view directly.
> So for example
>
> https://docs.kernel.org/6.19/core-api/genalloc.html
>
> Note the explicit version so that the problem will be findable in the
> distant future, even after the fix is applied.
Yeah that approach makes more sense; the versioning anchors the fix
well for future. I had used neutral HTML file names with local user
testing in mind where the canonical domain wouldn't be used now or in
post-fix source trees.
Rito
^ permalink raw reply
* Re: [PATCH] docs: ja_JP: process: translate second half of 'Describe your changes'
From: weibu @ 2026-03-23 8:29 UTC (permalink / raw)
To: Akira Yokosawa; +Cc: corbet, linux-kernel, linux-doc
In-Reply-To: <68e16682-aba3-4e10-8776-1c04c55b7ddc@gmail.com>
Hi Akira-san,
Thank you very much for your review and for your kind message.
I appreciate your taking the time to look at the patch.
Thank you also for letting me know about the minor translation issues.
If you are willing to prepare a follow-up patch, I would greatly
appreciate it.
I am also glad that the reason for the line wrapping makes sense.
Thank you again for your help and support.
Best regards,
Akiyoshi Kurita
2026-03-23 17:11 に Akira Yokosawa さんは書きました:
> Hi,
>
> Sorry for the late response.
>
> On Mon, 9 Mar 2026 19:50:15 +0900, Akiyoshi Kurita wrote:
>> Translate the remaining part of the "Describe your changes" section in
>> Documentation/translations/ja_JP/process/submitting-patches.rst.
>>
>> Follow review comments on wording and line wrapping, and cover
>> guidance
>> on self-contained patch descriptions, imperative mood, commit
>> references, and Link:/Closes:/Fixes: tags.
>>
>> Signed-off-by: Akiyoshi Kurita <weibu@redadmin.org>
>
> Acked-by: Akira Yokosawa <akiyks@gmail.com>
>
> I have noticed a couple of minor translation issues in this patch,
> but I'd rather submit a patch on top of this, rather than comment on
> those issues in English.
>
> I guess I can prepare a follow-up patch within a week.
>
> By the way, now I see why Kurita-san is wrapping lines at 30 wide-chars
> or so. I guess they are broken that way so that each line corresponds
> to that in the English text. That makes sense.
>
> Thanks, Akira
>
>> ---
>> .../ja_JP/process/submitting-patches.rst | 84
>> +++++++++++++++++++
>> 1 file changed, 84 insertions(+)
>>
> [...]
^ permalink raw reply
* Re: [PATCH v13 05/12] dmaengine: qcom: bam_dma: add support for BAM locking
From: Manivannan Sadhasivam @ 2026-03-23 9:34 UTC (permalink / raw)
To: Bartosz Golaszewski
Cc: Vinod Koul, Jonathan Corbet, Thara Gopinath, Herbert Xu,
David S. Miller, Udit Tiwari, Md Sadre Alam, Dmitry Baryshkov,
Stephan Gerhold, Bjorn Andersson, Peter Ujfalusi, Michal Simek,
Frank Li, dmaengine, linux-doc, linux-kernel, linux-arm-msm,
linux-crypto, linux-arm-kernel, brgl, Bartosz Golaszewski
In-Reply-To: <20260317-qcom-qce-cmd-descr-v13-5-0968eb4f8c40@oss.qualcomm.com>
On Tue, Mar 17, 2026 at 03:02:12PM +0100, Bartosz Golaszewski wrote:
> Add support for BAM pipe locking. To that end: when starting DMA on an RX
> channel - prepend the existing queue of issued descriptors with an
> additional "dummy" command descriptor with the LOCK bit set. Once the
> transaction is done (no more issued descriptors), issue one more dummy
> descriptor with the UNLOCK bit.
>
> We *must* wait until the transaction is signalled as done because we
> must not perform any writes into config registers while the engine is
> busy.
>
> The dummy writes must be issued into a scratchpad register of the client
> so provide a mechanism to communicate the right address via descriptor
> metadata.
>
> Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
I've left some comments in v12, but looks like you've missed them. Anyhow,
this version looks good to me as the design looks simple and doesn't warrant
much change from the client driver other than passing the scratchpad register as
metadata. I just have some minor comments in this version.
> ---
> drivers/dma/qcom/bam_dma.c | 160 ++++++++++++++++++++++++++++++++++++++-
> include/linux/dma/qcom_bam_dma.h | 4 +
> 2 files changed, 160 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c
> index 83491e7c2f17d8c9d12a1a055baea7e3a0a75a53..895286452c8b5e701c1df482095e5fe4a49f4246 100644
> --- a/drivers/dma/qcom/bam_dma.c
> +++ b/drivers/dma/qcom/bam_dma.c
> @@ -28,11 +28,13 @@
> #include <linux/clk.h>
> #include <linux/device.h>
> #include <linux/dma-mapping.h>
> +#include <linux/dma/qcom_bam_dma.h>
> #include <linux/dmaengine.h>
> #include <linux/init.h>
> #include <linux/interrupt.h>
> #include <linux/io.h>
> #include <linux/kernel.h>
> +#include <linux/lockdep.h>
> #include <linux/module.h>
> #include <linux/of_address.h>
> #include <linux/of_dma.h>
> @@ -60,6 +62,8 @@ struct bam_desc_hw {
> #define DESC_FLAG_EOB BIT(13)
> #define DESC_FLAG_NWD BIT(12)
> #define DESC_FLAG_CMD BIT(11)
> +#define DESC_FLAG_LOCK BIT(10)
> +#define DESC_FLAG_UNLOCK BIT(9)
>
> struct bam_async_desc {
> struct virt_dma_desc vd;
> @@ -391,6 +395,14 @@ struct bam_chan {
> struct list_head desc_list;
>
> struct list_head node;
> +
> + /* BAM locking infrastructure */
> + bool locked;
> + phys_addr_t scratchpad_addr;
> + struct scatterlist lock_sg;
> + struct scatterlist unlock_sg;
> + struct bam_cmd_element lock_ce;
> + struct bam_cmd_element unlock_ce;
> };
>
> static inline struct bam_chan *to_bam_chan(struct dma_chan *common)
> @@ -652,6 +664,27 @@ static int bam_slave_config(struct dma_chan *chan,
> return 0;
> }
>
> +static int bam_metadata_attach(struct dma_async_tx_descriptor *desc, void *data, size_t len)
> +{
> + struct bam_chan *bchan = to_bam_chan(desc->chan);
> + const struct bam_device_data *bdata = bchan->bdev->dev_data;
> + struct bam_desc_metadata *metadata = data;
> +
> + if (!data)
> + return -EINVAL;
> +
> + if (!bdata->pipe_lock_supported)
> + return -EOPNOTSUPP;
As mentioned in v12, you should return 0 to avoid erroring out the clients if
pipe lock is not supported.
> +
> + bchan->scratchpad_addr = metadata->scratchpad_addr;
> +
> + return 0;
> +}
> +
> +static const struct dma_descriptor_metadata_ops bam_metadata_ops = {
> + .attach = bam_metadata_attach,
> +};
> +
> /**
> * bam_prep_slave_sg - Prep slave sg transaction
> *
> @@ -668,6 +701,7 @@ static struct dma_async_tx_descriptor *bam_prep_slave_sg(struct dma_chan *chan,
> void *context)
> {
> struct bam_chan *bchan = to_bam_chan(chan);
> + struct dma_async_tx_descriptor *tx_desc;
> struct bam_device *bdev = bchan->bdev;
> struct bam_async_desc *async_desc;
> struct scatterlist *sg;
> @@ -723,7 +757,12 @@ static struct dma_async_tx_descriptor *bam_prep_slave_sg(struct dma_chan *chan,
> } while (remainder > 0);
> }
>
> - return vchan_tx_prep(&bchan->vc, &async_desc->vd, flags);
> + tx_desc = vchan_tx_prep(&bchan->vc, &async_desc->vd, flags);
> + if (!tx_desc)
> + return NULL;
> +
> + tx_desc->metadata_ops = &bam_metadata_ops;
> + return tx_desc;
> }
>
> /**
> @@ -1012,13 +1051,115 @@ static void bam_apply_new_config(struct bam_chan *bchan,
> bchan->reconfigure = 0;
> }
>
> +static struct bam_async_desc *
> +bam_make_lock_desc(struct bam_chan *bchan, struct scatterlist *sg,
> + struct bam_cmd_element *ce, unsigned long flag)
> +{
> + struct dma_chan *chan = &bchan->vc.chan;
> + struct bam_async_desc *async_desc;
> + struct bam_desc_hw *desc;
> + struct virt_dma_desc *vd;
> + struct virt_dma_chan *vc;
> + unsigned int mapped;
> + dma_cookie_t cookie;
> + int ret;
> +
> + sg_init_table(sg, 1);
> +
> + async_desc = kzalloc_flex(*async_desc, desc, 1, GFP_NOWAIT);
> + if (!async_desc) {
> + dev_err(bchan->bdev->dev, "failed to allocate the BAM lock descriptor\n");
> + return NULL;
> + }
> +
> + async_desc->num_desc = 1;
> + async_desc->curr_desc = async_desc->desc;
> + async_desc->dir = DMA_MEM_TO_DEV;
> +
> + desc = async_desc->desc;
> +
> + bam_prep_ce_le32(ce, bchan->scratchpad_addr, BAM_WRITE_COMMAND, 0);
> + sg_set_buf(sg, ce, sizeof(*ce));
> +
> + mapped = dma_map_sg_attrs(chan->slave, sg, 1, DMA_TO_DEVICE, DMA_PREP_CMD);
> + if (!mapped) {
> + kfree(async_desc);
> + return NULL;
> + }
> +
> + desc->flags |= cpu_to_le16(DESC_FLAG_CMD | flag);
> + desc->addr = sg_dma_address(sg);
> + desc->size = sizeof(struct bam_cmd_element);
> +
> + vc = &bchan->vc;
> + vd = &async_desc->vd;
> +
> + dma_async_tx_descriptor_init(&vd->tx, &vc->chan);
> + vd->tx.flags = DMA_PREP_CMD;
> + vd->tx.desc_free = vchan_tx_desc_free;
> + vd->tx_result.result = DMA_TRANS_NOERROR;
> + vd->tx_result.residue = 0;
> +
> + cookie = dma_cookie_assign(&vd->tx);
> + ret = dma_submit_error(cookie);
> + if (ret)
> + return NULL;
You are leaking async_desc here.
> +
> + return async_desc;
> +}
> +
> +static int bam_do_setup_pipe_lock(struct bam_chan *bchan, bool lock)
> +{
> + struct bam_device *bdev = bchan->bdev;
> + const struct bam_device_data *bdata = bdev->dev_data;
> + struct bam_async_desc *lock_desc;
> + struct bam_cmd_element *ce;
> + struct scatterlist *sgl;
> + unsigned long flag;
> +
> + lockdep_assert_held(&bchan->vc.lock);
> +
> + if (!bdata->pipe_lock_supported || !bchan->scratchpad_addr ||
> + bchan->slave.direction != DMA_MEM_TO_DEV)
> + return 0;
> +
> + if (lock) {
> + sgl = &bchan->lock_sg;
> + ce = &bchan->lock_ce;
> + flag = DESC_FLAG_LOCK;
> + } else {
> + sgl = &bchan->unlock_sg;
> + ce = &bchan->unlock_ce;
> + flag = DESC_FLAG_UNLOCK;
> + }
> +
> + lock_desc = bam_make_lock_desc(bchan, sgl, ce, flag);
> + if (!lock_desc)
> + return -ENOMEM;
> +
> + if (lock)
> + list_add(&lock_desc->vd.node, &bchan->vc.desc_issued);
> + else
> + list_add_tail(&lock_desc->vd.node, &bchan->vc.desc_issued);
> +
> + bchan->locked = lock;
What is this flag for?
> +
> + return 0;
> +}
> +
> +static void bam_setup_pipe_lock(struct bam_chan *bchan)
> +{
> + if (bam_do_setup_pipe_lock(bchan, true) || bam_do_setup_pipe_lock(bchan, false))
> + dev_err(bchan->vc.chan.slave, "Failed to setup BAM pipe lock descriptors");
> +}
> +
> /**
> * bam_start_dma - start next transaction
> * @bchan: bam dma channel
> */
> static void bam_start_dma(struct bam_chan *bchan)
> {
> - struct virt_dma_desc *vd = vchan_next_desc(&bchan->vc);
> + struct virt_dma_desc *vd;
> struct bam_device *bdev = bchan->bdev;
> struct bam_async_desc *async_desc = NULL;
> struct bam_desc_hw *desc;
> @@ -1030,6 +1171,9 @@ static void bam_start_dma(struct bam_chan *bchan)
>
> lockdep_assert_held(&bchan->vc.lock);
>
> + bam_setup_pipe_lock(bchan);
> +
> + vd = vchan_next_desc(&bchan->vc);
> if (!vd)
> return;
>
> @@ -1157,8 +1301,15 @@ static void bam_issue_pending(struct dma_chan *chan)
> */
> static void bam_dma_free_desc(struct virt_dma_desc *vd)
> {
> - struct bam_async_desc *async_desc = container_of(vd,
> - struct bam_async_desc, vd);
> + struct bam_async_desc *async_desc = container_of(vd, struct bam_async_desc, vd);
> + struct bam_desc_hw *desc = async_desc->desc;
> + struct dma_chan *chan = vd->tx.chan;
> + struct bam_chan *bchan = to_bam_chan(chan);
> +
> + if (le16_to_cpu(desc->flags) & DESC_FLAG_LOCK)
> + dma_unmap_sg(chan->slave, &bchan->lock_sg, 1, DMA_TO_DEVICE);
> + else if (le16_to_cpu(desc->flags) & DESC_FLAG_UNLOCK)
> + dma_unmap_sg(chan->slave, &bchan->unlock_sg, 1, DMA_TO_DEVICE);
>
> kfree(async_desc);
> }
> @@ -1350,6 +1501,7 @@ static int bam_dma_probe(struct platform_device *pdev)
> bdev->common.device_terminate_all = bam_dma_terminate_all;
> bdev->common.device_issue_pending = bam_issue_pending;
> bdev->common.device_tx_status = bam_tx_status;
> + bdev->common.desc_metadata_modes = DESC_METADATA_CLIENT;
> bdev->common.dev = bdev->dev;
>
> ret = dma_async_device_register(&bdev->common);
> diff --git a/include/linux/dma/qcom_bam_dma.h b/include/linux/dma/qcom_bam_dma.h
> index 68fc0e643b1b97fe4520d5878daa322b81f4f559..f85e0c72407b5e1a733750ac87bbaba6af6e8c78 100644
> --- a/include/linux/dma/qcom_bam_dma.h
> +++ b/include/linux/dma/qcom_bam_dma.h
> @@ -34,6 +34,10 @@ enum bam_command_type {
> BAM_READ_COMMAND,
> };
>
> +struct bam_desc_metadata {
> + phys_addr_t scratchpad_addr;
I think it'd be worth adding a comment for this.
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply
* Re: [PATCH v4 18/21] drivers: hv: vmbus: replace deprecated mmap hook with mmap_prepare
From: Lorenzo Stoakes (Oracle) @ 2026-03-23 9:13 UTC (permalink / raw)
To: Michael Kelley
Cc: Long Li, Andrew Morton, Jonathan Corbet, Clemens Ladisch,
Arnd Bergmann, Greg Kroah-Hartman, K . Y . Srinivasan,
Haiyang Zhang, Wei Liu, Dexuan Cui, Alexander Shishkin,
Maxime Coquelin, Alexandre Torgue, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Bodo Stroesser,
Martin K . Petersen, David Howells, Marc Dionne, Alexander Viro,
Christian Brauner, Jan Kara, David Hildenbrand, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, linux-kernel@vger.kernel.org,
linux-doc@vger.kernel.org, linux-hyperv@vger.kernel.org,
linux-stm32@st-md-mailman.stormreply.com,
linux-arm-kernel@lists.infradead.org,
linux-mtd@lists.infradead.org, linux-staging@lists.linux.dev,
linux-scsi@vger.kernel.org, target-devel@vger.kernel.org,
linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org,
linux-mm@kvack.org, Ryan Roberts
In-Reply-To: <SN6PR02MB41573DF211DA2469D7FFE892D44BA@SN6PR02MB4157.namprd02.prod.outlook.com>
On Mon, Mar 23, 2026 at 04:16:20AM +0000, Michael Kelley wrote:
> From: Lorenzo Stoakes (Oracle) <ljs@kernel.org> Sent: Friday, March 20, 2026 3:40 PM
> >
> > The f_op->mmap interface is deprecated, so update the vmbus driver to use
> > its successor, mmap_prepare.
> >
> > This updates all callbacks which referenced the function pointer
> > hv_mmap_ring_buffer to instead reference hv_mmap_prepare_ring_buffer,
> > utilising the newly introduced compat_set_desc_from_vma() and
> > __compat_vma_mmap() to be able to implement this change.
> >
> > The UIO HV generic driver is the only user of hv_create_ring_sysfs(),
> > which is the only function which references
> > vmbus_channel->mmap_prepare_ring_buffer which, in turn, is the only
> > external interface to hv_mmap_prepare_ring_buffer.
> >
> > This patch therefore updates this caller to use mmap_prepare instead,
> > which also previously used vm_iomap_memory(), so this change replaces it
> > with its mmap_prepare equivalent, mmap_action_simple_ioremap().
> >
> > Signed-off-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
> > ---
> > drivers/hv/hyperv_vmbus.h | 4 ++--
> > drivers/hv/vmbus_drv.c | 31 +++++++++++++++++++------------
> > drivers/uio/uio_hv_generic.c | 11 ++++++-----
> > include/linux/hyperv.h | 4 ++--
> > 4 files changed, 29 insertions(+), 21 deletions(-)
> >
>
> There are two mmap() code paths in the Hyper-V UIO code. One path is
> to mmap() the file descriptor for /dev/uio<n>, and the other is to mmap()
> the "ring" entry under /sys/devices/vmbus/devices/<uuid>. The former is
> done by uio_mmap(), and the latter by hv_uio_ring_mmap_prepare().
>
> I tested both these paths using a combination of two methods in a
> x86/x64 VM on Hyper-V:
>
> 1) Using the fcopy daemon, which maps the ring buffer for the primary
> channel and sends/receives messages with the Hyper-V host. This
> method tests only the 1st path because the fcopy daemon doesn't create
> any subchannels that would use the "ring" entry.
>
> 2) Using a custom-built test program. This program doesn't communicate
> with the Hyper-V host, but allows mostly verifying both code paths for the
> primary channel. As a sanity check, it verifies that the two mmaps are
> mapping the same memory, as expected.
>
> As such,
>
> Reviewed-by: Michael Kelley <mhklinux@outlook.com>
> Tested-by: Michael Kelley <mhklinux@outlook.com>
Perfect, thanks so much for this!
It is tricky for me to test these, beyond fairly exhaustive logical
confirmation of equivalence, so this is _hugely_ helpful.
>
> The most robust test would be to run DPDK networking against
> UIO, as it would communicate with the Hyper-V host and use
> multiple subchannels that resulting in mmap'ing the "ring"
> entry under /sys.
>
> @Long Li -- I'll leave it to your discretion as to whether you want
> to test DPDK against these mmap() changes.
Thanks in advance for taking a look on this also!
>
> I've noted one minor issue below.
>
> [snip]
>
> --- a/include/linux/hyperv.h
> +++ b/include/linux/hyperv.h
> @@ -1015,8 +1015,8 @@ struct vmbus_channel {
> /* The max size of a packet on this channel */
> u32 max_pkt_size;
>
> - /* function to mmap ring buffer memory to the channel's sysfs ring attribute */
> - int (*mmap_ring_buffer)(struct vmbus_channel *channel, struct vm_area_struct *vma);
> + /* function to mmap_prepare ring buffer memory to the channel's sysfs ring attribute */
>
> Changing the comment from "mmap ring buffer" to "mmap_prepare ring buffer"
> produces awkward wording since "mmap" is used here as a verb. It might be better
> to just leave the comment unchanged.
Sure am happy with that of course, I think Sashiko moaned about this but
it's obviously fine either way.
Andrew - do you mind restoring the comment to its original form above? Thanks!
>
> Michael
>
>
> + int (*mmap_prepare_ring_buffer)(struct vmbus_channel *channel, struct vm_area_desc *desc);
>
> /* boolean to control visibility of sysfs for ring buffer */
> bool ring_sysfs_visible;
Cheers, Lorenzo
^ permalink raw reply
* Re: [PATCH v8 2/5] crash: Exclude crash kernel memory in crash core
From: Jinjie Ruan @ 2026-03-23 9:12 UTC (permalink / raw)
To: corbet, skhan, catalin.marinas, will, chenhuacai, kernel, maddy,
mpe, npiggin, chleroy, pjw, palmer, aou, alex, tglx, mingo, bp,
dave.hansen, hpa, robh, saravanak, akpm, bhe, vgoyal, dyoung,
rdunlap, pmladek, dapeng1.mi, kees, paulmck, lirongqing, fvdl,
rppt, ardb, leitao, sourabhjain, jbohac, cfsworks, osandov,
tangyouling, ritesh.list, hbathini, eajames, songshuaishuai,
kevin.brodsky, samuel.holland, vishal.moola, junhui.liu, coxu,
liaoyuanhong, fuqiang.wang, brgerst, x86, linux-doc, linux-kernel,
linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv,
devicetree, kexec
In-Reply-To: <20260302035315.3892241-3-ruanjinjie@huawei.com>
On 2026/3/2 11:53, Jinjie Ruan wrote:
> The crash memory alloc, and the exclude of crashk_res, crashk_low_res
> and crashk_cma memory are almost identical across different architectures,
> handling them in the crash core would eliminate a lot of duplication, so
> do them in the common code.
>
> To achieve the above goal, three architecture-specific functions are
> introduced:
>
> - arch_get_system_nr_ranges(). Pre-counts the max number of memory ranges.
>
> - arch_crash_populate_cmem(). Collects the memory ranges and fills them
> into cmem.
>
> - arch_crash_exclude_ranges(). Architecture's additional crash memory
> ranges exclusion, defaulting to empty.
>
> Acked-by: Baoquan He <bhe@redhat.com>
> Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
> ---
> arch/arm64/kernel/machine_kexec_file.c | 39 +++-------
> arch/loongarch/kernel/machine_kexec_file.c | 39 +++-------
> arch/riscv/kernel/machine_kexec_file.c | 38 +++------
> arch/x86/kernel/crash.c | 89 +++-------------------
> include/linux/crash_core.h | 5 ++
> kernel/crash_core.c | 82 +++++++++++++++++++-
> 6 files changed, 132 insertions(+), 160 deletions(-)
>
> diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
> index fba260ad87a9..c338506a580b 100644
> --- a/arch/arm64/kernel/machine_kexec_file.c
> +++ b/arch/arm64/kernel/machine_kexec_file.c
> @@ -40,23 +40,23 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
> }
>
> #ifdef CONFIG_CRASH_DUMP
> -static int prepare_elf_headers(void **addr, unsigned long *sz)
> +unsigned int arch_get_system_nr_ranges(void)
> {
> - struct crash_mem *cmem;
> - unsigned int nr_ranges;
> - int ret;
> - u64 i;
> + unsigned int nr_ranges = 2; /* for exclusion of crashkernel region */
> phys_addr_t start, end;
> + u64 i;
>
> - nr_ranges = 2; /* for exclusion of crashkernel region */
> for_each_mem_range(i, &start, &end)
> nr_ranges++;
>
> - cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
> - if (!cmem)
> - return -ENOMEM;
> + return nr_ranges;
> +}
> +
> +int arch_crash_populate_cmem(struct crash_mem *cmem)
> +{
> + phys_addr_t start, end;
> + u64 i;
>
> - cmem->max_nr_ranges = nr_ranges;
> cmem->nr_ranges = 0;
> for_each_mem_range(i, &start, &end) {
> cmem->ranges[cmem->nr_ranges].start = start;
> @@ -64,22 +64,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
> cmem->nr_ranges++;
> }
>
> - /* Exclude crashkernel region */
> - ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
> - if (ret)
> - goto out;
> -
> - if (crashk_low_res.end) {
> - ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
> - if (ret)
> - goto out;
> - }
> -
> - ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
> -
> -out:
> - kfree(cmem);
> - return ret;
> + return 0;
> }
> #endif
>
> @@ -109,7 +94,7 @@ int load_other_segments(struct kimage *image,
> void *headers;
> unsigned long headers_sz;
> if (image->type == KEXEC_TYPE_CRASH) {
> - ret = prepare_elf_headers(&headers, &headers_sz);
> + ret = crash_prepare_headers(true, &headers, &headers_sz, NULL);
> if (ret) {
> pr_err("Preparing elf core header failed\n");
> goto out_err;
> diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
> index 5584b798ba46..4b318a94b564 100644
> --- a/arch/loongarch/kernel/machine_kexec_file.c
> +++ b/arch/loongarch/kernel/machine_kexec_file.c
> @@ -56,23 +56,23 @@ static void cmdline_add_initrd(struct kimage *image, unsigned long *cmdline_tmpl
> }
>
> #ifdef CONFIG_CRASH_DUMP
> -
> -static int prepare_elf_headers(void **addr, unsigned long *sz)
> +unsigned int arch_get_system_nr_ranges(void)
> {
> - int ret, nr_ranges;
> - uint64_t i;
> + int nr_ranges = 2; /* for exclusion of crashkernel region */
> phys_addr_t start, end;
> - struct crash_mem *cmem;
> + uint64_t i;
>
> - nr_ranges = 2; /* for exclusion of crashkernel region */
> for_each_mem_range(i, &start, &end)
> nr_ranges++;
>
> - cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
> - if (!cmem)
> - return -ENOMEM;
> + return nr_ranges;
> +}
> +
> +int arch_crash_populate_cmem(struct crash_mem *cmem)
> +{
> + phys_addr_t start, end;
> + uint64_t i;
>
> - cmem->max_nr_ranges = nr_ranges;
> cmem->nr_ranges = 0;
> for_each_mem_range(i, &start, &end) {
> cmem->ranges[cmem->nr_ranges].start = start;
> @@ -80,22 +80,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
> cmem->nr_ranges++;
> }
>
> - /* Exclude crashkernel region */
> - ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
> - if (ret < 0)
> - goto out;
> -
> - if (crashk_low_res.end) {
> - ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
> - if (ret < 0)
> - goto out;
> - }
> -
> - ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
> -
> -out:
> - kfree(cmem);
> - return ret;
> + return 0;
> }
>
> /*
> @@ -163,7 +148,7 @@ int load_other_segments(struct kimage *image,
> void *headers;
> unsigned long headers_sz;
>
> - ret = prepare_elf_headers(&headers, &headers_sz);
> + ret = crash_prepare_headers(true, &headers, &headers_sz, NULL);
> if (ret < 0) {
> pr_err("Preparing elf core header failed\n");
> goto out_err;
> diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
> index 54e2d9552e93..d0e331d87155 100644
> --- a/arch/riscv/kernel/machine_kexec_file.c
> +++ b/arch/riscv/kernel/machine_kexec_file.c
> @@ -44,6 +44,15 @@ static int get_nr_ram_ranges_callback(struct resource *res, void *arg)
> return 0;
> }
>
> +unsigned int arch_get_system_nr_ranges(void)
> +{
> + unsigned int nr_ranges = 1; /* For exclusion of crashkernel region */
> +
> + walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
> +
> + return nr_ranges;
> +}
> +
> static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
> {
> struct crash_mem *cmem = arg;
> @@ -55,33 +64,10 @@ static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
> return 0;
> }
>
> -static int prepare_elf_headers(void **addr, unsigned long *sz)
> +int arch_crash_populate_cmem(struct crash_mem *cmem)
> {
> - struct crash_mem *cmem;
> - unsigned int nr_ranges;
> - int ret;
> -
> - nr_ranges = 1; /* For exclusion of crashkernel region */
> - walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
> -
> - cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
> - if (!cmem)
> - return -ENOMEM;
> -
> - cmem->max_nr_ranges = nr_ranges;
> cmem->nr_ranges = 0;
> - ret = walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
> - if (ret)
> - goto out;
Hi, Baoquan
While using AI tools to assist in the review, I noticed the following
issues in the RISC-V implementation:
riscv supports crashk_low_res in commit 5882e5acf18d ("riscv: kdump:
Implement crashkernel=X,[high,low]"), so it should have excluded the
"crashk_low_res" reserved ranges from the crash kernel memory to prevent
them from being exported through /proc/vmcore, and the exclusion would
need an extra crash_mem range as below.
Should I include a fix for this in this patch set?
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -61,7 +61,7 @@ static int prepare_elf_headers(void **addr, unsigned
long *sz)
unsigned int nr_ranges;
int ret;
- nr_ranges = 1; /* For exclusion of crashkernel region */
+ nr_ranges = 2; /* For exclusion of crashkernel region */
walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
@@ -76,8 +76,16 @@ static int prepare_elf_headers(void **addr, unsigned
long *sz)
/* Exclude crashkernel region */
ret = crash_exclude_mem_range(cmem, crashk_res.start,
crashk_res.end);
- if (!ret)
- ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
+ if (ret)
+ goto out;
+
+ if (crashk_low_res.end) {
+ ret = crash_exclude_mem_range(cmem,
crashk_low_res.start, crashk_low_res.end);
+ if (ret)
+ goto out;
+ }
+
+ ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
out:
kfree(cmem);
> -
> - /* Exclude crashkernel region */
> - ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
> - if (!ret)
> - ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
> -
> -out:
> - kfree(cmem);
> - return ret;
> + return walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
> }
>
> static char *setup_kdump_cmdline(struct kimage *image, char *cmdline,
> @@ -273,7 +259,7 @@ int load_extra_segments(struct kimage *image, unsigned long kernel_start,
> if (image->type == KEXEC_TYPE_CRASH) {
> void *headers;
> unsigned long headers_sz;
> - ret = prepare_elf_headers(&headers, &headers_sz);
> + ret = crash_prepare_headers(true, &headers, &headers_sz, NULL);
> if (ret) {
> pr_err("Preparing elf core header failed\n");
> goto out;
> diff --git a/arch/x86/kernel/crash.c b/arch/x86/kernel/crash.c
> index 335fd2ee9766..3ad3f8b758a4 100644
> --- a/arch/x86/kernel/crash.c
> +++ b/arch/x86/kernel/crash.c
> @@ -152,16 +152,8 @@ static int get_nr_ram_ranges_callback(struct resource *res, void *arg)
> return 0;
> }
>
> -/* Gather all the required information to prepare elf headers for ram regions */
> -static struct crash_mem *fill_up_crash_elf_data(void)
> +unsigned int arch_get_system_nr_ranges(void)
> {
> - unsigned int nr_ranges = 0;
> - struct crash_mem *cmem;
> -
> - walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
> - if (!nr_ranges)
> - return NULL;
> -
> /*
> * Exclusion of crash region, crashk_low_res and/or crashk_cma_ranges
> * may cause range splits. So add extra slots here.
> @@ -176,49 +168,16 @@ static struct crash_mem *fill_up_crash_elf_data(void)
> * But in order to lest the low 1M could be changed in the future,
> * (e.g. [start, 1M]), add a extra slot.
> */
> - nr_ranges += 3 + crashk_cma_cnt;
> - cmem = vzalloc(struct_size(cmem, ranges, nr_ranges));
> - if (!cmem)
> - return NULL;
> -
> - cmem->max_nr_ranges = nr_ranges;
> + unsigned int nr_ranges = 3 + crashk_cma_cnt;
>
> - return cmem;
> + walk_system_ram_res(0, -1, &nr_ranges, get_nr_ram_ranges_callback);
> + return nr_ranges;
> }
>
> -/*
> - * Look for any unwanted ranges between mstart, mend and remove them. This
> - * might lead to split and split ranges are put in cmem->ranges[] array
> - */
> -static int elf_header_exclude_ranges(struct crash_mem *cmem)
> +int arch_crash_exclude_ranges(struct crash_mem *cmem)
> {
> - int ret = 0;
> - int i;
> -
> /* Exclude the low 1M because it is always reserved */
> - ret = crash_exclude_mem_range(cmem, 0, SZ_1M - 1);
> - if (ret)
> - return ret;
> -
> - /* Exclude crashkernel region */
> - ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
> - if (ret)
> - return ret;
> -
> - if (crashk_low_res.end)
> - ret = crash_exclude_mem_range(cmem, crashk_low_res.start,
> - crashk_low_res.end);
> - if (ret)
> - return ret;
> -
> - for (i = 0; i < crashk_cma_cnt; ++i) {
> - ret = crash_exclude_mem_range(cmem, crashk_cma_ranges[i].start,
> - crashk_cma_ranges[i].end);
> - if (ret)
> - return ret;
> - }
> -
> - return 0;
> + return crash_exclude_mem_range(cmem, 0, SZ_1M - 1);
> }
>
> static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
> @@ -232,35 +191,9 @@ static int prepare_elf64_ram_headers_callback(struct resource *res, void *arg)
> return 0;
> }
>
> -/* Prepare elf headers. Return addr and size */
> -static int prepare_elf_headers(void **addr, unsigned long *sz,
> - unsigned long *nr_mem_ranges)
> +int arch_crash_populate_cmem(struct crash_mem *cmem)
> {
> - struct crash_mem *cmem;
> - int ret;
> -
> - cmem = fill_up_crash_elf_data();
> - if (!cmem)
> - return -ENOMEM;
> -
> - ret = walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
> - if (ret)
> - goto out;
> -
> - /* Exclude unwanted mem ranges */
> - ret = elf_header_exclude_ranges(cmem);
> - if (ret)
> - goto out;
> -
> - /* Return the computed number of memory ranges, for hotplug usage */
> - *nr_mem_ranges = cmem->nr_ranges;
> -
> - /* By default prepare 64bit headers */
> - ret = crash_prepare_elf64_headers(cmem, IS_ENABLED(CONFIG_X86_64), addr, sz);
> -
> -out:
> - vfree(cmem);
> - return ret;
> + return walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
> }
> #endif
>
> @@ -418,7 +351,8 @@ int crash_load_segments(struct kimage *image)
> .buf_max = ULONG_MAX, .top_down = false };
>
> /* Prepare elf headers and add a segment */
> - ret = prepare_elf_headers(&kbuf.buffer, &kbuf.bufsz, &pnum);
> + ret = crash_prepare_headers(IS_ENABLED(CONFIG_X86_64), &kbuf.buffer,
> + &kbuf.bufsz, &pnum);
> if (ret)
> return ret;
>
> @@ -529,7 +463,8 @@ void arch_crash_handle_hotplug_event(struct kimage *image, void *arg)
> * Create the new elfcorehdr reflecting the changes to CPU and/or
> * memory resources.
> */
> - if (prepare_elf_headers(&elfbuf, &elfsz, &nr_mem_ranges)) {
> + if (crash_prepare_headers(IS_ENABLED(CONFIG_X86_64), &elfbuf, &elfsz,
> + &nr_mem_ranges)) {
> pr_err("unable to create new elfcorehdr");
> goto out;
> }
> diff --git a/include/linux/crash_core.h b/include/linux/crash_core.h
> index d35726d6a415..033b20204aca 100644
> --- a/include/linux/crash_core.h
> +++ b/include/linux/crash_core.h
> @@ -66,6 +66,8 @@ extern int crash_exclude_mem_range(struct crash_mem *mem,
> unsigned long long mend);
> extern int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_map,
> void **addr, unsigned long *sz);
> +extern int crash_prepare_headers(int need_kernel_map, void **addr,
> + unsigned long *sz, unsigned long *nr_mem_ranges);
>
> struct kimage;
> struct kexec_segment;
> @@ -83,6 +85,9 @@ int kexec_should_crash(struct task_struct *p);
> int kexec_crash_loaded(void);
> void crash_save_cpu(struct pt_regs *regs, int cpu);
> extern int kimage_crash_copy_vmcoreinfo(struct kimage *image);
> +extern unsigned int arch_get_system_nr_ranges(void);
> +extern int arch_crash_populate_cmem(struct crash_mem *cmem);
> +extern int arch_crash_exclude_ranges(struct crash_mem *cmem);
>
> #else /* !CONFIG_CRASH_DUMP*/
> struct pt_regs;
> diff --git a/kernel/crash_core.c b/kernel/crash_core.c
> index 2c1a3791e410..96a96e511f5a 100644
> --- a/kernel/crash_core.c
> +++ b/kernel/crash_core.c
> @@ -170,9 +170,6 @@ static inline resource_size_t crash_resource_size(const struct resource *res)
> return !res->end ? 0 : resource_size(res);
> }
>
> -
> -
> -
> int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_map,
> void **addr, unsigned long *sz)
> {
> @@ -274,6 +271,85 @@ int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_map,
> return 0;
> }
>
> +static struct crash_mem *alloc_cmem(unsigned int nr_ranges)
> +{
> + struct crash_mem *cmem;
> +
> + cmem = kvzalloc_flex(*cmem, ranges, nr_ranges);
> + if (!cmem)
> + return NULL;
> +
> + cmem->max_nr_ranges = nr_ranges;
> + return cmem;
> +}
> +
> +unsigned int __weak arch_get_system_nr_ranges(void) { return 0; }
> +int __weak arch_crash_populate_cmem(struct crash_mem *cmem) { return -1; }
> +int __weak arch_crash_exclude_ranges(struct crash_mem *cmem) { return 0; }
> +
> +static int crash_exclude_core_ranges(struct crash_mem *cmem)
> +{
> + int ret, i;
> +
> + /* Exclude crashkernel region */
> + ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
> + if (ret)
> + return ret;
> +
> + if (crashk_low_res.end) {
> + ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
> + if (ret)
> + return ret;
> + }
> +
> + for (i = 0; i < crashk_cma_cnt; ++i) {
> + ret = crash_exclude_mem_range(cmem, crashk_cma_ranges[i].start,
> + crashk_cma_ranges[i].end);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +int crash_prepare_headers(int need_kernel_map, void **addr, unsigned long *sz,
> + unsigned long *nr_mem_ranges)
> +{
> + unsigned int max_nr_ranges;
> + struct crash_mem *cmem;
> + int ret;
> +
> + max_nr_ranges = arch_get_system_nr_ranges();
> + if (!max_nr_ranges)
> + return -ENOMEM;
> +
> + cmem = alloc_cmem(max_nr_ranges);
> + if (!cmem)
> + return -ENOMEM;
> +
> + ret = arch_crash_populate_cmem(cmem);
> + if (ret)
> + goto out;
> +
> + ret = crash_exclude_core_ranges(cmem);
> + if (ret)
> + goto out;
> +
> + ret = arch_crash_exclude_ranges(cmem);
> + if (ret)
> + goto out;
> +
> + /* Return the computed number of memory ranges, for hotplug usage */
> + if (nr_mem_ranges)
> + *nr_mem_ranges = cmem->nr_ranges;
> +
> + ret = crash_prepare_elf64_headers(cmem, need_kernel_map, addr, sz);
> +
> +out:
> + kvfree(cmem);
> + return ret;
> +}
> +
> /**
> * crash_exclude_mem_range - exclude a mem range for existing ranges
> * @mem: mem->range contains an array of ranges sorted in ascending order
^ permalink raw reply
* [PATCH 05/10] docs: kdoc-test.yaml: add more tests
From: Mauro Carvalho Chehab @ 2026-03-23 9:10 UTC (permalink / raw)
To: Jonathan Corbet, Linux Doc Mailing List, Mauro Carvalho Chehab
Cc: Mauro Carvalho Chehab, linux-kernel, Randy Dunlap
In-Reply-To: <cover.1774256269.git.mchehab+huawei@kernel.org>
Add extra tests to check if the new "var" type is properly
handled and to cover mutex context annotations.
Co-developed-by: Randy Dunlap <rdunlap@infradead.org>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
tools/unittests/kdoc-test.yaml | 1548 +++++++++++++++++++++++++++++++-
1 file changed, 1546 insertions(+), 2 deletions(-)
diff --git a/tools/unittests/kdoc-test.yaml b/tools/unittests/kdoc-test.yaml
index b6e04f10ccdb..14d36daa1bba 100644
--- a/tools/unittests/kdoc-test.yaml
+++ b/tools/unittests/kdoc-test.yaml
@@ -22,7 +22,6 @@ tests:
*/
int func1(char *arg1) { return 0; };
-
expected:
- rst: |
.. c:function:: int func1 (char *arg1)
@@ -134,7 +133,6 @@ tests:
always return 0.
- # TODO: how to handle timestamps at .TH?
man: |
.TH "func2" 9 "February 2026" "" "Kernel API Manual"
.SH NAME
@@ -152,3 +150,1549 @@ tests:
.SH "SEE ALSO"
.PP
Kernel file \fBfunc2.c\fR
+
+- name: doc_with_complex_table
+ description: Test if complex tables are handled
+ fname: mock.c
+ source: |
+ /**
+ * DOC: Supported input formats and encodings
+ *
+ * Depending on the Hardware configuration of the Controller IP, it supports
+ * a subset of the following input formats and encodings on its internal
+ * 48bit bus.
+ *
+ * +----------------------+----------------------------------+------------------------------+
+ * | Format Name | Format Code | Encodings |
+ * +======================+==================================+==============================+
+ * | RGB 4:4:4 8bit | ``MEDIA_BUS_FMT_RGB888_1X24`` | ``V4L2_YCBCR_ENC_DEFAULT`` |
+ * +----------------------+----------------------------------+------------------------------+
+ * | RGB 4:4:4 10bits | ``MEDIA_BUS_FMT_RGB101010_1X30`` | ``V4L2_YCBCR_ENC_DEFAULT`` |
+ * +----------------------+----------------------------------+------------------------------+
+ */
+ expected:
+ - man: |
+ .TH "Supported input formats and encodings" 9 "March 2026" "" "Kernel API Manual"
+ .SH "Supported input formats and encodings"
+ Depending on the Hardware configuration of the Controller IP, it supports
+ a subset of the following input formats and encodings on its internal
+ 48bit bus.
+ .PP
+
+
+ .TS
+ box;
+ l l l.
+ \fBFormat Name\fP \fBFormat Code\fP \fBEncodings\fP
+ _
+ RGB 4:4:4 8bit ``MEDIA_BUS_FMT_RGB888_1X24 V4L2_YCBCR_ENC_DEFAULT
+ RGB 4:4:4 10bits MEDIA_BUS_FMT_RGB101010_1X30 V4L2_YCBCR_ENC_DEFAULT``
+ .TE
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock.c\fR
+
+ rst: |-
+ .. _Supported input formats and encodings:
+ **Supported input formats and encodings**
+ Depending on the Hardware configuration of the Controller IP, it supports
+ a subset of the following input formats and encodings on its internal
+ 48bit bus.
+ +----------------------+----------------------------------+------------------------------+
+ | Format Name | Format Code | Encodings |
+ +======================+==================================+==============================+
+ | RGB 4:4:4 8bit | ``MEDIA_BUS_FMT_RGB888_1X24`` | ``V4L2_YCBCR_ENC_DEFAULT`` |
+ +----------------------+----------------------------------+------------------------------+
+ | RGB 4:4:4 10bits | ``MEDIA_BUS_FMT_RGB101010_1X30`` | ``V4L2_YCBCR_ENC_DEFAULT`` |
+ +----------------------+----------------------------------+------------------------------+
+- name: func_with_ascii_artwork
+ description: Test if ascii artwork is properly output
+ fname: mock.c
+ source: |
+ /**
+ * add_cxl_resources() - reflect CXL fixed memory windows in iomem_resource
+ * @cxl_res: A standalone resource tree where each CXL window is a sibling
+ *
+ * Walk each CXL window in @cxl_res and add it to iomem_resource potentially
+ * expanding its boundaries to ensure that any conflicting resources become
+ * children. If a window is expanded it may then conflict with a another window
+ * entry and require the window to be truncated or trimmed. Consider this
+ * situation::
+ *
+ * |-- "CXL Window 0" --||----- "CXL Window 1" -----|
+ * |--------------- "System RAM" -------------|
+ *
+ * ...where platform firmware has established as System RAM resource across 2
+ * windows, but has left some portion of window 1 for dynamic CXL region
+ * provisioning. In this case "Window 0" will span the entirety of the "System
+ * RAM" span, and "CXL Window 1" is truncated to the remaining tail past the end
+ * of that "System RAM" resource.
+ */
+ static int add_cxl_resources(struct resource *cxl_res);
+ expected:
+ - man: |-
+ .TH "add_cxl_resources" 9 "March 2026" "" "Kernel API Manual"
+ .SH NAME
+ add_cxl_resources \- reflect CXL fixed memory windows in iomem_resource
+ .SH SYNOPSIS
+ .B "int" add_cxl_resources
+ .BI "(struct resource *cxl_res " ");"
+ .SH ARGUMENTS
+ .IP "cxl_res" 12
+ A standalone resource tree where each CXL window is a sibling
+ .SH "DESCRIPTION"
+ Walk each CXL window in \fIcxl_res\fP and add it to iomem_resource potentially
+ expanding its boundaries to ensure that any conflicting resources become
+ children. If a window is expanded it may then conflict with a another window
+ entry and require the window to be truncated or trimmed. Consider this
+ situation:
+ .nf
+
+ |-- "CXL Window 0" --||----- "CXL Window 1" -----|
+ |--------------- "System RAM" -------------|
+
+
+ .fi
+ .PP
+
+ \&...where platform firmware has established as System RAM resource across 2
+ windows, but has left some portion of window 1 for dynamic CXL region
+ provisioning. In this case "Window 0" will span the entirety of the "System
+ RAM" span, and "CXL Window 1" is truncated to the remaining tail past the end
+ of that "System RAM" resource.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock.c\fR
+ rst: |
+ .. c:function:: int add_cxl_resources (struct resource *cxl_res)
+
+ reflect CXL fixed memory windows in iomem_resource
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct resource *cxl_res``
+ A standalone resource tree where each CXL window is a sibling
+
+ **Description**
+
+ Walk each CXL window in **cxl_res** and add it to iomem_resource potentially
+ expanding its boundaries to ensure that any conflicting resources become
+ children. If a window is expanded it may then conflict with a another window
+ entry and require the window to be truncated or trimmed. Consider this
+ situation::
+
+ |-- "CXL Window 0" --||----- "CXL Window 1" -----|
+ |--------------- "System RAM" -------------|
+
+ ...where platform firmware has established as System RAM resource across 2
+ windows, but has left some portion of window 1 for dynamic CXL region
+ provisioning. In this case "Window 0" will span the entirety of the "System
+ RAM" span, and "CXL Window 1" is truncated to the remaining tail past the end
+ of that "System RAM" resource.
+
+- name: simple_tables
+ description: Test formatting two simple tables
+ fname: mock.c
+ source: |
+ /**
+ * bitmap_onto - translate one bitmap relative to another
+ * @dst: resulting translated bitmap
+ * @orig: original untranslated bitmap
+ * @relmap: bitmap relative to which translated
+ * @bits: number of bits in each of these bitmaps
+ *
+ * =============== ============== =================
+ * @orig tmp @dst
+ * 0 0 40
+ * 1 1 41
+ * =============== ============== =================
+ *
+ * And:
+ *
+ * =============== ============== =================
+ * @orig tmp @dst
+ * =============== ============== =================
+ * 9 9 95
+ * 10 0 40 [#f1]_
+ * =============== ============== =================
+ */
+ void bitmap_onto(unsigned long *dst, const unsigned long *orig,
+ const unsigned long *relmap, unsigned int bits);
+ expected:
+ - man: |
+ .TH "bitmap_onto" 9 "March 2026" "" "Kernel API Manual"
+ .SH NAME
+ bitmap_onto \- translate one bitmap relative to another
+ .SH SYNOPSIS
+ .B "void" bitmap_onto
+ .BI "(unsigned long *dst " ","
+ .BI "const unsigned long *orig " ","
+ .BI "const unsigned long *relmap " ","
+ .BI "unsigned int bits " ");"
+ .SH ARGUMENTS
+ .IP "dst" 12
+ resulting translated bitmap
+ .IP "orig" 12
+ original untranslated bitmap
+ .IP "relmap" 12
+ bitmap relative to which translated
+ .IP "bits" 12
+ number of bits in each of these bitmaps
+ .SH "DESCRIPTION"
+
+ .TS
+ box;
+ l l l.
+ \fIorig\fP tmp \fIdst\fP
+ 0 0 40
+ 1 1 41
+ .TE
+ .PP
+
+ And:
+ .PP
+
+
+ .TS
+ box;
+ l l l.
+ \fIorig\fP tmp \fIdst\fP
+ .TE
+ 9 9 95
+ 10 0 40 [#f1]_
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock.c\fR
+
+ rst: |
+ .. c:function:: void bitmap_onto (unsigned long *dst, const unsigned long *orig, const unsigned long *relmap, unsigned int bits)
+
+ translate one bitmap relative to another
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``unsigned long *dst``
+ resulting translated bitmap
+
+ ``const unsigned long *orig``
+ original untranslated bitmap
+
+ ``const unsigned long *relmap``
+ bitmap relative to which translated
+
+ ``unsigned int bits``
+ number of bits in each of these bitmaps
+
+ **Description**
+
+ =============== ============== =================
+ **orig** tmp **dst**
+ 0 0 40
+ 1 1 41
+ =============== ============== =================
+
+ And:
+
+ =============== ============== =================
+ **orig** tmp **dst**
+ =============== ============== =================
+ 9 9 95
+ 10 0 40 [#f1]_
+ =============== ============== =================
+
+#
+# Variable tests from Randy Dunlap's testset
+#
+- name: unsigned_long_var_on_uppercase
+ description: Test an unsigned long varaible in uppercase
+ fname: mock-vars.c
+ source: |
+ /**
+ * var ROOT_DEV - system root device
+ *
+ * @ROOT_DEV is either the successful root device or the root device
+ * that failed boot in the boot failure message.
+ */
+ unsigned long ROOT_DEV;
+ expected:
+ - man: |
+ .TH "var ROOT_DEV" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ ROOT_DEV \- system root device
+ .SH SYNOPSIS
+ unsigned long ROOT_DEV;
+ .SH "Description"
+ \fIROOT_DEV\fP is either the successful root device or the root device
+ that failed boot in the boot failure message.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: ROOT_DEV
+
+ ``unsigned long ROOT_DEV;``
+
+ system root device
+
+ **Description**
+
+ **ROOT_DEV** is either the successful root device or the root device
+ that failed boot in the boot failure message.
+- name: enum_var
+ description: Test an enum var with __read_mostly
+ fname: mock-vars.c
+ source: |
+ /**
+ * var system_state - system state used during boot or suspend/hibernate/resume
+ *
+ * @system_state can be used during boot to determine if it is safe to
+ * make certain calls to other parts of the kernel. It can also be used
+ * during suspend/hibernate or resume to determine the order of actions
+ * that need to be executed. The numerical values of system_state are
+ * sometimes used in numerical ordering tests, so the relative values
+ * must not be altered.
+ */
+ enum system_states system_state __read_mostly;
+ expected:
+ - man: |
+ .TH "var system_state" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ system_state \- system state used during boot or suspend/hibernate/resume
+ .SH SYNOPSIS
+ enum system_states system_state __read_mostly;
+ .SH "Description"
+ \fIsystem_state\fP can be used during boot to determine if it is safe to
+ make certain calls to other parts of the kernel. It can also be used
+ during suspend/hibernate or resume to determine the order of actions
+ that need to be executed. The numerical values of system_state are
+ sometimes used in numerical ordering tests, so the relative values
+ must not be altered.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: system_state
+
+ ``enum system_states system_state __read_mostly;``
+
+ system state used during boot or suspend/hibernate/resume
+
+ **Description**
+
+ **system_state** can be used during boot to determine if it is safe to
+ make certain calls to other parts of the kernel. It can also be used
+ during suspend/hibernate or resume to determine the order of actions
+ that need to be executed. The numerical values of system_state are
+ sometimes used in numerical ordering tests, so the relative values
+ must not be altered.
+- name: char_pointer_var
+ description: Test char * var with __ro_after_init
+ fname: mock-vars.c
+ source: |
+ /**
+ * var saved_command_line - kernel's command line, saved from use at
+ * any later time in the kernel.
+ */
+ char *saved_command_line __ro_after_init;
+ expected:
+ - man: |
+ .TH "var saved_command_line" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ saved_command_line \- kernel's command line, saved from use at any later time in the kernel.
+ .SH SYNOPSIS
+ char *saved_command_line __ro_after_init;
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: saved_command_line
+
+ ``char *saved_command_line __ro_after_init;``
+
+ kernel's command line, saved from use at any later time in the kernel.
+- name: unsigned_long_with_default
+ description: Test an unsigned long var that is set to a default value
+ fname: mock-vars.c
+ source: |
+ /**
+ * var loop_per_jiffy - calculated loop count needed to consume one jiffy
+ * of time
+ */
+ unsigned long loops_per_jiffy = (1<<12);
+ expected:
+ - man: |
+ .TH "var loops_per_jiffy" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ loops_per_jiffy \- calculated loop count needed to consume one jiffy of time
+ .SH SYNOPSIS
+ unsigned long loops_per_jiffy = (1<<12);
+ .SH "Initialization"
+ default: (1<<12)
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: loops_per_jiffy
+
+ ``unsigned long loops_per_jiffy = (1<<12);``
+
+ calculated loop count needed to consume one jiffy of time
+
+ **Initialization**
+
+ default: ``(1<<12)``
+- name: unsigned_long
+ description: test a simple unsigned long variable.
+ fname: mock-vars.c
+ source: |
+ /**
+ * var preset_lpj - lpj (loops per jiffy) value set from kernel
+ * command line using "lpj=VALUE"
+ *
+ * See Documentation/admin-guide/kernel-parameters.txt ("lpj=") for details.
+ */
+ unsigned long preset_lpj;
+ expected:
+ - man: |
+ .TH "var preset_lpj" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ preset_lpj \- lpj (loops per jiffy) value set from kernel command line using "lpj=VALUE"
+ .SH SYNOPSIS
+ unsigned long preset_lpj;
+ .SH "Description"
+ See Documentation/admin-guide/kernel-parameters.txt ("lpj=") for details.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: preset_lpj
+
+ ``unsigned long preset_lpj;``
+
+ lpj (loops per jiffy) value set from kernel command line using "lpj=VALUE"
+
+ **Description**
+
+ See Documentation/admin-guide/kernel-parameters.txt ("lpj=") for details.
+- name: char_array
+ description: test a char array variable
+ fname: mock-vars.c
+ source: |
+ /**
+ * var linux_proc_banner - text used from /proc/version file
+ *
+ * * first %s is sysname (e.g., "Linux")
+ * * second %s is release
+ * * third %s is version
+ */
+ char linux_proc_banner[];
+ expected:
+ - man: |
+ .TH "var linux_proc_banner" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ linux_proc_banner \- text used from /proc/version file
+ .SH SYNOPSIS
+ char linux_proc_banner[];
+ .SH "Description"
+ .IP \[bu]
+ first s is sysname (e.g., "Linux")
+ .IP \[bu]
+ second s is release
+ .IP \[bu]
+ third s is version
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: linux_proc_banner
+
+ ``char linux_proc_banner[];``
+
+ text used from /proc/version file
+
+ **Description**
+
+ * first ``s`` is sysname (e.g., "Linux")
+ * second ``s`` is release
+ * third ``s`` is version
+- name: const_char_array
+ description: test a const char array variable
+ fname: mock-vars.c
+ source: |
+ /**
+ * var linux_banner - Linux boot banner, usually printed at boot time
+ */
+ const char linux_banner[];
+ expected:
+ - man: |
+ .TH "var linux_banner" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ linux_banner \- Linux boot banner, usually printed at boot time
+ .SH SYNOPSIS
+ const char linux_banner[];
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: linux_banner
+
+ ``const char linux_banner[];``
+
+ Linux boot banner, usually printed at boot time
+- name: static_atomic64_t_var
+ description: test a static atomi64_t variable
+ fname: mock-vars.c
+ source: |
+ /**
+ * var diskseq - unique sequence number for block device instances
+ *
+ * Allows userspace to associate uevents to the lifetime of a device
+ */
+ static atomic64_t diskseq;
+ expected:
+ - man: |
+ .TH "var diskseq" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ diskseq \- unique sequence number for block device instances
+ .SH SYNOPSIS
+ static atomic64_t diskseq;
+ .SH "Description"
+ Allows userspace to associate uevents to the lifetime of a device
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: diskseq
+
+ ``static atomic64_t diskseq;``
+
+ unique sequence number for block device instances
+
+ **Description**
+
+ Allows userspace to associate uevents to the lifetime of a device
+- name: unsigned_long_on_init
+ description: test an unsigned long var at "init" with a different timestamp.
+ fname: init/mock-vars.c
+ source: |
+ /**
+ * var rtnl_mutex - historical global lock for networking control operations.
+ *
+ * @rtnl_mutex is used to serialize rtnetlink requests
+ * and protect all kernel internal data structures related to networking.
+ *
+ * See Documentation/networking/netdevices.rst for details.
+ * Often known as the rtnl_lock, although rtnl_lock is a kernel function.
+ */
+ unsigned long rtnl_mutex;
+ expected:
+ - man: |
+ .TH "var rtnl_mutex" 9 "February 2026" "init" "Kernel API Manual"
+ .SH NAME
+ rtnl_mutex \- historical global lock for networking control operations.
+ .SH SYNOPSIS
+ unsigned long rtnl_mutex;
+ .SH "Description"
+ \fIrtnl_mutex\fP is used to serialize rtnetlink requests
+ and protect all kernel internal data structures related to networking.
+ .PP
+
+ See Documentation/networking/netdevices.rst for details.
+ Often known as the rtnl_lock, although rtnl_lock is a kernel function.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBinit/mock-vars.c\fR
+ rst: |
+ .. c:macro:: rtnl_mutex
+
+ ``unsigned long rtnl_mutex;``
+
+ historical global lock for networking control operations.
+
+ **Description**
+
+ **rtnl_mutex** is used to serialize rtnetlink requests
+ and protect all kernel internal data structures related to networking.
+
+ See Documentation/networking/netdevices.rst for details.
+ Often known as the rtnl_lock, although rtnl_lock is a kernel function.
+
+
+- name: struct_kcov
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * struct kcov - kcov descriptor (one per opened debugfs file).
+ * State transitions of the descriptor:
+ *
+ * - initial state after open()
+ * - then there must be a single ioctl(KCOV_INIT_TRACE) call
+ * - then, mmap() call (several calls are allowed but not useful)
+ * - then, ioctl(KCOV_ENABLE, arg), where arg is
+ * KCOV_TRACE_PC - to trace only the PCs
+ * or
+ * KCOV_TRACE_CMP - to trace only the comparison operands
+ * - then, ioctl(KCOV_DISABLE) to disable the task.
+ *
+ * Enabling/disabling ioctls can be repeated (only one task a time allowed).
+ */
+ struct kcov {
+ /**
+ * @refcount: Reference counter. We keep one for:
+ * - opened file descriptor
+ * - task with enabled coverage (we can't unwire it from another task)
+ * - each code section for remote coverage collection
+ */
+ refcount_t refcount;
+ /**
+ * @lock: The lock protects mode, size, area and t.
+ */
+ spinlock_t lock;
+ /**
+ * @mode: the kcov_mode
+ */
+ enum kcov_mode mode __guarded_by(&lock);
+ /**
+ * @size: Size of arena (in long's).
+ */
+ unsigned int size __guarded_by(&lock);
+ /**
+ * @area: Coverage buffer shared with user space.
+ */
+ void *area __guarded_by(&lock);
+ /**
+ * @t: Task for which we collect coverage, or NULL.
+ */
+ struct task_struct *t __guarded_by(&lock);
+ /**
+ * @remote: Collecting coverage from remote (background) threads.
+ */
+ bool remote;
+ /**
+ * @remote_size: Size of remote area (in long's).
+ */
+ unsigned int remote_size;
+ /**
+ * @sequence: Sequence is incremented each time kcov is reenabled,
+ * used by kcov_remote_stop(), see the comment there.
+ */
+ int sequence;
+ };
+ expected:
+ - man: |
+ .TH "struct kcov" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ struct kcov \- kcov descriptor (one per opened debugfs file). State transitions of the descriptor:
+ .SH SYNOPSIS
+ struct kcov {
+ .br
+ .BI " refcount_t refcount;"
+ .br
+ .BI " spinlock_t lock;"
+ .br
+ .BI " enum kcov_mode mode;"
+ .br
+ .BI " unsigned int size;"
+ .br
+ .BI " void *area;"
+ .br
+ .BI " struct task_struct *t;"
+ .br
+ .BI " bool remote;"
+ .br
+ .BI " unsigned int remote_size;"
+ .br
+ .BI " int sequence;"
+ .br
+ .BI "
+ };
+ .br
+
+ .SH Members
+ .IP "refcount" 12
+ Reference counter. We keep one for:
+ .IP \[bu]
+ opened file descriptor
+ .IP \[bu]
+ task with enabled coverage (we can't unwire it from another task)
+ .IP \[bu]
+ each code section for remote coverage collection
+ .IP "lock" 12
+ The lock protects mode, size, area and t.
+ .IP "mode" 12
+ the kcov_mode
+ .IP "size" 12
+ Size of arena (in long's).
+ .IP "area" 12
+ Coverage buffer shared with user space.
+ .IP "t" 12
+ Task for which we collect coverage, or NULL.
+ .IP "remote" 12
+ Collecting coverage from remote (background) threads.
+ .IP "remote_size" 12
+ Size of remote area (in long's).
+ .IP "sequence" 12
+ Sequence is incremented each time kcov is reenabled,
+ used by \fBkcov_remote_stop\fP, see the comment there.
+ .SH "Description"
+ .IP \[bu]
+ initial state after \fBopen\fP
+ .IP \[bu]
+ then there must be a single ioctl(KCOV_INIT_TRACE) call
+ .IP \[bu]
+ then, \fBmmap\fP call (several calls are allowed but not useful)
+ .IP \[bu]
+ then, ioctl(KCOV_ENABLE, arg), where arg is
+ KCOV_TRACE_PC - to trace only the PCs
+ or
+ KCOV_TRACE_CMP - to trace only the comparison operands
+ .IP \[bu]
+ then, ioctl(KCOV_DISABLE) to disable the task.
+ .PP
+
+ Enabling/disabling ioctls can be repeated (only one task a time allowed).
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:struct:: kcov
+
+ kcov descriptor (one per opened debugfs file). State transitions of the descriptor:
+
+ .. container:: kernelindent
+
+ **Definition**::
+
+ struct kcov {
+ refcount_t refcount;
+ spinlock_t lock;
+ enum kcov_mode mode;
+ unsigned int size;
+ void *area;
+ struct task_struct *t;
+ bool remote;
+ unsigned int remote_size;
+ int sequence;
+ };
+
+ **Members**
+
+ ``refcount``
+ Reference counter. We keep one for:
+ - opened file descriptor
+ - task with enabled coverage (we can't unwire it from another task)
+ - each code section for remote coverage collection
+
+ ``lock``
+ The lock protects mode, size, area and t.
+
+ ``mode``
+ the kcov_mode
+
+ ``size``
+ Size of arena (in long's).
+
+ ``area``
+ Coverage buffer shared with user space.
+
+ ``t``
+ Task for which we collect coverage, or NULL.
+
+ ``remote``
+ Collecting coverage from remote (background) threads.
+
+ ``remote_size``
+ Size of remote area (in long's).
+
+ ``sequence``
+ Sequence is incremented each time kcov is reenabled,
+ used by kcov_remote_stop(), see the comment there.
+
+
+ **Description**
+
+ - initial state after open()
+ - then there must be a single ioctl(KCOV_INIT_TRACE) call
+ - then, mmap() call (several calls are allowed but not useful)
+ - then, ioctl(KCOV_ENABLE, arg), where arg is
+ KCOV_TRACE_PC - to trace only the PCs
+ or
+ KCOV_TRACE_CMP - to trace only the comparison operands
+ - then, ioctl(KCOV_DISABLE) to disable the task.
+
+ Enabling/disabling ioctls can be repeated (only one task a time allowed).
+
+- name: pool_offset
+ description: mock_tests/kdoc-drop-ctx-lock.c line 83
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * var pool_offset - Offset to the unused space in the currently used pool.
+ *
+ */
+ size_t pool_offset __guarded_by(&pool_lock) = DEPOT_POOL_SIZE;
+ expected:
+ - man: |
+ .TH "var pool_offset" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ pool_offset \- Offset to the unused space in the currently used pool.
+ .SH SYNOPSIS
+ size_t pool_offset __guarded_by(&pool_lock) = DEPOT_POOL_SIZE;
+ .SH "Initialization"
+ default: DEPOT_POOL_SIZE
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:macro:: pool_offset
+
+ ``size_t pool_offset __guarded_by(&pool_lock) = DEPOT_POOL_SIZE;``
+
+ Offset to the unused space in the currently used pool.
+
+ **Initialization**
+
+ default: ``DEPOT_POOL_SIZE``
+- name: free_stacks
+ description: mock_tests/kdoc-drop-ctx-lock.c line 88
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * var free_stacks - Freelist of stack records within stack_pools.
+ *
+ */
+ __guarded_by(&pool_lock) LIST_HEAD(free_stacks);
+ expected:
+ - man: |
+ .TH "var free_stacks" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ free_stacks \- Freelist of stack records within stack_pools.
+ .SH SYNOPSIS
+ __guarded_by(&pool_lock) LIST_HEAD(free_stacks);
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:macro:: free_stacks
+
+ ``__guarded_by(&pool_lock) LIST_HEAD(free_stacks);``
+
+ Freelist of stack records within stack_pools.
+- name: stack_pools
+ description: mock_tests/kdoc-drop-ctx-lock.c line 94
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * var stack_pools - Array of memory regions that store stack records.
+ *
+ */
+ void **stack_pools __pt_guarded_by(&pool_lock);
+ expected:
+ - man: |
+ .TH "var stack_pools" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ stack_pools \- Array of memory regions that store stack records.
+ .SH SYNOPSIS
+ void **stack_pools __pt_guarded_by(&pool_lock);
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:macro:: stack_pools
+
+ ``void **stack_pools __pt_guarded_by(&pool_lock);``
+
+ Array of memory regions that store stack records.
+- name: prepare_report_consumer
+ description: mock_tests/kdoc-drop-ctx-lock.c line 103
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * prepare_report_consumer - prepare the report consumer
+ * @flags: flags
+ * @ai: not that AI
+ * @other_info: yes that
+ */
+ bool prepare_report_consumer(unsigned long *flags,
+ const struct access_info *ai,
+ struct other_info *other_info)
+ __cond_acquires(true, &report_lock)
+ {
+ expected:
+ - man: |
+ .TH "prepare_report_consumer" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ prepare_report_consumer \- prepare the report consumer
+ .SH SYNOPSIS
+ .B "bool" prepare_report_consumer
+ .BI "(unsigned long *flags " ","
+ .BI "const struct access_info *ai " ","
+ .BI "struct other_info *other_info " ");"
+ .SH ARGUMENTS
+ .IP "flags" 12
+ flags
+ .IP "ai" 12
+ not that AI
+ .IP "other_info" 12
+ yes that
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: bool prepare_report_consumer (unsigned long *flags, const struct access_info *ai, struct other_info *other_info)
+
+ prepare the report consumer
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``unsigned long *flags``
+ flags
+
+ ``const struct access_info *ai``
+ not that AI
+
+ ``struct other_info *other_info``
+ yes that
+- name: tcp_sigpool_start
+ description: mock_tests/kdoc-drop-ctx-lock.c line 117
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * tcp_sigpool_start - start a tcp message of @id, using @c
+ * @id: TCP message ID
+ * @c: the &tcp_sigpool to use
+ */
+ int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c) __cond_acquires(0, RCU_BH)
+ {
+ expected:
+ - man: |
+ .TH "tcp_sigpool_start" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ tcp_sigpool_start \- start a tcp message of @id, using @c
+ .SH SYNOPSIS
+ .B "int" tcp_sigpool_start
+ .BI "(unsigned int id " ","
+ .BI "struct tcp_sigpool *c " ");"
+ .SH ARGUMENTS
+ .IP "id" 12
+ TCP message ID
+ .IP "c" 12
+ the \fItcp_sigpool\fP to use
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: int tcp_sigpool_start (unsigned int id, struct tcp_sigpool *c)
+
+ start a tcp message of **id**, using **c**
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``unsigned int id``
+ TCP message ID
+
+ ``struct tcp_sigpool *c``
+ the :c:type:`tcp_sigpool` to use
+- name: undo_report_consumer
+ description: mock_tests/kdoc-drop-ctx-lock.c line 129
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * undo_report_consumer - teardown a report consumer
+ * @flags: those flags
+ * @ai: not that AI
+ * @other_info: yes that
+ */
+ bool undo_report_consumer(unsigned long *flags,
+ const struct access_info *ai,
+ struct other_info *other_info)
+ __cond_releases(true, &report_lock)
+ {
+ expected:
+ - man: |
+ .TH "undo_report_consumer" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ undo_report_consumer \- teardown a report consumer
+ .SH SYNOPSIS
+ .B "bool" undo_report_consumer
+ .BI "(unsigned long *flags " ","
+ .BI "const struct access_info *ai " ","
+ .BI "struct other_info *other_info " ");"
+ .SH ARGUMENTS
+ .IP "flags" 12
+ those flags
+ .IP "ai" 12
+ not that AI
+ .IP "other_info" 12
+ yes that
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: bool undo_report_consumer (unsigned long *flags, const struct access_info *ai, struct other_info *other_info)
+
+ teardown a report consumer
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``unsigned long *flags``
+ those flags
+
+ ``const struct access_info *ai``
+ not that AI
+
+ ``struct other_info *other_info``
+ yes that
+- name: debugfs_enter_cancellation
+ description: mock_tests/kdoc-drop-ctx-lock.c line 143
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * debugfs_enter_cancellation - begin a cancellation operation on @file
+ * @file: the target file
+ * @cancellation: the operation to execute
+ */
+ void debugfs_enter_cancellation(struct file *file,
+ struct debugfs_cancellation *cancellation) __acquires(cancellation)
+ { }
+ expected:
+ - man: |
+ .TH "debugfs_enter_cancellation" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ debugfs_enter_cancellation \- begin a cancellation operation on @file
+ .SH SYNOPSIS
+ .B "void" debugfs_enter_cancellation
+ .BI "(struct file *file " ","
+ .BI "struct debugfs_cancellation *cancellation " ");"
+ .SH ARGUMENTS
+ .IP "file" 12
+ the target file
+ .IP "cancellation" 12
+ the operation to execute
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void debugfs_enter_cancellation (struct file *file, struct debugfs_cancellation *cancellation)
+
+ begin a cancellation operation on **file**
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct file *file``
+ the target file
+
+ ``struct debugfs_cancellation *cancellation``
+ the operation to execute
+- name: debugfs_leave_cancellation
+ description: mock_tests/kdoc-drop-ctx-lock.c line 152
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * debugfs_leave_cancellation - wrapup the cancellation operation on @file
+ * @file: the target file
+ * @cancellation: the operation to wrapup
+ */
+ void debugfs_leave_cancellation(struct file *file,
+ struct debugfs_cancellation *cancellation) __releases(cancellation)
+ { }
+ expected:
+ - man: |
+ .TH "debugfs_leave_cancellation" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ debugfs_leave_cancellation \- wrapup the cancellation operation on @file
+ .SH SYNOPSIS
+ .B "void" debugfs_leave_cancellation
+ .BI "(struct file *file " ","
+ .BI "struct debugfs_cancellation *cancellation " ");"
+ .SH ARGUMENTS
+ .IP "file" 12
+ the target file
+ .IP "cancellation" 12
+ the operation to wrapup
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void debugfs_leave_cancellation (struct file *file, struct debugfs_cancellation *cancellation)
+
+ wrapup the cancellation operation on **file**
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct file *file``
+ the target file
+
+ ``struct debugfs_cancellation *cancellation``
+ the operation to wrapup
+- name: acpi_os_acquire_lock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 161
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * acpi_os_acquire_lock - Acquire a spinlock.
+ * @lockp: pointer to the spinlock_t.
+ */
+ acpi_cpu_flags acpi_os_acquire_lock(acpi_spinlock lockp)
+ __acquires(lockp)
+ {
+ expected:
+ - man: |
+ .TH "acpi_os_acquire_lock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ acpi_os_acquire_lock \- Acquire a spinlock.
+ .SH SYNOPSIS
+ .B "acpi_cpu_flags" acpi_os_acquire_lock
+ .BI "(acpi_spinlock lockp " ");"
+ .SH ARGUMENTS
+ .IP "lockp" 12
+ pointer to the spinlock_t.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: acpi_cpu_flags acpi_os_acquire_lock (acpi_spinlock lockp)
+
+ Acquire a spinlock.
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``acpi_spinlock lockp``
+ pointer to the spinlock_t.
+- name: acpi_os_release_lock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 172
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * acpi_os_release_lock - Release a spinlock.
+ * @lockp: pointer to the spinlock_t.
+ * @not_used: these flags are not used.
+ */
+ void acpi_os_release_lock(acpi_spinlock lockp, acpi_cpu_flags not_used)
+ __releases(lockp)
+ {
+ expected:
+ - man: |
+ .TH "acpi_os_release_lock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ acpi_os_release_lock \- Release a spinlock.
+ .SH SYNOPSIS
+ .B "void" acpi_os_release_lock
+ .BI "(acpi_spinlock lockp " ","
+ .BI "acpi_cpu_flags not_used " ");"
+ .SH ARGUMENTS
+ .IP "lockp" 12
+ pointer to the spinlock_t.
+ .IP "not_used" 12
+ these flags are not used.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void acpi_os_release_lock (acpi_spinlock lockp, acpi_cpu_flags not_used)
+
+ Release a spinlock.
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``acpi_spinlock lockp``
+ pointer to the spinlock_t.
+
+ ``acpi_cpu_flags not_used``
+ these flags are not used.
+- name: tx
+ description: mock_tests/kdoc-drop-ctx-lock.c line 183
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * tx - transmit message ID @id
+ * @id: message ID to transmit
+ */
+ int tx(int id) __must_hold(&txlock)
+ {
+ expected:
+ - man: |
+ .TH "tx" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ tx \- transmit message ID @id
+ .SH SYNOPSIS
+ .B "int" tx
+ .BI "(int id " ");"
+ .SH ARGUMENTS
+ .IP "id" 12
+ message ID to transmit
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: int tx (int id)
+
+ transmit message ID **id**
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``int id``
+ message ID to transmit
+- name: contend_for_bm
+ description: mock_tests/kdoc-drop-ctx-lock.c line 192
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * contend_for_bm - try to become the bus master
+ * @card: the &fw_card (describes the bus)
+ */
+ enum bm_contention_outcome contend_for_bm(struct fw_card *card)
+ __must_hold(&card->lock)
+ {
+ expected:
+ - man: |
+ .TH "contend_for_bm" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ contend_for_bm \- try to become the bus master
+ .SH SYNOPSIS
+ .B "enum bm_contention_outcome" contend_for_bm
+ .BI "(struct fw_card *card " ");"
+ .SH ARGUMENTS
+ .IP "card" 12
+ the \fIfw_card\fP (describes the bus)
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: enum bm_contention_outcome contend_for_bm (struct fw_card *card)
+
+ try to become the bus master
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct fw_card *card``
+ the :c:type:`fw_card` (describes the bus)
+- name: prepare_report_producer
+ description: mock_tests/kdoc-drop-ctx-lock.c line 202
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * prepare_report_producer - prepare the report producer
+ * @flags: still flags
+ * @ai: some AI
+ * @other_info: Populate @other_info; requires that the provided
+ * @other_info not in use.
+ */
+ void prepare_report_producer(unsigned long *flags,
+ const struct access_info *ai,
+ struct other_info *other_info)
+ __must_not_hold(&report_lock)
+ { }
+ expected:
+ - man: |
+ .TH "prepare_report_producer" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ prepare_report_producer \- prepare the report producer
+ .SH SYNOPSIS
+ .B "void" prepare_report_producer
+ .BI "(unsigned long *flags " ","
+ .BI "const struct access_info *ai " ","
+ .BI "struct other_info *other_info " ");"
+ .SH ARGUMENTS
+ .IP "flags" 12
+ still flags
+ .IP "ai" 12
+ some AI
+ .IP "other_info" 12
+ Populate \fIother_info\fP; requires that the provided
+ \fIother_info\fP not in use.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void prepare_report_producer (unsigned long *flags, const struct access_info *ai, struct other_info *other_info)
+
+ prepare the report producer
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``unsigned long *flags``
+ still flags
+
+ ``const struct access_info *ai``
+ some AI
+
+ ``struct other_info *other_info``
+ Populate **other_info**; requires that the provided
+ **other_info** not in use.
+- name: crypto_alg_lookup
+ description: mock_tests/kdoc-drop-ctx-lock.c line 215
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * __crypto_alg_lookup() - lookup the algorithm by name/type/mask
+ * @name: name to search for
+ * @type: type to search for
+ * @mask: mask to match
+ */
+ struct crypto_alg *__crypto_alg_lookup(const char *name, u32 type,
+ u32 mask)
+ __must_hold_shared(&crypto_alg_sem)
+ {
+ expected:
+ - man: |
+ .TH "__crypto_alg_lookup" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ __crypto_alg_lookup \- lookup the algorithm by name/type/mask
+ .SH SYNOPSIS
+ .B "struct crypto_alg *" __crypto_alg_lookup
+ .BI "(const char *name " ","
+ .BI "u32 type " ","
+ .BI "u32 mask " ");"
+ .SH ARGUMENTS
+ .IP "name" 12
+ name to search for
+ .IP "type" 12
+ type to search for
+ .IP "mask" 12
+ mask to match
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: struct crypto_alg * __crypto_alg_lookup (const char *name, u32 type, u32 mask)
+
+ lookup the algorithm by name/type/mask
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``const char *name``
+ name to search for
+
+ ``u32 type``
+ type to search for
+
+ ``u32 mask``
+ mask to match
+- name: down_read_trylock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 228
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * down_read_trylock - trylock for reading
+ * @sem: the semaphore to try to lock
+ *
+ * Returns: 1 if successful, 0 if contention
+ */
+ extern int down_read_trylock(struct rw_semaphore *sem) __cond_acquires_shared(true, sem);
+ expected:
+ - man: |
+ .TH "down_read_trylock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ down_read_trylock \- trylock for reading
+ .SH SYNOPSIS
+ .B "int" down_read_trylock
+ .BI "(struct rw_semaphore *sem " ");"
+ .SH ARGUMENTS
+ .IP "sem" 12
+ the semaphore to try to lock
+ .SH "RETURN"
+ 1 if successful, 0 if contention
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: int down_read_trylock (struct rw_semaphore *sem)
+
+ trylock for reading
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct rw_semaphore *sem``
+ the semaphore to try to lock
+
+ **Return**
+
+ 1 if successful, 0 if contention
+- name: tomoyo_read_lock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 236
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * tomoyo_read_lock - Take lock for protecting policy.
+ *
+ * Returns: index number for tomoyo_read_unlock().
+ */
+ int tomoyo_read_lock(void)
+ __acquires_shared(&tomoyo_ss)
+ {
+ expected:
+ - man: |
+ .TH "tomoyo_read_lock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ tomoyo_read_lock \- Take lock for protecting policy.
+ .SH SYNOPSIS
+ .B "int" tomoyo_read_lock
+ .BI "(void " ");"
+ .SH ARGUMENTS
+ .IP "void" 12
+ no arguments
+ .SH "RETURN"
+ index number for \fBtomoyo_read_unlock\fP.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: int tomoyo_read_lock (void)
+
+ Take lock for protecting policy.
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``void``
+ no arguments
+
+ **Return**
+
+ index number for tomoyo_read_unlock().
+- name: tomoyo_read_unlock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 247
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * tomoyo_read_unlock - Release lock for protecting policy.
+ *
+ * @idx: Index number returned by tomoyo_read_lock().
+ */
+ void tomoyo_read_unlock(int idx)
+ __releases_shared(&tomoyo_ss)
+ { }
+ expected:
+ - man: |
+ .TH "tomoyo_read_unlock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ tomoyo_read_unlock \- Release lock for protecting policy.
+ .SH SYNOPSIS
+ .B "void" tomoyo_read_unlock
+ .BI "(int idx " ");"
+ .SH ARGUMENTS
+ .IP "idx" 12
+ Index number returned by \fBtomoyo_read_lock\fP.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void tomoyo_read_unlock (int idx)
+
+ Release lock for protecting policy.
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``int idx``
+ Index number returned by tomoyo_read_lock().
+- name: c_stop
+ description: mock_tests/kdoc-drop-ctx-lock.c line 256
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * c_stop - stop the seq_file iteration
+ * @m: the &struct seq_file
+ * @p: handle
+ */
+ void c_stop(struct seq_file *m, void *p)
+ __releases_shared(&crypto_alg_sem)
+ { }
+ expected:
+ - man: |
+ .TH "c_stop" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ c_stop \- stop the seq_file iteration
+ .SH SYNOPSIS
+ .B "void" c_stop
+ .BI "(struct seq_file *m " ","
+ .BI "void *p " ");"
+ .SH ARGUMENTS
+ .IP "m" 12
+ the \fIstruct seq_file\fP
+ .IP "p" 12
+ handle
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void c_stop (struct seq_file *m, void *p)
+
+ stop the seq_file iteration
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct seq_file *m``
+ the :c:type:`struct seq_file <seq_file>`
+
+ ``void *p``
+ handle
+- name: spin_lock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 265
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * spin_lock - spin until the @lock is acquired
+ * @lock: the spinlock
+ */
+ void spin_lock(spinlock_t *lock)
+ __acquires(lock) __no_context_analysis
+ { }
+ expected:
+ - man: |
+ .TH "spin_lock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ spin_lock \- spin until the @lock is acquired
+ .SH SYNOPSIS
+ .B "void" spin_lock
+ .BI "(spinlock_t *lock " ");"
+ .SH ARGUMENTS
+ .IP "lock" 12
+ the spinlock
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void spin_lock (spinlock_t *lock)
+
+ spin until the **lock** is acquired
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``spinlock_t *lock``
+ the spinlock
--
2.53.0
^ permalink raw reply related
* [PATCH 07/10] docs: kdoc: better handle source when producing YAML output
From: Mauro Carvalho Chehab @ 2026-03-23 9:10 UTC (permalink / raw)
To: Jonathan Corbet, Linux Doc Mailing List, Mauro Carvalho Chehab
Cc: Mauro Carvalho Chehab, linux-kernel, Aleksandr Loktionov,
Randy Dunlap
In-Reply-To: <cover.1774256269.git.mchehab+huawei@kernel.org>
The current logic was storing symbols source code on a list,
not linked to the actual KdocItem. While this works fine when
kernel-doc markups are OK, on places where there is a "/**"
without a valid kernel-doc markup, it ends that the 1:1 match
between source code and KdocItem doesn't happen, causing
problems to generate the YAML output.
Fix it by storing the source code directly into the KdocItem
structure.
This shouldn't affect performance or memory footprint, except
when --yaml option is used.
While here, add a __repr__() function for KdocItem, as it
helps debugging it.
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
tools/lib/python/kdoc/kdoc_files.py | 8 +-
tools/lib/python/kdoc/kdoc_item.py | 6 +-
tools/lib/python/kdoc/kdoc_parser.py | 100 ++++++++++++------------
tools/lib/python/kdoc/kdoc_yaml_file.py | 28 +++----
tools/unittests/test_kdoc_parser.py | 9 +++
5 files changed, 79 insertions(+), 72 deletions(-)
diff --git a/tools/lib/python/kdoc/kdoc_files.py b/tools/lib/python/kdoc/kdoc_files.py
index 5a299ed44d62..2428cfc4e843 100644
--- a/tools/lib/python/kdoc/kdoc_files.py
+++ b/tools/lib/python/kdoc/kdoc_files.py
@@ -203,10 +203,6 @@ class KernelFiles():
self.results[fname] = entries
- source = doc.get_source()
- if source:
- self.source[fname] = source
-
def process_export_file(self, fname):
"""
Parses ``EXPORT_SYMBOL*`` macros from a single Kernel source file.
@@ -294,7 +290,6 @@ class KernelFiles():
self.errors = 0
self.results = {}
- self.source = {}
self.files = set()
self.export_files = set()
@@ -364,8 +359,7 @@ class KernelFiles():
function_table, enable_lineno,
no_doc_sections)
- self.test_file.output_symbols(fname, symbols,
- self.source.get(fname))
+ self.test_file.output_symbols(fname, symbols)
continue
diff --git a/tools/lib/python/kdoc/kdoc_item.py b/tools/lib/python/kdoc/kdoc_item.py
index fe08cac861c2..a7aa6e1e4c1c 100644
--- a/tools/lib/python/kdoc/kdoc_item.py
+++ b/tools/lib/python/kdoc/kdoc_item.py
@@ -14,7 +14,8 @@ class KdocItem:
then pass into the output modules.
"""
- def __init__(self, name, fname, type, start_line, **other_stuff):
+ def __init__(self, name, fname, type, start_line,
+ **other_stuff):
self.name = name
self.fname = fname
self.type = type
@@ -60,6 +61,9 @@ class KdocItem:
def __getitem__(self, key):
return self.get(key)
+ def __repr__(self):
+ return f"KdocItem({self.name}, {self.fname}, {self.type}, {self.declaration_start_line})"
+
@classmethod
def from_dict(cls, d):
"""Create a KdocItem from a plain dict."""
diff --git a/tools/lib/python/kdoc/kdoc_parser.py b/tools/lib/python/kdoc/kdoc_parser.py
index a10e64589d76..74af7ae47aa4 100644
--- a/tools/lib/python/kdoc/kdoc_parser.py
+++ b/tools/lib/python/kdoc/kdoc_parser.py
@@ -265,9 +265,6 @@ class KernelDoc:
# Place all potential outputs into an array
self.entries = []
- # When store_src is true, the kernel-doc source content is stored here
- self.source = None
-
#
# We need Python 3.7 for its "dicts remember the insertion
# order" guarantee
@@ -720,13 +717,14 @@ class KernelDoc:
return declaration
- def dump_struct(self, ln, proto):
+ def dump_struct(self, ln, proto, source):
"""
Store an entry for a ``struct`` or ``union``
"""
#
# Do the basic parse to get the pieces of the declaration.
#
+ source = source
proto = trim_private_members(proto)
struct_parts = self.split_struct_proto(proto)
if not struct_parts:
@@ -756,10 +754,11 @@ class KernelDoc:
declaration_name)
self.check_sections(ln, declaration_name, decl_type)
self.output_declaration(decl_type, declaration_name,
+ source=source,
definition=self.format_struct_decl(declaration),
purpose=self.entry.declaration_purpose)
- def dump_enum(self, ln, proto):
+ def dump_enum(self, ln, proto, source):
"""
Store an ``enum`` inside self.entries array.
"""
@@ -767,6 +766,7 @@ class KernelDoc:
# Strip preprocessor directives. Note that this depends on the
# trailing semicolon we added in process_proto_type().
#
+ source = source
proto = trim_private_members(proto)
proto = KernRe(r'#\s*((define|ifdef|if)\s+|endif)[^;]*;', flags=re.S).sub('', proto)
#
@@ -831,9 +831,10 @@ class KernelDoc:
f"Excess enum value '@{k}' description in '{declaration_name}'")
self.output_declaration('enum', declaration_name,
+ source=source,
purpose=self.entry.declaration_purpose)
- def dump_var(self, ln, proto):
+ def dump_var(self, ln, proto, source):
"""
Store variables that are part of kAPI.
"""
@@ -846,6 +847,7 @@ class KernelDoc:
#
# Store the full prototype before modifying it
#
+ source = source
full_proto = proto
declaration_name = None
@@ -895,32 +897,34 @@ class KernelDoc:
default_val = default_val.lstrip("=").strip()
self.output_declaration("var", declaration_name,
+ source=source,
full_proto=full_proto,
default_val=default_val,
purpose=self.entry.declaration_purpose)
- def dump_declaration(self, ln, prototype):
+ def dump_declaration(self, ln, prototype, source):
"""
Store a data declaration inside self.entries array.
"""
if self.entry.decl_type == "enum":
- self.dump_enum(ln, prototype)
+ self.dump_enum(ln, prototype, source)
elif self.entry.decl_type == "typedef":
- self.dump_typedef(ln, prototype)
+ self.dump_typedef(ln, prototype, source)
elif self.entry.decl_type in ["union", "struct"]:
- self.dump_struct(ln, prototype)
+ self.dump_struct(ln, prototype, source)
elif self.entry.decl_type == "var":
- self.dump_var(ln, prototype)
+ self.dump_var(ln, prototype, source)
else:
# This would be a bug
self.emit_message(ln, f'Unknown declaration type: {self.entry.decl_type}')
- def dump_function(self, ln, prototype):
+ def dump_function(self, ln, prototype, source):
"""
Store a function or function macro inside self.entries array.
"""
+ source = source
found = func_macro = False
return_type = ''
decl_type = 'function'
@@ -1013,13 +1017,14 @@ class KernelDoc:
# Store the result.
#
self.output_declaration(decl_type, declaration_name,
+ source=source,
typedef=('typedef' in return_type),
functiontype=return_type,
purpose=self.entry.declaration_purpose,
func_macro=func_macro)
- def dump_typedef(self, ln, proto):
+ def dump_typedef(self, ln, proto, source):
"""
Store a ``typedef`` inside self.entries array.
"""
@@ -1030,6 +1035,8 @@ class KernelDoc:
typedef_ident = r'\*?\s*(\w\S+)\s*'
typedef_args = r'\s*\((.*)\);'
+ source = source
+
typedef1 = KernRe(typedef_type + r'\(' + typedef_ident + r'\)' + typedef_args)
typedef2 = KernRe(typedef_type + typedef_ident + typedef_args)
@@ -1050,6 +1057,7 @@ class KernelDoc:
self.create_parameter_list(ln, 'function', args, ',', declaration_name)
self.output_declaration('function', declaration_name,
+ source=source,
typedef=True,
functiontype=return_type,
purpose=self.entry.declaration_purpose)
@@ -1067,6 +1075,7 @@ class KernelDoc:
return
self.output_declaration('typedef', declaration_name,
+ source=source,
purpose=self.entry.declaration_purpose)
return
@@ -1104,7 +1113,7 @@ class KernelDoc:
function_set.add(symbol)
return True
- def process_normal(self, ln, line):
+ def process_normal(self, ln, line, source):
"""
STATE_NORMAL: looking for the ``/**`` to begin everything.
"""
@@ -1118,7 +1127,7 @@ class KernelDoc:
# next line is always the function name
self.state = state.NAME
- def process_name(self, ln, line):
+ def process_name(self, ln, line, source):
"""
STATE_NAME: Looking for the "name - description" line
"""
@@ -1251,7 +1260,7 @@ class KernelDoc:
return False
- def process_decl(self, ln, line):
+ def process_decl(self, ln, line, source):
"""
STATE_DECLARATION: We've seen the beginning of a declaration.
"""
@@ -1280,7 +1289,7 @@ class KernelDoc:
self.emit_msg(ln, f"bad line: {line}")
- def process_special(self, ln, line):
+ def process_special(self, ln, line, source):
"""
STATE_SPECIAL_SECTION: a section ending with a blank line.
"""
@@ -1331,7 +1340,7 @@ class KernelDoc:
# Unknown line, ignore
self.emit_msg(ln, f"bad line: {line}")
- def process_body(self, ln, line):
+ def process_body(self, ln, line, source):
"""
STATE_BODY: the bulk of a kerneldoc comment.
"""
@@ -1345,7 +1354,7 @@ class KernelDoc:
# Unknown line, ignore
self.emit_msg(ln, f"bad line: {line}")
- def process_inline_name(self, ln, line):
+ def process_inline_name(self, ln, line, source):
"""STATE_INLINE_NAME: beginning of docbook comments within a prototype."""
if doc_inline_sect.search(line):
@@ -1363,10 +1372,10 @@ class KernelDoc:
# Don't let it add partial comments at the code, as breaks the
# logic meant to remove comments from prototypes.
#
- self.process_proto_type(ln, "/**\n" + line)
+ self.process_proto_type(ln, "/**\n" + line, source)
# else ... ??
- def process_inline_text(self, ln, line):
+ def process_inline_text(self, ln, line, source):
"""STATE_INLINE_TEXT: docbook comments within a prototype."""
if doc_inline_end.search(line):
@@ -1452,7 +1461,7 @@ class KernelDoc:
return proto
- def process_proto_function(self, ln, line):
+ def process_proto_function(self, ln, line, source):
"""Ancillary routine to process a function prototype."""
# strip C99-style comments to end of line
@@ -1494,10 +1503,10 @@ class KernelDoc:
#
# ... and we're done
#
- self.dump_function(ln, self.entry.prototype)
+ self.dump_function(ln, self.entry.prototype, source)
self.reset_state(ln)
- def process_proto_type(self, ln, line):
+ def process_proto_type(self, ln, line, source):
"""
Ancillary routine to process a type.
"""
@@ -1527,7 +1536,7 @@ class KernelDoc:
elif chunk == '}':
self.entry.brcount -= 1
elif chunk == ';' and self.entry.brcount <= 0:
- self.dump_declaration(ln, self.entry.prototype)
+ self.dump_declaration(ln, self.entry.prototype, source)
self.reset_state(ln)
return
#
@@ -1536,7 +1545,7 @@ class KernelDoc:
#
self.entry.prototype += ' '
- def process_proto(self, ln, line):
+ def process_proto(self, ln, line, source):
"""STATE_PROTO: reading a function/whatever prototype."""
if doc_inline_oneline.search(line):
@@ -1548,17 +1557,18 @@ class KernelDoc:
self.state = state.INLINE_NAME
elif self.entry.decl_type == 'function':
- self.process_proto_function(ln, line)
+ self.process_proto_function(ln, line, source)
else:
- self.process_proto_type(ln, line)
+ self.process_proto_type(ln, line, source)
- def process_docblock(self, ln, line):
+ def process_docblock(self, ln, line, source):
"""STATE_DOCBLOCK: within a ``DOC:`` block."""
if doc_end.search(line):
self.dump_section()
- self.output_declaration("doc", self.entry.identifier)
+ self.output_declaration("doc", self.entry.identifier,
+ source=source)
self.reset_state(ln)
elif doc_content.search(line):
@@ -1596,15 +1606,6 @@ class KernelDoc:
state.DOCBLOCK: process_docblock,
}
- def get_source(self):
- """
- Return the file content of the lines handled by kernel-doc at the
- latest parse_kdoc() run.
-
- Returns none if KernelDoc() was not initialized with store_src,
- """
- return self.source
-
def parse_kdoc(self):
"""
Open and process each line of a C source file.
@@ -1618,8 +1619,8 @@ class KernelDoc:
prev = ""
prev_ln = None
export_table = set()
- self.source = []
self.state = state.NORMAL
+ source = ""
try:
with open(self.fname, "r", encoding="utf8",
@@ -1646,7 +1647,11 @@ class KernelDoc:
ln, state.name[self.state],
line)
- prev_state = self.state
+ if self.store_src:
+ if source and self.state == state.NORMAL:
+ source = ""
+ elif self.state != state.NORMAL:
+ source += line + "\n"
# This is an optimization over the original script.
# There, when export_file was used for the same file,
@@ -1655,16 +1660,11 @@ class KernelDoc:
#
if (self.state != state.NORMAL) or \
not self.process_export(export_table, line):
+ prev_state = self.state
# Hand this line to the appropriate state handler
- self.state_actions[self.state](self, ln, line)
-
- if self.store_src and prev_state != self.state or self.state != state.NORMAL:
- if self.state == state.NAME:
- # A "/**" was detected. Add a new source element
- self.source.append({"ln": ln, "data": line + "\n"})
- else:
- # Append to the existing one
- self.source[-1]["data"] += line + "\n"
+ self.state_actions[self.state](self, ln, line, source)
+ if prev_state == state.NORMAL and self.state != state.NORMAL:
+ source += line + "\n"
self.emit_unused_warnings()
diff --git a/tools/lib/python/kdoc/kdoc_yaml_file.py b/tools/lib/python/kdoc/kdoc_yaml_file.py
index 18737abb1176..1e2ae7c59d70 100644
--- a/tools/lib/python/kdoc/kdoc_yaml_file.py
+++ b/tools/lib/python/kdoc/kdoc_yaml_file.py
@@ -85,7 +85,7 @@ class KDocTestFile():
return d
- def output_symbols(self, fname, symbols, source):
+ def output_symbols(self, fname, symbols):
"""
Store source, symbols and output strings at self.tests.
"""
@@ -96,16 +96,10 @@ class KDocTestFile():
kdoc_item = []
expected = []
- if not symbols and not source:
- return
-
- if not source or len(symbols) != len(source):
- print(f"Warning: lengths are different. Ignoring {fname}")
-
- # Folding without line numbers is too hard.
- # The right thing to do here to proceed would be to delete
- # not-handled source blocks, as len(source) should be bigger
- # than len(symbols)
+ #
+ # Source code didn't produce any symbol
+ #
+ if not symbols:
return
base_name = "test_" + fname.replace(".", "_").replace("/", "_")
@@ -115,9 +109,15 @@ class KDocTestFile():
for i in range(0, len(symbols)):
arg = symbols[i]
- if "KdocItem" in self.yaml_content:
+ source = arg.get("source", "")
+
+ if arg and "KdocItem" in self.yaml_content:
msg = self.get_kdoc_item(arg)
+ other_stuff = msg.get("other_stuff", {})
+ if "source" in other_stuff:
+ del other_stuff["source"]
+
expected_dict["kdoc_item"] = msg
for out_style in self.out_style:
@@ -132,9 +132,9 @@ class KDocTestFile():
test = {
"name": name,
- "description": f"{fname} line {source[i]["ln"]}",
+ "description": f"{fname} line {arg.declaration_start_line}",
"fname": fname,
- "source": source[i]["data"],
+ "source": source,
"expected": [expected_dict]
}
diff --git a/tools/unittests/test_kdoc_parser.py b/tools/unittests/test_kdoc_parser.py
index f2250ef192ce..c4a76ed13dbc 100755
--- a/tools/unittests/test_kdoc_parser.py
+++ b/tools/unittests/test_kdoc_parser.py
@@ -167,7 +167,16 @@ class GenerateKdocItem(unittest.TestCase):
self.assertIsInstance(entry, KdocItem)
d = vars(entry)
+
+ other_stuff = d.get("other_stuff", {})
+ if "source" in other_stuff:
+ del other_stuff["source"]
+
for key, value in expected.items():
+ if key == "other_stuff":
+ if "source" in value:
+ del value["source"]
+
result = clean_whitespc(d[key], relax_whitespace)
value = clean_whitespc(value, relax_whitespace)
--
2.53.0
^ permalink raw reply related
* [PATCH 09/10] docs: kdoc_output: raise an error if full_proto not available for var
From: Mauro Carvalho Chehab @ 2026-03-23 9:10 UTC (permalink / raw)
To: Jonathan Corbet, Linux Doc Mailing List
Cc: Mauro Carvalho Chehab, linux-kernel, Mauro Carvalho Chehab
In-Reply-To: <cover.1774256269.git.mchehab+huawei@kernel.org>
This is mandatory, but if it is missing, we need to know what
symbol had problems.
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
tools/lib/python/kdoc/kdoc_output.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tools/lib/python/kdoc/kdoc_output.py b/tools/lib/python/kdoc/kdoc_output.py
index 2bfcd356654b..de107ab4a281 100644
--- a/tools/lib/python/kdoc/kdoc_output.py
+++ b/tools/lib/python/kdoc/kdoc_output.py
@@ -513,7 +513,9 @@ class RestFormat(OutputFormat):
def out_var(self, fname, name, args):
oldprefix = self.lineprefix
ln = args.declaration_start_line
- full_proto = args.other_stuff["full_proto"]
+ full_proto = args.other_stuff.get("full_proto")
+ if not full_proto:
+ raise KeyError(f"Can't find full proto for {name} variable")
self.lineprefix = " "
--
2.53.0
^ permalink raw reply related
* [PATCH 03/10] docs: tools: include kdoc_yaml_file at documentation
From: Mauro Carvalho Chehab @ 2026-03-23 9:10 UTC (permalink / raw)
To: Jonathan Corbet, Linux Doc Mailing List
Cc: Mauro Carvalho Chehab, linux-kernel, Mauro Carvalho Chehab,
Shuah Khan
In-Reply-To: <cover.1774256269.git.mchehab+huawei@kernel.org>
Add an autodoc entry for the new kdoc_yaml_file module.
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
Documentation/tools/kdoc_ancillary.rst | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/tools/kdoc_ancillary.rst b/Documentation/tools/kdoc_ancillary.rst
index 85f3806a431a..249753744d11 100644
--- a/Documentation/tools/kdoc_ancillary.rst
+++ b/Documentation/tools/kdoc_ancillary.rst
@@ -53,3 +53,11 @@ Python version ancillary methods
:members:
:show-inheritance:
:undoc-members:
+
+Write output on YAML file
+=========================
+
+.. automodule:: lib.python.kdoc.kdoc_yaml_file
+ :members:
+ :show-inheritance:
+ :undoc-members:
--
2.53.0
^ permalink raw reply related
* [PATCH 10/10] docs: c_lex.py: store logger on its data
From: Mauro Carvalho Chehab @ 2026-03-23 9:10 UTC (permalink / raw)
To: Jonathan Corbet, Linux Doc Mailing List
Cc: Mauro Carvalho Chehab, linux-kernel, Aleksandr Loktionov,
Mauro Carvalho Chehab
In-Reply-To: <cover.1774256269.git.mchehab+huawei@kernel.org>
By having the logger stored there, any code using CTokenizer can
log messages there.
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
tools/lib/python/kdoc/c_lex.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/tools/lib/python/kdoc/c_lex.py b/tools/lib/python/kdoc/c_lex.py
index e01b154f458e..cb95f5172448 100644
--- a/tools/lib/python/kdoc/c_lex.py
+++ b/tools/lib/python/kdoc/c_lex.py
@@ -177,7 +177,7 @@ class CTokenizer():
# This class is inspired and follows the basic concepts of:
# https://docs.python.org/3/library/re.html#writing-a-tokenizer
- def __init__(self, source=None, log=None):
+ def __init__(self, source=None):
"""
Create a regular expression to handle RE_SCANNER_LIST.
@@ -188,6 +188,12 @@ class CTokenizer():
when matching a code via RE_SCANNER.
"""
+ #
+ # Store logger to allow parser classes to re-use it
+ #
+ global log
+ self.log = log
+
self.tokens = []
if not source:
--
2.53.0
^ permalink raw reply related
* [PATCH 02/10] unittests: test_kdoc_parser: add command line arg to read a YAML file
From: Mauro Carvalho Chehab @ 2026-03-23 9:10 UTC (permalink / raw)
To: Jonathan Corbet, Linux Doc Mailing List, Mauro Carvalho Chehab
Cc: Mauro Carvalho Chehab, linux-kernel
In-Reply-To: <cover.1774256269.git.mchehab+huawei@kernel.org>
The test_kdoc_parser.py already supports loading dynamic tests
when running unit tests.
Add support to read from a different file. This is useful for:
- regression tests before/afer some changes;
- preparing new unit tests;
- test a different yaml before adding its contents at
tools/unittests/kdoc-test.yaml.
It should be noticed that passing an argument to a unit test
is not too trivial, as unittest core will load itself the
runner with a separate environment. The best (only?) way to
do it is by setting the system environment. This way, when
the class is called by the unit test loader, it can pick
the var from the environment without relying on a global
variable.
The unittest_helper has already provision for it, so let's
use its support.
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
tools/unittests/test_kdoc_parser.py | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/tools/unittests/test_kdoc_parser.py b/tools/unittests/test_kdoc_parser.py
index 723dd8c7f4f3..f2250ef192ce 100755
--- a/tools/unittests/test_kdoc_parser.py
+++ b/tools/unittests/test_kdoc_parser.py
@@ -30,7 +30,7 @@ from kdoc.kdoc_output import RestFormat, ManFormat
from kdoc.xforms_lists import CTransforms
-from unittest_helper import run_unittest
+from unittest_helper import TestUnits
#
@@ -38,6 +38,10 @@ from unittest_helper import run_unittest
#
TEST_FILE = os.path.join(SRC_DIR, "kdoc-test.yaml")
+env = {
+ "yaml_file": TEST_FILE
+}
+
#
# Ancillary logic to clean whitespaces
#
@@ -470,7 +474,9 @@ class KernelDocDynamicTests():
optional ones.
"""
- with open(TEST_FILE, encoding="utf-8") as fp:
+ test_file = os.environ.get("yaml_file", TEST_FILE)
+
+ with open(test_file, encoding="utf-8") as fp:
testset = yaml.safe_load(fp)
tests = testset["tests"]
@@ -531,4 +537,15 @@ KernelDocDynamicTests.create_tests()
# Run all tests
#
if __name__ == "__main__":
- run_unittest(__file__)
+ runner = TestUnits()
+ parser = runner.parse_args()
+ parser.add_argument("-y", "--yaml-file", "--yaml",
+ help='Name of the yaml file to load')
+
+ args = parser.parse_args()
+
+ if args.yaml_file:
+ env["yaml_file"] = os.path.expanduser(args.yaml_file)
+
+ # Run tests with customized arguments
+ runner.run(__file__, parser=parser, args=args, env=env)
--
2.53.0
^ permalink raw reply related
* [PATCH 04/10] docs: kdoc_yaml_file: add a representer to make strings look nicer
From: Mauro Carvalho Chehab @ 2026-03-23 9:10 UTC (permalink / raw)
To: Jonathan Corbet, Linux Doc Mailing List
Cc: Mauro Carvalho Chehab, linux-kernel, Mauro Carvalho Chehab
In-Reply-To: <cover.1774256269.git.mchehab+huawei@kernel.org>
The strings representation is not ok, currently. Add a helper
function to improve it, and drop blank lines at beginning and
at the end of the dumps
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
tools/lib/python/kdoc/kdoc_yaml_file.py | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/tools/lib/python/kdoc/kdoc_yaml_file.py b/tools/lib/python/kdoc/kdoc_yaml_file.py
index db131503c3f6..18737abb1176 100644
--- a/tools/lib/python/kdoc/kdoc_yaml_file.py
+++ b/tools/lib/python/kdoc/kdoc_yaml_file.py
@@ -126,7 +126,7 @@ class KDocTestFile():
else:
key = "rst"
- expected_dict[key]= out_style.output_symbols(fname, [arg])
+ expected_dict[key]= out_style.output_symbols(fname, [arg]).strip()
name = f"{base_name}_{i:03d}"
@@ -148,8 +148,20 @@ class KDocTestFile():
"""
import yaml
+ # Helper function to better handle multilines
+ def str_presenter(dumper, data):
+ if "\n" in data:
+ return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
+
+ return dumper.represent_scalar("tag:yaml.org,2002:str", data)
+
+ # Register the representer
+ yaml.add_representer(str, str_presenter)
+
data = {"tests": self.tests}
with open(self.test_file, "w", encoding="utf-8") as fp:
- yaml.safe_dump(data, fp, sort_keys=False, default_style="|",
- default_flow_style=False, allow_unicode=True)
+ yaml.dump(data, fp,
+ sort_keys=False, width=120, indent=2,
+ default_flow_style=False, allow_unicode=True,
+ explicit_start=False, explicit_end=False)
--
2.53.0
^ permalink raw reply related
* [PATCH 06/10] docs: kdoc_output: fix handling of simple tables
From: Mauro Carvalho Chehab @ 2026-03-23 9:10 UTC (permalink / raw)
To: Jonathan Corbet, Linux Doc Mailing List
Cc: Mauro Carvalho Chehab, linux-kernel, Mauro Carvalho Chehab
In-Reply-To: <cover.1774256269.git.mchehab+huawei@kernel.org>
Fix check for simple table delimiters.
ReST simple tables use "=" instead of "-". I ended testing it with
a table modified from a complex one, using "--- --- ---", instead
of searching for a real Kernel example.
Only noticed when adding an unit test and seek for an actual
example from kernel-doc markups.
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
tools/lib/python/kdoc/kdoc_output.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tools/lib/python/kdoc/kdoc_output.py b/tools/lib/python/kdoc/kdoc_output.py
index 1b54117dbe19..2bfcd356654b 100644
--- a/tools/lib/python/kdoc/kdoc_output.py
+++ b/tools/lib/python/kdoc/kdoc_output.py
@@ -846,14 +846,14 @@ class ManFormat(OutputFormat):
colspec_row = None
pos = []
- for m in KernRe(r'\-+').finditer(lines[i]):
+ for m in KernRe(r'\=+').finditer(lines[i]):
pos.append((m.start(), m.end() - 1))
i += 1
while i < len(lines):
line = lines[i]
- if KernRe(r"^\s*[\-]+[ \t\-]+$").match(line):
+ if KernRe(r"^\s*[\=]+[ \t\=]+$").match(line):
i += 1
break
@@ -969,7 +969,7 @@ class ManFormat(OutputFormat):
self.data += text
continue
- if KernRe(r"^\-+[ \t]\-[ \t\-]+$").match(line):
+ if KernRe(r"^\=+[ \t]\=[ \t\=]+$").match(line):
i, text = self.simple_table(lines, i)
self.data += text
continue
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox