diff mbox series

[libgpiod] daemon for controlling GPIOs

Message ID 1573334985.32589.1.camel@localhost
State New
Headers show
Series [libgpiod] daemon for controlling GPIOs | expand

Commit Message

info@kovacsandre.com Nov. 9, 2019, 9:29 p.m. UTC
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
diff mbox series

Patch

From f18d2fbdff11798cd457ac75684f7dbff6812cad Mon Sep 17 00:00:00 2001
From: Andre Kovacs <info@kovacsandre.com>
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@kovacsandre.com>
---
 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@kovacsandre.com>
+ */
+#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