lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <aVPDUVNX95Hv13VU@smile.fi.intel.com>
Date: Tue, 30 Dec 2025 14:19:29 +0200
From: Andriy Shevencho <andriy.shevchenko@...ux.intel.com>
To: Jonathan Brophy <professorjonny98@...il.com>
Cc: lee Jones <lee@...nel.org>, Pavel Machek <pavel@...nel.org>,
	Jonathan Brophy <professor_jonny@...mail.com>,
	Rob Herring <robh@...nel.org>,
	Krzysztof Kozlowski <krzk+dt@...nel.org>,
	Conor Dooley <conor+dt@...nel.org>,
	Radoslav Tsvetkov <rtsvetkov@...dotech.eu>,
	devicetree@...r.kernel.org, linux-kernel@...r.kernel.org,
	linux-leds@...r.kernel.org
Subject: Re: [PATCH v5 7/7] leds: Add virtual LED group driver with priority
 arbitration

On Tue, Dec 30, 2025 at 09:23:20PM +1300, Jonathan Brophy wrote:
> 
> Add a driver that implements virtual LED groups with priority-based
> arbitration for shared physical LEDs. The driver provides a multicolor
> LED interface while solving the coordination problem when multiple
> subsystems need to control the same physical LEDs.
> 
> Key features:
> 
> Winner-takes-all arbitration:
> - Only virtual LEDs with brightness > 0 participate
> - Highest priority wins (sequence number tie-breaking)
> - Winner controls ALL physical LEDs
> - Non-winner LEDs are turned off
> 
> Multicolor LED ABI support:
> - Full compliance with standard multicolor LED interface
> - Deterministic channel ordering by LED_COLOR_ID
> - Two modes: multicolor (dynamic) and standard (fixed-color)
> - Per-channel intensity and master brightness control
> 
> Memory optimization:
> - Conditional debug compilation (~30% size reduction when disabled)
> - Pre-allocated arbitration buffers
> - Efficient O(1) physical LED lookup via XArray
> - ~200 bytes per virtual LED in production builds
> 
> Locking design:
> - Hierarchical lock acquisition order prevents deadlocks
> - Lock-free arbitration with atomic sequence numbers
> - Temporary lock release during hardware I/O to allow concurrency
> 
> Hardware support:
> - GPIO, PWM, I2C, and SPI LED devices
> - Automatic physical LED discovery and claiming
> - Global ownership tracking prevents conflicts
> - Power management with suspend/resume
> 
> Debugfs telemetry (CONFIG_DEBUG_FS):
> - Arbitration statistics and latency metrics
> - Per-LED win/loss counters
> - Physical LED state inspection
> - Stress testing interface
> 
> Module parameters:
> - use_gamma_correction: Perceptual brightness (gamma 2.2)
> - update_delay_us: Rate limiting for slow buses
> - max_phys_leds: Buffer capacity (default 64)
> - enable_update_batching: 10ms coalescing for rapid changes
> 
> Typical use cases:
> - System status with boot/error priority levels
> - RGB lighting with coordinated control
> - Multi-element LED arrays (rings, strips)

...

> +/*
> + * leds-group-virtualcolor.c - Virtual grouped LED driver with Multicolor ABI

No name of the file in the file itself, please. This is proven to be often
forgotten if file name is changed.

> + *
> + * This driver is fully compliant with the multicolor LED ABI.
> + * It adds a policy layer to arbitrate shared physical LEDs,
> + * a problem not addressed by the LED core, without changing userspace-visible behavior.
> + * these additional extensions introduce new capabilities, such as:
> + *
> + * - Priority-based arbitration for shared physical LED ownership
> + * - Sequence/timestamp tie-breaking for deterministic conflict resolution
> + * - Runtime reconfiguration of shared channels for grouped LEDs
> + * - Atomic multi-device updates to ensure consistency
> + * - Group-wide brightness propagation and scaling
> + * - Support for arbitrated updates from multiple virtual LEDs to shared physical LEDs
> + * - Dynamic reallocation and resolution of conflicting virtual-to-physical mapping
> + *
> + * Priority-based arbitration resolves conflicts when multiple virtual LEDs
> + * reference the same physical LEDs. Arbitration rules are:
> + *   1. Priority value of led (higher wins)
> + *   2. Priority value of virtual controller (higher wins)
> + *   3. Sequence number for tie-breaking (most recent wins)
> + *   4. Winner-takes-all: ONE virtual LED controls ALL physical LEDs
> + *
> + * MC channel multipliers add advanced capabilities to LEDs, including:
> + * - Adjusting the relative brightness levels of different color channels
> + * - Normalizing output across different hardware vendors and physical configurations
> + * - Manipulating color temperature by changing the balance between channels
> + * - Avoiding overdriving specific channels unnecessarily
> + * - Mapping physical LEDs to application-specific color spaces and intensities
> + * - Emulating single fixed-color LEDs from multicolor LEDs
> + * - Dynamic reconfiguration of output characteristics
> + * - Capping brightness levels to suit specific use cases
> + *
> + * Winner-Takes-All Arbitration:
> + *   - Only vLEDs with brightness > 0 participate
> + *   - Highest priority wins (ties broken by sequence number)
> + *   - Winner controls ALL physical LEDs
> + *   - Physical LEDs not used by the winner are turned off
> + *
> + * Locking hierarchy (must be acquired in this order):
> + *   1. vcolor_controller.lock (per-controller)  ← Controller FIRST
> + *   2. global_owner_rwsem (global)             ← Global SECOND
> + *   3. virtual_led.lock (per-vLED)
> + *
> + * Never hold vLED locks during arbitration to avoid deadlock.
> + * Arbitration copies vLED state under the vLED lock, then releases locks
> + * before proceeding to core processing.
> + *
> + * Device Tree Dependency:
> + * This driver requires Device Tree (CONFIG_OF) due to LED phandle resolution.
> + * GPIO LEDs, in particular, rely on OF-specific APIs, as they lack full
> + * fwnode support. Minimal `CONFIG_OF` usage ensures easy migration to ACPI
> + * when fwnode abstraction improves. Key operations include:
> + *   - `of_led_get()` - Called for LED phandle resolution within the single
> + *                      bridge function `vcolor_led_from_fwnode()`.
> + *   - `device_for_each_child_node()` for child iteration
> + *   - `fwnode_property_*()` for property access
> + *   - `fwnode_handle_get/put()` for reference management
> + *
> + * By isolating OF dependency in the bridge function, migration to
> + * `fwnode_led_get()` will be straightforward when supported by the LED subsystem.
> + *
> + * Author: Jonathan Brophy <professor_jonny@...mail.com>

I would rather split this administrative meta information with the real doc.

/*
 * Copyright, Authorship, etc.
 */

/*
 * Top level documentation, may even be kernel-doc format (see DOC: prefix
 * for that).
 */

> + */

...

> +#include <linux/atomic.h>
> +#include <linux/compiler.h>
> +#include <linux/debugfs.h>
> +#include <linux/delay.h>

> +#include <linux/device.h>

> +#include <linux/err.h>

> +#include <linux/kernel.h>

No way you need this header.

> +#include <linux/kref.h>
> +#include <linux/ktime.h>
> +#include <linux/leds.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>

> +#include <linux/of_platform.h>

Why? You was thinking of mod_devicetable.h perhaps?

> +#include <linux/platform_device.h>

> +#include <linux/pm.h>
> +#include <linux/pm_runtime.h>

> +#include <linux/property.h>

> +#ifdef CONFIG_DEBUG_FS
> +  #include <linux/random.h>
> +#endif

> +#include <linux/ratelimit.h>
> +#include <linux/rwsem.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +#include <linux/workqueue.h>
> +#include <linux/xarray.h>

...

> +#ifdef CONFIG_DEBUG_FS
> +  #define MAX_DEBUGFS_NAME 96 /* Sized for "function:color-multicolor-##" + vLED name */
> +#endif

Unneeded ifdeffery.

...

> +#ifdef CONFIG_LOCKDEP
> +  #define assert_controller_locked(lvc) lockdep_assert_held(&(lvc)->lock)
> +  #define assert_vled_locked(vled) lockdep_assert_held(&(vled)->lock)
> +#else
> +#define assert_controller_locked(lvc) ((void)(lvc))
> +#define assert_vled_locked(vled) ((void)(vled))
> +#endif

Why?! Doesn't lockdep have already stubs?

