* [PATCH 01/15] drm/exynos: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-18 23:35 UTC (permalink / raw)
To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Jonathan Corbet, Shuah Khan
Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-tegra, virtualization, imx, linux-mediatek,
linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260719-drm_simple_encoder_init-v1-0-a78c509e3062@gmail.com>
The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.
Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.
Also check the return value from drm_encoder_init() to avoid silent
failures.
Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
drivers/gpu/drm/exynos/exynos_dp.c | 13 +++++++++++--
drivers/gpu/drm/exynos/exynos_drm_dpi.c | 14 ++++++++++++--
drivers/gpu/drm/exynos/exynos_drm_dsi.c | 11 +++++++++--
drivers/gpu/drm/exynos/exynos_drm_vidi.c | 14 ++++++++++++--
drivers/gpu/drm/exynos/exynos_hdmi.c | 15 +++++++++++++--
5 files changed, 57 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index b80540328150..1598892c602b 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -24,11 +24,11 @@
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_crtc.h>
+#include <drm/drm_encoder.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
#include <drm/exynos_drm.h>
#include "exynos_drm_crtc.h"
@@ -79,6 +79,10 @@ static void exynos_dp_nop(struct drm_encoder *encoder)
/* do nothing */
}
+static const struct drm_encoder_funcs exynos_dp_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
static const struct drm_encoder_helper_funcs exynos_dp_encoder_helper_funcs = {
.mode_set = exynos_dp_mode_set,
.enable = exynos_dp_nop,
@@ -95,7 +99,12 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data)
dp->drm_dev = drm_dev;
- drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
+ ret = drm_encoder_init(drm_dev, encoder, &exynos_dp_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret) {
+ dev_err(dp->dev, "Failed to initialize encoder\n");
+ return ret;
+ }
drm_encoder_helper_add(encoder, &exynos_dp_encoder_helper_funcs);
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dpi.c b/drivers/gpu/drm/exynos/exynos_drm_dpi.c
index 0dc36df6ada3..4e42a1da81d1 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_dpi.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_dpi.c
@@ -12,10 +12,10 @@
#include <linux/regulator/consumer.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_encoder.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
@@ -140,6 +140,10 @@ static void exynos_dpi_disable(struct drm_encoder *encoder)
}
}
+static const struct drm_encoder_funcs exynos_dpi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
static const struct drm_encoder_helper_funcs exynos_dpi_encoder_helper_funcs = {
.mode_set = exynos_dpi_mode_set,
.enable = exynos_dpi_enable,
@@ -194,7 +198,13 @@ int exynos_dpi_bind(struct drm_device *dev, struct drm_encoder *encoder)
{
int ret;
- drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS);
+ ret = drm_encoder_init(dev, encoder, &exynos_dpi_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret) {
+ DRM_DEV_ERROR(encoder_to_dpi(encoder)->dev,
+ "failed to create encoder ret = %d\n", ret);
+ return ret;
+ }
drm_encoder_helper_add(encoder, &exynos_dpi_encoder_helper_funcs);
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
index c4d098ab7863..6b7561ac9bb0 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
@@ -13,7 +13,7 @@
#include <drm/bridge/samsung-dsim.h>
#include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_encoder.h>
#include "exynos_drm_crtc.h"
#include "exynos_drm_drv.h"
@@ -22,6 +22,10 @@ struct exynos_dsi {
struct drm_encoder encoder;
};
+static const struct drm_encoder_funcs exynos_drm_dsi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
static irqreturn_t exynos_dsi_te_irq_handler(struct samsung_dsim *dsim)
{
struct exynos_dsi *dsi = dsim->priv;
@@ -79,7 +83,10 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, void *data
struct drm_device *drm_dev = data;
int ret;
- drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
+ ret = drm_encoder_init(drm_dev, encoder, &exynos_drm_dsi_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret)
+ return ret;
ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD);
if (ret < 0)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_vidi.c b/drivers/gpu/drm/exynos/exynos_drm_vidi.c
index 67bbf9b8bc0e..59dea853d364 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_vidi.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_vidi.c
@@ -13,10 +13,10 @@
#include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_vblank.h>
#include <drm/exynos_drm.h>
@@ -403,6 +403,10 @@ static void exynos_vidi_disable(struct drm_encoder *encoder)
{
}
+static const struct drm_encoder_funcs exynos_vidi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
static const struct drm_encoder_helper_funcs exynos_vidi_encoder_helper_funcs = {
.mode_set = exynos_vidi_mode_set,
.enable = exynos_vidi_enable,
@@ -445,7 +449,13 @@ static int vidi_bind(struct device *dev, struct device *master, void *data)
return PTR_ERR(ctx->crtc);
}
- drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
+ ret = drm_encoder_init(drm_dev, encoder, &exynos_vidi_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to initialize encoder ret = %d\n",
+ ret);
+ return ret;
+ }
drm_encoder_helper_add(encoder, &exynos_vidi_encoder_helper_funcs);
diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
index 09b2cabb236f..f44586ce0fdf 100644
--- a/drivers/gpu/drm/exynos/exynos_hdmi.c
+++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
@@ -36,9 +36,9 @@
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
#include "exynos_drm_crtc.h"
#include "regs-hdmi.h"
@@ -1575,6 +1575,11 @@ static void hdmi_disable(struct drm_encoder *encoder)
mutex_unlock(&hdata->mutex);
}
+static const struct drm_encoder_funcs exynos_hdmi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+
static const struct drm_encoder_helper_funcs exynos_hdmi_encoder_helper_funcs = {
.mode_fixup = hdmi_mode_fixup,
.enable = hdmi_enable,
@@ -1862,7 +1867,13 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
hdata->phy_clk.enable = hdmiphy_clk_enable;
- drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
+ ret = drm_encoder_init(drm_dev, encoder, &exynos_hdmi_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to initialize encoder ret = %d\n",
+ ret);
+ return ret;
+ }
drm_encoder_helper_add(encoder, &exynos_hdmi_encoder_helper_funcs);
--
2.54.0
^ permalink raw reply related
* [PATCH 00/15] drm/drm_simple: remove drm_simple_encoder_init
From: Diogo Silva @ 2026-07-18 23:35 UTC (permalink / raw)
To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Jonathan Corbet, Shuah Khan
Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-tegra, virtualization, imx, linux-mediatek,
linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.
This series open-codes all remaining drm_simple_encoder_init() users by
calling drm_encoder_init() directly and providing driver-local
drm_encoder_funcs where needed. After the driver conversions, the helper
is removed and the completed DRM todo item is dropped.
Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
Diogo Silva (15):
drm/exynos: remove dependency on DRM simple helpers
drm/xlnx/zynqmp_dpsub: remove dependency on DRM simple helpers
drm/tegra: remove dependency on DRM simple helpers
drm/fsl-dcu: remove dependency on DRM simple helpers
drm/kmb: remove dependency on DRM simple helpers
drm/virtio: remove dependency on DRM simple helpers
drm/tidss: remove dependency on DRM simple helpers
drm/imx: remove dependency on DRM simple helpers
drm/mediatek: remove dependency on DRM simple helpers
drm/renesas/shmobile: remove dependency on DRM simple helpers
drm/hisilicon/kirin: remove dependency on DRM simple helpers
drm/arm/komeda: remove dependency on DRM simple helpers
drm/meson: remove dependency on DRM simple helpers
drm/drm_simple: remove deprecated drm_simple_encoder_init function
Documentation/gpu: remove completed drm_simple_encoder_init() todo
Documentation/gpu/todo.rst | 15 ---------------
drivers/gpu/drm/arm/display/komeda/komeda_crtc.c | 9 +++++++--
drivers/gpu/drm/drm_simple_kms_helper.c | 13 ++-----------
drivers/gpu/drm/exynos/exynos_dp.c | 13 +++++++++++--
drivers/gpu/drm/exynos/exynos_drm_dpi.c | 14 ++++++++++++--
drivers/gpu/drm/exynos/exynos_drm_dsi.c | 11 +++++++++--
drivers/gpu/drm/exynos/exynos_drm_vidi.c | 14 ++++++++++++--
drivers/gpu/drm/exynos/exynos_hdmi.c | 15 +++++++++++++--
drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c | 10 +++++++---
drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c | 9 +++++++--
drivers/gpu/drm/imx/dc/dc-kms.c | 8 ++++++--
drivers/gpu/drm/kmb/kmb_dsi.c | 9 +++++++--
drivers/gpu/drm/mediatek/mtk_dsi.c | 10 +++++++---
drivers/gpu/drm/meson/meson_encoder_cvbs.c | 11 ++++++++---
drivers/gpu/drm/meson/meson_encoder_dsi.c | 11 ++++++++---
drivers/gpu/drm/meson/meson_encoder_hdmi.c | 11 ++++++++---
drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.c | 10 +++++++---
drivers/gpu/drm/tegra/dsi.c | 17 ++++++++++++++---
drivers/gpu/drm/tegra/rgb.c | 14 ++++++++++++--
drivers/gpu/drm/tidss/tidss_encoder.c | 10 +++++++---
drivers/gpu/drm/virtio/virtgpu_display.c | 12 ++++++++++--
drivers/gpu/drm/xlnx/zynqmp_kms.c | 13 +++++++++++--
include/drm/drm_simple_kms_helper.h | 4 ----
23 files changed, 185 insertions(+), 78 deletions(-)
---
base-commit: e55c6b9a3522aaf1441a0662d534c1c7bfa9b860
change-id: 20260718-drm_simple_encoder_init-d069a4cc7f8b
Best regards,
--
Diogo Silva <diogompaissilva@gmail.com>
^ permalink raw reply
* Re: [PATCH v7 2/3] iommu/arm-smmu-v3: Introduce CFGI/TLBI-repeat workaround infrastructure
From: Jason Gunthorpe @ 2026-07-18 16:45 UTC (permalink / raw)
To: Nicolin Chen
Cc: Ashish Mhetre, catalin.marinas, will, corbet, skhan, robin.murphy,
joro, linux-arm-kernel, linux-doc, linux-kernel, iommu,
linux-tegra
In-Reply-To: <alqbxaKVYFX1BZR5@nvidia.com>
On Fri, Jul 17, 2026 at 02:16:53PM -0700, Nicolin Chen wrote:
> Given the use cases on Tegra264, instead of patching the iommufd
> path as this patch does, perhaps we should simply spit a WARN in
> arm_vsmmu_init():
Nope, in this case you need to make the errata discoverable through
the viommu info so the VMM can validate the right DT is used, or apply
the WAR itself before submitting commands to the kernel.
Some kind of new flag in the viommu struct I guess
Jason
^ permalink raw reply
* [PATCH 6/6] blk-crypto: Update docs for blk-crypto-fallback motivation
From: Eric Biggers @ 2026-07-18 21:46 UTC (permalink / raw)
To: linux-fscrypt
Cc: linux-block, linux-fsdevel, linux-ext4, linux-f2fs-devel,
linux-doc, Eric Biggers
In-Reply-To: <20260718214655.63186-1-ebiggers@kernel.org>
The "Objective" section of inline-encryption.rst suggests that
blk-crypto-fallback is just for testing. That's no longer accurate, so
update it accordingly. Also fix a typo later in the document.
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
Documentation/block/inline-encryption.rst | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/Documentation/block/inline-encryption.rst b/Documentation/block/inline-encryption.rst
index 0052c7011b48..0df964507f76 100644
--- a/Documentation/block/inline-encryption.rst
+++ b/Documentation/block/inline-encryption.rst
@@ -37,12 +37,12 @@ initialization vector for each sector, and can be tested for correctness.
Objective
=========
-We want to support inline encryption in the kernel. To make testing easier, we
-also want support for falling back to the kernel crypto API when actual inline
-encryption hardware is absent. We also want inline encryption to work with
-layered devices like device-mapper and loopback (i.e. we want to be able to use
-the inline encryption hardware of the underlying devices if present, or else
-fall back to crypto API en/decryption).
+We want to support inline encryption hardware in the kernel. The API for using
+such hardware should also support a fallback to the CPU, so that users only need
+to use a single API and more of the code can be tested without actual hardware.
+We also want inline encryption to work with layered devices like device-mapper
+and loopback (i.e. we want to be able to use the inline encryption hardware of
+the underlying devices if present, or else fall back to the CPU).
Constraints and notes
=====================
@@ -295,7 +295,7 @@ hardware implementations might not implement both features together correctly,
and disallow the combination for now. Whenever a device supports integrity, the
kernel will pretend that the device does not support hardware inline encryption
(by setting the blk_crypto_profile in the request_queue of the device to NULL).
-When the crypto API fallback is enabled, this means that all bios with and
+When the crypto API fallback is enabled, this means that all bios with an
encryption context will use the fallback, and IO will complete as usual. When
the fallback is disabled, a bio with an encryption context will be failed.
--
2.55.0
^ permalink raw reply related
* [PATCH 5/6] blk-crypto: Remove unused function blk_crypto_config_supported()
From: Eric Biggers @ 2026-07-18 21:46 UTC (permalink / raw)
To: linux-fscrypt
Cc: linux-block, linux-fsdevel, linux-ext4, linux-f2fs-devel,
linux-doc, Eric Biggers
In-Reply-To: <20260718214655.63186-1-ebiggers@kernel.org>
blk_crypto_config_supported() is no longer called, so remove it.
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
Documentation/block/inline-encryption.rst | 29 ++++++++---------------
block/blk-crypto.c | 14 -----------
include/linux/blk-crypto.h | 2 --
3 files changed, 10 insertions(+), 35 deletions(-)
diff --git a/Documentation/block/inline-encryption.rst b/Documentation/block/inline-encryption.rst
index cae23949a626..0052c7011b48 100644
--- a/Documentation/block/inline-encryption.rst
+++ b/Documentation/block/inline-encryption.rst
@@ -185,20 +185,12 @@ blk-crypto-fallback is optional and is controlled by the
API presented to users of the block layer
=========================================
-``blk_crypto_config_supported()`` allows users to check ahead of time whether
-inline encryption with particular crypto settings will work on a particular
-block_device -- either via hardware or via blk-crypto-fallback. This function
-takes in a ``struct blk_crypto_config`` which is like blk_crypto_key, but omits
-the actual bytes of the key and instead just contains the algorithm, data unit
-size, etc. This function can be useful if blk-crypto-fallback is disabled.
-
``blk_crypto_init_key()`` allows users to initialize a blk_crypto_key.
Users must call ``blk_crypto_start_using_key()`` before actually starting to use
-a blk_crypto_key on a block_device (even if ``blk_crypto_config_supported()``
-was called earlier). This is needed to initialize blk-crypto-fallback if it
-will be needed. This must not be called from the data path, as this may have to
-allocate resources, which may deadlock in that case.
+a blk_crypto_key on a block_device. This is needed to initialize
+blk-crypto-fallback if it will be needed. This must not be called from the data
+path, as this may have to allocate resources, which may deadlock in that case.
Next, to attach an encryption context to a bio, users should call
``bio_crypt_set_ctx()``. This function allocates a bio_crypt_ctx and attaches
@@ -220,16 +212,15 @@ any kernel data structures it may be linked into.
In summary, for users of the block layer, the lifecycle of a blk_crypto_key is
as follows:
-1. ``blk_crypto_config_supported()`` (optional)
-2. ``blk_crypto_init_key()``
-3. ``blk_crypto_start_using_key()``
-4. ``bio_crypt_set_ctx()`` (potentially many times)
-5. ``blk_crypto_evict_key()`` (after all I/O has completed)
-6. Zeroize the blk_crypto_key (this has no dedicated function)
+1. ``blk_crypto_init_key()``
+2. ``blk_crypto_start_using_key()``
+3. ``bio_crypt_set_ctx()`` (potentially many times)
+4. ``blk_crypto_evict_key()`` (after all I/O has completed)
+5. Zeroize the blk_crypto_key (this has no dedicated function)
If a blk_crypto_key is being used on multiple block_devices, then
-``blk_crypto_config_supported()`` (if used), ``blk_crypto_start_using_key()``,
-and ``blk_crypto_evict_key()`` must be called on each block_device.
+``blk_crypto_start_using_key()`` and ``blk_crypto_evict_key()`` must be called
+on each block_device.
API presented to device drivers
===============================
diff --git a/block/blk-crypto.c b/block/blk-crypto.c
index 0fe6ef0eea1d..bc3a9f59574b 100644
--- a/block/blk-crypto.c
+++ b/block/blk-crypto.c
@@ -386,20 +386,6 @@ bool blk_crypto_config_supported_natively(struct block_device *bdev,
return true;
}
-/*
- * Check if bios with @cfg can be en/decrypted by blk-crypto (i.e. either the
- * block_device it's submitted to supports inline crypto, or the
- * blk-crypto-fallback is enabled and supports the cfg).
- */
-bool blk_crypto_config_supported(struct block_device *bdev,
- const struct blk_crypto_config *cfg)
-{
- if (IS_ENABLED(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) &&
- cfg->key_type == BLK_CRYPTO_KEY_TYPE_RAW)
- return true;
- return blk_crypto_config_supported_natively(bdev, cfg);
-}
-
/**
* blk_crypto_start_using_key() - Start using a blk_crypto_key on a device
* @bdev: block device to operate on
diff --git a/include/linux/blk-crypto.h b/include/linux/blk-crypto.h
index 5f40821f99cd..938ff536838c 100644
--- a/include/linux/blk-crypto.h
+++ b/include/linux/blk-crypto.h
@@ -171,8 +171,6 @@ void blk_crypto_evict_key(struct block_device *bdev,
bool blk_crypto_config_supported_natively(struct block_device *bdev,
const struct blk_crypto_config *cfg);
-bool blk_crypto_config_supported(struct block_device *bdev,
- const struct blk_crypto_config *cfg);
int blk_crypto_derive_sw_secret(struct block_device *bdev,
const u8 *eph_key, size_t eph_key_size,
--
2.55.0
^ permalink raw reply related
* [PATCH 4/6] fscrypt: Update docs for data path
From: Eric Biggers @ 2026-07-18 21:46 UTC (permalink / raw)
To: linux-fscrypt
Cc: linux-block, linux-fsdevel, linux-ext4, linux-f2fs-devel,
linux-doc, Eric Biggers
In-Reply-To: <20260718214655.63186-1-ebiggers@kernel.org>
Update the "Data path changes" section to accurately document and
elaborate on the current implementation of file contents en/decryption.
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
Documentation/filesystems/fscrypt.rst | 56 ++++++++++++++++++---------
1 file changed, 37 insertions(+), 19 deletions(-)
diff --git a/Documentation/filesystems/fscrypt.rst b/Documentation/filesystems/fscrypt.rst
index 5f1b5b53aa16..ef0925f78fa1 100644
--- a/Documentation/filesystems/fscrypt.rst
+++ b/Documentation/filesystems/fscrypt.rst
@@ -1475,25 +1475,43 @@ keys`_ and `DIRECT_KEY policies`_.
Data path changes
-----------------
-When inline encryption is used, filesystems just need to associate
-encryption contexts with bios to specify how the block layer or the
-inline encryption hardware will encrypt/decrypt the file contents.
-
-When inline encryption isn't used, filesystems must encrypt/decrypt
-the file contents themselves, as described below:
-
-For the read path (->read_folio()) of regular files, filesystems can
-read the ciphertext into the page cache and decrypt it in-place. The
-folio lock must be held until decryption has finished, to prevent the
-folio from becoming visible to userspace prematurely.
-
-For the write path (->writepages()) of regular files, filesystems
-cannot encrypt data in-place in the page cache, since the cached
-plaintext must be preserved. Instead, filesystems must encrypt into a
-temporary buffer or "bounce page", then write out the temporary
-buffer. Some filesystems, such as UBIFS, already use temporary
-buffers regardless of encryption. Other filesystems, such as ext4 and
-F2FS, have to allocate bounce pages specially for encryption.
+The block-based filesystems that support fscrypt, such as ext4 and
+f2fs, use blk-crypto (:ref:`inline_encryption`) to implement file
+contents encryption and decryption. With blk-crypto, the filesystem
+assigns an encryption context to each I/O request it issues to the
+contents of an encrypted file. The encryption (for writes) or
+decryption (for reads) is handled by the block layer transparently to
+the filesystem, using either the CPU or inline encryption hardware.
+
+Non-block-based filesystems can't use blk-crypto, so they make the
+calls to the cryptographic algorithms at the filesystem layer instead.
+
+Regardless of the layer in which they occur (blk-crypto-fallback or the
+filesystem), for CPU-based encryption and decryption of file contents:
+
+- For reads, the ciphertext data is read from the storage backend
+ (block device, network, UBI device, etc.) into the destination
+ buffers, then decrypted in-place. The destination buffers are
+ pagecache folios for buffered reads, or application-provided buffers
+ for direct reads. In either case, the filesystem reports success
+ only after decryption has successfully completed.
+
+- For writes, the plaintext data is encrypted from the source buffers
+ (which cannot be modified) into bounce buffers. Then, the
+ ciphertext in the bounce buffers is written to the storage backend.
+
+ The source buffers are usually pagecache folios for buffered writes,
+ or application-provided buffers for direct writes. There are also
+ some cases (all files on UBIFS, and compressed files on f2fs) where
+ the filesystem already uses bounce buffers for writes for other
+ reasons; in these cases the source plaintext data is already in
+ bounce buffers. UBIFS optimizes this case by encrypting the data
+ in-place in its existing bounce buffers.
+
+When inline encryption hardware is used instead of the CPU, reads from
+the storage backend logically return plaintext data, and writes accept
+plaintext data. In that case the flow is simplified: there's no
+scheduling of decryption work, and no bounce buffers are used.
Filename hashing and encoding
-----------------------------
--
2.55.0
^ permalink raw reply related
* [PATCH 3/6] fscrypt: Remove unused function fscrypt_finalize_bounce_page()
From: Eric Biggers @ 2026-07-18 21:46 UTC (permalink / raw)
To: linux-fscrypt
Cc: linux-block, linux-fsdevel, linux-ext4, linux-f2fs-devel,
linux-doc, Eric Biggers
In-Reply-To: <20260718214655.63186-1-ebiggers@kernel.org>
fscrypt_finalize_bounce_page() is no longer called, so remove it.
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
include/linux/fscrypt.h | 11 -----------
1 file changed, 11 deletions(-)
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index 52ff014aeae6..42ffd66a2491 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -1057,15 +1057,4 @@ static inline int fscrypt_encrypt_symlink(struct inode *inode,
return 0;
}
-/* If *pagep is a bounce page, free it and set *pagep to the pagecache page */
-static inline void fscrypt_finalize_bounce_page(struct page **pagep)
-{
- struct page *page = *pagep;
-
- if (fscrypt_is_bounce_page(page)) {
- *pagep = fscrypt_pagecache_page(page);
- fscrypt_free_bounce_page(page);
- }
-}
-
#endif /* _LINUX_FSCRYPT_H */
--
2.55.0
^ permalink raw reply related
* [PATCH 2/6] f2fs: Update outdated comment in f2fs_write_begin()
From: Eric Biggers @ 2026-07-18 21:46 UTC (permalink / raw)
To: linux-fscrypt
Cc: linux-block, linux-fsdevel, linux-ext4, linux-f2fs-devel,
linux-doc, Eric Biggers
In-Reply-To: <20260718214655.63186-1-ebiggers@kernel.org>
Refer to f2fs_set_bio_crypt_ctx() instead of the removed function
f2fs_encrypt_one_page().
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
fs/f2fs/data.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 65117dd2e123..a88ed125d266 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -3956,7 +3956,7 @@ static int f2fs_write_begin(const struct kiocb *iocb,
/*
* Although the block may be stored in the COW inode, the folio
* belongs to @inode and its data was encrypted (or not) using
- * @inode's context (see f2fs_encrypt_one_page()). Read with
+ * @inode's context (see f2fs_set_bio_crypt_ctx()). Read with
* @inode so the post-read decryption decision matches the
* folio's owner; otherwise an unencrypted @inode whose COW inode
* is encrypted hits a NULL ->i_crypt_info on decryption.
--
2.55.0
^ permalink raw reply related
* [PATCH 1/6] fs: Update outdated comment for SB_INLINECRYPT
From: Eric Biggers @ 2026-07-18 21:46 UTC (permalink / raw)
To: linux-fscrypt
Cc: linux-block, linux-fsdevel, linux-ext4, linux-f2fs-devel,
linux-doc, Eric Biggers
In-Reply-To: <20260718214655.63186-1-ebiggers@kernel.org>
Update the comment for SB_INLINECRYPT to match the latest code, where
SB_INLINECRYPT now controls whether blk-crypto uses inline encryption
hardware rather than whether blk-crypto is used.
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
include/linux/fs/super_types.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
index ef7941e9dc79..3bdd7f7fb9e5 100644
--- a/include/linux/fs/super_types.h
+++ b/include/linux/fs/super_types.h
@@ -300,7 +300,7 @@ struct super_block {
#define SB_NODIRATIME BIT(11) /* Do not update directory access times */
#define SB_SILENT BIT(15)
#define SB_POSIXACL BIT(16) /* Supports POSIX ACLs */
-#define SB_INLINECRYPT BIT(17) /* Use blk-crypto for encrypted files */
+#define SB_INLINECRYPT BIT(17) /* Use inline crypto hardware if available */
#define SB_KERNMOUNT BIT(22) /* this is a kern_mount call */
#define SB_I_VERSION BIT(23) /* Update inode I_version field */
#define SB_LAZYTIME BIT(25) /* Update the on-disk [acm]times lazily */
--
2.55.0
^ permalink raw reply related
* [PATCH 0/6] More fscrypt and blk-crypto doc updates and code removals
From: Eric Biggers @ 2026-07-18 21:46 UTC (permalink / raw)
To: linux-fscrypt
Cc: linux-block, linux-fsdevel, linux-ext4, linux-f2fs-devel,
linux-doc, Eric Biggers
This series applies on top of
"[PATCH v3 00/17] fscrypt: Standardize on blk-crypto"
(https://lore.kernel.org/linux-fscrypt/20260713023708.9245-1-ebiggers@kernel.org/).
It gets more documentation up to date, and it
removes a couple more functions that are no longer used.
I'm planning to take both series via the fscrypt tree.
Eric Biggers (6):
fs: Update outdated comment for SB_INLINECRYPT
f2fs: Update outdated comment in f2fs_write_begin()
fscrypt: Remove unused function fscrypt_finalize_bounce_page()
fscrypt: Update docs for data path
blk-crypto: Remove unused function blk_crypto_config_supported()
blk-crypto: Update docs for blk-crypto-fallback motivation
Documentation/block/inline-encryption.rst | 43 +++++++----------
Documentation/filesystems/fscrypt.rst | 56 +++++++++++++++--------
block/blk-crypto.c | 14 ------
fs/f2fs/data.c | 2 +-
include/linux/blk-crypto.h | 2 -
include/linux/fs/super_types.h | 2 +-
include/linux/fscrypt.h | 11 -----
7 files changed, 56 insertions(+), 74 deletions(-)
base-commit: 185a45827da9459c4484ea60cfe1797c3e9966b3
--
2.55.0
^ permalink raw reply
* [PATCH] fscrypt: Update encryption policy version docs
From: Eric Biggers @ 2026-07-18 21:25 UTC (permalink / raw)
To: linux-fscrypt; +Cc: linux-kernel, linux-doc, Eric Biggers
Update the wording of the documentation to put v1 encryption policies a
bit more firmly in the past, explicitly calling them "deprecated" (which
is consistent with the warning message the kernel has printed ever since
v5.4). Do the same for FS_IOC_GET_ENCRYPTION_POLICY which supports only
v1 policies, and remove the explicit recommendation to fall back to it.
Also clarify that reusing master keys across policies isn't the best
practice or normal usage, even though it's technically allowed for v2.
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
This patch is targeting the fscrypt tree
Documentation/filesystems/fscrypt.rst | 40 ++++++++++++---------------
1 file changed, 18 insertions(+), 22 deletions(-)
diff --git a/Documentation/filesystems/fscrypt.rst b/Documentation/filesystems/fscrypt.rst
index 5f1b5b53aa16..d7b71c5ce286 100644
--- a/Documentation/filesystems/fscrypt.rst
+++ b/Documentation/filesystems/fscrypt.rst
@@ -188,8 +188,8 @@ attacks:
- Non-root users cannot securely remove encryption keys.
All the above problems are fixed with v2 encryption policies. For
-this reason among others, it is recommended to use v2 encryption
-policies on all new encrypted directories.
+this reason among others, v1 encryption policies are deprecated. Use
+v2 encryption policies on all new encrypted directories.
Key hierarchy
=============
@@ -305,7 +305,8 @@ included in the IV. Moreover:
- For v2 encryption policies, the encryption is done with a per-mode
key derived using the KDF. Users may use the same master key for
- other v2 encryption policies.
+ other v2 encryption policies. However, using a distinct master key
+ for each policy is still the best practice and normal usage.
IV_INO_LBLK_64 policies
-----------------------
@@ -604,7 +605,9 @@ This structure must be initialized as follows:
struct fscrypt_policy_v1 is used or FSCRYPT_POLICY_V2 (2) if
struct fscrypt_policy_v2 is used. (Note: we refer to the original
policy version as "v1", though its version code is really 0.)
- For new encrypted directories, use v2 policies.
+ For new encrypted directories, use v2 policies, which are supported
+ since Linux v5.4. v1 policies are deprecated and have several
+ usability and security problems.
- ``contents_encryption_mode`` and ``filenames_encryption_mode`` must
be set to constants from ``<linux/fscrypt.h>`` which identify the
@@ -739,17 +742,6 @@ FS_IOC_SET_ENCRYPTION_POLICY can fail with the following errors:
Getting an encryption policy
----------------------------
-Two ioctls are available to get a file's encryption policy:
-
-- `FS_IOC_GET_ENCRYPTION_POLICY_EX`_
-- `FS_IOC_GET_ENCRYPTION_POLICY`_
-
-The extended (_EX) version of the ioctl is more general and is
-recommended to use when possible. However, on older kernels only the
-original ioctl is available. Applications should try the extended
-version, and if it fails with ENOTTY fall back to the original
-version.
-
FS_IOC_GET_ENCRYPTION_POLICY_EX
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -783,7 +775,6 @@ FS_IOC_GET_ENCRYPTION_POLICY_EX can fail with the following errors:
- ``ENODATA``: the file is not encrypted
- ``ENOTTY``: this type of filesystem does not implement encryption,
or this kernel is too old to support FS_IOC_GET_ENCRYPTION_POLICY_EX
- (try FS_IOC_GET_ENCRYPTION_POLICY instead)
- ``EOPNOTSUPP``: the kernel was not configured with encryption
support for this filesystem, or the filesystem superblock has not
had encryption enabled on it
@@ -799,12 +790,13 @@ check for STATX_ATTR_ENCRYPTED in stx_attributes.
FS_IOC_GET_ENCRYPTION_POLICY
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The FS_IOC_GET_ENCRYPTION_POLICY ioctl can also retrieve the
-encryption policy, if any, for a directory or regular file. However,
-unlike `FS_IOC_GET_ENCRYPTION_POLICY_EX`_,
-FS_IOC_GET_ENCRYPTION_POLICY only supports the original policy
-version. It takes in a pointer directly to struct fscrypt_policy_v1
-rather than struct fscrypt_get_policy_ex_arg.
+The FS_IOC_GET_ENCRYPTION_POLICY ioctl is deprecated. It supports
+only v1 encryption policies, which themselves are deprecated. Use
+`FS_IOC_GET_ENCRYPTION_POLICY_EX`_ instead.
+
+FS_IOC_GET_ENCRYPTION_POLICY retrieves the encryption policy for a
+directory or regular file, but only if it uses a v1 policy. It takes
+in a pointer directly to struct fscrypt_policy_v1.
The error codes for FS_IOC_GET_ENCRYPTION_POLICY are the same as those
for FS_IOC_GET_ENCRYPTION_POLICY_EX, except that
@@ -887,6 +879,10 @@ as follows:
To add this type of key, the calling process must have the
CAP_SYS_ADMIN capability in the initial user namespace.
+ (Note that v1 encryption policies are deprecated. The ability to
+ add a key for v1 encryption policies remains only for compatibility
+ with existing encrypted directories.)
+
Alternatively, if the key is being added for use by v2 encryption
policies, then ``key_spec.type`` must contain
FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER, and ``key_spec.u.identifier`` is
base-commit: 49a8501f1194e8e6a14c58628cbae678f3594a41
--
2.55.0
^ permalink raw reply related
* [RFC PATCH 2/2] init: support pinning the root image's fsverity digest
From: Eric Curtin @ 2026-07-18 19:15 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner
Cc: Jan Kara, Jonathan Corbet, Shuah Khan, Eric Biggers,
Theodore Y . Ts'o, Gao Xiang, Chao Yu, fsverity, linux-erofs,
linux-fsdevel, linux-doc, linux-kernel, Eric Curtin
In-Reply-To: <20260718191551.1703670-1-ericcurtin17@gmail.com>
When the root filesystem is mounted from an image file with rootimage=,
the carrier filesystem holding the image is typically writable and
therefore untrusted. Systems that seal their root images with fsverity
currently need an initramfs for the sole purpose of checking that the
image carries the expected fsverity digest before mounting it.
Add rootimageverity=<hash algorithm>:<hex digest>, which requires the
rootimage= file to have fsverity enabled with exactly this file digest
and fails the boot otherwise, using the same fsverity_get_digest()
interface that IMA and overlayfs already use for digest pinning.
Combined with a trusted kernel command line (e.g. a signed unified
kernel image, or a TPM-measured bootloader configuration), this extends
the chain of trust to every byte of the root filesystem without any
userspace boot stage: the digest pins the image's Merkle tree, and
fsverity keeps verifying all data read from the image against it at
runtime, so post-boot tampering with the carrier filesystem is detected
as well. It is the file-backed counterpart of setting up a dm-verity
target for a partition-backed root via dm-mod.create=.
Verification is done on the file the kernel is about to mount: it is
opened before mounting (which also loads the fsverity information) and
kept open across the mount, and no userspace exists yet that could race
a replacement in between.
Assisted-by: opencode:claude-fable-5
Signed-off-by: Eric Curtin <ericcurtin17@gmail.com>
---
.../admin-guide/kernel-parameters.txt | 13 ++++
init/do_mounts.c | 63 +++++++++++++++++++
2 files changed, 76 insertions(+)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 5dbd56098..105ebb171 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -6728,6 +6728,19 @@ Kernel parameters
root=) is moved to, instead of detaching it. Used
together with rootimage=.
+ rootimageverity= [KNL] Require the root image specified by
+ rootimage= to have fsverity enabled with this file
+ digest, given as <hash algorithm>:<hex digest>,
+ e.g. sha256:dd1b3fa9... The boot is aborted if the
+ image carries no or a different fsverity digest.
+ Because fsverity keeps verifying data read from the
+ image against its Merkle tree at runtime, a trusted
+ (e.g. signed or TPM-measured) kernel command line
+ extends the chain of trust to the complete root
+ filesystem contents without an initramfs. Requires
+ CONFIG_FS_VERITY and a carrier filesystem with
+ fsverity support.
+
rootwait [KNL] Wait (indefinitely) for root device to show up.
Useful for devices that are detected asynchronously
(e.g. USB and MMC devices).
diff --git a/init/do_mounts.c b/init/do_mounts.c
index 1b96ef30b..4eb792b27 100644
--- a/init/do_mounts.c
+++ b/init/do_mounts.c
@@ -24,6 +24,9 @@
#include <linux/nfs_fs_sb.h>
#include <linux/nfs_mount.h>
#include <linux/raid/detect.h>
+#include <linux/fsverity.h>
+#include <linux/hex.h>
+#include <crypto/hash_info.h>
#include <uapi/linux/mount.h>
#include "do_mounts.h"
@@ -155,10 +158,18 @@ static int __init root_image_srcdir_setup(char *str)
return 1;
}
+static char * __initdata root_image_verity;
+static int __init root_image_verity_setup(char *str)
+{
+ root_image_verity = str;
+ return 1;
+}
+
__setup("rootimage=", root_image_setup);
__setup("rootimagefstype=", root_image_fs_names_setup);
__setup("rootimageflags=", root_image_data_setup);
__setup("rootimagesrcdir=", root_image_srcdir_setup);
+__setup("rootimageverity=", root_image_verity_setup);
/* This can return zero length strings. Caller should check */
static int __init split_fs_names(char *page, size_t size, char *names)
@@ -442,6 +453,55 @@ void __init mount_root(char *root_device_name)
}
}
+#ifdef CONFIG_FS_VERITY
+/*
+ * Require the root image to carry the fsverity file digest given by
+ * rootimageverity=<hash algorithm>:<hex digest>. @file must have been
+ * opened so that its fsverity information is loaded. Any deviation
+ * fails the boot: with a trusted command line this pins the complete
+ * image contents, which fsverity keeps verifying against the image's
+ * Merkle tree as they are read.
+ */
+static void __init verify_root_image(struct file *file)
+{
+ u8 want[FS_VERITY_MAX_DIGEST_SIZE], got[FS_VERITY_MAX_DIGEST_SIZE];
+ enum hash_algo want_algo, got_algo;
+ int want_size, got_size, i;
+ char *hex;
+
+ hex = strchr(root_image_verity, ':');
+ if (!hex)
+ panic("VFS: rootimageverity= expects <algorithm>:<hex digest>");
+ *hex++ = '\0';
+ i = match_string(hash_algo_name, HASH_ALGO__LAST, root_image_verity);
+ if (i < 0)
+ panic("VFS: rootimageverity=: unknown hash algorithm \"%s\"",
+ root_image_verity);
+ want_algo = i;
+ want_size = hash_digest_size[want_algo];
+ if (strlen(hex) != 2 * want_size || hex2bin(want, hex, want_size))
+ panic("VFS: rootimageverity=: expected %d-byte hex digest",
+ want_size);
+
+ got_size = fsverity_get_digest(file_inode(file), got, NULL, &got_algo);
+ if (!got_size)
+ panic("VFS: root image does not have fsverity enabled");
+ if (got_algo != want_algo || got_size != want_size ||
+ memcmp(want, got, want_size))
+ panic("VFS: root image fsverity digest mismatch: expected %s:%*phN, got %s:%*phN",
+ hash_algo_name[want_algo], want_size, want,
+ hash_algo_name[got_algo], got_size, got);
+
+ pr_info("VFS: verified root image fsverity digest %s:%*phN\n",
+ hash_algo_name[want_algo], want_size, want);
+}
+#else /* !CONFIG_FS_VERITY */
+static void __init verify_root_image(struct file *file)
+{
+ panic("VFS: rootimageverity= requires CONFIG_FS_VERITY");
+}
+#endif /* !CONFIG_FS_VERITY */
+
/*
* Mount the actual root filesystem from the image file rootimage= on the
* filesystem that was just mounted from root= (the "carrier"), so that
@@ -481,6 +541,9 @@ static void __init mount_root_image(void)
panic("VFS: unable to open root image %s: error %ld",
root_image, PTR_ERR(file));
+ if (root_image_verity)
+ verify_root_image(file);
+
err = init_mkdir("/image", 0700);
if (err < 0 && err != -EEXIST)
panic("VFS: unable to create /image: error %d", err);
--
2.43.0
^ permalink raw reply related
* [RFC PATCH 1/2] init: support mounting the root filesystem from an image file
From: Eric Curtin @ 2026-07-18 19:15 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner
Cc: Jan Kara, Jonathan Corbet, Shuah Khan, Eric Biggers,
Theodore Y . Ts'o, Gao Xiang, Chao Yu, fsverity, linux-erofs,
linux-fsdevel, linux-doc, linux-kernel, Eric Curtin
In-Reply-To: <20260718191551.1703670-1-ericcurtin17@gmail.com>
Image-based Linux systems (bootc-style OS updaters, ChromeOS/Android-like
A/B schemes, embedded appliances) commonly keep one or more immutable
root filesystem images as plain files on a writable "carrier" filesystem
and pick one at boot. Today that always requires an initramfs, even when
the initramfs has nothing else to do: the kernel can only mount a block
device (or NFS/CIFS/ubi/mtd) as root, so userspace must mount the
carrier, loop-mount the image and switch_root into it.
Add a rootimage= parameter naming an image file on the filesystem
specified by root=. When set, root= only designates the carrier: after
mounting it as usual, mount the image file read-only on /image and make
that the root that prepare_namespace() pivots into. The image is
mounted through the filesystem's file-backed mount support (available in
erofs since v6.12), so no loop device is involved. rootimagefstype= and
rootimageflags= mirror rootfstype=/rootflags= for the image mount, while
"ro"/"rw"/rootflags= keep applying to the carrier.
The carrier mount is detached again by default; the image mount keeps
its superblock pinned, so userspace can still mount it later. Since
systems following this model virtually always need the carrier mounted
(it holds their writable state), rootimagesrcdir= optionally names a
directory inside the image where the carrier mount is moved instead.
This is the file-backed analogue of what dm-mod.create= (CONFIG_DM_INIT)
already does for device-mapper targets: moving a fixed, declarative bit
of early-boot setup from initramfs userspace into the kernel so that
small and verified systems can boot with no initramfs at all.
Assisted-by: opencode:claude-fable-5
Signed-off-by: Eric Curtin <ericcurtin17@gmail.com>
---
.../admin-guide/kernel-parameters.txt | 29 ++++
init/do_mounts.c | 158 ++++++++++++++++--
2 files changed, 174 insertions(+), 13 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index b5493a7f8..5dbd56098 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -6699,6 +6699,35 @@ Kernel parameters
rootfstype= [KNL] Set root filesystem type
+ rootimage= [KNL] Mount the actual root filesystem from this
+ filesystem image file (absolute path) located on the
+ filesystem specified by root=, instead of using that
+ filesystem as the root directly. This allows
+ image-based systems that keep their (typically
+ read-only, e.g. erofs) root filesystem images as
+ plain files on a carrier filesystem to boot without
+ an initramfs. The image is mounted read-only via
+ the filesystem's file-backed mount support (no loop
+ device is set up); "ro", "rw" and rootflags= keep
+ applying to the carrier filesystem. Once the image
+ is mounted, the carrier mount is detached (but kept
+ busy by the image mount itself) unless
+ rootimagesrcdir= is also given.
+
+ rootimageflags= [KNL] Set root image filesystem mount option string,
+ used together with rootimage=.
+
+ rootimagefstype= [KNL] Set root image filesystem type, used together
+ with rootimage=. Default is to try all filesystems
+ known to the kernel that can mount a block device or
+ an image file.
+
+ rootimagesrcdir= [KNL] Directory (absolute path) inside the root
+ image where the mount of the carrier filesystem
+ holding the image (i.e. the filesystem specified by
+ root=) is moved to, instead of detaching it. Used
+ together with rootimage=.
+
rootwait [KNL] Wait (indefinitely) for root device to show up.
Useful for devices that are detected asynchronously
(e.g. USB and MMC devices).
diff --git a/init/do_mounts.c b/init/do_mounts.c
index 95e0b3a0f..1b96ef30b 100644
--- a/init/do_mounts.c
+++ b/init/do_mounts.c
@@ -122,13 +122,51 @@ __setup("rootflags=", root_data_setup);
__setup("rootfstype=", fs_names_setup);
__setup("rootdelay=", root_delay_setup);
+/*
+ * rootimage= mounts the actual root filesystem from an image file located
+ * on the filesystem specified by root= instead of using that filesystem
+ * as the root directly.
+ */
+static char * __initdata root_image;
+static int __init root_image_setup(char *str)
+{
+ root_image = str;
+ return 1;
+}
+
+static char * __initdata root_image_fs_names;
+static int __init root_image_fs_names_setup(char *str)
+{
+ root_image_fs_names = str;
+ return 1;
+}
+
+static char * __initdata root_image_mount_data;
+static int __init root_image_data_setup(char *str)
+{
+ root_image_mount_data = str;
+ return 1;
+}
+
+static char * __initdata root_image_srcdir;
+static int __init root_image_srcdir_setup(char *str)
+{
+ root_image_srcdir = str;
+ return 1;
+}
+
+__setup("rootimage=", root_image_setup);
+__setup("rootimagefstype=", root_image_fs_names_setup);
+__setup("rootimageflags=", root_image_data_setup);
+__setup("rootimagesrcdir=", root_image_srcdir_setup);
+
/* This can return zero length strings. Caller should check */
-static int __init split_fs_names(char *page, size_t size)
+static int __init split_fs_names(char *page, size_t size, char *names)
{
int count = 1;
char *p = page;
- strscpy(p, root_fs_names, size);
+ strscpy(p, names, size);
while (*p++) {
if (p[-1] == ',') {
p[-1] = '\0';
@@ -139,8 +177,9 @@ static int __init split_fs_names(char *page, size_t size)
return count;
}
-static int __init do_mount_root(const char *name, const char *fs,
- const int flags, const void *data)
+static int __init do_mount_root(const char *name, const char *dir,
+ const char *fs, const int flags,
+ const void *data)
{
struct super_block *s;
char *data_page = NULL;
@@ -154,11 +193,11 @@ static int __init do_mount_root(const char *name, const char *fs,
strscpy_pad(data_page, data, PAGE_SIZE);
}
- ret = init_mount(name, "/root", fs, flags, data_page);
+ ret = init_mount(name, dir, fs, flags, data_page);
if (ret)
goto out;
- init_chdir("/root");
+ init_chdir(dir);
s = current->fs->pwd.dentry->d_sb;
ROOT_DEV = s->s_dev;
printk(KERN_INFO
@@ -185,7 +224,7 @@ void __init mount_root_generic(char *name, char *pretty_name, int flags)
scnprintf(b, BDEVNAME_SIZE, "unknown-block(%u,%u)",
MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
if (root_fs_names)
- num_fs = split_fs_names(fs_names, PAGE_SIZE);
+ num_fs = split_fs_names(fs_names, PAGE_SIZE, root_fs_names);
else
num_fs = list_bdev_fs_names(fs_names, PAGE_SIZE);
retry:
@@ -194,7 +233,7 @@ void __init mount_root_generic(char *name, char *pretty_name, int flags)
if (!*p)
continue;
- err = do_mount_root(name, p, flags, root_mount_data);
+ err = do_mount_root(name, "/root", p, flags, root_mount_data);
switch (err) {
case 0:
goto out;
@@ -266,7 +305,8 @@ static void __init mount_nfs_root(void)
*/
timeout = NFSROOT_TIMEOUT_MIN;
for (try = 1; ; try++) {
- if (!do_mount_root(root_dev, "nfs", root_mountflags, root_data))
+ if (!do_mount_root(root_dev, "/root", "nfs", root_mountflags,
+ root_data))
return;
if (try > NFSROOT_RETRY_MAX)
break;
@@ -303,7 +343,7 @@ static void __init mount_cifs_root(void)
timeout = CIFSROOT_TIMEOUT_MIN;
for (try = 1; ; try++) {
- if (!do_mount_root(root_dev, "cifs", root_mountflags,
+ if (!do_mount_root(root_dev, "/root", "cifs", root_mountflags,
root_data))
return;
if (try > CIFSROOT_RETRY_MAX)
@@ -345,7 +385,7 @@ static int __init mount_nodev_root(char *root_device_name)
fs_names = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!fs_names)
return -EINVAL;
- num_fs = split_fs_names(fs_names, PAGE_SIZE);
+ num_fs = split_fs_names(fs_names, PAGE_SIZE, root_fs_names);
for (i = 0, fstype = fs_names; i < num_fs;
i++, fstype += strlen(fstype) + 1) {
@@ -353,8 +393,8 @@ static int __init mount_nodev_root(char *root_device_name)
continue;
if (!fs_is_nodev(fstype))
continue;
- err = do_mount_root(root_device_name, fstype, root_mountflags,
- root_mount_data);
+ err = do_mount_root(root_device_name, "/root", fstype,
+ root_mountflags, root_mount_data);
if (!err)
break;
}
@@ -402,6 +442,96 @@ void __init mount_root(char *root_device_name)
}
}
+/*
+ * Mount the actual root filesystem from the image file rootimage= on the
+ * filesystem that was just mounted from root= (the "carrier"), so that
+ * image-based systems can boot without an initramfs.
+ *
+ * Called with the carrier mounted at /root and the cwd there. The image
+ * is always mounted read-only; "ro"/"rw"/rootflags= keep applying to the
+ * carrier. On success the cwd is the image's root, ready for the pivot
+ * in prepare_namespace(). The carrier mount is moved to rootimagesrcdir=
+ * inside the image if set, and detached otherwise; either way the image
+ * mount keeps the carrier superblock pinned.
+ */
+static void __init mount_root_image(void)
+{
+ unsigned long flags = MS_RDONLY | MS_SILENT;
+ char *path, *fs_names, *p;
+ struct file *file;
+ int num_fs, i, err;
+
+ if (root_image[0] != '/')
+ panic("VFS: rootimage= must be an absolute path");
+
+ path = kmalloc(PATH_MAX, GFP_KERNEL);
+ fs_names = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!path || !fs_names)
+ panic("VFS: unable to mount root image: not enough memory");
+
+ if (snprintf(path, PATH_MAX, "/root%s", root_image) >= PATH_MAX)
+ panic("VFS: rootimage= path too long");
+
+ /*
+ * Nothing that could modify the carrier runs yet, so the image
+ * cannot change between this open and the mount below.
+ */
+ file = filp_open(path, O_RDONLY | O_LARGEFILE, 0);
+ if (IS_ERR(file))
+ panic("VFS: unable to open root image %s: error %ld",
+ root_image, PTR_ERR(file));
+
+ err = init_mkdir("/image", 0700);
+ if (err < 0 && err != -EEXIST)
+ panic("VFS: unable to create /image: error %d", err);
+
+ if (root_image_fs_names)
+ num_fs = split_fs_names(fs_names, PAGE_SIZE,
+ root_image_fs_names);
+ else
+ num_fs = list_bdev_fs_names(fs_names, PAGE_SIZE);
+
+ for (i = 0, p = fs_names; i < num_fs; i++, p += strlen(p) + 1) {
+ if (!*p)
+ continue;
+ err = do_mount_root(path, "/image", p, flags,
+ root_image_mount_data);
+ switch (err) {
+ case 0:
+ goto mounted;
+ case -EACCES:
+ case -EINVAL:
+ case -ENOTBLK:
+ continue;
+ }
+ panic("VFS: unable to mount root image %s: error %d",
+ root_image, err);
+ }
+ panic("VFS: no filesystem could mount root image %s", root_image);
+
+mounted:
+ fput(file);
+
+ if (root_image_srcdir) {
+ if (root_image_srcdir[0] != '/')
+ panic("VFS: rootimagesrcdir= must be an absolute path");
+ if (snprintf(path, PATH_MAX, ".%s", root_image_srcdir) >=
+ PATH_MAX)
+ panic("VFS: rootimagesrcdir= path too long");
+ err = init_mount("/root", path, NULL, MS_MOVE, NULL);
+ if (err)
+ pr_err("VFS: failed to move the root image's carrier filesystem to %s: error %d\n",
+ root_image_srcdir, err);
+ } else {
+ err = -EINVAL;
+ }
+ if (err)
+ init_umount("/root", MNT_DETACH);
+
+ kfree(path);
+ kfree(fs_names);
+}
+
/* wait for any asynchronous scanning to complete */
static void __init wait_for_root(char *root_device_name)
{
@@ -481,6 +611,8 @@ void __init prepare_namespace(void)
if (root_wait)
wait_for_root(saved_root_name);
mount_root(saved_root_name);
+ if (root_image)
+ mount_root_image();
devtmpfs_mount();
if (init_pivot_root(".", ".")) {
--
2.43.0
^ permalink raw reply related
* [RFC PATCH 0/2] init: boot image-based systems without an initramfs (rootimage=)
From: Eric Curtin @ 2026-07-18 19:15 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner
Cc: Jan Kara, Jonathan Corbet, Shuah Khan, Eric Biggers,
Theodore Y . Ts'o, Gao Xiang, Chao Yu, fsverity, linux-erofs,
linux-fsdevel, linux-doc, linux-kernel, Eric Curtin
Image-based Linux systems (bootc-style OS updaters, ChromeOS/Android-like
A/B schemes, embedded appliances) keep one or more immutable root
filesystem images as sealed files on a writable filesystem and pick one
at boot. Booting such a system today always requires an initramfs, even
when that initramfs has nothing else to do; its only jobs are to parse
the kernel command line, mount the state filesystem, verify the image,
loop-mount it and switch_root into it.
This series teaches the kernel to do all of that directly:
root=PARTUUID=... rootimage=/deploy/a/root.erofs rootimagefstype=erofs \
rootimageverity=sha256:a9548f4c... rootimagesrcdir=/var/state
Patch 1 adds rootimage= (plus rootimagefstype=/rootimageflags=/
rootimagesrcdir=): root= then merely names the "carrier" filesystem.
The image file on it is mounted read-only through the filesystem's
file-backed mount support (available in erofs since v6.12), so no loop
device is involved, and it becomes the root that prepare_namespace()
pivots into. The carrier mount is detached by default, or moved to
rootimagesrcdir= inside the new root, which image-based systems
practically always want since the carrier holds their writable state.
Patch 2 adds rootimageverity=, which requires the image to carry a
specific fsverity file digest, reusing the fsverity_get_digest()
interface that IMA and overlayfs already use for digest pinning. With
a signed or measured command line (e.g. a unified kernel image), the
chain of trust extends to every byte of the root filesystem with no
userspace boot stage: the pinned digest authenticates the image's
Merkle tree root, and fsverity keeps verifying reads against it at
runtime, so later tampering with the carrier is caught as well.
This is the file-backed analogue of dm-mod.create= (CONFIG_DM_INIT),
which moved the equivalent block-device setup out of the initramfs for
verity-partition layouts back in v5.1. For file-based deployment
layouts nothing similar exists, so distributions ship a dracut stack
whose only purpose is the five steps above, and which remains the
largest and most failure-prone moving part of an otherwise fully
image-defined boot.
Tested on arm64 (qemu -M virt with KVM), with no initrd= at any point:
- control: plain ext4 root boots as before
- rootimage=: an erofs image file on ext4 is mounted as /, the carrier
ends up on /var/state, PID 1 runs from the image ~0.18s after kernel
entry
- rootimageverity= with the correct digest: boots, digest logged
- rootimageverity= with a wrong digest: panics with expected-vs-got
- build: defconfig and allnoconfig (!CONFIG_FS_VERITY, !CONFIG_BLOCK),
both with W=1, no warnings
Open questions for review:
- Should the carrier always be detached, dropping rootimagesrcdir=?
The image mount pins the carrier superblock either way, so userspace
can re-mount it, but a conflicting ro/rw state then needs a remount
dance.
- Should this be behind a Kconfig option like CONFIG_DM_INIT is? All
added code is __init and freed after boot.
- Naming: rootimage= vs. extending root= syntax (e.g. root=image:...).
This series was developed with AI assistance (see the Assisted-by tags
and Documentation/process/coding-assistants.rst).
Eric Curtin (2):
init: support mounting the root filesystem from an image file
init: support pinning the root image's fsverity digest
.../admin-guide/kernel-parameters.txt | 42 ++++
init/do_mounts.c | 221 ++++++++++++++++--
2 files changed, 250 insertions(+), 13 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH v2] Documentation: Extend the real-time hardware bits with some firmware bits
From: Sebastian Andrzej Siewior @ 2026-07-18 17:50 UTC (permalink / raw)
To: linux-rt-devel, linux-doc, linux-efi
Cc: Ilias Apalodimas, jenswi@kernel.org, op-tee, Ard Biesheuvel,
Clark Williams, Jan Kiszka, Jonathan Corbet, Shuah Khan,
Steven Rostedt, John Ogness
I have been reviewing how OP‑TEE is implemented and how secure‑world
invocations behave. The goal was to determine whether an OP‑TEE call can
delay the Linux side and introduce latency depending on the time spent
in the secure world.
Similar latency effects are already known for EFI runtime services, but
this was not documented. To mitigate the impact, EFI runtime invocations
can be restricted to specific CPUs so that real‑time workloads on other
CPUs remain unaffected. This mechanism, however, is only described in
the commit that introduced it.
This change adds a firmware section that documents these behaviours
explicitly. It highlights cases where firmware can delay the kernel,
information that may be unfamiliar to some users and surprising-or
concerning-to others.
Assisted-by: Microsoft-Copilot
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
v1…v2: https://lore.kernel.org/all/20260701091226.7SWW4TrT@linutronix.de
- Rewrote the OP-TEE bits after some feedback from Ilias and Jens.
Added a link to the TF-A/OP-TEE documentation. The main difference is
that in contrast to my initial belief, OP-TEE can disable
normal-world's interrupts and it is not guaranteed that normal world
can always preempt the secure world.
Documentation/core-api/real-time/hardware.rst | 104 ++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git a/Documentation/core-api/real-time/hardware.rst b/Documentation/core-api/real-time/hardware.rst
index 19f9bb3786e03..9f95e75e6aa18 100644
--- a/Documentation/core-api/real-time/hardware.rst
+++ b/Documentation/core-api/real-time/hardware.rst
@@ -130,3 +130,107 @@ https://github.com/Linutronix/RTC-Testbench.
The goal of this project is to validate real-time network communication. It can
be thought of as a "cyclictest" for networking and also serves as a starting
point for application development.
+
+Firmware
+--------
+
+The firmware often plays a significant role in system operation because it can
+perform tasks that the kernel cannot directly access, and in some cases it can
+even preempt or intercept the kernel.
+
+A common example of firmware assisting the kernel is when it provides a generic
+interface to a resource. Instead of accessing an RTC chip through an I2C host
+controller, the kernel may query the firmware for the current time, and the
+firmware then accesses the RTC behind the scenes.
+
+Firmware can also intercept kernel execution by providing services that
+temporarily take control of the system. One example is memory scrubbing, where
+the firmware periodically pauses the kernel, reads back portions of system
+memory, and then returns control. During this time, the kernel is effectively
+interrupted.
+In contrast, some systems provide hardware-based memory scrubbing, which
+operates independently of firmware or software. See
+Documentation/edac/scrub.rst for details.
+
+If the kernel is intercepted for longer periods then these periods can be made
+visible with the hardware latency detector. See
+Documentation/trace/hwlat_detector.rst.
+
+The kernel can also be intercepted in response to specific events, such as
+overheating. In this case, the firmware may throttle the CPU or shut it down
+immediately to prevent hardware damage.
+
+Unless the firmware is well documented, it should be thoroughly tested to
+uncover any unexpected behaviour.
+
+EFI
+~~~~
+
+EFI provides runtime services that act as a communication interface between the
+firmware and the operating system. One such service is reading and writing EFI
+variables, which are used, for example, to determine the boot source.
+
+Invoking a runtime service may require the architecture to disable kernel
+preemption or interrupts during the call. This means the duration of a service
+invocation directly affects the system’s observable latency. There is also
+nothing that prevents a service call from disabling interrupts internally while
+it runs.
+
+For these reasons, EFI runtime services are disabled by default on a PREEMPT_RT
+kernel. They can still be enabled at boot time or via a Kconfig option if
+required.
+The native EFI runtime service implementation (where both the EFI service and
+the kernel are either 32-bit or 64-bit executables) uses a wrapper mechanism
+that invokes the service through a dedicated workqueue. This workqueue is named
+efi_runtime, and it can be restricted to a housekeeping CPU using the
+``/sys/devices/virtual/workqueue/efi_runtime/cpumask`` sysfs file. Assigning it
+to a housekeeping CPU ensures that potentially long service invocations do not
+impact the real-time workload which is restricted to other CPUs.
+
+It must also be verified that the runtime services behave as expected. Some
+implementations on the x86 architecture pause all other CPUs while one CPU
+performs the service call. In such cases, the interruption affects all CPUs,
+and restricting the workqueue to a single CPU provides no benefit.
+
+OP-TEE (ARM)
+~~~~~~~~~~~~
+
+Execution flows from the normal world (Linux) into the secure world (OP-TEE)
+through the secure monitor at EL3. The transition is initiated by the `smc`
+(Secure Monitor Call) opcode or the `hvc` (Hypervisor Call) opcode together
+with a function identifier. The calling convention defines two types of calls:
+**yielding calls** and **fast calls**:
+
+- A **yielding call** unmasks interrupts before handling the requested service,
+ allowing normal world interrupts to occur.
+- A **fast call** handles the requested service atomically, without allowing
+ interrupts from either the normal world or the secure world.
+
+In addition, the secure world (EL3 and OP-TEE) can receive interrupts routed to
+the secure world. While a secure world interrupt is being serviced,
+normal world interrupts are masked and cannot preempt the operation.
+
+The transition from normal world to secure monitor to OP-TEE and back introduces
+additional latency due to world switching and context save/restore. This
+overhead is typically a few microseconds and usually remains within the noise
+floor.
+
+It is worth noting that the normal world cannot mask secure interrupts, while
+the secure world can mask normal-world interrupts during execution. How OP-TEE
+affects real-time workloads depends on whether secure interrupts are enabled
+and which OP-TEE services are invoked.
+
+A practical concern is any fast call that runs longer than expected, for
+example a function that occasionally performs a long-running cryptographic
+computation. Another example that may block in an unexpected way are OP-TEE
+drivers that issue RPC requests. An OP-TEE service in the secure world (RPMB
+for instance) may need to issue a request back to the normal world (the Linux
+driver) in order to complete the operation. While Linux remains preemptible,
+the thread that issued the request stays blocked until the RPC completes and
+the secure function call returns.
+
+The TF-A project provides documentation on interrupt management:
+https://trustedfirmware-a.readthedocs.io/en/latest/design/interrupt-framework-design.html#interrupt-management-framework
+
+The OP-TEE project provides documentation on how interrupts are handled:
+https://optee.readthedocs.io/en/latest/architecture/core.html#interrupt-handling
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v3] Documentation: admin-guide: fix trailing whitespace in cgroup-v2.rst
From: Randy Dunlap @ 2026-07-18 17:24 UTC (permalink / raw)
To: Yahya Toubali, Tejun Heo, Johannes Weiner, Michal Koutný,
Jonathan Corbet, Shuah Khan, open list:CONTROL GROUP (CGROUP),
open list:DOCUMENTATION, open list
Cc: Weijie Yuan
In-Reply-To: <20260718165614.1923568-1-yahya@yahyatoubali.me>
On 7/18/26 9:56 AM, Yahya Toubali wrote:
> Remove trailing whitespace flagged by checkpatch.pl in the cgroup
> memory and writeback sections.
>
> Signed-off-by: Yahya Toubali <yahya@yahyatoubali.me>
> ---
> Documentation/admin-guide/cgroup-v2.rst | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> index 14b8c571c0d1..481658cfef40 100644
> --- a/Documentation/admin-guide/cgroup-v2.rst
> +++ b/Documentation/admin-guide/cgroup-v2.rst
> @@ -1332,7 +1332,7 @@ PAGE_SIZE multiple when read back.
> cgroup is within its effective low boundary, the cgroup's
> memory won't be reclaimed unless there is no reclaimable
> memory available in unprotected cgroups.
> - Above the effective low boundary (or
> + Above the effective low boundary (or
> effective min boundary if it is higher), pages are reclaimed
> proportionally to the overage, reducing reclaim pressure for
> smaller overages.
> @@ -2194,7 +2194,7 @@ of the two is enforced.
>
> cgroup writeback requires explicit support from the underlying
> filesystem. Currently, cgroup writeback is implemented on ext2, ext4,
> -btrfs, f2fs, and xfs. On other filesystems, all writeback IOs are
> +btrfs, f2fs, and xfs. On other filesystems, all writeback IOs are
> attributed to the root cgroup.
>
> There are inherent differences in memory and writeback management
>
> base-commit: 1229e2e57a5c2980ccd457b9b53ea0eed5a22ab3
> prerequisite-patch-id: c8aee5eb39e3cd6f2b2f28c82163565665288d2a
From Documentation/process/submitting-patches.rst: (paraphrased)
Don't reply to v2 of a patch with v3 of that patch. We prefer that new/fresh
patches begin their own email thread.
thanks.
--
~Randy
^ permalink raw reply
* [PATCH RFC v3 11/11] platform/x86: ideapad-laptop: Fully support auto keyboard backlight
From: Rong Zhang @ 2026-07-18 17:05 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Jonathan Corbet, Shuah Khan,
Thomas Weißschuh, Benson Leung, Guenter Roeck,
Marek Behún, Mark Pearson, Derek J. Clark, Hans de Goede,
Ilpo Järvinen, Ike Panhc
Cc: Andrew Lunn, Jakub Kicinski, Vishnu Sankar, Vishnu Sankar,
linux-leds, netdev, linux-doc, linux-kernel, chrome-platform,
platform-driver-x86, Rong Zhang
In-Reply-To: <20260719-leds-trigger-hw-changed-v3-0-5fb55722e36e@rong.moe>
Currently, the auto brightness mode of keyboard backlight maps to
brightness=0 in LED classdev. The only method to switch to such a mode
is by pressing the manufacturer-defined shortcut (Fn+Space). However, 0
is a multiplexed brightness value; writing 0 simply results in the
backlight being turned off.
With brightness processing code decoupled from LED classdev, we can now
fully support the auto brightness mode. In this mode, the keyboard
backlight is controlled by the EC according to the ambient light sensor
(ALS).
To utilize this, a private hardware control trigger "ideapad-auto" is
added, with the event handling procedure calling the
led_trigger_notify_hw_control_changed() interface to activate/deactivate
the private trigger according to the current LED trigger state.
Meanwhile, block brightness changes on exit to prevent the side effect
of LED device unregistration when the private trigger is active from
resetting the brightness to zero, so that we can retain the state of
auto mode among boots.
Signed-off-by: Rong Zhang <i@rong.moe>
---
Changes in v3:
- Address concerns from Sashiko
- Fix a race condition in ideapad_kbd_bl_led_cdev_brightness_set()
- Fix trigger re-registration of ideapad_kbd_bl_auto_trigger
- https://sashiko.dev/#/patchset/20260618-leds-trigger-hw-changed-v2-0-c28c44053cf3%40rong.moe
- Make registration failures of ideapad_kbd_bl_auto_trigger non-fatal
---
drivers/platform/x86/lenovo/ideapad-laptop.c | 112 ++++++++++++++++++++++++---
1 file changed, 103 insertions(+), 9 deletions(-)
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 66e16abda5e3..253d2962b927 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -1714,9 +1714,58 @@ static int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev,
{
struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led);
+ /*
+ * When deinitializing: It must be the side effect of led_cdev
+ * unregistration when our private trigger is active. We've set
+ * LED_RETAIN_AT_SHUTDOWN to retain led_cdev brightness level.
+ * To do the same for auto mode, gate changes and return early.
+ */
+ if (unlikely(!priv->kbd_bl.initialized))
+ return 0;
+
return ideapad_kbd_bl_brightness_set(priv, brightness);
}
+static bool ideapad_kbd_bl_auto_trigger_offloaded(struct led_classdev *led_cdev)
+{
+ struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led);
+
+ return atomic_read(&priv->kbd_bl.last_hw_brightness) == KBD_BL_AUTO_MODE_HW_BRIGHTNESS;
+}
+
+static int ideapad_kbd_bl_auto_trigger_activate(struct led_classdev *led_cdev)
+{
+ struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led);
+
+ return ideapad_kbd_bl_hw_brightness_set(priv, KBD_BL_AUTO_MODE_HW_BRIGHTNESS);
+}
+
+static struct led_hw_trigger_type ideapad_kbd_bl_auto_trigger_type;
+
+static struct led_trigger ideapad_kbd_bl_auto_trigger = {
+ .name = "ideapad-auto",
+ .trigger_type = &ideapad_kbd_bl_auto_trigger_type,
+ .activate = ideapad_kbd_bl_auto_trigger_activate,
+ .offloaded = ideapad_kbd_bl_auto_trigger_offloaded,
+};
+
+static bool ideapad_kbd_bl_auto_trigger_registered;
+
+static void ideapad_kbd_bl_notify_hw_control(struct ideapad_private *priv,
+ int hw_brightness, int last_hw_brightness)
+{
+ bool hw_control, last_hw_control;
+
+ if (priv->kbd_bl.type != KBD_BL_TRISTATE_AUTO)
+ return;
+
+ hw_control = hw_brightness == KBD_BL_AUTO_MODE_HW_BRIGHTNESS;
+ last_hw_control = last_hw_brightness == KBD_BL_AUTO_MODE_HW_BRIGHTNESS;
+
+ if (hw_control != last_hw_control)
+ led_trigger_notify_hw_control_changed(&priv->kbd_bl.led, hw_control);
+}
+
static void ideapad_kbd_bl_notify(struct ideapad_private *priv)
{
int hw_brightness, brightness, last_hw_brightness;
@@ -1738,6 +1787,8 @@ static void ideapad_kbd_bl_notify(struct ideapad_private *priv)
if (hw_brightness == last_hw_brightness)
return;
+ ideapad_kbd_bl_notify_hw_control(priv, hw_brightness, last_hw_brightness);
+
led_classdev_notify_brightness_hw_changed(&priv->kbd_bl.led, brightness);
}
@@ -1768,6 +1819,24 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
switch (priv->kbd_bl.type) {
case KBD_BL_TRISTATE_AUTO:
+ priv->kbd_bl.led.max_brightness = 2;
+
+ if (!ideapad_kbd_bl_auto_trigger_registered) {
+ dev_warn(&priv->platform_device->dev,
+ "Could not provide LED trigger %s for keyboard backlight\n",
+ ideapad_kbd_bl_auto_trigger.name);
+ break;
+ }
+
+ priv->kbd_bl.led.flags |= LED_TRIG_HW_CHANGED;
+ priv->kbd_bl.led.hw_control_trigger = ideapad_kbd_bl_auto_trigger.name;
+ priv->kbd_bl.led.trigger_type = &ideapad_kbd_bl_auto_trigger_type;
+
+ /* Hardware remembers the last brightness level, including auto mode. */
+ if (hw_brightness == KBD_BL_AUTO_MODE_HW_BRIGHTNESS)
+ priv->kbd_bl.led.default_trigger = ideapad_kbd_bl_auto_trigger.name;
+
+ break;
case KBD_BL_TRISTATE:
priv->kbd_bl.led.max_brightness = 2;
break;
@@ -1779,13 +1848,22 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
unreachable();
}
- err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led);
- if (err)
- return err;
+ /* Queue notifications, as kbd_bl.initialized is about to be set. */
+ guard(mutex)(&priv->kbd_bl.notif_mutex);
+ /*
+ * Setting kbd_bl.initialized after led_classdev_register() could lead
+ * to race conditions in ideapad_kbd_bl_led_cdev_brightness_set() where
+ * kbd_bl.initialized is checked, so set it now. It can be reverted back
+ * if the LED classdev failed to register.
+ */
priv->kbd_bl.initialized = true;
- return 0;
+ err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led);
+ if (err)
+ priv->kbd_bl.initialized = false;
+
+ return err;
}
static void ideapad_kbd_bl_exit(struct ideapad_private *priv)
@@ -2612,17 +2690,30 @@ static int __init ideapad_laptop_init(void)
{
int err;
+ err = led_trigger_register(&ideapad_kbd_bl_auto_trigger);
+ if (err) {
+ pr_warn("Failed to register LED trigger %s: %d\n",
+ ideapad_kbd_bl_auto_trigger.name, err);
+ } else {
+ ideapad_kbd_bl_auto_trigger_registered = true;
+ }
+
err = ideapad_wmi_driver_register();
if (err)
- return err;
+ goto err_ledtrig;
err = platform_driver_register(&ideapad_acpi_driver);
- if (err) {
- ideapad_wmi_driver_unregister();
- return err;
- }
+ if (err)
+ goto err_wmi;
return 0;
+
+err_wmi:
+ ideapad_wmi_driver_unregister();
+err_ledtrig:
+ if (ideapad_kbd_bl_auto_trigger_registered)
+ led_trigger_unregister(&ideapad_kbd_bl_auto_trigger);
+ return err;
}
module_init(ideapad_laptop_init)
@@ -2630,6 +2721,9 @@ static void __exit ideapad_laptop_exit(void)
{
ideapad_wmi_driver_unregister();
platform_driver_unregister(&ideapad_acpi_driver);
+
+ if (ideapad_kbd_bl_auto_trigger_registered)
+ led_trigger_unregister(&ideapad_kbd_bl_auto_trigger);
}
module_exit(ideapad_laptop_exit)
--
2.53.0
^ permalink raw reply related
* [PATCH RFC v3 10/11] platform/x86: ideapad-laptop: Serialize keyboard backlight notifications
From: Rong Zhang @ 2026-07-18 17:05 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Jonathan Corbet, Shuah Khan,
Thomas Weißschuh, Benson Leung, Guenter Roeck,
Marek Behún, Mark Pearson, Derek J. Clark, Hans de Goede,
Ilpo Järvinen, Ike Panhc
Cc: Andrew Lunn, Jakub Kicinski, Vishnu Sankar, Vishnu Sankar,
linux-leds, netdev, linux-doc, linux-kernel, chrome-platform,
platform-driver-x86, Rong Zhang
In-Reply-To: <20260719-leds-trigger-hw-changed-v3-0-5fb55722e36e@rong.moe>
ACPI notifications are delivered in dedicated work contexts and may
arrive simultaneously. In the following change, much work will be done
while handling the notification, which could lead to potential race
conditions.
Introduce a new mutex to serialize keyboard backlight notifications to
prevent potential race conditions.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/platform/x86/lenovo/ideapad-laptop.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 5aa2fedb8472..66e16abda5e3 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -26,7 +26,9 @@
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/leds.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#include <linux/power_supply.h>
@@ -228,6 +230,8 @@ struct ideapad_private {
int type;
struct led_classdev led;
atomic_t last_hw_brightness;
+
+ struct mutex notif_mutex; /* protects notifications */
} kbd_bl;
struct {
bool initialized;
@@ -1720,6 +1724,8 @@ static void ideapad_kbd_bl_notify(struct ideapad_private *priv)
if (!priv->kbd_bl.initialized)
return;
+ guard(mutex)(&priv->kbd_bl.notif_mutex);
+
hw_brightness = ideapad_kbd_bl_hw_brightness_get(priv);
if (hw_brightness < 0)
return;
@@ -1745,6 +1751,10 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
if (WARN_ON(priv->kbd_bl.initialized))
return -EEXIST;
+ err = devm_mutex_init(&priv->platform_device->dev, &priv->kbd_bl.notif_mutex);
+ if (err)
+ return err;
+
hw_brightness = ideapad_kbd_bl_hw_brightness_get(priv);
if (hw_brightness < 0)
return hw_brightness;
--
2.53.0
^ permalink raw reply related
* [PATCH RFC v3 09/11] platform/x86: ideapad-laptop: Decouple hardware & classdev brightness for keyboard backlight
From: Rong Zhang @ 2026-07-18 17:05 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Jonathan Corbet, Shuah Khan,
Thomas Weißschuh, Benson Leung, Guenter Roeck,
Marek Behún, Mark Pearson, Derek J. Clark, Hans de Goede,
Ilpo Järvinen, Ike Panhc
Cc: Andrew Lunn, Jakub Kicinski, Vishnu Sankar, Vishnu Sankar,
linux-leds, netdev, linux-doc, linux-kernel, chrome-platform,
platform-driver-x86, Rong Zhang
In-Reply-To: <20260719-leds-trigger-hw-changed-v3-0-5fb55722e36e@rong.moe>
Some recent models come with an ambient light sensor (ALS). On these
models, their EC will automatically set the keyboard backlight to an
appropriate brightness when the effective "hardware brightness" is 3.
"Hardware brightness" can't be perfectly mapped to an LED classdev
brightness, but the EC does use this predefined brightness value to
represent auto mode.
Currently, the code processing keyboard backlight is coupled with LED
classdev, making it hard to expose the auto brightness (ALS) mode to the
userspace.
As the first step toward the goal, decouple hardware brightness from LED
classdev brightness, and update comments about corresponding backlight
modes.
Since upcoming changes will heavily rely on kbd_bl.last_hw_brightness,
also convert it into an atomic_t to prevent potential race conditions.
To minimalize the diff set in upcoming changes, a trivial refactor
also converts the initialization path into another equivalent form.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/platform/x86/lenovo/Kconfig | 1 +
drivers/platform/x86/lenovo/ideapad-laptop.c | 144 ++++++++++++++++++---------
2 files changed, 100 insertions(+), 45 deletions(-)
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index 4443f40ef8aa..e92b1e900795 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -16,6 +16,7 @@ config IDEAPAD_LAPTOP
select INPUT_SPARSEKMAP
select NEW_LEDS
select LEDS_CLASS
+ select LEDS_TRIGGERS
help
This is a driver for Lenovo IdeaPad netbooks contains drivers for
rfkill switch, hotkey, fan control and backlight control.
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 4fbc904f1fc3..5aa2fedb8472 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -9,6 +9,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
+#include <linux/atomic.h>
#include <linux/backlight.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
@@ -134,10 +135,31 @@ enum {
};
/*
- * These correspond to the number of supported states - 1
- * Future keyboard types may need a new system, if there's a collision
- * KBD_BL_TRISTATE_AUTO has no way to report or set the auto state
- * so it effectively has 3 states, but needs to handle 4
+ * The enumeration has two purposes:
+ * - as an internal identifier for all known types of keyboard backlight
+ * - as a mandatory parameter of the KBLC command
+ *
+ * For each type, the hardware brightness values are defined as follows:
+ * +--------------------------+----------+-----+------+------+
+ * | Hardware brightness | 0 | 1 | 2 | 3 |
+ * | Type | | | | |
+ * +--------------------------+----------+-----+------+------+
+ * | KBD_BL_STANDARD | off | on | N/A | N/A |
+ * +--------------------------+----------+-----+------+------+
+ * | KBD_BL_TRISTATE | off | low | high | N/A |
+ * +--------------------------+----------+-----+------+------+
+ * | KBD_BL_TRISTATE_AUTO | off | low | high | auto |
+ * +--------------------------+----------+-----+------+------+
+ *
+ * We map LED classdev brightness for KBD_BL_TRISTATE_AUTO as follows:
+ * +--------------------------+----------+-----+------+
+ * | LED classdev brightness | 0 | 1 | 2 |
+ * | Operation | | | |
+ * +--------------------------+----------+-----+------+
+ * | Read | off/auto | low | high |
+ * +--------------------------+----------+-----+------+
+ * | Write | off | low | high |
+ * +--------------------------+----------+-----+------+
*/
enum {
KBD_BL_STANDARD = 1,
@@ -145,6 +167,8 @@ enum {
KBD_BL_TRISTATE_AUTO = 3,
};
+#define KBD_BL_AUTO_MODE_HW_BRIGHTNESS 3
+
#define KBD_BL_QUERY_TYPE 0x1
#define KBD_BL_TRISTATE_TYPE 0x5
#define KBD_BL_TRISTATE_AUTO_TYPE 0x7
@@ -203,7 +227,7 @@ struct ideapad_private {
bool initialized;
int type;
struct led_classdev led;
- unsigned int last_brightness;
+ atomic_t last_hw_brightness;
} kbd_bl;
struct {
bool initialized;
@@ -1592,7 +1616,24 @@ static int ideapad_kbd_bl_check_tristate(int type)
return (type == KBD_BL_TRISTATE) || (type == KBD_BL_TRISTATE_AUTO);
}
-static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv)
+static int ideapad_kbd_bl_brightness_parse(struct ideapad_private *priv, int hw_brightness)
+{
+ /* Off, low or high */
+ if (hw_brightness <= priv->kbd_bl.led.max_brightness)
+ return hw_brightness;
+
+ /* Auto (controlled by EC according to ALS), report as off */
+ if (priv->kbd_bl.type == KBD_BL_TRISTATE_AUTO &&
+ hw_brightness == KBD_BL_AUTO_MODE_HW_BRIGHTNESS)
+ return 0;
+
+ /* Unknown value */
+ dev_warn(&priv->platform_device->dev,
+ "Unknown keyboard backlight value: %d", hw_brightness);
+ return -EINVAL;
+}
+
+static int ideapad_kbd_bl_hw_brightness_get(struct ideapad_private *priv)
{
unsigned long value;
int err;
@@ -1606,21 +1647,7 @@ static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv)
if (err)
return err;
- /* Convert returned value to brightness level */
- value = FIELD_GET(KBD_BL_GET_BRIGHTNESS, value);
-
- /* Off, low or high */
- if (value <= priv->kbd_bl.led.max_brightness)
- return value;
-
- /* Auto, report as off */
- if (value == priv->kbd_bl.led.max_brightness + 1)
- return 0;
-
- /* Unknown value */
- dev_warn(&priv->platform_device->dev,
- "Unknown keyboard backlight value: %lu", value);
- return -EINVAL;
+ return FIELD_GET(KBD_BL_GET_BRIGHTNESS, value);
}
err = eval_hals(priv->adev->handle, &value);
@@ -1630,6 +1657,16 @@ static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv)
return !!test_bit(HALS_KBD_BL_STATE_BIT, &value);
}
+static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv)
+{
+ int hw_brightness = ideapad_kbd_bl_hw_brightness_get(priv);
+
+ if (hw_brightness < 0)
+ return hw_brightness;
+
+ return ideapad_kbd_bl_brightness_parse(priv, hw_brightness);
+}
+
static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev)
{
struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led);
@@ -1637,32 +1674,37 @@ static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_cla
return ideapad_kbd_bl_brightness_get(priv);
}
-static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness)
+static int ideapad_kbd_bl_hw_brightness_set(struct ideapad_private *priv, int hw_brightness)
{
- int err;
unsigned long value;
int type = priv->kbd_bl.type;
+ int err;
if (ideapad_kbd_bl_check_tristate(type)) {
- if (brightness > priv->kbd_bl.led.max_brightness)
- return -EINVAL;
-
- value = FIELD_PREP(KBD_BL_SET_BRIGHTNESS, brightness) |
+ value = FIELD_PREP(KBD_BL_SET_BRIGHTNESS, hw_brightness) |
FIELD_PREP(KBD_BL_COMMAND_TYPE, type) |
KBD_BL_COMMAND_SET;
err = exec_kblc(priv->adev->handle, value);
} else {
- err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF);
+ value = hw_brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF;
+ err = exec_sals(priv->adev->handle, value);
}
-
if (err)
return err;
- priv->kbd_bl.last_brightness = brightness;
+ atomic_set(&priv->kbd_bl.last_hw_brightness, hw_brightness);
return 0;
}
+static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, int brightness)
+{
+ if (brightness > priv->kbd_bl.led.max_brightness)
+ return -EINVAL;
+
+ return ideapad_kbd_bl_hw_brightness_set(priv, brightness);
+}
+
static int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
@@ -1673,26 +1715,29 @@ static int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev,
static void ideapad_kbd_bl_notify(struct ideapad_private *priv)
{
- int brightness;
+ int hw_brightness, brightness, last_hw_brightness;
if (!priv->kbd_bl.initialized)
return;
- brightness = ideapad_kbd_bl_brightness_get(priv);
- if (brightness < 0)
+ hw_brightness = ideapad_kbd_bl_hw_brightness_get(priv);
+ if (hw_brightness < 0)
return;
- if (brightness == priv->kbd_bl.last_brightness)
- return;
+ brightness = ideapad_kbd_bl_brightness_parse(priv, hw_brightness);
+ if (brightness < 0)
+ return; /* Reject insane values early. */
- priv->kbd_bl.last_brightness = brightness;
+ last_hw_brightness = atomic_xchg(&priv->kbd_bl.last_hw_brightness, hw_brightness);
+ if (hw_brightness == last_hw_brightness)
+ return;
led_classdev_notify_brightness_hw_changed(&priv->kbd_bl.led, brightness);
}
static int ideapad_kbd_bl_init(struct ideapad_private *priv)
{
- int brightness, err;
+ int hw_brightness, err;
if (!priv->features.kbd_bl)
return -ENODEV;
@@ -1700,21 +1745,30 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
if (WARN_ON(priv->kbd_bl.initialized))
return -EEXIST;
- if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type))
- priv->kbd_bl.led.max_brightness = 2;
- else
- priv->kbd_bl.led.max_brightness = 1;
+ hw_brightness = ideapad_kbd_bl_hw_brightness_get(priv);
+ if (hw_brightness < 0)
+ return hw_brightness;
- brightness = ideapad_kbd_bl_brightness_get(priv);
- if (brightness < 0)
- return brightness;
+ atomic_set(&priv->kbd_bl.last_hw_brightness, hw_brightness);
- priv->kbd_bl.last_brightness = brightness;
priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT;
priv->kbd_bl.led.brightness_get = ideapad_kbd_bl_led_cdev_brightness_get;
priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set;
priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED | LED_RETAIN_AT_SHUTDOWN;
+ switch (priv->kbd_bl.type) {
+ case KBD_BL_TRISTATE_AUTO:
+ case KBD_BL_TRISTATE:
+ priv->kbd_bl.led.max_brightness = 2;
+ break;
+ case KBD_BL_STANDARD:
+ priv->kbd_bl.led.max_brightness = 1;
+ break;
+ default:
+ /* This has already been validated by ideapad_check_features(). */
+ unreachable();
+ }
+
err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led);
if (err)
return err;
--
2.53.0
^ permalink raw reply related
* [PATCH RFC v3 08/11] leds: trigger: Add led_trigger_notify_hw_control_changed() interface
From: Rong Zhang @ 2026-07-18 17:05 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Jonathan Corbet, Shuah Khan,
Thomas Weißschuh, Benson Leung, Guenter Roeck,
Marek Behún, Mark Pearson, Derek J. Clark, Hans de Goede,
Ilpo Järvinen, Ike Panhc
Cc: Andrew Lunn, Jakub Kicinski, Vishnu Sankar, Vishnu Sankar,
linux-leds, netdev, linux-doc, linux-kernel, chrome-platform,
platform-driver-x86, Rong Zhang
In-Reply-To: <20260719-leds-trigger-hw-changed-v3-0-5fb55722e36e@rong.moe>
Some hardware can autonomously activate/deactivate hardware control.
After that, the LED hardware notifies the LED driver. Currently, there
is no mechanism for LED drivers to notify the LED core about such events
and initiate a trigger transition to reflect the hardware state.
Add a new interface called led_trigger_notify_hw_control_changed(), so
that LED drivers can call it to notify the LED core about the
transition.
The interface only allows two transitions:
1. "none" => private trigger
2. private trigger => "none"
If the current trigger is neither the private trigger nor "none", no
transition will be made. This protects the currently selected software
trigger.
Note that LED_OFF won't be emitted during the #2 transition, as some
hardware may have selected a new brightness level during its hardware
state transition (e.g., laptop keyboards with a shortcut cycling through
different backlight brightnesses and auto mode).
The interface is designed as a void function as any failure should be
non-fatal and the result of transition should not have any impact on the
LED drivers' event handling procedures.
To use the interface, LEDS_TRIGGERS_HW_CHANGED must be enabled in
Kconfig, and the LED driver must set the LED_TRIG_HW_CHANGED flag for
the classdev.
Signed-off-by: Rong Zhang <i@rong.moe>
---
Changes in v3:
- Adopt guard() (Thanks Thomas Weißschuh)
- Reword documentations
---
Documentation/leds/leds-class.rst | 52 +++++++++++++++++++++++++
drivers/leds/led-triggers.c | 82 ++++++++++++++++++++++++++++++++++++++-
drivers/leds/trigger/Kconfig | 9 +++++
include/linux/leds.h | 8 ++++
4 files changed, 149 insertions(+), 2 deletions(-)
diff --git a/Documentation/leds/leds-class.rst b/Documentation/leds/leds-class.rst
index 2d41a6db602c..adbc57b9f49c 100644
--- a/Documentation/leds/leds-class.rst
+++ b/Documentation/leds/leds-class.rst
@@ -334,6 +334,58 @@ not necessary for them to coordinate via `hw_control_*` callbacks.
When the LED is in hw control, no software blink is possible and doing so
will effectively disable hw control.
+Hardware-initiated trigger transition
+=====================================
+
+Some hardware can autonomously activate/deactivate hardware control. After that,
+the LED hardware notifies the LED driver.
+
+If the driver can detect such transitions and thus wants to notify the LED core
+to update the current trigger then the `LED_TRIG_HW_CHANGED` flag must be set in
+flags before registering. To update the current trigger accordingly, call
+`led_trigger_notify_hw_control_changed` on the LED classdev.
+
+This capability is restricted to the LED device's private trigger. The private
+trigger must have been properly registered (see above) and named after
+`hw_control_trigger`.
+
+Only two transitions are defined:
+
+- "none" => private trigger:
+ This happens when the hardware autonomously activates hardware control
+ and when "none" (i.e., no trigger) is currently active. If the private
+ trigger is already active when the method is called, this is essentially
+ a no-op.
+
+ The activation sequence for the private trigger will be executed as
+ normal.
+
+ The LED driver and its private trigger must be able to handle the
+ activation sequence even if the hardware is currently in hardware
+ control.
+
+ If error occurs in the activation sequence, the LED Trigger core reverts
+ the effective trigger to "none".
+
+- private trigger => "none"
+ This happens when the hardware autonomously deactivates hardware control
+ and when the private trigger is currently active. If "none" (i.e., no
+ trigger) is active when the method is called, this is essentially a
+ no-op.
+
+ The deactivation sequence for the private trigger will be executed as
+ normal, except that the current LED brightness is retained. The reason
+ for keeping the brightness unchanged is that some hardware may choose a
+ specific brightness instead of simply turning off the LED after
+ autonomously deactivating hardware control.
+
+ The LED driver and its private trigger must be able to handle the
+ deactivation sequence even if the hardware is not currently in hardware
+ control.
+
+If the current trigger is neither the private trigger nor "none", no transition
+will be made.
+
Known Issues
============
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index 726fa7bf88cf..6ae28cbd1c77 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -7,6 +7,7 @@
* Author: Richard Purdie <rpurdie@openedhand.com>
*/
+#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/export.h>
#include <linux/kernel.h>
@@ -192,7 +193,8 @@ ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
EXPORT_SYMBOL_GPL(led_trigger_read);
/* Caller must ensure led_cdev->trigger_lock held */
-int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
+static int __led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig,
+ bool hw_triggered)
{
char *event = NULL;
char *envp[2];
@@ -223,7 +225,21 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
led_cdev->trigger_data = NULL;
led_cdev->activated = false;
led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
- led_set_brightness(led_cdev, LED_OFF);
+
+ /*
+ * Hardware may have selected a new brightness level during its
+ * hardware control transition, so only reset brightness if we
+ * are switching to another trigger or if the switching is not
+ * hardware triggered.
+ *
+ * Note that this does not apply to the error path, as running
+ * into the error path implies a none => private trigger
+ * transition. This hints that the LED driver and its private
+ * trigger must have some fundamental bugs, so don't bother
+ * leaving the LED in an undefined state.
+ */
+ if (trig || !hw_triggered)
+ led_set_brightness(led_cdev, LED_OFF);
}
if (trig) {
spin_lock(&trig->leddev_list_lock);
@@ -287,6 +303,11 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
return ret;
}
+
+int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
+{
+ return __led_trigger_set(led_cdev, trig, false);
+}
EXPORT_SYMBOL_GPL(led_trigger_set);
void led_trigger_remove(struct led_classdev *led_cdev)
@@ -467,6 +488,63 @@ int devm_led_trigger_register(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_led_trigger_register);
+#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED
+static void led_trigger_do_hw_control_transition(struct led_classdev *led_cdev, bool activate,
+ struct led_trigger *hc_trig)
+{
+ int err = 0;
+
+ if (!led_cdev->trigger) {
+ /* "none" => private trigger. */
+ if (activate)
+ err = __led_trigger_set(led_cdev, hc_trig, true);
+ } else if (led_cdev->trigger == hc_trig) {
+ /* private trigger => "none". */
+ if (!activate)
+ err = __led_trigger_set(led_cdev, NULL, true);
+ } else {
+ /* Other trigger is active. */
+ dev_dbg(led_cdev->dev,
+ "Ignoring hw control transition (%s %s) while %s is active",
+ activate ? "activate" : "deactivate", hc_trig->name,
+ led_cdev->trigger->name);
+
+ return;
+ }
+
+ if (err)
+ dev_warn(led_cdev->dev, "Failed to %s %s in hw control transition: %d",
+ activate ? "activate" : "deactivate", hc_trig->name, err);
+}
+
+void led_trigger_notify_hw_control_changed(struct led_classdev *led_cdev, bool activate)
+{
+ struct led_trigger *trig;
+
+ /* Restricted to private triggers. */
+ if (WARN_ON(!(led_cdev->flags & LED_TRIG_HW_CHANGED) ||
+ !led_cdev->hw_control_trigger || !led_cdev->trigger_type))
+ return;
+
+ scoped_guard(rwsem_read, &triggers_list_lock) {
+ list_for_each_entry(trig, &trigger_list, next_trig) {
+ if (trig->trigger_type == led_cdev->trigger_type &&
+ !strcmp(trig->name, led_cdev->hw_control_trigger)) {
+ guard(rwsem_write)(&led_cdev->trigger_lock);
+
+ led_trigger_do_hw_control_transition(led_cdev, activate, trig);
+ return;
+ }
+ }
+ }
+
+ dev_err(led_cdev->dev,
+ "%s() is called, but the private trigger (%s) is not properly registered\n",
+ __func__, led_cdev->hw_control_trigger);
+}
+EXPORT_SYMBOL_GPL(led_trigger_notify_hw_control_changed);
+#endif /* CONFIG_LEDS_TRIGGERS_HW_CHANGED */
+
/* Simple LED Trigger Interface */
void led_trigger_event(struct led_trigger *trig,
diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig
index c11282a74b5a..798122154049 100644
--- a/drivers/leds/trigger/Kconfig
+++ b/drivers/leds/trigger/Kconfig
@@ -9,6 +9,15 @@ menuconfig LEDS_TRIGGERS
if LEDS_TRIGGERS
+config LEDS_TRIGGERS_HW_CHANGED
+ bool "LED hardware-initiated trigger transition support"
+ help
+ This option enables support for hardware initiated hardware control
+ transitions, where the LED hardware autonomously switches between
+ "none" (i.e., no trigger) and its private trigger.
+
+ See Documentation/leds/leds-class.rst for details.
+
config LEDS_TRIGGER_TIMER
tristate "LED Timer Trigger"
help
diff --git a/include/linux/leds.h b/include/linux/leds.h
index cc664da33e94..167598962b73 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -109,6 +109,7 @@ struct led_classdev {
#define LED_INIT_DEFAULT_TRIGGER BIT(23)
#define LED_REJECT_NAME_CONFLICT BIT(24)
#define LED_MULTI_COLOR BIT(25)
+#define LED_TRIG_HW_CHANGED BIT(26)
/* set_brightness_work / blink_timer flags, atomic, private. */
unsigned long work_flags;
@@ -609,6 +610,13 @@ led_trigger_get_brightness(const struct led_trigger *trigger)
#endif /* CONFIG_LEDS_TRIGGERS */
+#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED
+void led_trigger_notify_hw_control_changed(struct led_classdev *led_cdev, bool activate);
+#else
+static inline void led_trigger_notify_hw_control_changed(struct led_classdev *led_cdev,
+ bool activate) {}
+#endif
+
/* Trigger specific enum */
enum led_trigger_netdev_modes {
TRIGGER_NETDEV_LINK = 0,
--
2.53.0
^ permalink raw reply related
* [PATCH RFC v3 07/11] leds: trigger: Enforce strict checks in led_trigger_is_hw_controlled()
From: Rong Zhang @ 2026-07-18 17:05 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Jonathan Corbet, Shuah Khan,
Thomas Weißschuh, Benson Leung, Guenter Roeck,
Marek Behún, Mark Pearson, Derek J. Clark, Hans de Goede,
Ilpo Järvinen, Ike Panhc
Cc: Andrew Lunn, Jakub Kicinski, Vishnu Sankar, Vishnu Sankar,
linux-leds, netdev, linux-doc, linux-kernel, chrome-platform,
platform-driver-x86, Rong Zhang
In-Reply-To: <20260719-leds-trigger-hw-changed-v3-0-5fb55722e36e@rong.moe>
With all existing triggers adopting the new interface, strict checks
could be enforced to make the semantics of hardware control triggers
clearer.
In detail, a hardware control trigger should:
- Implement offloaded() callback to indicate hardware control
- Associate with the LED classdev's hw_control_trigger string
Signed-off-by: Rong Zhang <i@rong.moe>
---
Changes in v3:
- New patch in the series, splitted from PATCH 3 (thanks Thomas
Weißschuh)
---
drivers/leds/led-triggers.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index c3c41ef40f01..726fa7bf88cf 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -42,9 +42,16 @@ static bool __led_trigger_is_hw_controlled(struct led_classdev *led_cdev)
if (!led_cdev->trigger)
return false;
+ if (!led_cdev->hw_control_trigger ||
+ strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name))
+ return false;
+
if (led_cdev->trigger->offloaded)
return led_cdev->trigger->offloaded(led_cdev);
+ dev_warn_once(led_cdev->dev, "hw control trigger %s doesn't implement offloaded()\n",
+ led_cdev->trigger->name);
+
return led_cdev->trigger->trigger_type;
}
--
2.53.0
^ permalink raw reply related
* [PATCH RFC v3 06/11] leds: trigger: netdev: Implement offloaded() callback
From: Rong Zhang @ 2026-07-18 17:05 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Jonathan Corbet, Shuah Khan,
Thomas Weißschuh, Benson Leung, Guenter Roeck,
Marek Behún, Mark Pearson, Derek J. Clark, Hans de Goede,
Ilpo Järvinen, Ike Panhc
Cc: Andrew Lunn, Jakub Kicinski, Vishnu Sankar, Vishnu Sankar,
linux-leds, netdev, linux-doc, linux-kernel, chrome-platform,
platform-driver-x86, Rong Zhang
In-Reply-To: <20260719-leds-trigger-hw-changed-v3-0-5fb55722e36e@rong.moe>
"netdev" can run in hardware control according to hardware capabilities
and trigger options.
Implement offloaded() callback to provide its hardware control state to
the LED core, and document the relation between the custom "offloaded"
attribute and the generic "trigger_may_offload" attribute.
Signed-off-by: Rong Zhang <i@rong.moe>
---
Changes in v3:
- Do not deprecate netdev's "offloaded" attribute (thanks Thomas
Weißschuh)
- Document the relation between the custom "offloaded" attribute and the
generic "trigger_may_offload" attribute (ditto)
---
Documentation/ABI/testing/sysfs-class-led | 3 +++
Documentation/ABI/testing/sysfs-class-led-trigger-netdev | 3 +++
drivers/leds/trigger/ledtrig-netdev.c | 8 ++++++++
3 files changed, 14 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-class-led b/Documentation/ABI/testing/sysfs-class-led
index b61fc2e71bd3..7dc95f7a3505 100644
--- a/Documentation/ABI/testing/sysfs-class-led
+++ b/Documentation/ABI/testing/sysfs-class-led
@@ -100,6 +100,9 @@ Description:
- `[foo_trigger]`: the trigger is selected and offloaded to
hardware.
+ The "netdev" trigger also provides a custom attribute to
+ indicate its state, see `/sys/class/leds/<led>/offloaded`.
+
What: /sys/class/leds/<led>/inverted
Date: January 2011
KernelVersion: 2.6.38
diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-netdev b/Documentation/ABI/testing/sysfs-class-led-trigger-netdev
index ed46b37ab8a2..a5146ea1e3e6 100644
--- a/Documentation/ABI/testing/sysfs-class-led-trigger-netdev
+++ b/Documentation/ABI/testing/sysfs-class-led-trigger-netdev
@@ -75,6 +75,9 @@ Description:
If 1, the LED blinking in requested mode is offloaded to
hardware.
+ LED trigger core also provides a generic attribute for this
+ purpose, see `/sys/class/leds/<led>/trigger_may_offload`.
+
What: /sys/class/leds/<led>/link_10
Date: Jun 2023
KernelVersion: 6.5
diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
index 64c078e997f2..a26109ca4b1c 100644
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -754,10 +754,18 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev)
kfree(trigger_data);
}
+static bool netdev_trig_offloaded(struct led_classdev *led_cdev)
+{
+ struct led_netdev_data *trigger_data = led_get_trigger_data(led_cdev);
+
+ return trigger_data->hw_control;
+}
+
static struct led_trigger netdev_led_trigger = {
.name = "netdev",
.activate = netdev_trig_activate,
.deactivate = netdev_trig_deactivate,
+ .offloaded = netdev_trig_offloaded,
.groups = netdev_trig_groups,
};
--
2.53.0
^ permalink raw reply related
* [PATCH RFC v3 05/11] leds: turris-omnia: trigger: Implement offloaded() and declare hw_control_trigger
From: Rong Zhang @ 2026-07-18 17:05 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Jonathan Corbet, Shuah Khan,
Thomas Weißschuh, Benson Leung, Guenter Roeck,
Marek Behún, Mark Pearson, Derek J. Clark, Hans de Goede,
Ilpo Järvinen, Ike Panhc
Cc: Andrew Lunn, Jakub Kicinski, Vishnu Sankar, Vishnu Sankar,
linux-leds, netdev, linux-doc, linux-kernel, chrome-platform,
platform-driver-x86, Rong Zhang
In-Reply-To: <20260719-leds-trigger-hw-changed-v3-0-5fb55722e36e@rong.moe>
"omnia-mcu" is a private hardware control trigger which always stays in
hardware control mode. Implement offloaded() callback with its return
value to be always true to reflect this.
Meanwhile, declare it as a hardware control trigger as it's forgotten
before.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/leds/leds-turris-omnia.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c
index ed6a47bbb44f..32d40d176d3f 100644
--- a/drivers/leds/leds-turris-omnia.c
+++ b/drivers/leds/leds-turris-omnia.c
@@ -195,10 +195,16 @@ static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
err);
}
+static bool omnia_hwtrig_offloaded(struct led_classdev *cdev)
+{
+ return true;
+}
+
static struct led_trigger omnia_hw_trigger = {
.name = "omnia-mcu",
.activate = omnia_hwtrig_activate,
.deactivate = omnia_hwtrig_deactivate,
+ .offloaded = omnia_hwtrig_offloaded,
.trigger_type = &omnia_hw_trigger_type,
};
@@ -251,6 +257,7 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
* by LED class from the linux,default-trigger property.
*/
cdev->default_trigger = omnia_hw_trigger.name;
+ cdev->hw_control_trigger = omnia_hw_trigger.name;
/* Put the LED into software mode */
ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(led->reg) |
--
2.53.0
^ permalink raw reply related
* [PATCH RFC v3 04/11] leds: cros_ec: trigger: Implement offloaded() callback
From: Rong Zhang @ 2026-07-18 17:05 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Jonathan Corbet, Shuah Khan,
Thomas Weißschuh, Benson Leung, Guenter Roeck,
Marek Behún, Mark Pearson, Derek J. Clark, Hans de Goede,
Ilpo Järvinen, Ike Panhc
Cc: Andrew Lunn, Jakub Kicinski, Vishnu Sankar, Vishnu Sankar,
linux-leds, netdev, linux-doc, linux-kernel, chrome-platform,
platform-driver-x86, Rong Zhang
In-Reply-To: <20260719-leds-trigger-hw-changed-v3-0-5fb55722e36e@rong.moe>
"chromeos-auto" is a private hardware control trigger which always stays
in hardware control. Implement offloaded() callback with its return
value to be always true to reflect this.
Reviewed-by: Thomas Weißschuh <linux@weissschuh.net>
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/leds/leds-cros_ec.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/leds/leds-cros_ec.c b/drivers/leds/leds-cros_ec.c
index 1844d0cd5f52..6db83d015277 100644
--- a/drivers/leds/leds-cros_ec.c
+++ b/drivers/leds/leds-cros_ec.c
@@ -85,12 +85,18 @@ static int cros_ec_led_trigger_activate(struct led_classdev *led_cdev)
return cros_ec_led_send_cmd(priv->cros_ec, &arg);
}
+static bool cros_ec_led_trigger_offloaded(struct led_classdev *led_cdev)
+{
+ return true;
+}
+
static struct led_hw_trigger_type cros_ec_led_trigger_type;
static struct led_trigger cros_ec_led_trigger = {
.name = "chromeos-auto",
.trigger_type = &cros_ec_led_trigger_type,
.activate = cros_ec_led_trigger_activate,
+ .offloaded = cros_ec_led_trigger_offloaded,
};
static int cros_ec_led_brightness_set_blocking(struct led_classdev *led_cdev,
--
2.53.0
^ permalink raw reply related
* [PATCH RFC v3 03/11] leds: trigger: Add offloaded() callback and provide trigger_may_offload attribute
From: Rong Zhang @ 2026-07-18 17:05 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Jonathan Corbet, Shuah Khan,
Thomas Weißschuh, Benson Leung, Guenter Roeck,
Marek Behún, Mark Pearson, Derek J. Clark, Hans de Goede,
Ilpo Järvinen, Ike Panhc
Cc: Andrew Lunn, Jakub Kicinski, Vishnu Sankar, Vishnu Sankar,
linux-leds, netdev, linux-doc, linux-kernel, chrome-platform,
platform-driver-x86, Rong Zhang
In-Reply-To: <20260719-leds-trigger-hw-changed-v3-0-5fb55722e36e@rong.moe>
There are multiple triggers implementing hardware control. However, the
LED trigger core doesn't really know the hardware control (offloaded)
state since the coordination is done directly between the trigger and
the LED driver. It can only assume private triggers as offloaded and
generic ones as not offloaded.
Add an offloaded() callback so that triggers can report their offloaded
states to the LED trigger core. When unimplemented, it defaults to true
for private triggers and false for generic ones to keep the current
behavior unchanged.
With that, provide a new attribute "trigger_may_offload", so that
userspace can determine:
- if the LED device supports hardware control (supported => visible)
- which trigger is the hardware control trigger selected by the LED
device
- if the trigger is selected ("<foo_trigger>")
- if the trigger is offloaded ("[foo_trigger]")
Note: the documentation describes the attribute as "returning a list"
despite the LED core currently only supports one hardware control
trigger per LED device. This is intentional to make the attribute
extensible in the future without breaking userspace.
Signed-off-by: Rong Zhang <i@rong.moe>
---
Changes in v3:
- Rearrange the series so that the code using the offloaded() callback is
introduced before the driver implementation (thanks Thomas Weißschuh)
- Reword documentation (ditto)
- Adopt guard() and lockdep (ditto)
- Adopt __led_trigger_is_hw_controlled() from newly-integrated PATCH 1
---
Documentation/ABI/testing/sysfs-class-led | 22 ++++++++++++++++++++++
Documentation/leds/leds-class.rst | 20 ++++++++++++++++++++
drivers/leds/led-class.c | 22 ++++++++++++++++++++++
drivers/leds/led-triggers.c | 29 +++++++++++++++++++++++++++++
drivers/leds/leds.h | 2 ++
include/linux/leds.h | 1 +
6 files changed, 96 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-class-led b/Documentation/ABI/testing/sysfs-class-led
index d4c918cc11a1..b61fc2e71bd3 100644
--- a/Documentation/ABI/testing/sysfs-class-led
+++ b/Documentation/ABI/testing/sysfs-class-led
@@ -78,6 +78,28 @@ Description:
(which would often be configured in the device tree for the
hardware).
+What: /sys/class/leds/<led>/trigger_may_offload
+Date: July 2026
+KernelVersion: 7.3
+Contact: linux-leds@vger.kernel.org
+Description:
+ Names and states of triggers that may be offloaded to hardware.
+ Such triggers are also called "hardware control trigger" in some
+ context.
+
+ Only exists when the LED supports trigger offload.
+
+ Reading this file returns a list of triggers that are capable to
+ be offloaded. The optional brackets around the trigger name
+ indicate the state of the current trigger:
+
+ - `foo_trigger`: the trigger is not selected.
+ - `<foo_trigger>`: the trigger is selected, but falls back to
+ software blink for some reason (e.g., incompatible trigger
+ parameters)
+ - `[foo_trigger]`: the trigger is selected and offloaded to
+ hardware.
+
What: /sys/class/leds/<led>/inverted
Date: January 2011
KernelVersion: 2.6.38
diff --git a/Documentation/leds/leds-class.rst b/Documentation/leds/leds-class.rst
index 3913966cfdac..2d41a6db602c 100644
--- a/Documentation/leds/leds-class.rst
+++ b/Documentation/leds/leds-class.rst
@@ -242,6 +242,9 @@ ops and needs to declare specific support for the supported triggers.
With hw control we refer to the LED driven by hardware.
+A sysfs attribute `trigger_may_offload` is provided for userspace to
+query supported triggers and their states.
+
LED driver must define the following value to support hw control:
- hw_control_trigger:
@@ -298,6 +301,15 @@ LED driver must implement the following API to support hw control:
Returns a pointer to a struct device or NULL if nothing
is currently attached.
+LED trigger should implement the following API to indicate hw control:
+ - offloaded:
+ return a boolean indicating if the trigger is currently
+ offloaded to hardware.
+
+ If a trigger doesn't implement this callback, the default
+ value will be true for private triggers and false for generic
+ ones.
+
LED driver can activate additional modes by default to workaround the
impossibility of supporting each different mode on the supported trigger.
Examples are hardcoding the blink speed to a set interval, enable special
@@ -311,6 +323,14 @@ the end use hw_control_set to activate hw control.
A trigger can use hw_control_get to check if a LED is already in hw control
and init their flags.
+Alternatively, a private trigger can be implemented along with the LED driver if
+the LED's hardware control doesn't fit any generic trigger. To associate the
+private trigger with the LED classdev, their `trigger_type` must be the same. To
+declare that the private trigger provides hardware control for the associated
+LED classdev, set the `hw_control_trigger` string to the trigger's name. Since
+both the LED classdev and the private trigger are in the same LED driver, it's
+not necessary for them to coordinate via `hw_control_*` callbacks.
+
When the LED is in hw control, no software blink is possible and doing so
will effectively disable hw control.
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index ab61e41a00a3..2460fcf0c469 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -96,8 +96,30 @@ static const struct bin_attribute *const led_trigger_bin_attrs[] = {
&bin_attr_trigger,
NULL,
};
+
+static DEVICE_ATTR_RO(trigger_may_offload);
+static struct attribute *led_trigger_attrs[] = {
+ &dev_attr_trigger_may_offload.attr,
+ NULL
+};
+
+static umode_t led_trigger_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int idx)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+ if (attr == &dev_attr_trigger_may_offload.attr)
+ return led_cdev->hw_control_trigger ? attr->mode : 0;
+
+ return attr->mode;
+}
+
static const struct attribute_group led_trigger_group = {
.bin_attrs = led_trigger_bin_attrs,
+ .attrs = led_trigger_attrs,
+ .is_visible = led_trigger_is_visible,
};
#endif
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index 804a04b326c4..c3c41ef40f01 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -42,6 +42,9 @@ static bool __led_trigger_is_hw_controlled(struct led_classdev *led_cdev)
if (!led_cdev->trigger)
return false;
+ if (led_cdev->trigger->offloaded)
+ return led_cdev->trigger->offloaded(led_cdev);
+
return led_cdev->trigger->trigger_type;
}
@@ -341,6 +344,32 @@ void led_trigger_set_default(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_trigger_set_default);
+ssize_t trigger_may_offload_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_trigger *trig;
+ bool hit, offloaded;
+ int len;
+
+ guard(mutex)(&led_cdev->led_access);
+ guard(rwsem_read)(&led_cdev->trigger_lock);
+
+ trig = led_cdev->trigger;
+
+ offloaded = __led_trigger_is_hw_controlled(led_cdev);
+ hit = offloaded || (trig && !strcmp(led_cdev->hw_control_trigger, trig->name));
+
+ /* [offloaded] <active_but_not_offloaded> inactive */
+ len = sysfs_emit(buf, "%s%s%s\n",
+ offloaded ? "[" : (hit ? "<" : ""),
+ led_cdev->hw_control_trigger,
+ offloaded ? "]" : (hit ? ">" : ""));
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(trigger_may_offload_show);
+
/* LED Trigger Interface */
int led_trigger_register(struct led_trigger *trig)
diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
index bee46651e068..b08a289397e4 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -27,6 +27,8 @@ ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count);
+ssize_t trigger_may_offload_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
extern struct rw_semaphore leds_list_lock;
extern struct list_head leds_list;
diff --git a/include/linux/leds.h b/include/linux/leds.h
index d7d3dd905432..cc664da33e94 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -485,6 +485,7 @@ struct led_trigger {
const char *name;
int (*activate)(struct led_classdev *led_cdev);
void (*deactivate)(struct led_classdev *led_cdev);
+ bool (*offloaded)(struct led_classdev *led_cdev);
/* Brightness set by led_trigger_event */
enum led_brightness brightness;
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox