util/cbmem: Extract devmem and common code to separate files

Extract devmem-specific code to a separate file providing unified API.
Move hexdump() and cbmem_print_entry() to common.c.
Create common function for getting coreboot table entries. This can be
adjusted later to use higher-level API that selects appropriate backend.

BUG=b:391874512
TEST=cbmem -l; cbmem -x; cbmem -r 434f4e53; cbmem -t

Change-Id: Ic11f0659833e03324f6909fa3c1d62c36988b7b7
Signed-off-by: Jakub Czapiga <czapiga@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/86557
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Julius Werner <jwerner@chromium.org>
This commit is contained in:
Jakub Czapiga 2025-04-24 18:15:06 +00:00 committed by Matt DeVillier
commit dd19f6bc5a
4 changed files with 866 additions and 780 deletions

View file

@ -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 $(COMMONLIB)/bsd/ipchksum.o
OBJS = $(PROGRAM).o devmem_drv.o $(COMMONLIB)/bsd/ipchksum.o
all: $(PROGRAM)

File diff suppressed because it is too large Load diff

96
util/cbmem/cbmem_util.h Normal file
View file

@ -0,0 +1,96 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <commonlib/bsd/cbmem_id.h>
#include <commonlib/bsd/helpers.h>
extern int cbmem_util_verbose;
#define debug(x...) \
do { \
if (cbmem_util_verbose > 1) \
printf("[%s:%d %s()] ", __FILE__, __LINE__, __func__); \
if (cbmem_util_verbose) \
printf(x); \
} while (0)
#define die(x...) \
do { \
fprintf(stderr, x); \
abort(); \
} while (0)
struct cbmem_console {
uint32_t size;
uint32_t cursor;
uint8_t body[];
} __packed;
#define CBMC_CURSOR_MASK ((1 << 28) - 1)
#define CBMC_OVERFLOW (1 << 31)
/**
* Function pointer type used by CBMEM foreach iteration calls.
*
* @param id CBMEM_ID_* value.
* @param physical_address CBMEM entry address in physical memory.
* @param buf heap-allocated buffer with CBMEM entry contents.
* @param size is the size of CBMEM entry in bytes.
* @param data callback-specific context data.
*
* @returns true if iteration should finish, false if it should continue.
*/
typedef bool (*cbmem_iterator_callback)(const uint32_t id, const uint64_t physical_address,
const uint8_t *buf, const size_t size, void *data);
/* API for accessing CBMEM via /dev/mem */
/**
* Initialize the driver.
*
* @param writeable tries to map CBMEM in R/W mode.
*
* @returns true on success, false otherwise.
*/
bool cbmem_devmem_init(bool writeable);
/**
* Cleanup and terminate the driver. **MUST** be called if cbmem_devmem_init succeeded before.
*/
void cbmem_devmem_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. Optional.
* @param addr_out pointer to the output buffer for entry address in physical memory. Optional.
*
* @returns true on success, false otherwise.
*/
bool cbmem_devmem_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_devmem_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_devmem_foreach_cbmem_entry(cbmem_iterator_callback cb, void *data,
bool with_contents);

675
util/cbmem/devmem_drv.c Normal file
View file

@ -0,0 +1,675 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <alloca.h>
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <endian.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <commonlib/bsd/cbmem_id.h>
#include <commonlib/bsd/ipchksum.h>
#include <commonlib/coreboot_tables.h>
#include <commonlib/helpers.h>
#include <commonlib/timestamp_serialized.h>
#include <commonlib/tpm_log_serialized.h>
#ifdef __OpenBSD__
#include <sys/param.h>
#include <sys/sysctl.h>
#endif
#include "cbmem_util.h"
struct mapping {
void *virt;
size_t offset;
size_t virt_size;
unsigned long long phys;
size_t size;
};
/* File handle used to access /dev/mem */
static int mem_fd;
static struct mapping lbtable_mapping;
static unsigned long long system_page_size(void)
{
static unsigned long long page_size;
if (!page_size)
page_size = getpagesize();
return page_size;
}
static inline size_t size_to_mib(size_t sz)
{
return sz >> 20;
}
/* Return mapping of physical address requested. */
static void *mapping_virt(const struct mapping *mapping)
{
char *v = mapping->virt;
if (v == NULL)
return NULL;
return v + mapping->offset;
}
/* Returns virtual address on success, NULL on error. mapping is filled in. */
static void *map_memory_with_prot(struct mapping *mapping, unsigned long long phys, size_t sz,
int prot)
{
void *v;
unsigned long long page_size;
page_size = system_page_size();
mapping->virt = NULL;
mapping->offset = phys % page_size;
mapping->virt_size = sz + mapping->offset;
mapping->size = sz;
mapping->phys = phys;
if (size_to_mib(mapping->virt_size) == 0) {
debug("Mapping %zuB of physical memory at 0x%llx (requested 0x%llx).\n",
mapping->virt_size, phys - mapping->offset, phys);
} else {
debug("Mapping %zuMB of physical memory at 0x%llx (requested 0x%llx).\n",
size_to_mib(mapping->virt_size), phys - mapping->offset, phys);
}
v = mmap(NULL, mapping->virt_size, prot, MAP_SHARED, mem_fd, phys - mapping->offset);
if (v == MAP_FAILED) {
debug("Mapping failed %zuB of physical memory at 0x%llx.\n", mapping->virt_size,
phys - mapping->offset);
return NULL;
}
mapping->virt = v;
if (mapping->offset != 0)
debug(" ... padding virtual address with 0x%zx bytes.\n", mapping->offset);
return mapping_virt(mapping);
}
/* Convenience helper for the common case of read-only mappings. */
static const void *map_memory(struct mapping *mapping, unsigned long long phys, size_t sz)
{
return map_memory_with_prot(mapping, phys, sz, PROT_READ);
}
/* Returns 0 on success, < 0 on error. mapping is cleared if successful. */
static int unmap_memory(struct mapping *mapping)
{
if (mapping->virt == NULL)
return -1;
munmap(mapping->virt, mapping->virt_size);
mapping->virt = NULL;
mapping->offset = 0;
mapping->virt_size = 0;
return 0;
}
/*
* Some architectures map /dev/mem memory in a way that doesn't support
* unaligned accesses. Most normal libc memcpy()s aren't safe to use in this
* case, so build our own which makes sure to never do unaligned accesses on
* *src (*dest is fine since we never map /dev/mem for writing).
*/
static void *aligned_memcpy(void *dest, const void *src, size_t n)
{
uint8_t *d = dest;
const volatile uint8_t *s = src; /* volatile to prevent optimization */
while ((uintptr_t)s & (sizeof(size_t) - 1)) {
if (n-- == 0)
return dest;
*d++ = *s++;
}
while (n >= sizeof(size_t)) {
*(size_t *)d = *(const volatile size_t *)s;
d += sizeof(size_t);
s += sizeof(size_t);
n -= sizeof(size_t);
}
while (n-- > 0)
*d++ = *s++;
return dest;
}
/* Return < 0 on error, 0 on success. */
static int parse_cbtable(uint64_t address, size_t table_size)
{
const uint8_t *buf;
struct mapping header_mapping;
size_t req_size;
size_t i;
req_size = table_size;
/* Default to 4 KiB search space. */
if (req_size == 0)
req_size = 4 * 1024;
debug("Looking for coreboot table at %" PRIx64 " %zd bytes.\n", address, req_size);
buf = map_memory(&header_mapping, address, req_size);
if (!buf)
return -1;
/* look at every 16 bytes */
for (i = 0; i <= req_size - sizeof(struct lb_header); i += 16) {
const struct lb_header *lbh;
struct mapping table_mapping;
lbh = (const struct lb_header *)&buf[i];
if (memcmp(lbh->signature, "LBIO", sizeof(lbh->signature)) ||
!lbh->header_bytes || ipchksum(lbh, sizeof(*lbh))) {
continue;
}
/* Map in the whole table to parse. */
if (!map_memory(&table_mapping, address + i,
lbh->header_bytes + lbh->table_bytes)) {
debug("Couldn't map in table\n");
continue;
}
const uint8_t *table_contents =
&((uint8_t *)mapping_virt(&table_mapping))[lbh->header_bytes];
if (ipchksum(table_contents, lbh->table_bytes) !=
lbh->table_checksum) {
debug("Signature found, but wrong checksum.\n");
unmap_memory(&table_mapping);
continue;
}
debug("Found at %#" PRIx64 "\n", address + i);
const struct lb_record *lbr_p;
for (size_t offset = 0; offset < lbh->table_bytes; offset += lbr_p->size) {
lbr_p = (const struct lb_record *)&table_contents[offset];
debug(" coreboot table entry 0x%02x\n", lbr_p->tag);
if (lbr_p->tag != LB_TAG_FORWARD)
continue;
/* This is a forwarding entry. Repeat the search at the new address. */
struct lb_forward lbf_p = *(const struct lb_forward *)lbr_p;
debug(" Found forwarding entry.\n");
const uint64_t next_addr = lbf_p.forward;
unmap_memory(&header_mapping);
unmap_memory(&table_mapping);
return parse_cbtable(next_addr, 0);
}
debug("correct coreboot table found.\n");
unmap_memory(&header_mapping);
lbtable_mapping = table_mapping;
return 0;
}
unmap_memory(&header_mapping);
return -1;
}
#if defined(__arm__) || defined(__aarch64__)
static void dt_update_cells(const char *name, int *addr_cells_ptr, int *size_cells_ptr)
{
if (*addr_cells_ptr >= 0 && *size_cells_ptr >= 0)
return;
int buffer;
size_t nlen = strlen(name);
char *prop = alloca(nlen + sizeof("/#address-cells"));
strcpy(prop, name);
if (*addr_cells_ptr < 0) {
strcpy(prop + nlen, "/#address-cells");
int fd = open(prop, O_RDONLY);
if (fd < 0 && errno != ENOENT) {
perror(prop);
} else if (fd >= 0) {
if (read(fd, &buffer, sizeof(int)) < 0)
perror(prop);
else
*addr_cells_ptr = ntohl(buffer);
close(fd);
}
}
if (*size_cells_ptr < 0) {
strcpy(prop + nlen, "/#size-cells");
int fd = open(prop, O_RDONLY);
if (fd < 0 && errno != ENOENT) {
perror(prop);
} else if (fd >= 0) {
if (read(fd, &buffer, sizeof(int)) < 0)
perror(prop);
else
*size_cells_ptr = ntohl(buffer);
close(fd);
}
}
}
static char *dt_find_compat(const char *parent, const char *compat, int *addr_cells_ptr,
int *size_cells_ptr)
{
char *ret = NULL;
struct dirent *entry;
DIR *dir;
if (!(dir = opendir(parent))) {
perror(parent);
return NULL;
}
/* Loop through all files in the directory (DT node). */
while ((entry = readdir(dir))) {
/* We only care about compatible props or subnodes. */
if (entry->d_name[0] == '.' ||
!((entry->d_type & DT_DIR) || !strcmp(entry->d_name, "compatible")))
continue;
/* Assemble the file name (on the stack, for speed). */
size_t plen = strlen(parent);
char *name = alloca(plen + strlen(entry->d_name) + 2);
strcpy(name, parent);
name[plen] = '/';
strcpy(name + plen + 1, entry->d_name);
/* If it's a subnode, recurse. */
if (entry->d_type & DT_DIR) {
ret = dt_find_compat(name, compat, addr_cells_ptr, size_cells_ptr);
/* There is only one matching node to find, abort. */
if (ret) {
/* Gather cells values on the way up. */
dt_update_cells(parent, addr_cells_ptr, size_cells_ptr);
break;
}
continue;
}
/* If it's a compatible string, see if it's the right one. */
int fd = open(name, O_RDONLY);
int clen = strlen(compat);
char *buffer = alloca(clen + 1);
if (fd < 0) {
perror(name);
continue;
}
if (read(fd, buffer, clen + 1) < 0) {
perror(name);
close(fd);
continue;
}
close(fd);
if (!strcmp(compat, buffer)) {
/* Initialize these to "unset" for the way up. */
*addr_cells_ptr = *size_cells_ptr = -1;
/* Can't leave string on the stack or we'll lose it! */
ret = strdup(parent);
break;
}
}
closedir(dir);
return ret;
}
#endif /* defined(__arm__) || defined(__aarch64__) */
bool cbmem_devmem_init(bool writeable)
{
mem_fd = open("/dev/mem", writeable ? O_RDWR : O_RDONLY, 0);
if (mem_fd < 0) {
fprintf(stderr, "Failed to gain memory access: %s\n", strerror(errno));
return false;
}
#if defined(__arm__) || defined(__aarch64__)
int addr_cells, size_cells;
char *coreboot_node =
dt_find_compat("/proc/device-tree", "coreboot", &addr_cells, &size_cells);
if (!coreboot_node) {
fprintf(stderr, "Could not find 'coreboot' compatible node!\n");
return false;
}
if (addr_cells < 0) {
fprintf(stderr, "Warning: no #address-cells node in tree!\n");
addr_cells = 1;
}
int nlen = strlen(coreboot_node);
char *reg = alloca(nlen + sizeof("/reg"));
strcpy(reg, coreboot_node);
strcpy(reg + nlen, "/reg");
free(coreboot_node);
int fd = open(reg, O_RDONLY);
if (fd < 0) {
perror(reg);
return false;
}
int i;
size_t size_to_read = addr_cells * 4 + size_cells * 4;
uint8_t *dtbuffer = alloca(size_to_read);
if (read(fd, dtbuffer, size_to_read) < 0) {
perror(reg);
return false;
}
close(fd);
/* No variable-length byte swap function anywhere in C... how sad. */
uint64_t baseaddr = 0;
for (i = 0; i < addr_cells * 4; i++) {
baseaddr <<= 8;
baseaddr |= *dtbuffer;
dtbuffer++;
}
uint64_t cb_table_size = 0;
for (i = 0; i < size_cells * 4; i++) {
cb_table_size <<= 8;
cb_table_size |= *dtbuffer;
dtbuffer++;
}
parse_cbtable(baseaddr, cb_table_size);
#else
unsigned long long possible_base_addresses[] = {0, 0xf0000};
/* Find and parse coreboot table */
for (size_t j = 0; j < ARRAY_SIZE(possible_base_addresses); j++) {
if (!parse_cbtable(possible_base_addresses[j], 0))
break;
}
#endif
if (mapping_virt(&lbtable_mapping) == NULL) {
debug("Table not found.\n");
return false;
}
return true;
}
void cbmem_devmem_terminate(void)
{
unmap_memory(&lbtable_mapping);
close(mem_fd);
mem_fd = -1;
}
/* This is a work-around for a nasty problem introduced by initially having
* pointer sized entries in the lb_cbmem_ref structures. This caused problems
* on 64bit x86 systems because coreboot is 32bit on those systems.
* When the problem was found, it was corrected, but there are a lot of
* systems out there with a firmware that does not produce the right
* lb_cbmem_ref structure. Hence we try to autocorrect this issue here.
*/
static struct lb_cbmem_ref parse_cbmem_ref(const struct lb_cbmem_ref *cbmem_ref)
{
struct lb_cbmem_ref ret;
aligned_memcpy(&ret, cbmem_ref, sizeof(ret));
if (cbmem_ref->size < sizeof(*cbmem_ref))
ret.cbmem_addr = (uint32_t)ret.cbmem_addr;
debug(" cbmem_addr = %" PRIx64 "\n", ret.cbmem_addr);
return ret;
}
static uint32_t cbmem_id_to_lb_tag(uint32_t tag)
{
/* Minimal subset. Expand based on the CBMEM to coreboot table
records mapping in lib/coreboot_table.c */
switch (tag) {
case CBMEM_ID_TIMESTAMP:
return LB_TAG_TIMESTAMPS;
case CBMEM_ID_CONSOLE:
return LB_TAG_CBMEM_CONSOLE;
case CBMEM_ID_TPM_CB_LOG:
return LB_TAG_TPM_CB_LOG;
}
return LB_TAG_UNUSED;
}
static bool cbmem_devmem_probe_cbmem_entry(uint32_t id, uint64_t *addr_out, size_t *size_out)
{
const uint8_t *table;
const struct lb_header *lbh;
const uint32_t legacy_tag = cbmem_id_to_lb_tag(id);
struct lb_cbmem_ref *ref = NULL;
const struct lb_record *lbr = NULL;
table = mapping_virt(&lbtable_mapping);
if (table == NULL)
die("No correct coreboot table found\n");
lbh = (const struct lb_header *)table;
table = &table[lbh->header_bytes];
/* Fast track for the coreboot table. */
if (id == CBMEM_ID_CBTABLE) {
*addr_out = lbtable_mapping.phys;
*size_out = lbtable_mapping.size;
return true;
}
for (size_t offset = 0; offset < lbh->table_bytes - sizeof(struct lb_cbmem_entry);) {
lbr = (const void *)(table + offset);
offset += lbr->size;
/* Store coreboot table entry for later if CBMEM entry does not exist.
CBMEM entry stores size including the reserved area, so prefer it,
so more potential data and/or space is available. */
if (legacy_tag != LB_TAG_UNUSED && lbr->tag == legacy_tag)
ref = (struct lb_cbmem_ref *)lbr;
if (lbr->tag != LB_TAG_CBMEM_ENTRY)
continue;
struct lb_cbmem_entry lbe;
aligned_memcpy(&lbe, lbr, sizeof(lbe));
if (lbe.id != id)
continue;
*addr_out = lbe.address;
*size_out = lbe.entry_size;
return true;
}
/* No mapping and/or no potential reference means that
the requested entry does not exit. */
if (legacy_tag == LB_TAG_UNUSED || ref == NULL)
return false;
debug("Found coreboot table record equivalent of CBMEM entry id: %#x, tag: %#x\n", id,
legacy_tag);
const struct lb_cbmem_ref lbc = parse_cbmem_ref(ref);
size_t header_map_size = 0;
/* Process legacy coreboot table entries */
switch (lbc.tag) {
case LB_TAG_TIMESTAMPS:
header_map_size = sizeof(struct timestamp_table);
break;
case LB_TAG_CBMEM_CONSOLE:
header_map_size = sizeof(struct cbmem_console);
break;
case LB_TAG_TPM_CB_LOG:
header_map_size = sizeof(struct tpm_cb_log_table);
break;
}
struct mapping entry_mapping;
const void *entry_header = NULL;
entry_header = map_memory(&entry_mapping, lbc.cbmem_addr, header_map_size);
if (!entry_header)
die("Unable to map header for coreboot table entry id: %#x\n", legacy_tag);
*addr_out = lbc.cbmem_addr;
switch (legacy_tag) {
case LB_TAG_TIMESTAMPS: {
const struct timestamp_table *tst_p = entry_header;
*size_out = sizeof(*tst_p) + tst_p->num_entries * sizeof(tst_p->entries[0]);
break;
}
case LB_TAG_CBMEM_CONSOLE: {
const struct cbmem_console *console_p = entry_header;
*size_out = sizeof(*console_p) + console_p->size;
break;
}
case LB_TAG_TPM_CB_LOG: {
const struct tpm_cb_log_table *tclt_p = entry_header;
*size_out = sizeof(*tclt_p) + tclt_p->num_entries * sizeof(tclt_p->entries[0]);
break;
}
}
unmap_memory(&entry_mapping);
return true;
}
static void fetch_cbmem_entry(const uint32_t id, const uint64_t addr, const size_t size,
uint8_t **buf_out)
{
struct mapping cbmem_mapping;
const uint8_t *buf = map_memory(&cbmem_mapping, addr, size);
if (!buf)
die("Unable to map CBMEM entry id: %#x, size: %zu\n", id, size);
*buf_out = malloc(size);
if (!*buf_out) {
unmap_memory(&cbmem_mapping);
die("Unable to allocate memory for CBMEM entry id: %#x, size: %zu\n", id, size);
}
aligned_memcpy(*buf_out, buf, size);
unmap_memory(&cbmem_mapping);
}
bool cbmem_devmem_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_devmem_probe_cbmem_entry(id, &addr, &size)) {
debug("CBMEM entry not found. CBMEM id: %#x\n", id);
return false;
}
fetch_cbmem_entry(id, addr, size, buf_out);
if (size_out)
*size_out = size;
if (addr_out)
*addr_out = addr;
return true;
}
bool cbmem_devmem_write_cbmem_entry(uint32_t id, uint8_t *buf, size_t buf_size)
{
uint64_t addr = 0;
size_t size = 0;
uint8_t *origin_buf = NULL;
struct mapping mapping;
if (!cbmem_devmem_probe_cbmem_entry(id, &addr, &size)) {
debug("CBMEM entry not found. CBMEM id: %#x\n", id);
return false;
}
if (buf_size > size)
die("Attempting to write %zu bytes to CBMEM entry id: %#x of %zu bytes. Operation not possible.\n",
buf_size, id, size);
origin_buf = map_memory_with_prot(&mapping, addr, size, PROT_READ | PROT_WRITE);
if (!origin_buf)
die("Unable to map CBMEM entry id: %#x, size: %zu for read-write access.\n", id,
size);
aligned_memcpy(origin_buf, buf, buf_size);
unmap_memory(&mapping);
return true;
}
void cbmem_devmem_foreach_cbmem_entry(cbmem_iterator_callback cb, void *data,
bool with_contents)
{
uint8_t *table = NULL;
size_t table_size = 0;
if (!cbmem_devmem_get_cbmem_entry(CBMEM_ID_CBTABLE, &table, &table_size, NULL))
die("coreboot table not found.\n");
const struct lb_header *lbh = (const struct lb_header *)table;
const struct lb_record *lbr = NULL;
bool should_iteration_end = false;
size_t offset = 0;
while (offset < lbh->table_bytes - sizeof(struct lb_cbmem_entry) &&
should_iteration_end == false) {
lbr = (const struct lb_record *)&table[lbh->header_bytes + offset];
offset += lbr->size;
if (lbr->tag != LB_TAG_CBMEM_ENTRY)
continue;
const struct lb_cbmem_entry *lbe = (const struct lb_cbmem_entry *)lbr;
uint8_t *buf = NULL;
if (with_contents)
fetch_cbmem_entry(lbe->id, lbe->address, lbe->entry_size, &buf);
should_iteration_end = cb(lbe->id, lbe->address, buf, lbe->entry_size, data);
if (with_contents)
free(buf);
}
free(table);
}