Kapil Porwal has uploaded this change for review.

View Change

drivers/self_test: coreboot self test

Add a self test infrastructure with below features -
- Modularity: New tests can be added easily as a module.
- Flexibility: Where tests can be executed at any stage.
- Where results can be dumped during payload or the OS.

BUG=b:233012780
TEST=Print self test result in the OS

Run the below command in the OS to get the self test logs:
$ cbmem -r 53545354 | hexdump -C

Sample output:
00000000 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
00001000

Change-Id: I8d7813d972167592b8dc9e9e7e9b0eb1f50c3da6
Signed-off-by: Kapil Porwal <kapilporwal@google.com>
---
M src/commonlib/bsd/include/commonlib/bsd/cbmem_id.h
A src/drivers/self_test/Kconfig
A src/drivers/self_test/Makefile.mk
A src/drivers/self_test/self_test.c
A src/include/self_test.h
A src/include/self_test_id.h
6 files changed, 278 insertions(+), 1 deletion(-)

git pull ssh://review.coreboot.org:29418/coreboot refs/changes/45/82045/1
diff --git a/src/commonlib/bsd/include/commonlib/bsd/cbmem_id.h b/src/commonlib/bsd/include/commonlib/bsd/cbmem_id.h
index b88a083..955b4e3 100644
--- a/src/commonlib/bsd/include/commonlib/bsd/cbmem_id.h
+++ b/src/commonlib/bsd/include/commonlib/bsd/cbmem_id.h
@@ -90,6 +90,7 @@
#define CBMEM_ID_CSE_INFO 0x4553435F
#define CBMEM_ID_CSE_BP_INFO 0x42455343
#define CBMEM_ID_AMD_OPENSIL 0x4153494C
+#define CBMEM_ID_SELF_TEST 0x53545354

#define CBMEM_ID_TO_NAME_TABLE \
{ CBMEM_ID_ACPI, "ACPI " }, \
@@ -172,5 +173,6 @@
{ CBMEM_ID_AMD_MP2, "AMD MP2 BUFFER"},\
{ CBMEM_ID_CSE_INFO, "CSE SPECIFIC INFO"},\
{ CBMEM_ID_CSE_BP_INFO, "CSE BP INFO"}, \
- { CBMEM_ID_AMD_OPENSIL, "OPENSIL DATA"}
+ { CBMEM_ID_AMD_OPENSIL, "OPENSIL DATA"}, \
+ { CBMEM_ID_SELF_TEST, "SELF TEST"}
#endif /* _CBMEM_ID_H_ */
diff --git a/src/drivers/self_test/Kconfig b/src/drivers/self_test/Kconfig
new file mode 100644
index 0000000..37f6e29
--- /dev/null
+++ b/src/drivers/self_test/Kconfig
@@ -0,0 +1,31 @@
+## SPDX-License-Identifier: GPL-2.0-only
+
+config SELF_TEST
+ bool "Support for self test"
+ default n
+ help
+ Enable support for self test.
+
+source "src/drivers/sefl_test/modules/*/Kconfig"
+
+if SELF_TEST
+
+config SELF_TEST_DEBUG
+ bool "Enable debug output for self test"
+ default y
+
+config SELF_TEST_CBMEM
+ bool "Store a copy of self test output in CBMEM"
+ default y
+ help
+ This option will have SELF_TEST store a copy of the self test output
+ in a CBMEM region.
+
+
+config SELF_TEST_OUTPUT_BUFFER_SIZE
+ int "Size of buffer to store output of self test"
+ default 4096
+ help
+ Size of the buffer.
+
+endif
diff --git a/src/drivers/self_test/Makefile.mk b/src/drivers/self_test/Makefile.mk
new file mode 100644
index 0000000..2802d67
--- /dev/null
+++ b/src/drivers/self_test/Makefile.mk
@@ -0,0 +1 @@
+ramstage-$(CONFIG_SELF_TEST) += self_test.c
diff --git a/src/drivers/self_test/self_test.c b/src/drivers/self_test/self_test.c
new file mode 100644
index 0000000..103db1c
--- /dev/null
+++ b/src/drivers/self_test/self_test.c
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+
+#include <bootstate.h>
+#include <cbmem.h>
+#include <console/console.h>
+#include <commonlib/stdlib.h>
+#include <list.h>
+#include <self_test.h>
+
+struct st_id_to_name {
+ uint32_t id;
+ const char *name;
+};
+static const struct st_id_to_name st_ids[] = { ST_ID_TO_NAME_TABLE };
+
+#define ST_POPULATE_MAP_ST(a) [ST_##a] = #a
+static const char * const st_status_str[] = {
+ ST_POPULATE_MAP_ST(PASSED),
+ ST_POPULATE_MAP_ST(SKIPPED),
+ ST_POPULATE_MAP_ST(WARNING),
+ ST_POPULATE_MAP_ST(FAILED),
+ [ST_STATUS_MAX] = NULL
+};
+
+static u32 cbmem_st_buffer_size = CONFIG_SELF_TEST_OUTPUT_BUFFER_SIZE;
+static char *cbmem_st_buffer = NULL;
+static struct self_test_log *cbmem_st_log = NULL;
+static u32 cbmem_st_log_index = 0;
+
+typedef struct {
+ struct self_test_t test;
+ int result;
+ struct list_node list_node;
+} selftest_entry_t;
+
+struct st_exec_state_t {
+ boot_state_t state;
+ boot_state_sequence_t when;
+ struct list_node selftest_list;
+ struct boot_state_callback callback;
+};
+
+#define ST_BS_STATE_MAX (BS_PAYLOAD_BOOT + 1)
+#define ST_BS_SEQ_MAX (BS_ON_EXIT + 1)
+
+#define ST_POPULATE_MAP(a) [a] = #a
+static const char * const st_state_to_name[] = {
+ ST_POPULATE_MAP(BS_PRE_DEVICE),
+ ST_POPULATE_MAP(BS_DEV_INIT_CHIPS),
+ ST_POPULATE_MAP(BS_DEV_ENUMERATE),
+ ST_POPULATE_MAP(BS_DEV_RESOURCES),
+ ST_POPULATE_MAP(BS_DEV_ENABLE),
+ ST_POPULATE_MAP(BS_DEV_INIT),
+ ST_POPULATE_MAP(BS_POST_DEVICE),
+ ST_POPULATE_MAP(BS_OS_RESUME_CHECK),
+ ST_POPULATE_MAP(BS_OS_RESUME),
+ ST_POPULATE_MAP(BS_WRITE_TABLES),
+ ST_POPULATE_MAP(BS_PAYLOAD_LOAD),
+ ST_POPULATE_MAP(BS_PAYLOAD_BOOT)
+};
+static const char * const st_seq_to_name[] = {
+ ST_POPULATE_MAP(BS_ON_ENTRY),
+ ST_POPULATE_MAP(BS_ON_EXIT)
+};
+
+static struct st_exec_state_t st_exec_states[ST_BS_STATE_MAX * ST_BS_SEQ_MAX];
+
+static void st_log(uint32_t id, st_status status)
+{
+ if ((cbmem_st_log_index + 1) * sizeof(struct self_test_log) <= cbmem_st_buffer_size) {
+ cbmem_st_log[cbmem_st_log_index].id = id;
+ cbmem_st_log[cbmem_st_log_index].status = status;
+ cbmem_st_log_index++;
+ }
+}
+
+static void add_selftest(const struct self_test_t *test)
+{
+ if (test == NULL)
+ return;
+ if ((test->state >= ST_BS_STATE_MAX) || (test->when >= ST_BS_SEQ_MAX))
+ return;
+ selftest_entry_t *e = xmalloc(sizeof(selftest_entry_t));
+ memcpy(&e->test, test, sizeof(struct self_test_t));
+ e->result = ST_SKIPPED;
+ e->list_node.prev = NULL;
+ e->list_node.next = NULL;
+ list_append(&e->list_node, &st_exec_states[e->test.state * ST_BS_SEQ_MAX + e->test.when].selftest_list);
+ st_debug("Added self test 0x%x\n", e->test.id);
+}
+
+void register_selftest(const struct self_test_t *tests)
+{
+ if (tests == NULL)
+ return;
+ while (tests->id != ST_INVALID_ID) {
+ add_selftest(tests);
+ tests++;
+ }
+}
+
+static const char *st_get_name(uint32_t id)
+{
+ for (int i = 0; i < ARRAY_SIZE(st_ids); i++) {
+ if (st_ids[i].id == id)
+ return st_ids[i].name;
+ }
+ return NULL;
+}
+
+static void run_selftest(void *data)
+{
+ size_t idx;
+ selftest_entry_t *node;
+ if (cbmem_st_buffer == NULL) {
+ st_debug("No self test buffer in cbmem\n");
+ return;
+ }
+ struct st_exec_state_t *st_exec_state = (struct st_exec_state_t *)data;
+
+ if (st_exec_state->selftest_list.next == NULL)
+ return;
+
+ st_info("Executing self test(s) at %s/%s\n",
+ st_state_to_name[st_exec_state->state], st_seq_to_name[st_exec_state->when]);
+ st_debug("START\n");
+ idx = 0;
+ list_for_each(node, st_exec_state->selftest_list, list_node) {
+ st_debug("[%lu] Running 0x%x\n", idx + 1, node->test.id);
+ node->result = node->test.exec();
+ st_info("Selftest: %s (0x%x), Result: %s\n", st_get_name(node->test.id),
+ node->test.id, st_status_str[node->result]);
+ st_log(node->test.id, node->result);
+ idx++;
+ }
+ st_debug("END\n");
+ st_debug("Executed %lu self test(s)\n", idx);
+ st_info("self test buffer state: %lu/%u\n",
+ (cbmem_st_log_index * sizeof(struct self_test_log)), cbmem_st_buffer_size);
+}
+
+static void init_selftest(void *unused)
+{
+ st_debug("Starting self test init\n");
+ st_info("self test buffer size is %d\n", cbmem_st_buffer_size);
+ cbmem_st_buffer = cbmem_add(CBMEM_ID_SELF_TEST, cbmem_st_buffer_size);
+ if (cbmem_st_buffer == NULL) {
+ st_debug("failed to add self test buffer in cbmem\n");
+ return;
+ }
+
+ memset(cbmem_st_buffer, 0, cbmem_st_buffer_size);
+ cbmem_st_log = (struct self_test_log *)cbmem_st_buffer;
+
+ for (int i = 0; i < ARRAY_SIZE(st_exec_states); i++) {
+ st_exec_states[i].callback.callback = run_selftest;
+ st_exec_states[i].callback.arg = &st_exec_states[i];
+ st_exec_states[i].state = i / ST_BS_SEQ_MAX;
+ st_exec_states[i].when = i % ST_BS_SEQ_MAX;
+ if (st_exec_states[i].when == BS_ON_ENTRY)
+ boot_state_sched_on_entry(&st_exec_states[i].callback, st_exec_states[i].state);
+ else if (st_exec_states[i].when == BS_ON_EXIT)
+ boot_state_sched_on_exit(&st_exec_states[i].callback, st_exec_states[i].state);
+ }
+
+ st_debug("Exiting self test init\n");
+}
+
+BOOT_STATE_INIT_ENTRY(BS_PRE_DEVICE, BS_ON_ENTRY, init_selftest, NULL);
diff --git a/src/include/self_test.h b/src/include/self_test.h
new file mode 100644
index 0000000..ada35ad
--- /dev/null
+++ b/src/include/self_test.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef _SELF_TEST_H_
+#define _SELF_TEST_H_
+
+#include "self_test_id.h"
+
+typedef enum {
+ ST_PASSED,
+ ST_SKIPPED,
+ ST_WARNING,
+ ST_FAILED,
+ ST_STATUS_MAX
+} st_status;
+
+struct self_test_log {
+ uint32_t id;
+ st_status status;
+};
+
+#if CONFIG(SELF_TEST)
+
+struct self_test_t {
+ /*
+ * A 1-based unique number to identify a self test
+ */
+ uint32_t id;
+ /*
+ * Test override function to perform the test.
+ * the return type of the function should be in st_status
+ */
+ st_status (*exec)(void);
+ /*
+ * Specify the Boot State to execute this test, e.g. -
+ * 1. BS_DEV_ENABLE, BS_ON_ENTRY <------- After PCI Enumeration
+ * 2. BS_PAYLOAD_LOAD, BS_ON_EXIT <------ Before Ready to Boot on Normal Boot Path
+ * 3. BS_OS_RESUME, BS_ON_ENTRY <------- Before Ready to Boot on S3 resume Path
+ */
+ boot_state_t state;
+ boot_state_sequence_t when;
+};
+
+void register_selftest(const struct self_test_t *tests);
+
+#define REGISTER_SELFTEST(tests) \
+ static void register_selftest_##tests(void *unused) \
+ { \
+ register_selftest(tests); \
+ } \
+ BOOT_STATE_INIT_ENTRY(BS_PRE_DEVICE, BS_ON_ENTRY, register_selftest_##tests, NULL)
+
+#define st_info(STR...) printk(BIOS_INFO, STR)
+
+#if CONFIG(SELF_TEST_DEBUG)
+#define st_debug(STR...) printk(BIOS_DEBUG, STR)
+#else
+#define st_debug(STR...)
+#endif
+
+#endif
+
+#endif /* _SELF_TEST_H_ */
diff --git a/src/include/self_test_id.h b/src/include/self_test_id.h
new file mode 100644
index 0000000..f556fdb9
--- /dev/null
+++ b/src/include/self_test_id.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef _SELF_TEST_ID_H_
+#define _SELF_TEST_ID_H_
+
+#define ST_INVALID_ID 0x00
+
+#define ST_ID_TO_NAME_TABLE \
+ {ST_INVALID_ID, "Invalid Test"}
+
+#endif /* _SELF_TEST_ID_H_ */

To view, visit change 82045. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: coreboot
Gerrit-Branch: main
Gerrit-Change-Id: I8d7813d972167592b8dc9e9e7e9b0eb1f50c3da6
Gerrit-Change-Number: 82045
Gerrit-PatchSet: 1
Gerrit-Owner: Kapil Porwal <kapilporwal@google.com>
Gerrit-MessageType: newchange