/* 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; size_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); }