| // 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> |
| #include <relocate.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| #if CONFIG_IS_ENABLED(EVENT_DEBUG) |
| const char *const type_name[] = { |
| "none", |
| "test", |
| |
| /* Events related to driver model */ |
| "dm_post_init", |
| "dm_pre_probe", |
| "dm_post_probe", |
| "dm_pre_remove", |
| "dm_post_remove", |
| |
| /* init hooks */ |
| "misc_init_f", |
| |
| /* fdt hooks */ |
| "ft_fixup", |
| }; |
| |
| _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(NEEDS_MANUAL_RELOC) |
| int event_manual_reloc(void) |
| { |
| struct evspy_info *spy, *end; |
| |
| spy = ll_entry_start(struct evspy_info, evspy_info); |
| end = ll_entry_end(struct evspy_info, evspy_info); |
| for (; spy < end; spy++) |
| MANUAL_RELOC(spy->func); |
| |
| return 0; |
| } |
| #endif |
| |
| #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; |
| |
| 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 */ |