* [PATCH v6 09/11] Documentation: leds: document pattern behavior of Samsung S2M series PMIC RGB LEDs
From: Kaustabh Chakraborty @ 2026-05-15 10:39 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński
Cc: linux-leds, devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-rtc, linux-doc, Kaustabh Chakraborty
In-Reply-To: <20260515-s2mu005-pmic-v6-0-1979106992d4@disroot.org>
Add documentation to describe how hardware patterns (as defined by the
documentation of led-class-multicolor) are parsed and implemented by the
Samsung S2M series PMIC RGB LED driver.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
Documentation/leds/index.rst | 1 +
Documentation/leds/leds-s2m-rgb.rst | 60 +++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+)
diff --git a/Documentation/leds/index.rst b/Documentation/leds/index.rst
index bebf440042787..23fa9ff7aaf4b 100644
--- a/Documentation/leds/index.rst
+++ b/Documentation/leds/index.rst
@@ -28,6 +28,7 @@ LEDs
leds-lp5812
leds-mlxcpld
leds-mt6370-rgb
+ leds-s2m-rgb
leds-sc27xx
leds-st1202
leds-qcom-lpg
diff --git a/Documentation/leds/leds-s2m-rgb.rst b/Documentation/leds/leds-s2m-rgb.rst
new file mode 100644
index 0000000000000..4f89a8c89ea86
--- /dev/null
+++ b/Documentation/leds/leds-s2m-rgb.rst
@@ -0,0 +1,60 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======================================
+Samsung S2M Series PMIC RGB LED Driver
+======================================
+
+Description
+-----------
+
+The RGB LED on the S2M series PMIC hardware features a three-channel LED that
+is grouped together as a single device. Furthermore, it supports 8-bit
+brightness control for each channel. This LED is typically used as a status
+indicator in mobile devices. It also supports various parameters for hardware
+patterns.
+
+The hardware pattern can be programmed using the "pattern" trigger, using the
+hw_pattern attribute.
+
+/sys/class/leds/<led>/repeat
+----------------------------
+
+The hardware supports only indefinitely repeating patterns. The repeat
+attribute must be set to -1 for hardware patterns to function.
+
+/sys/class/leds/<led>/hw_pattern
+--------------------------------
+
+Specify a hardware pattern for the RGB LEDs.
+
+The pattern is a series of brightness levels and durations in milliseconds.
+There should be only one non-zero brightness level. Unlike the results
+described in leds-trigger-pattern, the transitions between on and off states
+are smoothed out by the hardware.
+
+Simple pattern::
+
+ "255 3000 0 1000"
+
+ 255 -+ ''''''-. .-'''''''-.
+ | '. .' '.
+ | \ / \
+ | '. .' '.
+ | '-.......-' '-
+ 0 -+-------+-------+-------+-------+-------+-------+--> time (s)
+ 0 1 2 3 4 5 6
+
+As described in leds-trigger-pattern, it is also possible to use zero-length
+entries to disable the ramping mechanism.
+
+On-Off pattern::
+
+ "255 1000 255 0 0 1000 0 0"
+
+ 255 -+ ------+ +-------+ +-------+
+ | | | | | |
+ | | | | | |
+ | | | | | |
+ | +-------+ +-------+ +-------
+ 0 -+-------+-------+-------+-------+-------+-------+--> time (s)
+ 0 1 2 3 4 5 6
--
2.53.0
^ permalink raw reply related
* [PATCH v6 08/11] leds: rgb: add support for Samsung S2M series PMIC RGB LED device
From: Kaustabh Chakraborty @ 2026-05-15 10:39 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński
Cc: linux-leds, devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-rtc, linux-doc, Kaustabh Chakraborty
In-Reply-To: <20260515-s2mu005-pmic-v6-0-1979106992d4@disroot.org>
Add support for the RGB LEDs found in certain Samsung S2M series PMICs.
The device has three LED channels, controlled as a single device. These
LEDs are typically used as status indicators in mobile phones.
The driver includes initial support for the S2MU005 PMIC RGB LEDs.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
drivers/leds/rgb/Kconfig | 11 ++
drivers/leds/rgb/Makefile | 1 +
drivers/leds/rgb/leds-s2m-rgb.c | 416 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 428 insertions(+)
diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
index 28ef4c487367c..cf57d85e574ec 100644
--- a/drivers/leds/rgb/Kconfig
+++ b/drivers/leds/rgb/Kconfig
@@ -75,6 +75,17 @@ config LEDS_QCOM_LPG
If compiled as a module, the module will be named leds-qcom-lpg.
+config LEDS_S2M_RGB
+ tristate "Samsung S2M series PMICs RGB LED support"
+ depends on LEDS_CLASS
+ depends on MFD_SEC_CORE
+ select REGMAP_IRQ
+ help
+ This option enables support for the S2MU005 RGB LEDs. These devices
+ have three LED channels, with 8-bit brightness control for each
+ channel. The S2MU005 usually found in mobile phones as status
+ indicators.
+
config LEDS_MT6370_RGB
tristate "LED Support for MediaTek MT6370 PMIC"
depends on MFD_MT6370
diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile
index be45991f63f50..98050e1aa4255 100644
--- a/drivers/leds/rgb/Makefile
+++ b/drivers/leds/rgb/Makefile
@@ -6,4 +6,5 @@ obj-$(CONFIG_LEDS_LP5812) += leds-lp5812.o
obj-$(CONFIG_LEDS_NCP5623) += leds-ncp5623.o
obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o
obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o
+obj-$(CONFIG_LEDS_S2M_RGB) += leds-s2m-rgb.o
obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o
diff --git a/drivers/leds/rgb/leds-s2m-rgb.c b/drivers/leds/rgb/leds-s2m-rgb.c
new file mode 100644
index 0000000000000..e7295d0b150da
--- /dev/null
+++ b/drivers/leds/rgb/leds-s2m-rgb.c
@@ -0,0 +1,416 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RGB LED Driver for Samsung S2M series PMICs.
+ *
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd
+ * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org>
+ */
+
+#include <linux/container_of.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/mfd/samsung/core.h>
+#include <linux/mfd/samsung/s2mu005.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+struct s2m_rgb {
+ struct device *dev;
+ struct regmap *regmap;
+ struct led_classdev_mc mc;
+ /*
+ * The mutex object prevents race conditions when evaluation and
+ * application of LED pattern state.
+ */
+ struct mutex lock;
+ /*
+ * State variables representing the current LED pattern, these only to
+ * be accessed when lock is held.
+ */
+ u8 ramp_up;
+ u8 ramp_dn;
+ u8 stay_hi;
+ u8 stay_lo;
+};
+
+static struct led_classdev_mc *to_s2m_mc(struct led_classdev *cdev)
+{
+ return container_of(cdev, struct led_classdev_mc, led_cdev);
+}
+
+static struct s2m_rgb *to_s2m_rgb(struct led_classdev_mc *mc)
+{
+ return container_of(mc, struct s2m_rgb, mc);
+}
+
+static const u32 s2mu005_rgb_lut_ramp[] = {
+ 0, 100, 200, 300, 400, 500, 600, 700,
+ 800, 1000, 1200, 1400, 1600, 1800, 2000, 2200,
+};
+
+static const u32 s2mu005_rgb_lut_stay_hi[] = {
+ 100, 200, 300, 400, 500, 750, 1000, 1250,
+ 1500, 1750, 2000, 2250, 2500, 2750, 3000, 3250,
+};
+
+static const u32 s2mu005_rgb_lut_stay_lo[] = {
+ 0, 500, 1000, 1500, 2000, 2500, 3000, 3500,
+ 4000, 4500, 5000, 6000, 7000, 8000, 10000, 12000,
+};
+
+static int s2mu005_rgb_apply_params(struct s2m_rgb *rgb)
+{
+ struct regmap *regmap = rgb->regmap;
+ unsigned int ramp_val = 0;
+ unsigned int stay_val = 0;
+ int ret;
+
+ ramp_val |= FIELD_PREP(S2MU005_RGB_CH_RAMP_UP, rgb->ramp_up);
+ ramp_val |= FIELD_PREP(S2MU005_RGB_CH_RAMP_DN, rgb->ramp_dn);
+
+ stay_val |= FIELD_PREP(S2MU005_RGB_CH_STAY_HI, rgb->stay_hi);
+ stay_val |= FIELD_PREP(S2MU005_RGB_CH_STAY_LO, rgb->stay_lo);
+
+ ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_RESET);
+ if (ret) {
+ dev_err(rgb->dev, "failed to reset RGB LEDs\n");
+ return ret;
+ }
+
+ for (int i = 0; i < rgb->mc.num_colors; i++) {
+ ret = regmap_write(regmap, S2MU005_REG_RGB_CH_CTRL(i),
+ rgb->mc.subled_info[i].brightness);
+ if (ret) {
+ dev_err(rgb->dev, "failed to set LED brightness\n");
+ return ret;
+ }
+
+ ret = regmap_write(regmap, S2MU005_REG_RGB_CH_RAMP(i), ramp_val);
+ if (ret) {
+ dev_err(rgb->dev, "failed to set ramp timings\n");
+ return ret;
+ }
+
+ ret = regmap_write(regmap, S2MU005_REG_RGB_CH_STAY(i), stay_val);
+ if (ret) {
+ dev_err(rgb->dev, "failed to set stay timings\n");
+ return ret;
+ }
+ }
+
+ ret = regmap_update_bits(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_SLOPE,
+ S2MU005_RGB_SLOPE_SMOOTH);
+ if (ret) {
+ dev_err(rgb->dev, "failed to set ramp slope\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int s2mu005_rgb_reset_params(struct s2m_rgb *rgb)
+{
+ struct regmap *regmap = rgb->regmap;
+ int ret;
+
+ ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_RESET);
+ if (ret) {
+ dev_err(rgb->dev, "failed to reset RGB LEDs\n");
+ return ret;
+ }
+
+ rgb->ramp_up = 0;
+ rgb->ramp_dn = 0;
+ rgb->stay_hi = 0;
+ rgb->stay_lo = 0;
+
+ return 0;
+}
+
+/*
+ * s2m_rgb_lut_get_closest_duration - find closest duration in look-up table
+ * @lut: the look-up table to search for the closest timing
+ * @len: number of elements in the look-up table array
+ * @duration: the timing duration requested
+ *
+ * This function does a binary search on the given array, and finds the closest
+ * value to the requested timing. It is expected that the look-up table to be
+ * provided, is already sorted.
+ *
+ * This function returns a negative error code, or a non-negative index of the
+ * value in the look-up table closest to the one requested.
+ */
+static int s2m_rgb_lut_get_closest_duration(const u32 *lut, const size_t len, const u32 duration)
+{
+ u32 closest_distance = abs(duration - lut[0]);
+ int closest_index = 0;
+ int lo = 0;
+ int hi = len - 1;
+
+ /* Bounds checking */
+ if (duration < lut[0] || duration > lut[len - 1])
+ return -EINVAL;
+
+ while (lo <= hi) {
+ int mid = lo + (hi - lo) / 2;
+
+ /* Narrow down search window as per binary-search algorithm. */
+ if (duration < lut[mid])
+ hi = mid - 1;
+ else
+ lo = mid + 1;
+
+ if (abs(duration - lut[mid]) < closest_distance) {
+ closest_distance = abs(duration - lut[mid]);
+ closest_index = mid;
+ }
+ }
+
+ return closest_index;
+}
+
+static int s2m_rgb_pattern_set(struct led_classdev *cdev, struct led_pattern *pattern, u32 len,
+ int repeat)
+{
+ struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
+ const u32 *lut_ramp_up, *lut_ramp_dn, *lut_stay_hi, *lut_stay_lo;
+ size_t lut_ramp_up_len, lut_ramp_dn_len, lut_stay_hi_len, lut_stay_lo_len;
+ int brightness_peak = 0;
+ u32 time_hi = 0, time_lo = 0;
+ bool ramp_up_en, ramp_dn_en;
+ int ret;
+
+ /*
+ * The typical pattern supported by this device can be represented with
+ * the following graph:
+ *
+ * 255 T ''''''-. .-'''''''-.
+ * | '. .' '.
+ * | \ / \
+ * | '. .' '.
+ * | '-...........-' '-
+ * 0 +----------------------------------------------------> time (s)
+ *
+ * <---- HIGH ----><-- LOW --><-------- HIGH --------->
+ * <-----><-------><---------><-------><-----><------->
+ * stay_hi ramp_dn stay_lo ramp_up stay_hi ramp_dn
+ *
+ * There are two states, named HIGH and LOW. HIGH has a non-zero
+ * brightness level, while LOW is of zero brightness. The pattern
+ * provided should mention only one zero and non-zero brightness level.
+ * The hardware always starts the pattern from the HIGH state, as shown
+ * in the graph.
+ *
+ * The HIGH state can be divided in three somewhat equal timings:
+ * ramp_up, stay_hi, and ramp_dn. The LOW state has only one timing:
+ * stay_lo.
+ */
+
+ /* Only indefinitely looping patterns are supported. */
+ if (repeat != -1)
+ return -EINVAL;
+
+ /* Pattern should consist of at least two tuples. */
+ if (len < 2)
+ return -EINVAL;
+
+ for (int i = 0; i < len; i++) {
+ int brightness = pattern[i].brightness;
+ u32 delta_t = pattern[i].delta_t;
+
+ if (brightness) {
+ /*
+ * The pattern shold define only one non-zero
+ * brightness in the HIGH state. The device doesn't
+ * have any provisions to handle multiple peak
+ * brightness levels.
+ */
+ if (brightness_peak && brightness_peak != brightness)
+ return -EINVAL;
+
+ brightness_peak = brightness;
+ time_hi += delta_t;
+ ramp_dn_en = !!delta_t;
+ } else {
+ time_lo += delta_t;
+ ramp_up_en = !!delta_t;
+ }
+ }
+
+ /* LUTs are specific to device variant. */
+ lut_ramp_up = s2mu005_rgb_lut_ramp;
+ lut_ramp_up_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp);
+ lut_ramp_dn = s2mu005_rgb_lut_ramp;
+ lut_ramp_dn_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp);
+ lut_stay_hi = s2mu005_rgb_lut_stay_hi;
+ lut_stay_hi_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_hi);
+ lut_stay_lo = s2mu005_rgb_lut_stay_lo;
+ lut_stay_lo_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_lo);
+
+ mutex_lock(&rgb->lock);
+
+ /*
+ * The timings ramp_up, stay_hi, and ramp_dn of the HIGH state are
+ * roughly equal. Firstly, calculate and set timings for ramp_up and
+ * ramp_dn (making sure they're exactly equal).
+ */
+ rgb->ramp_up = 0;
+ rgb->ramp_dn = 0;
+
+ if (ramp_up_en) {
+ ret = s2m_rgb_lut_get_closest_duration(lut_ramp_up, lut_ramp_up_len, time_hi / 3);
+ if (ret < 0)
+ goto param_fail;
+ rgb->ramp_up = (u8)ret;
+ }
+
+ if (ramp_dn_en) {
+ ret = s2m_rgb_lut_get_closest_duration(lut_ramp_dn, lut_ramp_dn_len, time_hi / 3);
+ if (ret < 0)
+ goto param_fail;
+ rgb->ramp_dn = (u8)ret;
+ }
+
+ /*
+ * Subtract the allocated ramp timings from time_hi (and also making
+ * sure it doesn't underflow!). The remaining time is allocated to
+ * stay_hi.
+ */
+ time_hi -= min(time_hi, lut_ramp_up[rgb->ramp_up]);
+ time_hi -= min(time_hi, lut_ramp_dn[rgb->ramp_dn]);
+
+ ret = s2m_rgb_lut_get_closest_duration(lut_stay_hi, lut_stay_hi_len, time_hi);
+ if (ret < 0)
+ goto param_fail;
+ rgb->stay_hi = (u8)ret;
+
+ ret = s2m_rgb_lut_get_closest_duration(lut_stay_lo, lut_stay_lo_len, time_lo);
+ if (ret < 0)
+ goto param_fail;
+ rgb->stay_lo = (u8)ret;
+
+ led_mc_calc_color_components(&rgb->mc, brightness_peak);
+ /* Apply params with variant-specific implementation. */
+ ret = s2mu005_rgb_apply_params(rgb);
+ if (ret)
+ goto param_fail;
+
+ mutex_unlock(&rgb->lock);
+
+ return 0;
+
+param_fail:
+ rgb->ramp_up = 0;
+ rgb->ramp_dn = 0;
+ rgb->stay_hi = 0;
+ rgb->stay_lo = 0;
+
+ mutex_unlock(&rgb->lock);
+
+ return ret;
+}
+
+static int s2m_rgb_pattern_clear(struct led_classdev *cdev)
+{
+ struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
+ int ret = 0;
+
+ mutex_lock(&rgb->lock);
+
+ /* Reset params with variant-specific implementation. */
+ ret = s2mu005_rgb_reset_params(rgb);
+
+ mutex_unlock(&rgb->lock);
+
+ return ret;
+}
+
+static int s2m_rgb_brightness_set(struct led_classdev *cdev, enum led_brightness value)
+{
+ struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
+ int ret = 0;
+
+ if (!value)
+ return s2m_rgb_pattern_clear(cdev);
+
+ mutex_lock(&rgb->lock);
+
+ led_mc_calc_color_components(&rgb->mc, value);
+ /* Apply params with variant-specific implementation. */
+ ret = s2mu005_rgb_apply_params(rgb);
+
+ mutex_unlock(&rgb->lock);
+
+ return ret;
+}
+
+static struct mc_subled s2mu005_rgb_subled_info[] = {
+ { .channel = 0, .color_index = LED_COLOR_ID_BLUE },
+ { .channel = 1, .color_index = LED_COLOR_ID_GREEN },
+ { .channel = 2, .color_index = LED_COLOR_ID_RED },
+};
+
+static int s2m_rgb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sec_pmic_dev *pmic_drvdata = dev_get_drvdata(dev->parent);
+ struct s2m_rgb *rgb;
+ struct led_init_data init_data = {};
+ int ret;
+
+ rgb = devm_kzalloc(dev, sizeof(*rgb), GFP_KERNEL);
+ if (!rgb)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, rgb);
+ rgb->dev = dev;
+ rgb->regmap = pmic_drvdata->regmap_pmic;
+
+ /* Configure variant-specific details. */
+ rgb->mc.subled_info = s2mu005_rgb_subled_info;
+ rgb->mc.num_colors = ARRAY_SIZE(s2mu005_rgb_subled_info);
+
+ rgb->mc.led_cdev.max_brightness = 255;
+ rgb->mc.led_cdev.brightness_set_blocking = s2m_rgb_brightness_set;
+ rgb->mc.led_cdev.pattern_set = s2m_rgb_pattern_set;
+ rgb->mc.led_cdev.pattern_clear = s2m_rgb_pattern_clear;
+
+ ret = devm_mutex_init(dev, &rgb->lock);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to create mutex lock\n");
+
+ init_data.fwnode = of_fwnode_handle(dev->of_node);
+ ret = devm_led_classdev_multicolor_register_ext(dev, &rgb->mc, &init_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to create LED device\n");
+
+ return 0;
+}
+
+static const struct platform_device_id s2m_rgb_id_table[] = {
+ { "s2mu005-rgb", S2MU005 },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, s2m_rgb_id_table);
+
+static const struct of_device_id s2m_rgb_of_match_table[] = {
+ { .compatible = "samsung,s2mu005-rgb", .data = (void *)S2MU005 },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, s2m_rgb_of_match_table);
+
+static struct platform_driver s2m_rgb_driver = {
+ .driver = {
+ .name = "s2m-rgb",
+ },
+ .probe = s2m_rgb_probe,
+ .id_table = s2m_rgb_id_table,
+};
+module_platform_driver(s2m_rgb_driver);
+
+MODULE_DESCRIPTION("RGB LED Driver for Samsung S2M Series PMICs");
+MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH v6 07/11] leds: flash: add support for Samsung S2M series PMIC flash LED device
From: Kaustabh Chakraborty @ 2026-05-15 10:39 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński
Cc: linux-leds, devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-rtc, linux-doc, Kaustabh Chakraborty
In-Reply-To: <20260515-s2mu005-pmic-v6-0-1979106992d4@disroot.org>
Add support for flash LEDs found in certain Samsung S2M series PMICs.
The device has two channels for LEDs, typically for the back and front
cameras in mobile devices. Both channels can be independently
controlled, and can be operated in torch or flash modes.
The driver includes initial support for the S2MU005 PMIC flash LEDs.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
drivers/leds/flash/Kconfig | 12 ++
drivers/leds/flash/Makefile | 1 +
drivers/leds/flash/leds-s2m-flash.c | 352 ++++++++++++++++++++++++++++++++++++
3 files changed, 365 insertions(+)
diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
index 5e08102a67841..b6542ecc522dd 100644
--- a/drivers/leds/flash/Kconfig
+++ b/drivers/leds/flash/Kconfig
@@ -114,6 +114,18 @@ config LEDS_RT8515
To compile this driver as a module, choose M here: the module
will be called leds-rt8515.
+config LEDS_S2M_FLASH
+ tristate "Samsung S2M series PMICs flash/torch LED support"
+ depends on LEDS_CLASS
+ depends on MFD_SEC_CORE
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ select REGMAP_IRQ
+ help
+ This option enables support for the flash/torch LEDs found in certain
+ Samsung S2M series PMICs, such as the S2MU005. It has a LED channel
+ dedicated for every physical LED. The LEDs can be controlled in flash
+ and torch modes.
+
config LEDS_SGM3140
tristate "LED support for the SGM3140"
depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile
index 712fb737a428e..44e6c1b4beb37 100644
--- a/drivers/leds/flash/Makefile
+++ b/drivers/leds/flash/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
obj-$(CONFIG_LEDS_QCOM_FLASH) += leds-qcom-flash.o
obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o
obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o
+obj-$(CONFIG_LEDS_S2M_FLASH) += leds-s2m-flash.o
obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o
obj-$(CONFIG_LEDS_SY7802) += leds-sy7802.o
obj-$(CONFIG_LEDS_TPS6131X) += leds-tps6131x.o
diff --git a/drivers/leds/flash/leds-s2m-flash.c b/drivers/leds/flash/leds-s2m-flash.c
new file mode 100644
index 0000000000000..40cc5f8b8adc9
--- /dev/null
+++ b/drivers/leds/flash/leds-s2m-flash.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Flash and Torch LED Driver for Samsung S2M series PMICs.
+ *
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd
+ * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org>
+ */
+
+#include <linux/container_of.h>
+#include <linux/led-class-flash.h>
+#include <linux/mfd/samsung/core.h>
+#include <linux/mfd/samsung/s2mu005.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define MAX_CHANNELS 2
+
+struct s2m_led {
+ struct regmap *regmap;
+ struct led_classdev_flash fled;
+ struct v4l2_flash *v4l2_flash;
+ /*
+ * The mutex object prevents the concurrent access of flash control
+ * registers by the LED and V4L2 subsystems.
+ */
+ struct mutex lock;
+ unsigned int reg_enable;
+ u8 channel;
+ u8 flash_brightness;
+ u8 flash_timeout;
+};
+
+static struct s2m_led *to_s2m_led(struct led_classdev_flash *fled)
+{
+ return container_of(fled, struct s2m_led, fled);
+}
+
+static struct led_classdev_flash *to_s2m_fled(struct led_classdev *cdev)
+{
+ return container_of(cdev, struct led_classdev_flash, led_cdev);
+}
+
+static int s2m_fled_flash_brightness_set(struct led_classdev_flash *fled, u32 brightness)
+{
+ struct s2m_led *led = to_s2m_led(fled);
+ struct led_flash_setting *setting = &fled->brightness;
+
+ led->flash_brightness = (brightness - setting->min) / setting->step;
+
+ return 0;
+}
+
+static int s2m_fled_flash_timeout_set(struct led_classdev_flash *fled, u32 timeout)
+{
+ struct s2m_led *led = to_s2m_led(fled);
+ struct led_flash_setting *setting = &fled->timeout;
+
+ led->flash_timeout = (timeout - setting->min) / setting->step;
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static int s2m_fled_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable)
+{
+ struct s2m_led *led = to_s2m_led(v4l2_flash->fled_cdev);
+
+ mutex_lock(&led->lock);
+
+ led->fled.ops->strobe_set(&led->fled, enable);
+
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops = {
+ .external_strobe_set = s2m_fled_flash_external_strobe_set,
+};
+#else
+static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops;
+#endif
+
+static void s2m_fled_v4l2_flash_release(void *v4l2_flash)
+{
+ v4l2_flash_release(v4l2_flash);
+}
+
+static int s2mu005_fled_torch_brightness_set(struct led_classdev *cdev, enum led_brightness value)
+{
+ struct s2m_led *led = to_s2m_led(to_s2m_fled(cdev));
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (!value) {
+ ret = regmap_clear_bits(led->regmap, led->reg_enable,
+ S2MU005_FLED_TORCH_EN(led->channel));
+ if (ret)
+ dev_err(cdev->dev, "failed to disable torch LED\n");
+ goto unlock;
+ }
+
+ ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL1(led->channel),
+ S2MU005_FLED_TORCH_IOUT,
+ FIELD_PREP(S2MU005_FLED_TORCH_IOUT, value - 1));
+ if (ret) {
+ dev_err(cdev->dev, "failed to set torch current\n");
+ goto unlock;
+ }
+
+ ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_TORCH_EN(led->channel));
+ if (ret) {
+ dev_err(cdev->dev, "failed to enable torch LED\n");
+ goto unlock;
+ }
+
+unlock:
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int s2mu005_fled_flash_strobe_set(struct led_classdev_flash *fled, bool state)
+{
+ struct s2m_led *led = to_s2m_led(fled);
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ ret = regmap_clear_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel));
+ if (ret) {
+ dev_err(fled->led_cdev.dev, "failed to disable flash LED\n");
+ goto unlock;
+ }
+
+ if (!state)
+ goto unlock;
+
+ ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL0(led->channel),
+ S2MU005_FLED_FLASH_IOUT,
+ FIELD_PREP(S2MU005_FLED_FLASH_IOUT, led->flash_brightness));
+ if (ret) {
+ dev_err(fled->led_cdev.dev, "failed to set flash brightness\n");
+ goto unlock;
+ }
+
+ ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL3(led->channel),
+ S2MU005_FLED_FLASH_TIMEOUT,
+ FIELD_PREP(S2MU005_FLED_FLASH_TIMEOUT, led->flash_timeout));
+ if (ret) {
+ dev_err(fled->led_cdev.dev, "failed to set flash timeout\n");
+ goto unlock;
+ }
+
+ ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel));
+ if (ret) {
+ dev_err(fled->led_cdev.dev, "failed to enable flash LED\n");
+ goto unlock;
+ }
+
+unlock:
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int s2mu005_fled_flash_strobe_get(struct led_classdev_flash *fled, bool *state)
+{
+ struct s2m_led *led = to_s2m_led(fled);
+ u32 val;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ ret = regmap_read(led->regmap, S2MU005_REG_FLED_STATUS, &val);
+ if (ret) {
+ dev_err(fled->led_cdev.dev, "failed to fetch LED status\n");
+ goto unlock;
+ }
+
+ *state = !!(val & S2MU005_FLED_FLASH_STATUS(led->channel));
+
+unlock:
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static const struct led_flash_ops s2mu005_fled_flash_ops = {
+ .flash_brightness_set = s2m_fled_flash_brightness_set,
+ .timeout_set = s2m_fled_flash_timeout_set,
+ .strobe_set = s2mu005_fled_flash_strobe_set,
+ .strobe_get = s2mu005_fled_flash_strobe_get,
+};
+
+static int s2mu005_fled_init(struct s2m_led *led, struct device *dev, struct regmap *regmap,
+ unsigned int nr_channels)
+{
+ unsigned int val;
+ int ret;
+
+ /* Enable the LED channels. */
+ ret = regmap_set_bits(regmap, S2MU005_REG_FLED_CTRL1, S2MU005_FLED_CH_EN);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable LED channels\n");
+
+ ret = regmap_read(regmap, S2MU005_REG_ID, &val);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to read revision\n");
+
+ for (int i = 0; i < nr_channels; i++) {
+ /*
+ * Read the revision register. Revision EVT0 has the register
+ * at CTRL4, while EVT1 and higher have it at CTRL6.
+ */
+ if (FIELD_GET(S2MU005_ID_MASK, val) == 0)
+ led[i].reg_enable = S2MU005_REG_FLED_CTRL4;
+ else
+ led[i].reg_enable = S2MU005_REG_FLED_CTRL6;
+ }
+
+ return 0;
+}
+
+static int s2mu005_fled_init_channel(struct s2m_led *led, struct device *dev,
+ struct fwnode_handle *fwnp)
+{
+ struct led_classdev *cdev = &led->fled.led_cdev;
+ struct led_init_data init_data = {};
+ struct v4l2_flash_config v4l2_cfg = {};
+ int ret;
+
+ cdev->max_brightness = 16;
+ cdev->brightness_set_blocking = s2mu005_fled_torch_brightness_set;
+ cdev->flags |= LED_DEV_CAP_FLASH;
+
+ led->fled.timeout.min = 62000;
+ led->fled.timeout.step = 62000;
+ led->fled.timeout.max = 992000;
+ led->fled.timeout.val = 992000;
+
+ led->fled.brightness.min = 25000;
+ led->fled.brightness.step = 25000;
+ led->fled.brightness.max = 375000; /* 400000 causes flickering */
+ led->fled.brightness.val = 375000;
+
+ s2m_fled_flash_timeout_set(&led->fled, led->fled.timeout.val);
+ s2m_fled_flash_brightness_set(&led->fled, led->fled.brightness.val);
+
+ led->fled.ops = &s2mu005_fled_flash_ops;
+
+ init_data.fwnode = fwnp;
+ ret = devm_led_classdev_flash_register_ext(dev, &led->fled, &init_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to create LED flash device\n");
+
+ v4l2_cfg.intensity.min = led->fled.brightness.min;
+ v4l2_cfg.intensity.step = led->fled.brightness.step;
+ v4l2_cfg.intensity.max = led->fled.brightness.max;
+ v4l2_cfg.intensity.val = led->fled.brightness.val;
+
+ v4l2_cfg.has_external_strobe = true;
+
+ led->v4l2_flash = v4l2_flash_init(dev, fwnp, &led->fled, &s2m_fled_v4l2_flash_ops,
+ &v4l2_cfg);
+ if (IS_ERR(led->v4l2_flash))
+ return dev_err_probe(dev, PTR_ERR(led->v4l2_flash),
+ "failed to create V4L2 flash device\n");
+
+ ret = devm_add_action_or_reset(dev, (void *)s2m_fled_v4l2_flash_release, led->v4l2_flash);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add cleanup action\n");
+
+ return 0;
+}
+
+static int s2m_fled_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sec_pmic_dev *ddata = dev_get_drvdata(dev->parent);
+ struct s2m_led *led;
+ int ret;
+
+ led = devm_kzalloc(dev, sizeof(*led) * MAX_CHANNELS, GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ /* Initialize LED controller with variant-specific implementation. */
+ ret = s2mu005_fled_init(led, dev, ddata->regmap_pmic, MAX_CHANNELS);
+ if (ret)
+ return ret;
+
+ device_for_each_child_node_scoped(dev, child) {
+ u32 reg;
+
+ if (fwnode_property_read_u32(child, "reg", ®))
+ continue;
+
+ if (reg >= MAX_CHANNELS) {
+ dev_warn(dev, "channel %d is non-existent\n", reg);
+ continue;
+ }
+
+ if (led[reg].regmap) {
+ dev_warn(dev, "duplicate node for channel %d\n", reg);
+ continue;
+ }
+
+ led[reg].regmap = ddata->regmap_pmic;
+ led[reg].channel = (u8)reg;
+
+ ret = devm_mutex_init(dev, &led[reg].lock);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to create mutex\n");
+
+ /* Initialize LED channel with variant-specific implementation. */
+ ret = s2mu005_fled_init_channel(led + reg, dev, child);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct platform_device_id s2m_fled_id_table[] = {
+ { "s2mu005-flash", S2MU005 },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, s2m_fled_id_table);
+
+static const struct of_device_id s2m_fled_of_match_table[] = {
+ { .compatible = "samsung,s2mu005-flash", .data = (void *)S2MU005 },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, s2m_fled_of_match_table);
+
+static struct platform_driver s2m_fled_driver = {
+ .driver = {
+ .name = "s2m-flash",
+ },
+ .probe = s2m_fled_probe,
+ .id_table = s2m_fled_id_table,
+};
+module_platform_driver(s2m_fled_driver);
+
+MODULE_DESCRIPTION("Flash/Torch LED Driver for Samsung S2M Series PMICs");
+MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH v6 06/11] mfd: sec: resolve PMIC revision in S2MU005
From: Kaustabh Chakraborty @ 2026-05-15 10:39 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński
Cc: linux-leds, devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-rtc, linux-doc, Kaustabh Chakraborty
In-Reply-To: <20260515-s2mu005-pmic-v6-0-1979106992d4@disroot.org>
In devices other than S2MPG1X, the revision can be retrieved from the
first register of the PMIC regmap. In S2MU005 however, the location is
in offset 0x73. Introduce a switch-case block to allow selecting the
REG_ID register.
S2MU005 also has a field mask for the revision. Apply it using
FIELD_GET() and get the extracted value.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
drivers/mfd/sec-common.c | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/drivers/mfd/sec-common.c b/drivers/mfd/sec-common.c
index afc9e691bb67c..fe92bc4a3dd26 100644
--- a/drivers/mfd/sec-common.c
+++ b/drivers/mfd/sec-common.c
@@ -16,6 +16,7 @@
#include <linux/mfd/samsung/irq.h>
#include <linux/mfd/samsung/s2mps11.h>
#include <linux/mfd/samsung/s2mps13.h>
+#include <linux/mfd/samsung/s2mu005.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm.h>
@@ -119,20 +120,25 @@ static const struct mfd_cell s2mu005_devs[] = {
static void sec_pmic_dump_rev(struct sec_pmic_dev *sec_pmic)
{
- unsigned int val;
+ unsigned int reg, mask, val;
- /* For s2mpg1x, the revision is in a different regmap */
switch (sec_pmic->device_type) {
case S2MPG10:
case S2MPG11:
+ /* For s2mpg1x, the revision is in a different regmap */
return;
- default:
+ case S2MU005:
+ reg = S2MU005_REG_ID;
+ mask = S2MU005_ID_MASK;
break;
+ default:
+ /* For other device types, REG_ID is always the first register. */
+ reg = S2MPS11_REG_ID;
+ mask = ~0;
}
- /* For each device type, the REG_ID is always the first register */
- if (!regmap_read(sec_pmic->regmap_pmic, S2MPS11_REG_ID, &val))
- dev_dbg(sec_pmic->dev, "Revision: 0x%x\n", val);
+ if (!regmap_read(sec_pmic->regmap_pmic, reg, &val))
+ dev_dbg(sec_pmic->dev, "Revision: 0x%x\n", field_get(mask, val));
}
static void sec_pmic_configure(struct sec_pmic_dev *sec_pmic)
--
2.53.0
^ permalink raw reply related
* [PATCH v6 05/11] mfd: sec: set DMA coherent mask
From: Kaustabh Chakraborty @ 2026-05-15 10:39 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński
Cc: linux-leds, devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-rtc, linux-doc, Kaustabh Chakraborty
In-Reply-To: <20260515-s2mu005-pmic-v6-0-1979106992d4@disroot.org>
Kernel logs are filled with "DMA mask not set" messages for every
sub-device. The device does not use DMA for communication, so these
messages are useless. Disable the coherent DMA mask for the PMIC device,
which is also propagated to sub-devices.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
drivers/mfd/sec-common.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/mfd/sec-common.c b/drivers/mfd/sec-common.c
index ad1c7de83a620..afc9e691bb67c 100644
--- a/drivers/mfd/sec-common.c
+++ b/drivers/mfd/sec-common.c
@@ -215,6 +215,9 @@ int sec_pmic_probe(struct device *dev, int device_type, unsigned int irq,
if (IS_ERR(irq_data))
return PTR_ERR(irq_data);
+ dev->coherent_dma_mask = 0;
+ dev->dma_mask = &dev->coherent_dma_mask;
+
pm_runtime_set_active(sec_pmic->dev);
switch (sec_pmic->device_type) {
--
2.53.0
^ permalink raw reply related
* [PATCH v6 04/11] mfd: sec: add support for S2MU005 PMIC
From: Kaustabh Chakraborty @ 2026-05-15 10:39 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński
Cc: linux-leds, devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-rtc, linux-doc, Kaustabh Chakraborty
In-Reply-To: <20260515-s2mu005-pmic-v6-0-1979106992d4@disroot.org>
Samsung's S2MU005 PMIC includes subdevices for a charger, an MUIC (Micro
USB Interface Controller), and flash and RGB LED controllers.
S2MU005's interrupt registers can be properly divided into three regmap
IRQ chips, one each for the charger, flash LEDs, and the MUIC.
Add initial support for S2MU005 in the PMIC driver, along with it's three
interrupt chips.
Co-developed-by: Łukasz Lebiedziński <kernel@lvkasz.us>
Signed-off-by: Łukasz Lebiedziński <kernel@lvkasz.us>
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
drivers/mfd/sec-common.c | 16 ++
drivers/mfd/sec-i2c.c | 29 ++++
drivers/mfd/sec-irq.c | 74 ++++++++
include/linux/mfd/samsung/core.h | 1 +
include/linux/mfd/samsung/irq.h | 66 +++++++
include/linux/mfd/samsung/s2mu005.h | 332 ++++++++++++++++++++++++++++++++++++
6 files changed, 518 insertions(+)
diff --git a/drivers/mfd/sec-common.c b/drivers/mfd/sec-common.c
index bd8b5f9686892..ad1c7de83a620 100644
--- a/drivers/mfd/sec-common.c
+++ b/drivers/mfd/sec-common.c
@@ -105,6 +105,18 @@ static const struct mfd_cell s2mpu05_devs[] = {
MFD_CELL_RES("s2mps15-rtc", s2mpu05_rtc_resources),
};
+static const struct resource s2mu005_muic_resources[] = {
+ DEFINE_RES_IRQ_NAMED(S2MU005_IRQ_MUIC_ATTACH, "attach"),
+ DEFINE_RES_IRQ_NAMED(S2MU005_IRQ_MUIC_DETACH, "detach"),
+};
+
+static const struct mfd_cell s2mu005_devs[] = {
+ MFD_CELL_NAME("s2mu005-charger"),
+ MFD_CELL_OF("s2mu005-flash", NULL, NULL, 0, 0, "samsung,s2mu005-flash"),
+ MFD_CELL_OF("s2mu005-muic", s2mu005_muic_resources, NULL, 0, 0, "samsung,s2mu005-muic"),
+ MFD_CELL_OF("s2mu005-rgb", NULL, NULL, 0, 0, "samsung,s2mu005-rgb"),
+};
+
static void sec_pmic_dump_rev(struct sec_pmic_dev *sec_pmic)
{
unsigned int val;
@@ -250,6 +262,10 @@ int sec_pmic_probe(struct device *dev, int device_type, unsigned int irq,
sec_devs = s2mpu05_devs;
num_sec_devs = ARRAY_SIZE(s2mpu05_devs);
break;
+ case S2MU005:
+ sec_devs = s2mu005_devs;
+ num_sec_devs = ARRAY_SIZE(s2mu005_devs);
+ break;
default:
return dev_err_probe(sec_pmic->dev, -EINVAL,
"Unsupported device type %d\n",
diff --git a/drivers/mfd/sec-i2c.c b/drivers/mfd/sec-i2c.c
index 3132b849b4bc4..d8609886fcc80 100644
--- a/drivers/mfd/sec-i2c.c
+++ b/drivers/mfd/sec-i2c.c
@@ -17,6 +17,7 @@
#include <linux/mfd/samsung/s2mps14.h>
#include <linux/mfd/samsung/s2mps15.h>
#include <linux/mfd/samsung/s2mpu02.h>
+#include <linux/mfd/samsung/s2mu005.h>
#include <linux/mfd/samsung/s5m8767.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
@@ -66,6 +67,19 @@ static bool s2mpu02_volatile(struct device *dev, unsigned int reg)
}
}
+static bool s2mu005_volatile(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case S2MU005_REG_CHGR_INT1M:
+ case S2MU005_REG_FLED_INT1M:
+ case S2MU005_REG_MUIC_INT1M:
+ case S2MU005_REG_MUIC_INT2M:
+ return false;
+ default:
+ return true;
+ }
+}
+
static const struct regmap_config s2dos05_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
@@ -130,6 +144,15 @@ static const struct regmap_config s2mpu05_regmap_config = {
.val_bits = 8,
};
+static const struct regmap_config s2mu005_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = S2MU005_REG_MUIC_LDOADC_H,
+ .volatile_reg = s2mu005_volatile,
+ .cache_type = REGCACHE_FLAT_S,
+};
+
static const struct regmap_config s5m8767_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
@@ -203,6 +226,11 @@ static const struct sec_pmic_i2c_platform_data s2mpu05_data = {
.device_type = S2MPU05,
};
+static const struct sec_pmic_i2c_platform_data s2mu005_data = {
+ .regmap_cfg = &s2mu005_regmap_config,
+ .device_type = S2MU005,
+};
+
static const struct sec_pmic_i2c_platform_data s5m8767_data = {
.regmap_cfg = &s5m8767_regmap_config,
.device_type = S5M8767X,
@@ -217,6 +245,7 @@ static const struct of_device_id sec_pmic_i2c_of_match[] = {
{ .compatible = "samsung,s2mps15-pmic", .data = &s2mps15_data, },
{ .compatible = "samsung,s2mpu02-pmic", .data = &s2mpu02_data, },
{ .compatible = "samsung,s2mpu05-pmic", .data = &s2mpu05_data, },
+ { .compatible = "samsung,s2mu005-pmic", .data = &s2mu005_data, },
{ .compatible = "samsung,s5m8767-pmic", .data = &s5m8767_data, },
{ },
};
diff --git a/drivers/mfd/sec-irq.c b/drivers/mfd/sec-irq.c
index 133188391f7c2..91a2922463fb6 100644
--- a/drivers/mfd/sec-irq.c
+++ b/drivers/mfd/sec-irq.c
@@ -16,6 +16,7 @@
#include <linux/mfd/samsung/s2mps14.h>
#include <linux/mfd/samsung/s2mpu02.h>
#include <linux/mfd/samsung/s2mpu05.h>
+#include <linux/mfd/samsung/s2mu005.h>
#include <linux/mfd/samsung/s5m8767.h>
#include <linux/regmap.h>
#include "sec-core.h"
@@ -223,6 +224,65 @@ static const struct regmap_irq s2mpu05_irqs[] = {
REGMAP_IRQ_REG(S2MPU05_IRQ_TSD, 2, S2MPU05_IRQ_TSD_MASK),
};
+static const struct regmap_irq s2mu005_irqs[] = {
+ REGMAP_IRQ_REG(S2MU005_IRQ_CHGR_DETBAT, 0, S2MU005_IRQ_CHGR_DETBAT_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_CHGR_BAT, 0, S2MU005_IRQ_CHGR_BAT_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_CHGR_IVR, 0, S2MU005_IRQ_CHGR_IVR_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_CHGR_EVENT, 0, S2MU005_IRQ_CHGR_EVENT_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_CHGR_CHG, 0, S2MU005_IRQ_CHGR_CHG_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_CHGR_VMID, 0, S2MU005_IRQ_CHGR_VMID_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_CHGR_WCIN, 0, S2MU005_IRQ_CHGR_WCIN_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_CHGR_VBUS, 0, S2MU005_IRQ_CHGR_VBUS_MASK),
+
+ REGMAP_IRQ_REG(S2MU005_IRQ_FLED_LBPROT, 1, S2MU005_IRQ_FLED_LBPROT_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_FLED_OPENCH2, 1, S2MU005_IRQ_FLED_OPENCH2_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_FLED_OPENCH1, 1, S2MU005_IRQ_FLED_OPENCH1_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_FLED_SHORTCH2, 1, S2MU005_IRQ_FLED_SHORTCH2_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_FLED_SHORTCH1, 1, S2MU005_IRQ_FLED_SHORTCH1_MASK),
+
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_ATTACH, 2, S2MU005_IRQ_MUIC_ATTACH_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_DETACH, 2, S2MU005_IRQ_MUIC_DETACH_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_KP, 2, S2MU005_IRQ_MUIC_KP_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_LKP, 2, S2MU005_IRQ_MUIC_LKP_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_LKR, 2, S2MU005_IRQ_MUIC_LKR_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_RIDCHG, 2, S2MU005_IRQ_MUIC_RIDCHG_MASK),
+
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_VBUSON, 3, S2MU005_IRQ_MUIC_VBUSON_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_RSVD, 3, S2MU005_IRQ_MUIC_RSVD_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_ADC, 3, S2MU005_IRQ_MUIC_ADC_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_STUCK, 3, S2MU005_IRQ_MUIC_STUCK_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_STUCKRCV, 3, S2MU005_IRQ_MUIC_STUCKRCV_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_MHDL, 3, S2MU005_IRQ_MUIC_MHDL_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_AVCHG, 3, S2MU005_IRQ_MUIC_AVCHG_MASK),
+ REGMAP_IRQ_REG(S2MU005_IRQ_MUIC_VBUSOFF, 3, S2MU005_IRQ_MUIC_VBUSOFF_MASK),
+};
+
+static unsigned int s2mu005_irq_get_reg(struct regmap_irq_chip_data *data,
+ unsigned int base, int index)
+{
+ const unsigned int irqf_regs[] = {
+ S2MU005_REG_CHGR_INT1,
+ S2MU005_REG_FLED_INT1,
+ S2MU005_REG_MUIC_INT1,
+ S2MU005_REG_MUIC_INT2,
+ };
+ const unsigned int mask_regs[] = {
+ S2MU005_REG_CHGR_INT1M,
+ S2MU005_REG_FLED_INT1M,
+ S2MU005_REG_MUIC_INT1M,
+ S2MU005_REG_MUIC_INT2M,
+ };
+
+ switch (base) {
+ case S2MU005_REG_CHGR_INT1:
+ return irqf_regs[index];
+ case S2MU005_REG_CHGR_INT1M:
+ return mask_regs[index];
+ }
+
+ return base;
+}
+
static const struct regmap_irq s5m8767_irqs[] = {
REGMAP_IRQ_REG(S5M8767_IRQ_PWRR, 0, S5M8767_IRQ_PWRR_MASK),
REGMAP_IRQ_REG(S5M8767_IRQ_PWRF, 0, S5M8767_IRQ_PWRF_MASK),
@@ -337,6 +397,17 @@ static const struct regmap_irq_chip s2mpu05_irq_chip = {
.ack_base = S2MPU05_REG_INT1,
};
+static const struct regmap_irq_chip s2mu005_irq_chip = {
+ .name = "s2mu005",
+ .irqs = s2mu005_irqs,
+ .num_irqs = ARRAY_SIZE(s2mu005_irqs),
+ .num_regs = 4,
+ .status_base = S2MU005_REG_CHGR_INT1,
+ .mask_base = S2MU005_REG_CHGR_INT1M,
+ .ack_base = S2MU005_REG_CHGR_INT1,
+ .get_irq_reg = s2mu005_irq_get_reg,
+};
+
static const struct regmap_irq_chip s5m8767_irq_chip = {
.name = "s5m8767",
.irqs = s5m8767_irqs,
@@ -442,6 +513,9 @@ struct regmap_irq_chip_data *sec_irq_init(struct sec_pmic_dev *sec_pmic)
case S2MPU05:
sec_irq_chip = &s2mpu05_irq_chip;
break;
+ case S2MU005:
+ sec_irq_chip = &s2mu005_irq_chip;
+ break;
default:
return dev_err_ptr_probe(sec_pmic->dev, -EINVAL, "Unsupported device type %d\n",
sec_pmic->device_type);
diff --git a/include/linux/mfd/samsung/core.h b/include/linux/mfd/samsung/core.h
index 4480c631110a6..6191f409de945 100644
--- a/include/linux/mfd/samsung/core.h
+++ b/include/linux/mfd/samsung/core.h
@@ -47,6 +47,7 @@ enum sec_device_type {
S2MPS15X,
S2MPU02,
S2MPU05,
+ S2MU005,
};
/**
diff --git a/include/linux/mfd/samsung/irq.h b/include/linux/mfd/samsung/irq.h
index 6eab95de6fa83..19d0f0e12944f 100644
--- a/include/linux/mfd/samsung/irq.h
+++ b/include/linux/mfd/samsung/irq.h
@@ -408,6 +408,72 @@ enum s2mpu05_irq {
#define S2MPU05_IRQ_INT140C_MASK BIT(1)
#define S2MPU05_IRQ_TSD_MASK BIT(2)
+enum s2mu005_irq {
+ S2MU005_IRQ_CHGR_DETBAT,
+ S2MU005_IRQ_CHGR_BAT,
+ S2MU005_IRQ_CHGR_IVR,
+ S2MU005_IRQ_CHGR_EVENT,
+ S2MU005_IRQ_CHGR_CHG,
+ S2MU005_IRQ_CHGR_VMID,
+ S2MU005_IRQ_CHGR_WCIN,
+ S2MU005_IRQ_CHGR_VBUS,
+
+ S2MU005_IRQ_FLED_LBPROT,
+ S2MU005_IRQ_FLED_OPENCH2,
+ S2MU005_IRQ_FLED_OPENCH1,
+ S2MU005_IRQ_FLED_SHORTCH2,
+ S2MU005_IRQ_FLED_SHORTCH1,
+
+ S2MU005_IRQ_MUIC_ATTACH,
+ S2MU005_IRQ_MUIC_DETACH,
+ S2MU005_IRQ_MUIC_KP,
+ S2MU005_IRQ_MUIC_LKP,
+ S2MU005_IRQ_MUIC_LKR,
+ S2MU005_IRQ_MUIC_RIDCHG,
+
+ S2MU005_IRQ_MUIC_VBUSON,
+ S2MU005_IRQ_MUIC_RSVD,
+ S2MU005_IRQ_MUIC_ADC,
+ S2MU005_IRQ_MUIC_STUCK,
+ S2MU005_IRQ_MUIC_STUCKRCV,
+ S2MU005_IRQ_MUIC_MHDL,
+ S2MU005_IRQ_MUIC_AVCHG,
+ S2MU005_IRQ_MUIC_VBUSOFF,
+
+ S2MU005_IRQ_NR,
+};
+
+#define S2MU005_IRQ_CHGR_DETBAT_MASK BIT(0)
+#define S2MU005_IRQ_CHGR_BAT_MASK BIT(1)
+#define S2MU005_IRQ_CHGR_IVR_MASK BIT(2)
+#define S2MU005_IRQ_CHGR_EVENT_MASK BIT(3)
+#define S2MU005_IRQ_CHGR_CHG_MASK BIT(4)
+#define S2MU005_IRQ_CHGR_VMID_MASK BIT(5)
+#define S2MU005_IRQ_CHGR_WCIN_MASK BIT(6)
+#define S2MU005_IRQ_CHGR_VBUS_MASK BIT(7)
+
+#define S2MU005_IRQ_FLED_LBPROT_MASK BIT(2)
+#define S2MU005_IRQ_FLED_OPENCH2_MASK BIT(4)
+#define S2MU005_IRQ_FLED_OPENCH1_MASK BIT(5)
+#define S2MU005_IRQ_FLED_SHORTCH2_MASK BIT(6)
+#define S2MU005_IRQ_FLED_SHORTCH1_MASK BIT(7)
+
+#define S2MU005_IRQ_MUIC_ATTACH_MASK BIT(0)
+#define S2MU005_IRQ_MUIC_DETACH_MASK BIT(1)
+#define S2MU005_IRQ_MUIC_KP_MASK BIT(2)
+#define S2MU005_IRQ_MUIC_LKP_MASK BIT(3)
+#define S2MU005_IRQ_MUIC_LKR_MASK BIT(4)
+#define S2MU005_IRQ_MUIC_RIDCHG_MASK BIT(5)
+
+#define S2MU005_IRQ_MUIC_VBUSON_MASK BIT(0)
+#define S2MU005_IRQ_MUIC_RSVD_MASK BIT(1)
+#define S2MU005_IRQ_MUIC_ADC_MASK BIT(2)
+#define S2MU005_IRQ_MUIC_STUCK_MASK BIT(3)
+#define S2MU005_IRQ_MUIC_STUCKRCV_MASK BIT(4)
+#define S2MU005_IRQ_MUIC_MHDL_MASK BIT(5)
+#define S2MU005_IRQ_MUIC_AVCHG_MASK BIT(6)
+#define S2MU005_IRQ_MUIC_VBUSOFF_MASK BIT(7)
+
enum s5m8767_irq {
S5M8767_IRQ_PWRR,
S5M8767_IRQ_PWRF,
diff --git a/include/linux/mfd/samsung/s2mu005.h b/include/linux/mfd/samsung/s2mu005.h
new file mode 100644
index 0000000000000..46e7759545af2
--- /dev/null
+++ b/include/linux/mfd/samsung/s2mu005.h
@@ -0,0 +1,332 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd
+ * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org>
+ * Copyright (c) 2026 Łukasz Lebiedziński <kernel@lvkasz.us>
+ */
+
+#ifndef __LINUX_MFD_S2MU005_H
+#define __LINUX_MFD_S2MU005_H
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+
+/* S2MU005 registers */
+enum s2mu005_reg {
+ S2MU005_REG_CHGR_INT1,
+ S2MU005_REG_CHGR_INT1M,
+
+ S2MU005_REG_FLED_INT1,
+ S2MU005_REG_FLED_INT1M,
+
+ S2MU005_REG_MUIC_INT1,
+ S2MU005_REG_MUIC_INT2,
+ S2MU005_REG_MUIC_INT1M,
+ S2MU005_REG_MUIC_INT2M,
+
+ S2MU005_REG_CHGR_STATUS0,
+ S2MU005_REG_CHGR_STATUS1,
+ S2MU005_REG_CHGR_STATUS2,
+ S2MU005_REG_CHGR_STATUS3,
+ S2MU005_REG_CHGR_STATUS4,
+ S2MU005_REG_CHGR_STATUS5,
+ S2MU005_REG_CHGR_CTRL0,
+ S2MU005_REG_CHGR_CTRL1,
+ S2MU005_REG_CHGR_CTRL2,
+ S2MU005_REG_CHGR_CTRL3,
+ S2MU005_REG_CHGR_CTRL4,
+ S2MU005_REG_CHGR_CTRL5,
+ S2MU005_REG_CHGR_CTRL6,
+ S2MU005_REG_CHGR_CTRL7,
+ S2MU005_REG_CHGR_CTRL8,
+ S2MU005_REG_CHGR_CTRL9,
+ S2MU005_REG_CHGR_CTRL10,
+ S2MU005_REG_CHGR_CTRL11,
+ S2MU005_REG_CHGR_CTRL12,
+ S2MU005_REG_CHGR_CTRL13,
+ S2MU005_REG_CHGR_CTRL14,
+ S2MU005_REG_CHGR_CTRL15,
+ S2MU005_REG_CHGR_CTRL16,
+ S2MU005_REG_CHGR_CTRL17,
+ S2MU005_REG_CHGR_CTRL18,
+ S2MU005_REG_CHGR_CTRL19,
+ S2MU005_REG_CHGR_TEST0,
+ S2MU005_REG_CHGR_TEST1,
+ S2MU005_REG_CHGR_TEST2,
+ S2MU005_REG_CHGR_TEST3,
+ S2MU005_REG_CHGR_TEST4,
+ S2MU005_REG_CHGR_TEST5,
+ S2MU005_REG_CHGR_TEST6,
+ S2MU005_REG_CHGR_TEST7,
+ S2MU005_REG_CHGR_TEST8,
+ S2MU005_REG_CHGR_TEST9,
+ S2MU005_REG_CHGR_TEST10,
+
+ S2MU005_REG_FLED_STATUS,
+ S2MU005_REG_FLED_CH0_CTRL0,
+ S2MU005_REG_FLED_CH0_CTRL1,
+ S2MU005_REG_FLED_CH0_CTRL2,
+ S2MU005_REG_FLED_CH0_CTRL3,
+ S2MU005_REG_FLED_CH1_CTRL0,
+ S2MU005_REG_FLED_CH1_CTRL1,
+ S2MU005_REG_FLED_CH1_CTRL2,
+ S2MU005_REG_FLED_CH1_CTRL3,
+ S2MU005_REG_FLED_CTRL0,
+ S2MU005_REG_FLED_CTRL1,
+ S2MU005_REG_FLED_CTRL2,
+ S2MU005_REG_FLED_CTRL3,
+ S2MU005_REG_FLED_CTRL4,
+ S2MU005_REG_FLED_CTRL5,
+ S2MU005_REG_FLED_CTRL6,
+
+ S2MU005_REG_RGB_EN,
+ S2MU005_REG_RGB_CH0_CTRL,
+ S2MU005_REG_RGB_CH1_CTRL,
+ S2MU005_REG_RGB_CH2_CTRL,
+ S2MU005_REG_RGB_CH0_RAMP,
+ S2MU005_REG_RGB_CH0_STAY,
+ S2MU005_REG_RGB_CH1_RAMP,
+ S2MU005_REG_RGB_CH1_STAY,
+ S2MU005_REG_RGB_CH2_RAMP,
+ S2MU005_REG_RGB_CH2_STAY,
+ S2MU005_REG_RGB_TEST0,
+ S2MU005_REG_RGB_CTRL0,
+
+ S2MU005_REG_MUIC_ADC,
+ S2MU005_REG_MUIC_DEV1,
+ S2MU005_REG_MUIC_DEV2,
+ S2MU005_REG_MUIC_DEV3,
+ S2MU005_REG_MUIC_BUTTON1,
+ S2MU005_REG_MUIC_BUTTON2,
+ S2MU005_REG_MUIC_RESET,
+ S2MU005_REG_MUIC_CHGTYPE,
+ S2MU005_REG_MUIC_DEVAPPLE,
+ S2MU005_REG_MUIC_BCDRESCAN,
+ S2MU005_REG_MUIC_TEST1,
+ S2MU005_REG_MUIC_TEST2,
+ S2MU005_REG_MUIC_TEST3,
+
+ S2MU005_REG_ID = 0x73,
+
+ S2MU005_REG_MUIC_CTRL1 = 0xb2,
+ S2MU005_REG_MUIC_TIMERSET1,
+ S2MU005_REG_MUIC_TIMERSET2,
+ S2MU005_REG_MUIC_SWCTRL,
+ S2MU005_REG_MUIC_TIMERSET3,
+ S2MU005_REG_MUIC_CTRL2,
+ S2MU005_REG_MUIC_CTRL3,
+
+ S2MU005_REG_MUIC_LDOADC_L = 0xbf,
+ S2MU005_REG_MUIC_LDOADC_H,
+};
+
+#define S2MU005_REG_FLED_CH_CTRL0(x) (S2MU005_REG_FLED_CH0_CTRL0 + 4 * (x))
+#define S2MU005_REG_FLED_CH_CTRL1(x) (S2MU005_REG_FLED_CH0_CTRL1 + 4 * (x))
+#define S2MU005_REG_FLED_CH_CTRL2(x) (S2MU005_REG_FLED_CH0_CTRL2 + 4 * (x))
+#define S2MU005_REG_FLED_CH_CTRL3(x) (S2MU005_REG_FLED_CH0_CTRL3 + 4 * (x))
+
+#define S2MU005_REG_RGB_CH_CTRL(x) (S2MU005_REG_RGB_CH0_CTRL + 1 * (x))
+#define S2MU005_REG_RGB_CH_RAMP(x) (S2MU005_REG_RGB_CH0_RAMP + 2 * (x))
+#define S2MU005_REG_RGB_CH_STAY(x) (S2MU005_REG_RGB_CH0_STAY + 2 * (x))
+
+/* S2MU005_REG_CHGR_STATUS0 */
+#define S2MU005_CHGR_VBUS BIT(7)
+#define S2MU005_CHGR_WCIN BIT(6)
+#define S2MU005_CHGR_VMID BIT(5)
+#define S2MU005_CHGR_CHG BIT(4)
+#define S2MU005_CHGR_STAT GENMASK(3, 0)
+
+#define S2MU005_CHGR_STAT_DONE 8
+#define S2MU005_CHGR_STAT_TOPOFF 7
+#define S2MU005_CHGR_STAT_DONE_FLAG 6
+#define S2MU005_CHGR_STAT_CV 5
+#define S2MU005_CHGR_STAT_CC 4
+#define S2MU005_CHGR_STAT_COOL_CHG 3
+#define S2MU005_CHGR_STAT_PRE_CHG 2
+
+/* S2MU005_REG_CHGR_STATUS1 */
+#define S2MU005_CHGR_DETBAT BIT(7)
+#define S2MU005_CHGR_VBUS_OVP GENMASK(6, 4)
+
+#define S2MU005_CHGR_VBUS_OVP_OVERVOLT 2
+
+/* S2MU005_REG_CHGR_STATUS2 */
+#define S2MU005_CHGR_BAT GENMASK(6, 4)
+
+#define S2MU005_CHGR_BAT_VOLT_DET 7
+#define S2MU005_CHGR_BAT_FAST_CHG_DET 6
+#define S2MU005_CHGR_BAT_COOL_CHG_DET 5
+#define S2MU005_CHGR_BAT_LOW_CHG 2
+#define S2MU005_CHGR_BAT_SELF_DISCHG 1
+#define S2MU005_CHGR_BAT_OVP_DET 0
+
+/* S2MU005_REG_CHGR_STATUS3 */
+#define S2MU005_CHGR_EVT GENMASK(3, 0)
+
+#define S2MU005_CHGR_EVT_WDT_RST 6
+#define S2MU005_CHGR_EVT_WDT_SUSP 5
+#define S2MU005_CHGR_EVT_VSYS_VUVLO 4
+#define S2MU005_CHGR_EVT_VSYS_VOVP 3
+#define S2MU005_CHGR_EVT_THERM_FOLDBACK 2
+#define S2MU005_CHGR_EVT_THERM_SHUTDOWN 1
+
+/* S2MU005_REG_CHGR_CTRL0 */
+#define S2MU005_CHGR_CHG_EN BIT(4)
+#define S2MU005_CHGR_OP_MODE GENMASK(2, 0)
+
+#define S2MU005_CHGR_OP_MODE_OTG BIT(2)
+#define S2MU005_CHGR_OP_MODE_CHG BIT(1)
+
+/* S2MU005_REG_CHGR_CTRL1 */
+#define S2MU005_CHGR_VIN_DROP GENMASK(6, 4)
+
+/* S2MU005_REG_CHGR_CTRL2 */
+#define S2MU005_CHGR_IN_CURR_LIM GENMASK(5, 0)
+
+/* S2MU005_REG_CHGR_CTRL4 */
+#define S2MU005_CHGR_OTG_OCP_ON BIT(5)
+#define S2MU005_CHGR_OTG_OCP_OFF BIT(4)
+#define S2MU005_CHGR_OTG_OCP GENMASK(3, 2)
+#define S2MU005_CHGR_OTG_OCP_1P5A 0x3
+
+/* S2MU005_REG_CHGR_CTRL5 */
+#define S2MU005_CHGR_VMID_BOOST GENMASK(4, 0)
+#define S2MU005_CHGR_VMID_BOOST_5P1V 0x16
+
+/* S2MU005_REG_CHGR_CTRL6 */
+#define S2MU005_CHGR_COOL_CHG_CURR GENMASK(5, 0)
+
+/* S2MU005_REG_CHGR_CTRL7 */
+#define S2MU005_CHGR_FAST_CHG_CURR GENMASK(5, 0)
+
+/* S2MU005_REG_CHGR_CTRL8 */
+#define S2MU005_CHGR_VF_VBAT GENMASK(6, 1)
+
+/* S2MU005_REG_CHGR_CTRL10 */
+#define S2MU005_CHGR_TOPOFF_CURR(x) (GENMASK(3, 0) << 4 * (x))
+
+/* S2MU005_REG_CHGR_CTRL11 */
+#define S2MU005_CHGR_OSC_BOOST GENMASK(6, 5)
+#define S2MU005_CHGR_OSC_BUCK GENMASK(4, 3)
+#define S2MU005_CHGR_OSC_BOOST_2MHZ 0x3
+
+/* S2MU005_REG_CHGR_CTRL12 */
+#define S2MU005_CHGR_WDT GENMASK(2, 0)
+
+#define S2MU005_CHGR_WDT_ON BIT(2)
+#define S2MU005_CHGR_WDT_OFF BIT(1)
+
+/* S2MU005_REG_CHGR_CTRL15 */
+#define S2MU005_CHGR_OTG_EN GENMASK(3, 2)
+#define S2MU005_CHGR_OTG_EN_ON 0x3
+
+/* S2MU005_REG_FLED_STATUS */
+#define S2MU005_FLED_FLASH_STATUS(x) (BIT(7) >> 2 * (x))
+#define S2MU005_FLED_TORCH_STATUS(x) (BIT(6) >> 2 * (x))
+
+/* S2MU005_REG_FLED_CHx_CTRL0 */
+#define S2MU005_FLED_FLASH_IOUT GENMASK(3, 0)
+
+/* S2MU005_REG_FLED_CHx_CTRL1 */
+#define S2MU005_FLED_TORCH_IOUT GENMASK(3, 0)
+
+/* S2MU005_REG_FLED_CHx_CTRL2 */
+#define S2MU005_FLED_TORCH_TIMEOUT GENMASK(3, 0)
+
+/* S2MU005_REG_FLED_CHx_CTRL3 */
+#define S2MU005_FLED_FLASH_TIMEOUT GENMASK(3, 0)
+
+/* S2MU005_REG_FLED_CTRL1 */
+#define S2MU005_FLED_CH_EN BIT(7)
+
+/*
+ * S2MU005_REG_FLED_CTRL4 - Rev. EVT0
+ * S2MU005_REG_FLED_CTRL6 - Rev. EVT1 and later
+ */
+#define S2MU005_FLED_FLASH_EN(x) (GENMASK(7, 6) >> 4 * (x))
+#define S2MU005_FLED_TORCH_EN(x) (GENMASK(5, 4) >> 4 * (x))
+
+/* S2MU005_REG_RGB_EN */
+#define S2MU005_RGB_RESET BIT(6)
+#define S2MU005_RGB_SLOPE GENMASK(5, 0)
+
+#define S2MU005_RGB_SLOPE_CONST (BIT(4) | BIT(2) | BIT(0))
+#define S2MU005_RGB_SLOPE_SMOOTH (BIT(5) | BIT(3) | BIT(1))
+
+/* S2MU005_REG_RGB_CHx_RAMP */
+#define S2MU005_RGB_CH_RAMP_UP GENMASK(7, 4)
+#define S2MU005_RGB_CH_RAMP_DN GENMASK(3, 0)
+
+/* S2MU005_REG_RGB_CHx_STAY */
+#define S2MU005_RGB_CH_STAY_HI GENMASK(7, 4)
+#define S2MU005_RGB_CH_STAY_LO GENMASK(3, 0)
+
+/* S2MU005_REG_MUIC_DEV1 */
+#define S2MU005_MUIC_OTG BIT(7)
+#define S2MU005_MUIC_DCP BIT(6)
+#define S2MU005_MUIC_CDP BIT(5)
+#define S2MU005_MUIC_T1_T2_CHG BIT(4)
+#define S2MU005_MUIC_UART BIT(3)
+#define S2MU005_MUIC_SDP BIT(2)
+#define S2MU005_MUIC_LANHUB BIT(1)
+#define S2MU005_MUIC_AUDIO BIT(0)
+
+/* S2MU005_REG_MUIC_DEV2 */
+#define S2MU005_MUIC_SDP_1P8S BIT(7)
+#define S2MU005_MUIC_AV BIT(6)
+#define S2MU005_MUIC_TTY BIT(5)
+#define S2MU005_MUIC_PPD BIT(4)
+#define S2MU005_MUIC_JIG_UART_OFF BIT(3)
+#define S2MU005_MUIC_JIG_UART_ON BIT(2)
+#define S2MU005_MUIC_JIG_USB_OFF BIT(1)
+#define S2MU005_MUIC_JIG_USB_ON BIT(0)
+
+/* S2MU005_REG_MUIC_DEV3 */
+#define S2MU005_MUIC_U200_CHG BIT(7)
+#define S2MU005_MUIC_VBUS_AV BIT(4)
+#define S2MU005_MUIC_VBUS_R255 BIT(1)
+#define S2MU005_MUIC_MHL BIT(0)
+
+/* S2MU005_REG_MUIC_DEVAPPLE */
+#define S2MU005_MUIC_APPLE_CHG_0P5A BIT(7)
+#define S2MU005_MUIC_APPLE_CHG_1P0A BIT(6)
+#define S2MU005_MUIC_APPLE_CHG_2P0A BIT(5)
+#define S2MU005_MUIC_APPLE_CHG_2P4A BIT(4)
+#define S2MU005_MUIC_SDP_DCD_OUT BIT(3)
+#define S2MU005_MUIC_RID_WAKEUP BIT(2)
+#define S2MU005_MUIC_VBUS_WAKEUP BIT(1)
+#define S2MU005_MUIC_BCV1P2_OR_OPEN BIT(0)
+
+/* S2MU005_REG_ID */
+#define S2MU005_ID_MASK GENMASK(3, 0)
+
+/* S2MU005_REG_MUIC_SWCTRL */
+#define S2MU005_MUIC_DM_DP GENMASK(7, 2)
+#define S2MU005_MUIC_JIG BIT(0)
+
+#define S2MU005_MUIC_DM_DP_UART 0x12
+#define S2MU005_MUIC_DM_DP_USB 0x09
+
+/* S2MU005_REG_MUIC_CTRL1 */
+#define S2MU005_MUIC_OPEN BIT(4)
+#define S2MU005_MUIC_RAW_DATA BIT(3)
+#define S2MU005_MUIC_MAN_SW BIT(2)
+#define S2MU005_MUIC_WAIT BIT(1)
+#define S2MU005_MUIC_IRQ BIT(0)
+
+/* S2MU005_REG_MUIC_CTRL3 */
+#define S2MU005_MUIC_ONESHOT_ADC BIT(2)
+
+/* S2MU005_REG_MUIC_LDOADC_L and S2MU005_REG_MUIC_LDOADC_H */
+#define S2MU005_MUIC_VSET GENMASK(4, 0)
+
+#define S2MU005_MUIC_VSET_3P0V 0x1f
+#define S2MU005_MUIC_VSET_2P6V 0x0e
+#define S2MU005_MUIC_VSET_2P4V 0x0c
+#define S2MU005_MUIC_VSET_2P2V 0x0a
+#define S2MU005_MUIC_VSET_2P0V 0x08
+#define S2MU005_MUIC_VSET_1P5V 0x03
+#define S2MU005_MUIC_VSET_1P4V 0x02
+#define S2MU005_MUIC_VSET_1P2V 0x00
+
+#endif /* __LINUX_MFD_S2MU005_H */
--
2.53.0
^ permalink raw reply related
* [PATCH v6 03/11] dt-bindings: mfd: add documentation for S2MU005 PMIC
From: Kaustabh Chakraborty @ 2026-05-15 10:38 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński
Cc: linux-leds, devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-rtc, linux-doc, Kaustabh Chakraborty
In-Reply-To: <20260515-s2mu005-pmic-v6-0-1979106992d4@disroot.org>
Samsung's S2MU005 PMIC includes subdevices for a charger, an MUIC (Micro
USB Interface Controller), and flash and RGB LED controllers.
Add the compatible and documentation for the S2MU005 PMIC. Also, add an
example for nodes for supported sub-devices, i.e. MUIC, flash LEDs, and
RGB LEDs. Charger sub-device uses the node of the parent.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
.../bindings/mfd/samsung,s2mu005-pmic.yaml | 120 +++++++++++++++++++++
1 file changed, 120 insertions(+)
diff --git a/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml b/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
new file mode 100644
index 0000000000000..0e6afb7d2017b
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
@@ -0,0 +1,120 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/samsung,s2mu005-pmic.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Samsung S2MU005 Power Management IC
+
+maintainers:
+ - Kaustabh Chakraborty <kauschluss@disroot.org>
+
+description: |
+ The S2MU005 is a companion power management IC which includes subdevices for
+ a charger controller, an MUIC (Micro USB Interface Controller), and flash and
+ RGB LED controllers.
+
+allOf:
+ - $ref: /schemas/power/supply/power-supply.yaml#
+
+properties:
+ compatible:
+ const: samsung,s2mu005-pmic
+
+ flash:
+ $ref: /schemas/leds/samsung,s2mu005-flash.yaml
+ description:
+ Child node describing flash LEDs.
+
+ interrupts:
+ maxItems: 1
+
+ muic:
+ $ref: /schemas/extcon/samsung,s2mu005-muic.yaml#
+ description:
+ Child node describing MUIC device.
+
+ multi-led:
+ type: object
+
+ allOf:
+ - $ref: /schemas/leds/leds-class-multicolor.yaml#
+
+ properties:
+ compatible:
+ const: samsung,s2mu005-rgb
+
+ required:
+ - compatible
+
+ unevaluatedProperties: false
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/leds/common.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pmic@3d {
+ compatible = "samsung,s2mu005-pmic";
+ reg = <0x3d>;
+ interrupt-parent = <&gpa2>;
+ interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
+
+ monitored-battery = <&battery>;
+
+ flash {
+ compatible = "samsung,s2mu005-flash";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led@0 {
+ reg = <0>;
+ color = <LED_COLOR_ID_WHITE>;
+ function = LED_FUNCTION_FLASH;
+ };
+
+ led@1 {
+ reg = <1>;
+ color = <LED_COLOR_ID_WHITE>;
+ function = LED_FUNCTION_FLASH;
+ function-enumerator = <1>;
+ };
+ };
+
+ muic {
+ compatible = "samsung,s2mu005-muic";
+
+ connector {
+ compatible = "usb-b-connector";
+ label = "micro-USB";
+ type = "micro";
+ };
+
+ port {
+ muic_to_usb: endpoint {
+ remote-endpoint = <&usb_to_muic>;
+ };
+ };
+ };
+
+ multi-led {
+ compatible = "samsung,s2mu005-rgb";
+ color = <LED_COLOR_ID_RGB>;
+ function = LED_FUNCTION_INDICATOR;
+ linux,default-trigger = "pattern";
+ };
+ };
+ };
--
2.53.0
^ permalink raw reply related
* [PATCH v6 02/11] dt-bindings: extcon: document Samsung S2M series PMIC extcon device
From: Kaustabh Chakraborty @ 2026-05-15 10:38 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński
Cc: linux-leds, devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-rtc, linux-doc, Kaustabh Chakraborty
In-Reply-To: <20260515-s2mu005-pmic-v6-0-1979106992d4@disroot.org>
Certain Samsung S2M series PMICs have a MUIC device which reports
various cable states by measuring the ID-GND resistance with an internal
ADC. Document the devicetree schema for this device.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
.../bindings/extcon/samsung,s2mu005-muic.yaml | 40 ++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/Documentation/devicetree/bindings/extcon/samsung,s2mu005-muic.yaml b/Documentation/devicetree/bindings/extcon/samsung,s2mu005-muic.yaml
new file mode 100644
index 0000000000000..0a320d5e2a352
--- /dev/null
+++ b/Documentation/devicetree/bindings/extcon/samsung,s2mu005-muic.yaml
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/extcon/samsung,s2mu005-muic.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MUIC Device for Samsung S2M series PMICs
+
+maintainers:
+ - Kaustabh Chakraborty <kauschluss@disroot.org>
+
+description: |
+ The Samsung S2M series PMIC MUIC device is a USB port accessory
+ detector. It reports multiple states depending on the ID-GND
+ resistance measured by an internal ADC.
+
+ This is a part of device tree bindings for S2M and S5M family of Power
+ Management IC (PMIC).
+
+ See also Documentation/devicetree/bindings/mfd/samsung,s2mps11.yaml for
+ additional information and example.
+
+properties:
+ compatible:
+ enum:
+ - samsung,s2mu005-muic
+
+ connector:
+ $ref: /schemas/connector/usb-connector.yaml#
+
+ port:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: Port connecting to the USB controller or PHY.
+
+required:
+ - compatible
+ - connector
+ - port
+
+additionalProperties: false
--
2.53.0
^ permalink raw reply related
* [PATCH v6 01/11] dt-bindings: leds: document Samsung S2M series PMIC flash LED device
From: Kaustabh Chakraborty @ 2026-05-15 10:38 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński
Cc: linux-leds, devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-rtc, linux-doc, Kaustabh Chakraborty, Conor Dooley
In-Reply-To: <20260515-s2mu005-pmic-v6-0-1979106992d4@disroot.org>
Certain Samsung S2M series PMICs have a flash LED controller with
two LED channels, and with torch and flash control modes. Document the
devicetree schema for the device.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
.../bindings/leds/samsung,s2mu005-flash.yaml | 52 ++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/Documentation/devicetree/bindings/leds/samsung,s2mu005-flash.yaml b/Documentation/devicetree/bindings/leds/samsung,s2mu005-flash.yaml
new file mode 100644
index 0000000000000..36051ab20509f
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/samsung,s2mu005-flash.yaml
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/samsung,s2mu005-flash.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Flash and Torch LED Controller for Samsung S2M series PMICs
+
+maintainers:
+ - Kaustabh Chakraborty <kauschluss@disroot.org>
+
+description: |
+ The Samsung S2M series PMIC flash LED has two led channels (typically
+ as back and front camera flashes), with support for both torch and
+ flash modes.
+
+ This is a part of device tree bindings for S2M and S5M family of Power
+ Management IC (PMIC).
+
+ See also Documentation/devicetree/bindings/mfd/samsung,s2mps11.yaml for
+ additional information and example.
+
+properties:
+ compatible:
+ enum:
+ - samsung,s2mu005-flash
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+patternProperties:
+ "^led@[0-1]$":
+ type: object
+ $ref: common.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ enum: [0, 1]
+
+ required:
+ - reg
+
+required:
+ - compatible
+ - "#address-cells"
+ - "#size-cells"
+
+additionalProperties: false
--
2.53.0
^ permalink raw reply related
* [PATCH v6 00/11] Support for Samsung S2MU005 PMIC and its sub-devices
From: Kaustabh Chakraborty @ 2026-05-15 10:38 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński
Cc: linux-leds, devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-rtc, linux-doc, Kaustabh Chakraborty, Conor Dooley
S2MU005 is an MFD chip manufactured by Samsung Electronics. This is
found in various devices manufactured by Samsung and others, including
all Exynos 7870 devices. It is known to have the following features:
1. Two LED channels with adjustable brightness for use as a torch, or a
flash strobe.
2. An RGB LED with 8-bit channels. Usually programmed as a notification
indicator.
3. An MUIC, which works with USB micro-B (and USB-C?). For the micro-B
variant though, it measures the ID-GND resistance using an internal
ADC.
4. A charger device, which reports if charger is online, voltage,
resistance, etc.
This patch series implements a lot of these features. Naturally, this
series touches upon a lot of subsystems. The 'parent' is the MFD driver,
so the subsystems have some form of dependency to the MFD driver, so
they are not separable.
Here are the subsystems corresponding to the patch numbers:
dt-bindings - 01, 02, 03
mfd - 03, 04, 05, 06
led - 01, 07, 08, 09
extcon - 02, 10
power - 11
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
Changes in v6:
- Fix build, UAF, and functional errors with
CONFIG_V4L2_FLASH_LED_CLASS=m (Lee Jones)
- Remove (ret < 0) wherever redundant (Lee Jones)
- Remove extra conditionals for supporting multiple variants (Lee Jones)
- Fix OOB condition in initailizing flash LED channels (Lee Jones)
- declare i inside for, like: for (int i = 0; ...) (Lee Jones)
- Rewrite and simplify closest timing function for clarity (Lee Jones)
- Link to v5: https://lore.kernel.org/r/20260424-s2mu005-pmic-v5-0-fcbc9da5a004@disroot.org
Changes in v5:
- Drop port property from charger dt binding (Krzysztof Kozlowski)
- Create separate dt binding for S2MU005 MFD (Krzysztof Kozlowski)
- Move RGB LED and charger schema to parent schema (Rob Herring)
- Fix error of using invalid revision mask
- Link to v4: https://lore.kernel.org/r/20260414-s2mu005-pmic-v4-0-7fe7480577e6@disroot.org
Changes in v4:
- Use OF graph to connect charger with MUIC in device tree
- Move DMA coherent mask to all MFD PMICs (André Draszik)
- Modify pointer names for flash/RGB drivers (Lee Jones)
- Use 100-char line wrap for flash/RGB drivers (Lee Jones)
- Revamp LED device initialization in flash driver (Lee Jones)
- Add proper USB 2.0 support in charger driver (Łukasz Lebiedziński)
- Link to v3: https://lore.kernel.org/r/20260225-s2mu005-pmic-v3-0-b4afee947603@disroot.org
Changes in v3:
- Remove "extcon" text from dt-bindings documentation (Rob Herring)
- Add connector for MUIC node
- Fix dt binding errors reported by robh's bot
- Fix kernel test robot const errors
- Remove FIELD_PREP() values in register header file (André Draszik)
- Add max_register, volatile_reg, cache_type (André Draszik)
- Redo [v2 07/12] to NOT store the PMIC revision (André Draszik)
- Add a commit to fix DMA coherent mask in I2C PMICs
- Implement various flow changes in flash LED driver (André Draszik)
- Use device_for_each_child_node_scoped() (André Draszik)
- Fix CFI panic in devm_add_action_or_reset()
- Link to v2: https://lore.kernel.org/r/20260126-s2mu005-pmic-v2-0-78f1a75f547a@disroot.org
Changes in v2:
- Drop [v1 06/13], instead use regmap_irq_chip::get_irq_regs()
- Remove references to driver in devicetree commits (Conor Dooley)
- Propagate errors of sec_pmic_store_rev() (André Draszik)
- Fix documentation language errors (Randy Dunlap)
- Link to v1: https://lore.kernel.org/r/20251114-s2mu005-pmic-v1-0-9e3184d3a0c9@disroot.org
To: Lee Jones <lee@kernel.org>
To: Pavel Machek <pavel@kernel.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Kaustabh Chakraborty <kauschluss@disroot.org>
To: MyungJoo Ham <myungjoo.ham@samsung.com>
To: Chanwoo Choi <cw00.choi@samsung.com>
To: Krzysztof Kozlowski <krzk@kernel.org>
To: André Draszik <andre.draszik@linaro.org>
To: Nam Tran <trannamatk@gmail.com>
To: Jonathan Corbet <corbet@lwn.net>
To: Shuah Khan <skhan@linuxfoundation.org>
To: Sebastian Reichel <sre@kernel.org>
Cc: linux-leds@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-samsung-soc@vger.kernel.org
Cc: linux-doc@vger.kernel.org
Cc: linux-pm@vger.kernel.org
---
Kaustabh Chakraborty (11):
dt-bindings: leds: document Samsung S2M series PMIC flash LED device
dt-bindings: extcon: document Samsung S2M series PMIC extcon device
dt-bindings: mfd: add documentation for S2MU005 PMIC
mfd: sec: add support for S2MU005 PMIC
mfd: sec: set DMA coherent mask
mfd: sec: resolve PMIC revision in S2MU005
leds: flash: add support for Samsung S2M series PMIC flash LED device
leds: rgb: add support for Samsung S2M series PMIC RGB LED device
Documentation: leds: document pattern behavior of Samsung S2M series PMIC RGB LEDs
extcon: add support for Samsung S2M series PMIC extcon devices
power: supply: add support for Samsung S2M series PMIC charger device
.../bindings/extcon/samsung,s2mu005-muic.yaml | 40 ++
.../bindings/leds/samsung,s2mu005-flash.yaml | 52 +++
.../bindings/mfd/samsung,s2mu005-pmic.yaml | 120 ++++++
Documentation/leds/index.rst | 1 +
Documentation/leds/leds-s2m-rgb.rst | 60 +++
drivers/extcon/Kconfig | 10 +
drivers/extcon/Makefile | 1 +
drivers/extcon/extcon-s2m.c | 345 +++++++++++++++++
drivers/leds/flash/Kconfig | 12 +
drivers/leds/flash/Makefile | 1 +
drivers/leds/flash/leds-s2m-flash.c | 352 +++++++++++++++++
drivers/leds/rgb/Kconfig | 11 +
drivers/leds/rgb/Makefile | 1 +
drivers/leds/rgb/leds-s2m-rgb.c | 416 +++++++++++++++++++++
drivers/mfd/sec-common.c | 37 +-
drivers/mfd/sec-i2c.c | 29 ++
drivers/mfd/sec-irq.c | 74 ++++
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/s2m-charger.c | 299 +++++++++++++++
include/linux/mfd/samsung/core.h | 1 +
include/linux/mfd/samsung/irq.h | 66 ++++
include/linux/mfd/samsung/s2mu005.h | 332 ++++++++++++++++
23 files changed, 2266 insertions(+), 6 deletions(-)
---
base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83
change-id: 20251112-s2mu005-pmic-0c67fa6bac3c
Best regards,
--
Kaustabh Chakraborty <kauschluss@disroot.org>
^ permalink raw reply
* Re: [PATCH 1/5] io_uring/zcrx: notify user when out of buffers
From: Pavel Begunkov @ 2026-05-15 10:26 UTC (permalink / raw)
To: Clément Léger, io-uring, Jens Axboe
Cc: linux-doc, linux-kernel, linux-kselftest, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Jonathan Corbet, Shuah Khan, Vishwanath Seshagiri,
Vishwanath Seshagiri
In-Reply-To: <20260422112522.3316660-2-cleger@meta.com>
On 4/22/26 12:25, Clément Léger wrote:
> From: Pavel Begunkov <asml.silence@gmail.com>
...> static inline struct page *io_zcrx_iov_page(const struct net_iov *niov)
> {
> struct io_zcrx_area *area = io_zcrx_iov_to_area(niov);
> @@ -531,6 +541,7 @@ static struct io_zcrx_ifq *io_zcrx_ifq_alloc(struct io_ring_ctx *ctx)
>
> ifq->if_rxq = -1;
> spin_lock_init(&ifq->rq.lock);
> + spin_lock_init(&ifq->ctx_lock);
> mutex_init(&ifq->pp_lock);
> refcount_set(&ifq->refs, 1);
> refcount_set(&ifq->user_refs, 1);
> @@ -585,6 +596,11 @@ static void io_zcrx_ifq_free(struct io_zcrx_ifq *ifq)
> if (ifq->dev)
> put_device(ifq->dev);
>
> + scoped_guard(spinlock_bh, &ifq->ctx_lock) {
> + if (ifq->master_ctx)
> + percpu_ref_put(&ifq->master_ctx->refs);
> + }
> +
Something very odd happened here. It's not my patch but rather an edited
squash of two other patches. This particular hunk creates a circular
dependency, i.e. io_uring waits for this reference to be put down before
destroying the zcrx instance that triggers io_zcrx_ifq_free.
--
Pavel Begunkov
^ permalink raw reply
* [PATCH] cpufreq: Documentation: fix sampling_down_factor documentation range
From: Pengjie Zhang @ 2026-05-15 9:49 UTC (permalink / raw)
To: rafael, viresh.kumar, corbet
Cc: skhan, linux-pm, linux-doc, zhanjie9, zhenglifeng1, lihuisong,
yubowen8, linhongye, linuxarm, zhangpengjie2, wangzhi12
The ondemand governor implementation accepts sampling_down_factor values
from 1 to 100000 via MAX_SAMPLING_DOWN_FACTOR, but the documentation in
admin-guide/pm/cpufreq.rst still says the valid range is 1 to 100.
Update the documentation to match the actual code.
Fixes: 2a0e49279850 ("cpufreq: User/admin documentation update and consolidation")
Signed-off-by: Pengjie Zhang <zhangpengjie2@huawei.com>
---
Documentation/admin-guide/pm/cpufreq.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/admin-guide/pm/cpufreq.rst b/Documentation/admin-guide/pm/cpufreq.rst
index dbe6d23a5d67..fdca59c955dc 100644
--- a/Documentation/admin-guide/pm/cpufreq.rst
+++ b/Documentation/admin-guide/pm/cpufreq.rst
@@ -516,7 +516,7 @@ This governor exposes the following tunables:
of those tasks above 0 and set this attribute to 1.
``sampling_down_factor``
- Temporary multiplier, between 1 (default) and 100 inclusive, to apply to
+ Temporary multiplier, between 1 (default) and 100000 inclusive, to apply to
the ``sampling_rate`` value if the CPU load goes above ``up_threshold``.
This causes the next execution of the governor's worker routine (after
--
2.33.0
^ permalink raw reply related
* Re: [PATCH v2 3/3] drivers: add deprecated remarks to strlcat()
From: Geert Uytterhoeven @ 2026-05-15 9:41 UTC (permalink / raw)
To: David Laight
Cc: Andy Shevchenko, Manuel Ebner, Kees Cook, Jonathan Corbet,
Shuah Khan, Andy Whitcroft, Joe Perches, Dwaipayan Ray,
Lukas Bulwahn, Randy Dunlap, Jani Nikula, Heiko Carstens,
open list:DOCUMENTATION PROCESS, open list:DOCUMENTATION,
open list
In-Reply-To: <20260515103128.323da7e4@pumpkin>
Hi David,
On Fri, 15 May 2026 at 11:31, David Laight <david.laight.linux@gmail.com> wrote:
> On Fri, 15 May 2026 09:32:06 +0200
> Geert Uytterhoeven <geert@linux-m68k.org> wrote:
> > On Fri, 15 May 2026 at 09:31, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> > > On Fri, May 15, 2026 at 10:23 AM Geert Uytterhoeven
> > > <geert@linux-m68k.org> wrote:
> > > > On Thu, 14 May 2026 at 18:32, Manuel Ebner <manuelebner@mailbox.org> wrote:
> > > > > add kernel-doc comment to strlcat() function definitions
> > >
> > > ...
> > >
> > > > > +/**
> > > > > + * strlcat - Append a string to an existing string
> > > > > + *
> > > > > + * @dest: pointer to %NUL-terminated string to append to
> > > > > + * @src: pointer to %NUL-terminated string to append from
> > > > > + * @count: Maximum bytes available in @dest
> > > > > + *
> > > >
> > > > Missing "Returns ...".
> > >
> > > Documentation says "Return:" as
> > > - the section (note important colon)
> > > - the singular (however plural is undocumented and supported)
> >
> > Trailing "s" is not always plural in English ;-)
>
> It is for 'Return' :-)
In case "returns" is a shorthand for "return values", it indeed is ;-)
I meant a conjugation of the verb "to return".
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH v2 3/3] drivers: add deprecated remarks to strlcat()
From: David Laight @ 2026-05-15 9:31 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: Andy Shevchenko, Manuel Ebner, Kees Cook, Jonathan Corbet,
Shuah Khan, Andy Whitcroft, Joe Perches, Dwaipayan Ray,
Lukas Bulwahn, Randy Dunlap, Jani Nikula, Heiko Carstens,
open list:DOCUMENTATION PROCESS, open list:DOCUMENTATION,
open list
In-Reply-To: <CAMuHMdW12mAV2YROUiFLoKKmtNS=tYNZs7pWN0e4wTKkSMA2xQ@mail.gmail.com>
On Fri, 15 May 2026 09:32:06 +0200
Geert Uytterhoeven <geert@linux-m68k.org> wrote:
> Hi Andy,
>
> On Fri, 15 May 2026 at 09:31, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> > On Fri, May 15, 2026 at 10:23 AM Geert Uytterhoeven
> > <geert@linux-m68k.org> wrote:
> > > On Thu, 14 May 2026 at 18:32, Manuel Ebner <manuelebner@mailbox.org> wrote:
> > > > add kernel-doc comment to strlcat() function definitions
> >
> > ...
> >
> > > > +/**
> > > > + * strlcat - Append a string to an existing string
> > > > + *
> > > > + * @dest: pointer to %NUL-terminated string to append to
> > > > + * @src: pointer to %NUL-terminated string to append from
> > > > + * @count: Maximum bytes available in @dest
> > > > + *
> > >
> > > Missing "Returns ...".
> >
> > Documentation says "Return:" as
> > - the section (note important colon)
> > - the singular (however plural is undocumented and supported)
>
> Trailing "s" is not always plural in English ;-)
It is for 'Return' :-)
Most of the exceptions are words taken directly from Latin,
where something that might be a Latin plural is used (who knows what
the Romans actually used).
It many cases the 's' plural is also valid.
-- David
>
> Gr{oetje,eeting}s,
>
> Geert
>
^ permalink raw reply
* [PATCH v12 4/4] kunit: Add documentation for warning backtrace suppression API
From: Albert Esteve @ 2026-05-15 8:52 UTC (permalink / raw)
To: Arnd Bergmann, Brendan Higgins, David Gow, Rae Moar,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Jonathan Corbet, Shuah Khan, Andrew Morton,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti
Cc: linux-kernel, linux-arch, linux-kselftest, kunit-dev, dri-devel,
workflows, linux-riscv, linux-doc, peterz, Guenter Roeck,
Linux Kernel Functional Testing, Alessandro Carminati,
Albert Esteve, Dan Carpenter, Kees Cook
In-Reply-To: <20260515-kunit_add_support-v12-0-a216dc228be8@redhat.com>
From: Guenter Roeck <linux@roeck-us.net>
Document API functions for suppressing warning backtraces.
Tested-by: Linux Kernel Functional Testing <lkft@linaro.org>
Acked-by: Dan Carpenter <dan.carpenter@linaro.org>
Reviewed-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Alessandro Carminati <acarmina@redhat.com>
Reviewed-by: David Gow <david@davidgow.net>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
Documentation/dev-tools/kunit/usage.rst | 46 ++++++++++++++++++++++++++++++++-
1 file changed, 45 insertions(+), 1 deletion(-)
diff --git a/Documentation/dev-tools/kunit/usage.rst b/Documentation/dev-tools/kunit/usage.rst
index ebd06f5ea4550..1c78dfff94e8a 100644
--- a/Documentation/dev-tools/kunit/usage.rst
+++ b/Documentation/dev-tools/kunit/usage.rst
@@ -157,6 +157,50 @@ Alternatively, one can take full control over the error message by using
if (some_setup_function())
KUNIT_FAIL(test, "Failed to setup thing for testing");
+Suppressing warning backtraces
+------------------------------
+
+Some unit tests trigger warning backtraces either intentionally or as a side
+effect. Such backtraces are normally undesirable since they distract from
+the actual test and may result in the impression that there is a problem.
+
+Backtraces can be suppressed with **task-scoped suppression**: while
+suppression is active on the current task, the backtrace and stack dump from
+``WARN*()``, ``WARN_ON*()``, and related macros on that task are suppressed.
+Two API forms are available.
+
+- Scoped suppression is the simplest form. Wrap the code that triggers
+ warnings in a ``kunit_warning_suppress()`` block:
+
+.. code-block:: c
+
+ static void some_test(struct kunit *test)
+ {
+ kunit_warning_suppress(test) {
+ trigger_backtrace();
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
+ }
+ }
+
+.. note::
+ The warning count must be checked inside the block; the suppression handle
+ is not accessible after the block exits.
+
+- Direct functions return an explicit handle pointer. Use them when the handle
+ needs to be retained or passed across helper functions:
+
+.. code-block:: c
+
+ static void some_test(struct kunit *test)
+ {
+ struct kunit_suppressed_warning *w;
+
+ w = kunit_start_suppress_warning(test);
+ trigger_backtrace();
+ kunit_end_suppress_warning(test, w);
+
+ KUNIT_EXPECT_EQ(test, kunit_suppressed_warning_count(w), 1);
+ }
Test Suites
~~~~~~~~~~~
@@ -1211,4 +1255,4 @@ For example:
dev_managed_string = devm_kstrdup(fake_device, "Hello, World!");
// Everything is cleaned up automatically when the test ends.
- }
\ No newline at end of file
+ }
--
2.53.0
^ permalink raw reply related
* [PATCH v12 3/4] drm: Suppress intentional warning backtraces in scaling unit tests
From: Albert Esteve @ 2026-05-15 8:52 UTC (permalink / raw)
To: Arnd Bergmann, Brendan Higgins, David Gow, Rae Moar,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Jonathan Corbet, Shuah Khan, Andrew Morton,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti
Cc: linux-kernel, linux-arch, linux-kselftest, kunit-dev, dri-devel,
workflows, linux-riscv, linux-doc, peterz, Guenter Roeck,
Linux Kernel Functional Testing, Maíra Canal,
Alessandro Carminati, Albert Esteve, Dan Carpenter, Simona Vetter
In-Reply-To: <20260515-kunit_add_support-v12-0-a216dc228be8@redhat.com>
From: Guenter Roeck <linux@roeck-us.net>
The drm_test_rect_calc_hscale and drm_test_rect_calc_vscale unit tests
intentionally trigger warning backtraces by providing invalid parameters
the tested functions. Suppress the backtraces to avoid clogging the
kernel log and distracting from real problems.
The suppression API also exposes a warning counter, which is used
to assert that the expected warning was actually triggered. On
CONFIG_BUG=n, WARN_ON() is a no-op and the counter stays zero;
the expected count is adjusted accordingly, preserving the return
value check on all configurations.
Tested-by: Linux Kernel Functional Testing <lkft@linaro.org>
Acked-by: Dan Carpenter <dan.carpenter@linaro.org>
Acked-by: Maíra Canal <mcanal@igalia.com>
Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: David Airlie <airlied@gmail.com>
Cc: Daniel Vetter <daniel@ffwll.ch>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Alessandro Carminati <acarmina@redhat.com>
Acked-by: David Gow <david@davidgow.net>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/tests/drm_rect_test.c | 36 +++++++++++++++++++++++++++++------
1 file changed, 30 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/tests/drm_rect_test.c b/drivers/gpu/drm/tests/drm_rect_test.c
index 17e1f34b76101..5aa8ec5fc4d64 100644
--- a/drivers/gpu/drm/tests/drm_rect_test.c
+++ b/drivers/gpu/drm/tests/drm_rect_test.c
@@ -10,6 +10,7 @@
#include <drm/drm_rect.h>
#include <drm/drm_mode.h>
+#include <linux/limits.h>
#include <linux/string_helpers.h>
#include <linux/errno.h>
@@ -407,10 +408,22 @@ KUNIT_ARRAY_PARAM(drm_rect_scale, drm_rect_scale_cases, drm_rect_scale_case_desc
static void drm_test_rect_calc_hscale(struct kunit *test)
{
const struct drm_rect_scale_case *params = test->param_value;
- int scaling_factor;
+ /* With CONFIG_BUG=n, WARN_ON() is a no-op so no warning fires. */
+ int expected_warnings = IS_ENABLED(CONFIG_BUG) ?
+ (params->expected_scaling_factor == -EINVAL) : 0;
+ int scaling_factor = INT_MIN;
- scaling_factor = drm_rect_calc_hscale(¶ms->src, ¶ms->dst,
- params->min_range, params->max_range);
+ /*
+ * drm_rect_calc_hscale() generates a warning backtrace whenever bad
+ * parameters are passed to it. This affects unit tests with -EINVAL
+ * error code in expected_scaling_factor.
+ */
+ kunit_warning_suppress(test) {
+ scaling_factor = drm_rect_calc_hscale(¶ms->src, ¶ms->dst,
+ params->min_range,
+ params->max_range);
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, expected_warnings);
+ }
KUNIT_EXPECT_EQ(test, scaling_factor, params->expected_scaling_factor);
}
@@ -418,10 +431,21 @@ static void drm_test_rect_calc_hscale(struct kunit *test)
static void drm_test_rect_calc_vscale(struct kunit *test)
{
const struct drm_rect_scale_case *params = test->param_value;
- int scaling_factor;
+ /* With CONFIG_BUG=n, WARN_ON() is a no-op so no warning fires. */
+ int expected_warnings = IS_ENABLED(CONFIG_BUG) ?
+ (params->expected_scaling_factor == -EINVAL) : 0;
+ int scaling_factor = INT_MIN;
- scaling_factor = drm_rect_calc_vscale(¶ms->src, ¶ms->dst,
- params->min_range, params->max_range);
+ /*
+ * drm_rect_calc_vscale() generates a warning backtrace whenever bad
+ * parameters are passed to it. This affects unit tests with -EINVAL
+ * error code in expected_scaling_factor.
+ */
+ kunit_warning_suppress(test) {
+ scaling_factor = drm_rect_calc_vscale(¶ms->src, ¶ms->dst,
+ params->min_range, params->max_range);
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, expected_warnings);
+ }
KUNIT_EXPECT_EQ(test, scaling_factor, params->expected_scaling_factor);
}
--
2.53.0
^ permalink raw reply related
* [PATCH v12 2/4] kunit: Add backtrace suppression self-tests
From: Albert Esteve @ 2026-05-15 8:52 UTC (permalink / raw)
To: Arnd Bergmann, Brendan Higgins, David Gow, Rae Moar,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Jonathan Corbet, Shuah Khan, Andrew Morton,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti
Cc: linux-kernel, linux-arch, linux-kselftest, kunit-dev, dri-devel,
workflows, linux-riscv, linux-doc, peterz, Guenter Roeck,
Linux Kernel Functional Testing, Alessandro Carminati,
Albert Esteve, Dan Carpenter, Kees Cook
In-Reply-To: <20260515-kunit_add_support-v12-0-a216dc228be8@redhat.com>
From: Guenter Roeck <linux@roeck-us.net>
Add unit tests to verify that warning backtrace suppression works.
Tests cover both API forms:
- Scoped: kunit_warning_suppress() with in-block count verification
and post-block inactivity check.
- Direct functions: kunit_start/end_suppress_warning() with
sequential independent suppression blocks and per-block counts.
Furthermore, tests verify incremental warning counting, that
kunit_has_active_suppress_warning() transitions correctly around
suppression boundaries, and that suppression active in the test
kthread does not leak to a separate kthread.
If backtrace suppression does _not_ work, the unit tests will likely
trigger unsuppressed backtraces, which should actually help to get
the affected architectures / platforms fixed.
Tested-by: Linux Kernel Functional Testing <lkft@linaro.org>
Acked-by: Dan Carpenter <dan.carpenter@linaro.org>
Reviewed-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Alessandro Carminati <acarmina@redhat.com>
Reviewed-by: David Gow <david@davidgow.net>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
lib/kunit/Makefile | 1 +
lib/kunit/backtrace-suppression-test.c | 192 +++++++++++++++++++++++++++++++++
2 files changed, 193 insertions(+)
diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
index 4592f9d0aa8dd..2e8a6b71a2ab0 100644
--- a/lib/kunit/Makefile
+++ b/lib/kunit/Makefile
@@ -22,6 +22,7 @@ obj-$(if $(CONFIG_KUNIT),y) += hooks.o
obj-$(CONFIG_KUNIT_TEST) += kunit-test.o
obj-$(CONFIG_KUNIT_TEST) += platform-test.o
+obj-$(CONFIG_KUNIT_TEST) += backtrace-suppression-test.o
# string-stream-test compiles built-in only.
ifeq ($(CONFIG_KUNIT_TEST),y)
diff --git a/lib/kunit/backtrace-suppression-test.c b/lib/kunit/backtrace-suppression-test.c
new file mode 100644
index 0000000000000..59a038b2739f5
--- /dev/null
+++ b/lib/kunit/backtrace-suppression-test.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit test for suppressing warning tracebacks.
+ *
+ * Copyright (C) 2024, Guenter Roeck
+ * Author: Guenter Roeck <linux@roeck-us.net>
+ */
+
+#include <kunit/test.h>
+#include <linux/bug.h>
+#include <linux/completion.h>
+#include <linux/kthread.h>
+
+static void backtrace_suppression_test_warn_direct(struct kunit *test)
+{
+ if (!IS_ENABLED(CONFIG_BUG))
+ kunit_skip(test, "requires CONFIG_BUG");
+
+ kunit_warning_suppress(test) {
+ WARN(1, "This backtrace should be suppressed");
+ /*
+ * Count must be checked inside the scope; the handle
+ * is not accessible after the block exits.
+ */
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
+ }
+ KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());
+}
+
+static noinline void trigger_backtrace_warn(void)
+{
+ WARN(1, "This backtrace should be suppressed");
+}
+
+static void backtrace_suppression_test_warn_indirect(struct kunit *test)
+{
+ if (!IS_ENABLED(CONFIG_BUG))
+ kunit_skip(test, "requires CONFIG_BUG");
+
+ kunit_warning_suppress(test) {
+ trigger_backtrace_warn();
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
+ }
+}
+
+static void backtrace_suppression_test_warn_multi(struct kunit *test)
+{
+ if (!IS_ENABLED(CONFIG_BUG))
+ kunit_skip(test, "requires CONFIG_BUG");
+
+ kunit_warning_suppress(test) {
+ WARN(1, "This backtrace should be suppressed");
+ trigger_backtrace_warn();
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 2);
+ }
+}
+
+static void backtrace_suppression_test_warn_on_direct(struct kunit *test)
+{
+ if (!IS_ENABLED(CONFIG_BUG))
+ kunit_skip(test, "requires CONFIG_BUG");
+
+ kunit_warning_suppress(test) {
+ WARN_ON(1);
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
+ }
+}
+
+static noinline void trigger_backtrace_warn_on(void)
+{
+ WARN_ON(1);
+}
+
+static void backtrace_suppression_test_warn_on_indirect(struct kunit *test)
+{
+ if (!IS_ENABLED(CONFIG_BUG))
+ kunit_skip(test, "requires CONFIG_BUG");
+
+ kunit_warning_suppress(test) {
+ trigger_backtrace_warn_on();
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
+ }
+}
+
+static void backtrace_suppression_test_count(struct kunit *test)
+{
+ if (!IS_ENABLED(CONFIG_BUG))
+ kunit_skip(test, "requires CONFIG_BUG");
+
+ kunit_warning_suppress(test) {
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 0);
+
+ WARN(1, "suppressed");
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
+
+ WARN(1, "suppressed again");
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 2);
+ }
+}
+
+static void backtrace_suppression_test_active_state(struct kunit *test)
+{
+ KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());
+
+ kunit_warning_suppress(test) {
+ KUNIT_EXPECT_TRUE(test, kunit_has_active_suppress_warning());
+ }
+
+ KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());
+
+ kunit_warning_suppress(test) {
+ KUNIT_EXPECT_TRUE(test, kunit_has_active_suppress_warning());
+ }
+
+ KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());
+}
+
+static void backtrace_suppression_test_multi_scope(struct kunit *test)
+{
+ struct kunit_suppressed_warning *sw1, *sw2;
+
+ if (!IS_ENABLED(CONFIG_BUG))
+ kunit_skip(test, "requires CONFIG_BUG");
+
+ sw1 = kunit_start_suppress_warning(test);
+ trigger_backtrace_warn_on();
+ WARN(1, "suppressed by sw1");
+ kunit_end_suppress_warning(test, sw1);
+
+ sw2 = kunit_start_suppress_warning(test);
+ WARN(1, "suppressed by sw2");
+ kunit_end_suppress_warning(test, sw2);
+
+ KUNIT_EXPECT_EQ(test, kunit_suppressed_warning_count(sw1), 2);
+ KUNIT_EXPECT_EQ(test, kunit_suppressed_warning_count(sw2), 1);
+}
+
+struct cross_kthread_data {
+ bool was_active;
+ struct completion done;
+};
+
+static int cross_kthread_fn(void *data)
+{
+ struct cross_kthread_data *d = data;
+
+ d->was_active = kunit_has_active_suppress_warning();
+ complete(&d->done);
+ while (!kthread_should_stop())
+ schedule();
+ return 0;
+}
+
+static void backtrace_suppression_test_cross_kthread(struct kunit *test)
+{
+ struct cross_kthread_data data;
+ struct task_struct *task;
+
+ data.was_active = false;
+ init_completion(&data.done);
+
+ kunit_warning_suppress(test) {
+ task = kthread_run(cross_kthread_fn, &data, "kunit-cross-test");
+ KUNIT_ASSERT_FALSE(test, IS_ERR(task));
+ wait_for_completion(&data.done);
+ kthread_stop(task);
+ }
+
+ KUNIT_EXPECT_FALSE(test, data.was_active);
+}
+
+static struct kunit_case backtrace_suppression_test_cases[] = {
+ KUNIT_CASE(backtrace_suppression_test_warn_direct),
+ KUNIT_CASE(backtrace_suppression_test_warn_indirect),
+ KUNIT_CASE(backtrace_suppression_test_warn_multi),
+ KUNIT_CASE(backtrace_suppression_test_warn_on_direct),
+ KUNIT_CASE(backtrace_suppression_test_warn_on_indirect),
+ KUNIT_CASE(backtrace_suppression_test_count),
+ KUNIT_CASE(backtrace_suppression_test_active_state),
+ KUNIT_CASE(backtrace_suppression_test_multi_scope),
+ KUNIT_CASE(backtrace_suppression_test_cross_kthread),
+ {}
+};
+
+static struct kunit_suite backtrace_suppression_test_suite = {
+ .name = "backtrace-suppression-test",
+ .test_cases = backtrace_suppression_test_cases,
+};
+kunit_test_suites(&backtrace_suppression_test_suite);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KUnit test to verify warning backtrace suppression");
--
2.53.0
^ permalink raw reply related
* [PATCH v12 1/4] bug/kunit: Core support for suppressing warning backtraces
From: Albert Esteve @ 2026-05-15 8:52 UTC (permalink / raw)
To: Arnd Bergmann, Brendan Higgins, David Gow, Rae Moar,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Jonathan Corbet, Shuah Khan, Andrew Morton,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti
Cc: linux-kernel, linux-arch, linux-kselftest, kunit-dev, dri-devel,
workflows, linux-riscv, linux-doc, peterz, Alessandro Carminati,
Guenter Roeck, Kees Cook, Albert Esteve
In-Reply-To: <20260515-kunit_add_support-v12-0-a216dc228be8@redhat.com>
From: Alessandro Carminati <acarmina@redhat.com>
Some unit tests intentionally trigger warning backtraces by passing bad
parameters to kernel API functions. Such unit tests typically check the
return value from such calls, not the existence of the warning backtrace.
Such intentionally generated warning backtraces are neither desirable
nor useful for a number of reasons:
- They can result in overlooked real problems.
- A warning that suddenly starts to show up in unit tests needs to be
investigated and has to be marked to be ignored, for example by
adjusting filter scripts. Such filters are ad hoc because there is
no real standard format for warnings. On top of that, such filter
scripts would require constant maintenance.
Solve the problem by providing a means to suppress warning backtraces
originating from the current kthread while executing test code. Since
each KUnit test runs in its own kthread, this effectively scopes
suppression to the test that enabled it. Limit changes to generic code
to the absolute minimum.
Implementation details:
Suppression is integrated into the existing KUnit hooks infrastructure
in test-bug.h, reusing the kunit_running static branch for zero
overhead when no tests are running.
Suppression is checked at three points in the warning path:
- In warn_slowpath_fmt(), the check runs before any output, fully
suppressing both message and backtrace. This covers architectures
without __WARN_FLAGS.
- In __warn_printk(), the check suppresses the warning message text.
This covers architectures that define __WARN_FLAGS but not their own
__WARN_printf (arm64, loongarch, parisc, powerpc, riscv, sh), where
the message is printed before the trap enters __report_bug().
- In __report_bug(), the check runs before __warn() is called,
suppressing the backtrace and stack dump.
To avoid double-counting on architectures where both __warn_printk()
and __report_bug() run for the same warning, kunit_is_suppressed_warning()
takes a bool parameter: true to increment the suppression counter
(used in warn_slowpath_fmt and __report_bug), false to check only
(used in __warn_printk).
The suppression state is dynamically allocated via kzalloc() and its
lifetime is managed via kunit_add_action(). On cleanup, the node is
removed with list_del_rcu() and the object is freed asynchronously via
call_rcu(), ensuring that any concurrent RCU reader that obtained a
pointer to the handle before removal can finish safely. Writer-side
access to the global suppression list is serialized with a spinlock;
readers use RCU.
Two API forms are provided:
- kunit_warning_suppress(test) { ... }: scoped, uses __cleanup for
automatic teardown on scope exit, kunit_add_action() as safety net
for abnormal exits (e.g. kthread_exit from failed assertions).
Suppression handle is only accessible inside the block.
- kunit_start/end_suppress_warning(test): direct functions returning
an explicit handle, for retaining the handle within the test,
or for cross-function usage.
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Alessandro Carminati <acarmina@redhat.com>
Reviewed-by: Kees Cook <kees@kernel.org>
Reviewed-by: David Gow <david@davidgow.net>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
include/kunit/test-bug.h | 26 ++++++++++
include/kunit/test.h | 98 +++++++++++++++++++++++++++++++++++
kernel/panic.c | 11 ++++
lib/bug.c | 12 ++++-
lib/kunit/Makefile | 3 +-
lib/kunit/bug.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++
lib/kunit/hooks-impl.h | 2 +
7 files changed, 280 insertions(+), 3 deletions(-)
diff --git a/include/kunit/test-bug.h b/include/kunit/test-bug.h
index 47aa8f21ccce8..99869029fc686 100644
--- a/include/kunit/test-bug.h
+++ b/include/kunit/test-bug.h
@@ -10,6 +10,7 @@
#define _KUNIT_TEST_BUG_H
#include <linux/stddef.h> /* for NULL */
+#include <linux/types.h> /* for bool */
#if IS_ENABLED(CONFIG_KUNIT)
@@ -23,6 +24,7 @@ DECLARE_STATIC_KEY_FALSE(kunit_running);
extern struct kunit_hooks_table {
__printf(3, 4) void (*fail_current_test)(const char*, int, const char*, ...);
void *(*get_static_stub_address)(struct kunit *test, void *real_fn_addr);
+ bool (*is_suppressed_warning)(bool count);
} kunit_hooks;
/**
@@ -60,9 +62,33 @@ static inline struct kunit *kunit_get_current_test(void)
} \
} while (0)
+/**
+ * kunit_is_suppressed_warning() - Check if warnings are being suppressed
+ * by the current KUnit test.
+ * @count: if true, increment the suppression counter on match.
+ *
+ * Returns true if the current task has active warning suppression.
+ * Uses the kunit_running static branch for zero overhead when no tests run.
+ *
+ * A single WARN*() may traverse multiple call sites in the warning path
+ * (e.g., __warn_printk() and __report_bug()). Pass @count = true at the
+ * primary suppression point to count each warning exactly once, and
+ * @count = false at secondary points to suppress output without
+ * inflating the count.
+ */
+static inline bool kunit_is_suppressed_warning(bool count)
+{
+ if (!static_branch_unlikely(&kunit_running))
+ return false;
+
+ return kunit_hooks.is_suppressed_warning &&
+ kunit_hooks.is_suppressed_warning(count);
+}
+
#else
static inline struct kunit *kunit_get_current_test(void) { return NULL; }
+static inline bool kunit_is_suppressed_warning(bool count) { return false; }
#define kunit_fail_current_test(fmt, ...) do {} while (0)
diff --git a/include/kunit/test.h b/include/kunit/test.h
index 9cd1594ab697d..be71612f61655 100644
--- a/include/kunit/test.h
+++ b/include/kunit/test.h
@@ -1795,4 +1795,102 @@ do { \
// include resource.h themselves if they need it.
#include <kunit/resource.h>
+/*
+ * Warning backtrace suppression API.
+ *
+ * Suppresses WARN*() backtraces on the current task while active. Two forms
+ * are provided:
+ *
+ * - Scoped: kunit_warning_suppress(test) { ... }
+ * Suppression is active for the duration of the block. On normal exit,
+ * the for-loop increment deactivates suppression. On early exit (break,
+ * return, goto), the __cleanup attribute fires. On kthread_exit() (e.g.,
+ * a failed KUnit assertion), kunit_add_action() cleans up at test
+ * teardown. The suppression handle is only accessible inside the block,
+ * so warning counts must be checked before the block exits.
+ *
+ * - Direct: kunit_start_suppress_warning() / kunit_end_suppress_warning()
+ * The underlying functions, returning an explicit handle pointer. Use
+ * when the handle needs to be retained (e.g., for post-suppression
+ * count checks) or passed across helper functions.
+ */
+struct kunit_suppressed_warning;
+
+struct kunit_suppressed_warning *
+kunit_start_suppress_warning(struct kunit *test);
+void kunit_end_suppress_warning(struct kunit *test,
+ struct kunit_suppressed_warning *w);
+int kunit_suppressed_warning_count(struct kunit_suppressed_warning *w);
+void __kunit_suppress_auto_cleanup(struct kunit_suppressed_warning **wp);
+bool kunit_has_active_suppress_warning(void);
+
+/**
+ * kunit_warning_suppress() - Suppress WARN*() backtraces for the duration
+ * of a block.
+ * @test: The test context object.
+ *
+ * Scoped form of the suppression API. Suppression starts when the block is
+ * entered and ends automatically when the block exits through any path. See
+ * the section comment above for the cleanup guarantees on each exit path.
+ * Fails the test if suppression is already active; nesting is not supported.
+ *
+ * The warning count can be checked inside the block via
+ * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(). The handle is not accessible
+ * after the block exits.
+ *
+ * Example::
+ *
+ * kunit_warning_suppress(test) {
+ * trigger_warning();
+ * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
+ * }
+ */
+#define kunit_warning_suppress(test) \
+ for (struct kunit_suppressed_warning *__kunit_suppress \
+ __cleanup(__kunit_suppress_auto_cleanup) = \
+ kunit_start_suppress_warning(test); \
+ __kunit_suppress; \
+ kunit_end_suppress_warning(test, __kunit_suppress), \
+ __kunit_suppress = NULL)
+
+/**
+ * KUNIT_SUPPRESSED_WARNING_COUNT() - Returns the suppressed warning count.
+ *
+ * Returns the number of WARN*() calls suppressed since the current
+ * suppression block started, or 0 if the handle is NULL. Usable inside a
+ * kunit_warning_suppress() block.
+ */
+#define KUNIT_SUPPRESSED_WARNING_COUNT() \
+ kunit_suppressed_warning_count(__kunit_suppress)
+
+/**
+ * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT() - Sets an expectation that the
+ * suppressed warning count equals
+ * @expected.
+ * @test: The test context object.
+ * @expected: an expression that evaluates to the expected warning count.
+ *
+ * Sets an expectation that the number of suppressed WARN*() calls equals
+ * @expected. This is semantically equivalent to
+ * KUNIT_EXPECT_EQ(@test, KUNIT_SUPPRESSED_WARNING_COUNT(), @expected).
+ * See KUNIT_EXPECT_EQ() for more information.
+ */
+#define KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, expected) \
+ KUNIT_EXPECT_EQ(test, KUNIT_SUPPRESSED_WARNING_COUNT(), expected)
+
+/**
+ * KUNIT_ASSERT_SUPPRESSED_WARNING_COUNT() - Sets an assertion that the
+ * suppressed warning count equals
+ * @expected.
+ * @test: The test context object.
+ * @expected: an expression that evaluates to the expected warning count.
+ *
+ * Sets an assertion that the number of suppressed WARN*() calls equals
+ * @expected. This is the same as KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(),
+ * except it causes an assertion failure (see KUNIT_ASSERT_TRUE()) when the
+ * assertion is not met.
+ */
+#define KUNIT_ASSERT_SUPPRESSED_WARNING_COUNT(test, expected) \
+ KUNIT_ASSERT_EQ(test, KUNIT_SUPPRESSED_WARNING_COUNT(), expected)
+
#endif /* _KUNIT_TEST_H */
diff --git a/kernel/panic.c b/kernel/panic.c
index 20feada5319d4..213725b612aa1 100644
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -39,6 +39,7 @@
#include <linux/sys_info.h>
#include <trace/events/error_report.h>
#include <asm/sections.h>
+#include <kunit/test-bug.h>
#define PANIC_TIMER_STEP 100
#define PANIC_BLINK_SPD 18
@@ -1124,6 +1125,11 @@ void warn_slowpath_fmt(const char *file, int line, unsigned taint,
bool rcu = warn_rcu_enter();
struct warn_args args;
+ if (kunit_is_suppressed_warning(true)) {
+ warn_rcu_exit(rcu);
+ return;
+ }
+
pr_warn(CUT_HERE);
if (!fmt) {
@@ -1146,6 +1152,11 @@ void __warn_printk(const char *fmt, ...)
bool rcu = warn_rcu_enter();
va_list args;
+ if (kunit_is_suppressed_warning(false)) {
+ warn_rcu_exit(rcu);
+ return;
+ }
+
pr_warn(CUT_HERE);
va_start(args, fmt);
diff --git a/lib/bug.c b/lib/bug.c
index 224f4cfa4aa31..874cb4ae4d047 100644
--- a/lib/bug.c
+++ b/lib/bug.c
@@ -48,6 +48,7 @@
#include <linux/rculist.h>
#include <linux/ftrace.h>
#include <linux/context_tracking.h>
+#include <kunit/test-bug.h>
extern struct bug_entry __start___bug_table[], __stop___bug_table[];
@@ -209,8 +210,6 @@ static enum bug_trap_type __report_bug(struct bug_entry *bug, unsigned long buga
return BUG_TRAP_TYPE_NONE;
}
- disable_trace_on_warning();
-
bug_get_file_line(bug, &file, &line);
fmt = bug_get_format(bug);
@@ -220,6 +219,15 @@ static enum bug_trap_type __report_bug(struct bug_entry *bug, unsigned long buga
no_cut = bug->flags & BUGFLAG_NO_CUT_HERE;
has_args = bug->flags & BUGFLAG_ARGS;
+ /*
+ * Before the once logic so suppressed warnings do not consume
+ * the single-fire budget of WARN_ON_ONCE().
+ */
+ if (warning && kunit_is_suppressed_warning(true))
+ return BUG_TRAP_TYPE_WARN;
+
+ disable_trace_on_warning();
+
if (warning && once) {
if (done)
return BUG_TRAP_TYPE_WARN;
diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
index 656f1fa35abcc..4592f9d0aa8dd 100644
--- a/lib/kunit/Makefile
+++ b/lib/kunit/Makefile
@@ -10,7 +10,8 @@ kunit-objs += test.o \
executor.o \
attributes.o \
device.o \
- platform.o
+ platform.o \
+ bug.o
ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
kunit-objs += debugfs.o
diff --git a/lib/kunit/bug.c b/lib/kunit/bug.c
new file mode 100644
index 0000000000000..8a5725aebcaa4
--- /dev/null
+++ b/lib/kunit/bug.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit helpers for backtrace suppression
+ *
+ * Copyright (C) 2025 Alessandro Carminati <acarmina@redhat.com>
+ * Copyright (C) 2024 Guenter Roeck <linux@roeck-us.net>
+ */
+
+#include <kunit/resource.h>
+#include <linux/export.h>
+#include <linux/rculist.h>
+#include <linux/sched.h>
+#include <linux/sched/task.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include "hooks-impl.h"
+
+struct kunit_suppressed_warning {
+ struct list_head node;
+ struct rcu_head rcu;
+ struct task_struct *task;
+ struct kunit *test;
+ atomic_t counter;
+};
+
+static LIST_HEAD(suppressed_warnings);
+static DEFINE_SPINLOCK(suppressed_warnings_lock);
+
+static void kunit_suppress_warning_free(struct rcu_head *rcu)
+{
+ struct kunit_suppressed_warning *w =
+ container_of(rcu, struct kunit_suppressed_warning, rcu);
+
+ put_task_struct(w->task);
+ kfree(w);
+}
+
+static void kunit_suppress_warning_remove(struct kunit_suppressed_warning *w)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&suppressed_warnings_lock, flags);
+ list_del_rcu(&w->node);
+ spin_unlock_irqrestore(&suppressed_warnings_lock, flags);
+ call_rcu(&w->rcu, kunit_suppress_warning_free);
+}
+
+KUNIT_DEFINE_ACTION_WRAPPER(kunit_suppress_warning_cleanup,
+ kunit_suppress_warning_remove,
+ struct kunit_suppressed_warning *);
+
+bool kunit_has_active_suppress_warning(void)
+{
+ return __kunit_is_suppressed_warning_impl(false);
+}
+EXPORT_SYMBOL_GPL(kunit_has_active_suppress_warning);
+
+struct kunit_suppressed_warning *
+kunit_start_suppress_warning(struct kunit *test)
+{
+ struct kunit_suppressed_warning *w;
+ unsigned long flags;
+ int ret;
+
+ if (kunit_has_active_suppress_warning()) {
+ KUNIT_FAIL(test, "Another suppression block is already active");
+ return NULL;
+ }
+
+ w = kzalloc(sizeof(*w), GFP_KERNEL);
+ if (!w) {
+ KUNIT_FAIL(test, "Failed to allocate suppression handle.");
+ return NULL;
+ }
+
+ w->task = get_task_struct(current);
+ w->test = test;
+
+ spin_lock_irqsave(&suppressed_warnings_lock, flags);
+ list_add_rcu(&w->node, &suppressed_warnings);
+ spin_unlock_irqrestore(&suppressed_warnings_lock, flags);
+
+ ret = kunit_add_action_or_reset(test,
+ kunit_suppress_warning_cleanup, w);
+ if (ret) {
+ KUNIT_FAIL(test, "Failed to add suppression cleanup action.");
+ return NULL;
+ }
+
+ return w;
+}
+EXPORT_SYMBOL_GPL(kunit_start_suppress_warning);
+
+void kunit_end_suppress_warning(struct kunit *test,
+ struct kunit_suppressed_warning *w)
+{
+ if (!w)
+ return;
+ kunit_release_action(test, kunit_suppress_warning_cleanup, w);
+}
+EXPORT_SYMBOL_GPL(kunit_end_suppress_warning);
+
+void __kunit_suppress_auto_cleanup(struct kunit_suppressed_warning **wp)
+{
+ if (*wp)
+ kunit_end_suppress_warning((*wp)->test, *wp);
+}
+EXPORT_SYMBOL_GPL(__kunit_suppress_auto_cleanup);
+
+int kunit_suppressed_warning_count(struct kunit_suppressed_warning *w)
+{
+ return w ? atomic_read(&w->counter) : 0;
+}
+EXPORT_SYMBOL_GPL(kunit_suppressed_warning_count);
+
+bool __kunit_is_suppressed_warning_impl(bool count)
+{
+ struct kunit_suppressed_warning *w;
+
+ guard(rcu)();
+ list_for_each_entry_rcu(w, &suppressed_warnings, node) {
+ if (w->task == current) {
+ if (count)
+ atomic_inc(&w->counter);
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/lib/kunit/hooks-impl.h b/lib/kunit/hooks-impl.h
index 4e71b2d0143ba..d8720f2616925 100644
--- a/lib/kunit/hooks-impl.h
+++ b/lib/kunit/hooks-impl.h
@@ -19,6 +19,7 @@ void __printf(3, 4) __kunit_fail_current_test_impl(const char *file,
int line,
const char *fmt, ...);
void *__kunit_get_static_stub_address_impl(struct kunit *test, void *real_fn_addr);
+bool __kunit_is_suppressed_warning_impl(bool count);
/* Code to set all of the function pointers. */
static inline void kunit_install_hooks(void)
@@ -26,6 +27,7 @@ static inline void kunit_install_hooks(void)
/* Install the KUnit hook functions. */
kunit_hooks.fail_current_test = __kunit_fail_current_test_impl;
kunit_hooks.get_static_stub_address = __kunit_get_static_stub_address_impl;
+ kunit_hooks.is_suppressed_warning = __kunit_is_suppressed_warning_impl;
}
#endif /* _KUNIT_HOOKS_IMPL_H */
--
2.53.0
^ permalink raw reply related
* [PATCH v12 0/4] kunit: Add support for suppressing warning backtraces
From: Albert Esteve @ 2026-05-15 8:52 UTC (permalink / raw)
To: Arnd Bergmann, Brendan Higgins, David Gow, Rae Moar,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Jonathan Corbet, Shuah Khan, Andrew Morton,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti
Cc: linux-kernel, linux-arch, linux-kselftest, kunit-dev, dri-devel,
workflows, linux-riscv, linux-doc, peterz, Alessandro Carminati,
Guenter Roeck, Kees Cook, Albert Esteve,
Linux Kernel Functional Testing, Maíra Canal, Dan Carpenter,
Kees Cook, Simona Vetter
Some unit tests intentionally trigger warning backtraces by passing bad
parameters to kernel API functions. Such unit tests typically check the
return value from such calls, not the existence of the warning backtrace.
Such intentionally generated warning backtraces are neither desirable
nor useful for a number of reasons:
- They can result in overlooked real problems.
- A warning that suddenly starts to show up in unit tests needs to be
investigated and has to be marked to be ignored, for example by
adjusting filter scripts. Such filters are ad hoc because there is
no real standard format for warnings. On top of that, such filter
scripts would require constant maintenance.
One option to address the problem would be to add messages such as
"expected warning backtraces start/end here" to the kernel log.
However, that would again require filter scripts, might result in
missing real problematic warning backtraces triggered while the test
is running, and the irrelevant backtrace(s) would still clog the
kernel log.
Solve the problem by providing a means to suppress warning backtraces
originating from the current kthread while executing test code.
Since each KUnit test runs in its own kthread, this effectively scopes
suppression to the test that enabled it, without requiring any
architecture-specific code.
Overview:
Patch#1 Introduces the suppression infrastructure integrated into
KUnit's hook mechanism.
Patch#2 Adds selftests to validate the functionality.
Patch#3 Demonstrates real-world usage in the DRM subsystem.
Patch#4 Documents the new API and usage guidelines.
Design Notes:
Suppression is integrated into the existing KUnit hooks infrastructure,
reusing the kunit_running static branch for zero overhead
when no tests are running. The implementation lives entirely in the
kunit module; only a static-inline wrapper and a function pointer
slot are added to built-in code.
Suppression is checked at three points in the warning path:
- In `warn_slowpath_fmt()` (kernel/panic.c), for architectures without
__WARN_FLAGS. The check runs before any output, fully suppressing
both message and backtrace.
- In `__warn_printk()` (kernel/panic.c), for architectures that define
__WARN_FLAGS but not their own __WARN_printf (arm64, loongarch,
parisc, powerpc, riscv, sh). The check suppresses the warning message
text that is printed before the trap enters __report_bug().
- In `__report_bug()` (lib/bug.c), for architectures that define
__WARN_FLAGS. The check runs before `__warn()` is called, suppressing
the backtrace and stack dump.
To avoid double-counting on architectures where both `__warn_printk()`
and `__report_bug()` run for the same warning, the hook takes a bool
parameter: true to increment the suppression counter, false to suppress
without counting.
The suppression state is dynamically allocated via kunit_kzalloc() and
tied to the KUnit test lifecycle via `kunit_add_action()`, ensuring
automatic cleanup at test exit. Writer-side access to the global
suppression list is serialized with a spinlock; readers use RCU.
Two API forms are provided:
- kunit_warning_suppress(test) { ... }: scoped blocks with automatic
cleanup. The suppression handle is not accessible outside the block,
so warning counts (if needed) must be checked inside. Multiple
suppression blocks are allowed.
- kunit_start/end_suppress_warning(test): direct functions that return
an explicit handle. Use when the handle needs to be retained, or passed
across helpers. Multiple suppression blocks are allowed.
This series is based on the RFC patch and subsequent discussion at
https://patchwork.kernel.org/project/linux-kselftest/patch/02546e59-1afe-4b08-ba81-d94f3b691c9a@moroto.mountain/
and offers a more comprehensive solution of the problem discussed there.
Changes since RFC:
- Introduced CONFIG_KUNIT_SUPPRESS_BACKTRACE
- Minor cleanups and bug fixes
- Added support for all affected architectures
- Added support for counting suppressed warnings
- Added unit tests using those counters
- Added patch to suppress warning backtraces in dev_addr_lists tests
Changes since v1:
- Rebased to v6.9-rc1
- Added Tested-by:, Acked-by:, and Reviewed-by: tags
[I retained those tags since there have been no functional changes]
- Introduced KUNIT_SUPPRESS_BACKTRACE configuration option, enabled by
default.
Changes since v2:
- Rebased to v6.9-rc2
- Added comments to drm warning suppression explaining why it is needed.
- Added patch to move conditional code in arch/sh/include/asm/bug.h
to avoid kerneldoc warning
- Added architecture maintainers to Cc: for architecture specific patches
- No functional changes
Changes since v3:
- Rebased to v6.14-rc6
- Dropped net: "kunit: Suppress lock warning noise at end of dev_addr_lists tests"
since 3db3b62955cd6d73afde05a17d7e8e106695c3b9
- Added __kunit_ and KUNIT_ prefixes.
- Tested on interessed architectures.
Changes since v4:
- Rebased to v6.15-rc7
- Dropped all code in __report_bug()
- Moved all checks in WARN*() macros.
- Dropped all architecture specific code.
- Made __kunit_is_suppressed_warning nice to noinstr functions.
Changes since v5:
- Rebased to v7.0-rc3
- Added RCU protection for the suppressed warnings list.
- Added static key and branching optimization.
- Removed custom `strcmp` implementation and reworked
__kunit_is_suppressed_warning() entrypoint function.
Changes since v6:
- Moved suppression checks from WARN*() macros to warn_slowpath_fmt()
and __report_bug().
- Replaced stack-allocated suppression struct with kunit_kzalloc() heap
allocation tied to the KUnit test lifecycle.
- Changed suppression strategy from function-name matching to task-scoped:
all warnings on the current task are suppressed between START and END,
rather than only warnings originating from a specific named function.
- Simplified macro API: removed KUNIT_DECLARE_SUPPRESSED_WARNING(),
the START macro now takes (test) and handles allocation internally.
- Removed static key and branching optiomization, as by the time it
was executed, callers are already in warn slowpaths.
- Link to v6: https://lore.kernel.org/r/20260317-kunit_add_support-v6-0-dd22aeb3fe5d@redhat.com
Changes since v7:
- Integrated suppression into existing KUnit hooks infrastructure
- Removed CONFIG_KUNIT_SUPPRESS_BACKTRACE
- Added suppression check in __warn_printk()
- Added spinlock for writer-side RCU protection
- Replaced explicit rcu_read_lock/unlock with guard(rcu)()
- Added scoped API (kunit_warning_suppress) using __cleanup attribute
- Updated DRM patch to use scoped API
- Expanded self-tests: incremental counting, cross-kthread isolation
- Rewrote documentation covering all three API forms with examples
- Link to v7: https://lore.kernel.org/r/20260420-kunit_add_support-v7-0-e8bc6e0f70de@redhat.com
Changes since v8:
- Rebased to v7.1-rc2
- Remove KUNIT_START/END_SUPPRESSED_WARNING() macros
- Add KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT checks to drm tests
- Link to v8: https://lore.kernel.org/r/20260504-kunit_add_support-v8-0-3e5957cdd235@redhat.com
Changes since v9:
- Fix silent false-pass when kunit_start_suppress_warning() returns NULL
- Fix RCU lockdep splat for kunit_is_suppressed_warning() calls
- Move disable_trace_on_warning() in __report_bug()
- Make suppress counter atomic
- Mark helper warn functions in selftest as noinline
- Add kunit_skip() for CONFIG_BUG=n in selftests
- Fix potentially uninitialized data.was_active in kthread seltest
- Add kthread_stop() in kthread selftest early exit
- Initialize scaling_factor to INT_MIN in DRM scaling tests
- Add include for bool in test-bug.h to fix CONFIG_KUNIT=n case
- Link to v9: https://lore.kernel.org/r/20260508-kunit_add_support-v9-0-99df7aa880f6@redhat.com
Changes since v10:
- Remove synchronize_rcu() to avoid sleeping in atomic context
- Pin task_struct refcount to prevent ABA false-positive matches
- Loop in suppression selftest to prevent use-after-free on kthread exit
- Skip DRM rect tests on CONFIG_BUG=n
- Link to v10: https://lore.kernel.org/r/20260513-kunit_add_support-v10-0-e379d206c8cd@redhat.com
Changes since v11:
- Use call_rcu() to defer free without blocking
- Remove #ifdef CONFIG_KUNIT guard in lib/bug.c
- Remove stale config checks from selftest
- Replace skip on DRM rect tests with conditional expectation
- Link to v11: https://lore.kernel.org/r/20260514-kunit_add_support-v11-0-b36a530a6d8f@redhat.com
--
2.34.1
---
To: Brendan Higgins <brendan.higgins@linux.dev>
To: David Gow <david@davidgow.net>
To: Rae Moar <raemoar63@gmail.com>
To: Andrew Morton <akpm@linux-foundation.org>
To: Paul Walmsley <pjw@kernel.org>
To: Palmer Dabbelt <palmer@dabbelt.com>
To: Albert Ou <aou@eecs.berkeley.edu>
To: Alexandre Ghiti <alex@ghiti.fr>
To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
To: Maxime Ripard <mripard@kernel.org>
To: Thomas Zimmermann <tzimmermann@suse.de>
To: David Airlie <airlied@gmail.com>
To: Simona Vetter <simona@ffwll.ch>
To: Jonathan Corbet <corbet@lwn.net>
To: Shuah Khan <skhan@linuxfoundation.org>
Cc: linux-kernel@vger.kernel.org
Cc: linux-kselftest@vger.kernel.org
Cc: kunit-dev@googlegroups.com
Cc: linux-riscv@lists.infradead.org
Cc: dri-devel@lists.freedesktop.org
Cc: workflows@vger.kernel.org
Cc: linux-doc@vger.kernel.org
---
Alessandro Carminati (1):
bug/kunit: Core support for suppressing warning backtraces
Guenter Roeck (3):
kunit: Add backtrace suppression self-tests
drm: Suppress intentional warning backtraces in scaling unit tests
kunit: Add documentation for warning backtrace suppression API
Documentation/dev-tools/kunit/usage.rst | 46 +++++++-
drivers/gpu/drm/tests/drm_rect_test.c | 36 +++++-
include/kunit/test-bug.h | 26 +++++
include/kunit/test.h | 98 ++++++++++++++++
kernel/panic.c | 11 ++
lib/bug.c | 12 +-
lib/kunit/Makefile | 4 +-
lib/kunit/backtrace-suppression-test.c | 192 ++++++++++++++++++++++++++++++++
lib/kunit/bug.c | 131 ++++++++++++++++++++++
lib/kunit/hooks-impl.h | 2 +
10 files changed, 548 insertions(+), 10 deletions(-)
---
base-commit: 74fe02ce122a6103f207d29fafc8b3a53de6abaf
change-id: 20260312-kunit_add_support-2f35806b19dd
Best regards,
--
Albert Esteve <aesteve@redhat.com>
^ permalink raw reply
* Re: [PATCH v11 2/4] kunit: Add backtrace suppression self-tests
From: Albert Esteve @ 2026-05-15 8:30 UTC (permalink / raw)
To: Arnd Bergmann, Brendan Higgins, David Gow, Rae Moar,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Jonathan Corbet, Shuah Khan, Andrew Morton,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti
Cc: linux-kernel, linux-arch, linux-kselftest, kunit-dev, dri-devel,
workflows, linux-riscv, linux-doc, peterz, Guenter Roeck,
Linux Kernel Functional Testing, Alessandro Carminati,
Dan Carpenter, Kees Cook
In-Reply-To: <20260514-kunit_add_support-v11-2-b36a530a6d8f@redhat.com>
On Thu, May 14, 2026 at 1:07 PM Albert Esteve <aesteve@redhat.com> wrote:
>
> From: Guenter Roeck <linux@roeck-us.net>
>
> Add unit tests to verify that warning backtrace suppression works.
>
> Tests cover both API forms:
> - Scoped: kunit_warning_suppress() with in-block count verification
> and post-block inactivity check.
> - Direct functions: kunit_start/end_suppress_warning() with
> sequential independent suppression blocks and per-block counts.
>
> Furthermore, tests verify incremental warning counting, that
> kunit_has_active_suppress_warning() transitions correctly around
> suppression boundaries, and that suppression active in the test
> kthread does not leak to a separate kthread.
>
> If backtrace suppression does _not_ work, the unit tests will likely
> trigger unsuppressed backtraces, which should actually help to get
> the affected architectures / platforms fixed.
>
> Tested-by: Linux Kernel Functional Testing <lkft@linaro.org>
> Acked-by: Dan Carpenter <dan.carpenter@linaro.org>
> Reviewed-by: Kees Cook <keescook@chromium.org>
> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> Signed-off-by: Alessandro Carminati <acarmina@redhat.com>
> Reviewed-by: David Gow <david@davidgow.net>
> Signed-off-by: Albert Esteve <aesteve@redhat.com>
> ---
> lib/kunit/Makefile | 1 +
> lib/kunit/backtrace-suppression-test.c | 198 +++++++++++++++++++++++++++++++++
> 2 files changed, 199 insertions(+)
>
> diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
> index 4592f9d0aa8dd..2e8a6b71a2ab0 100644
> --- a/lib/kunit/Makefile
> +++ b/lib/kunit/Makefile
> @@ -22,6 +22,7 @@ obj-$(if $(CONFIG_KUNIT),y) += hooks.o
>
> obj-$(CONFIG_KUNIT_TEST) += kunit-test.o
> obj-$(CONFIG_KUNIT_TEST) += platform-test.o
> +obj-$(CONFIG_KUNIT_TEST) += backtrace-suppression-test.o
>
> # string-stream-test compiles built-in only.
> ifeq ($(CONFIG_KUNIT_TEST),y)
> diff --git a/lib/kunit/backtrace-suppression-test.c b/lib/kunit/backtrace-suppression-test.c
> new file mode 100644
> index 0000000000000..7a2a59c6a780d
> --- /dev/null
> +++ b/lib/kunit/backtrace-suppression-test.c
> @@ -0,0 +1,198 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * KUnit test for suppressing warning tracebacks.
> + *
> + * Copyright (C) 2024, Guenter Roeck
> + * Author: Guenter Roeck <linux@roeck-us.net>
> + */
> +
> +#include <kunit/test.h>
> +#include <linux/bug.h>
> +#include <linux/completion.h>
> +#include <linux/kthread.h>
> +
> +static void backtrace_suppression_test_warn_direct(struct kunit *test)
> +{
> + if (!IS_ENABLED(CONFIG_BUG))
> + kunit_skip(test, "requires CONFIG_BUG");
> +
> + kunit_warning_suppress(test) {
> + WARN(1, "This backtrace should be suppressed");
> + /*
> + * Count must be checked inside the scope; the handle
> + * is not accessible after the block exits.
> + */
> + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
> + }
> + KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());
> +}
> +
> +static noinline void trigger_backtrace_warn(void)
> +{
> + WARN(1, "This backtrace should be suppressed");
> +}
> +
> +static void backtrace_suppression_test_warn_indirect(struct kunit *test)
> +{
> + if (!IS_ENABLED(CONFIG_BUG))
> + kunit_skip(test, "requires CONFIG_BUG");
> +
> + kunit_warning_suppress(test) {
> + trigger_backtrace_warn();
> + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
> + }
> +}
> +
> +static void backtrace_suppression_test_warn_multi(struct kunit *test)
> +{
> + if (!IS_ENABLED(CONFIG_BUG))
> + kunit_skip(test, "requires CONFIG_BUG");
> +
> + kunit_warning_suppress(test) {
> + WARN(1, "This backtrace should be suppressed");
> + trigger_backtrace_warn();
> + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 2);
> + }
> +}
> +
> +static void backtrace_suppression_test_warn_on_direct(struct kunit *test)
> +{
> + if (!IS_ENABLED(CONFIG_BUG))
> + kunit_skip(test, "requires CONFIG_BUG");
> + if (!IS_ENABLED(CONFIG_DEBUG_BUGVERBOSE) && !IS_ENABLED(CONFIG_KALLSYMS))
> + kunit_skip(test, "requires CONFIG_DEBUG_BUGVERBOSE or CONFIG_KALLSYMS");
> +
> + kunit_warning_suppress(test) {
> + WARN_ON(1);
> + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
> + }
> +}
> +
> +static noinline void trigger_backtrace_warn_on(void)
> +{
> + WARN_ON(1);
> +}
> +
> +static void backtrace_suppression_test_warn_on_indirect(struct kunit *test)
> +{
> + if (!IS_ENABLED(CONFIG_BUG))
> + kunit_skip(test, "requires CONFIG_BUG");
> + if (!IS_ENABLED(CONFIG_DEBUG_BUGVERBOSE))
> + kunit_skip(test, "requires CONFIG_DEBUG_BUGVERBOSE");
Sashiko says:
"""
Is there a reason why backtrace_suppression_test_warn_on_direct() falls back
to checking CONFIG_KALLSYMS while
backtrace_suppression_test_warn_on_indirect() does not?
The core warning suppression logic matches on the task, so it seems like
CONFIG_KALLSYMS should be sufficient for both.
Could this cause tests to be unnecessarily skipped on systems with
CONFIG_KALLSYMS enabled but CONFIG_DEBUG_BUGVERBOSE disabled?
"""
This is interesting. I am not sure why they were different; it was
probably an oversight. But after looking at it, this is probably a
leftover from when suppression occurred at the macros. They do not
seem necessary anymore. So I will remove them.
> +
> + kunit_warning_suppress(test) {
> + trigger_backtrace_warn_on();
> + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
> + }
> +}
> +
> +static void backtrace_suppression_test_count(struct kunit *test)
> +{
> + if (!IS_ENABLED(CONFIG_BUG))
> + kunit_skip(test, "requires CONFIG_BUG");
> +
> + kunit_warning_suppress(test) {
> + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 0);
> +
> + WARN(1, "suppressed");
> + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
> +
> + WARN(1, "suppressed again");
> + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 2);
> + }
> +}
> +
> +static void backtrace_suppression_test_active_state(struct kunit *test)
> +{
> + KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());
> +
> + kunit_warning_suppress(test) {
> + KUNIT_EXPECT_TRUE(test, kunit_has_active_suppress_warning());
> + }
> +
> + KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());
> +
> + kunit_warning_suppress(test) {
> + KUNIT_EXPECT_TRUE(test, kunit_has_active_suppress_warning());
> + }
> +
> + KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());
> +}
> +
> +static void backtrace_suppression_test_multi_scope(struct kunit *test)
> +{
> + struct kunit_suppressed_warning *sw1, *sw2;
> +
> + if (!IS_ENABLED(CONFIG_BUG))
> + kunit_skip(test, "requires CONFIG_BUG");
> + if (!IS_ENABLED(CONFIG_DEBUG_BUGVERBOSE))
> + kunit_skip(test, "requires CONFIG_DEBUG_BUGVERBOSE");
> +
> + sw1 = kunit_start_suppress_warning(test);
> + trigger_backtrace_warn_on();
> + WARN(1, "suppressed by sw1");
> + kunit_end_suppress_warning(test, sw1);
> +
> + sw2 = kunit_start_suppress_warning(test);
> + WARN(1, "suppressed by sw2");
> + kunit_end_suppress_warning(test, sw2);
> +
> + KUNIT_EXPECT_EQ(test, kunit_suppressed_warning_count(sw1), 2);
> + KUNIT_EXPECT_EQ(test, kunit_suppressed_warning_count(sw2), 1);
> +}
> +
> +struct cross_kthread_data {
> + bool was_active;
> + struct completion done;
> +};
> +
> +static int cross_kthread_fn(void *data)
> +{
> + struct cross_kthread_data *d = data;
> +
> + d->was_active = kunit_has_active_suppress_warning();
> + complete(&d->done);
> + while (!kthread_should_stop())
> + schedule();
> + return 0;
> +}
> +
> +static void backtrace_suppression_test_cross_kthread(struct kunit *test)
> +{
> + struct cross_kthread_data data;
> + struct task_struct *task;
> +
> + data.was_active = false;
> + init_completion(&data.done);
> +
> + kunit_warning_suppress(test) {
> + task = kthread_run(cross_kthread_fn, &data, "kunit-cross-test");
> + KUNIT_ASSERT_FALSE(test, IS_ERR(task));
> + wait_for_completion(&data.done);
> + kthread_stop(task);
> + }
> +
> + KUNIT_EXPECT_FALSE(test, data.was_active);
> +}
> +
> +static struct kunit_case backtrace_suppression_test_cases[] = {
> + KUNIT_CASE(backtrace_suppression_test_warn_direct),
> + KUNIT_CASE(backtrace_suppression_test_warn_indirect),
> + KUNIT_CASE(backtrace_suppression_test_warn_multi),
> + KUNIT_CASE(backtrace_suppression_test_warn_on_direct),
> + KUNIT_CASE(backtrace_suppression_test_warn_on_indirect),
> + KUNIT_CASE(backtrace_suppression_test_count),
> + KUNIT_CASE(backtrace_suppression_test_active_state),
> + KUNIT_CASE(backtrace_suppression_test_multi_scope),
> + KUNIT_CASE(backtrace_suppression_test_cross_kthread),
> + {}
> +};
> +
> +static struct kunit_suite backtrace_suppression_test_suite = {
> + .name = "backtrace-suppression-test",
> + .test_cases = backtrace_suppression_test_cases,
> +};
> +kunit_test_suites(&backtrace_suppression_test_suite);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("KUnit test to verify warning backtrace suppression");
>
> --
> 2.53.0
>
^ permalink raw reply
* Re: [PATCH v7 13/20] KVM: arm64: Apply dynamic guest counter reservations
From: James Clark @ 2026-05-15 8:28 UTC (permalink / raw)
To: Colton Lewis
Cc: alexandru.elisei, pbonzini, corbet, linux, catalin.marinas, will,
maz, oliver.upton, mizhang, joey.gouly, suzuki.poulose, yuzenghui,
mark.rutland, shuah, gankulkarni, linux-doc, linux-kernel,
linux-arm-kernel, kvmarm, linux-perf-users, linux-kselftest, kvm
In-Reply-To: <gsntlddlbylw.fsf@coltonlewis-kvm.c.googlers.com>
On 14/05/2026 8:05 pm, Colton Lewis wrote:
> James Clark <james.clark@linaro.org> writes:
>
>> On 13/05/2026 5:45 pm, Colton Lewis wrote:
>>> James Clark <james.clark@linaro.org> writes:
>
>>>> On 04/05/2026 10:18 pm, Colton Lewis wrote:
>>>>> Apply dynamic guest counter reservations by checking if the requested
>>>>> guest mask collides with any events the host has scheduled and calling
>>>>> pmu_perf_resched_update() with a hook that updates the mask of
>>>>> available counters in between schedule out and schedule in.
>
>>>>> Signed-off-by: Colton Lewis <coltonlewis@google.com>
>>>>> ---
>>>>> arch/arm64/kvm/pmu-direct.c | 69 ++++++++++++++++++++++++++++++++
>>>>> ++++
>>>>> include/linux/perf/arm_pmu.h | 1 +
>>>>> 2 files changed, 70 insertions(+)
>
>>>>> diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
>>>>> index 2252d3b905db9..14cc419dbafad 100644
>>>>> --- a/arch/arm64/kvm/pmu-direct.c
>>>>> +++ b/arch/arm64/kvm/pmu-direct.c
>>>>> @@ -100,6 +100,73 @@ u8 kvm_pmu_hpmn(struct kvm_vcpu *vcpu)
>>>>> return *host_data_ptr(nr_event_counters);
>>>>> }
>
>>>>> +/* Callback to update counter mask between perf scheduling */
>>>>> +static void kvm_pmu_update_mask(struct pmu *pmu, void *data)
>>>>> +{
>>>>> + struct arm_pmu *arm_pmu = to_arm_pmu(pmu);
>>>>> + unsigned long *new_mask = data;
>>>>> +
>>>>> + bitmap_copy(arm_pmu->cntr_mask, new_mask, ARMPMU_MAX_HWEVENTS);
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * kvm_pmu_set_guest_counters() - Handle dynamic counter reservations
>>>>> + * @cpu_pmu: struct arm_pmu to potentially modify
>>>>> + * @guest_mask: new guest mask for the pmu
>>>>> + *
>>>>> + * Check if guest counters will interfere with current host events
>>>>> and
>>>>> + * call into perf_pmu_resched_update if a reschedule is required.
>>>>> + */
>>>>> +static void kvm_pmu_set_guest_counters(struct arm_pmu *cpu_pmu, u64
>>>>> guest_mask)
>>>>> +{
>>>>> + struct pmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events);
>>>>> + DECLARE_BITMAP(guest_bitmap, ARMPMU_MAX_HWEVENTS);
>>>>> + DECLARE_BITMAP(new_mask, ARMPMU_MAX_HWEVENTS);
>>>>> + bool need_resched = false;
>>>>> +
>>>>> + bitmap_from_arr64(guest_bitmap, &guest_mask,
>>>>> ARMPMU_MAX_HWEVENTS);
>>>>> + bitmap_copy(new_mask, cpu_pmu->hw_cntr_mask,
>>>>> ARMPMU_MAX_HWEVENTS);
>>>>> +
>>>>> + if (guest_mask) {
>>>>> + /* Subtract guest counters from available host mask */
>>>>> + bitmap_andnot(new_mask, new_mask, guest_bitmap,
>>>>> ARMPMU_MAX_HWEVENTS);
>>>>> +
>>>>> + /* Did we collide with an active host event? */
>>>>> + if (bitmap_intersects(cpuc->used_mask, guest_bitmap,
>>>>> ARMPMU_MAX_HWEVENTS)) {
>>>>> + int idx;
>>>>> +
>>>>> + need_resched = true;
>>>>> + cpuc->host_squeezed = true;
>>>>> +
>>>>> + /* Look for pinned events that are about to be
>>>>> preempted */
>>>>> + for_each_set_bit(idx, guest_bitmap,
>>>>> ARMPMU_MAX_HWEVENTS) {
>>>>> + if (test_bit(idx, cpuc->used_mask) && cpuc-
>>>>> >events[idx] &&
>>>>> + cpuc->events[idx]->attr.pinned) {
>>>>> + pr_warn_ratelimited("perf: Pinned host event
>>>>> squeezed out by KVM guest PMU partition\n");
>
>>>> Hi Colton,
>
>>>> I get "perf: Pinned host event squeezed out by KVM guest PMU partition"
>>>> even with arm_pmuv3.reserved_host_counters=3 for example. I would have
>>>> expected any non zero value to stop the warning.
>
>>>> I think armv8pmu_get_single_idx() needs to be changed to allocate from
>>>> the high end host counters first. A more complicated option would be
>>>> checking to see if there are any non-pinned counters in the host
>>>> reserved half when a new pinned counter is opened, then swapping the
>>>> places of the new pinned and existing non-pinned counters so pinned
>>>> always prefer being put into the host half. But it's probably not worth
>>>> doing that.
>
>>>> James
>
>
>>> I agree it makes the most sense to allocate from the top, but I'm happy
>>> the basic idea works.
>
>
>> Another thing I forgot to mention is that even with the ratelimited
>> warning, this spams the logs any time the host and guest are both using
>> the PMU and I'm not sure how useful that is.
>
> I'm sure it does. I'll delete it.
>
A warn_once might save someone a few hours of debugging, but we probably
don't need more than that.
>>>>> + break;
>>>>> + }
>>>>> + }
>>>>> + }
>>>>> + } else {
>>>>> + /*
>>>>> + * Restoring to hw_cntr_mask.
>>>>> + * Only resched if we previously squeezed an event.
>>>>> + */
>>>>> + if (cpuc->host_squeezed) {
>>>>> + need_resched = true;
>>>>> + cpuc->host_squeezed = false;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + if (need_resched) {
>>>>> + /* Collision: run full perf reschedule */
>>>>> + perf_pmu_resched_update(&cpu_pmu->pmu, kvm_pmu_update_mask,
>>>>> new_mask);
>>>>> + } else {
>>>>> + /* Host was never using guest counters anyway */
>>>>> + bitmap_copy(cpu_pmu->cntr_mask, new_mask,
>>>>> ARMPMU_MAX_HWEVENTS);
>>>>> + }
>>>>> +}
>>>>> +
>>>>> /**
>>>>> * kvm_pmu_host_counter_mask() - Compute bitmask of host-reserved
>>>>> counters
>>>>> * @pmu: Pointer to arm_pmu struct
>>>>> @@ -218,6 +285,7 @@ void kvm_pmu_load(struct kvm_vcpu *vcpu)
>
>>>>> pmu = vcpu->kvm->arch.arm_pmu;
>>>>> guest_counters = kvm_pmu_guest_counter_mask(pmu);
>>>>> + kvm_pmu_set_guest_counters(pmu, guest_counters);
>>>>> kvm_pmu_apply_event_filter(vcpu);
>
>>>>> for_each_set_bit(i, &guest_counters, ARMPMU_MAX_HWEVENTS) {
>>>>> @@ -319,5 +387,6 @@ void kvm_pmu_put(struct kvm_vcpu *vcpu)
>>>>> val = read_sysreg(pmintenset_el1);
>>>>> __vcpu_assign_sys_reg(vcpu, PMINTENSET_EL1, val & mask);
>
>>>>> + kvm_pmu_set_guest_counters(pmu, 0);
>>>>> preempt_enable();
>>>>> }
>>>>> diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/
>>>>> arm_pmu.h
>>>>> index f7b000bb3eca8..63f88fec5e80f 100644
>>>>> --- a/include/linux/perf/arm_pmu.h
>>>>> +++ b/include/linux/perf/arm_pmu.h
>>>>> @@ -75,6 +75,7 @@ struct pmu_hw_events {
>
>>>>> /* Active events requesting branch records */
>>>>> unsigned int branch_users;
>>>>> + bool host_squeezed;
>>>>> };
>
>>>>> enum armpmu_attr_groups {
^ permalink raw reply
* Re: [PATCH v10 8/9] platform/chrome: Protect cros_ec_device lifecycle with revocable
From: Tzung-Bi Shih @ 2026-05-15 8:28 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
Linus Walleij, Benson Leung, linux-kernel, chrome-platform,
driver-core, linux-doc, linux-gpio, Rafael J. Wysocki,
Danilo Krummrich, Jonathan Corbet, Shuah Khan, Laurent Pinchart,
Wolfram Sang, Johan Hovold, Paul E . McKenney
In-Reply-To: <20260514160214.GH787748@nvidia.com>
On Thu, May 14, 2026 at 01:02:14PM -0300, Jason Gunthorpe wrote:
> On Thu, May 14, 2026 at 03:33:55AM +0000, Tzung-Bi Shih wrote:
>
> > > Given you say this is such a bug I think you really should be sending
> > > a series that is patches 5 through 7 from the other series and a
> > > simple rwsem instead of misc_deregister_sync() to deal with this bug
> > > ASAP. No need to complicate a simple bug fix in a driver with all
> > > these core changes.
> >
> > Apologies for missing this suggestion.
> >
> > For "patches 5 through 7 from the other series" I guess you're referring:
> > - https://lore.kernel.org/all/20260427134659.95181-6-tzungbi@kernel.org
> > - https://lore.kernel.org/all/20260427134659.95181-7-tzungbi@kernel.org
> > - https://lore.kernel.org/all/20260427134659.95181-8-tzungbi@kernel.org
>
> Yes
>
> > Could you provide a bit more detail on the rwsem approach? I'm not
> > entirely clear on what data or operations the rwsem would be protecting.
>
> Just put a rwsem, or even scru, inside the driver's fops.
>
> You can refactor that out to a misc or revocable later.
I see. Thank you for your suggestion. I will explore it and send out a
new version.
^ permalink raw reply
* htmldocs: Documentation/scheduler/sched-arch.rst:108: WARNING: Block quote ends without a blank line; unexpected unindent. [docutils]
From: kernel test robot @ 2026-05-15 8:10 UTC (permalink / raw)
To: Shrikanth Hegde; +Cc: oe-kbuild-all, 0day robot, linux-doc
tree: https://github.com/intel-lab-lkp/linux/commits/Shrikanth-Hegde/sched-debug-Remove-unused-schedstats/20260515-054159
head: 7c2ea3b4f5530a92ddf8bf0b9835101138cbcefb
commit: a9c3470cec4255c02ebdc8637ef447726b6b792f sched/docs: Document cpu_preferred_mask and Preferred CPU concept
date: 10 hours ago
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
docutils: docutils (Docutils 0.21.2, Python 3.13.5, on linux)
reproduce: (https://download.01.org/0day-ci/archive/20260515/202605151011.KtNAJKrV-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605151011.KtNAJKrV-lkp@intel.com/
All warnings (new ones prefixed by >>):
Checksumming on output with GSO
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [docutils]
MAINTAINERS:40: WARNING: Inline strong start-string without end-string. [docutils]
Documentation/scheduler/sched-arch.rst:107: ERROR: Unexpected indentation. [docutils]
>> Documentation/scheduler/sched-arch.rst:108: WARNING: Block quote ends without a blank line; unexpected unindent. [docutils]
Documentation/userspace-api/landlock:504: ./security/landlock/errata/abi-4.h:5: ERROR: Unexpected section title.
vim +108 Documentation/scheduler/sched-arch.rst
102
103 Notes:
104 1. This feature is available under CONFIG_PREFERRED_CPU
105 2. This feature works for FAIR/RT class.
106 3. A task pinned, which can't be moved to preferred CPUs will continue
107 to run based on its affinity. But no load balancing happens
> 108 4. If needed, steal time based governors/arch dependent method
109 could be used to cater to different types of cpu numbers.
110 Arch can do so by implementing its own hooks.
111 5. Decision to use/not use is driven by kernel. Hence it shouldn't
112 break user affinities. One of the main reason why CPU hotplug
113 or Isolated cpuset partitions was not a solution.
114
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH] driver core: Add cmdline option to force probe type
From: Greg KH @ 2026-05-15 8:05 UTC (permalink / raw)
To: Jianlin Lv
Cc: corbet, skhan, rafael, dakr, jianlv, linux-kernel, linux-doc,
driver-core
In-Reply-To: <CAFA-uR8Jj+MJaauAeEUoS+OfuudrV8C4mG2we-nd7-Cvofokhw@mail.gmail.com>
On Fri, May 15, 2026 at 03:54:17PM +0800, Jianlin Lv wrote:
> On Fri, May 15, 2026 at 2:13 PM Greg KH <gregkh@linuxfoundation.org> wrote:
> >
> > On Thu, May 14, 2026 at 11:09:03PM +0800, Jianlin Lv wrote:
> > > On Thu, May 14, 2026 at 9:49 PM Greg KH <gregkh@linuxfoundation.org> wrote:
> > > >
> > > > On Thu, May 14, 2026 at 09:35:08PM +0800, Jianlin Lv wrote:
> > > > > On Thu, May 14, 2026 at 6:16 PM Greg KH <gregkh@linuxfoundation.org> wrote:
> > > > > >
> > > > > > On Thu, May 14, 2026 at 05:49:55PM +0800, Jianlin Lv wrote:
> > > > > > > From: Jianlin Lv <iecedge@gmail.com>
> > > > > > >
> > > > > > > Device drivers that use asynchronous probing can cause non-deterministic
> > > > > > > device ordering and naming across reboots. A typical example is storage
> > > > > > > drivers (like sd/nvme): asynchronous probing can lead to inconsistent disk
> > > > > > > logical names after reboot. In scenarios where disk naming consistency is
> > > > > > > critical, the probe type should be set to synchronous.
> > > > > > >
> > > > > > > This patch introduces a driver_probe kernel parameter that overrides any
> > > > > > > driver's hard-coded probe type settings and allows runtime control without
> > > > > > > requiring kernel recompilation:
> > > > > > >
> > > > > > > driver_probe=PROBE_TYPE_SYNC,nvme,sd # Force specific drivers sync
> > > > > > > driver_probe=PROBE_TYPE_ASYNC,*,usb # Force all async except usb
> > > > > > > driver_probe=PROBE_TYPE_SYNC,* # Force all drivers synchronous
> > > > > > >
> > > > > > > The implementation replaces the limited driver_async_probe parameter with
> > > > > > > a more flexible interface that can force either synchronous or asynchronous
> > > > > > > probing as needed.
> > > > > > >
> > > > > > > Signed-off-by: Jianlin Lv <iecedge@gmail.com>
> > > > > > > ---
> > > > > > > .../admin-guide/kernel-parameters.txt | 27 +++++--
> > > > > > > drivers/base/dd.c | 71 ++++++++++++++-----
> > > > > > > 2 files changed, 74 insertions(+), 24 deletions(-)
> > > > > > >
> > > > > > > diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> > > > > > > index 4d0f545fb3ec..b43a8bd20356 100644
> > > > > > > --- a/Documentation/admin-guide/kernel-parameters.txt
> > > > > > > +++ b/Documentation/admin-guide/kernel-parameters.txt
> > > > > > > @@ -1377,12 +1377,27 @@ Kernel parameters
> > > > > > > it becomes active and is searched during signature
> > > > > > > verification.
> > > > > > >
> > > > > > > - driver_async_probe= [KNL]
> > > > > > > - List of driver names to be probed asynchronously. *
> > > > > > > - matches with all driver names. If * is specified, the
> > > > > > > - rest of the listed driver names are those that will NOT
> > > > > > > - match the *.
> > > > > > > - Format: <driver_name1>,<driver_name2>...
> > > > > >
> > > > > > You can not remove an existing user/kernel api, sorry, that is not
> > > > > > allowed as you just broke all systems that were relying on this :(
> > > > > >
> > > > > Could you provide more suggestions on how to improve this patch?
> > > >
> > > > Not really, sorry, I don't think this is a change that should be done at
> > > > all. disk naming is a long-solved issue, to think that you can fix that
> > > > by doing sync/async device probing is not understanding both the issues
> > > > involved, and how we solved it already :)
> > >
> > > Do you mean referencing disks via by-path/by-id?
> >
> > No, use something that does not change, like filesystem labels or
> > serial numbers, or something else that is guaranteed unique.
> >
> > > In our production env
> > > they can also be unstable; this is an example I encountered before:
> > > https://lore.kernel.org/all/CAFA-uR_jk6jCmf9DTebSVBRwtoLuXuyvf1Biq+OObqRVAOZbBw@mail.gmail.com/
> >
> > Yes, paths can, and will, change. Don't use them.
> >
> > Why not use a UUID, that is explicitly what those are designed for.
>
> For cloud deployment, the local volume provisioner detects
> and creates PVs for each local disk on the host, and it cleans up
> the disks when they are released.
> The local volume provisioner is deployed in the cluster as
> DaemonSet. For the same SKU, it uses a single, unified configuration
> file to initialize local disks. In this scenario, UUIDs are not applicable.
> In our case, logical names are used to identify the disk.
Ok, but again, there is NEVER a guarantee that those logical names are
going to be the same across boots. Please NEVER rely on them if you
wish to mount things properly. Use a label on the filesystem instead.
Or something else, you have loads of possibilities here. Attempting to
manipulate driver sync/async order is not going to solve your problem at
all.
> > > I understand that device naming in the kernel can change at any time. However,
> > > Is it necessary to provide an interface that allows users to choose
> > > the probe mode themselves?
> >
> > It's not going to solve your problem, so I wouldn't worry about it. And
> > you can't remove it, although I really would like to :)
>
> OK,Is the reason we can’t change the priority of driver_async_probe setting that
> the original logic has changed, i.e. it would alter the original behavior?
Because it is a user api that people might be relying on for various
unknown reasons.
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH v11 1/4] bug/kunit: Core support for suppressing warning backtraces
From: Albert Esteve @ 2026-05-15 7:58 UTC (permalink / raw)
To: Arnd Bergmann, Brendan Higgins, David Gow, Rae Moar,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Jonathan Corbet, Shuah Khan, Andrew Morton,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti
Cc: linux-kernel, linux-arch, linux-kselftest, kunit-dev, dri-devel,
workflows, linux-riscv, linux-doc, peterz, Alessandro Carminati,
Guenter Roeck, Kees Cook
In-Reply-To: <20260514-kunit_add_support-v11-1-b36a530a6d8f@redhat.com>
On Thu, May 14, 2026 at 1:07 PM Albert Esteve <aesteve@redhat.com> wrote:
>
> From: Alessandro Carminati <acarmina@redhat.com>
>
> Some unit tests intentionally trigger warning backtraces by passing bad
> parameters to kernel API functions. Such unit tests typically check the
> return value from such calls, not the existence of the warning backtrace.
>
> Such intentionally generated warning backtraces are neither desirable
> nor useful for a number of reasons:
> - They can result in overlooked real problems.
> - A warning that suddenly starts to show up in unit tests needs to be
> investigated and has to be marked to be ignored, for example by
> adjusting filter scripts. Such filters are ad hoc because there is
> no real standard format for warnings. On top of that, such filter
> scripts would require constant maintenance.
>
> Solve the problem by providing a means to suppress warning backtraces
> originating from the current kthread while executing test code. Since
> each KUnit test runs in its own kthread, this effectively scopes
> suppression to the test that enabled it. Limit changes to generic code
> to the absolute minimum.
>
> Implementation details:
> Suppression is integrated into the existing KUnit hooks infrastructure
> in test-bug.h, reusing the kunit_running static branch for zero
> overhead when no tests are running.
>
> Suppression is checked at three points in the warning path:
> - In warn_slowpath_fmt(), the check runs before any output, fully
> suppressing both message and backtrace. This covers architectures
> without __WARN_FLAGS.
> - In __warn_printk(), the check suppresses the warning message text.
> This covers architectures that define __WARN_FLAGS but not their own
> __WARN_printf (arm64, loongarch, parisc, powerpc, riscv, sh), where
> the message is printed before the trap enters __report_bug().
> - In __report_bug(), the check runs before __warn() is called,
> suppressing the backtrace and stack dump.
>
> To avoid double-counting on architectures where both __warn_printk()
> and __report_bug() run for the same warning, kunit_is_suppressed_warning()
> takes a bool parameter: true to increment the suppression counter
> (used in warn_slowpath_fmt and __report_bug), false to check only
> (used in __warn_printk).
>
> The suppression state is dynamically allocated via kunit_kzalloc() and
> tied to the KUnit test lifecycle via kunit_add_action(), ensuring
> automatic cleanup at test exit. Writer-side access to the global
> suppression list is serialized with a spinlock; readers use RCU.
>
> Two API forms are provided:
> - kunit_warning_suppress(test) { ... }: scoped, uses __cleanup for
> automatic teardown on scope exit, kunit_add_action() as safety net
> for abnormal exits (e.g. kthread_exit from failed assertions).
> Suppression handle is only accessible inside the block.
> - kunit_start/end_suppress_warning(test): direct functions returning
> an explicit handle, for retaining the handle within the test,
> or for cross-function usage.
>
> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> Signed-off-by: Alessandro Carminati <acarmina@redhat.com>
> Reviewed-by: Kees Cook <kees@kernel.org>
> Reviewed-by: David Gow <david@davidgow.net>
> Signed-off-by: Albert Esteve <aesteve@redhat.com>
> ---
> include/kunit/test-bug.h | 26 ++++++++++
> include/kunit/test.h | 98 ++++++++++++++++++++++++++++++++++++++
> kernel/panic.c | 11 +++++
> lib/bug.c | 14 +++++-
> lib/kunit/Makefile | 3 +-
> lib/kunit/bug.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++
> lib/kunit/hooks-impl.h | 2 +
> 7 files changed, 271 insertions(+), 3 deletions(-)
>
> diff --git a/include/kunit/test-bug.h b/include/kunit/test-bug.h
> index 47aa8f21ccce8..99869029fc686 100644
> --- a/include/kunit/test-bug.h
> +++ b/include/kunit/test-bug.h
> @@ -10,6 +10,7 @@
> #define _KUNIT_TEST_BUG_H
>
> #include <linux/stddef.h> /* for NULL */
> +#include <linux/types.h> /* for bool */
>
> #if IS_ENABLED(CONFIG_KUNIT)
>
> @@ -23,6 +24,7 @@ DECLARE_STATIC_KEY_FALSE(kunit_running);
> extern struct kunit_hooks_table {
> __printf(3, 4) void (*fail_current_test)(const char*, int, const char*, ...);
> void *(*get_static_stub_address)(struct kunit *test, void *real_fn_addr);
> + bool (*is_suppressed_warning)(bool count);
> } kunit_hooks;
>
> /**
> @@ -60,9 +62,33 @@ static inline struct kunit *kunit_get_current_test(void)
> } \
> } while (0)
>
> +/**
> + * kunit_is_suppressed_warning() - Check if warnings are being suppressed
> + * by the current KUnit test.
> + * @count: if true, increment the suppression counter on match.
> + *
> + * Returns true if the current task has active warning suppression.
> + * Uses the kunit_running static branch for zero overhead when no tests run.
> + *
> + * A single WARN*() may traverse multiple call sites in the warning path
> + * (e.g., __warn_printk() and __report_bug()). Pass @count = true at the
> + * primary suppression point to count each warning exactly once, and
> + * @count = false at secondary points to suppress output without
> + * inflating the count.
> + */
> +static inline bool kunit_is_suppressed_warning(bool count)
> +{
> + if (!static_branch_unlikely(&kunit_running))
> + return false;
> +
> + return kunit_hooks.is_suppressed_warning &&
> + kunit_hooks.is_suppressed_warning(count);
> +}
> +
> #else
>
> static inline struct kunit *kunit_get_current_test(void) { return NULL; }
> +static inline bool kunit_is_suppressed_warning(bool count) { return false; }
>
> #define kunit_fail_current_test(fmt, ...) do {} while (0)
>
> diff --git a/include/kunit/test.h b/include/kunit/test.h
> index 9cd1594ab697d..be71612f61655 100644
> --- a/include/kunit/test.h
> +++ b/include/kunit/test.h
> @@ -1795,4 +1795,102 @@ do { \
> // include resource.h themselves if they need it.
> #include <kunit/resource.h>
>
> +/*
> + * Warning backtrace suppression API.
> + *
> + * Suppresses WARN*() backtraces on the current task while active. Two forms
> + * are provided:
> + *
> + * - Scoped: kunit_warning_suppress(test) { ... }
> + * Suppression is active for the duration of the block. On normal exit,
> + * the for-loop increment deactivates suppression. On early exit (break,
> + * return, goto), the __cleanup attribute fires. On kthread_exit() (e.g.,
> + * a failed KUnit assertion), kunit_add_action() cleans up at test
> + * teardown. The suppression handle is only accessible inside the block,
> + * so warning counts must be checked before the block exits.
> + *
> + * - Direct: kunit_start_suppress_warning() / kunit_end_suppress_warning()
> + * The underlying functions, returning an explicit handle pointer. Use
> + * when the handle needs to be retained (e.g., for post-suppression
> + * count checks) or passed across helper functions.
> + */
> +struct kunit_suppressed_warning;
> +
> +struct kunit_suppressed_warning *
> +kunit_start_suppress_warning(struct kunit *test);
> +void kunit_end_suppress_warning(struct kunit *test,
> + struct kunit_suppressed_warning *w);
> +int kunit_suppressed_warning_count(struct kunit_suppressed_warning *w);
> +void __kunit_suppress_auto_cleanup(struct kunit_suppressed_warning **wp);
> +bool kunit_has_active_suppress_warning(void);
> +
> +/**
> + * kunit_warning_suppress() - Suppress WARN*() backtraces for the duration
> + * of a block.
> + * @test: The test context object.
> + *
> + * Scoped form of the suppression API. Suppression starts when the block is
> + * entered and ends automatically when the block exits through any path. See
> + * the section comment above for the cleanup guarantees on each exit path.
> + * Fails the test if suppression is already active; nesting is not supported.
> + *
> + * The warning count can be checked inside the block via
> + * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(). The handle is not accessible
> + * after the block exits.
> + *
> + * Example::
> + *
> + * kunit_warning_suppress(test) {
> + * trigger_warning();
> + * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
> + * }
> + */
> +#define kunit_warning_suppress(test) \
> + for (struct kunit_suppressed_warning *__kunit_suppress \
> + __cleanup(__kunit_suppress_auto_cleanup) = \
> + kunit_start_suppress_warning(test); \
> + __kunit_suppress; \
> + kunit_end_suppress_warning(test, __kunit_suppress), \
> + __kunit_suppress = NULL)
> +
> +/**
> + * KUNIT_SUPPRESSED_WARNING_COUNT() - Returns the suppressed warning count.
> + *
> + * Returns the number of WARN*() calls suppressed since the current
> + * suppression block started, or 0 if the handle is NULL. Usable inside a
> + * kunit_warning_suppress() block.
> + */
> +#define KUNIT_SUPPRESSED_WARNING_COUNT() \
> + kunit_suppressed_warning_count(__kunit_suppress)
> +
> +/**
> + * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT() - Sets an expectation that the
> + * suppressed warning count equals
> + * @expected.
> + * @test: The test context object.
> + * @expected: an expression that evaluates to the expected warning count.
> + *
> + * Sets an expectation that the number of suppressed WARN*() calls equals
> + * @expected. This is semantically equivalent to
> + * KUNIT_EXPECT_EQ(@test, KUNIT_SUPPRESSED_WARNING_COUNT(), @expected).
> + * See KUNIT_EXPECT_EQ() for more information.
> + */
> +#define KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, expected) \
> + KUNIT_EXPECT_EQ(test, KUNIT_SUPPRESSED_WARNING_COUNT(), expected)
> +
> +/**
> + * KUNIT_ASSERT_SUPPRESSED_WARNING_COUNT() - Sets an assertion that the
> + * suppressed warning count equals
> + * @expected.
> + * @test: The test context object.
> + * @expected: an expression that evaluates to the expected warning count.
> + *
> + * Sets an assertion that the number of suppressed WARN*() calls equals
> + * @expected. This is the same as KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(),
> + * except it causes an assertion failure (see KUNIT_ASSERT_TRUE()) when the
> + * assertion is not met.
> + */
> +#define KUNIT_ASSERT_SUPPRESSED_WARNING_COUNT(test, expected) \
> + KUNIT_ASSERT_EQ(test, KUNIT_SUPPRESSED_WARNING_COUNT(), expected)
> +
> #endif /* _KUNIT_TEST_H */
> diff --git a/kernel/panic.c b/kernel/panic.c
> index 20feada5319d4..213725b612aa1 100644
> --- a/kernel/panic.c
> +++ b/kernel/panic.c
> @@ -39,6 +39,7 @@
> #include <linux/sys_info.h>
> #include <trace/events/error_report.h>
> #include <asm/sections.h>
> +#include <kunit/test-bug.h>
>
> #define PANIC_TIMER_STEP 100
> #define PANIC_BLINK_SPD 18
> @@ -1124,6 +1125,11 @@ void warn_slowpath_fmt(const char *file, int line, unsigned taint,
> bool rcu = warn_rcu_enter();
> struct warn_args args;
>
> + if (kunit_is_suppressed_warning(true)) {
> + warn_rcu_exit(rcu);
> + return;
> + }
> +
> pr_warn(CUT_HERE);
>
> if (!fmt) {
> @@ -1146,6 +1152,11 @@ void __warn_printk(const char *fmt, ...)
> bool rcu = warn_rcu_enter();
> va_list args;
>
> + if (kunit_is_suppressed_warning(false)) {
> + warn_rcu_exit(rcu);
> + return;
> + }
> +
> pr_warn(CUT_HERE);
>
> va_start(args, fmt);
> diff --git a/lib/bug.c b/lib/bug.c
> index 224f4cfa4aa31..d99e369bc1103 100644
> --- a/lib/bug.c
> +++ b/lib/bug.c
> @@ -48,6 +48,7 @@
> #include <linux/rculist.h>
> #include <linux/ftrace.h>
> #include <linux/context_tracking.h>
> +#include <kunit/test-bug.h>
>
> extern struct bug_entry __start___bug_table[], __stop___bug_table[];
>
> @@ -209,8 +210,6 @@ static enum bug_trap_type __report_bug(struct bug_entry *bug, unsigned long buga
> return BUG_TRAP_TYPE_NONE;
> }
>
> - disable_trace_on_warning();
> -
> bug_get_file_line(bug, &file, &line);
> fmt = bug_get_format(bug);
>
> @@ -220,6 +219,17 @@ static enum bug_trap_type __report_bug(struct bug_entry *bug, unsigned long buga
> no_cut = bug->flags & BUGFLAG_NO_CUT_HERE;
> has_args = bug->flags & BUGFLAG_ARGS;
>
> +#ifdef CONFIG_KUNIT
Sashiko says:
"""
Is the CONFIG_KUNIT check sufficient here?
CONFIG_KUNIT is a tristate configuration option. When KUnit is built as a
module, the preprocessor macro CONFIG_KUNIT_MODULE is defined instead,
leaving CONFIG_KUNIT undefined.
Because lib/bug.c is compiled into the core kernel, this block will be
silently stripped out during a module build. This prevents warning
suppression from working on all architectures that rely on __report_bug().
Could this use IS_ENABLED(CONFIG_KUNIT) instead, or be dropped completely
since include/kunit/test-bug.h provides a safe stub?
"""
Ugh, it is right. I did not consider the module case. Not only is it
safe now as it says, but iirc we added it for performance, however,
since we now have static_branch, it is not really needed. I think
removing it is the right thing to do here.
> + /*
> + * Before the once logic so suppressed warnings do not consume
> + * the single-fire budget of WARN_ON_ONCE().
> + */
> + if (warning && kunit_is_suppressed_warning(true))
> + return BUG_TRAP_TYPE_WARN;
> +#endif
> +
> + disable_trace_on_warning();
> +
> if (warning && once) {
> if (done)
> return BUG_TRAP_TYPE_WARN;
> diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
> index 656f1fa35abcc..4592f9d0aa8dd 100644
> --- a/lib/kunit/Makefile
> +++ b/lib/kunit/Makefile
> @@ -10,7 +10,8 @@ kunit-objs += test.o \
> executor.o \
> attributes.o \
> device.o \
> - platform.o
> + platform.o \
> + bug.o
>
> ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
> kunit-objs += debugfs.o
> diff --git a/lib/kunit/bug.c b/lib/kunit/bug.c
> new file mode 100644
> index 0000000000000..8579235c9ca68
> --- /dev/null
> +++ b/lib/kunit/bug.c
> @@ -0,0 +1,120 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * KUnit helpers for backtrace suppression
> + *
> + * Copyright (C) 2025 Alessandro Carminati <acarmina@redhat.com>
> + * Copyright (C) 2024 Guenter Roeck <linux@roeck-us.net>
> + */
> +
> +#include <kunit/resource.h>
> +#include <linux/export.h>
> +#include <linux/rculist.h>
> +#include <linux/sched.h>
> +#include <linux/sched/task.h>
> +#include <linux/spinlock.h>
> +
> +#include "hooks-impl.h"
> +
> +struct kunit_suppressed_warning {
> + struct list_head node;
> + struct task_struct *task;
> + struct kunit *test;
> + atomic_t counter;
> +};
> +
> +static LIST_HEAD(suppressed_warnings);
> +static DEFINE_SPINLOCK(suppressed_warnings_lock);
> +
> +static void kunit_suppress_warning_remove(struct kunit_suppressed_warning *w)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&suppressed_warnings_lock, flags);
> + list_del_rcu(&w->node);
> + spin_unlock_irqrestore(&suppressed_warnings_lock, flags);
> + put_task_struct(w->task);
Sashiko says:
"""
Does this code introduce a use-after-free regression for concurrent RCU
readers?
Because the suppression handle is allocated using kunit_kzalloc() below,
the KUnit framework will automatically free it with a synchronous kfree()
at the end of the test.
Since the handle is unlinked using list_del_rcu() here, but there is no
synchronize_rcu() or kfree_rcu() between the list removal and the memory
free, a concurrent task evaluating warnings under rcu_read_lock() could
dereference the pointer after it has been freed.
Would it be safer to allocate the handle with kzalloc() and explicitly free
it using kfree_rcu() inside this cleanup action?
"""
It is taking a few iterations to get this right...
In the previous version we ruled out synchronize_rcu() because it is a
blocking call that can deadlock if exited while holding the RCU lock.
On the other hand, the suggested kfree_rcu(), only frees memory, but
we also need to release the task reference in the w struct after the
grace period. Reading
`Documentation/RCU/Design/Memory-Ordering/Tree-RCU-Memory-Ordering.rst`,
a solution could be to hold an `rcu_head` in the suppressed warning
struct and invoke call_rcu directly (and explicitly free as suggested
by Sashiko, so I'd need to change kunit_kzalloc() too).
I hope that clears all races.
> +}
> +
> +KUNIT_DEFINE_ACTION_WRAPPER(kunit_suppress_warning_cleanup,
> + kunit_suppress_warning_remove,
> + struct kunit_suppressed_warning *);
> +
> +bool kunit_has_active_suppress_warning(void)
> +{
> + return __kunit_is_suppressed_warning_impl(false);
> +}
> +EXPORT_SYMBOL_GPL(kunit_has_active_suppress_warning);
> +
> +struct kunit_suppressed_warning *
> +kunit_start_suppress_warning(struct kunit *test)
> +{
> + struct kunit_suppressed_warning *w;
> + unsigned long flags;
> + int ret;
> +
> + if (kunit_has_active_suppress_warning()) {
> + KUNIT_FAIL(test, "Another suppression block is already active");
> + return NULL;
> + }
> +
> + w = kunit_kzalloc(test, sizeof(*w), GFP_KERNEL);
> + if (!w) {
> + KUNIT_FAIL(test, "Failed to allocate suppression handle.");
> + return NULL;
> + }
> +
> + w->task = get_task_struct(current);
> + w->test = test;
> +
> + spin_lock_irqsave(&suppressed_warnings_lock, flags);
> + list_add_rcu(&w->node, &suppressed_warnings);
> + spin_unlock_irqrestore(&suppressed_warnings_lock, flags);
> +
> + ret = kunit_add_action_or_reset(test,
> + kunit_suppress_warning_cleanup, w);
> + if (ret) {
> + KUNIT_FAIL(test, "Failed to add suppression cleanup action.");
> + return NULL;
> + }
> +
> + return w;
> +}
> +EXPORT_SYMBOL_GPL(kunit_start_suppress_warning);
> +
> +void kunit_end_suppress_warning(struct kunit *test,
> + struct kunit_suppressed_warning *w)
> +{
> + if (!w)
> + return;
> + kunit_release_action(test, kunit_suppress_warning_cleanup, w);
> +}
> +EXPORT_SYMBOL_GPL(kunit_end_suppress_warning);
> +
> +void __kunit_suppress_auto_cleanup(struct kunit_suppressed_warning **wp)
> +{
> + if (*wp)
> + kunit_end_suppress_warning((*wp)->test, *wp);
> +}
> +EXPORT_SYMBOL_GPL(__kunit_suppress_auto_cleanup);
> +
> +int kunit_suppressed_warning_count(struct kunit_suppressed_warning *w)
> +{
> + return w ? atomic_read(&w->counter) : 0;
> +}
> +EXPORT_SYMBOL_GPL(kunit_suppressed_warning_count);
> +
> +bool __kunit_is_suppressed_warning_impl(bool count)
> +{
> + struct kunit_suppressed_warning *w;
> +
> + guard(rcu)();
> + list_for_each_entry_rcu(w, &suppressed_warnings, node) {
> + if (w->task == current) {
> + if (count)
> + atomic_inc(&w->counter);
> + return true;
> + }
> + }
> +
> + return false;
> +}
> diff --git a/lib/kunit/hooks-impl.h b/lib/kunit/hooks-impl.h
> index 4e71b2d0143ba..d8720f2616925 100644
> --- a/lib/kunit/hooks-impl.h
> +++ b/lib/kunit/hooks-impl.h
> @@ -19,6 +19,7 @@ void __printf(3, 4) __kunit_fail_current_test_impl(const char *file,
> int line,
> const char *fmt, ...);
> void *__kunit_get_static_stub_address_impl(struct kunit *test, void *real_fn_addr);
> +bool __kunit_is_suppressed_warning_impl(bool count);
>
> /* Code to set all of the function pointers. */
> static inline void kunit_install_hooks(void)
> @@ -26,6 +27,7 @@ static inline void kunit_install_hooks(void)
> /* Install the KUnit hook functions. */
> kunit_hooks.fail_current_test = __kunit_fail_current_test_impl;
> kunit_hooks.get_static_stub_address = __kunit_get_static_stub_address_impl;
> + kunit_hooks.is_suppressed_warning = __kunit_is_suppressed_warning_impl;
> }
>
> #endif /* _KUNIT_HOOKS_IMPL_H */
>
> --
> 2.53.0
>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox