Introduce pollers

Handlers may way to poll on multiple file descriptors. This change
introduces pollers, which are registered with:
console_register_poller(), which provides a function to call when a
specified fd has pollable events.

Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
diff --git a/console-server.c b/console-server.c
index e68eab1..1ddb36c 100644
--- a/console-server.c
+++ b/console-server.c
@@ -6,6 +6,7 @@
 
 #define _GNU_SOURCE
 
+#include <assert.h>
 #include <stdint.h>
 #include <stdbool.h>
 #include <stdlib.h>
@@ -29,10 +30,27 @@
 	int		tty_sirq;
 	int		tty_lpc_addr;
 	int		tty_fd;
+
 	struct handler	**handlers;
 	int		n_handlers;
+
+	struct poller	**pollers;
+	int		n_pollers;
+
+	struct pollfd	*pollfds;
 };
 
+struct poller {
+	struct handler	*handler;
+	void		*data;
+	poller_fn_t	fn;
+	bool		remove;
+};
+
+/* we have one extra entry in the pollfds array for the VUART tty */
+static const int n_internal_pollfds = 1;
+
+
 static void usage(const char *progname)
 {
 	fprintf(stderr,
@@ -148,6 +166,9 @@
 	 */
 	fcntl(console->tty_fd, F_SETFL, FNDELAY);
 
+	console->pollfds[console->n_pollers].fd = console->tty_fd;
+	console->pollfds[console->n_pollers].events = POLLIN;
+
 	return 0;
 }
 
@@ -210,25 +231,125 @@
 	}
 
 	return rc;
+
 }
 
-static int handlers_poll_event(struct console *console,
-		struct pollfd *pollfds)
+struct poller *console_register_poller(struct console *console,
+		struct handler *handler, poller_fn_t poller_fn,
+		int fd, int events, void *data)
 {
-	struct handler *handler;
-	int i, rc, tmp;
+	struct poller *poller;
+	int n;
+
+	poller = malloc(sizeof(*poller));
+	poller->remove = false;
+	poller->handler = handler;
+	poller->fn = poller_fn;
+	poller->data = data;
+
+	/* add one to our pollers array */
+	n = console->n_pollers++;
+	console->pollers = realloc(console->pollers,
+			sizeof(*console->pollers) * console->n_pollers);
+
+	console->pollers[n] = poller;
+
+	/* increase pollfds array too  */
+	console->pollfds = realloc(console->pollfds,
+			sizeof(*console->pollfds) *
+				(n_internal_pollfds + console->n_pollers));
+
+	/* shift the end pollfds up by one */
+	memcpy(&console->pollfds[n+n_internal_pollfds],
+			&console->pollfds[n],
+			sizeof(*console->pollfds) * n_internal_pollfds);
+
+	console->pollfds[n].fd = fd;
+	console->pollfds[n].events = events;
+
+	return poller;
+}
+
+void console_unregister_poller(struct console *console,
+		struct poller *poller)
+{
+	int i;
+
+	/* find the entry in our pollers array */
+	for (i = 0; i < console->n_pollers; i++)
+		if (console->pollers[i] == poller)
+			break;
+
+	assert(i < console->n_pollers);
+
+	console->n_pollers--;
+
+	/* remove the item from the pollers array... */
+	memmove(&console->pollers[i], &console->pollers[i+1],
+			sizeof(*console->pollers)
+				* (console->n_pollers - i));
+
+	console->pollers = realloc(console->pollers,
+			sizeof(*console->pollers) * console->n_pollers);
+
+	/* ... and the pollfds array */
+	memmove(&console->pollfds[i], &console->pollfds[i+1],
+			sizeof(*console->pollfds) *
+				(n_internal_pollfds + console->n_pollers - i));
+
+	console->pollfds = realloc(console->pollfds,
+			sizeof(*console->pollfds) *
+				(n_internal_pollfds + console->n_pollers));
+
+
+	free(poller);
+}
+
+static int call_pollers(struct console *console)
+{
+	struct poller *poller;
+	struct pollfd *pollfd;
+	enum poller_ret prc;
+	int i, rc;
 
 	rc = 0;
 
-	for (i = 0; i < console->n_handlers; i++) {
-		handler = console->handlers[i];
+	/*
+	 * Process poll events by iterating through the pollers and pollfds
+	 * in-step, calling any pollers that we've found revents for.
+	 */
+	for (i = 0; i < console->n_pollers; i++) {
+		poller = console->pollers[i];
+		pollfd = &console->pollfds[i];
 
-		if (!handler->poll_event)
+		if (!pollfd->revents)
 			continue;
 
-		tmp = handler->poll_event(handler, pollfds[i].revents);
-		if (tmp == HANDLER_EXIT)
-			rc = 1;
+		prc = poller->fn(poller->handler, pollfd->revents,
+				poller->data);
+		if (prc == POLLER_EXIT)
+			rc = -1;
+		else if (prc == POLLER_REMOVE)
+			poller->remove = true;
+	}
+
+	/**
+	 * Process deferred removals; restarting each time we unregister, as
+	 * the array will have changed
+	 */
+	for (;;) {
+		bool removed = false;
+
+		for (i = 0; i < console->n_pollers; i++) {
+			poller = console->pollers[i];
+			if (poller->remove) {
+				console_unregister_poller(console, poller);
+				removed = true;
+				break;
+			}
+		}
+		if (!removed)
+			break;
 	}
 
 	return rc;
@@ -236,31 +357,22 @@
 
 int run_console(struct console *console)
 {
-	struct handler *handler;
-	struct pollfd *pollfds;
-	int i, rc;
-
-	pollfds = calloc(console->n_handlers + 1, sizeof(*pollfds));
-
-	pollfds[0].fd = console->tty_fd;
-	pollfds[0].events = POLLIN;
+	int rc;
 
 	for (;;) {
 		uint8_t buf[4096];
 
-		/* init pollers */
-		for (i = 0; i < console->n_handlers; i++) {
-			handler = console->handlers[i];
-			handler->init_poll(handler, &pollfds[i+1]);
-		}
-
-		rc = poll(pollfds, console->n_handlers + 1, -1);
+		rc = poll(console->pollfds,
+				console->n_pollers + n_internal_pollfds, -1);
 		if (rc < 0) {
 			warn("poll error");
 			return -1;
 		}
 
-		if (pollfds[0].revents) {
+		/* process internal fd first */
+		BUILD_ASSERT(n_internal_pollfds == 1);
+
+		if (console->pollfds[console->n_pollers].revents) {
 			rc = read(console->tty_fd, buf, sizeof(buf));
 			if (rc <= 0) {
 				warn("Error reading from tty device");
@@ -271,7 +383,8 @@
 				return 0;
 		}
 
-		rc = handlers_poll_event(console, pollfds + 1);
+		/* ... and then the pollers */
+		rc = call_pollers(console);
 		if (rc)
 			return 0;
 	}
@@ -328,6 +441,9 @@
 		}
 	}
 
+	console->pollfds = calloc(n_internal_pollfds,
+			sizeof(*console->pollfds));
+
 	if (!console->tty_kname) {
 		fprintf(stderr,
 			"Error: No TTY device specified (use --device)\n");
diff --git a/console-server.h b/console-server.h
index ec77102..38cddef 100644
--- a/console-server.h
+++ b/console-server.h
@@ -1,9 +1,10 @@
 
-struct console;
-
 #include <poll.h>
 #include <stdint.h>
 
+struct console;
+
+/* handler API */
 enum {
 	HANDLER_OK = 0,
 	HANDLER_EXIT,
@@ -13,10 +14,6 @@
 	const char	*name;
 	int		(*init)(struct handler *handler,
 				struct console *console);
-	int		(*init_poll)(struct handler *hander,
-				struct pollfd *pollfd);
-	int		(*poll_event)(struct handler *handler,
-				int events);
 	int		(*data_in)(struct handler *handler,
 				uint8_t *buf, size_t len);
 	void		(*fini)(struct handler *handler);
@@ -33,6 +30,24 @@
 
 int console_data_out(struct console *console, const uint8_t *data, size_t len);
 
+/* poller API */
+struct poller;
+
+enum poller_ret {
+	POLLER_OK = 0,
+	POLLER_REMOVE,
+	POLLER_EXIT,
+};
+
+typedef enum poller_ret (*poller_fn_t)(struct handler *handler,
+					int revents, void *data);
+
+struct poller *console_register_poller(struct console *console,
+		struct handler *handler, poller_fn_t poller_fn,
+		int fd, int events, void *data);
+
+void console_unregister_poller(struct console *console, struct poller *poller);
+
 /* utils */
 int write_buf_to_fd(int fd, const uint8_t *buf, size_t len);
 
diff --git a/log-handler.c b/log-handler.c
index 0eb9553..8396890 100644
--- a/log-handler.c
+++ b/log-handler.c
@@ -57,14 +57,6 @@
 	return 0;
 }
 
-static int log_init_poll(struct handler *handler, struct pollfd *pollfd)
-{
-	(void)handler;
-	pollfd->fd = -1;
-	pollfd->events = 0;
-	return 0;
-}
-
 static int log_trim(struct log_handler *lh, size_t space)
 {
 	int rc, n_shift_pages, shift_len, shift_start;
@@ -130,7 +122,6 @@
 	.handler = {
 		.name		= "log",
 		.init		= log_init,
-		.init_poll	= log_init_poll,
 		.data_in	= log_data,
 		.fini		= log_fini,
 	},
diff --git a/stdio-handler.c b/stdio-handler.c
index e74aaf5..d8a5a8a 100644
--- a/stdio-handler.c
+++ b/stdio-handler.c
@@ -12,6 +12,7 @@
 struct stdio_handler {
 	struct handler	handler;
 	struct console	*console;
+	struct poller	*poller;
 	int		fd_in;
 	int		fd_out;
 	bool		is_tty;
@@ -28,6 +29,59 @@
 static const uint8_t esc_str[] = { '\r', '~', '.' };
 
 
+static int process_input(struct stdio_handler *sh,
+		uint8_t *buf, size_t len)
+{
+	unsigned long i;
+	uint8_t e;
+
+	e = esc_str[sh->esc_str_pos];
+
+	for (i = 0; i < len; i++) {
+		if (buf[i] == e) {
+			sh->esc_str_pos++;
+			if (sh->esc_str_pos == ARRAY_SIZE(esc_str))
+				return 1;
+			e = esc_str[sh->esc_str_pos];
+		} else {
+			console_data_out(sh->console,
+					esc_str, sh->esc_str_pos);
+			sh->esc_str_pos = 0;
+		}
+	}
+	return 0;
+}
+
+static enum poller_ret stdio_poll(struct handler *handler,
+		int events, void __attribute__((unused)) *data)
+{
+	struct stdio_handler *sh = to_stdio_handler(handler);
+	uint8_t buf[4096];
+	ssize_t len;
+	int rc;
+
+	if (!(events & POLLIN))
+		return POLLER_OK;
+
+	len = read(sh->fd_in, buf, sizeof(buf));
+	if (len <= 0)
+		goto err;
+
+	rc = process_input(sh, buf, len);
+	if (rc)
+		return POLLER_EXIT;
+
+	rc = console_data_out(sh->console, buf, len);
+	if (rc < 0)
+		goto err;
+
+	return POLLER_OK;
+
+err:
+	sh->poller = NULL;
+	return POLLER_REMOVE;
+}
+
 
 
 /*
@@ -62,14 +116,9 @@
 		return -1;
 	}
 
-	return 0;
-}
+	sh->poller = console_register_poller(console, handler, stdio_poll,
+			sh->fd_in, POLLIN, NULL);
 
-static int stdio_init_poll(struct handler *handler, struct pollfd *pollfd)
-{
-	struct stdio_handler *sh = to_stdio_handler(handler);
-	pollfd->fd = sh->fd_in;
-	pollfd->events = POLLIN;
 	return 0;
 }
 
@@ -79,67 +128,19 @@
 	return write_buf_to_fd(sh->fd_out, buf, len);
 }
 
-static int process_input(struct stdio_handler *sh,
-		uint8_t *buf, size_t len)
-{
-	unsigned long i;
-	uint8_t e;
-
-	e = esc_str[sh->esc_str_pos];
-
-	for (i = 0; i < len; i++) {
-		if (buf[i] == e) {
-			sh->esc_str_pos++;
-			if (sh->esc_str_pos == ARRAY_SIZE(esc_str))
-				return 1;
-			e = esc_str[sh->esc_str_pos];
-		} else {
-			console_data_out(sh->console,
-					esc_str, sh->esc_str_pos);
-			sh->esc_str_pos = 0;
-		}
-	}
-	return 0;
-}
-
-static int stdio_poll_event(struct handler *handler, int events)
-{
-	struct stdio_handler *sh = to_stdio_handler(handler);
-	uint8_t buf[4096];
-	ssize_t len;
-	int rc;
-
-	if (!(events & POLLIN))
-		return 0;
-
-	len = read(sh->fd_in, buf, sizeof(buf));
-	if (len <= 0)
-		return -1;
-
-	rc = process_input(sh, buf, len);
-	if (rc)
-		return HANDLER_EXIT;
-
-	rc = console_data_out(sh->console, buf, len);
-	if (rc < 0)
-		return -1;
-
-	return 0;
-}
-
 static void stdio_fini(struct handler *handler)
 {
 	struct stdio_handler *sh = to_stdio_handler(handler);
 	if (sh->is_tty)
 		tcsetattr(sh->fd_in, TCSANOW, &sh->orig_termios);
+	if (sh->poller)
+		console_unregister_poller(sh->console, sh->poller);
 }
 
 static struct stdio_handler stdio_handler = {
 	.handler = {
 		.name		= "stdio",
 		.init		= stdio_init,
-		.init_poll	= stdio_init_poll,
-		.poll_event	= stdio_poll_event,
 		.data_in	= stdio_data,
 		.fini		= stdio_fini,
 	},