All of lore.kernel.org
 help / color / mirror / Atom feed
From: Andriy Shevencho <andriy.shevchenko@linux.intel.com>
To: Jonathan Brophy <professorjonny98@gmail.com>
Cc: lee Jones <lee@kernel.org>, Pavel Machek <pavel@kernel.org>,
	Jonathan Brophy <professor_jonny@hotmail.com>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	Radoslav Tsvetkov <rtsvetkov@gradotech.eu>,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-leds@vger.kernel.org
Subject: Re: [PATCH v5 7/7] leds: Add virtual LED group driver with priority arbitration
Date: Tue, 30 Dec 2025 14:19:29 +0200	[thread overview]
Message-ID: <aVPDUVNX95Hv13VU@smile.fi.intel.com> (raw)
In-Reply-To: <20251230082336.3308403-8-professorjonny98@gmail.com>

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@hotmail.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



  reply	other threads:[~2025-12-30 12:19 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-12-30  8:23 [PATCH v5 0/7] leds: Add virtual LED group driver with priority arbitration Jonathan Brophy
2025-12-30  8:23 ` [PATCH v5 1/7] dt-bindings: leds: add function virtual_status to led common properties Jonathan Brophy
2025-12-30  8:23 ` [PATCH v5 2/7] dt-bindings: leds: Add virtual LED class bindings Jonathan Brophy
2025-12-30  8:23 ` [PATCH v5 3/7] dt-bindings: leds: Add virtual LED group controller bindings Jonathan Brophy
2025-12-30  8:23 ` [PATCH v5 4/7] ABI: Add sysfs documentation for leds-group-virtualcolor Jonathan Brophy
2025-12-30 11:52   ` Andriy Shevencho
2025-12-30  8:23 ` [PATCH v5 5/7] leds: Add driver " Jonathan Brophy
2025-12-30  8:23 ` [PATCH v5 6/7] leds: Add fwnode_led_get() for firmware-agnostic LED resolution Jonathan Brophy
2025-12-30 12:00   ` Andriy Shevencho
2025-12-31  2:30   ` kernel test robot
2025-12-31 23:37   ` kernel test robot
2025-12-31 23:45   ` kernel test robot
2026-01-02 12:20   ` kernel test robot
2026-01-02 15:07   ` kernel test robot
2026-01-02 16:29   ` kernel test robot
2025-12-30  8:23 ` [PATCH v5 7/7] leds: Add virtual LED group driver with priority arbitration Jonathan Brophy
2025-12-30 12:19   ` Andriy Shevencho [this message]
2026-01-03  8:22     ` [PATCH v5 7/7] leds: Add virtual LED group driver Jonathan Brophy
2026-01-03 12:56       ` Andriy Shevencho
2026-01-06 16:59 ` [PATCH v5 0/7] leds: Add virtual LED group driver with priority arbitration Rob Herring
2026-01-13 11:52   ` Lee Jones
2026-01-13 11:57 ` Lee Jones
2026-01-13 20:35   ` Jonathan Brophy
2026-01-15 15:07     ` Lee Jones
2026-01-15 16:58       ` Andriy Shevencho

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aVPDUVNX95Hv13VU@smile.fi.intel.com \
    --to=andriy.shevchenko@linux.intel.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=lee@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=pavel@kernel.org \
    --cc=professor_jonny@hotmail.com \
    --cc=professorjonny98@gmail.com \
    --cc=robh@kernel.org \
    --cc=rtsvetkov@gradotech.eu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.