The shortcomings of the Linux LEDs API

In a recent post I mentioned that the Linux kernel has a dedicated API for LEDs. This API is composed of the drivers/leds/ directory and the additional <linux/leds.h> include file, Documentation exists in form of the Documentation/leds-class.txt file. To quote:

“The underlying design philosophy is simplicity. LEDs are simple devices and the aim is to keep a small amount of code giving as much functionality as possible.  Please keep this in mind when suggesting enhancements.”

While this goal appears supportable, the result leaves room for discussion. The API introduces the notion of LED drivers and LED triggers:

  • LED drivers implement the hardware access to drive a LED and register themselves with the LEDs API, which results in the creation of subdirectories beneath /sys/class/leds for every LED registered. Via the brightness file in these subdirectories, the appropriate LED could be set to different brightness levels, ie. not just turned on and off but also dimmed. However, for some reason, the data type used for passing the brightness level, enum led_brightness defines only the levels LED_OFF, LED_HALF and LED_FULL.
  • LED triggers are kernel-space routines that enable or disable a LED (or regulate its brightness, for what it’s worth) based on certain decisions, eg. events. The LEDs API distinguishes between simple and complex triggers: whereas a simple trigger is designed to be free from configuration and be called from other kernel subsystems, complex triggers support per-LED configuration through additional sysfs files.

So while a LED can be controlled through user-space programs and scripts through its sysfs brightness file, the concept of LED triggers allows for in-kernel driving of LEDs based on certain events. For example, ledtrig-ide-disk is a simple trigger that provides a function that is called from the IDE subsystem whenever hard disk/CD-ROM etc. access is done. ledtrig-ide-disk then turns a LED on for the time of the access, similar to the LED in ordinary PC cases that is driven by hardware on PC mainboards. You might ask why one should implement in software what is already provided by hardware, but keep in mind that this is just an example trigger and some platforms, eg. in embedded scenarios, don’t provide such a LED.

LED drivers and LED triggers are associated by means of the sysfs triggers file: writing the name of a LED trigger into a LED’s triggers file activates the trigger for this LED and may eventually cause additional sysfs files appear for configuration purposes. For example, the ledtrig-netdev trigger which visualizes network activity requires the specification of the network interface that is to be watched. Thus, complex triggers such as this one can drive multiple LEDs (but need to be assigned to each LED separately).

A LED trigger can choose whatever logic it deems necessary to determine the conditions under which a LED’s status should change. It can use timers, explicit function calls from other subsystems etc. However, the only (official) interface to a LED driver is a function call to set the LED color to one of the three defined resp. documented brightness levels. A LED trigger can thus for one thing not determine the current brightness level and for another only signal three different conditions.

Why is this a shortcoming? Think of my tri-state LEDs: these support three different colors besides the off state. How should I map these four states to the three led_brightness states? Also, each tri-state LED is controlled by two pins. Should I export each pin as a seperate LED controlled by a trigger separately and “hope” that a certain combination of events triggers the desired color?

The documentation reads with regard to this:


“LED Device Naming
=================
Is currently of the form:
“devicename:colour:function”

There have been calls for LED properties such as colour to be exported as individual led class attributes. As a solution which doesn’t incur as much overhead, I suggest these become part of the device name. The naming scheme above leaves scope for further attributes should they be needed. If sections of the name don’t apply, just leave that section blank.”

Does this mean I should have a LED driver register separately for each color? This introduces problems with the semantics as well as each trigger sees the different colors as a single LED again. Also, consider a LED is green, because the system has booted. Now an event occurs, causing the LED to be set to yellow for the duration of the event. How does the LED get green again?

Also: what if I want blinking? For a simple LED, blinking means turning it on and off again, true. But for a LED that supports different colors, blinking means restoring the previous color.

With my EPIA MII project, I have to make design decisions to fit my requirements along with the restrictions of the existing API (since I don’t want to extend it manually). My solution: since I had to write a custom driver for my parallel port-connected LEDs anyway, I could design a mapping between sysfs/triggers-visible “LEDs” and really present LEDs freely. For example, as explained, my first LED is a “SYS” LED, indicating both system boot and RAID array status. With the LEDs API, I had to export a boot LED and a raidstate LED. While boot can be set to LED_OFF (still booting) or LED_FULL (system has booted), raidstate can be set to LED_OFF, LED_HALF or LED_FULL, depending on how much of an array is present — a healthy array is represented by LED_FULL. My leds-epia driver does a custom mapping of the combinations that can be set using these two pseudo LEDs onto the actual LED’s color.

In general, with the current API the question arises rather often where to place code like this. The best choice in my opinion is to provide a custom LED driver, whenever possible, and keep the LED triggers as generically usable as possible.

Leave a comment