event: Add basic support for events

Add a way to create and dispatch events without needing to allocate
memory. Also add a way to 'spy' on events, thus allowing 'hooks' to be
created.

Use a linker list for static events, which we can use to replace functions
like arch_cpu_init_f(). Allow an EVENT_DEBUG option which makes it
easier to see what is going on at runtime, but uses more code space.

Dynamic events allow the creation of a spy at runtime. This is not always
necessary, but can be enabled with EVENT_DYNAMIC if needed.

A 'test' event is the only option for now.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/common/event.c b/common/event.c
new file mode 100644
index 0000000..366be24
--- /dev/null
+++ b/common/event.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Events provide a general-purpose way to react to / subscribe to changes
+ * within U-Boot
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY	LOGC_EVENT
+
+#include <common.h>
+#include <event.h>
+#include <event_internal.h>
+#include <log.h>
+#include <linker_lists.h>
+#include <malloc.h>
+#include <asm/global_data.h>
+#include <linux/list.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#if CONFIG_IS_ENABLED(EVENT_DEBUG)
+const char *const type_name[] = {
+	"none",
+	"test",
+};
+
+_Static_assert(ARRAY_SIZE(type_name) == EVT_COUNT, "event type_name size");
+#endif
+
+static const char *event_type_name(enum event_t type)
+{
+#if CONFIG_IS_ENABLED(EVENT_DEBUG)
+	return type_name[type];
+#else
+	return "(unknown)";
+#endif
+}
+
+static int notify_static(struct event *ev)
+{
+	struct evspy_info *start =
+		ll_entry_start(struct evspy_info, evspy_info);
+	const int n_ents = ll_entry_count(struct evspy_info, evspy_info);
+	struct evspy_info *spy;
+
+	for (spy = start; spy != start + n_ents; spy++) {
+		if (spy->type == ev->type) {
+			int ret;
+
+			log_debug("Sending event %x/%s to spy '%s'\n", ev->type,
+				  event_type_name(ev->type), event_spy_id(spy));
+			ret = spy->func(NULL, ev);
+
+			/*
+			 * TODO: Handle various return codes to
+			 *
+			 * - claim an event (no others will see it)
+			 * - return an error from the event
+			 */
+			if (ret)
+				return log_msg_ret("spy", ret);
+		}
+	}
+
+	return 0;
+}
+
+static int notify_dynamic(struct event *ev)
+{
+	struct event_state *state = gd_event_state();
+	struct event_spy *spy, *next;
+
+	list_for_each_entry_safe(spy, next, &state->spy_head, sibling_node) {
+		if (spy->type == ev->type) {
+			int ret;
+
+			log_debug("Sending event %x/%s to spy '%s'\n", ev->type,
+				  event_type_name(ev->type), spy->id);
+			ret = spy->func(spy->ctx, ev);
+
+			/*
+			 * TODO: Handle various return codes to
+			 *
+			 * - claim an event (no others will see it)
+			 * - return an error from the event
+			 */
+			if (ret)
+				return log_msg_ret("spy", ret);
+		}
+	}
+
+	return 0;
+}
+
+int event_notify(enum event_t type, void *data, int size)
+{
+	struct event event;
+	int ret;
+
+	event.type = type;
+	if (size > sizeof(event.data))
+		return log_msg_ret("size", -E2BIG);
+	memcpy(&event.data, data, size);
+
+	ret = notify_static(&event);
+	if (ret)
+		return log_msg_ret("dyn", ret);
+
+	if (CONFIG_IS_ENABLED(EVENT_DYNAMIC)) {
+		ret = notify_dynamic(&event);
+		if (ret)
+			return log_msg_ret("dyn", ret);
+	}
+
+	return 0;
+}
+
+int event_notify_null(enum event_t type)
+{
+	return event_notify(type, NULL, 0);
+}
+
+void event_show_spy_list(void)
+{
+	struct evspy_info *start =
+		ll_entry_start(struct evspy_info, evspy_info);
+	const int n_ents = ll_entry_count(struct evspy_info, evspy_info);
+	struct evspy_info *spy;
+	const int size = sizeof(ulong) * 2;
+
+	printf("Seq  %-24s  %*s  %s\n", "Type", size, "Function", "ID");
+	for (spy = start; spy != start + n_ents; spy++) {
+		printf("%3x  %-3x %-20s  %*p  %s\n", (uint)(spy - start),
+		       spy->type, event_type_name(spy->type), size, spy->func,
+		       event_spy_id(spy));
+	}
+}
+
+#if CONFIG_IS_ENABLED(EVENT_DYNAMIC)
+static void spy_free(struct event_spy *spy)
+{
+	list_del(&spy->sibling_node);
+}
+
+int event_register(const char *id, enum event_t type, event_handler_t func, void *ctx)
+{
+	struct event_state *state = gd_event_state();
+	struct event_spy *spy;
+
+	if (!CONFIG_IS_ENABLED(EVENT_DYNAMIC))
+		return -ENOSYS;
+	spy = malloc(sizeof(*spy));
+	if (!spy)
+		return log_msg_ret("alloc", -ENOMEM);
+
+	spy->id = id;
+	spy->type = type;
+	spy->func = func;
+	spy->ctx = ctx;
+	list_add_tail(&spy->sibling_node, &state->spy_head);
+
+	return 0;
+}
+
+int event_uninit(void)
+{
+	struct event_state *state = gd_event_state();
+	struct event_spy *spy, *next;
+
+	list_for_each_entry_safe(spy, next, &state->spy_head, sibling_node)
+		spy_free(spy);
+
+	return 0;
+}
+
+int event_init(void)
+{
+	struct event_state *state = gd_event_state();
+
+	INIT_LIST_HEAD(&state->spy_head);
+
+	return 0;
+}
+#endif /* EVENT_DYNAMIC */