* [PATCH V10 0/8] dax: prepare for famfs
From: John Groves @ 2026-03-27 21:03 UTC (permalink / raw)
To: John Groves, Miklos Szeredi, Dan Williams, Bernd Schubert,
Alison Schofield
Cc: John Groves, Jonathan Corbet, Shuah Khan, Vishal Verma,
Dave Jiang, Matthew Wilcox, Jan Kara, Alexander Viro,
David Hildenbrand, Christian Brauner, Darrick J . Wong,
Randy Dunlap, Jeff Layton, Amir Goldstein, Jonathan Cameron,
Stefan Hajnoczi, Joanne Koong, Josef Bacik, Bagas Sanjaya,
Chen Linxuan, James Morse, Fuad Tabba, Sean Christopherson,
Shivank Garg, Ackerley Tng, Gregory Price, Aravind Ramesh,
Ajay Joshi, venkataravis@micron.com, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, nvdimm@lists.linux.dev,
linux-cxl@vger.kernel.org, linux-fsdevel@vger.kernel.org,
John Groves
In-Reply-To: <20260327210311.79099-1-john@jagalactic.com>
From: John Groves <john@groves.net>
This patch series along with the bundled patches to fuse are available
as a git tag at [0].
Dropped the "bundle" thread. If this submission goes smoothly, I'll update
the fuse patches to v10 (very little change there as yet).
Changes v9 -> v10
- Minor modernizations per comments from (mostly) Jonathan
- Minor Kconfig simplification
- bus.c:dax_match_type(): don't make fsdev_dax eligible for automatic binding
where devdax would otherwise bind
- dax-private.h: add missing kerneldoc comment for field cached_size in
struct dev_dax_range (thanks Dave)
- fsdev_write_dax(): s/pmem_addr/addr/ (thanks Dave)
- include/linux/dax.h: remove a spuriously-added declaration of inode_dax()
(thanks Jonathan)
Description:
This patch series introduces the required dax support for famfs.
Previous versions of the famfs series included both dax and fuse patches.
This series separates them into separate patch series' (and the fuse
series dependends on this dax series).
The famfs user space code can be found at [1]
Dax Overview:
This series introduces a new "famfs mode" of devdax, whose driver is
drivers/dax/fsdev.c. This driver supports dax_iomap_rw() and
dax_iomap_fault() calls against a character dax instance. A dax device
now can be converted among three modes: 'system-ram', 'devdax' and
'famfs' via daxctl or sysfs (e.g. unbind devdax and bind famfs instead).
In famfs mode, a dax device initializes its pages consistent with the
fsdaxmode of pmem. Raw read/write/mmap are not supported in this mode,
but famfs is happy in this mode - using dax_iomap_rw() for read/write and
dax_iomap_fault() for mmap faults.
Changes v8 -> v9
- Big clarifying comments in fs/dax.c:dax_folio_reset_order() in response
to Jonathan's comments/questions
- Added drivers/dax/Kconfig:CONFIG_DEV_DAX_FSDEV to control inclusion of the
new famfs dax mode (which is in drivers/dax/fsdev.c - bind that to a daxdev
and you have famfs mode)
- Some trivial refactoring, mostly per Jonathan's comments
Changes v7 -> v8
- dax: Added a devm action to clear folio state when unbinding fsdev.c
(thanks Allison)
- Added a missing device_lock() in fs_dax_get() (thanks Dave)
- Re-factored some __free blocks for inline declaration
- Used FIELD_PREP where appropriate
- Minor doc edits
Changes v6 -> v7
- Fixed a regression in famfs_interleave_fileofs_to_daxofs() that
was reported by Intel's kernel test robot
- Added a check in __fsdev_dax_direct_access() for negative return
from pgoff_to_phys(), which would indicate an out-of-range offset
- Fixed a bug in __famfs_meta_free(), where not all interleaved
extents were freed
- Added chunksize alignment checks in famfs_fuse_meta_alloc() and
famfs_interleave_fileofs_to_daxofs() as interleaved chunks must
be PTE or PMD aligned
- Simplified famfs_file_init_dax() a bit
- Re-ran CM's kernel code review prompts on the entire series and
fixed several minor issues
Changes v4 -> v5 -> v6
- None. Re-sending due to technical difficulties
Changes v3 [9] -> v4
- The patch "dax: prevent driver unbind while filesystem holds device"
has been dropped. Dan Williams indicated that the favored behavior is
for a file system to stop working if an underlying driver is unbound,
rather than preventing the unbind.
- The patch "famfs_fuse: Famfs mount opt: -o shadow=<shadowpath>" has
been dropped. Found a way for the famfs user space to do without the
-o opt (via getxattr).
- Squashed the fs/fuse/Kconfig patch into the first subsequent patch
that needed the change
("famfs_fuse: Basic fuse kernel ABI enablement for famfs")
- Many review comments addressed.
- Addressed minor kerneldoc infractions reported by test robot.
Changes v2 [7] -> v3
- Dax: Completely new fsdev driver (drivers/dax/fsdev.c) replaces the
dev_dax_iomap modifications to bus.c/device.c. Devdax devices can now
be switched among 'devdax', 'famfs' and 'system-ram' modes via daxctl
or sysfs.
- Dax: fsdev uses MEMORY_DEVICE_FS_DAX type and leaves folios at order-0
(no vmemmap_shift), allowing fs-dax to manage folio lifecycles
dynamically like pmem does.
- Dax: The "poisoned page" problem is properly fixed via
fsdev_clear_folio_state(), which clears stale mapping/compound state
when fsdev binds. The temporary WARN_ON_ONCE workaround in fs/dax.c
has been removed.
- Dax: Added dax_set_ops() so fsdev can set dax_operations at bind time
(and clear them on unbind), since the dax_device is created before we
know which driver will bind.
- Dax: Added custom bind/unbind sysfs handlers; unbind return -EBUSY if a
filesystem holds the device, preventing unbind while famfs is mounted.
- Fuse: Famfs mounts now require that the fuse server/daemon has
CAP_SYS_RAWIO because they expose raw memory devices.
- Fuse: Added DAX address_space_operations with noop_dirty_folio since
famfs is memory-backed with no writeback required.
- Rebased to latest kernels, fully compatible with Alistair Popple
et. al's recent dax refactoring.
- Ran this series through Chris Mason's code review AI prompts to check
for issues - several subtle problems found and fixed.
- Dropped RFC status - this version is intended to be mergeable.
Changes v1 [8] -> v2:
- The GET_FMAP message/response has been moved from LOOKUP to OPEN, as
was the pretty much unanimous consensus.
- Made the response payload to GET_FMAP variable sized (patch 12)
- Dodgy kerneldoc comments cleaned up or removed.
- Fixed memory leak of fc->shadow in patch 11 (thanks Joanne)
- Dropped many pr_debug and pr_notice calls
References
[0] - https://github.com/jagalactic/linux/tree/famfs-v10 (this patch set)
[1] - https://famfs.org (famfs user space)
[2] - https://lore.kernel.org/linux-cxl/cover.1708709155.git.john@groves.net/
[3] - https://lore.kernel.org/linux-cxl/cover.1714409084.git.john@groves.net/
[4] - https://lwn.net/Articles/983105/ (lsfmm 2024)
[5] - https://lwn.net/Articles/1020170/ (lsfmm 2025)
[6] - https://lore.kernel.org/linux-cxl/cover.8068ad144a7eea4a813670301f4d2a86a8e68ec4.1740713401.git-series.apopple@nvidia.com/
[7] - https://lore.kernel.org/linux-fsdevel/20250703185032.46568-1-john@groves.net/ (famfs fuse v2)
[8] - https://lore.kernel.org/linux-fsdevel/20250421013346.32530-1-john@groves.net/ (famfs fuse v1)
[9] - https://lore.kernel.org/linux-fsdevel/20260107153244.64703-1-john@groves.net/T/#mb2c868801be16eca82dab239a1d201628534aea7 (famfs fuse v3)
John Groves (8):
dax: move dax_pgoff_to_phys from [drivers/dax/] device.c to bus.c
dax: Factor out dax_folio_reset_order() helper
dax: add fsdev.c driver for fs-dax on character dax
dax: Save the kva from memremap
dax: Add dax_operations for use by fs-dax on fsdev dax
dax: Add dax_set_ops() for setting dax_operations at bind time
dax: Add fs_dax_get() func to prepare dax for fs-dax usage
dax: export dax_dev_get()
MAINTAINERS | 8 +
drivers/dax/Kconfig | 5 +
drivers/dax/Makefile | 2 +
drivers/dax/bus.c | 22 ++-
drivers/dax/bus.h | 3 +
drivers/dax/dax-private.h | 4 +
drivers/dax/device.c | 23 ---
drivers/dax/fsdev.c | 346 ++++++++++++++++++++++++++++++++++++++
drivers/dax/super.c | 107 +++++++++++-
fs/dax.c | 74 ++++++--
include/linux/dax.h | 19 ++-
11 files changed, 563 insertions(+), 50 deletions(-)
create mode 100644 drivers/dax/fsdev.c
base-commit: c369299895a591d96745d6492d4888259b004a9e
--
2.53.0
^ permalink raw reply
* Re: [PATCH] doc tools: better handle KBUILD_VERBOSE
From: Jacob Keller @ 2026-03-27 18:35 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Jonathan Corbet, Linux Doc Mailing List
Cc: linux-kernel, Mauro Carvalho Chehab, Randy Dunlap, Shuah Khan
In-Reply-To: <7a99788db75630fb14828d612c0fd77c45ec1891.1774591065.git.mchehab+huawei@kernel.org>
On 3/26/2026 10:57 PM, Mauro Carvalho Chehab wrote:
> As reported by Jacob, there are troubles when KBUILD_VERBOSE is
> set at the environment.
>
> Fix it on both kernel-doc and sphinx-build-wrapper.
>
> Reported-by: Jacob Keller <jacob.e.keller@intel.com>
> Closes: https://lore.kernel.org/linux-doc/9367d899-53af-4d9c-9320-22fc4dbadca5@intel.com/
> Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
> ---
I loaded this on my system and tested the build works as expected both
with V=0 and when I export KBUILD_VERBOSE manually.
Thanks for fixing this quickly!
Tested-by: Jacob Keller <jacob.e.keller@intel.com>
> tools/docs/sphinx-build-wrapper | 7 ++++++-
> tools/lib/python/kdoc/kdoc_files.py | 7 ++++++-
> 2 files changed, 12 insertions(+), 2 deletions(-)
>
> diff --git a/tools/docs/sphinx-build-wrapper b/tools/docs/sphinx-build-wrapper
> index 2c63d28f639d..1bb962202784 100755
> --- a/tools/docs/sphinx-build-wrapper
> +++ b/tools/docs/sphinx-build-wrapper
> @@ -238,7 +238,12 @@ class SphinxBuilder:
> self.latexopts = os.environ.get("LATEXOPTS", "")
>
> if not verbose:
> - verbose = bool(os.environ.get("KBUILD_VERBOSE", "") != "")
> + try:
> + verbose = bool(int(os.environ.get("KBUILD_VERBOSE", 0)))
> + except ValueError:
> + # Handles an eventual case where verbosity is not a number
> + # like KBUILD_VERBOSE=""
Strictly speaking I think os.environ.get() will handle the case of an
empty KBUILD_VERBOSE by converting to the default value (in this case 0).
The intent of the comment and code is pretty clear though, so I don't
know that deserves a re-roll.
Thanks,
Jake
^ permalink raw reply
* Re: kernel-doc overly verbose with V=0
From: Jacob Keller @ 2026-03-27 18:32 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Jonathan Corbet, Linux Doc Mailing List, Shuah Khan, Randy Dunlap,
linux-kernel@vger.kernel.org
In-Reply-To: <20260327071103.5569ad4f@foz.lan>
On 3/26/2026 11:11 PM, Mauro Carvalho Chehab wrote:
> On Wed, 25 Mar 2026 13:42:44 -0700
> Jacob Keller <jacob.e.keller@intel.com> wrote:
>
>> On 3/25/2026 4:50 AM, Mauro Carvalho Chehab wrote:
>>> On Tue, 24 Mar 2026 13:37:39 -0700
>>> Jacob Keller <jacob.e.keller@intel.com> wrote:
>>>
>>>> Hi,
>>>>
>>>> I recently saw some strange behavior with the Python kernel-doc. I was
>>>> seeing the verbose info lines from the kernel-doc script, i.e.:
>>>>
>>>>> Info: ice_ptp_hw.c:5377 Scanning doc for function ice_cgu_get_pin_freq_supp
>>>>> Info: ice_ptp_hw.c:5406 Scanning doc for function ice_cgu_get_pin_name
>>>>> Info: ice_ptp_hw.c:5441 Scanning doc for function ice_cgu_state_to_name
>>>>> Info: ice_ptp_hw.c:5463 Scanning doc for function ice_get_dpll_ref_sw_status
>>>>> Info: ice_ptp_hw.c:5505 Scanning doc for function ice_set_dpll_ref_sw_status
>>>>> Info: ice_ptp_hw.c:5544 Scanning doc for function ice_get_cgu_state
>>>>> Info: ice_ptp_hw.c:5612 Scanning doc for function ice_get_cgu_rclk_pin_info
>>>>> Info: ice_ptp_hw.c:5671 Scanning doc for function ice_cgu_get_output_pin_state_caps
>>>>> Info: ice_ptp_hw.c:5733 Scanning doc for function ice_ptp_lock
>>>>> Info: ice_ptp_hw.c:5770 Scanning doc for function ice_ptp_unlock
>>>>> Info: ice_ptp_hw.c:5782 Scanning doc for function ice_ptp_init_hw
>>>>> Info: ice_ptp_hw.c:5811 Scanning doc for function ice_ptp_write_port_cmd
>>>>> Info: ice_ptp_hw.c:5834 Scanning doc for function ice_ptp_one_port_cmd
>>>>> Info: ice_ptp_hw.c:5866 Scanning doc for function ice_ptp_port_cmd
>>>>> Info: ice_ptp_hw.c:5901 Scanning doc for function ice_ptp_tmr_cmd
>>>>> Info: ice_ptp_hw.c:5934 Scanning doc for function ice_ptp_init_time
>>>>> Info: ice_ptp_hw.c:5986 Scanning doc for function ice_ptp_write_incval
>>>>> Info: ice_ptp_hw.c:6035 Scanning doc for function ice_ptp_write_incval_locked
>>>>> Info: ice_ptp_hw.c:6056 Scanning doc for function ice_ptp_adj_clock
>>>>> Info: ice_ptp_hw.c:6107 Scanning doc for function ice_read_phy_tstamp
>>>>> Info: ice_ptp_hw.c:6134 Scanning doc for function ice_clear_phy_tstamp
>>>>> Info: ice_ptp_hw.c:6164 Scanning doc for function ice_ptp_reset_ts_memory
>>>>> Info: ice_ptp_hw.c:6183 Scanning doc for function ice_ptp_init_phc
>>>>> Info: ice_ptp_hw.c:6215 Scanning doc for function ice_get_phy_tx_tstamp_ready
>>>>> Info: ice_ptp_hw.c:6247 Scanning doc for function ice_check_phy_tx_tstamp_ready
>>>>> Info: ice_ptp_hw.c:6273 Scanning doc for function ice_ptp_config_sfd
>>>>> Info: ice_ptp_hw.c:6293 Scanning doc for function refsync_pin_id_valid
>>>>
>>>> I didn't understand why I was seeing this as it should only be happening
>>>> if running kernel-doc in verbose mode. Then I discovered I had set
>>>> KBUILD_VERBOSE=0 in my environment.
>>>>
>>>> The python kernel-doc implementation reads this in the __init__ for
>>>> KernelFiles() on line 165:
>>>>
>>>>> if not verbose:
>>>>> verbose = bool(os.environ.get("KBUILD_VERBOSE", 0))
>>>>
>>>> After some debugging, I realized this reads KBUILD_VERBOSE as a string,
>>>> then converts it to a boolean using python's standard rules, so "0"
>>>> becomes true, which enables the verbose output.
>>>
>>> Looking at tools/docs/sphinx-build-wrapper, it implements verbosity
>>> by doing:
>>>
>>> verbose = bool(os.environ.get("KBUILD_VERBOSE", "") != "")
>>>
>>> which will also have the same problem as the one you detected.
>>>
>>
>> Yep.
>>
>>> Perhaps the right fix would be to first convert to int then to bool
>>> on both places, in a way that "" will also be handled properly.
>>> Perhaps with:
>>>
>>> try:
>>> verbose = bool(int(os.environ.get("KBUILD_VERBOSE", 0)))
>>> except ValueError:
>>> # Handles an eventual case where verbosity is not a number
>>> # like KBUILD_VERBOSE=""
>>> verbose = False
>>>>> This is in contrast to the (now removed) kernel-doc.pl script which
>>>> checked the value for a 1:
>>>>
>>>>> if (defined($ENV{'KBUILD_VERBOSE'}) && $ENV{'KBUILD_VERBOSE'} =~ '1')
>>>> The same behavior happens if you assign V=0 on the command line or to
>>>> any other non-empty string, since when V is set on the command line it
>>>> sets KBUILD_VERBOSE.
>>>
>>> That's funny... we did test make V=0 htmldocs / make V=1 htmldocs
>>>
>>
>> Strange. The Makefile does this:
>>
>> ifeq ("$(origin V)", "command line")
>> KBUILD_VERBOSE = $(V)
>> endif
>>
>> I can see KBUILD_VERBOSE=0 from the top level Makefile, but you're right
>> it doesn't seem to trigger the environment variable..
>>
>>> It sounds that the problem is only if you explicitly set it without
>>> relying on gnu make.
>>>
>>
>> Adding some warn prints I do see the Makefile sets KBUILD_VERBOSE=0 when
>> you do V=0.. and it has an export clause for KBUILD_VERBOSE
>>
>> Oh, it might be your particular build doesn't have W=1 so checkdoc isn't
>> being defined and thus kernel-doc isn't running?
>>
>> If I do "make W=1 V=0" I do actually see these lines:
>>
>>> Info: ../drivers/pci/pci.c:3646 Scanning doc for function pci_acs_init
>>> Info: ../drivers/pci/pci.c:3663 Scanning doc for function pci_enable_atomic_ops_to_root
>>> Info: ../drivers/pci/pci.c:3746 Scanning doc for function pci_release_region
>>> Info: ../drivers/pci/pci.c:3772 Scanning doc for function __pci_request_region
>>> Info: ../drivers/pci/pci.c:3820 Scanning doc for function pci_request_region
>>> Info: ../drivers/pci/pci.c:3841 Scanning doc for function pci_release_selected_regions
>>> Info: ../drivers/pci/pci.c:3879 Scanning doc for function pci_request_selected_regions
>>> Info: ../drivers/pci/pci.c:3894 Scanning doc for function pci_request_selected_regions_exclusive
>>> Info: ../drivers/pci/pci.c:3910 Scanning doc for function pci_release_regions
>>> Info: ../drivers/pci/pci.c:3925 Scanning doc for function pci_request_regions
>>> Info: ../drivers/pci/pci.c:3944 Scanning doc for function pci_request_regions_exclusive
>>> Info: ../drivers/pci/pci.c:4026 Scanning doc for function pci_remap_iospace
>>> Info: ../drivers/pci/pci.c:4062 Scanning doc for function pci_unmap_iospace
>>> Info: ../drivers/pci/pci.c:4097 Scanning doc for function pcibios_setup
>>> Info: ../drivers/pci/pci.c:4109 Scanning doc for function pcibios_set_master
>>> Info: ../drivers/pci/pci.c:4136 Scanning doc for function pci_set_master
>>> Info: ../drivers/pci/pci.c:4150 Scanning doc for function pci_clear_master
>>> Info: ../drivers/pci/pci.c:4160 Scanning doc for function pci_set_cacheline_size
>>> Info: ../drivers/pci/pci.c:4198 Scanning doc for function pci_set_mwi
>>> Info: ../drivers/pci/pci.c:4229 Scanning doc for function pci_try_set_mwi
>>> Info: ../drivers/pci/pci.c:4248 Scanning doc for function pci_clear_mwi
>>> Info: ../drivers/pci/pci.c:4268 Scanning doc for function pci_disable_parity
>>> Info: ../drivers/pci/pci.c:4285 Scanning doc for function pci_intx
>>> Info: ../drivers/pci/pci.c:4310 Scanning doc for function pci_wait_for_pending_transaction
>>> Info: ../drivers/pci/pci.c:4326 Scanning doc for function pcie_flr
>>> Info: ../drivers/pci/pci.c:4366 Scanning doc for function pcie_reset_flr
>>> Info: ../drivers/pci/pci.c:4443 Scanning doc for function pci_pm_reset
>>> Info: ../drivers/pci/pci.c:4497 Scanning doc for function pcie_wait_for_link_status
>>> Info: ../drivers/pci/pci.c:4527 Scanning doc for function pcie_retrain_link
>>> Info: ../drivers/pci/pci.c:4592 Scanning doc for function pcie_wait_for_link_delay
>>> Info: ../drivers/pci/pci.c:4642 Scanning doc for function pcie_wait_for_link
>>> Info: ../drivers/pci/pci.c:4679 Scanning doc for function pci_bridge_wait_for_secondary_bus
>>> Info: ../drivers/pci/pci.c:4818 Scanning doc for function pci_bridge_secondary_bus_reset
>>> Info: ../drivers/pci/pci.c:5077 Scanning doc for function __pci_reset_function_locked
>>> Info: ../drivers/pci/pci.c:5135 Scanning doc for function pci_init_reset_methods
>>> Info: ../drivers/pci/pci.c:5167 Scanning doc for function pci_reset_function
>>> Info: ../drivers/pci/pci.c:5214 Scanning doc for function pci_reset_function_locked
>>> Info: ../drivers/pci/pci.c:5252 Scanning doc for function pci_try_reset_function
>
>
>>
>>>> Of course, I can remove KBUILD_VERBOSE from my environment, I'm not
>>>> entirely sure when or why I added it.
>>>>
>>>> Would think it would make sense to update the kdoc_files.py script to
>>>> check and interpret the string value the same way the perl script used
>>>> to? It seems reasonable to me that users might set "V=0" thinking that
>>>> it disables the verbosity. Other verbosity checks are based on the
>>>> string containing a 1,
>>>
>>> kernel-doc has a set of "-W" flags to control its verbosity. Direct
>>> support for KBUILD_VERBOSE was added there just to make it bug-compatible
>>> with kernel-doc.pl when building via Makefile.
>>>
>>
>> Right.
>>
>>> Yet, as using it via "make htmldocs" don't use "-W", IMO it makes
>>> sense to ensure that "-Wall" is enabled if V=1.
>>>
>>
>> We enable -Wall if KBUILD_EXTRA_WARN contains a 2:
>>
>> ifeq ($(KBUILD_EXTMOD),)
>> ifneq ($(KBUILD_EXTRA_WARN),)
>> cmd_checkdoc = PYTHONDONTWRITEBYTECODE=1 $(PYTHON3) $(KERNELDOC) -none
>> $(KDOCFLAGS) \
>> $(if $(findstring 2, $(KBUILD_EXTRA_WARN)), -Wall) \
>> $<
>> endif
>> endif
>>
>> If KBUILD_EXTRA_WARN has 2 we do -Wall, and if its any non-zero value we
>> enable checkdoc. KBUILD_VERBOSE is handled internally to the script so
>> not part of the Make invocation.
>>
>> So V=0 only manifests if KBUILD_EXTRA_WARN is set.
>>
>> We set KBUILD_EXTRA_WARN in top level:
>>
>> ifeq ("$(origin W)", "command line")
>> KBUILD_EXTRA_WARN := $(W)
>> endif
>>
>> export KBUILD_EXTRA_WARN
>
> Good point.
>
>>
>>>> (some even use 2 for even more printing).
>>>
>>> Documentation had support for V=2, but this was dropped on this
>>> commit:
>>> c0d3b83100c8 ("kbuild: do not print extra logs for V=2")
>>>
>>
>> Looks like there's some stale leftover bits then:
>>
>> #
>> # If KBUILD_VERBOSE contains 1, the whole command is echoed.
>> # If KBUILD_VERBOSE contains 2, the reason for rebuilding is printed.
>> #
>> # To put more focus on warnings, be less verbose as default
>> # Use 'make V=1' to see the full commands
>> I don't have strong opinions either way.
>>
>>
>>>> I'm not entirely sure what the best implementation for python is to
>>>> avoid this misinterpretation, so I haven't drafted a proper patch yet.
>>>
>>> Perhaps something like the patch below (untested).
>>>
>>
>> The patch seems reasonable, though I don't know about the enabling other
>> errors, as those are controlled by W=2 right now. I don't personally
>> have objections to enabling them with V as well, but others might?
>
> As W=2 already turns those, I opted to just apply the fix
> for KBUILD_VERBOSE without touching -W macros:
>
> https://lore.kernel.org/linux-doc/7a99788db75630fb14828d612c0fd77c45ec1891.1774591065.git.mchehab+huawei@kernel.org/T/#u
>
>
> On a very quick test with:
>
> $ make W=2 V=0 drivers/pci/
>
> and with:
>
> $ KBUILD_VERBOSE="asdf" ./scripts/kernel-doc drivers/pci/ -none
> $ KBUILD_VERBOSE="" ./scripts/kernel-doc drivers/pci/ -none
> $ KBUILD_VERBOSE="0" ./scripts/kernel-doc drivers/pci/ -none
> $ KBUILD_VERBOSE=0 ./scripts/kernel-doc drivers/pci/ -none
>
> None of the above enabled verbosity.
>
> Using either one of the above:
>
> $ KBUILD_VERBOSE=1 ./scripts/kernel-doc drivers/pci/ -none
> $ KBUILD_VERBOSE="1" ./scripts/kernel-doc drivers/pci/ -none
>
> made it verbose.
>
> So, I guess this should be enough to fix the issue. Please test
> and reply to the patch if it works or not for you.
>
> Thanks,
> Mauro
It looks good to me. I'll reply with a tested-by on the patch.
Thanks,
Jake
^ permalink raw reply
* Re: [PATCH v3 4/9] lsm: framework for BPF integrity verification
From: Song Liu @ 2026-03-27 18:24 UTC (permalink / raw)
To: Blaise Boscaccy
Cc: Jonathan Corbet, Paul Moore, James Morris, Serge E. Hallyn,
Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf
In-Reply-To: <871ph5f99z.fsf@microsoft.com>
On Fri, Mar 27, 2026 at 10:54 AM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
>
> Song Liu <song@kernel.org> writes:
>
> > On Wed, Mar 25, 2026 at 11:07 PM Blaise Boscaccy
> > <bboscaccy@linux.microsoft.com> wrote:
> > [...]
> >> The first new callback, bpf_prog_load_integrity(), located within the
> >> security_bpf_prog_load() hook, is necessary to ensure that the integrity
> >> verification callbacks are executed before any of the existing LSMs
> >> are executed via the bpf_prog_load() callback. Reusing the existing
> >> bpf_prog_load() callback for integrity verification could result in LSMs
> >> not having access to the integrity verification results when asked to
> >> authorize the BPF program load in the bpf_prog_load() callback.
> >>
> >> The new LSM hook, security_bpf_prog_load_post_integrity(), is intended
> >> to be called from within LSMs performing BPF program integrity
> >> verification. It is used to report the verdict of the integrity
> >> verification to other LSMs enforcing access control policy on BPF
> >> program loads. LSMs enforcing such access controls should register a
> >> bpf_prog_load_post_integrity() callback to receive integrity verdicts.
> >
> > bpf_prog_load_post_integrity() is weird. Some questions about it:
> >
> > 1. Is it possible to call it from other LSMs (not hornet)? Specifically, is it
> > possible to call it from BPF LSM?
>
> There is nothing hornet exclusive about that security hook. If the BPF
> LSM folks wanted to use it they would probably need to implement a
> kfunc to invoke it.
Please also include the kfunc in v4.
Thanks,
Song
^ permalink raw reply
* [PATCH] docs: pt_BR: translate process/2.Process.rst
From: Daniel Castro @ 2026-03-27 18:17 UTC (permalink / raw)
To: danielmaraboo; +Cc: corbet, linux-doc, Daniel Castro
Add Brazilian Portuguese translation of the development process
document (Documentation/process/2.Process.rst), covering the
development cycle overview, patch lifecycle, subsystem trees,
staging trees, tools, mailing lists, and getting started with
kernel development.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Daniel Castro <arantescastro@gmail.com>
---
Documentation/translations/pt_BR/index.rst | 1 +
.../translations/pt_BR/process/2.Process.rst | 514 ++++++++++++++++++
2 files changed, 515 insertions(+)
create mode 100644 Documentation/translations/pt_BR/process/2.Process.rst
diff --git a/Documentation/translations/pt_BR/index.rst b/Documentation/translations/pt_BR/index.rst
index 4f7fcc3c66fb..edf19ddf8916 100644
--- a/Documentation/translations/pt_BR/index.rst
+++ b/Documentation/translations/pt_BR/index.rst
@@ -69,4 +69,5 @@ kernel e sobre como ver seu trabalho integrado.
Introdução <process/1.Intro>
Como começar <process/howto>
Requisitos mínimos <process/changes>
+ Como o processo de desenvolvimento funciona <process/2.Process>
Manuais dos mantenedores <process/maintainer-handbooks>
diff --git a/Documentation/translations/pt_BR/process/2.Process.rst b/Documentation/translations/pt_BR/process/2.Process.rst
new file mode 100644
index 000000000000..6443e80f0ecd
--- /dev/null
+++ b/Documentation/translations/pt_BR/process/2.Process.rst
@@ -0,0 +1,514 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Como o processo de desenvolvimento funciona
+===========================================
+
+O desenvolvimento do kernel Linux no início da década de 1990 era bastante
+informal, com um número relativamente pequeno de usuários e desenvolvedores
+envolvidos. Com uma base de usuários na casa dos milhões e cerca de 2.000
+desenvolvedores envolvidos ao longo de um ano, o kernel teve que desenvolver uma
+série de processos para manter o desenvolvimento fluindo sem problemas. Um
+sólido conhecimento de como o processo funciona é necessário para participar
+efetivamente dele.
+
+Visão geral
+-----------
+
+O kernel Linux utiliza um modelo de desenvolvimento contínuo ("rolling release")
+baseado em prazos flexíveis. Uma nova versão principal do kernel (que
+chamaremos, por exemplo, de 9.x) [1]_ ocorre a cada dois ou três meses, trazendo
+novos recursos, alterações internas na API e muito mais. Uma versão típica pode
+conter cerca de 13.000 conjuntos de alterações, com mudanças em várias centenas
+de milhares de linhas de código. Versões recentes, juntamente com suas datas,
+podem ser encontradas na `Wikipedia
+<https://en.wikipedia.org/wiki/Linux_kernel_version_history>`_.
+
+.. [1] Rigorosamente falando, o kernel Linux não utiliza o esquema de
+ versionamento semântico. Em vez disso, o par 9.x identifica a
+ versão principal como um número inteiro. Para cada versão, x é
+ incrementado, mas 9 é incrementado apenas se x for considerado
+ grande o suficiente (por exemplo, o Linux 5.0 é lançado após o
+ Linux 4.20).
+
+Segue-se um procedimento relativamente simples quanto à integração de
+patches para cada lançamento. No início de cada ciclo de desenvolvimento, a
+janela de fusão ("merge window") é considerada aberta. Nesse momento, o código
+que é considerado suficientemente estável (e que é aceito pela comunidade de
+desenvolvimento) é integrado ao kernel principal. A maior parte das
+alterações para
+um novo ciclo de desenvolvimento (e todas as principais alterações) será
+integrada durante esse período, a uma taxa próxima de 1.000 alterações
+("patches" ou "conjuntos de alterações") por dia.
+
+(Vale observar que as alterações integradas durante a
+janela de merge não surgem do nada; elas foram coletadas, testadas e
+preparadas com
+antecedência. O funcionamento desse processo será descrito em detalhes mais
+adiante).
+
+A janela de merge dura aproximadamente duas semanas. Ao final desse período, Linus
+Torvalds declarará que a janela está fechada e lançará o primeiro dos kernels
+"rc". Para o kernel que está destinado a ser o 9.x, por exemplo, o lançamento
+que ocorre ao final da janela de merge será chamado de 9.x-rc1. O lançamento
+-rc1 é
+o sinal de que o tempo para incorporar novos recursos passou e que o tempo para
+estabilizar o próximo kernel começou.
+
+Nas próximas seis a dez semanas, somente patches que corrijam problemas devem
+ser enviados para a versão principal ("mainline"). Ocasionalmente, uma mudança
+mais significativa será permitida, mas tais ocasiões são raras; desenvolvedores
+que tentam integrar novos recursos fora da janela de merge tendem a receber uma
+recepção pouco calorosa. Como regra geral, se você perder a janela de merge para
+um determinado recurso, o melhor a fazer é esperar pelo próximo ciclo de
+desenvolvimento. (Uma exceção ocasional é feita para drivers para hardware
+anteriormente não suportado; se eles não alterarem nenhum código já
+presente na árvore ("in-tree"), não podem causar regressões e podem ser
+adicionados com segurança a qualquer momento).
+
+À medida que as correções são incorporadas à mainline, o ritmo de envio de
+patches diminui gradualmente. Linus lança novos kernels -rc aproximadamente uma
+vez por semana; uma série normal costuma chegar entre o -rc6 e o -rc9 antes que
+o kernel seja considerado suficientemente estável para o lançamento da versão
+final. Nesse ponto, todo o processo se reinicia.
+
+Como exemplo, veja como foi o ciclo de desenvolvimento da versão 5.4 (todas as
+datas em 2019):
+
+ ============== ===================================================
+ 15 de setembro Lançamento da versão estável 5.3
+ 30 de setembro 5.4-rc1, janela de merge fechada
+ 6 de outubro 5.4-rc2
+ 13 de outubro 5.4-rc3
+ 20 de outubro 5.4-rc4
+ 27 de outubro 5.4-rc5
+ 3 de novembro 5.4-rc6
+ 10 de novembro 5.4-rc7
+ 17 de novembro 5.4-rc8
+ 24 de novembro 5.4 lançamento da versão estável ("stable release")
+ ============== ===================================================
+
+Como os desenvolvedores decidem quando encerrar o ciclo de desenvolvimento e
+criar a versão estável? A métrica mais importante utilizada é a lista de
+regressões em relação às versões anteriores. Nenhum bug é bem-vindo, mas
+aqueles que
+quebram sistemas que funcionavam no passado são considerados especialmente
+graves. Por esse motivo, patches que causam regressões são vistos com maus
+olhos e têm grande probabilidade de serem revertidas durante o período de
+estabilização.
+
+O objetivo dos desenvolvedores é corrigir todas as regressões conhecidas antes
+do lançamento da versão estável. Na prática, esse nível de perfeição é difícil
+de alcançar; há muitas variáveis em um projeto desse porte.
+Chega um ponto em que adiar o lançamento final só piora o problema; a pilha de
+alterações aguardando a próxima janela de merge aumentará, criando ainda mais
+regressões na próxima vez. Portanto, a maioria dos kernels é lançada com algumas
+regressões conhecidas, embora, idealmente, nenhuma delas seja grave.
+
+Assim que uma versão estável é lançada, sua manutenção contínua é transferida
+para a equipe estável ("stable team"), atualmente composta por Greg
+Kroah-Hartman e Sasha Levin. A equipe estável lançará atualizações
+ocasionais para
+a versão estável usando o esquema de numeração 9.x.y.
+
+Para ser considerado para uma versão de atualização ("update release"), um
+patch deve (1) corrigir um bug significativo e (2) já ter sido incorporado
+à mainline do próximo kernel de desenvolvimento. Os kernels normalmente
+recebem atualizações estáveis por um pouco mais de um ciclo de desenvolvimento
+após o lançamento inicial. Assim, por exemplo, o histórico do kernel 5.2
+era o seguinte (todas as datas em 2019):
+
+ ============== ===============================
+ 7 de julho 5.2 versão estável
+ 14 de julho 5.2.1
+ 21 de julho 5.2.2
+ 26 de julho 5.2.3
+ 28 de julho 5.2.4
+ 31 de julho 5.2.5
+ ... ...
+ 11 de outubro 5.2.21
+ ============== ===============================
+
+A versão 5.2.21 foi a última atualização estável da série 5.2.
+
+Alguns kernels são designados como kernels de "longo prazo"; eles receberão
+suporte por um período mais longo. Consulte o seguinte link para obter a lista
+de versões de kernel de longo prazo ativas e seus respectivos mantenedores:
+
+ https://www.kernel.org/category/releases.html
+
+A seleção de um kernel para suporte a longo prazo ("long-term support") é
+puramente uma questão de um mantenedor ter a necessidade e o tempo para manter
+essa versão. Não há planos conhecidos para suporte a longo prazo para qualquer
+versão futura específica.
+
+O ciclo de vida de um patch
+---------------------------
+
+Os patches não vão diretamente do teclado do desenvolvedor para o kernel
+principal ("mainline"). Em vez disso, existe um processo razoavelmente complexo
+(embora relativamente informal) projetado para garantir que cada patch seja
+revisado
+quanto à qualidade e que cada patch implemente uma alteração desejável no kernel
+mainline.
+
+Esse processo pode ser rápido para correções menores ou, no caso de mudanças
+grandes e controversas, levar anos. Grande parte da frustração dos
+desenvolvedores vem da falta de compreensão desse processo ou de tentativas de
+contorná-lo.
+
+Na esperança de reduzir essa frustração, este documento descreverá como um patch
+é inserido no kernel. O que se segue é uma introdução que descreve o
+processo de forma um tanto idealizada. Uma abordagem muito mais detalhada
+será apresentada em seções posteriores.
+
+As etapas pelas quais um patch passa são, geralmente:
+
+ - Projeto ("Design"). É aqui que os requisitos reais para o patch - e a
+ maneira como esses requisitos serão atendidos - são definidos. O trabalho
+ de projeto geralmente é feito sem envolver a comunidade, mas é melhor
+ fazer esse trabalho de forma aberta, se possível; isso pode economizar
+ muito tempo com reformulações posteriores.
+
+ - Revisão inicial ("Early review"). Os patches são enviados para a lista
+ de discussão relevante, e os desenvolvedores dessa lista respondem com
+ quaisquer comentários que possam ter. Esse processo deve revelar
+ quaisquer problemas importantes com um patch, se tudo correr bem.
+
+ - Revisão mais ampla ("Wider review"). Quando o patch estiver quase
+ pronto para inclusão na mainline, ele deve ser aceito por um mantenedor
+ de subsistema relevante - embora essa aceitação não seja uma garantia
+ de que o patch chegará à mainline. O patch aparecerá na árvore do
+ subsistema do mantenedor e nas árvores -next (descritas abaixo). Quando
+ o processo funciona, esta etapa leva a uma revisão mais extensa do
+ patch e à descoberta de quaisquer problemas resultantes da integração
+ deste patch com o trabalho que está sendo feito por outros.
+
+- Observe que a maioria dos mantenedores também tem empregos regulares,
+ portanto, o merge do seu patch pode não ser a prioridade máxima deles.
+ Se o seu patch estiver recebendo feedback sobre mudanças necessárias,
+ você deve implementá-las ou justificar por que não devem ser feitas.
+ Se o seu patch não tiver reclamações de revisão, mas não estiver sendo
+ integrado pelo mantenedor do subsistema ou driver apropriado, você deve
+ persistir na atualização do patch para o kernel atual para que ele se
+ aplique corretamente e continuar enviando-o para revisão e merge.
+
+ - Integração na mainline. Por fim, um patch bem-sucedido será incorporado
+ ao repositório mainline gerenciado por Linus Torvalds. Novos comentários
+ ou problemas podem surgir nesta etapa; é fundamental que o desenvolvedor
+ responda prontamente a eles e solucione quaisquer falhas que surjam.
+
+ - Versão estável. A base de usuários afetada pelo patch agora é ampla;
+ consequentemente, novas falhas podem ser detectadas.
+
+ - Manutenção a longo prazo ("long-term maintenance"). Embora seja
+ plenamente possível que um desenvolvedor se esqueça de seu código após a
+ integração, tal comportamento tende a causar uma má impressão na
+ comunidade. A incorporação do código alivia parte do fardo da
+ manutenção, uma vez que outros desenvolvedores corrigirão possíveis
+ falhas decorrentes de mudanças na API. Contudo, o autor original deve
+ manter a responsabilidade sobre o código, caso deseje que ele
+ permaneça útil a longo prazo.
+
+Um dos maiores erros cometidos pelos desenvolvedores do kernel (ou seus
+empregadores) é tentar reduzir o processo a uma única etapa de "integração à
+mainline". Essa abordagem invariavelmente leva à frustração de todos os
+envolvidos.
+
+Como os patches chegam ao Kernel
+--------------------------------
+
+Existe apenas uma pessoa que pode incorporar patches ao repositório mainline do
+kernel: Linus Torvalds. No entanto, dos mais de 9.500 patches integrados ao
+kernel 2.6.38, por exemplo, apenas 112 (cerca de 1,3%) foram selecionados
+diretamente por ele. O projeto do kernel há muito superou a escala em que um
+único desenvolvedor seria capaz de inspecionar e filtrar todas as contribuições
+sem auxílio. A solução adotada pela comunidade para gerenciar esse crescimento
+foi a implementação de uma hierarquia de mantenedores fundamentada em uma cadeia
+de confiança.
+
+A base de código do kernel é subdividida logicamente em subsistemas: rede,
+suporte a arquiteturas específicas, gerenciamento de memória, dispositivos de
+vídeo, entre outros. A maioria possui um mantenedor designado — um desenvolvedor
+com responsabilidade integral pelo código daquela área. Esses mantenedores
+atuam, em linhas gerais, como os guardiões de seus respectivos domínios, sendo
+os responsáveis por validar e aceitar patches para inclusão no kernel mainline.
+
+Cada mantenedor de subsistema gerencia sua própria versão da árvore de códigos
+do kernel, geralmente (mas nem sempre) utilizando a ferramenta de controle de
+versão git. Ferramentas como o git (e correlatas, como quilt ou mercurial)
+permitem que os mantenedores rastreiem uma lista de patches, incluindo
+informações de autoria e outros metadados. A qualquer momento, o mantenedor
+consegue identificar quais patches em seu repositório não constam na mainline.
+
+Quando a janela de merge é aberta, os mantenedores de alto nível solicitam que
+Linus realize o "pull" dos patches selecionados em seus respectivos repositórios
+para integração. Caso Linus concorde, o fluxo de patches sobe para o seu
+repositório, tornando-se parte do kernel mainline. O nível de atenção que Linus
+dedica a patches específicos recebidos em uma operação de pull varia; é evidente
+que, por vezes, ele os analisa minuciosamente. No entanto, como regra geral,
+Linus confia que os mantenedores de subsistemas não enviarão patches
+problemáticos para o topo da cadeia ("upstream").
+
+Os mantenedores de subsistemas, por sua vez, podem realizar o pull de patches de
+outros mantenedores. Por exemplo, a árvore de rede é construída a partir de
+patches que se acumularam primeiramente em árvores dedicadas a drivers de
+dispositivos de rede, redes sem fio, entre outras. Essa sequência de
+repositórios pode ser arbitrariamente longa, embora raramente exceda dois ou
+três elos. Cada mantenedor na cadeia confia naqueles que gerenciam as árvores de
+nível inferior; esse processo é conhecido como "cadeia de confiança".
+
+Claramente, em um sistema como este, a inclusão de patches no kernel depende de
+encontrar o mantenedor correto. Enviar patches diretamente para Linus não
+costuma ser o procedimento adequado.
+
+Árvores next ("Next trees")
+---------------------------
+
+A cadeia de árvores de subsistemas orienta o fluxo de patches para o kernel, mas
+também levanta uma questão interessante: e se alguém quiser analisar todos os
+patches que estão sendo preparados para a próxima janela de merge?
+Desenvolvedores estarão interessados em quais outras mudanças estão pendentes
+para verificar se há conflitos preocupantes; um patch que altere o protótipo de
+uma função essencial do kernel, por exemplo, entrará em conflito com quaisquer
+outros patches que utilizem a forma antiga dessa função. Revisores e testadores
+desejam acessar as mudanças em sua forma integrada antes que todas elas cheguem
+ao kernel mainline. Seria possível realizar o pull de alterações de todas as
+árvores de subsistemas relevantes, mas isso seria uma tarefa onerosa e propensa
+a erros.
+
+A resposta vem na forma das árvores -next, onde as árvores de subsistemas são
+reunidas para testes e revisão. A mais antiga dessas árvores, mantida por Andrew
+Morton, é chamada de "-mm" (sigla para "memory management", ou gerenciamento de
+memória, que deu origem ao nome). A árvore -mm integra patches de uma
+extensa lista de árvores de subsistemas e também contém alguns patches voltados
+para auxiliar na depuração ("debugging").
+
+Além disso, a -mm contém uma coleção significativa de patches selecionados
+diretamente por Andrew. Esses patches podem ter sido publicados em uma lista de
+discussão ("mailing list") ou podem se aplicar a uma parte do kernel para a qual
+não exista uma árvore de subsistema designada. Como resultado, a -mm opera como
+uma espécie de árvore de subsistema de última instância; se não houver outro
+caminho óbvio para um patch chegar à mainline, é provável que ele termine na
+-mm. Patches diversos que se acumulam na -mm serão, eventualmente, encaminhados
+para uma árvore de subsistema apropriada ou enviados diretamente para Linus. Em
+um ciclo de desenvolvimento típico, aproximadamente 5% a 10% dos patches que
+entram na mainline chegam lá via -mm.
+
+O patch -mm atual está disponível no diretório "mmotm" (-mm of the moment) em:
+
+ https://www.ozlabs.org/~akpm/mmotm/
+
+O uso da árvore MMOTM, entretanto, tende a ser uma experiência frustrante; há
+uma chance real de que o código sequer compile.
+
+A árvore principal para a integração de patches do próximo ciclo é a linux-next,
+mantida por Mark Brown. A linux-next é, por concepção, uma captura ("snapshot") do
+que se espera que a mainline venha a ser após o fechamento da próxima janela de
+merge. As árvores linux-next são anunciadas nas listas de discussão linux-kernel e
+linux-next assim que são consolidadas; elas podem ser baixadas em:
+
+ https://www.kernel.org/pub/linux/kernel/next/
+
+A linux-next tornou-se parte integrante do processo de desenvolvimento do
+kernel; idealmente, todos os patches incorporados durante uma determinada janela
+de merge devem ter sido integrados à linux-next algum tempo antes da abertura
+dessa janela.
+
+Árvores de Staging ("Staging trees")
+------------------------------------
+
+A árvore de código-fonte do kernel contém o diretório drivers/staging/, que
+abriga diversos subdiretórios de drivers ou sistemas de arquivos em processo
+de inclusão na árvore do kernel. Eles permanecem em drivers/staging/ enquanto
+demandam aprimoramentos; uma vez concluídos, podem ser movidos para o kernel
+propriamente dito. Esta é uma forma de rastrear drivers que ainda não
+atingiram os padrões de codificação ou qualidade do kernel Linux, mas que a
+comunidade pode desejar utilizar e acompanhar o desenvolvimento.
+
+Atualmente, Greg Kroah-Hartman mantém a árvore de staging. Drivers que ainda
+necessitam de ajustes são enviados a ele, cada um ocupando seu próprio
+subdiretório em drivers/staging/. Juntamente com os arquivos-fonte do driver,
+deve constar um arquivo TODO no diretório. O arquivo TODO lista as tarefas
+pendentes para a aceitação no kernel propriamente dito, além de uma lista de
+pessoas que devem ser copiadas ("Cc'd") em quaisquer patches relacionados ao
+driver. As regras atuais exigem que os drivers submetidos ao staging devam,
+no mínimo, compilar corretamente.
+
+O staging pode ser um caminho relativamente simples para inserir novos
+drivers na mainline onde, com sorte, atrairão a atenção de outros
+desenvolvedores e evoluirão rapidamente. Entretanto, a entrada no staging
+não encerra o processo; códigos que não apresentem progresso regular acabarão
+removidos. Além disso, os distribuidores tendem a ser relutantes em habilitar
+drivers de staging. Portanto, o staging é, na melhor das hipóteses, uma etapa
+temporária no percurso para se tornar um driver adequado para a mainline.
+
+Ferramentas
+-----------
+
+Conforme observado no texto anterior, o processo de desenvolvimento do
+kernel depende fortemente da capacidade de orquestrar coleções de patches em
+diversas direções. O sistema como um todo não funcionaria com a eficiência
+atual sem o auxílio de ferramentas adequadamente robustas. Tutoriais sobre o
+uso dessas ferramentas estão além do escopo deste documento, mas cabe aqui
+apresentar algumas orientações básicas.
+
+O sistema de gerenciamento de código-fonte predominante na comunidade do
+kernel é, de longe, o git. O git é um dos diversos sistemas de controle de
+versão distribuídos desenvolvidos pela comunidade de software livre. Ele é
+altamente otimizado para o desenvolvimento do kernel, apresentando excelente
+desempenho ao lidar com grandes repositórios e volumes massivos de patches.
+Também possui a reputação de ser complexo para aprender e operar, embora
+tenha evoluído significativamente com o tempo. Algum nível de domínio da
+ferramenta é praticamente um requisito para desenvolvedores de kernel; mesmo
+que não a utilizem em seu próprio fluxo de trabalho, precisarão do git para
+acompanhar as atividades de outros desenvolvedores (e da mainline).
+
+O git já está disponível nos repositórios de quase todas as distribuições
+Linux. A página oficial do projeto encontra-se em:
+
+ https://git-scm.com/
+
+Essa página contém links para documentações e tutoriais.
+
+Entre os desenvolvedores de kernel que não utilizam o git, a escolha mais
+popular é, quase certamente, o Mercurial:
+
+ https://www.selenic.com/mercurial/
+
+O Mercurial compartilha muitos recursos com o git, mas oferece uma interface
+que muitos consideram mais fácil de usar.
+
+A outra ferramenta que vale a pena conhecer é o Quilt:
+
+ https://savannah.nongnu.org/projects/quilt/
+
+O quilt é um sistema de gerenciamento de patches, em vez de um sistema de
+gerenciamento de código-fonte. Ele não rastreia o histórico ao longo do
+tempo; em vez disso, é orientado ao rastreamento de um conjunto específico de
+alterações em relação a uma base de código em evolução. Alguns mantenedores
+de subsistemas importantes usam o quilt para gerenciar patches destinados ao
+upstream. Para o gerenciamento de certos tipos de árvores (como a -mm, por
+exemplo), o quilt é a melhor ferramenta para o trabalho.
+
+Listas de discussão ("Mailing lists")
+-------------------------------------
+
+Grande parte do trabalho de desenvolvimento do kernel Linux é realizada por
+meio de listas de discussão ("mailing lists"). É difícil participar
+plenamente da comunidade sem ingressar em, pelo menos, uma lista em algum
+lugar. No entanto, as listas de discussão do Linux também representam um
+risco potencial para os desenvolvedores, que podem acabar soterrados por uma
+carga de mensagens eletrônicas, infringir as convenções utilizadas nas listas
+do Linux, ou ambos.
+
+A maioria das listas de discussão do kernel está hospedada em kernel.org; a
+lista principal pode ser encontrada em:
+
+ https://subspace.kernel.org
+
+Existem listas hospedadas em outros locais; por favor, verifique o arquivo
+MAINTAINERS para encontrar a lista relevante de qualquer subsistema
+específico.
+
+A principal lista de discussão para o desenvolvimento do kernel é,
+naturalmente, a linux-kernel. Esta lista é um lugar intimidador; o volume
+pode chegar a 500 mensagens por dia, o nível de ruído é alto, as conversas
+podem ser extremamente técnicas e os participantes nem sempre estão
+preocupados em demonstrar um alto grau de polidez. No entanto, não há outro
+lugar onde a comunidade de desenvolvimento do kernel se reúna como um todo;
+desenvolvedores que evitam esta lista perderão informações importantes.
+
+Existem algumas dicas que podem ajudar na sobrevivência à linux-kernel:
+
+ - Direcione as mensagens da lista para uma pasta separada, em vez de
+ sua caixa de entrada principal. É preciso ser capaz de ignorar o fluxo
+ de mensagens por períodos prolongados.
+
+ - Não tente acompanhar todas as conversas — ninguém consegue. É importante
+ filtrar tanto pelo tópico de interesse (embora conversas longas possam se
+ desviar do assunto original sem alterar o campo de assunto do e-mail)
+ quanto pelas pessoas que estão participando.
+
+ - Não alimente os trolls. Se alguém estiver tentando provocar uma reação
+ irritada, ignore-o.
+
+ - Ao responder a um e-mail da linux-kernel (ou de outras listas), preserve
+ o cabeçalho Cc: para todos os envolvidos. Na ausência de um motivo forte
+ (como uma solicitação explícita), você nunca deve remover destinatários.
+ Certifique-se sempre de que a pessoa a quem você está respondendo esteja
+ na lista de Cc:. Essa convenção também torna desnecessário pedir
+ explicitamente para ser copiado nas respostas às suas postagens.
+
+ - Pesquise nos arquivos da lista (e na rede como um todo) antes de fazer
+ perguntas. Alguns desenvolvedores podem ficar impacientes com pessoas
+ que claramente não fizeram o "dever de casa".
+
+ - Use respostas intercaladas ("inline"), o que torna sua resposta mais
+ fácil de ler. (Ou seja, evite o "top-posting" — a prática de colocar
+ sua resposta acima do texto citado ao qual você está respondendo).
+ Para mais detalhes, veja:
+ :ref:`Documentation/process/submitting-patches.rst <interleaved_replies>`.
+
+ - Pergunte na lista de discussão correta. A linux-kernel pode ser o
+ ponto de encontro geral, mas não é o melhor lugar para encontrar
+ desenvolvedores de todos os subsistemas.
+
+O último ponto — encontrar a lista de discussão correta — é onde
+desenvolvedores iniciantes
+costumam errar. Alguém que faça uma pergunta relacionada a
+redes na linux-kernel quase certamente receberá uma sugestão educada para
+perguntar na lista netdev, pois essa é a lista frequentada pela maioria
+dos desenvolvedores de rede. Existem outras listas para os subsistemas
+SCSI, video4linux, IDE, sistemas de arquivos ("filesystems"), etc. O
+melhor lugar para procurar por listas de discussão é no arquivo MAINTAINERS que
+acompanha o código-fonte do kernel.
+
+Iniciando no desenvolvimento do Kernel
+--------------------------------------
+
+Dúvidas sobre como iniciar no processo de desenvolvimento do kernel são
+comuns — tanto por parte de indivíduos quanto de empresas. Igualmente
+comuns são os tropeços que tornam o início desse relacionamento mais difícil
+do que precisa ser.
+
+As empresas costumam buscar a contratação de desenvolvedores conhecidos
+para dar o pontapé inicial em um grupo de desenvolvimento. Esta pode ser,
+de fato, uma técnica eficaz. No entanto, ela também tende a ser cara e não
+contribui muito para expandir o grupo de desenvolvedores experientes de
+kernel. É possível capacitar desenvolvedores internos no desenvolvimento do
+kernel Linux, desde que se invista um pouco de tempo. Dedicar esse tempo
+pode dotar um empregador de um grupo de desenvolvedores que compreendem
+tanto o kernel quanto a empresa, e que também podem ajudar a treinar
+outros. A médio prazo, esta é frequentemente a abordagem mais proveitosa.
+
+Desenvolvedores individuais muitas vezes sentem-se, compreensivelmente,
+perdidos sobre por onde começar. Iniciar com um projeto grande pode ser
+intimidador; geralmente deseja-se "testar o terreno" com algo menor
+primeiro. Este é o ponto onde alguns desenvolvedores se lançam na criação
+de patches que corrigem erros ortográficos ou problemas menores de estilo
+de codificação (coding style). Infelizmente, tais patches criam um nível
+de ruído que distrai a comunidade de desenvolvimento como um todo; por
+isso, cada vez mais, eles são vistos com desdém. Novos desenvolvedores que
+desejam se apresentar à comunidade não receberão o tipo de recepção que
+esperam por meio desses métodos.
+
+Andrew Morton oferece este conselho para aspirantes a desenvolvedores de
+kernel
+
+::
+
+ O projeto nº 1 para todos os iniciantes no kernel certamente deve
+ ser "certificar-se de que o kernel funcione perfeitamente o tempo
+ todo em todas as máquinas em que você puder colocar as mãos".
+ Geralmente, a maneira de fazer isso é trabalhar com outros para
+ que as coisas sejam corrigidas (isso pode exigir persistência!),
+ mas tudo bem — isso faz parte do desenvolvimento do kernel.
+
+(https://lwn.net/Articles/283982/).
+
+Na ausência de problemas óbvios para corrigir, os desenvolvedores são
+aconselhados a olhar para as listas atuais de regressões e bugs abertos em
+geral. Nunca há escassez de problemas que precisem de correção; ao abordar
+essas questões, os desenvolvedores ganharão experiência com o processo
+enquanto, ao mesmo tempo, constroem respeito junto ao restante da
+comunidade de desenvolvimento.
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v3 4/9] lsm: framework for BPF integrity verification
From: Blaise Boscaccy @ 2026-03-27 17:54 UTC (permalink / raw)
To: Song Liu
Cc: Jonathan Corbet, Paul Moore, James Morris, Serge E. Hallyn,
Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf
In-Reply-To: <CAPhsuW6P-K=nTPxBk0_Wd0E1wDCBqb=uUQzmzpEg2NJub=L+dg@mail.gmail.com>
Song Liu <song@kernel.org> writes:
> On Wed, Mar 25, 2026 at 11:07 PM Blaise Boscaccy
> <bboscaccy@linux.microsoft.com> wrote:
> [...]
>> The first new callback, bpf_prog_load_integrity(), located within the
>> security_bpf_prog_load() hook, is necessary to ensure that the integrity
>> verification callbacks are executed before any of the existing LSMs
>> are executed via the bpf_prog_load() callback. Reusing the existing
>> bpf_prog_load() callback for integrity verification could result in LSMs
>> not having access to the integrity verification results when asked to
>> authorize the BPF program load in the bpf_prog_load() callback.
>>
>> The new LSM hook, security_bpf_prog_load_post_integrity(), is intended
>> to be called from within LSMs performing BPF program integrity
>> verification. It is used to report the verdict of the integrity
>> verification to other LSMs enforcing access control policy on BPF
>> program loads. LSMs enforcing such access controls should register a
>> bpf_prog_load_post_integrity() callback to receive integrity verdicts.
>
> bpf_prog_load_post_integrity() is weird. Some questions about it:
>
> 1. Is it possible to call it from other LSMs (not hornet)? Specifically, is it
> possible to call it from BPF LSM?
There is nothing hornet exclusive about that security hook. If the BPF
LSM folks wanted to use it they would probably need to implement a
kfunc to invoke it.
> 2. This set does not include any LSMs that attach functions to
> bpf_prog_load_post_integrity. This is against the new LSM hook policy.
> I guess the plan is to add LSM users in follow up patches? Could you
> please include at least some of such code in this patchset? This will
> help folks understand the use case.
>
Sure thing. I will be sending out a v4 with additional patches that has
a concrete user of bpf_prog_load_post_integrity.
> Thanks,
> Song
>
> [...]
^ permalink raw reply
* Re: [PATCH] Documentation: Provide hints on how to debug Python GDB scripts
From: Radu Rendec @ 2026-03-27 17:50 UTC (permalink / raw)
To: Florian Fainelli, linux-kernel, akpm
Cc: tglx, Jonathan Corbet, Shuah Khan, Illia Ostapyshyn,
open list:DOCUMENTATION PROCESS, open list:DOCUMENTATION
In-Reply-To: <20260326233226.2248817-1-florian.fainelli@broadcom.com>
On Thu, 2026-03-26 at 16:32 -0700, Florian Fainelli wrote:
> By default GDB does not print a full stack of its integrated Python
> interpreter, thus making the debugging of GDB scripts more painful than
> it has to be.
>
> Suggested-by: Radu Rendec <radu@rendec.net>
> Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
> ---
> Documentation/process/debugging/gdb-kernel-debugging.rst | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/Documentation/process/debugging/gdb-kernel-debugging.rst b/Documentation/process/debugging/gdb-kernel-debugging.rst
> index 9475c759c722..53e225760a4d 100644
> --- a/Documentation/process/debugging/gdb-kernel-debugging.rst
> +++ b/Documentation/process/debugging/gdb-kernel-debugging.rst
> @@ -173,3 +173,12 @@ this is just a snapshot of the initial version::
>
> Detailed help can be obtained via "help <command-name>" for commands and "help
> function <function-name>" for convenience functions.
> +
> +Debugging GDB scripts
> +---------------------
> +
> +GDB does not enable a full Python backtrace which can make debugging GDB
> +scripts more difficult than necessary. The following will allow for printing a
> +full backtrace of the python environment::
> +
> + (gdb) set python print-stack full
Reviewed-by: Radu Rendec <radu@rendec.net>
^ permalink raw reply
* Re: [PATCH v8 2/2] hwmon: temperature: add support for EMC1812
From: Guenter Roeck @ 2026-03-27 17:29 UTC (permalink / raw)
To: Marius.Cristea
Cc: corbet, linux-hwmon, devicetree, robh, linux-kernel, krzk+dt,
linux-doc, conor+dt
In-Reply-To: <350d1d2bf73c11c2c311c4ae6bf1b8b423151113.camel@microchip.com>
On 3/27/26 09:30, Marius.Cristea@microchip.com wrote:
> Hi Guenther,
>
> Thanks for the review, please see my comments below:
>
> ...
>
>>
>>
>>
>>
>>> +static int emc1812_init(struct emc1812_data *priv)
>>> +{
>>> + int ret;
>>> + u8 val;
>>> +
>>> + /*
>>> + * Set default values in registers. APDD, RECD12 and RECD34
>>> are active
>>> + * on 0. Set ALERT pin to be in comparator mode.
>>> + * Set the device to be in Run (Active) state and converting
>>> on all
>>> + * channels.
>>> + * Don't change conversion rate. After reset, default is 4
>>> conversions/seconds.
>>> + * The temperature measurement range is -64°C to +191.875°C.
>>> + */
>>> + val = FIELD_PREP(EMC1812_CFG_MSKAL, 1) |
>>> + FIELD_PREP(EMC1812_CFG_RS, 0) |
>>> + FIELD_PREP(EMC1812_CFG_ATTHM, 1) |
>>> + FIELD_PREP(EMC1812_CFG_RECD12, !priv->recd12_en) |
>>> + FIELD_PREP(EMC1812_CFG_RECD34, !priv->recd34_en) |
>>> + FIELD_PREP(EMC1812_CFG_RANGE, 1) |
>>> + FIELD_PREP(EMC1812_CFG_DA_ENA, 0) |
>>> + FIELD_PREP(EMC1812_CFG_APDD, !priv->apdd_en);
>>> +
>>
>> I assume it is on purpose that the defaults for EMC1812_CFG_RECD12
>> and
>> EMC1812_CFG_RECD34 deviate from the chip default (chip: enabled;
>> driver:
>> disabled).
>>
>
> Yes, EMC1812_CFG_ATTHM was set in order for the alerts to be clear
> automaticaly when the limits goes back to normal.
>
> The EMC1812_CFG_RANGE is set to extended range in order to be able to
> measure from the -64 to 191,875 degree Celsius.
>
> The EMC1812_CFG_MSKAL could be left at the "reset", so I will change it
> to 0.
>
> The EMC1812_CFG_RECD12 and EMC1812_CFG_RECD34 will be set based on the
> device tree setting and is related to the hardware and if the system
> designer wants to enable or disable the resistance error correction.
>
>
>>> + ret = regmap_write(priv->regmap, EMC1812_CFG_ADDR, val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + ret = regmap_write(priv->regmap, EMC1812_THRM_HYS_ADDR,
>>> 0x0A);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + ret = regmap_write(priv->regmap, EMC1812_CONSEC_ALERT_ADDR,
>>> 0x70);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + ret = regmap_write(priv->regmap, EMC1812_FILTER_SEL_ADDR, 0);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + ret = regmap_write(priv->regmap, EMC1812_HOTTEST_CFG_ADDR,
>>> 0);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + /* Enables the beta compensation factor auto-detection
>>> function for beta1 and beta2 */
>>> + ret = regmap_write(priv->regmap,
>>> EMC1812_EXT1_BETA_CONFIG_ADDR,
>>> + EMC1812_BETA_LOCK_VAL);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + ret = regmap_write(priv->regmap,
>>> EMC1812_EXT2_BETA_CONFIG_ADDR,
>>
>> AI review thinks that this register only exists on EMC1812. I don't
>> find that detail in the datasheet, but it is odd that there are two
>> registers
>> with supposedly the same functionality.
>>
>>
>
> All devices "have" the EMC1812_EXT2_BETA_CONFIG register (I mean if you
> are writing something to it, there will be no NAK on the i2c bus, but
> the value read back will be "0" for the devices that has the register
> not writable).
> EMC1812 having only one external channel, will not have the
> EMC1812_EXT2_BETA_CONFIG writable.
>
Ah, so the second register is for the second external channel. Seems
obvious, thinking about it ;-). Still, would it make sense to only write
the register if the second external channel actually exists ?
Thanks,
Guenter
^ permalink raw reply
* Re: [PATCH 0/4] hwmon: Add WITRN USB tester driver
From: Rong Zhang @ 2026-03-27 17:23 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc
In-Reply-To: <cc67f9e5-9f6d-40cd-bb89-9de0b084d42c@roeck-us.net>
Hi Guenter,
Thanks for your detailed elaboration. It's very helpful.
On Fri, 2026-03-27 at 08:42 -0700, Guenter Roeck wrote:
> On 3/27/26 05:01, Rong Zhang wrote:
> > Hi Guenter,
> >
> > Thanks a lot for your review and applying patch 1 :-)
> >
> > On Thu, 2026-03-26 at 17:05 -0700, Guenter Roeck wrote:
> > > On 3/26/26 12:19, Rong Zhang wrote:
> > > > WITRN produces a series of devices to monitor power characteristics of
> > > > USB connections and display those on a on-device display. Most of them
> > > > contain an additional port which exposes the measurements via USB HID.
> > > >
> > > > These devices report sensor values in IEEE-754 float (binary32) format.
> > > > The driver must perform floating-point number to integer conversions to
> > > > provide hwmon channels. Meanwhile, they also report accumulative float
> > > > values, and simple division or multiplication turns them into useful
> > > > hwmon channels.
> > > >
> > > > Patch 1 adds label support for 64-bit energy attributes, as the driver
> > > > needs it.
> > > >
> > > > Patch 2 adds a helper module for floating-point to integer conversions,
> > > > so that the conversion, multification and division methods can be used
> > > > in this driver as well as other drivers (I am also working on another
> > > > USB tester driver that needs it).
> > > >
> > > > Patch 3 adds a barebone HID driver for WITRN K2.
> > > >
> > > > Patch 4 adds hwmon channels and attributes to the driver.
> > > >
> > > > Signed-off-by: Rong Zhang <i@rong.moe>
> > > > ---
> > > > Rong Zhang (4):
> > > > hwmon: Add label support for 64-bit energy attributes
> > > > hwmon: New helper module for floating-point to integer conversions
> > >
> > > Nack. This is not a hwmon problem and should reside in a driver or in lib/
> > > (if it is needed by multiple drivers).
> >
> > Makes sense. I will try.
> >
> > >
> > > > hwmon: Add barebone HID driver for WITRN
> > >
> > > Nack. This is the wrong place for such a driver. It should reside somewhere
> > > in drivers/usb, or maybe in drivers/misc/.
> >
> > Hmm, I decided to place it here because:
> >
> > - It's not a hid_ll_driver but a dumb hid_driver, i.e., does no low
> > level things but just receives hid event from the HID core. It doesn't
> > even send any HID report to the device.
> >
> > - There has been numerous hid_driver in drivers/hwmon/.
> >
> > - There has been a similar USB tester driver in drivers/hwmon/, i.e.,
> > powerz. That's the major reason of my decision.
> >
>
> powerz is a pure hwmon driver, nothing else. It does not claim to be a
> "pure hid driver". If your driver _only_ provides a hwmon ABI, it would
> be acceptable. But then this and the next patch should be one patch,
> and it should only provide the hwmon ABI, nothing else (except maybe
> debugfs entries or sysfs entries attached directly to the HID device
> to display information such as serial number etc).
>
Understood. I will squash the two patches into a single patch, as it
will only provide hwmon ABI.
> Reading ROM/RAM addresses,
> as mentioned below, would be outside that scope.
Thanks for clarification. I am not going to support this, as dancing
with undocumented ROM/RAM access is too dangerous.
>
> The entire powerz driver is 269 lines of code. Your driver has well above
> 1,000 LOC.
>
The witrn driver itself is less than 700 LOC. It's longer because of
more channels and the need to call floating-point conversion and
arithmetic methods.
Other LOC mostly locates at the floating-point conversions and
arithmetic module. Since I will turn it into a generic lib, it won't be
a part of the witrn driver when I resubmit it. Thanks for the
suggestion.
> Your code has separate source files plus an include file.
The include file is for the floating-point conversion and arithmetic
module.
> That suggests that it does more than just reporting hardware monitoring
> attributes.
>
> I have not looked further into the code itself. My response is based purely
> on the subjects and code organization, which suggests that this is a HID
> driver with attached hardware monitoring.
I should have explained the structure of the series more detailedly.
Sorry for causing the misunderstanding.
>
> I am not sure I understand what all that would have to do with UCSI. UCSI
> support is implemented in drivers/usb/typec/ucsi. Anything associated
> with that protocol should be implemented there if it is part of the
> protocol.
Thanks for clarification. I think the feature has nothing to do with
UCSI because it's a passive dumb raw packet sniffer.
>
> > Could you kindly explain what kinds of driver can be accepted into
> > drivers/hwmon/?
> >
> > >
> > > > hwmon: (witrn) Add monitoring support
> > >
> > > This should be implemented as auxiliary driver.
> >
> > Could you kindly elaborate? Did you mean that if the device supports
> > multiple functionalities they should be implemented as multiple
> > auxiliary drivers in different subsystems?
> >
>
> Correct. Your series suggests that this would be the case.
Sorry for causing the misunderstanding again.
>
> > FYI, the USB tester doesn't provide any other meaningful feature that
> > fits other subsystems. The tester only provides two features through USB
> > HID: power measurements (this series), and raw PD packets sniffing.
> >
>
> Again, support for raw PD packets sniffing would be outside the scope
> of the hardware monitoring subsystem.
Agreed. I am not going to support it in any other subsystem either.
Users should use hidraw and parse everything themselves if they need it.
Thanks,
Rong
>
> Thanks,
> Guenter
>
> > As for the latter, the USB tester can sniff raw PD packets between the
> > source and sink if enabled in the device menu. It doesn't provide the
> > parsed result for packets, and the PC cannot ask the tester to send PD
> > packets. This doesn't fit UCSI at all, as a UCSI device operates at a
> > higher level and must accept commands. AFAIK such a dumb sniffer won't
> > fit any subsystem in the kernel. Hence, the only thing fits a subsystem
> > is its power measurements.
> >
> > All measurements supported by the official utility for Windows can be
> > found in `struct witrn_sensor'. Other than that, all extra features
> > provided by the utility are implemented in software and I didn't see any
> > extra USB packets other than querying the serial number [1] when I was
> > randomly messing around with the utility [2].
> >
> > I separated patch 3 and 4 just for easier review. If you are not in
> > favor of such a style, I will squash them.
> >
> > [1]: In fact, the utility directly asks the device to return the content
> > on several specific ROM/RAM addresses, and then the utility calculates
> > the serial number with an unknown algorithm. Reading a ROM/RAM address
> > seems to be the only command that the device accepts from the USB host.
> >
> > [2]: Yeah, their utility does not support PD packet capturing or
> > parsing. It seems that the manufacturer provides the PD sniffing feature
> > as is and expects users to capture it via hidraw or libusb and parse it
> > themselves.
> >
> > >
> > > Sashiko has a lot of feedback that you might want to address before
> > > resubmitting.
> > >
> > > https://sashiko.dev/#/patchset/20260327-b4-hwmon-witrn-v1-0-8d2f1896c045%40rong.moe
> >
> > Sashiko's feedback helps a lot. Thanks.
> >
> > Thanks,
> > Rong
> >
> > >
> > > Thanks,
> > > Guenter
> > >
> > > >
> > > > Documentation/hwmon/index.rst | 1 +
> > > > Documentation/hwmon/witrn.rst | 53 ++++
> > > > MAINTAINERS | 7 +
> > > > drivers/hwmon/Kconfig | 14 +
> > > > drivers/hwmon/Makefile | 2 +
> > > > drivers/hwmon/hwmon-fp.c | 262 ++++++++++++++++
> > > > drivers/hwmon/hwmon-fp.h | 212 +++++++++++++
> > > > drivers/hwmon/hwmon.c | 1 +
> > > > drivers/hwmon/witrn.c | 691 ++++++++++++++++++++++++++++++++++++++++++
> > > > 9 files changed, 1243 insertions(+)
> > > > ---
> > > > base-commit: 0138af2472dfdef0d56fc4697416eaa0ff2589bd
> > > > change-id: 20260327-b4-hwmon-witrn-a629b9040250
> > > >
> > > > Thanks,
> > > > Rong
> > > >
^ permalink raw reply
* [PATCH v8 2/3] hwmon: ltc4283: Add support for the LTC4283 Swap Controller
From: Nuno Sá via B4 Relay @ 2026-03-27 17:26 UTC (permalink / raw)
To: linux-gpio, linux-hwmon, devicetree, linux-doc
Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Linus Walleij, Bartosz Golaszewski
In-Reply-To: <20260327-ltc4283-support-v8-0-471de255d728@analog.com>
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 | 1796 +++++++++++++++++++++++++++++++++++++++
6 files changed, 2077 insertions(+)
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 559c32344cd3..eab95152abee 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 000000000000..ba88445e45f4
--- /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 13ae2f3db449..38d22cf622b7 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 7dd8381ba0d0..d8334edf5514 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 556e86d277b1..cb77938dbe07 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 000000000000..f5320f34eb45
--- /dev/null
+++ b/drivers/hwmon/ltc4283.c
@@ -0,0 +1,1796 @@
+// 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;
+
+ val = clamp_val(val, 0, st->power_max);
+ __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);
+ val = clamp_val(val, 0, isense_max);
+ 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;
+
+ ret = regmap_read(st->map, LTC4283_PGIO_CONFIG, ®_val);
+ if (ret)
+ return ret;
+
+ /* Can be latter overwritten in ltc4283_pgio_config() */
+ if (FIELD_GET(LTC4283_PGIO4_CFG_MASK, reg_val) < LTC4283_PGIO_FUNC_GPIO)
+ st->ext_fault = true;
+
+ /* 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)
+ return dev_err_probe(dev, func,
+ "Invalid adi,pgio1-func property\n");
+ 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)
+ 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++;
+ st->ext_fault = false;
+ } 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;
+ }
+
+ 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;
+ 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;
+ 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 < 0 && ret != -EINVAL)
+ 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 < 0 && ret != -EINVAL)
+ 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")) {
+ u32 chan = LTC4283_CHAN_DRNS - LTC4283_CHAN_ADI_1;
+
+ __clear_bit(LTC4283_CHAN_DRNS, &st->ch_enable_mask);
+ /*
+ * Then, let's by default disable DRNS from ADC2 given that it
+ * 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;
+
+ val = 1;
+ } else {
+ val = 0;
+ }
+
+ 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");
--
2.53.0
^ permalink raw reply related
* [PATCH v8 3/3] gpio: gpio-ltc4283: Add support for the LTC4283 Swap Controller
From: Nuno Sá via B4 Relay @ 2026-03-27 17:26 UTC (permalink / raw)
To: linux-gpio, linux-hwmon, devicetree, linux-doc
Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Linus Walleij, Bartosz Golaszewski,
Bartosz Golaszewski
In-Reply-To: <20260327-ltc4283-support-v8-0-471de255d728@analog.com>
From: Nuno Sá <nuno.sa@analog.com>
The LTC4283 device has up to 8 pins that can be configured as GPIOs.
Note that PGIO pins are not set as GPIOs by default so if they are
configured to be used as GPIOs we need to make sure to initialize them
to a sane default. They are set as inputs by default.
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
MAINTAINERS | 2 +
drivers/gpio/Kconfig | 15 +++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-ltc4283.c | 218 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 236 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 38d22cf622b7..0f4fc6c189f6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15143,9 +15143,11 @@ F: drivers/hwmon/ltc4282.c
LTC4283 HARDWARE MONITOR AND GPIO DRIVER
M: Nuno Sá <nuno.sa@analog.com>
+L: linux-gpio@vger.kernel.org
L: linux-hwmon@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/hwmon/adi,ltc4283.yaml
+F: drivers/gpio/gpio-ltc4283.c
F: drivers/hwmon/ltc4283.c
LTC4286 HARDWARE MONITOR DRIVER
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index b45fb799e36c..ba2621024598 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1758,6 +1758,21 @@ config GPIO_WM8994
endmenu
+menu "Auxiliary Bus GPIO drivers"
+ depends on AUXILIARY_BUS
+
+config GPIO_LTC4283
+ tristate "Analog Devices LTC4283 GPIO support"
+ depends on SENSORS_LTC4283
+ help
+ If you say yes here you want the GPIO function available in Analog
+ Devices LTC4283 Negative Voltage Hot Swap Controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called gpio-ltc4283.
+
+endmenu
+
menu "PCI GPIO expanders"
depends on PCI
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index c05f7d795c43..ff37aca5029c 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -102,6 +102,7 @@ obj-$(CONFIG_GPIO_LP873X) += gpio-lp873x.o
obj-$(CONFIG_GPIO_LP87565) += gpio-lp87565.o
obj-$(CONFIG_GPIO_LPC18XX) += gpio-lpc18xx.o
obj-$(CONFIG_GPIO_LPC32XX) += gpio-lpc32xx.o
+obj-$(CONFIG_GPIO_LTC4283) += gpio-ltc4283.o
obj-$(CONFIG_GPIO_MACSMC) += gpio-macsmc.o
obj-$(CONFIG_GPIO_MADERA) += gpio-madera.o
obj-$(CONFIG_GPIO_MAX3191X) += gpio-max3191x.o
diff --git a/drivers/gpio/gpio-ltc4283.c b/drivers/gpio/gpio-ltc4283.c
new file mode 100644
index 000000000000..6609443c5d62
--- /dev/null
+++ b/drivers/gpio/gpio-ltc4283.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices LTC4283 GPIO driver
+ *
+ * Copyright 2025 Analog Devices Inc.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#define LTC4283_PINS_MAX 8
+#define LTC4283_PGIOX_START_NR 4
+#define LTC4283_INPUT_STATUS 0x02
+#define LTC4283_PGIO_CONFIG 0x10
+#define LTC4283_PGIO_CFG_MASK(pin) \
+ GENMASK(((pin) - LTC4283_PGIOX_START_NR) * 2 + 1, (((pin) - LTC4283_PGIOX_START_NR) * 2))
+#define LTC4283_PGIO_CONFIG_2 0x11
+
+#define LTC4283_ADIO_CONFIG 0x12
+/* starts at bit 4 */
+#define LTC4283_ADIOX_CONFIG_MASK(pin) BIT((pin) + 4)
+#define LTC4283_PGIO_DIR_IN 3
+#define LTC4283_PGIO_DIR_OUT 2
+
+struct ltc4283_gpio {
+ struct gpio_chip gpio_chip;
+ struct regmap *regmap;
+};
+
+static int ltc4283_pgio_get_direction(const struct ltc4283_gpio *st, unsigned int off)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, LTC4283_PGIO_CONFIG, &val);
+ if (ret)
+ return ret;
+
+ val = field_get(LTC4283_PGIO_CFG_MASK(off), val);
+ if (val == LTC4283_PGIO_DIR_IN)
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int ltc4283_gpio_get_direction(struct gpio_chip *gc, unsigned int off)
+{
+ struct ltc4283_gpio *st = gpiochip_get_data(gc);
+ unsigned int val;
+ int ret;
+
+ if (off >= LTC4283_PGIOX_START_NR)
+ return ltc4283_pgio_get_direction(st, off);
+
+ ret = regmap_read(st->regmap, LTC4283_ADIO_CONFIG, &val);
+ if (ret)
+ return ret;
+
+ if (val & LTC4283_ADIOX_CONFIG_MASK(off))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int ltc4283_gpio_direction_set(const struct ltc4283_gpio *st,
+ unsigned int off, bool input)
+{
+ if (off >= LTC4283_PGIOX_START_NR) {
+ unsigned int val = LTC4283_PGIO_DIR_OUT;
+
+ if (input)
+ val = LTC4283_PGIO_DIR_IN;
+
+ val = field_prep(LTC4283_PGIO_CFG_MASK(off), val);
+ return regmap_update_bits(st->regmap, LTC4283_PGIO_CONFIG,
+ LTC4283_PGIO_CFG_MASK(off), val);
+ }
+
+ return regmap_update_bits(st->regmap, LTC4283_ADIO_CONFIG,
+ LTC4283_ADIOX_CONFIG_MASK(off),
+ field_prep(LTC4283_ADIOX_CONFIG_MASK(off), input));
+}
+
+static int __ltc4283_gpio_set_value(const struct ltc4283_gpio *st,
+ unsigned int off, int val)
+{
+ u32 reg = off < LTC4283_PGIOX_START_NR ? LTC4283_ADIO_CONFIG : LTC4283_PGIO_CONFIG_2;
+
+ return regmap_update_bits(st->regmap, reg, BIT(off),
+ field_prep(BIT(off), !!val));
+}
+
+static int ltc4283_gpio_direction_input(struct gpio_chip *gc, unsigned int off)
+{
+ struct ltc4283_gpio *st = gpiochip_get_data(gc);
+
+ return ltc4283_gpio_direction_set(st, off, true);
+}
+
+static int ltc4283_gpio_direction_output(struct gpio_chip *gc, unsigned int off, int val)
+{
+ struct ltc4283_gpio *st = gpiochip_get_data(gc);
+ int ret;
+
+ ret = ltc4283_gpio_direction_set(st, off, false);
+ if (ret)
+ return ret;
+
+ return __ltc4283_gpio_set_value(st, off, val);
+}
+
+static int ltc4283_gpio_get_value(struct gpio_chip *gc, unsigned int off)
+{
+ struct ltc4283_gpio *st = gpiochip_get_data(gc);
+ unsigned int val, reg;
+ int ret, dir;
+
+ dir = ltc4283_gpio_get_direction(gc, off);
+ if (dir < 0)
+ return dir;
+
+ if (dir == GPIO_LINE_DIRECTION_IN) {
+ ret = regmap_read(st->regmap, LTC4283_INPUT_STATUS, &val);
+ if (ret)
+ return ret;
+
+ /* ADIO1 is at bit 3. */
+ if (off < LTC4283_PGIOX_START_NR)
+ return !!(val & BIT(3 - off));
+
+ /* PGIO1 is at bit 7. */
+ return !!(val & BIT(7 - (off - LTC4283_PGIOX_START_NR)));
+ }
+
+ if (off < LTC4283_PGIOX_START_NR)
+ reg = LTC4283_ADIO_CONFIG;
+ else
+ reg = LTC4283_PGIO_CONFIG_2;
+
+ ret = regmap_read(st->regmap, reg, &val);
+ if (ret)
+ return ret;
+
+ return !!(val & BIT(off));
+}
+
+static int ltc4283_gpio_set_value(struct gpio_chip *gc, unsigned int off, int val)
+{
+ struct ltc4283_gpio *st = gpiochip_get_data(gc);
+
+ return __ltc4283_gpio_set_value(st, off, val);
+}
+
+static int ltc4283_init_valid_mask(struct gpio_chip *gc, unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ unsigned long *mask = dev_get_platdata(gc->parent);
+
+ bitmap_copy(valid_mask, mask, ngpios);
+ return 0;
+}
+
+static int ltc4283_gpio_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct ltc4283_gpio *st;
+ struct gpio_chip *gc;
+
+ st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ st->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!st->regmap)
+ return dev_err_probe(dev, -ENODEV,
+ "Failed to get regmap\n");
+
+ gc = &st->gpio_chip;
+ gc->parent = dev;
+ gc->get_direction = ltc4283_gpio_get_direction;
+ gc->direction_input = ltc4283_gpio_direction_input;
+ gc->direction_output = ltc4283_gpio_direction_output;
+ gc->get = ltc4283_gpio_get_value;
+ gc->set = ltc4283_gpio_set_value;
+ gc->init_valid_mask = ltc4283_init_valid_mask;
+ gc->can_sleep = true;
+
+ gc->base = -1;
+ gc->ngpio = LTC4283_PINS_MAX;
+ gc->label = adev->name;
+ gc->owner = THIS_MODULE;
+
+ return devm_gpiochip_add_data(dev, &st->gpio_chip, st);
+}
+
+static const struct auxiliary_device_id ltc4283_aux_id_table[] = {
+ { "ltc4283.gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, ltc4283_aux_id_table);
+
+static struct auxiliary_driver ltc4283_gpio_driver = {
+ .probe = ltc4283_gpio_probe,
+ .id_table = ltc4283_aux_id_table,
+};
+module_auxiliary_driver(ltc4283_gpio_driver);
+
+MODULE_AUTHOR("Nuno Sá <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("GPIO LTC4283 Driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH v8 0/3] hwmon: Add support for the LTC4283 Hot Swap Controller
From: Nuno Sá via B4 Relay @ 2026-03-27 17:26 UTC (permalink / raw)
To: linux-gpio, linux-hwmon, devicetree, linux-doc
Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Linus Walleij, Bartosz Golaszewski,
Bartosz Golaszewski
This is v8 for the LTC4283 how swap controller.
Similar to the LTC4282 device, we're clearing some fault logs in the
reset_history attributes.
---
Changes in v8:
- Patch 1:
* Improved descriptin in adi,fet-turn-off-disable so it's more clear;
* Same for adi,dvdt-inrush-control-disable;
* Fixed typo in adi,external-fault-retry-enable description.
- Patch 2:
* Use return value from clamp_val();
* Add missing 'ret < 0' for device_property_match_property_string()
calls;
* Fixed logic in when adi,vpower-drns-enable is enabled;
* Check default state of the pgio4 pin. So that we can, accordingly
flag st->ext_fault.
- Link to v7: https://lore.kernel.org/linux-hwmon/20260314-ltc4283-support-v7-0-1cda48e93802@analog.com/
---
---
Nuno Sá (3):
dt-bindings: hwmon: Document the LTC4283 Swap Controller
hwmon: ltc4283: Add support for the LTC4283 Swap Controller
gpio: gpio-ltc4283: Add support for the LTC4283 Swap Controller
.../devicetree/bindings/hwmon/adi,ltc4283.yaml | 272 +++
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/ltc4283.rst | 266 +++
MAINTAINERS | 9 +
drivers/gpio/Kconfig | 15 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-ltc4283.c | 218 +++
drivers/hwmon/Kconfig | 12 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/ltc4283.c | 1796 ++++++++++++++++++++
10 files changed, 2591 insertions(+)
---
base-commit: cd041796c380961f0e3c04d9627af80131608adc
change-id: 20260303-ltc4283-support-063f78acc5a4
--
Thanks!
- Nuno Sá
^ permalink raw reply
* [PATCH v8 1/3] dt-bindings: hwmon: Document the LTC4283 Swap Controller
From: Nuno Sá via B4 Relay @ 2026-03-27 17:26 UTC (permalink / raw)
To: linux-gpio, linux-hwmon, devicetree, linux-doc
Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Linus Walleij, Bartosz Golaszewski
In-Reply-To: <20260327-ltc4283-support-v8-0-471de255d728@analog.com>
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>
---
.../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 000000000000..05e2132ad4d8
--- /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 not turned off when a FET fault is detected.
+ type: boolean
+
+ adi,tmr-pull-down-disable:
+ description: Disables 2uA pull-down current on the TMR pin.
+ type: boolean
+
+ adi,dvdt-inrush-control-disable:
+ description:
+ Disables dV/dt inrush control during startup. In dV/dt mode, the inrush
+ current is limited by controlling a constant output voltage ramp rate.
+ When disabled, the inrush control mechanism is active current limiting.
+ type: boolean
+
+ 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 after an external fault.
+ type: boolean
+
+ 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
+
+ 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 830c6f076b00..13ae2f3db449 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.53.0
^ permalink raw reply related
* [PATCH] drm/vkms: Remove completed task from Plane Features
From: Cristian Ciocaltea @ 2026-03-27 17:18 UTC (permalink / raw)
To: Louis Chauvet, Haneen Mohammed, Simona Vetter, Melissa Wen,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Jonathan Corbet, Shuah Khan
Cc: kernel, dri-devel, linux-doc, linux-kernel
Since commit 0b9eff726d56 ("drm/vkms: Support setting custom background
color") the related TODO item in the 'Add Plane Features' section has
been completed, so remove it.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
Documentation/gpu/vkms.rst | 2 --
1 file changed, 2 deletions(-)
diff --git a/Documentation/gpu/vkms.rst b/Documentation/gpu/vkms.rst
index 1e79e62a6bc4..792eaeb43c82 100644
--- a/Documentation/gpu/vkms.rst
+++ b/Documentation/gpu/vkms.rst
@@ -217,8 +217,6 @@ Add Plane Features
There's lots of plane features we could add support for:
-- Add background color KMS property[Good to get started].
-
- Scaling.
- Additional buffer formats. Low/high bpp RGB formats would be interesting
---
base-commit: e88b5cc6d6e5b1ba257f00e5c186ba137e6e8bc3
change-id: 20260327-vkms-todo-upd-1e89d39284b3
^ permalink raw reply related
* Re: [PATCH v4 11/13] ima: Support staging and deleting N measurements entries
From: Roberto Sassu @ 2026-03-27 17:02 UTC (permalink / raw)
To: steven chen, corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg,
paul, jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, nramas, Roberto Sassu
In-Reply-To: <0e186faf-8111-4fd9-a7df-bff30f7fb20a@linux.microsoft.com>
On Thu, 2026-03-26 at 16:19 -0700, steven chen wrote:
> On 3/26/2026 10:30 AM, Roberto Sassu wrote:
> > From: Roberto Sassu <roberto.sassu@huawei.com>
> >
> > Add support for sending a value N between 1 and ULONG_MAX to the staging
> > interface. This value represents the number of measurements that should be
> > deleted from the current measurements list.
> >
> > This staging method allows the remote attestation agents to easily separate
> > the measurements that were verified (staged and deleted) from those that
> > weren't due to the race between taking a TPM quote and reading the
> > measurements list.
> >
> > In order to minimize the locking time of ima_extend_list_mutex, deleting
> > N entries is realized by staging the entire current measurements list
> > (with the lock), by determining the N-th staged entry (without the lock),
> > and by splicing the entries in excess back to the current measurements list
> > (with the lock). Finally, the N entries are deleted (without the lock).
> >
> > Flushing the hash table is not supported for N entries, since it would
> > require removing the N entries one by one from the hash table under the
> > ima_extend_list_mutex lock, which would increase the locking time.
> >
> > The ima_extend_list_mutex lock is necessary in ima_dump_measurement_list()
> > because ima_queue_staged_delete_partial() uses __list_cut_position() to
> > modify ima_measurements_staged, for which no RCU-safe variant exists. For
> > the staging with prompt flavor alone, list_replace_rcu() could have been
> > used instead, but since both flavors share the same kexec serialization
> > path, the mutex is required regardless.
> >
> > Link: https://github.com/linux-integrity/linux/issues/1
> > Suggested-by: Steven Chen <chenste@linux.microsoft.com>
> > Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> > ---
> > security/integrity/ima/Kconfig | 3 ++
> > security/integrity/ima/ima.h | 1 +
> > security/integrity/ima/ima_fs.c | 22 +++++++++-
> > security/integrity/ima/ima_queue.c | 70 ++++++++++++++++++++++++++++++
> > 4 files changed, 95 insertions(+), 1 deletion(-)
> >
> > diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
> > index e714726f3384..6ddb4e77bff5 100644
> > --- a/security/integrity/ima/Kconfig
> > +++ b/security/integrity/ima/Kconfig
> > @@ -341,6 +341,9 @@ config IMA_STAGING
> > It allows user space to stage the measurements list for deletion and
> > to delete the staged measurements after confirmation.
> >
> > + Or, alternatively, it allows user space to specify N measurements
> > + entries to be deleted.
> > +
> > On kexec, staging is reverted and staged measurements are prepended
> > to the current measurements list when measurements are copied to the
> > secondary kernel.
> > diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> > index 699b735dec7d..de0693fce53c 100644
> > --- a/security/integrity/ima/ima.h
> > +++ b/security/integrity/ima/ima.h
> > @@ -319,6 +319,7 @@ struct ima_template_desc *lookup_template_desc(const char *name);
> > bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
> > int ima_queue_stage(void);
> > int ima_queue_staged_delete_all(void);
> > +int ima_queue_staged_delete_partial(unsigned long req_value);
> > int ima_restore_measurement_entry(struct ima_template_entry *entry);
> > int ima_restore_measurement_list(loff_t bufsize, void *buf);
> > int ima_measurements_show(struct seq_file *m, void *v);
> > diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
> > index 39d9128e9f22..eb3f343c1138 100644
> > --- a/security/integrity/ima/ima_fs.c
> > +++ b/security/integrity/ima/ima_fs.c
> > @@ -28,6 +28,7 @@
> > * Requests:
> > * 'A\n': stage the entire measurements list
> > * 'D\n': delete all staged measurements
> > + * '[1, ULONG_MAX]\n' delete N measurements entries
> > */
> > #define STAGED_REQ_LENGTH 21
> >
> > @@ -319,6 +320,7 @@ static ssize_t ima_measurements_staged_write(struct file *file,
> > size_t datalen, loff_t *ppos)
> > {
> > char req[STAGED_REQ_LENGTH];
> > + unsigned long req_value;
> > int ret;
> >
> > if (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH)
> > @@ -346,7 +348,25 @@ static ssize_t ima_measurements_staged_write(struct file *file,
> > ret = ima_queue_staged_delete_all();
> > break;
> > default:
> > - ret = -EINVAL;
> > + if (ima_flush_htable) {
> > + pr_debug("Deleting staged N measurements not supported when flushing the hash table is requested\n");
> > + return -EINVAL;
> > + }
> > +
> > + ret = kstrtoul(req, 10, &req_value);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (req_value == 0) {
> > + pr_debug("Must delete at least one entry\n");
> > + return -EINVAL;
> > + }
> > +
> > + ret = ima_queue_stage();
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = ima_queue_staged_delete_partial(req_value);
> The default processing is "Trim N" idea plus performance improvement.
>
> Here do everything in one time. And this is what I said in v3.
>
> [PATCH v3 1/3] ima: Remove ima_h_table structure
> <https://lore.kernel.org/linux-integrity/c61aeaa79929a98cb3a6d30835972891fac3570f.camel@linux.ibm.com/T/#t>
In your approach you do:
lock ima_extend_measure_list_mutex
scan entries until N
cut list staged -> trim
unlock ima_extend_measure_list_mutex
In my approach I do:
lock ima_extend_measure_list_mutex
list replace active -> staged
unlock ima_extend_measure_list_mutex
scan entries until N
lock ima_extend_measure_list_mutex
cut list staged -> trim
splice staged ->active
unlock ima_extend_measure_list_mutex
So, I guess if you refer to less user space locking time, you mean one
lock/unlock and one list replace + list splice less in your solution.
In exchange, you propose to hold the lock in the kernel while scanning
N. I think it is a significant increase of kernel locking time vs a
negligible increase of user space locking time (in the kernel, all
processes need to wait for the ima_extend_measure_list_mutex to be
released, in user space it is just the agent waiting).
Roberto
> The important two parts of trimming is "trim N" and performance improvement.
>
> The performance improvement include two parts:
> hash table staging
> active log list staging
>
> And I think "Trim N" plus performance improvement is the right direction
> to go.
> Lots of code for two steps "stage and trim" "stage" part can be removed.
>
> Also race condition may happen if not holding the list all time in user
> space
> during attestation period: from stage, read list, attestation and trimming.
>
> So in order to improve the above user space lock time, "Trim T:N" can be
> used
> not to hold list long in user space during attestation.
>
> For Trim T:N, T represent total log trimmed since system boot up. Please
> refer to
> https://lore.kernel.org/linux-integrity/20260205235849.7086-1-chenste@linux.microsoft.com/T/#t
>
> Thanks,
>
> Steven
> > }
> >
> > if (ret < 0)
> > diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
> > index f5c18acfbc43..4fb557d61a88 100644
> > --- a/security/integrity/ima/ima_queue.c
> > +++ b/security/integrity/ima/ima_queue.c
> > @@ -371,6 +371,76 @@ int ima_queue_staged_delete_all(void)
> > return 0;
> > }
> >
> > +int ima_queue_staged_delete_partial(unsigned long req_value)
> > +{
> > + unsigned long req_value_copy = req_value;
> > + unsigned long size_to_remove = 0, num_to_remove = 0;
> > + struct list_head *cut_pos = NULL;
> > + LIST_HEAD(ima_measurements_trim);
> > + struct ima_queue_entry *qe;
> > + int ret = 0;
> > +
> > + /*
> > + * Safe walk (no concurrent write), not under ima_extend_list_mutex
> > + * for performance reasons.
> > + */
> > + list_for_each_entry(qe, &ima_measurements_staged, later) {
> > + size_to_remove += get_binary_runtime_size(qe->entry);
> > + num_to_remove++;
> > +
> > + if (--req_value_copy == 0) {
> > + /* qe->later always points to a valid list entry. */
> > + cut_pos = &qe->later;
> > + break;
> > + }
> > + }
> > +
> > + /* Nothing to remove, undoing staging. */
> > + if (req_value_copy > 0) {
> > + size_to_remove = 0;
> > + num_to_remove = 0;
> > + ret = -ENOENT;
> > + }
> > +
> > + mutex_lock(&ima_extend_list_mutex);
> > + if (list_empty(&ima_measurements_staged)) {
> > + mutex_unlock(&ima_extend_list_mutex);
> > + return -ENOENT;
> > + }
> > +
> > + if (cut_pos != NULL)
> > + /*
> > + * ima_dump_measurement_list() does not modify the list,
> > + * cut_pos remains the same even if it was computed before
> > + * the lock.
> > + */
> > + __list_cut_position(&ima_measurements_trim,
> > + &ima_measurements_staged, cut_pos);
> > +
> > + atomic_long_sub(num_to_remove, &ima_num_entries[BINARY_STAGED]);
> > + atomic_long_add(atomic_long_read(&ima_num_entries[BINARY_STAGED]),
> > + &ima_num_entries[BINARY]);
> > + atomic_long_set(&ima_num_entries[BINARY_STAGED], 0);
> > +
> > + if (IS_ENABLED(CONFIG_IMA_KEXEC)) {
> > + binary_runtime_size[BINARY_STAGED] -= size_to_remove;
> > + binary_runtime_size[BINARY] +=
> > + binary_runtime_size[BINARY_STAGED];
> > + binary_runtime_size[BINARY_STAGED] = 0;
> > + }
> > +
> > + /*
> > + * Splice (prepend) any remaining non-deleted staged entries to the
> > + * active list (RCU not needed, there cannot be concurrent readers).
> > + */
> > + list_splice(&ima_measurements_staged, &ima_measurements);
> > + INIT_LIST_HEAD(&ima_measurements_staged);
> > + mutex_unlock(&ima_extend_list_mutex);
> > +
> > + ima_queue_delete(&ima_measurements_trim, false);
> > + return ret;
> > +}
> > +
> > static void ima_queue_delete(struct list_head *head, bool flush_htable)
> > {
> > struct ima_queue_entry *qe, *qe_tmp;
>
^ permalink raw reply
* Re: [PATCH v3 4/9] lsm: framework for BPF integrity verification
From: Song Liu @ 2026-03-27 16:46 UTC (permalink / raw)
To: Blaise Boscaccy
Cc: Jonathan Corbet, Paul Moore, James Morris, Serge E. Hallyn,
Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf
In-Reply-To: <20260326060655.2550595-5-bboscaccy@linux.microsoft.com>
On Wed, Mar 25, 2026 at 11:07 PM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
[...]
> The first new callback, bpf_prog_load_integrity(), located within the
> security_bpf_prog_load() hook, is necessary to ensure that the integrity
> verification callbacks are executed before any of the existing LSMs
> are executed via the bpf_prog_load() callback. Reusing the existing
> bpf_prog_load() callback for integrity verification could result in LSMs
> not having access to the integrity verification results when asked to
> authorize the BPF program load in the bpf_prog_load() callback.
>
> The new LSM hook, security_bpf_prog_load_post_integrity(), is intended
> to be called from within LSMs performing BPF program integrity
> verification. It is used to report the verdict of the integrity
> verification to other LSMs enforcing access control policy on BPF
> program loads. LSMs enforcing such access controls should register a
> bpf_prog_load_post_integrity() callback to receive integrity verdicts.
bpf_prog_load_post_integrity() is weird. Some questions about it:
1. Is it possible to call it from other LSMs (not hornet)? Specifically, is it
possible to call it from BPF LSM?
2. This set does not include any LSMs that attach functions to
bpf_prog_load_post_integrity. This is against the new LSM hook policy.
I guess the plan is to add LSM users in follow up patches? Could you
please include at least some of such code in this patchset? This will
help folks understand the use case.
Thanks,
Song
[...]
^ permalink raw reply
* Re: [PATCH v4 09/13] ima: Add support for staging measurements with prompt
From: Roberto Sassu @ 2026-03-27 16:45 UTC (permalink / raw)
To: steven chen, corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg,
paul, jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, nramas, Roberto Sassu
In-Reply-To: <ef9c296a-940a-4bb5-a0b9-184532cf4bb6@linux.microsoft.com>
On Thu, 2026-03-26 at 15:44 -0700, steven chen wrote:
> On 3/26/2026 10:30 AM, Roberto Sassu wrote:
> > From: Roberto Sassu <roberto.sassu@huawei.com>
> >
> > Introduce the ability of staging the IMA measurement list and deleting them
> > with a prompt.
> >
> > Staging means moving the current content of the measurement list to a
> > separate location, and allowing users to read and delete it. This causes
> > the measurement list to be atomically truncated before new measurements can
> > be added. Staging can be done only once at a time. In the event of kexec(),
> > staging is reverted and staged entries will be carried over to the new
> > kernel.
> >
> > Introduce ascii_runtime_measurements_<algo>_staged and
> > binary_runtime_measurements_<algo>_staged interfaces to stage and delete
> > the measurements. Use 'echo A > <IMA interface>' and
> > 'echo D > <IMA interface>' to respectively stage and delete the entire
> > measurements list. Locking of these interfaces is also mediated with a call
> > to _ima_measurements_open() and with ima_measurements_release().
> >
> > Implement the staging functionality by introducing the new global
> > measurements list ima_measurements_staged, and ima_queue_stage() and
> > ima_queue_delete_staged_all() to respectively move measurements from the
> > current measurements list to the staged one, and to move staged
> > measurements to the ima_measurements_trim list for deletion. Introduce
> > ima_queue_delete() to delete the measurements.
> >
> > Finally, introduce the BINARY_STAGED AND BINARY_FULL binary measurements
> > list types, to maintain the counters and the binary size of staged
> > measurements and the full measurements list (including entries that were
> > staged). BINARY still represents the current binary measurements list.
> >
> > Use the binary size for the BINARY + BINARY_STAGED types in
> > ima_add_kexec_buffer(), since both measurements list types are copied to
> > the secondary kernel during kexec. Use BINARY_FULL in
> > ima_measure_kexec_event(), to generate a critical data record.
> >
> > It should be noted that the BINARY_FULL counter is not passed through
> > kexec. Thus, the number of entries included in the kexec critical data
> > records refers to the entries since the previous kexec records.
> >
> > Note: This code derives from the Alt-IMA Huawei project, whose license is
> > GPL-2.0 OR MIT.
> >
> > Link: https://github.com/linux-integrity/linux/issues/1
> > Suggested-by: Gregory Lumen <gregorylumen@linux.microsoft.com> (staging revert)
> > Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> > ---
> > security/integrity/ima/Kconfig | 13 +++
> > security/integrity/ima/ima.h | 8 +-
> > security/integrity/ima/ima_fs.c | 167 ++++++++++++++++++++++++++---
> > security/integrity/ima/ima_kexec.c | 22 +++-
> > security/integrity/ima/ima_queue.c | 97 ++++++++++++++++-
> > 5 files changed, 286 insertions(+), 21 deletions(-)
> >
> > diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
> > index 976e75f9b9ba..e714726f3384 100644
> > --- a/security/integrity/ima/Kconfig
> > +++ b/security/integrity/ima/Kconfig
> > @@ -332,4 +332,17 @@ config IMA_KEXEC_EXTRA_MEMORY_KB
> > If set to the default value of 0, an extra half page of memory for those
> > additional measurements will be allocated.
> >
> > +config IMA_STAGING
> > + bool "Support for staging the measurements list"
> > + default y
> > + help
> > + Add support for staging the measurements list.
> > +
> > + It allows user space to stage the measurements list for deletion and
> > + to delete the staged measurements after confirmation.
> > +
> > + On kexec, staging is reverted and staged measurements are prepended
> > + to the current measurements list when measurements are copied to the
> > + secondary kernel.
> > +
> > endif
> > diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> > index 97b7d6024b5d..65db152a0a24 100644
> > --- a/security/integrity/ima/ima.h
> > +++ b/security/integrity/ima/ima.h
> > @@ -30,9 +30,11 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8, TPM_PCR10 = 10 };
> >
> > /*
> > * BINARY: current binary measurements list
> > + * BINARY_STAGED: staged binary measurements list
> > + * BINARY_FULL: binary measurements list since IMA init (lost after kexec)
> > */
> > enum binary_lists {
> > - BINARY, BINARY__LAST
> > + BINARY, BINARY_STAGED, BINARY_FULL, BINARY__LAST
> > };
> >
> > /* digest size for IMA, fits SHA1 or MD5 */
> > @@ -125,6 +127,7 @@ struct ima_queue_entry {
> > struct ima_template_entry *entry;
> > };
> > extern struct list_head ima_measurements; /* list of all measurements */
> > +extern struct list_head ima_measurements_staged; /* list of staged meas. */
> >
> > /* Some details preceding the binary serialized measurement list */
> > struct ima_kexec_hdr {
> > @@ -314,6 +317,8 @@ struct ima_template_desc *ima_template_desc_current(void);
> > struct ima_template_desc *ima_template_desc_buf(void);
> > struct ima_template_desc *lookup_template_desc(const char *name);
> > bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
> > +int ima_queue_stage(void);
> > +int ima_queue_staged_delete_all(void);
> > int ima_restore_measurement_entry(struct ima_template_entry *entry);
> > int ima_restore_measurement_list(loff_t bufsize, void *buf);
> > int ima_measurements_show(struct seq_file *m, void *v);
> > @@ -334,6 +339,7 @@ extern spinlock_t ima_queue_lock;
> > extern atomic_long_t ima_num_entries[BINARY__LAST];
> > extern atomic_long_t ima_num_violations;
> > extern struct hlist_head __rcu *ima_htable;
> > +extern struct mutex ima_extend_list_mutex;
> >
> > static inline unsigned int ima_hash_key(u8 *digest)
> > {
> > diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
> > index 7709a4576322..39d9128e9f22 100644
> > --- a/security/integrity/ima/ima_fs.c
> > +++ b/security/integrity/ima/ima_fs.c
> > @@ -24,6 +24,13 @@
> >
> > #include "ima.h"
> >
> > +/*
> > + * Requests:
> > + * 'A\n': stage the entire measurements list
> > + * 'D\n': delete all staged measurements
> > + */
> > +#define STAGED_REQ_LENGTH 21
> > +
> > static DEFINE_MUTEX(ima_write_mutex);
> > static DEFINE_MUTEX(ima_measure_mutex);
> > static long ima_measure_users;
> > @@ -97,6 +104,11 @@ static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
> > return _ima_measurements_start(m, pos, &ima_measurements);
> > }
> >
> > +static void *ima_measurements_staged_start(struct seq_file *m, loff_t *pos)
> > +{
> > + return _ima_measurements_start(m, pos, &ima_measurements_staged);
> > +}
> > +
> > static void *_ima_measurements_next(struct seq_file *m, void *v, loff_t *pos,
> > struct list_head *head)
> > {
> > @@ -118,6 +130,12 @@ static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
> > return _ima_measurements_next(m, v, pos, &ima_measurements);
> > }
> >
> > +static void *ima_measurements_staged_next(struct seq_file *m, void *v,
> > + loff_t *pos)
> > +{
> > + return _ima_measurements_next(m, v, pos, &ima_measurements_staged);
> > +}
> > +
> > static void ima_measurements_stop(struct seq_file *m, void *v)
> > {
> > }
> > @@ -283,6 +301,68 @@ static const struct file_operations ima_measurements_ops = {
> > .release = ima_measurements_release,
> > };
> >
> > +static const struct seq_operations ima_measurments_staged_seqops = {
> > + .start = ima_measurements_staged_start,
> > + .next = ima_measurements_staged_next,
> > + .stop = ima_measurements_stop,
> > + .show = ima_measurements_show
> > +};
> > +
> > +static int ima_measurements_staged_open(struct inode *inode, struct file *file)
> > +{
> > + return _ima_measurements_open(inode, file,
> > + &ima_measurments_staged_seqops);
> > +}
> > +
> > +static ssize_t ima_measurements_staged_write(struct file *file,
> > + const char __user *buf,
> > + size_t datalen, loff_t *ppos)
> > +{
> > + char req[STAGED_REQ_LENGTH];
> > + int ret;
> > +
> > + if (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH)
> > + return -EINVAL;
> > +
> > + if (copy_from_user(req, buf, datalen) != 0)
> > + return -EFAULT;
> > +
> > + if (req[datalen - 1] != '\n')
> > + return -EINVAL;
> > +
> > + req[datalen - 1] = '\0';
> > +
> > + switch (req[0]) {
> > + case 'A':
> > + if (datalen != 2)
> > + return -EINVAL;
> > +
> > + ret = ima_queue_stage();
> > + break;
> > + case 'D':
> > + if (datalen != 2)
> > + return -EINVAL;
> > +
> > + ret = ima_queue_staged_delete_all();
> > + break;
>
> I think the following two steps may not work because of race condition:
>
> step1: ret = ima_queue_stage(); //this will put all logs in active list into staged list;
> step2: ret = ima_queue_staged_delete_all(); //this will delete all logs in staged list;
>
> The following is the step of race condition:
> 1. current active log list LA1;
> 2. user agent read the TPM quote QA1 match list LA1;
> 3. new event NewLog is added into active log list LA1+NewLog
> 4. user agent call ima_queue_stage() and generated staged list
> including LA1+NewLog.
> 5. user agent call ima_queue_staged_delete_all();
> The new log NewLog in step 3 is also deleted
Please refer to the documentation patch which explains the intended
workflow of this approach (Remote Attestation Agent Workflow).
Roberto
> Next time the attestation will fail if using the active log list in the
> kernel.
>
> Thanks,
>
> Steven
>
> > + default:
> > + ret = -EINVAL;
> > + }
> > +
> > + if (ret < 0)
> > + return ret;
> > +
> > + return datalen;
> > +}
> > +
> > +static const struct file_operations ima_measurements_staged_ops = {
> > + .open = ima_measurements_staged_open,
> > + .read = seq_read,
> > + .write = ima_measurements_staged_write,
> > + .llseek = seq_lseek,
> > + .release = ima_measurements_release,
> > +};
> > +
> > void ima_print_digest(struct seq_file *m, u8 *digest, u32 size)
> > {
> > u32 i;
> > @@ -356,6 +436,28 @@ static const struct file_operations ima_ascii_measurements_ops = {
> > .release = ima_measurements_release,
> > };
> >
> > +static const struct seq_operations ima_ascii_measurements_staged_seqops = {
> > + .start = ima_measurements_staged_start,
> > + .next = ima_measurements_staged_next,
> > + .stop = ima_measurements_stop,
> > + .show = ima_ascii_measurements_show
> > +};
> > +
> > +static int ima_ascii_measurements_staged_open(struct inode *inode,
> > + struct file *file)
> > +{
> > + return _ima_measurements_open(inode, file,
> > + &ima_ascii_measurements_staged_seqops);
> > +}
> > +
> > +static const struct file_operations ima_ascii_measurements_staged_ops = {
> > + .open = ima_ascii_measurements_staged_open,
> > + .read = seq_read,
> > + .write = ima_measurements_staged_write,
> > + .llseek = seq_lseek,
> > + .release = ima_measurements_release,
> > +};
> > +
> > static ssize_t ima_read_policy(char *path)
> > {
> > void *data = NULL;
> > @@ -459,10 +561,21 @@ static const struct seq_operations ima_policy_seqops = {
> > };
> > #endif
> >
> > -static int __init create_securityfs_measurement_lists(void)
> > +static int __init create_securityfs_measurement_lists(bool staging)
> > {
> > + const struct file_operations *ascii_ops = &ima_ascii_measurements_ops;
> > + const struct file_operations *binary_ops = &ima_measurements_ops;
> > + mode_t permissions = S_IRUSR | S_IRGRP;
> > + const char *file_suffix = "";
> > int count = NR_BANKS(ima_tpm_chip);
> >
> > + if (staging) {
> > + ascii_ops = &ima_ascii_measurements_staged_ops;
> > + binary_ops = &ima_measurements_staged_ops;
> > + file_suffix = "_staged";
> > + permissions |= (S_IWUSR | S_IWGRP);
> > + }
> > +
> > if (ima_sha1_idx >= NR_BANKS(ima_tpm_chip))
> > count++;
> >
> > @@ -473,29 +586,32 @@ static int __init create_securityfs_measurement_lists(void)
> >
> > if (algo == HASH_ALGO__LAST)
> > snprintf(file_name, sizeof(file_name),
> > - "ascii_runtime_measurements_tpm_alg_%x",
> > - ima_tpm_chip->allocated_banks[i].alg_id);
> > + "ascii_runtime_measurements_tpm_alg_%x%s",
> > + ima_tpm_chip->allocated_banks[i].alg_id,
> > + file_suffix);
> > else
> > snprintf(file_name, sizeof(file_name),
> > - "ascii_runtime_measurements_%s",
> > - hash_algo_name[algo]);
> > - dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
> > + "ascii_runtime_measurements_%s%s",
> > + hash_algo_name[algo], file_suffix);
> > + dentry = securityfs_create_file(file_name, permissions,
> > ima_dir, (void *)(uintptr_t)i,
> > - &ima_ascii_measurements_ops);
> > + ascii_ops);
> > if (IS_ERR(dentry))
> > return PTR_ERR(dentry);
> >
> > if (algo == HASH_ALGO__LAST)
> > snprintf(file_name, sizeof(file_name),
> > - "binary_runtime_measurements_tpm_alg_%x",
> > - ima_tpm_chip->allocated_banks[i].alg_id);
> > + "binary_runtime_measurements_tpm_alg_%x%s",
> > + ima_tpm_chip->allocated_banks[i].alg_id,
> > + file_suffix);
> > else
> > snprintf(file_name, sizeof(file_name),
> > - "binary_runtime_measurements_%s",
> > - hash_algo_name[algo]);
> > - dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
> > + "binary_runtime_measurements_%s%s",
> > + hash_algo_name[algo], file_suffix);
> > +
> > + dentry = securityfs_create_file(file_name, permissions,
> > ima_dir, (void *)(uintptr_t)i,
> > - &ima_measurements_ops);
> > + binary_ops);
> > if (IS_ERR(dentry))
> > return PTR_ERR(dentry);
> > }
> > @@ -503,6 +619,23 @@ static int __init create_securityfs_measurement_lists(void)
> > return 0;
> > }
> >
> > +static int __init create_securityfs_staging_links(void)
> > +{
> > + struct dentry *dentry;
> > +
> > + dentry = securityfs_create_symlink("binary_runtime_measurements_staged",
> > + ima_dir, "binary_runtime_measurements_sha1_staged", NULL);
> > + if (IS_ERR(dentry))
> > + return PTR_ERR(dentry);
> > +
> > + dentry = securityfs_create_symlink("ascii_runtime_measurements_staged",
> > + ima_dir, "ascii_runtime_measurements_sha1_staged", NULL);
> > + if (IS_ERR(dentry))
> > + return PTR_ERR(dentry);
> > +
> > + return 0;
> > +}
> > +
> > /*
> > * ima_open_policy: sequentialize access to the policy file
> > */
> > @@ -595,7 +728,13 @@ int __init ima_fs_init(void)
> > goto out;
> > }
> >
> > - ret = create_securityfs_measurement_lists();
> > + ret = create_securityfs_measurement_lists(false);
> > + if (ret == 0 && IS_ENABLED(CONFIG_IMA_STAGING)) {
> > + ret = create_securityfs_measurement_lists(true);
> > + if (ret == 0)
> > + ret = create_securityfs_staging_links();
> > + }
> > +
> > if (ret != 0)
> > goto out;
> >
> > diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
> > index d7d0fb639d99..d5503dd5cc9b 100644
> > --- a/security/integrity/ima/ima_kexec.c
> > +++ b/security/integrity/ima/ima_kexec.c
> > @@ -42,8 +42,8 @@ void ima_measure_kexec_event(const char *event_name)
> > long len;
> > int n;
> >
> > - buf_size = ima_get_binary_runtime_size(BINARY);
> > - len = atomic_long_read(&ima_num_entries[BINARY]);
> > + buf_size = ima_get_binary_runtime_size(BINARY_FULL);
> > + len = atomic_long_read(&ima_num_entries[BINARY_FULL]);
> >
> > n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
> > "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
> > @@ -106,13 +106,26 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,
> >
> > memset(&khdr, 0, sizeof(khdr));
> > khdr.version = 1;
> > - /* This is an append-only list, no need to hold the RCU read lock */
> > - list_for_each_entry_rcu(qe, &ima_measurements, later, true) {
> > + /* It can race with ima_queue_stage() and ima_queue_delete_staged(). */
> > + mutex_lock(&ima_extend_list_mutex);
> > +
> > + list_for_each_entry_rcu(qe, &ima_measurements_staged, later,
> > + lockdep_is_held(&ima_extend_list_mutex)) {
> > ret = ima_dump_measurement(&khdr, qe);
> > if (ret < 0)
> > break;
> > }
> >
> > + list_for_each_entry_rcu(qe, &ima_measurements, later,
> > + lockdep_is_held(&ima_extend_list_mutex)) {
> > + if (!ret)
> > + ret = ima_dump_measurement(&khdr, qe);
> > + if (ret < 0)
> > + break;
> > + }
> > +
> > + mutex_unlock(&ima_extend_list_mutex);
> > +
> > /*
> > * fill in reserved space with some buffer details
> > * (eg. version, buffer size, number of measurements)
> > @@ -167,6 +180,7 @@ void ima_add_kexec_buffer(struct kimage *image)
> > extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024;
> >
> > binary_runtime_size = ima_get_binary_runtime_size(BINARY) +
> > + ima_get_binary_runtime_size(BINARY_STAGED) +
> > extra_memory;
> >
> > if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE)
> > diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
> > index b6d10dceb669..50519ed837d4 100644
> > --- a/security/integrity/ima/ima_queue.c
> > +++ b/security/integrity/ima/ima_queue.c
> > @@ -26,6 +26,7 @@
> > static struct tpm_digest *digests;
> >
> > LIST_HEAD(ima_measurements); /* list of all measurements */
> > +LIST_HEAD(ima_measurements_staged); /* list of staged measurements */
> > #ifdef CONFIG_IMA_KEXEC
> > static unsigned long binary_runtime_size[BINARY__LAST];
> > #else
> > @@ -45,11 +46,11 @@ atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0);
> > /* key: inode (before secure-hashing a file) */
> > struct hlist_head __rcu *ima_htable;
> >
> > -/* mutex protects atomicity of extending measurement list
> > +/* mutex protects atomicity of extending and staging measurement list
> > * and extending the TPM PCR aggregate. Since tpm_extend can take
> > * long (and the tpm driver uses a mutex), we can't use the spinlock.
> > */
> > -static DEFINE_MUTEX(ima_extend_list_mutex);
> > +DEFINE_MUTEX(ima_extend_list_mutex);
> >
> > /*
> > * Used internally by the kernel to suspend measurements.
> > @@ -174,12 +175,16 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
> > lockdep_is_held(&ima_extend_list_mutex));
> >
> > atomic_long_inc(&ima_num_entries[BINARY]);
> > + atomic_long_inc(&ima_num_entries[BINARY_FULL]);
> > +
> > if (update_htable) {
> > key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
> > hlist_add_head_rcu(&qe->hnext, &htable[key]);
> > }
> >
> > ima_update_binary_runtime_size(entry, BINARY);
> > + ima_update_binary_runtime_size(entry, BINARY_FULL);
> > +
> > return 0;
> > }
> >
> > @@ -280,6 +285,94 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
> > return result;
> > }
> >
> > +int ima_queue_stage(void)
> > +{
> > + int ret = 0;
> > +
> > + mutex_lock(&ima_extend_list_mutex);
> > + if (!list_empty(&ima_measurements_staged)) {
> > + ret = -EEXIST;
> > + goto out_unlock;
> > + }
> > +
> > + if (list_empty(&ima_measurements)) {
> > + ret = -ENOENT;
> > + goto out_unlock;
> > + }
> > +
> > + list_replace(&ima_measurements, &ima_measurements_staged);
> > + INIT_LIST_HEAD(&ima_measurements);
> > +
> > + atomic_long_set(&ima_num_entries[BINARY_STAGED],
> > + atomic_long_read(&ima_num_entries[BINARY]));
> > + atomic_long_set(&ima_num_entries[BINARY], 0);
> > +
> > + if (IS_ENABLED(CONFIG_IMA_KEXEC)) {
> > + binary_runtime_size[BINARY_STAGED] =
> > + binary_runtime_size[BINARY];
> > + binary_runtime_size[BINARY] = 0;
> > + }
> > +out_unlock:
> > + mutex_unlock(&ima_extend_list_mutex);
> > + return ret;
> > +}
> > +
> > +static void ima_queue_delete(struct list_head *head);
> > +
> > +int ima_queue_staged_delete_all(void)
> > +{
> > + LIST_HEAD(ima_measurements_trim);
> > +
> > + mutex_lock(&ima_extend_list_mutex);
> > + if (list_empty(&ima_measurements_staged)) {
> > + mutex_unlock(&ima_extend_list_mutex);
> > + return -ENOENT;
> > + }
> > +
> > + list_replace(&ima_measurements_staged, &ima_measurements_trim);
> > + INIT_LIST_HEAD(&ima_measurements_staged);
> > +
> > + atomic_long_set(&ima_num_entries[BINARY_STAGED], 0);
> > +
> > + if (IS_ENABLED(CONFIG_IMA_KEXEC))
> > + binary_runtime_size[BINARY_STAGED] = 0;
> > +
> > + mutex_unlock(&ima_extend_list_mutex);
> > +
> > + ima_queue_delete(&ima_measurements_trim);
> > + return 0;
> > +}
> > +
> > +static void ima_queue_delete(struct list_head *head)
> > +{
> > + struct ima_queue_entry *qe, *qe_tmp;
> > + unsigned int i;
> > +
> > + list_for_each_entry_safe(qe, qe_tmp, head, later) {
> > + /*
> > + * Safe to free template_data here without synchronize_rcu()
> > + * because the only htable reader, ima_lookup_digest_entry(),
> > + * accesses only entry->digests, not template_data. If new
> > + * htable readers are added that access template_data, a
> > + * synchronize_rcu() is required here.
> > + */
> > + for (i = 0; i < qe->entry->template_desc->num_fields; i++) {
> > + kfree(qe->entry->template_data[i].data);
> > + qe->entry->template_data[i].data = NULL;
> > + qe->entry->template_data[i].len = 0;
> > + }
> > +
> > + list_del(&qe->later);
> > +
> > + /* No leak if condition is false, referenced by ima_htable. */
> > + if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) {
> > + kfree(qe->entry->digests);
> > + kfree(qe->entry);
> > + kfree(qe);
> > + }
> > + }
> > +}
> > +
> > int ima_restore_measurement_entry(struct ima_template_entry *entry)
> > {
> > int result = 0;
>
^ permalink raw reply
* Re: [PATCH V9 3/8] dax: add fsdev.c driver for fs-dax on character dax
From: Ira Weiny @ 2026-03-27 16:40 UTC (permalink / raw)
To: John Groves, Ira Weiny
Cc: Jonathan Cameron, John Groves, Miklos Szeredi, Dan Williams,
Bernd Schubert, Alison Schofield, John Groves, Jonathan Corbet,
Shuah Khan, Vishal Verma, Dave Jiang, Matthew Wilcox, Jan Kara,
Alexander Viro, David Hildenbrand, Christian Brauner,
Darrick J . Wong, Randy Dunlap, Jeff Layton, Amir Goldstein,
Stefan Hajnoczi, Joanne Koong, Josef Bacik, Bagas Sanjaya,
Chen Linxuan, James Morse, Fuad Tabba, Sean Christopherson,
Shivank Garg, Ackerley Tng, Gregory Price, Aravind Ramesh,
Ajay Joshi, venkataravis@micron.com, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, nvdimm@lists.linux.dev,
linux-cxl@vger.kernel.org, linux-fsdevel@vger.kernel.org
In-Reply-To: <acXMdEKG7kO11OtH@groves.net>
John Groves wrote:
> On 26/03/26 05:46PM, Ira Weiny wrote:
> > John Groves wrote:
> > > On 26/03/25 11:04AM, Ira Weiny wrote:
> > > > John Groves wrote:
> > > > > On 26/03/24 02:39PM, Jonathan Cameron wrote:
> > > > > > On Tue, 24 Mar 2026 00:38:31 +0000
> > > > > > John Groves <john@jagalactic.com> wrote:
> > > > > >
> > > > > > > From: John Groves <john@groves.net>
> > > > > > >
> > > > > > > The new fsdev driver provides pages/folios initialized compatibly with
> > > > > > > fsdax - normal rather than devdax-style refcounting, and starting out
> > > > > > > with order-0 folios.
> > > > > > >
> > > > > > > When fsdev binds to a daxdev, it is usually (always?) switching from the
> > > > > > > devdax mode (device.c), which pre-initializes compound folios according
> > > > > > > to its alignment. Fsdev uses fsdev_clear_folio_state() to switch the
> > > > > > > folios into a fsdax-compatible state.
> > > > > > >
> > > > > > > A side effect of this is that raw mmap doesn't (can't?) work on an fsdev
> > > > > > > dax instance. Accordingly, The fsdev driver does not provide raw mmap -
> > > > > > > devices must be put in 'devdax' mode (drivers/dax/device.c) to get raw
> > > > > > > mmap capability.
> > > > > > >
> > > > > > > In this commit is just the framework, which remaps pages/folios compatibly
> > > > > > > with fsdax.
> > > > > > >
> > > > > > > Enabling dax changes:
> > > > > > >
> > > > > > > - bus.h: add DAXDRV_FSDEV_TYPE driver type
> > > > > > > - bus.c: allow DAXDRV_FSDEV_TYPE drivers to bind to daxdevs
> > > > > > > - dax.h: prototype inode_dax(), which fsdev needs
> > > > > > >
> > > > > > > Suggested-by: Dan Williams <dan.j.williams@intel.com>
> > > > > > > Suggested-by: Gregory Price <gourry@gourry.net>
> > > > > > > Signed-off-by: John Groves <john@groves.net>
> > > > > >
> > > > > > I was kind of thinking you'd go with a hidden KCONFIG option with default
> > > > > > magic to do the same build condition to you had in the Makefil, but one the
> > > > > > user can opt in or out for is also fine.
> > > > > >
> > > > > > Comments on that below. Meh, I think this is better anyway :)
> > > > > >
> > > > > > Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
> > > > > >
> > > > > >
> > > > > >
> > > > > > > diff --git a/drivers/dax/Kconfig b/drivers/dax/Kconfig
> > > > > > > index d656e4c0eb84..7051b70980d5 100644
> > > > > > > --- a/drivers/dax/Kconfig
> > > > > > > +++ b/drivers/dax/Kconfig
> > > > > > > @@ -61,6 +61,17 @@ config DEV_DAX_HMEM_DEVICES
> > > > > > > depends on DEV_DAX_HMEM && DAX
> > > > > > > def_bool y
> > > > > > >
> > > > > > > +config DEV_DAX_FSDEV
> > > > > > > + tristate "FSDEV DAX: fs-dax compatible devdax driver"
> > > > > > > + depends on DEV_DAX && FS_DAX
> > > > > > > + help
> > > > > > > + Support fs-dax access to DAX devices via a character device
> > > > > > > + interface. Unlike device_dax (which pre-initializes compound folios
> > > > > > > + based on device alignment), this driver leaves folios at order-0 so
> > > > > > > + that fs-dax filesystems can manage folio order dynamically.
> > > > > > > +
> > > > > > > + Say M if unsure.
> > > > > > Fine like this, but if you wanted to hide it in interests of not
> > > > > > confusing users...
> > > > > >
> > > > > > config DEV_DAX_FSDEV
> > > > > > tristate
> > > > > > depends on DEV_DAX && FS_DAX
> > > > > > default DEV_DAX
> > > > >
> > > > > I like this better. I see no reason not to default to including fsdev.
> > > > > It does nothing other than frustrating famfs users if it's off - since
> > > > > building it still has no effect unless you put a daxdev in famfs mode.
> > > > >
> > > > > Ira, it's kinda in your hands at the moment. Do you feel like making this
> > > > > change?
> > > >
> > > > I don't mind making this change. But we have to deal with the breakage to
> > > > current device dax users.
> > > >
> > > > https://lore.kernel.org/all/69c36921255b6_e9d8d1009b@iweiny-mobl.notmuch/
> > > >
> > > > What am I missing?
> > > >
> > > > Ira
> > >
> > > OK, I can reproduce that failure with kernel 7.0.0-rc5 and
> > > straight ndctl v84. So it's not famfs.
> >
> > No it is the fsdev_dax driver which causes the issue.
> >
> > I can reload the driver and effectively change the order the drivers are
> > searched.
> >
> > I can prove this with a simple print. With my test system (where
> > fsdev_dax _happens_ to be the first driver searched) the failure happens.
> >
> > [ 526.564232] IKW searching drv type 0 ; type 1
> > [ 526.564515] IKW searching drv type 2 ; type 1
> >
> > If I remove your driver (modprobe -r fsdev_dax) prior to running the test
> > I get.
> >
> > [ 59.748171] IKW searching drv type 0 ; type 1
> > [ 59.749127] IKW searching drv type 1 ; type 1
> >
> > And it passes. I can continue by loading fsdev_dax back and it will
> > continue to work. If you are getting this to pass it must be because in
> > your system that driver gets loaded first... not sure how.
> >
> > This is with the same exact kernel just with your module removed at run
> > time.
> >
> > dax_match_type() needs some other way of matching when the fsdev_dax
> > driver should be used.
>
> I think the correct answer is that fsdev/famfs should never automatically
> match and bind. Weird that I haven't seen it do that (or maybe it did but
> I didn't notice?)
Agreed.
>
> If one does a mkfs.famfs or 'famfs mount', the famfs tools already try to
> bind fsdev/famfs mode if necessary and fail if they can't.
Yep.
>
> >
> > I'm not seeing a clear path ATM.
>
> I do, but I need to test it out. If it works I'll send a v10 patch set
> in a day or two.
>
> Also, I am definitely seeing ndctl/dax test failures from the device-dax
> and dm.sh tests at rc5 with no famfs code (dax or otherwise) at all; I'm
> puzzled that you don't see any ndctl test failures in that situation. If
> I understood Allison correctly, she saw something similar to what I saw).
> But no worries, we'll get it sorted.
:-/ Ok I can get dm.sh to fail with rc5. But it is intermittent. I'll
investigate that.
FWIW I'm not saying device-dax does not ever fail on rc5. I've not seen
it though.
But I can definitely get it to fail with the procedure above. If there is
another failure it would be good to send your log with a report and I'll
look at that separately.
>
> If my strategy works, the next version won't ever automatically bind fsdev,
> but it will be explicitly bindable via daxctl or famfs tools. Famfs does not
> need fsdev to ever be automatically bound do dax mem...
>
> >
> > >
> > > I also studied the verbose logs trying to figure out if famfs
> > > could cause it (while running a famfs kernel and ndctl), but
> > > I don't see it.
> > >
> > > Then I tried non-famfs kernel and ndctl and it's the same with
> > > or without famfs kernel and famfs ndctl.
> >
> > :-/ I'm not seeing any failures with rc5.
> >
> > Also I'm not running with famfs. Just the dax changes.
>
> Right - if fsdev ever gets automatically bound instead of
> drivers/dax/device.c, that's my bad. Weird that I haven't seen that happen,
> but that's why we review and test :D
Sounds good!
Thanks,
Ira
^ permalink raw reply
* Re: [PATCH v8 2/2] hwmon: temperature: add support for EMC1812
From: Marius.Cristea @ 2026-03-27 16:30 UTC (permalink / raw)
To: linux
Cc: corbet, linux-hwmon, devicetree, robh, linux-kernel, krzk+dt,
linux-doc, conor+dt
In-Reply-To: <f1e55e6e-a374-4b97-b1f3-706d627ebab5@roeck-us.net>
Hi Guenther,
Thanks for the review, please see my comments below:
...
>
>
>
>
> > +static int emc1812_init(struct emc1812_data *priv)
> > +{
> > + int ret;
> > + u8 val;
> > +
> > + /*
> > + * Set default values in registers. APDD, RECD12 and RECD34
> > are active
> > + * on 0. Set ALERT pin to be in comparator mode.
> > + * Set the device to be in Run (Active) state and converting
> > on all
> > + * channels.
> > + * Don't change conversion rate. After reset, default is 4
> > conversions/seconds.
> > + * The temperature measurement range is -64°C to +191.875°C.
> > + */
> > + val = FIELD_PREP(EMC1812_CFG_MSKAL, 1) |
> > + FIELD_PREP(EMC1812_CFG_RS, 0) |
> > + FIELD_PREP(EMC1812_CFG_ATTHM, 1) |
> > + FIELD_PREP(EMC1812_CFG_RECD12, !priv->recd12_en) |
> > + FIELD_PREP(EMC1812_CFG_RECD34, !priv->recd34_en) |
> > + FIELD_PREP(EMC1812_CFG_RANGE, 1) |
> > + FIELD_PREP(EMC1812_CFG_DA_ENA, 0) |
> > + FIELD_PREP(EMC1812_CFG_APDD, !priv->apdd_en);
> > +
>
> I assume it is on purpose that the defaults for EMC1812_CFG_RECD12
> and
> EMC1812_CFG_RECD34 deviate from the chip default (chip: enabled;
> driver:
> disabled).
>
Yes, EMC1812_CFG_ATTHM was set in order for the alerts to be clear
automaticaly when the limits goes back to normal.
The EMC1812_CFG_RANGE is set to extended range in order to be able to
measure from the -64 to 191,875 degree Celsius.
The EMC1812_CFG_MSKAL could be left at the "reset", so I will change it
to 0.
The EMC1812_CFG_RECD12 and EMC1812_CFG_RECD34 will be set based on the
device tree setting and is related to the hardware and if the system
designer wants to enable or disable the resistance error correction.
> > + ret = regmap_write(priv->regmap, EMC1812_CFG_ADDR, val);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_write(priv->regmap, EMC1812_THRM_HYS_ADDR,
> > 0x0A);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_write(priv->regmap, EMC1812_CONSEC_ALERT_ADDR,
> > 0x70);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_write(priv->regmap, EMC1812_FILTER_SEL_ADDR, 0);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_write(priv->regmap, EMC1812_HOTTEST_CFG_ADDR,
> > 0);
> > + if (ret)
> > + return ret;
> > +
> > + /* Enables the beta compensation factor auto-detection
> > function for beta1 and beta2 */
> > + ret = regmap_write(priv->regmap,
> > EMC1812_EXT1_BETA_CONFIG_ADDR,
> > + EMC1812_BETA_LOCK_VAL);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_write(priv->regmap,
> > EMC1812_EXT2_BETA_CONFIG_ADDR,
>
> AI review thinks that this register only exists on EMC1812. I don't
> find that detail in the datasheet, but it is odd that there are two
> registers
> with supposedly the same functionality.
>
>
All devices "have" the EMC1812_EXT2_BETA_CONFIG register (I mean if you
are writing something to it, there will be no NAK on the i2c bus, but
the value read back will be "0" for the devices that has the register
not writable).
EMC1812 having only one external channel, will not have the
EMC1812_EXT2_BETA_CONFIG writable.
Regards,
Marius
^ permalink raw reply
* Re: [PATCH v6 02/40] arm_mpam: Reset when feature configuration bit unset
From: James Morse @ 2026-03-27 16:21 UTC (permalink / raw)
To: Ben Horgan
Cc: amitsinght, baisheng.gao, baolin.wang, carl, dave.martin, david,
dfustini, fenghuay, gshan, 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
In-Reply-To: <20260313144617.3420416-3-ben.horgan@arm.com>
Hi Ben,
On 13/03/2026 14:45, Ben Horgan wrote:
> To indicate that the configuration, of the controls used by resctrl, in a
> RIS need resetting to driver defaults the reset flags in mpam_config are
> set. However, these flags are only ever set temporarily at RIS scope in
> mpam_reset_ris() and hence mpam_cpu_online() will never reset these
> controls to default. As the hardware reset is unknown this leads to unknown
> configuration when the control values haven't been configured away from the
> defaults.
>
> Use the policy that an unset feature configuration bit means reset. In this
> way the mpam_config in the component can encode that it should be in reset
> state and mpam_reprogram_msc() will reset controls as needed.
> diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
> index 0fd6590a9b5c..ff861291bd4e 100644
> --- a/drivers/resctrl/mpam_devices.c
> +++ b/drivers/resctrl/mpam_devices.c
> @@ -1364,17 +1364,15 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid,
> __mpam_intpart_sel(ris->ris_idx, partid, msc);
> }
>
> - if (mpam_has_feature(mpam_feat_cpor_part, rprops) &&
> - mpam_has_feature(mpam_feat_cpor_part, cfg)) {
> - if (cfg->reset_cpbm)
After this, nothing reads/writes these explicit reset flags so they can be removed from
struct mpam_config.
(I'll do this locally)
> - mpam_reset_msc_bitmap(msc, MPAMCFG_CPBM, rprops->cpbm_wd);
> - else
> + if (mpam_has_feature(mpam_feat_cpor_part, rprops)) {
> + if (mpam_has_feature(mpam_feat_cpor_part, cfg))
> mpam_write_partsel_reg(msc, CPBM, cfg->cpbm);
> + else
> + mpam_reset_msc_bitmap(msc, MPAMCFG_CPBM, rprops->cpbm_wd);
> }
>
> - if (mpam_has_feature(mpam_feat_mbw_part, rprops) &&
> - mpam_has_feature(mpam_feat_mbw_part, cfg)) {
> - if (cfg->reset_mbw_pbm)
> + if (mpam_has_feature(mpam_feat_mbw_part, rprops)) {
> + if (mpam_has_feature(mpam_feat_mbw_part, cfg))
> mpam_reset_msc_bitmap(msc, MPAMCFG_MBW_PBM, rprops->mbw_pbm_bits);
> else
> mpam_write_partsel_reg(msc, MBW_PBM, cfg->mbw_pbm);
Reviewed-by: James Morse <james.morse@arm.com>
Thanks!
James
^ permalink raw reply
* Re: [PATCH v2] bootconfig: Apply early options from embedded config
From: Breno Leitao @ 2026-03-27 16:11 UTC (permalink / raw)
To: Masami Hiramatsu
Cc: Jonathan Corbet, Shuah Khan, linux-kernel, linux-trace-kernel,
linux-doc, oss, paulmck, rostedt, kernel-team
In-Reply-To: <20260327231630.2d6f4273b7d615bda4b51053@kernel.org>
On Fri, Mar 27, 2026 at 11:16:30PM +0900, Masami Hiramatsu wrote:
> > Given all the feedback on this series, I see three types of issues to address:
> >
> > 1) Minor patch improvements
> > 2) Architecture-specific super early parameters being parsed before bootconfig
> > is available
> > 3) Unifying kernel cmdline and bootconfig interfaces
>
> I think we can start with 1) for embedded bootconfig for this series
> with using bootconfig in parse_early_param().
Thanks for the clear direction.
I'll work on integrating bootconfig into parse_early_param() to see
what can be achieved and identify any potential blockers.
I should be back soon with more fun.
Thanks so far,
--breno
^ permalink raw reply
* Re: [PATCH v2] doc: Add CPU Isolation documentation
From: Valentin Schneider @ 2026-03-27 16:01 UTC (permalink / raw)
To: Frederic Weisbecker, LKML
Cc: Frederic Weisbecker, Anna-Maria Behnsen, Gabriele Monaco,
Ingo Molnar, Jonathan Corbet, Marcelo Tosatti, Marco Crivellari,
Michal Hocko, Paul E . McKenney, Peter Zijlstra, Phil Auld,
Steven Rostedt, Thomas Gleixner, Vlastimil Babka, Waiman Long,
linux-doc, Sebastian Andrzej Siewior, Bagas Sanjaya
In-Reply-To: <20260326140055.41555-1-frederic@kernel.org>
On 26/03/26 15:00, Frederic Weisbecker wrote:
> nohz_full was introduced in v3.10 in 2013, which means this
> documentation is overdue for 13 years.
>
> Fortunately Paul wrote a part of the needed documentation a while ago,
> especially concerning nohz_full in Documentation/timers/no_hz.rst and
> also about per-CPU kthreads in
> Documentation/admin-guide/kernel-per-CPU-kthreads.rst
>
> Introduce a new page that gives an overview of CPU isolation in general.
>
> Signed-off-by: Frederic Weisbecker <frederic@kernel.org>
With what's already been brought up:
Reviewed-by: Valentin Schneider <vschneid@redhat.com>
^ permalink raw reply
* Re: [PATCH v6 36/40] arm_mpam: Add workaround for T241-MPAM-1
From: James Morse @ 2026-03-27 15:48 UTC (permalink / raw)
To: Gavin Shan, Ben Horgan
Cc: amitsinght, baisheng.gao, baolin.wang, carl, dave.martin, david,
dfustini, fenghuay, 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: <7b73d10e-4bfd-434f-b05f-25c4859a7abd@redhat.com>
Hi Gavin,
On 24/03/2026 04:16, Gavin Shan wrote:
> On 3/14/26 12:46 AM, Ben Horgan wrote:
>> From: Shanker Donthineni <sdonthineni@nvidia.com>
>>
>> The MPAM bandwidth partitioning controls will not be correctly configured,
>> and hardware will retain default configuration register values, meaning
>> generally that bandwidth will remain unprovisioned.
>>
>> To address the issue, follow the below steps after updating the MBW_MIN
>> and/or MBW_MAX registers.
>>
>> - Perform 64b reads from all 12 bridge MPAM shadow registers at offsets
>> (0x360048 + slice*0x10000 + partid*8). These registers are read-only.
>> - Continue iterating until all 12 shadow register values match in a loop.
>> pr_warn_once if the values fail to match within the loop count 1000.
>> - Perform 64b writes with the value 0x0 to the two spare registers at
>> offsets 0x1b0000 and 0x1c0000.
>>
>> In the hardware, writes to the MPAMCFG_MBW_MAX MPAMCFG_MBW_MIN registers
>> are transformed into broadcast writes to the 12 shadow registers. The
>> final two writes to the spare registers cause a final rank of downstream
>> micro-architectural MPAM registers to be updated from the shadow copies.
>> The intervening loop to read the 12 shadow registers helps avoid a race
>> condition where writes to the spare registers occur before all shadow
>> registers have been updated.
> One question below.
>
> Reviewed-by: Gavin Shan <gshan@redhat.com>
>> diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
>> index e66631f3f732..b1753498f07f 100644
>> --- a/drivers/resctrl/mpam_devices.c
>> +++ b/drivers/resctrl/mpam_devices.c
>> @@ -630,7 +640,45 @@ static struct mpam_msc_ris *mpam_get_or_create_ris(struct mpam_msc
>> *msc,
>> return ERR_PTR(-ENOENT);
>> }
>> +static int mpam_enable_quirk_nvidia_t241_1(struct mpam_msc *msc,
>> + const struct mpam_quirk *quirk)
>> +{
>> + s32 soc_id = arm_smccc_get_soc_id_version();
>> + struct resource *r;
>> + phys_addr_t phys;
>> +
>> + /*
>> + * A mapping to a device other than the MSC is needed, check
>> + * SOC_ID is NVIDIA T241 chip (036b:0241)
>> + */
>> + if (soc_id < 0 || soc_id != SMCCC_SOC_ID_T241)
>> + return -EINVAL;
>> +
>> + r = platform_get_resource(msc->pdev, IORESOURCE_MEM, 0);
>> + if (!r)
>> + return -EINVAL;
>> +
>> + /* Find the internal registers base addr from the CHIP ID */
>> + msc->t241_id = T241_CHIP_ID(r->start);
>> + phys = FIELD_PREP(GENMASK_ULL(45, 44), msc->t241_id) | 0x19000000ULL;
>> +
>> + t241_scratch_regs[msc->t241_id] = ioremap(phys, SZ_8M);
>> + if (WARN_ON_ONCE(!t241_scratch_regs[msc->t241_id]))
>> + return -EINVAL;
>
> Those IO regions aren't unmapped when the MSCs are removed. I guess it would be
> something to be improved? :-)
It's just leaking some VA space in the unlikely event the error interrupt goes off.
That is never expected to happen - all the errors indicate a software bug, so its
not a case of being unlucky. (This assumes T241 supports the error interrupt!).
Adding some teardown would just be for this erratum, I expect it to be the only one
that needs to map some other device to poke at. I'm not sure its worth it.
I'm also very nervous changing this quirk as its difficult for me to test!
>> +
>> + pr_info_once("Enabled workaround for NVIDIA T241 erratum T241-MPAM-1\n");
>> +
>> + return 0;
>> +}
>> +
>> static const struct mpam_quirk mpam_quirks[] = {
>> + {
>> + /* NVIDIA t241 erratum T241-MPAM-1 */
>> + .init = mpam_enable_quirk_nvidia_t241_1,
>> + .iidr = MPAM_IIDR_NVIDIA_T241,
>> + .iidr_mask = MPAM_IIDR_MATCH_ONE,
>> + .workaround = T241_SCRUB_SHADOW_REGS,
>
> Perhaps we need a more leading space for every line in the above block.
Sure, done locally.
>> + },
>> { NULL } /* Sentinel */
>> };
Thanks,
James
^ permalink raw reply
* Re: [PATCH v6 25/40] arm_mpam: resctrl: Add support for 'MB' resource
From: James Morse @ 2026-03-27 15:47 UTC (permalink / raw)
To: Gavin Shan, Ben Horgan
Cc: amitsinght, baisheng.gao, baolin.wang, carl, dave.martin, david,
dfustini, fenghuay, 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: <3ae3356d-a901-4b71-90df-557d468e4785@redhat.com>
Hi Gavin,
On 23/03/2026 23:09, Gavin Shan wrote:
> On 3/14/26 12:46 AM, Ben Horgan wrote:
>> From: James Morse <james.morse@arm.com>
>>
>> resctrl supports 'MB', as a percentage throttling of traffic from the
>> L3. This is the control that mba_sc uses, so ideally the class chosen
>> should be as close as possible to the counters used for mbm_total. If there
>> is a single L3, it's the last cache, and the topology of the memory matches
>> then the traffic at the memory controller will be equivalent to that at
>> egress of the L3. If these conditions are met allow the memory class to
>> back MB.
>>
>> MB's percentage control should be backed either with the fixed point
>> fraction MBW_MAX or bandwidth portion bitmaps. The bandwidth portion
>> bitmaps is not used as its tricky to pick which bits to use to avoid
>> contention, and may be possible to expose this as something other than a
>> percentage in the future.
> One comment below and it deserves to be addressed if we have another respin:
>
> Reviewed-by: Gavin Shan <gshan@redhat.com>
Thanks!
>> diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
>> index 93c8a9608ed4..cad65cf7d12d 100644
>> --- a/drivers/resctrl/mpam_resctrl.c
>> +++ b/drivers/resctrl/mpam_resctrl.c
>> @@ -317,6 +344,166 @@ static u16 percent_to_mbw_max(u8 pc, struct mpam_props *cprops)
>> +/*
>> + * Test if the traffic for a class matches that at egress from the L3. For
>> + * MSC at memory controllers this is only possible if there is a single L3
>> + * as otherwise the counters at the memory can include bandwidth from the
>> + * non-local L3.
>> + */
>> +static bool traffic_matches_l3(struct mpam_class *class)
>> +{
>> + int err, cpu;
>> +
>> + lockdep_assert_cpus_held();
>> +
>> + if (class->type == MPAM_CLASS_CACHE && class->level == 3)
>> + return true;
>> +
>> + if (class->type == MPAM_CLASS_CACHE && class->level != 3) {
>> + pr_debug("class %u is a different cache from L3\n", class->level);
>> + return false;
>> + }
>> +
>> + if (class->type != MPAM_CLASS_MEMORY) {
>> + pr_debug("class %u is neither of type cache or memory\n", class->level);
>> + return false;
>> + }
>> +
>
> We bail if the calss isn't MPAM_CLASS_MEMORY here ...
>
>> + cpumask_var_t __free(free_cpumask_var) tmp_cpumask = CPUMASK_VAR_NULL;
>> + if (!alloc_cpumask_var(&tmp_cpumask, GFP_KERNEL)) {
>> + pr_debug("cpumask allocation failed\n");
>> + return false;
>> + }
>> +
>> + if (class->type != MPAM_CLASS_MEMORY) {
>> + pr_debug("class %u is neither of type cache or memory\n",
>> + class->level);
>> + return false;
>> + }
>> +
>
> Duplicated check here as the previous one. So this check can be dropped.
Heh, that looks like a rebase conflict! Thanks for spotting it.
Fixed locally.
James
^ permalink raw reply
* Re: [PATCH v6 22/40] arm_mpam: resctrl: Convert to/from MPAMs fixed-point formats
From: James Morse @ 2026-03-27 15:47 UTC (permalink / raw)
To: Gavin Shan, Ben Horgan
Cc: amitsinght, baisheng.gao, baolin.wang, carl, dave.martin, david,
dfustini, fenghuay, 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: <bd450f1f-05a3-44d6-9bbc-1c48d967baa4@redhat.com>
Hi Gavin,
On 23/03/2026 22:49, Gavin Shan wrote:
> On 3/14/26 12:45 AM, Ben Horgan wrote:
>> From: Dave Martin <Dave.Martin@arm.com>
>>
>> MPAM uses a fixed-point formats for some hardware controls. Resctrl
>> provides the bandwidth controls as a percentage. Add helpers to convert
>> between these.
>>
>> Ensure bwa_wd is at most 16 to make it clear higher values have no meaning.
> One nitpick below, but this looks good to me in either way.
>
> Reviewed-by: Gavin Shan <gshan@redhat.com>
Thanks!
>> diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
>> index 0e5e24ef60fe..0c97f7708722 100644
>> --- a/drivers/resctrl/mpam_devices.c
>> +++ b/drivers/resctrl/mpam_devices.c
>> @@ -713,6 +713,13 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
>> mpam_set_feature(mpam_feat_mbw_part, props);
>> props->bwa_wd = FIELD_GET(MPAMF_MBW_IDR_BWA_WD, mbw_features);
>> +
>> + /*
>> + * The BWA_WD field can represent 0-63, but the control fields it
>> + * describes have a maximum of 16 bits.
>> + */
>> + props->bwa_wd = min(props->bwa_wd, 16);
>> +
>
> 16 may deserve a definition for it since it's a constant value and referred
> for multiple times in this patch, if we need to give this series another
> respin :-)
Hmmm.,. I've left this, I'm not sure what you'd call it. U16_BITS? That sort of thing
might be needed for long/int etc. Here there is either a comment, or its
accepting/returning a u16. I think its fairly obvious where the number 16 is coming from.
Thanks,
James
^ permalink raw reply
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