config: Add support for aspeed-uart-routing config directive

It accepts one or more words of the form SINK:SOURCE, where SOURCE is
written to the file SINK in the aspeed-uart-routing driver's sysfs
directory (they are thus expected to be things like "uart1", "uart2",
"io1", etc.).

Signed-off-by: Zev Weiss <zev@bewilderbeest.net>
Change-Id: Iacbc524340e4b73f3d122bc77670eedb3957a858
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c0a0419..3edefa1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,10 @@
 
 ## [Unreleased]
 
+### Added
+
+1. config: Added support for the `aspeed-uart-routing` configuration key
+
 ### Removed
 
 1. Deprecated D-Bus interface `xyz.openbmc_project.console` is no longer used.
diff --git a/console-server.c b/console-server.c
index 4f894a4..5715589 100644
--- a/console-server.c
+++ b/console-server.c
@@ -28,6 +28,7 @@
 #include <err.h>
 #include <string.h>
 #include <getopt.h>
+#include <glob.h>
 #include <limits.h>
 #include <time.h>
 #include <termios.h>
@@ -386,6 +387,112 @@
 	free(console->tty.dev);
 }
 
+static int write_to_path(const char *path, const char *data)
+{
+	int rc = 0;
+	FILE *f = fopen(path, "w");
+	if (!f) {
+		return -1;
+	}
+
+	if (fprintf(f, "%s", data) < 0) {
+		rc = -1;
+	}
+
+	if (fclose(f)) {
+		rc = -1;
+	}
+
+	return rc;
+}
+
+#define ASPEED_UART_ROUTING_PATTERN                                            \
+	"/sys/bus/platform/drivers/aspeed-uart-routing/*.uart-routing"
+
+static void uart_routing_init(struct config *config)
+{
+	const char *muxcfg;
+	const char *p;
+	size_t buflen;
+	char *sink;
+	char *source;
+	char *muxdir;
+	char *path;
+	glob_t globbuf;
+
+	muxcfg = config_get_value(config, "aspeed-uart-routing");
+	if (!muxcfg) {
+		return;
+	}
+
+	/* Find the driver's sysfs directory */
+	if (glob(ASPEED_UART_ROUTING_PATTERN, GLOB_ERR | GLOB_NOSORT, NULL,
+		 &globbuf) != 0) {
+		warn("Couldn't find uart-routing driver directory, cannot apply config");
+		return;
+	}
+	if (globbuf.gl_pathc != 1) {
+		warnx("Found %zd uart-routing driver directories, cannot apply config",
+		      globbuf.gl_pathc);
+		goto out_free_glob;
+	}
+	muxdir = globbuf.gl_pathv[0];
+
+	/*
+	 * Rather than faff about tracking a bunch of separate buffer sizes,
+	 * just use one (worst-case) size for all of them -- +2 for a trailing
+	 * NUL and a '/' separator to construct the sysfs file path.
+	 */
+	buflen = strlen(muxdir) + strlen(muxcfg) + 2;
+
+	sink = malloc(buflen);
+	source = malloc(buflen);
+	path = malloc(buflen);
+	if (!path || !sink || !source) {
+		warnx("Out of memory applying uart routing config");
+		goto out_free_bufs;
+	}
+
+	p = muxcfg;
+	while (*p) {
+		ssize_t bytes_scanned;
+
+		if (sscanf(p, " %[^:/ \t]:%[^: \t] %zn", sink, source,
+			   &bytes_scanned) != 2) {
+			warnx("Invalid syntax in aspeed uart config: '%s' not applied",
+			      p);
+			break;
+		}
+		p += bytes_scanned;
+
+		/*
+		 * Check that the sink name looks reasonable before proceeding
+		 * (there are other writable files in the same directory that
+		 * we shouldn't be touching, such as 'driver_override' and
+		 * 'uevent').
+		 */
+		if (strncmp(sink, "io", strlen("io")) != 0 &&
+		    strncmp(sink, "uart", strlen("uart")) != 0) {
+			warnx("Skipping invalid uart routing name '%s' (must be ioN or uartN)",
+			      sink);
+			continue;
+		}
+
+		snprintf(path, buflen, "%s/%s", muxdir, sink);
+		if (write_to_path(path, source)) {
+			warn("Failed to apply uart-routing config '%s:%s'",
+			     sink, source);
+		}
+	}
+
+out_free_bufs:
+	free(path);
+	free(source);
+	free(sink);
+out_free_glob:
+	globfree(&globbuf);
+}
+
 int console_data_out(struct console *console, const uint8_t *data, size_t len)
 {
 	return write_buf_to_fd(console->tty.fd, data, len);
@@ -835,6 +942,8 @@
 		goto out_config_fini;
 	}
 
+	uart_routing_init(config);
+
 	rc = tty_init(console, config, config_tty_kname);
 	if (rc) {
 		goto out_config_fini;