console-server: Add UART Mux Support
This commit adds support for uart-muxes which can be controlled via
gpios.
Change-Id: I91a4de1962554adf4302a2a59d2b371f492dc21d
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
Signed-off-by: Andrew Jeffery <andrew@codeconstruct.com.au>
diff --git a/console-dbus.c b/console-dbus.c
index f6749e1..778f1cd 100644
--- a/console-dbus.c
+++ b/console-dbus.c
@@ -20,8 +20,9 @@
#include <string.h>
#include <sys/socket.h>
-#include "console-server.h"
#include "config.h"
+#include "console-mux.h"
+#include "console-server.h"
/* size of the dbus object path length */
const size_t dbus_obj_path_len = 1024;
@@ -127,6 +128,8 @@
return sd_bus_reply_method_error(msg, err);
}
+ console_mux_activate(console);
+
/* Register the consumer. */
socket_fd = dbus_create_socket_consumer(console);
if (socket_fd < 0) {
diff --git a/console-mux.c b/console-mux.c
new file mode 100644
index 0000000..105d968
--- /dev/null
+++ b/console-mux.c
@@ -0,0 +1,320 @@
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <gpiod.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "console-server.h"
+#include "console-mux.h"
+#include "config.h"
+
+struct console_gpio {
+ char *name;
+ struct gpiod_line *line;
+};
+
+struct console_mux {
+ struct console_gpio *mux_gpios;
+ size_t n_mux_gpios;
+};
+
+static const char *key_mux_gpios = "mux-gpios";
+static const char *key_mux_index = "mux-index";
+
+__attribute__((nonnull)) static size_t strtokcnt(const char *str,
+ const char sep)
+{
+ ssize_t n = 1;
+
+ while (*str) {
+ if (*str == sep) {
+ n++;
+ }
+ str++;
+ }
+
+ return n;
+}
+
+__attribute__((nonnull)) static size_t
+count_mux_gpios(const char *config_mux_gpios)
+{
+ return strtokcnt(config_mux_gpios, ',');
+}
+
+__attribute__((nonnull)) static char *
+extract_mux_gpio_name(const char **config_gpio_names)
+{
+ const char *current;
+ const char *comma;
+ ptrdiff_t length;
+
+ assert(*config_gpio_names);
+ current = *config_gpio_names;
+ comma = strchrnul(current, ',');
+ length = comma - current;
+
+ if (length == 0) {
+ return NULL;
+ }
+
+ char *word = calloc(length + 1, 1);
+ if (!word) {
+ return NULL;
+ }
+
+ strncpy(word, current, length);
+
+ *config_gpio_names = comma + !!(*comma);
+
+ return word;
+}
+
+__attribute__((nonnull)) static struct console_gpio *
+console_mux_find_gpio_by_index(struct console_gpio *gpio,
+ const char **config_gpio_names)
+{
+ assert(*config_gpio_names);
+
+ gpio->name = extract_mux_gpio_name(config_gpio_names);
+ if (gpio->name == NULL) {
+ warnx("could not extract mux gpio name from config '%s'",
+ *config_gpio_names);
+ return NULL;
+ }
+
+ gpio->line = gpiod_line_find(gpio->name);
+ if (gpio->line == NULL) {
+ warnx("libgpiod: could not find line %s", gpio->name);
+ free(gpio->name);
+ return NULL;
+ }
+
+ return gpio;
+}
+
+__attribute__((nonnull)) static void
+console_mux_release_gpio_lines(struct console_server *server)
+{
+ for (unsigned long i = 0; i < server->mux->n_mux_gpios; i++) {
+ struct console_gpio *gpio = &server->mux->mux_gpios[i];
+ gpiod_line_release(gpio->line);
+ gpiod_line_close_chip(gpio->line);
+
+ free(gpio->name);
+ gpio->name = NULL;
+ }
+}
+
+__attribute__((nonnull)) static int
+console_mux_request_gpio_lines(struct console_server *server,
+ const char *config_gpio_names)
+{
+ const char *current = config_gpio_names;
+ struct console_gpio *gpio;
+ int status = 0;
+
+ for (server->mux->n_mux_gpios = 0; *current;
+ server->mux->n_mux_gpios++) {
+ size_t i = server->mux->n_mux_gpios;
+ gpio = console_mux_find_gpio_by_index(
+ &server->mux->mux_gpios[i], ¤t);
+ if (gpio == NULL) {
+ console_mux_release_gpio_lines(server);
+ return -1;
+ }
+
+ status = gpiod_line_request_output(
+ gpio->line, program_invocation_short_name, 0);
+ if (status != 0) {
+ warnx("could not set line %s as output", gpio->name);
+ warnx("releasing all lines already requested");
+ console_mux_release_gpio_lines(server);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int console_server_mux_init(struct console_server *server)
+{
+ const char *config_gpio_names;
+ size_t max_ngpios;
+ size_t ngpios;
+
+ config_gpio_names = config_get_value(server->config, key_mux_gpios);
+ if (!config_gpio_names) {
+ return 0;
+ }
+
+ ngpios = count_mux_gpios(config_gpio_names);
+ max_ngpios = sizeof(((struct console *)0)->mux_index) * CHAR_BIT;
+ if (ngpios > max_ngpios) {
+ return -1;
+ }
+
+ server->mux = calloc(1, sizeof(struct console_mux));
+ if (!server->mux) {
+ return -1;
+ }
+
+ server->mux->n_mux_gpios = 0;
+ server->mux->mux_gpios = calloc(ngpios, sizeof(struct console_gpio));
+ if (!server->mux->mux_gpios) {
+ return -1;
+ }
+
+ return console_mux_request_gpio_lines(server, config_gpio_names);
+}
+
+void console_server_mux_fini(struct console_server *server)
+{
+ if (!server->mux) {
+ return;
+ }
+
+ console_mux_release_gpio_lines(server);
+
+ free(server->mux->mux_gpios);
+ server->mux->mux_gpios = NULL;
+
+ free(server->mux);
+ server->mux = NULL;
+}
+
+int console_mux_init(struct console *console, struct config *config)
+{
+ if (!console->server->mux) {
+ return 0;
+ }
+
+ if (console->server->mux->n_mux_gpios == 0) {
+ return 0;
+ }
+
+ const char *gpio_value = config_get_section_value(
+ config, console->console_id, key_mux_index);
+
+ if (gpio_value == NULL) {
+ warnx("console %s does not have property %s in config",
+ console->console_id, key_mux_index);
+ return -1;
+ }
+
+ errno = 0;
+ console->mux_index = strtoul(gpio_value, NULL, 0);
+ if (errno == ERANGE) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int console_timestamp(char *buffer, size_t size)
+{
+ size_t status;
+ time_t rawtime;
+ struct tm *timeinfo;
+
+ time(&rawtime);
+ timeinfo = gmtime(&rawtime);
+
+ status = strftime(buffer, size, "%Y-%m-%d %H:%M:%S UTC", timeinfo);
+ return !status;
+}
+
+static int console_print_timestamped(struct console *console,
+ const char *message)
+{
+#define TIMESTAMP_MAX_SIZE 32
+ char buf_timestamp[TIMESTAMP_MAX_SIZE];
+ int status;
+ char *buf;
+
+ status = console_timestamp(buf_timestamp, sizeof(buf_timestamp));
+ if (status != 0) {
+ warnx("Error: unable to print timestamp");
+ return status;
+ }
+
+ status = asprintf(&buf, "[obmc-console] %s %s\n", buf_timestamp,
+ message);
+ if (status == -1) {
+ return -1;
+ }
+
+ ringbuffer_queue(console->rb, (uint8_t *)buf, strlen(buf));
+
+ free(buf);
+
+ return 0;
+}
+
+static int console_mux_set_lines(struct console *console)
+{
+ int status = 0;
+
+ for (size_t i = 0; i < console->server->mux->n_mux_gpios; i++) {
+ struct console_gpio *gpio = &console->server->mux->mux_gpios[i];
+ const uint8_t value = (console->mux_index >> i) & 0x1;
+
+ status = gpiod_line_set_value(gpio->line, value);
+ if (status != 0) {
+ warnx("could not set line %s", gpio->name);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int console_mux_activate(struct console *console)
+{
+ struct console_server *server = console->server;
+ const bool first_activation = server->active == NULL;
+ const bool is_active = server->active == console;
+ int status = 0;
+
+ if (is_active) {
+ return 0;
+ }
+
+ if (server->mux) {
+ status = console_mux_set_lines(console);
+ }
+
+ if (status != 0) {
+ warnx("Error: unable to set mux gpios");
+ return status;
+ }
+
+ server->active = console;
+
+ /* Don't print disconnect/connect events on startup */
+ if (first_activation) {
+ return 0;
+ }
+
+ for (size_t i = 0; i < server->n_consoles; i++) {
+ struct console *other = server->consoles[i];
+ if (other == console) {
+ continue;
+ }
+ console_print_timestamped(other, "DISCONNECTED");
+
+ for (long j = 0; j < other->n_handlers; j++) {
+ struct handler *h = other->handlers[j];
+
+ if (h->type->deselect) {
+ h->type->deselect(h);
+ }
+ }
+ }
+
+ console_print_timestamped(console, "CONNECTED");
+
+ return 0;
+}
diff --git a/console-mux.h b/console-mux.h
new file mode 100644
index 0000000..7b2942a
--- /dev/null
+++ b/console-mux.h
@@ -0,0 +1,26 @@
+/**
+ * Copyright © 2024 9elements
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+struct config;
+struct console;
+struct console_server;
+
+int console_server_mux_init(struct console_server *server);
+void console_server_mux_fini(struct console_server *server);
+int console_mux_init(struct console *console, struct config *config);
+int console_mux_activate(struct console *console);
diff --git a/console-server.c b/console-server.c
index 870e5a7..60d2d2e 100644
--- a/console-server.c
+++ b/console-server.c
@@ -38,6 +38,8 @@
#include <sys/socket.h>
#include <poll.h>
+#include "console-mux.h"
+
#include "console-server.h"
#include "config.h"
@@ -1075,6 +1077,12 @@
goto cleanup_console;
}
+ rc = console_mux_init(console, config);
+ if (rc) {
+ warnx("could not set mux gpios from config, exiting");
+ goto cleanup_rb;
+ }
+
if (set_socket_info(console, config, console_id)) {
warnx("set_socket_info failed");
goto cleanup_rb;
@@ -1210,6 +1218,11 @@
return -1;
}
+ rc = console_server_mux_init(server);
+ if (rc != 0) {
+ return -1;
+ }
+
uart_routing_init(server->config);
rc = tty_init(server, server->config, config_tty_kname);
@@ -1224,8 +1237,14 @@
return -1;
}
- server->active = console_server_add_consoles(server, console_id);
- if (server->active == NULL) {
+ struct console *initial_active =
+ console_server_add_consoles(server, console_id);
+ if (initial_active == NULL) {
+ return -1;
+ }
+
+ rc = console_mux_activate(initial_active);
+ if (rc != 0) {
return -1;
}
@@ -1242,6 +1261,7 @@
dbus_server_fini(server);
tty_fini(server);
free(server->pollfds);
+ console_server_mux_fini(server);
config_fini(server->config);
}
diff --git a/console-server.h b/console-server.h
index 7c2e711..0636dc7 100644
--- a/console-server.h
+++ b/console-server.h
@@ -52,6 +52,7 @@
struct console *console, struct config *config);
void (*fini)(struct handler *handler);
int (*baudrate)(struct handler *handler, speed_t baudrate);
+ void (*deselect)(struct handler *handler);
};
struct handler {
@@ -136,6 +137,9 @@
size_t dbus_pollfd_index;
struct sd_bus *bus;
+
+ // may be NULL in case there is no mux
+ struct console_mux *mux;
};
struct console {
@@ -156,6 +160,9 @@
struct poller **pollers;
long n_pollers;
+
+ // values to configure the mux
+ unsigned long mux_index;
};
/* poller API */
diff --git a/log-handler.c b/log-handler.c
index 732fe2e..79b0f99 100644
--- a/log-handler.c
+++ b/log-handler.c
@@ -180,7 +180,13 @@
}
lh->maxsize = logsize <= lh->pagesize ? lh->pagesize + 1 : logsize;
- filename = config_get_value(config, "logfile");
+ filename = config_get_section_value(config, console->console_id,
+ "logfile");
+
+ if (!filename && console->server->n_consoles == 1) {
+ filename = config_get_value(config, "logfile");
+ }
+
if (!filename) {
filename = default_filename;
}
diff --git a/meson.build b/meson.build
index c0a600d..27ac968 100644
--- a/meson.build
+++ b/meson.build
@@ -59,6 +59,7 @@
'console-dbus.c',
'console-server.c',
'console-socket.c',
+ 'console-mux.c',
'log-handler.c',
'ringbuffer.c',
'socket-handler.c',
@@ -71,6 +72,7 @@
dependencies: [
dependency('libsystemd'),
iniparser_dep,
+ dependency('libgpiod'),
meson.get_compiler('c').find_library('rt')
],
install_dir: get_option('sbindir'),
diff --git a/socket-handler.c b/socket-handler.c
index 6268fca..efcd5b8 100644
--- a/socket-handler.c
+++ b/socket-handler.c
@@ -30,6 +30,7 @@
#include <sys/un.h>
#include <systemd/sd-daemon.h>
+#include "console-mux.h"
#include "console-server.h"
#define SOCKET_HANDLER_PKT_SIZE 512
@@ -321,6 +322,8 @@
return POLLER_OK;
}
+ console_mux_activate(sh->console);
+
client = malloc(sizeof(*client));
memset(client, 0, sizeof(*client));
@@ -496,6 +499,17 @@
return NULL;
}
+static void socket_deselect(struct handler *handler)
+{
+ struct socket_handler *sh = to_socket_handler(handler);
+
+ while (sh->n_clients) {
+ struct client *c = sh->clients[0];
+ client_drain_queue(c, 0);
+ client_close(c);
+ }
+}
+
static void socket_fini(struct handler *handler)
{
struct socket_handler *sh = to_socket_handler(handler);
@@ -515,6 +529,7 @@
static const struct handler_type socket_handler = {
.name = "socket",
.init = socket_init,
+ .deselect = socket_deselect,
.fini = socket_fini,
};
diff --git a/subprojects/libgpiod.wrap b/subprojects/libgpiod.wrap
new file mode 100644
index 0000000..3a92657
--- /dev/null
+++ b/subprojects/libgpiod.wrap
@@ -0,0 +1,14 @@
+[wrap-file]
+directory = libgpiod-1.6.5
+source_url = https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/snapshot/libgpiod-1.6.5.tar.gz
+source_filename = libgpiod-1.6.5.tar.gz
+source_hash = 1473d3035b506065393a4569763cf6b5c98e59c8f865326374ebadffa2578f3a
+patch_filename = libgpiod_1.6.5-1_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/libgpiod_1.6.5-1/get_patch
+patch_hash = a8ee005ecaeaf9d4382d196a1b82e808e72b750f6b8cf61b84f7ccee134b8d4f
+source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/libgpiod_1.6.5-1/libgpiod-1.6.5.tar.gz
+wrapdb_version = 1.6.5-1
+
+[provide]
+libgpiod = gpiod_dep
+libgpiodcxx = gpiodcxx_dep