Hi!
I wrote a simple daemon for controlling GPIOs via UNIX socket.
Currently only the output set/unset works. The program read a config
file and configuring the pins with libgpiod. The config file is in the
tools directory. I don't really know where should I drop and how to
configure libtool. The fork() is commented yet so the daemon writes
info to the terminal. With the command for ex.
echo "gpiochip1:PD21 set=1" | socat - UNIX-
CONNECT:/var/run/gpiodaemon.sock
you can set a configured out to high. If it is useful for the project I
can send more patch in the future with more feature. This is as you can
see a demo only. I tested with an Allwinner H6 SoC (OrangePi Lite2). I
am newbie in contributing so please fix me if required.
Regards,
André Kovács
From f18d2fbdff11798cd457ac75684f7dbff6812cad Mon Sep 17 00:00:00 2001
From: Andre Kovacs <info@xxxxxxxxxxxxxxx>
Date: Sat, 9 Nov 2019 19:14:13 +0100
Subject: [PATCH] gpiodaemon basics
The program read the config file and configure the IOs. Over a UNIX
socket it is possible to set the configured output(s). Some little
modifications was needed in the lib.
Signed-off-by: Andre Kovacs <info@xxxxxxxxxxxxxxx>
---
include/gpiod.h | 16 ++
lib/core.c | 5 +
lib/iter.c | 5 +
tools/Makefile.am | 4 +-
tools/daemonconfig | 2 +
tools/gpiodaemon.c | 466 +++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 497 insertions(+), 1 deletion(-)
create mode 100644 tools/daemonconfig
create mode 100644 tools/gpiodaemon.c
diff --git a/include/gpiod.h b/include/gpiod.h
index 9860ea8..ad231b9 100644
--- a/include/gpiod.h
+++ b/include/gpiod.h
@@ -659,6 +659,13 @@ enum {
};
/**
+ * @brief Get the line's chip name.
+ * @param line GPIO line object.
+ * @return Chip name.
+ */
+const char *gpiod_line_chip_name(struct gpiod_line *line) GPIOD_API;
+
+/**
* @brief Read the GPIO line offset.
* @param line GPIO line object.
* @return Line offset.
@@ -1321,6 +1328,15 @@ gpiod_chip_iter_next(struct gpiod_chip_iter *iter) GPIOD_API;
struct gpiod_chip *
gpiod_chip_iter_next_noclose(struct gpiod_chip_iter *iter) GPIOD_API;
+
+/**
+ * @brief Set offset to zero.
+ * @param iter The gpiochip iterator object.
+ */
+
+
+void gpiod_chip_iter_reset_offset(struct gpiod_chip_iter *iter) GPIOD_API;
+
/**
* @brief Iterate over all GPIO chips present in the system.
* @param iter An initialized GPIO chip iterator.
diff --git a/lib/core.c b/lib/core.c
index a04514e..fd4e17d 100644
--- a/lib/core.c
+++ b/lib/core.c
@@ -334,6 +334,11 @@ struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line)
return line->chip;
}
+const char *gpiod_line_chip_name(struct gpiod_line *line)
+{
+ return line->chip->name[0] == '\0' ? NULL : line->chip->name;
+}
+
unsigned int gpiod_line_offset(struct gpiod_line *line)
{
return line->offset;
diff --git a/lib/iter.c b/lib/iter.c
index a4d883a..afaf567 100644
--- a/lib/iter.c
+++ b/lib/iter.c
@@ -169,3 +169,8 @@ struct gpiod_line *gpiod_line_iter_next(struct gpiod_line_iter *iter)
return iter->offset < (iter->num_lines)
? iter->lines[iter->offset++] : NULL;
}
+
+void gpiod_chip_iter_reset_offset(struct gpiod_chip_iter *iter)
+{
+ iter->offset = 0;
+}
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 897ff32..bbc8950 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -14,7 +14,7 @@ libtools_common_la_SOURCES = tools-common.c tools-common.h
LDADD = libtools-common.la $(top_builddir)/lib/libgpiod.la
-bin_PROGRAMS = gpiodetect gpioinfo gpioget gpioset gpiomon gpiofind
+bin_PROGRAMS = gpiodetect gpioinfo gpioget gpioset gpiomon gpiofind gpiodaemon
gpiodetect_SOURCES = gpiodetect.c
@@ -28,6 +28,8 @@ gpiomon_SOURCES = gpiomon.c
gpiofind_SOURCES = gpiofind.c
+gpiodaemon_SOURCES = gpiodaemon.c
+
EXTRA_DIST = gpio-tools-test gpio-tools-test.bats
if WITH_TESTS
diff --git a/tools/daemonconfig b/tools/daemonconfig
new file mode 100644
index 0000000..7bbc00d
--- /dev/null
+++ b/tools/daemonconfig
@@ -0,0 +1,2 @@
+gpiochip1:PD21 direction=out,defval=0,consumer=asd;
+gpiochip1:118 direction=in,consumer=asd2;
diff --git a/tools/gpiodaemon.c b/tools/gpiodaemon.c
new file mode 100644
index 0000000..70d9a4f
--- /dev/null
+++ b/tools/gpiodaemon.c
@@ -0,0 +1,466 @@
+/*
+ * 2019 André Kovács <info@xxxxxxxxxxxxxxx>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <gpiod.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#define SOCK_PATH "/var/run/gpiodaemon.sock"
+#define MAX_CLIENTS 10
+
+#define CONSUMER_MAX 32
+
+volatile sig_atomic_t exit_flag = 0;
+
+void exit_program(int s)
+{
+ exit_flag = 1;
+}
+
+static int match_keyword(char *kw, char *opt,
+ char *consumer, int *val,
+ struct gpiod_line_request_config *line)
+{
+ int rv = 0;
+
+ if (!strcmp(kw, "direction")) {
+ if (!strcmp(opt, "in")) {
+ puts("directon set to input");
+ line->request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
+ }
+ else if (!strcmp(opt, "out")) {
+ puts("directon set to output");
+ line->request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
+ }
+ else {
+ fprintf(stderr, "Wrong option for \"%s\"\n", kw);
+ rv = 1;
+ }
+ }
+ else if (!strcmp(kw, "defval")) {
+ if (atoi(opt) == 0) {
+ puts("default value LOW");
+ *val = 0;
+ }
+ else {
+ puts("default value HIGH");
+ *val = 1;
+ }
+ }
+ else if (!strcmp(kw, "consumer")) {
+ printf("consumer is \"%s\"\n", opt);
+ strncpy(consumer, opt, CONSUMER_MAX);
+ }
+ else if (!strcmp(kw, "flags")) {
+ if (!strcmp(opt, "open_drain")) {
+ line->flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
+ puts("flag open drain");
+ }
+ else if (!strcmp(opt, "open_source")) {
+ line->flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
+ puts("flag open source");
+ }
+ else if (!strcmp(opt, "active_low")) {
+ line->flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+ puts("flag active low");
+ }
+ else {
+ fprintf(stderr, "Wrong option for \"%s\"\n", kw);
+ rv = 1;
+ }
+ }
+ else if (!strcmp(kw, "edge")) {
+ if (!strcmp(opt, "rising")) {
+ line->request_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
+ puts("rising edge");
+ }
+ else if (!strcmp(opt, "falling")) {
+ line->request_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
+ puts("falling edge");
+ }
+ else if (!strcmp(opt, "both")) {
+ line->request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
+ puts("both edges");
+ }
+ else {
+ fprintf(stderr, "Wrong option for \"%s\"\n", kw);
+ rv = 1;
+ }
+ }
+ else {
+ fprintf(stderr, "Wrong kw \"%s\"\n", kw);
+ rv = 1;
+ }
+
+ return rv;
+}
+
+static int set_gpio_line(const char *chip_name, int line_num, int defval,
+ struct gpiod_line_request_config *linecfg,
+ struct gpiod_chip_iter *iter,
+ struct gpiod_line_bulk *entries)
+{
+ int rv = -1, found = 0;
+ struct gpiod_chip *gpio_chip;
+
+ gpiod_foreach_chip_noclose(iter, gpio_chip) {
+ if (!strcmp(gpiod_chip_name(gpio_chip), chip_name)) {
+ found = 1;
+ break;
+ }
+ }
+ gpiod_chip_iter_reset_offset(iter);
+
+ if (found != 1) {
+ fprintf(stderr, "No chip found by name \"%s\"\n", chip_name);
+ goto out;
+ } else
+ printf("chip_name is \"%s\"\n", gpiod_chip_name(gpio_chip));
+
+ entries->lines[entries->num_lines] = gpiod_chip_get_line(gpio_chip, line_num);
+ if (entries->lines[entries->num_lines] == NULL) {
+ fprintf(stderr, "gpiod_chip_get_line(): %s\n", strerror(errno));
+ goto out;
+ }
+
+ if (!gpiod_line_is_free(entries->lines[entries->num_lines])) {
+ fprintf(stderr, "GPIO line (%s) is used\n",
+ gpiod_line_consumer(entries->lines[entries->num_lines]));
+ free(entries->lines[entries->num_lines]);
+ goto out;
+ }
+
+ if (gpiod_line_request(entries->lines[entries->num_lines], linecfg, defval) < 0) {
+ fprintf(stderr, "gpiod_line_request(): %s\n", strerror(errno));
+ free(entries->lines[entries->num_lines]);
+ goto out;
+ }
+
+ entries->num_lines += 1;
+ rv = 0;
+
+out:
+ return rv;
+}
+
+static int find_chip_name(char **lineptr, char **arri,
+ char *line, char *gpiochip, size_t len)
+{
+ while (**lineptr && **lineptr != ':')
+ (*lineptr)++;
+
+ if (**lineptr == '\0') {
+ fprintf(stderr, "Missing gpiochip\n");
+ return -1;
+ }
+
+ **lineptr = '\0';
+ (*lineptr)++;
+ *arri = *lineptr;
+ strncpy(gpiochip, line, len);
+
+ return 0;
+}
+
+static int find_pin(char **lineptr, char **arri, unsigned int *line_num)
+{
+ char pin[12];
+ int port = -1;
+
+ while (**lineptr && !isspace(**lineptr))
+ (*lineptr)++;
+
+ if (**lineptr == '\n') {
+ fprintf(stderr, "Missing pin\n");
+ return -1;
+ }
+
+ **lineptr = '\0';
+ (*lineptr)++;
+ strncpy(pin, *arri, sizeof(pin)-1);
+
+ if (sscanf(pin, "%d", line_num) != 1) {
+ for (int i = 'A'; i < 'Z'; i++) {
+ if (pin[1] == i) {
+ port = i - 65;
+ break;
+ }
+ }
+
+ if (port < 0) {
+ fprintf(stderr, "No valid pin found.");
+ return -1;
+ }
+ else
+ /* Line number calculation. This is the pin identifier in the GPIO block device */
+ *line_num = port * 32 + atoi(&pin[2]);
+ }
+
+ return 0;
+}
+
+static int parser(const char *config_file,
+ struct gpiod_chip_iter *iter,
+ struct gpiod_line_bulk *entries)
+{
+ FILE *fp;
+ char line[1024], property[20], keyword[20],
+ gpiochip[32], consumer_str[CONSUMER_MAX];
+ char *lineptr, *arri;
+ int defval = 0;
+ unsigned int pin;
+
+ struct gpiod_line_request_config config = {.flags = 0};
+
+ if ((fp = fopen(config_file, "r")) == NULL) {
+ perror("fopen()");
+ return -1;
+ }
+
+ bzero(property, sizeof(property));
+ bzero(consumer_str, sizeof(consumer_str));
+
+ config.consumer = consumer_str;
+
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ lineptr = line;
+ printf("%s\n", lineptr);
+
+ if (*lineptr == '#')
+ continue;
+
+ if (line[strlen(line)-2] != ';') {
+ fprintf(stderr, "%s\n", "Missing semicolon or newline");
+ return -1;
+ }
+
+ if (find_chip_name(&lineptr, &arri, line, gpiochip, sizeof(gpiochip)))
+ return -1;
+
+ if (find_pin(&lineptr, &arri, &pin))
+ return -1;
+
+ /* find keyword */
+ arri = property;
+ while (*lineptr && *lineptr != '\n') {
+ if (*lineptr == ',' || *lineptr == ';') {
+ match_keyword(keyword, property,
+ consumer_str, &defval, &config);
+
+ bzero(property, sizeof(property));
+ arri = property;
+ }
+ else if (*lineptr == '=') {
+ strncpy(keyword, property, sizeof(keyword));
+ bzero(property, sizeof(property));
+ arri = property;
+ }
+ else if (isspace(*lineptr)) {}
+ else {
+ *arri = *lineptr;
+ arri++;
+ }
+ lineptr++;
+ }
+
+ if (set_gpio_line(gpiochip, pin, defval, &config, iter, entries) < 0) {
+ fprintf(stderr, "Skip line: %d\n", pin);
+ }
+ config.flags = 0;
+ config.request_type = 0;
+ }
+
+ fclose(fp);
+
+ return 0;
+}
+
+static int processing_client_req(char *message, struct gpiod_line_bulk *entries)
+{
+ char chipname[32];
+ char *ptr, *start;
+ const char *lines_chipname;
+ int value = 0;
+ unsigned int line_num, offset;
+
+ start = ptr = message;
+
+ if (find_chip_name(&ptr, &start, message, chipname, sizeof(chipname)))
+ return -1;
+ printf("chip: %s\n", chipname);
+
+ if (find_pin(&ptr, &start, &line_num))
+ return -1;
+ printf("line: %d\n", line_num);
+
+ if (!strcmp(ptr, "set=1\n"))
+ value = 1;
+ else if (!strcmp(ptr, "set=0\n"))
+ value = 0;
+ else {
+ fprintf(stderr, "Wrong property\n");
+ return -1;
+ }
+
+ for (size_t i = 0; i <= entries->num_lines; i++) {
+ offset = gpiod_line_offset(entries->lines[i]);
+ lines_chipname = gpiod_line_chip_name(entries->lines[i]);
+
+ if (!strcmp(lines_chipname, chipname) && offset == line_num) {
+ gpiod_line_set_value(entries->lines[i], value);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, const char *argv[])
+{
+ int client_socket[MAX_CLIENTS], master_socket,
+ new_socket, sd, max_sd, srv, len;
+ socklen_t t;
+ fd_set rdfs;
+ struct sockaddr_un local, remote;
+
+ struct gpiod_chip *gpio_chip;
+ struct gpiod_chip_iter *iter;
+ struct gpiod_line_bulk entries = { .num_lines = 0 };
+
+ struct sigaction sa_exit;
+
+ argc -= optind;
+ if (argc != 1) {
+ fprintf(stderr, "Usage: %s <config path>\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ sa_exit.sa_handler = exit_program;
+ sa_exit.sa_flags = 0;
+ sigemptyset(&sa_exit.sa_mask);
+ if (sigaction(SIGINT, &sa_exit, NULL) < 0) {
+ perror("sigaction()");
+ exit(EXIT_FAILURE);
+ }
+
+ exit_flag = 0;
+
+ iter = gpiod_chip_iter_new();
+ if (!iter) {
+ fprintf(stderr, "%s\n", "unable to access GPIO chips");
+ exit(EXIT_FAILURE);
+ }
+
+ if (parser(argv[1], iter, &entries) < 0)
+ goto out;
+
+ /* Init socket */
+ for (size_t i = 0; i < MAX_CLIENTS; i++)
+ client_socket[i] = 0;
+
+ if ((master_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ perror("socket()");
+ goto out;
+ }
+
+ local.sun_family = AF_UNIX;
+ strcpy(local.sun_path, SOCK_PATH);
+ unlink(local.sun_path);
+ len = strlen(local.sun_path) + sizeof(local.sun_family);
+ if (bind(master_socket, (struct sockaddr *)&local, len) == -1) {
+ perror("bind()");
+ goto out;
+ }
+
+ if (listen(master_socket, 5) == -1) {
+ perror("listen()");
+ goto out;
+ }
+
+ t = sizeof(remote);
+
+/*
+ if (daemon(0,0) < 0)
+ fprintf(stderr, "daemon(): %s\n", strerror(errno));
+*/
+ while (!exit_flag) {
+ FD_ZERO(&rdfs);
+
+ FD_SET(master_socket, &rdfs);
+ max_sd = master_socket;
+
+ for (size_t i = 0; i < MAX_CLIENTS; i++) {
+ sd = client_socket[i];
+ if(sd > 0)
+ FD_SET(sd , &rdfs);
+
+ if(sd > max_sd)
+ max_sd = sd;
+ }
+
+ srv = select(max_sd + 1 , &rdfs , NULL , NULL , NULL);
+
+ if (srv < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("select()");
+ }
+ /* New client */
+ if (FD_ISSET(master_socket, &rdfs)) {
+ if ((new_socket = accept(master_socket,
+ (struct sockaddr *)&remote, (socklen_t*)&t)) < 0) {
+ perror("accept");
+ }
+
+ for (size_t i = 0; i < MAX_CLIENTS; i++) {
+ if(client_socket[i] == 0) {
+ client_socket[i] = new_socket;
+ break;
+ }
+ }
+ }
+ /* Incoming data from client */
+ else {
+ for (size_t i = 0; i < MAX_CLIENTS; i++) {
+ sd = client_socket[i];
+ char buffer[128];
+ bzero(buffer, sizeof(buffer));
+
+ if (FD_ISSET(sd, &rdfs)) {
+ /* Somebody disconnected */
+ if (read(sd, buffer, 127) == 0) {
+ close(sd);
+ client_socket[i] = 0;
+ }
+ else {
+ printf("%s", buffer);
+ processing_client_req(buffer, &entries);
+ }
+ }
+ }
+ }
+ }
+
+ for (size_t i = 0; i < MAX_CLIENTS; i++) {
+ if (client_socket[i] != 0)
+ close(client_socket[i]);
+ }
+ close(master_socket);
+
+
+out:
+ unlink(local.sun_path);
+ /* Creepy free but need */
+ gpiod_foreach_chip(iter, gpio_chip) {}
+ gpiod_chip_iter_free(iter);
+
+ return 0;
+}
--
2.11.0