> +/* Structured logging macros */
> +#define ctrl_err(lvc, fmt, ...) \
> +	dev_err(&(lvc)->pdev->dev, "[CTRL] " fmt, ##__VA_ARGS__)
> +
> +#define ctrl_warn(lvc, fmt, ...) \
> +	dev_warn(&(lvc)->pdev->dev, "[CTRL] " fmt, ##__VA_ARGS__)
> +
> +#define ctrl_info(lvc, fmt, ...) \
> +	dev_info(&(lvc)->pdev->dev, "[CTRL] " fmt, ##__VA_ARGS__)
> +
> +#define ctrl_dbg(lvc, fmt, ...) \
> +	dev_dbg(&(lvc)->pdev->dev, "[CTRL] " fmt, ##__VA_ARGS__)
> +
> +#define arb_err(lvc, fmt, ...) \
> +	dev_err_ratelimited(&(lvc)->pdev->dev, "[ARB] " fmt, ##__VA_ARGS__)
> +
> +#define vled_err(vled, fmt, ...) \
> +	dev_err(&(vled)->ctrl->pdev->dev, "[vLED:%s] " fmt, (vled)->name, ##__VA_ARGS__)

This usually is not required. You probably missed dev_fmt() macro.

...

> +/* Module parameters */
> +#ifdef CONFIG_DEBUG_FS
> +static bool enable_debugfs = true;
> +#else
> +static bool enable_debugfs;
> +#endif

Huh?! We don't need this as module parameter. Sure.

...

> +static inline unsigned long get_stable_led_key(struct led_classdev *cdev)
> +{
> +	if (!cdev)
> +		return 0;
> +
> +	/* GPIO LEDs don't have dev - use cdev pointer as key */
> +	if (cdev->dev)
> +		return (unsigned long)cdev->dev;
> +	else
> +		return (unsigned long)cdev;
> +}

This is magic that needs a good comment explaining all this voodoo pointer castings.

...

> +static inline bool controller_safe_arbitrate(struct vcolor_controller *lvc)
> +{
> +	bool ran;
> +
> +	if (!lvc)
> +		return false;
> +
> +	/* Fast path: avoid lock acquisition if nothing to do */
> +	if (atomic_read(&lvc->removing))
> +		return false;
> +
> +	/* FIX: Queue deferred arbitration if rebuilding */
> +	if (atomic_read(&lvc->rebuilding)) {
> +		lvc->needs_arbitration = true;
> +		return false;
> +	}

> +	mutex_lock(&lvc->lock);

I don't see unlock. Have you run sparse?

> +	/* Check suspended under lock to prevent suspend race */
> +	ran = false;
> +	if (!lvc->suspended && !atomic_read(&lvc->rebuilding) &&
> +		device_is_registered(&lvc->pdev->dev)) {
> +		controller_run_arbitration_and_update(lvc);
> +		ran = true;
> +	}
> +
> +	/* FIX: Lock was released by controller_run_arbitration_and_update */

Then at least you should add annotations for sparse.

> +	return ran;
> +}
> +
> +

Single blank line is enough.

> +/*

It looks like a kernel-doc, but not marked so. Any reason why this is done?

> + * parse_leds_fwnode_array - Parse LED references using fwnode APIs
> + * @dev: Device for logging and memory allocation
> + * @fwnode: Firmware node containing LED references
> + * @propname: Property name (e.g., "leds")
> + * @out_leds: Output array of LED classdev pointers
> + * @out_devs: Output array of device pointers (may contain NULLs for GPIO LEDs)
> + * @out_count: Number of LEDs found
> + *
> + * Uses fwnode APIs for property traversal, with a single OF bridge for LED resolution.
> + * This pattern mirrors V4L2's approach and makes future fwnode_led_get() migration trivial.
> + */
> +static int parse_leds_fwnode_array(struct device *dev,
> +				   const struct fwnode_handle *fwnode,
> +				   const char *propname,
> +				   struct led_classdev ***out_leds,

When I see triple pointers, my first thought that the data structures are badly
designed.

> +				   struct device ***out_devs,
> +				   u8 *out_count)
> +{
> +	struct fwnode_reference_args args;
> +	int count, idx, valid, i;
> +	struct led_classdev **leds;
> +	struct device **devs;
> +	struct led_classdev *cdev;
> +	struct device *led_dev;
> +	int ret;
> +
> +	*out_leds = NULL;
> +	*out_devs = NULL;
> +	*out_count = 0;
> +
> +	/* Count phandle references using generic fwnode API */
> +	count = 0;
> +	while (fwnode_property_get_reference_args(fwnode, propname,
> +						  NULL, 0, count, &args) == 0) {
> +		fwnode_handle_put(args.fwnode);
> +		count++;
> +	}
> +
> +	if (count <= 0)
> +		return 0;

> +	/* Allocate temporary arrays for LED/device pointers */
> +	leds = kcalloc(count, sizeof(*leds), GFP_KERNEL);
> +	if (!leds)
> +		return -ENOMEM;
> +
> +	devs = kcalloc(count, sizeof(*devs), GFP_KERNEL);
> +	if (!devs) {
> +		kfree(leds);

Have you considered using __free() and no_free_ptr()?

> +		return -ENOMEM;
> +	}
> +
> +	/* Iterate through each LED reference and PACK valid entries */
> +	valid = 0;
> +	for (idx = 0; idx < count; idx++) {
> +
> +   /*Resolve LED from fwnode using index.*/

Wrong indentation and style of the one-line comment.

> +		cdev = fwnode_led_get(fwnode, idx, &led_dev);
> +
> +		if (IS_ERR(cdev)) {
> +			ret = PTR_ERR(cdev);
> +
> +			/* Handle deferred probe - cleanup and return immediately */
> +			if (ret == -EPROBE_DEFER) {
> +				dev_info(dev, "LED %d not ready yet (EPROBE_DEFER), will retry\n", idx);
> +
> +				/* Release all previously acquired LEDs and devices */
> +				for (i = 0; i < valid; i++) {
> +					if (leds[i])
> +						led_put(leds[i]);
> +					if (devs[i])
> +						put_device(devs[i]);
> +				}
> +
> +				kfree(leds);
> +				kfree(devs);
> +				return -EPROBE_DEFER;
> +			}
> +
> +			/* Other errors - log and skip this LED */
> +			dev_warn(dev, "Failed to resolve LED %d: %d\n", idx, ret);
> +			continue;
> +		}
> +
> +		/* Store valid LED in PACKED position */
> +		if (is_valid_led_cdev(cdev)) {
> +			leds[valid] = cdev;	  /* Pack at 'valid' index, not 'idx' */
> +			devs[valid] = led_dev;   /* May be NULL for GPIO LEDs */
> +			valid++;
> +		} else {
> +			dev_warn(dev, "LED %d is not valid (no brightness_set method)\n", idx);
> +			led_put(cdev);
> +			if (led_dev)
> +				put_device(led_dev);
> +		}
> +	}
> +
> +	/* Check if we got any valid LEDs */
> +	if (valid == 0) {
> +		dev_warn(dev, "Property '%s': none of %d LED(s) resolved\n",
> +			 propname, count);
> +		kfree(leds);
> +		kfree(devs);
> +		return -ENODEV;
> +	}
> +
> +	/* Success - return PACKED arrays to caller */
> +	*out_leds = leds;
> +	*out_devs = devs;
> +	*out_count = (u8)valid;
> +
> +	return 0;
> +}

...

> +static const u8 gamma_table[256] = {
> +	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4,
> +	4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14,
> +	14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 25, 25, 26,
> +	26, 27, 28, 28, 29, 30, 30, 31, 32, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, 40, 41,
> +	42, 43, 44, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
> +	63, 64, 65, 66, 67, 68, 70, 71, 72, 73, 74, 75, 76, 78, 79, 80, 81, 82, 84, 85, 86, 87,
> +	89, 90, 91, 92, 94, 95, 96, 97, 99, 100, 101, 103, 104, 105, 107, 108, 109, 111, 112,
> +	114, 115, 116, 118, 119, 121, 122, 123, 125, 126, 128, 129, 131, 132, 134, 135, 137,
> +	138, 140, 141, 143, 144, 146, 147, 149, 150, 152, 154, 155, 157, 158, 160, 162, 163,
> +	165, 167, 168, 170, 172, 173, 175, 177, 178, 180, 182, 184, 185, 187, 189, 191, 192,
> +	194, 196, 198, 200, 201, 203, 205, 207, 209, 211, 212, 214, 216, 218, 220, 222, 224,
> +	226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 253, 255
> +};

This is utterly unreadable and unmaintainable.

Just make them to be a fixed number per line (usually power of 2, like 8 or 16)
with an index comment, like

	0, 0, 0, 0, 0, 0, 0, 0,				/*   0 -   8 */
	...
	240, 242, 244, 246, 248, 250, 253, 255,		/* 248 - 255 */

...

> +module_param(enable_debugfs, bool, 0444);
> +MODULE_PARM_DESC(enable_debugfs,
> +	"Enable debugfs interface for telemetry and testing (default: Y if CONFIG_DEBUG_FS)");
> +
> +module_param(use_gamma_correction, bool, 0644);
> +MODULE_PARM_DESC(use_gamma_correction,
> +	"Apply 2.2 gamma correction to brightness values (default: N)");
> +
> +module_param(update_delay_us, uint, 0644);
> +MODULE_PARM_DESC(update_delay_us,
> +	"Artificial delay in microseconds after each LED update batch (default: 0, max: 1000000)");
> +
> +module_param(max_phys_leds, uint, 0444);
> +MODULE_PARM_DESC(max_phys_leds,
> +	"Maximum unique physical LEDs per controller (default: 64, range: 1-1024)");
> +
> +module_param(enable_update_batching, bool, 0644);
> +MODULE_PARM_DESC(enable_update_batching,
> +	"Batch brightness updates with 10ms delay to reduce CPU overhead (default: N)");

No module parameters in a new code, please.

...

I stopped with this, this patch is half-baked and unreviewable. Please, split
it to a few features and add one-by-one, for example:

- very basic sypport
- feature A
- ...
- debugfs

So I expect 3+ patches out of this one. And try to keep size of a change less
than 1000 LoCs.

-- 
With Best Regards,
Andy Shevchenko



Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