Files
feedbackd/src/fbd-ledctrl.c
T
Luca Weiss e16202eeb5 fbd-dev-leds: support for multicolor LEDs
Add support for multicolor LEDs as supported by the kernel.
Used by Qualcomm devices such as SHIFT 6mq, OnePlus 6, etc.

Documentation:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/ABI/testing/sysfs-class-led-multicolor

Co-Developed-by: Dylan Van Assche <me@dylanvanassche.be>
2022-05-17 20:04:23 +02:00

192 lines
5.0 KiB
C

/*
* Copyright (C) 2020 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
* Author: Guido Günther <agx@sigxcpu.org>
*/
#define G_LOG_DOMAIN "fbd-ledctrl"
#include <glib.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define LED_BRIGHTNESS_ATTR "brightness"
#define LED_MULTI_INTENSITY_ATTR "multi_intensity"
#define LED_PATTERN_ATTR "pattern"
#define LED_REPEAT_ATTR "repeat"
#define LED_TRIGGER_ATTR "trigger"
#define LED_TRIGGER_PATTERN "pattern"
enum {
FBD_LEDCTRL_ERR_CMDLINE = 1,
FBD_LEDCTRL_ERR_TRIGGER = 2,
FBD_LEDCTRL_ERR_PERMS = 3,
};
static gboolean
write_sysfs_attr (const char *sysfs_path, const char *attr, const char *val)
{
gint fd;
int len;
g_autofree gchar *path = g_strjoin ("/", sysfs_path, attr, NULL);
fd = open (path, O_WRONLY|O_TRUNC, 0666);
if (fd < 0) {
fprintf (stderr, "Failed to open %s: %s\n", path, strerror (errno));
return FALSE;
}
len = strlen (val);
if (write (fd, val, len) < 0) {
fprintf (stderr, "Failed to write %s to %s: %s\n", val, path, strerror (errno));
close (fd);
return FALSE;
}
if (close (fd) < 0) {
fprintf (stderr, "Failed to close %s: %s\n", path, strerror (errno));
return FALSE;
}
return TRUE;
}
static gchar *
read_sysfs_attr (const char *sysfs_path, const char *attr)
{
gchar *content = NULL;
g_autoptr (GError) err = NULL;
g_autofree gchar *path = g_strjoin ("/", sysfs_path, attr, NULL);
if (!g_file_get_contents (path, &content, NULL, &err)) {
fprintf (stderr, "Failed to read %s: %s\n", path, err->message);
return NULL;
}
return content;
}
static gboolean
set_sysfs_attr_perm (const char *sysfs_path, const char *attr, gid_t gid)
{
g_autofree gchar *path = g_strjoin ("/", sysfs_path, attr, NULL);
if (chown(path, -1, gid) < 0) {
fprintf (stderr, "Failed to set perms of %s to %d: %s\n", path, gid, strerror (errno));
return FALSE;
}
if (chmod(path, 0664) < 0) {
fprintf (stderr, "Failed to set mode of %s: %s\n", path, strerror (errno));
return FALSE;
}
return TRUE;
}
static gboolean
set_trigger (const char *sysfs_path, const char *trigger)
{
g_autofree gchar *val = NULL;
g_auto(GStrv) triggers = NULL;
/*
* Check the current trigger, don't do anything if it's already
* set. This prevents a change event storm when setting the
* same trigger over and over again in a udev rule.
*/
val = read_sysfs_attr (sysfs_path, LED_TRIGGER_ATTR);
g_strstrip(val);
triggers = g_strsplit (val, " ", 0);
/*
* The active trigger is marked with [...] so if we don't find the
* trigger verbatim it's already the active one:
*/
if (!g_strv_contains ((const gchar * const *)triggers, trigger))
return TRUE;
return write_sysfs_attr (sysfs_path, LED_TRIGGER_ATTR, trigger);
}
static gboolean
set_perms (const char *sysfs_path, const char *trigger, const char *gname)
{
struct group *group;
gboolean success = TRUE;
group = getgrnam (gname);
if (!set_sysfs_attr_perm (sysfs_path, LED_BRIGHTNESS_ATTR, group->gr_gid))
return FALSE;
// Attribute is optional
set_sysfs_attr_perm (sysfs_path, LED_MULTI_INTENSITY_ATTR, group->gr_gid);
if (!g_strcmp0 (trigger, LED_TRIGGER_PATTERN)) {
if (!set_sysfs_attr_perm (sysfs_path, LED_PATTERN_ATTR, group->gr_gid))
success = FALSE;
if (!set_sysfs_attr_perm (sysfs_path, LED_REPEAT_ATTR, group->gr_gid))
success = FALSE;
}
/* specific setup for other triggers goes here */
return success;
}
int main(int argc, char *argv[])
{
g_autoptr(GError) err = NULL;
g_autoptr(GOptionContext) opt_context = NULL;
g_autofree gchar *sysfs_path = NULL;
g_autofree gchar *group = NULL;
g_autofree gchar *trigger = NULL;
const GOptionEntry options [] = {
{"path", 'p', 0, G_OPTION_ARG_STRING, &sysfs_path,
"Path to LEDs sysfs dir", NULL},
{"group", 'G', 0, G_OPTION_ARG_STRING, &group,
"Group to set permissions to", NULL},
{"trigger", 't', 0, G_OPTION_ARG_STRING, &trigger,
"LED trigger to configure", NULL},
{ NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
};
opt_context = g_option_context_new ("- helper to prepare sysfs for feedbackd");
g_option_context_add_main_entries (opt_context, options, NULL);
if (!g_option_context_parse (opt_context, &argc, &argv, &err)) {
fprintf (stderr, "%s", err->message);
g_clear_error (&err);
return 1;
}
if (!sysfs_path) {
fprintf (stderr, "No sysfs path given\n");
return FBD_LEDCTRL_ERR_CMDLINE;
}
if (!trigger) {
fprintf (stderr, "No trigger specified\n");
return FBD_LEDCTRL_ERR_CMDLINE;;
}
g_debug ("Configuring LED at %s for trigger %s", sysfs_path, trigger);
if (!set_trigger (sysfs_path, trigger))
return FBD_LEDCTRL_ERR_TRIGGER;
if (group) {
g_debug ("Setting permission of %s to %s", sysfs_path, group);
if (!set_perms (sysfs_path, trigger, group))
return FBD_LEDCTRL_ERR_PERMS;
}
return 0;
}