From 7095c99a8743805a6bce437547389c0d1e9265c6 Mon Sep 17 00:00:00 2001 From: Jakub Czapiga Date: Fri, 25 Apr 2025 13:58:49 +0000 Subject: [PATCH] util/cbmem: Add support for CBMEM in sysfs This commit adds support for CBMEM in sysfs. Useful for systems without access to /dev/mem e.g. Android. Linux kernel driver: drivers/firmware/google/cbmem.c Linux driver Kconfig: CONFIG_GOOGLE_CBMEM BUG=b:391874512 TEST=(devmem) cbmem -l; cbmem -x; cbmem -r 434f4e53; cbmem -t; cbmem -a 1200 TEST=modprobe cbmem; cbmem -l; cbmem -x; cbmem -r 434f4e53; cbmem -t; cbmem -a 1200 Change-Id: I527889509ffc84203be42d0160e5363c60eafd02 Signed-off-by: Jakub Czapiga Reviewed-on: https://review.coreboot.org/c/coreboot/+/86606 Tested-by: build bot (Jenkins) Reviewed-by: Julius Werner --- util/cbmem/Makefile | 2 +- util/cbmem/cbmem.c | 58 ++++++--- util/cbmem/cbmem_drv.c | 95 ++++++++++++++ util/cbmem/cbmem_util.h | 98 +++++++++++++++ util/cbmem/sysfs_drv.c | 266 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 500 insertions(+), 19 deletions(-) create mode 100644 util/cbmem/cbmem_drv.c create mode 100644 util/cbmem/sysfs_drv.c diff --git a/util/cbmem/Makefile b/util/cbmem/Makefile index 1376b04d52..e265ea6693 100644 --- a/util/cbmem/Makefile +++ b/util/cbmem/Makefile @@ -14,7 +14,7 @@ CFLAGS += -Wall -Wextra -Wmissing-prototypes -Wshadow $(WERROR) CPPFLAGS += -I . -I $(ROOT)/commonlib/include -I $(ROOT)/commonlib/bsd/include CPPFLAGS += -include $(ROOT)/commonlib/bsd/include/commonlib/bsd/compiler.h -OBJS = $(PROGRAM).o devmem_drv.o $(COMMONLIB)/bsd/ipchksum.o +OBJS = $(PROGRAM).o cbmem_drv.o devmem_drv.o sysfs_drv.o $(COMMONLIB)/bsd/ipchksum.o all: $(PROGRAM) diff --git a/util/cbmem/cbmem.c b/util/cbmem/cbmem.c index 93a58d5999..018ea871f9 100644 --- a/util/cbmem/cbmem.c +++ b/util/cbmem/cbmem.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,7 @@ #include "cbmem_util.h" -#define CBMEM_VERSION "1.1" +#define CBMEM_VERSION "1.2" /* Global verbosity level for debug() macro. */ int cbmem_util_verbose; @@ -140,8 +141,7 @@ static void cbmem_get_lb_table_entry(uint32_t tag, uint8_t **buf_out, size_t *si const uint8_t *lbtable_raw; bool tag_found = false; - if (!cbmem_devmem_get_cbmem_entry(CBMEM_ID_CBTABLE, (uint8_t **)&lbtable_raw, NULL, - NULL)) + if (!cbmem_drv_get_cbmem_entry(CBMEM_ID_CBTABLE, (uint8_t **)&lbtable_raw, NULL, NULL)) die("coreboot table not found.\n"); const struct lb_header *lbh = (const struct lb_header *)lbtable_raw; @@ -337,7 +337,7 @@ static void dump_timestamps(enum timestamps_print_type output_type) uint64_t prev_stamp = 0; uint64_t total_time = 0; - if (!cbmem_devmem_get_cbmem_entry(CBMEM_ID_TIMESTAMP, (uint8_t **)&tst_p, &size, NULL)) + if (!cbmem_drv_get_cbmem_entry(CBMEM_ID_TIMESTAMP, (uint8_t **)&tst_p, &size, NULL)) die("Timestamps not found.\n"); timestamp_set_tick_freq(tst_p->tick_freq_mhz); @@ -437,7 +437,7 @@ static void timestamp_add_now(uint32_t timestamp_id) struct timestamp_table *tst_p; size_t tst_size; - if (!cbmem_devmem_get_cbmem_entry(CBMEM_ID_TIMESTAMP, (uint8_t **)&tst_p, &tst_size, NULL)) + if (!cbmem_drv_get_cbmem_entry(CBMEM_ID_TIMESTAMP, (uint8_t **)&tst_p, &tst_size, NULL)) die("Unable to find timestamps.\n"); /* @@ -454,7 +454,7 @@ static void timestamp_add_now(uint32_t timestamp_id) tst_p->num_entries += 1; } - if (!cbmem_devmem_write_cbmem_entry(CBMEM_ID_TIMESTAMP, (uint8_t *)tst_p, tst_size)) + if (!cbmem_drv_write_cbmem_entry(CBMEM_ID_TIMESTAMP, (uint8_t *)tst_p, tst_size)) die("Unable to write timestamps.\n"); free((uint8_t *)tst_p); } @@ -674,7 +674,7 @@ static void dump_tpm_cb_log(void) { const struct tpm_cb_log_table *tclt_p; - if (!cbmem_devmem_get_cbmem_entry(CBMEM_ID_TPM_CB_LOG, (uint8_t **)&tclt_p, NULL, NULL)) + if (!cbmem_drv_get_cbmem_entry(CBMEM_ID_TPM_CB_LOG, (uint8_t **)&tclt_p, NULL, NULL)) die("coreboot TPM log not found.\n"); printf("coreboot TPM log:\n\n"); @@ -694,8 +694,8 @@ static void dump_tpm_log(void) { uint8_t *buf; - if (cbmem_devmem_get_cbmem_entry(CBMEM_ID_TCPA_TCG_LOG, &buf, NULL, NULL) || - cbmem_devmem_get_cbmem_entry(CBMEM_ID_TPM2_TCG_LOG, &buf, NULL, NULL)) { + if (cbmem_drv_get_cbmem_entry(CBMEM_ID_TCPA_TCG_LOG, &buf, NULL, NULL) || + cbmem_drv_get_cbmem_entry(CBMEM_ID_TPM2_TCG_LOG, &buf, NULL, NULL)) { dump_tpm_std_log(buf); free(buf); } else @@ -738,7 +738,7 @@ static void dump_console(enum console_print_type type, int max_loglevel, int pri char *console_c; size_t size, cursor, previous; - if (!cbmem_devmem_get_cbmem_entry(CBMEM_ID_CONSOLE, (uint8_t **)&console_p, NULL, NULL)) + if (!cbmem_drv_get_cbmem_entry(CBMEM_ID_CONSOLE, (uint8_t **)&console_p, NULL, NULL)) die("CBMEM console not found.\n"); cursor = console_p->cursor & CBMC_CURSOR_MASK; @@ -882,7 +882,7 @@ static bool hexdump_handler(const uint32_t id, const uint64_t physical_address, static void dump_cbmem_hex(void) { - cbmem_devmem_foreach_cbmem_entry(hexdump_handler, NULL, true); + cbmem_drv_foreach_cbmem_entry(hexdump_handler, NULL, true); } static void dump_cbmem_raw(unsigned int id) @@ -890,7 +890,7 @@ static void dump_cbmem_raw(unsigned int id) uint8_t *buf; size_t size; - if (!cbmem_devmem_get_cbmem_entry(id, &buf, &size, NULL)) + if (!cbmem_drv_get_cbmem_entry(id, &buf, &size, NULL)) die("cbmem entry id: %#x not found.\n", id); fwrite(buf, 1, size, stdout); @@ -958,7 +958,7 @@ static void dump_cbmem_toc(void) printf("CBMEM table of contents:\n"); printf(" %-20s %-8s %-8s %-8s\n", "NAME", "ID", "START", "LENGTH"); - cbmem_devmem_foreach_cbmem_entry(toc_handler, &i, false); + cbmem_drv_foreach_cbmem_entry(toc_handler, &i, false); } #define COVERAGE_MAGIC 0x584d4153 @@ -995,7 +995,7 @@ static void dump_coverage(void) unsigned long phys_offset; #define phys_to_virt(x) ((void *)(unsigned long)(x) + phys_offset) - if (!cbmem_devmem_get_cbmem_entry(CBMEM_ID_COVERAGE, &coverage, NULL, &start)) + if (!cbmem_drv_get_cbmem_entry(CBMEM_ID_COVERAGE, &coverage, NULL, &start)) die("No coverage information found\n"); /* Map coverage area */ @@ -1059,6 +1059,7 @@ static void print_usage(const char *name, int exit_code) " -c | --console: print cbmem console\n" " -1 | --oneboot: print cbmem console for last boot only\n" " -2 | --2ndtolast: print cbmem console for the boot that came before the last one only\n" + " -b | --backend [devmem|sysfs]: select specific CBMEM backend\n" " -B | --loglevel: maximum loglevel to print; prefix `+` (e.g. -B +INFO) to also print lines that have no level\n" " -C | --coverage: dump coverage information\n" " -l | --list: print cbmem table of contents\n" @@ -1091,10 +1092,12 @@ int main(int argc, char** argv) int max_loglevel = BIOS_NEVER; int print_unknown_logs = 1; uint32_t timestamp_id = 0; + enum cbmem_drv_backend_type backend_type = CBMEM_DRV_BACKEND_ANY; int opt, option_index = 0; static struct option long_options[] = { {"console", 0, 0, 'c'}, + {"backend", required_argument, 0, 'b'}, {"oneboot", 0, 0, '1'}, {"2ndtolast", 0, 0, '2'}, {"loglevel", required_argument, 0, 'B'}, @@ -1112,13 +1115,24 @@ int main(int argc, char** argv) {"help", 0, 0, 'h'}, {0, 0, 0, 0} }; - while ((opt = getopt_long(argc, argv, "c12B:CltTSa:LxVvh?r:", + while ((opt = getopt_long(argc, argv, "cb:12B:CltTSa:LxVvh?r:", long_options, &option_index)) != EOF) { switch (opt) { case 'c': print_console = 1; print_defaults = 0; break; + case 'b': + if (!strcasecmp(optarg, "devmem")) + backend_type = CBMEM_DRV_BACKEND_DEVMEM; + else if (!strcasecmp(optarg, "sysfs")) + backend_type = CBMEM_DRV_BACKEND_SYSFS; + else if (!strcasecmp(optarg, "any")) + backend_type = CBMEM_DRV_BACKEND_ANY; + else + die("Unrecognized backend type: '%s'\n", optarg); + break; + case '1': print_console = 1; console_type = CONSOLE_PRINT_LAST; @@ -1194,8 +1208,16 @@ int main(int argc, char** argv) print_usage(argv[0], 1); } - if (!cbmem_devmem_init(timestamp_id != 0)) - die("Unable to initialize /dev/mem access to coreboot tables and CBMEM.\n"); + if (print_hexdump) { + debug("Hexdump requested. CBMEM backend force-set to DEVMEM.\n"); + backend_type = CBMEM_DRV_BACKEND_DEVMEM; + } + + if (!cbmem_drv_init(backend_type, timestamp_id != 0)) { + if (print_hexdump) + fprintf(stderr, "Hexdump is only available on systems with /dev/mem.\n"); + die("Unable to initialize CBMEM access. Check if you have either /dev/mem access or sysfs CBMEM entries.\n"); + } if (print_console) dump_console(console_type, max_loglevel, print_unknown_logs); @@ -1224,7 +1246,7 @@ int main(int argc, char** argv) if (print_tcpa_log) dump_tpm_log(); - cbmem_devmem_terminate(); + cbmem_drv_terminate(); return 0; } diff --git a/util/cbmem/cbmem_drv.c b/util/cbmem/cbmem_drv.c new file mode 100644 index 0000000000..bc9234b5a1 --- /dev/null +++ b/util/cbmem/cbmem_drv.c @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include + +#include "cbmem_util.h" + +/* "ANY" (0) means no backend is active. */ +static enum cbmem_drv_backend_type active_backend = CBMEM_DRV_BACKEND_ANY; + +static const char no_backend_error_message[] = + "No active/initialized CBMEM backend detected. Terminating.\n"; + +static bool init_specific_backend(enum cbmem_drv_backend_type backend, bool writeable) +{ + switch (backend) { + case CBMEM_DRV_BACKEND_DEVMEM: + if (!cbmem_devmem_init(writeable)) + return false; + debug("Initialized CBMEM backend: devmem\n"); + break; + case CBMEM_DRV_BACKEND_SYSFS: + if (!cbmem_sysfs_init()) + return false; + debug("Initialized CBMEM backend: sysfs\n"); + break; + default: + die("Unsupported backend selected. Terminating.\n"); + } + + active_backend = backend; + return true; +} + +bool cbmem_drv_init(enum cbmem_drv_backend_type backend, bool writeable) +{ + if (backend != CBMEM_DRV_BACKEND_ANY) + return init_specific_backend(backend, writeable); + + /* First try SYSFS backend as it's more secure. */ + if (!init_specific_backend(CBMEM_DRV_BACKEND_SYSFS, writeable)) + return init_specific_backend(CBMEM_DRV_BACKEND_DEVMEM, writeable); + + return true; +} + +void cbmem_drv_terminate(void) +{ + /* Only /dev/mem driver requires termination. */ + if (active_backend == CBMEM_DRV_BACKEND_DEVMEM) + cbmem_devmem_terminate(); + + active_backend = CBMEM_DRV_BACKEND_ANY; +} + +bool cbmem_drv_get_cbmem_entry(uint32_t id, uint8_t **buf_out, size_t *size_out, uint64_t *addr_out) +{ + switch (active_backend) { + case CBMEM_DRV_BACKEND_DEVMEM: + return cbmem_devmem_get_cbmem_entry(id, buf_out, size_out, addr_out); + case CBMEM_DRV_BACKEND_SYSFS: + return cbmem_sysfs_get_cbmem_entry(id, buf_out, size_out, addr_out); + default: + die(no_backend_error_message); + } + return false; +} + +bool cbmem_drv_write_cbmem_entry(uint32_t id, uint8_t *buf, size_t buf_size) +{ + switch (active_backend) { + case CBMEM_DRV_BACKEND_DEVMEM: + return cbmem_devmem_write_cbmem_entry(id, buf, buf_size); + case CBMEM_DRV_BACKEND_SYSFS: + return cbmem_sysfs_write_cbmem_entry(id, buf, buf_size); + default: + die(no_backend_error_message); + } + return false; +} + +void cbmem_drv_foreach_cbmem_entry(cbmem_iterator_callback cb, void *data, bool with_contents) +{ + switch (active_backend) { + case CBMEM_DRV_BACKEND_DEVMEM: + cbmem_devmem_foreach_cbmem_entry(cb, data, with_contents); + break; + case CBMEM_DRV_BACKEND_SYSFS: + cbmem_sysfs_foreach_cbmem_entry(cb, data, with_contents); + break; + default: + die(no_backend_error_message); + } +} diff --git a/util/cbmem/cbmem_util.h b/util/cbmem/cbmem_util.h index 8e513c1f32..738e7d35b9 100644 --- a/util/cbmem/cbmem_util.h +++ b/util/cbmem/cbmem_util.h @@ -46,6 +46,62 @@ struct cbmem_console { typedef bool (*cbmem_iterator_callback)(const uint32_t id, const uint64_t physical_address, const uint8_t *buf, const size_t size, void *data); +/* Common CBMEM access API */ + +enum cbmem_drv_backend_type { + CBMEM_DRV_BACKEND_ANY, + CBMEM_DRV_BACKEND_DEVMEM, + CBMEM_DRV_BACKEND_SYSFS, +}; + +/** + * Pick and initialize CBMEM driver. Function can either probe for available drivers or initialize only the selected one. + * + * @param backend preferred CBMEM backend to initialize. + * @param writeable initialize CBMEM driver in writeable mode. Required by devmem driver to modify CBMEM. + * + * @returns true on success, false otherwise. + */ +bool cbmem_drv_init(enum cbmem_drv_backend_type backend, bool writeable); + +/** + * Cleanup and terminate previously initialized CBMEM driver. + * **MUST** be called if cbmem_drv_init() succeeded. + */ +void cbmem_drv_terminate(void); + +/** + * Get CBMEM entry as an allocated buffer. + * + * @param id CBMEM_ID_* value. + * @param buf_out return pointer for the allocated buffer containing entry contents. + * @param size_out size of returned buffer. + * @param addr_out pointer to the output buffer for entry address in physical memory. Optional. + * + * @returns true on success, false otherwise. + */ +bool cbmem_drv_get_cbmem_entry(uint32_t id, uint8_t **buf_out, size_t *size_out, uint64_t *addr_out); + +/** + * Write provided buffer contents to the CBMEM entry. + * + * @param id CBMEM_ID_* value. + * @param buf pointer to the source buffer. + * @param buf_size size of the source buffer. + * + * @returns true on success, false otherwise. + */ +bool cbmem_drv_write_cbmem_entry(uint32_t id, uint8_t *buf, size_t buf_size); + +/** + * Backend-specific function iterating over CBMEM entries. + * + * @param cb user callback function to call during iteration. + * @param data pointer to the context data for the callback. + * @param with_contents tells whether the callback should get NULL (false) or copy of the entry (true). + */ +void cbmem_drv_foreach_cbmem_entry(cbmem_iterator_callback cb, void *data, bool with_contents); + /* API for accessing CBMEM via /dev/mem */ /** @@ -94,3 +150,45 @@ bool cbmem_devmem_write_cbmem_entry(uint32_t id, uint8_t *buf, size_t buf_size); */ void cbmem_devmem_foreach_cbmem_entry(cbmem_iterator_callback cb, void *data, bool with_contents); + +/* API for accessing CBMEM via sysfs entries */ + +/** + * Initialize the sysfs driver. + * + * @returns true on success, false otherwise. + */ +bool cbmem_sysfs_init(void); + +/** + * Get CBMEM entry as an allocated buffer. + * + * @param id CBMEM_ID_* value. + * @param buf_out return pointer for the allocated buffer containing entry contents. + * @param size_out size of returned buffer. + * @param addr_out pointer to the output buffer for entry address in physical memory. Optional. + * + * @returns true on success, false otherwise. + */ +bool cbmem_sysfs_get_cbmem_entry(uint32_t id, uint8_t **buf_out, size_t *size_out, uint64_t *addr_out); + +/** + * Write provided buffer contents to the CBMEM entry. + * + * @param id CBMEM_ID_* value. + * @param buf pointer to the source buffer. + * @param buf_size size of the source buffer. + * + * @returns true on success, false otherwise. + */ +bool cbmem_sysfs_write_cbmem_entry(uint32_t id, uint8_t *buf, size_t buf_size); + +/** + * Backend-specific function iterating over CBMEM entries. + * + * @param cb user callback function to call during iteration. + * @param data pointer to the context data for the callback. + * @param with_contents tells whether the callback should get NULL (false) or copy of the entry (true). + */ +void cbmem_sysfs_foreach_cbmem_entry(cbmem_iterator_callback cb, void *data, + bool with_contents); diff --git a/util/cbmem/sysfs_drv.c b/util/cbmem/sysfs_drv.c new file mode 100644 index 0000000000..2820bbd4ee --- /dev/null +++ b/util/cbmem/sysfs_drv.c @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) || defined(__ANDROID__) +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "cbmem_util.h" + +#define CBMEM_SYSFS_BASE_DIR "/sys/bus/coreboot/devices/" +#define CBMEM_SYSFS_ENTRY_DIR_NAME_PREFIX "cbmem-" +#define CBMEM_SYSFS_ENTRY_DIR_NAME_SCN_FMT CBMEM_SYSFS_ENTRY_DIR_NAME_PREFIX "%8" SCNx32 +#define CBMEM_SYSFS_ENTRY_DIR_NAME_PRI_FMT CBMEM_SYSFS_ENTRY_DIR_NAME_PREFIX "%08" PRIx32 +#define CBMEM_SYSFS_ENTRY_DIR_PATH_PRI_FMT \ + CBMEM_SYSFS_BASE_DIR CBMEM_SYSFS_ENTRY_DIR_NAME_PRI_FMT + +enum cbmem_sysfs_path_type { + CBMEM_SYSFS_PATH_BASE, + CBMEM_SYSFS_PATH_ADDRESS, + CBMEM_SYSFS_PATH_MEM, + CBMEM_SYSFS_PATH_SIZE, +}; + +/* Maximum filesystem path length. */ +#define PATH_MAX_LEN 1024 + +/** + * Create new path for CBMEM entry of a requested type. free() path_out after use. + * + * @param id CBMEM_ID_* value. + * @param t path type. + * @param path_out output pointer to created path. + */ +static void new_sysfs_path(uint32_t id, enum cbmem_sysfs_path_type t, char *path_out, size_t path_out_max_size) +{ + const char *const type2str[] = { + [CBMEM_SYSFS_PATH_BASE] = "", + [CBMEM_SYSFS_PATH_ADDRESS] = "/address", + [CBMEM_SYSFS_PATH_MEM] = "/mem", + [CBMEM_SYSFS_PATH_SIZE] = "/size" + }; + + if (t >= ARRAY_SIZE(type2str)) + die("Incorrect path type requested: %d\n", t); + + if (snprintf(path_out, path_out_max_size, CBMEM_SYSFS_ENTRY_DIR_PATH_PRI_FMT "%s", id, + type2str[t]) <= 0) + die("Unable to create sysfs path string for coreboot table: %#" PRIx32 + ". Path type: %d. Error: %s\n", + id, t, strerror(errno)); +} + +bool cbmem_sysfs_init(void) +{ + char path[PATH_MAX_LEN]; + + for (enum cbmem_sysfs_path_type t = CBMEM_SYSFS_PATH_ADDRESS; t <= CBMEM_SYSFS_PATH_SIZE; ++t) { + new_sysfs_path(CBMEM_ID_CBTABLE, t, path, sizeof(path)); + + FILE *f = fopen(path, "rb"); + if (!f) { + debug("Unable to open path %s for reading. Error: %s\n", path, strerror(errno)); + return false; + } + + uint8_t test_byte = 0; + if (fread(&test_byte, 1, 1, f) != 1) { + debug("Unable to read data from %s. Error: %s\n", path, strerror(errno)); + return false; + } + + fclose(f); + } + return true; +} + +static bool cbmem_sysfs_probe_cbmem_entry(uint32_t id, uint64_t *addr_out, size_t *size_out) +{ + char path[PATH_MAX_LEN]; + + new_sysfs_path(id, CBMEM_SYSFS_PATH_ADDRESS, path, sizeof(path)); + + FILE *address_file = fopen(path, "rb"); + if (!address_file) { + debug("Unable to access CBMEM entry id: %#" PRIx32 + " address file at %s. Error: %s\n", + id, path, strerror(errno)); + return false; + } + + if (fscanf(address_file, "%" SCNx64, addr_out) != 1) { + debug("Read from %s failed.\n", path); + fclose(address_file); + return false; + } + fclose(address_file); + + new_sysfs_path(id, CBMEM_SYSFS_PATH_SIZE, path, sizeof(path)); + + FILE *size_file = fopen(path, "rb"); + if (!size_file) { + debug("Unable to access CBMEM entry id: %#" PRIx32 + " size file at %s. Error: %s\n", + id, path, strerror(errno)); + return false; + } + + if (fscanf(size_file, "%zx", size_out) != 1) { + debug("Read from %s failed.\n", path); + fclose(size_file); + *addr_out = 0; + return false; + } + fclose(size_file); + + return true; +} + +static void fetch_cbmem_entry(const uint32_t id, const size_t size, uint8_t **buf_out) +{ + char path[PATH_MAX_LEN]; + + new_sysfs_path(id, CBMEM_SYSFS_PATH_MEM, path, sizeof(path)); + + FILE *mem_file = fopen(path, "rb"); + if (!mem_file) + die("Unable to open mem file for CBMEM entry id: %#" PRIx32 + " at %s. Error: %s\n", + id, path, strerror(errno)); + + *buf_out = malloc(size); + if (!buf_out) + die("Unable to allocate memory for CBMEM entry id: %#" PRIx32 + " of size: %zuB.\n", + id, size); + + if (fread(*buf_out, 1, size, mem_file) != size) + die("Unable to correctly read memory of CBMEM entry id: %#" PRIx32 + " at %s. Error: %s\n", + id, path, strerror(errno)); + + fclose(mem_file); +} + +bool cbmem_sysfs_get_cbmem_entry(uint32_t id, uint8_t **buf_out, size_t *size_out, uint64_t *addr_out) +{ + uint64_t addr; + size_t size; + + if (!cbmem_sysfs_probe_cbmem_entry(id, &addr, &size)) { + debug("CBMEM entry id: %#" PRIx32 " not found.\n", id); + return false; + } + + fetch_cbmem_entry(id, size, buf_out); + if (size_out) + *size_out = size; + if (addr_out) + *addr_out = addr; + return true; +} + +bool cbmem_sysfs_write_cbmem_entry(uint32_t id, uint8_t *buf, size_t buf_size) +{ + char path[PATH_MAX_LEN]; + uint64_t addr; + size_t size; + + if (!cbmem_sysfs_probe_cbmem_entry(id, &addr, &size)) { + debug("Unable to find CBMEM entry id: %#" PRIx32 "\n", id); + return false; + } + + if (buf_size > size) + die("Attempting to write %zu bytes to CBMEM entry id: %#" PRIx32 + " of %zu bytes. Operation not possible.\n", + buf_size, id, size); + + new_sysfs_path(id, CBMEM_SYSFS_PATH_MEM, path, sizeof(path)); + + FILE *mem_file = fopen(path, "rb+"); + if (!mem_file) + die("Unable to open mem file for CBMEM entry id: %#" PRIx32 + " at %s. Error: %s\n", + id, path, strerror(errno)); + + if (fwrite(buf, 1, buf_size, mem_file) != buf_size) + die("Unable to correctly write memory of CBMEM entry id: %#" PRIx32 + " at %s. Error: %s\n", + id, path, strerror(errno)); + + fclose(mem_file); + + return true; +} + +void cbmem_sysfs_foreach_cbmem_entry(cbmem_iterator_callback cb, void *data, bool with_contents) +{ + DIR *dir; + struct dirent *entry; + + if ((dir = opendir(CBMEM_SYSFS_BASE_DIR)) == NULL) + die("Unable to open directory containing CBMEM entries. Path: %s. Error: %s\n", + CBMEM_SYSFS_BASE_DIR, strerror(errno)); + + while ((entry = readdir(dir)) != NULL) { + debug("Checking path: %s%s\n", CBMEM_SYSFS_BASE_DIR, entry->d_name); + /* Check directory name prefix */ + if (strncmp(entry->d_name, CBMEM_SYSFS_ENTRY_DIR_NAME_PREFIX, + strlen(CBMEM_SYSFS_ENTRY_DIR_NAME_PREFIX)) != 0) + continue; + + uint32_t id = 0; + /* Extract CBMEM entry id from the directory name. + If it fails, then directory is not a correct entry. */ + if (sscanf(entry->d_name, CBMEM_SYSFS_ENTRY_DIR_NAME_SCN_FMT, &id) != 1) + continue; + + uint64_t addr = 0; + uint64_t size = 0; + uint8_t *buf; + + /* If entry was not found or previously matched directory + does not contain necessary files, then omit the entry. */ + if (!cbmem_sysfs_probe_cbmem_entry(id, &addr, &size)) + continue; + + if (with_contents) + fetch_cbmem_entry(id, size, &buf); + + debug("Invoking callback on %s%s\n", CBMEM_SYSFS_BASE_DIR, entry->d_name); + + const bool res = cb(id, addr, buf, size, data); + + if (with_contents) + free(buf); + + /* Finish iteration if callback requested it. */ + if (res) + break; + } + + closedir(dir); +}