drivers/elog: use region_device for mirroring into ram

A region_device can be used to represent the in-memory mirror
of the event log. The region_device infrastructure has builtin
bounds checking so there's no need to duplicate that. In addition,
it allows for removing much of the math juggling for the buffer
size, etc.

BUG=chrome-os-partner:55932

Change-Id: Ic7fe9466019640b449257c5905ed919ac522bb58
Signed-off-by: Aaron Durbin <adurbin@chromium.org>
Reviewed-on: https://review.coreboot.org/16097
Tested-by: build bot (Jenkins)
Reviewed-by: Furquan Shaikh <furquan@google.com>
This commit is contained in:
Aaron Durbin 2016-08-04 14:25:59 -05:00 committed by Martin Roth
parent 422c440fa6
commit 281c994ce2
2 changed files with 176 additions and 82 deletions

View File

@ -44,7 +44,6 @@
/*
* Static variables for ELOG state
*/
static struct elog_area *elog_area;
static u16 total_size;
static u16 log_size; /* excluding header */
static u32 flash_base;
@ -59,6 +58,8 @@ static u16 next_event_offset; /* from end of header */
static u16 event_count;
static struct spi_flash *elog_spi;
/* Device that mirrors the eventlog in memory. */
static struct mem_region_device mirror_dev;
static enum {
ELOG_UNINITIALIZED = 0,
@ -66,13 +67,27 @@ static enum {
ELOG_BROKEN,
} elog_initialized = ELOG_UNINITIALIZED;
static inline struct region_device *mirror_dev_get(void)
{
return &mirror_dev.rdev;
}
/*
* Pointer to an event log header in the event data area
*/
static inline struct event_header*
elog_get_event_base(u32 offset)
static struct event_header *elog_get_event_buffer(size_t offset, size_t size)
{
return (struct event_header *)&elog_area->data[offset];
/*
* Events are appended relative to the end of the header. Update
* offset to include the header size.
*/
offset += sizeof(struct elog_header);
return rdev_mmap(mirror_dev_get(), offset, size);
}
static void elog_put_event_buffer(struct event_header *event)
{
rdev_munmap(mirror_dev_get(), event);
}
/*
@ -103,17 +118,25 @@ static u8 elog_checksum_event(struct event_header *event)
*/
static int elog_is_buffer_clear(size_t offset)
{
size_t size = total_size - offset;
u8 *current = offset + (u8 *)elog_area;
u8 *end = current + size;
size_t i;
const struct region_device *rdev = mirror_dev_get();
size_t size = region_device_sz(rdev) - offset;
uint8_t *buffer = rdev_mmap(rdev, offset, size);
int ret = 1;
elog_debug("elog_is_buffer_clear(offset=%zu size=%zu)\n", offset, size);
for (; current != end; current++) {
if (*current != ELOG_TYPE_EOL)
return 0;
if (buffer == NULL)
return 0;
for (i = 0; i < size; i++) {
if (buffer[i] != ELOG_TYPE_EOL) {
ret = 0;
break;
}
}
return 1;
rdev_munmap(rdev, buffer);
return ret;
}
static int elog_event_buffer_is_clear(size_t offset)
@ -144,13 +167,22 @@ static int elog_is_area_valid(void)
}
/*
* Verify the contents of an ELOG Header structure
* Verify if the mirrored elog structure is valid.
* Returns 1 if the header is valid, 0 otherwise
*/
static int elog_is_header_valid(struct elog_header *header)
static int elog_is_header_valid(void)
{
struct elog_header *header;
elog_debug("elog_is_header_valid()\n");
header = rdev_mmap(mirror_dev_get(), 0, sizeof(*header));
if (header == NULL) {
printk(BIOS_ERR, "ELOG: could not map header.\n");
return 0;
}
if (header->magic != ELOG_SIGNATURE) {
printk(BIOS_ERR, "ELOG: header magic 0x%X != 0x%X\n",
header->magic, ELOG_SIGNATURE);
@ -172,45 +204,38 @@ static int elog_is_header_valid(struct elog_header *header)
/*
* Validate the event header and data.
*/
static int elog_is_event_valid(u32 offset)
static size_t elog_is_event_valid(size_t offset)
{
uint8_t checksum;
struct event_header *event;
uint8_t len;
const size_t len_offset = offsetof(struct event_header, length);
const size_t size = sizeof(len);
event = elog_get_event_base(offset);
if (!event)
return 0;
/* Validate event length */
if ((offsetof(struct event_header, type) +
sizeof(event->type) - 1 + offset) >= log_size)
return 0;
/* End of event marker has been found */
if (event->type == ELOG_TYPE_EOL)
return 0;
/* Check if event fits in area */
if ((offsetof(struct event_header, length) +
sizeof(event->length) - 1 + offset) >= log_size)
return 0;
/*
* If the current event length + the current offset exceeds
* the area size then the event area is corrupt.
*/
if ((event->length + offset) >= log_size)
/* Read and validate length. */
if (rdev_readat(mirror_dev_get(), &len, offset + len_offset, size) < 0)
return 0;
/* Event length must be at least header size + checksum */
if (event->length < (sizeof(*event) + 1))
if (len < (sizeof(*event) + sizeof(checksum)))
return 0;
if (len > MAX_EVENT_SIZE)
return 0;
event = elog_get_event_buffer(offset, len);
if (!event)
return 0;
/* If event checksum is invalid the area is corrupt */
if (elog_checksum_event(event) != 0)
checksum = elog_checksum_event(event);
elog_put_event_buffer(event);
if (checksum != 0)
return 0;
/* Event is valid */
return 1;
return len;
}
/*
@ -221,11 +246,12 @@ static int elog_is_event_valid(u32 offset)
static void elog_flash_write(size_t offset, size_t size)
{
void *address;
const struct region_device *rdev = mirror_dev_get();
if (!size || !elog_spi)
return;
address = offset + (u8 *)elog_area;
address = rdev_mmap(rdev, offset, size);
/* Ensure offset is absolute. */
offset += flash_base;
@ -233,8 +259,13 @@ static void elog_flash_write(size_t offset, size_t size)
elog_debug("elog_flash_write(address=0x%p offset=0x%08x size=%u)\n",
address, offset, size);
if (address == NULL)
return;
/* Write the data to flash */
elog_spi->write(elog_spi, offset, size, address);
rdev_munmap(rdev, address);
}
static void elog_append_event(size_t offset, size_t event_size)
@ -270,34 +301,38 @@ static void elog_update_event_buffer_state(void)
{
u32 count = 0;
u32 offset = 0;
struct event_header *event;
elog_debug("elog_update_event_buffer_state()\n");
/* Go through each event and validate it */
while (1) {
event = elog_get_event_base(offset);
uint8_t type;
const size_t type_offset = offsetof(struct event_header, type);
size_t len;
const size_t size = sizeof(type);
size_t abs_offset = offset + sizeof(struct elog_header);
/* Do not de-reference anything past the area length */
if ((offsetof(struct event_header, type) +
sizeof(event->type) - 1 + offset) >= log_size) {
if (rdev_readat(mirror_dev_get(), &type,
abs_offset + type_offset, size) < 0) {
event_buffer_state = ELOG_EVENT_BUFFER_CORRUPTED;
break;
}
/* The end of the event marker has been found */
if (event->type == ELOG_TYPE_EOL)
if (type == ELOG_TYPE_EOL)
break;
/* Validate the event */
if (!elog_is_event_valid(offset)) {
len = elog_is_event_valid(abs_offset);
if (!len) {
event_buffer_state = ELOG_EVENT_BUFFER_CORRUPTED;
break;
}
/* Move to the next event */
count++;
offset += event->length;
offset += len;
}
/* Ensure the remaining buffer is empty */
@ -312,13 +347,17 @@ static void elog_update_event_buffer_state(void)
static void elog_scan_flash(void)
{
elog_debug("elog_scan_flash()\n");
void *mirror_buffer;
const struct region_device *rdev = mirror_dev_get();
area_state = ELOG_AREA_UNDEFINED;
header_state = ELOG_HEADER_INVALID;
event_buffer_state = ELOG_EVENT_BUFFER_OK;
/* Fill memory buffer by reading from SPI */
elog_spi->read(elog_spi, flash_base, total_size, elog_area);
mirror_buffer = rdev_mmap_full(rdev);
elog_spi->read(elog_spi, flash_base, total_size, mirror_buffer);
rdev_munmap(rdev, mirror_buffer);
next_event_offset = 0;
event_count = 0;
@ -332,7 +371,7 @@ static void elog_scan_flash(void)
area_state = ELOG_AREA_HAS_CONTENT;
/* Validate the header */
if (!elog_is_header_valid(&elog_area->header)) {
if (!elog_is_header_valid()) {
header_state = ELOG_HEADER_INVALID;
return;
}
@ -341,55 +380,111 @@ static void elog_scan_flash(void)
elog_update_event_buffer_state();
}
static size_t elog_write_header_in_mirror(void)
{
static const struct elog_header header = {
.magic = ELOG_SIGNATURE,
.version = ELOG_VERSION,
.header_size = sizeof(struct elog_header),
.reserved = {
[0] = ELOG_TYPE_EOL,
[1] = ELOG_TYPE_EOL,
},
};
rdev_writeat(mirror_dev_get(), &header, 0, sizeof(header));
return sizeof(header);
}
static void elog_prepare_empty(void)
{
struct elog_header *header;
elog_debug("elog_prepare_empty()\n");
/* Write out the header */
header = &elog_area->header;
header->magic = ELOG_SIGNATURE;
header->version = ELOG_VERSION;
header->header_size = sizeof(struct elog_header);
header->reserved[0] = ELOG_TYPE_EOL;
header->reserved[1] = ELOG_TYPE_EOL;
elog_flash_write(0, header->header_size);
size_t size = elog_write_header_in_mirror();
elog_flash_write(0, size);
elog_scan_flash();
}
static void elog_move_events_to_front(size_t offset, size_t size)
{
void *src;
void *dest;
size_t start_offset = sizeof(struct elog_header);
const struct region_device *rdev = mirror_dev_get();
/*
* Events are appended relative to the end of the header. Update
* offset to include the header size.
*/
offset += start_offset;
src = rdev_mmap(rdev, offset, size);
dest = rdev_mmap(rdev, start_offset, size);
if (src == NULL || dest == NULL) {
printk(BIOS_ERR, "ELOG: failure moving events!\n");
rdev_munmap(rdev, dest);
rdev_munmap(rdev, src);
return;
}
/* Move the events to the front. */
memmove(dest, src, size);
rdev_munmap(rdev, dest);
rdev_munmap(rdev, src);
/* Mark EOL for previously used buffer until the end. */
offset = start_offset + size;
size = region_device_sz(rdev) - offset;
dest = rdev_mmap(rdev, offset, size);
if (dest == NULL) {
printk(BIOS_ERR, "ELOG: failure filling EOL!\n");
return;
}
memset(dest, ELOG_TYPE_EOL, size);
rdev_munmap(rdev, dest);
}
/*
* Shrink the log, deleting old entries and moving the
* remaining ones to the front of the log.
*/
static int elog_shrink(void)
{
struct event_header *event;
u16 discard_count = 0;
const struct region_device *rdev = mirror_dev_get();
u16 offset = 0;
u16 new_size = 0;
elog_debug("elog_shrink()\n");
while (1) {
const size_t type_offset = offsetof(struct event_header, type);
const size_t len_offset = offsetof(struct event_header, length);
const size_t size = sizeof(uint8_t);
uint8_t type;
uint8_t len;
size_t abs_offset = offset + sizeof(struct elog_header);
/* Next event has exceeded constraints */
if (offset > shrink_size)
break;
event = elog_get_event_base(offset);
/* Reached the end of the area */
if (!event || event->type == ELOG_TYPE_EOL)
if (rdev_readat(rdev, &type, abs_offset + type_offset,
size) < 0)
break;
offset += event->length;
discard_count++;
/* Reached the end of the area */
if (type == ELOG_TYPE_EOL)
break;
if (rdev_readat(rdev, &len, abs_offset + len_offset, size) < 0)
break;
offset += len;
}
new_size = next_event_offset - offset;
memmove(&elog_area->data[0], &elog_area->data[offset], new_size);
memset(&elog_area->data[new_size], ELOG_TYPE_EOL, log_size - new_size);
elog_move_events_to_front(offset, next_event_offset - offset);
elog_flash_erase();
elog_flash_write(0, total_size);
@ -439,7 +534,7 @@ int elog_smbios_write_type15(unsigned long *current, int handle)
void *cbmem = cbmem_add(CBMEM_ID_ELOG, total_size);
if (!cbmem)
return 0;
memcpy(cbmem, elog_area, total_size);
rdev_readat(mirror_dev_get(), cbmem, 0, total_size);
#endif
memset(t, 0, len);
@ -518,6 +613,8 @@ static void elog_find_flash(void)
*/
int elog_init(void)
{
void *mirror_buffer;
switch (elog_initialized) {
case ELOG_UNINITIALIZED:
break;
@ -551,11 +648,12 @@ int elog_init(void)
return -1;
}
elog_area = malloc(total_size);
if (!elog_area) {
mirror_buffer = malloc(total_size);
if (!mirror_buffer) {
printk(BIOS_ERR, "ELOG: Unable to allocate backing store\n");
return -1;
}
mem_region_device_rw_init(&mirror_dev, mirror_buffer, total_size);
/* Load the log from flash */
elog_scan_flash();
@ -579,7 +677,7 @@ int elog_init(void)
}
printk(BIOS_INFO, "ELOG: FLASH @0x%p [SPI 0x%08x]\n",
elog_area, flash_base);
mirror_buffer, flash_base);
printk(BIOS_INFO, "ELOG: area is %d bytes, full threshold %d,"
" shrink size %d\n", total_size, full_threshold, shrink_size);
@ -672,14 +770,14 @@ void elog_add_event_raw(u8 event_type, void *data, u8 data_size)
}
/* Make sure event data can fit */
if ((next_event_offset + event_size) >= log_size) {
event = elog_get_event_buffer(next_event_offset, event_size);
if (event == NULL) {
printk(BIOS_ERR, "ELOG: Event(%X) does not fit\n",
event_type);
return;
}
/* Fill out event data */
event = elog_get_event_base(next_event_offset);
event->type = event_type;
event->length = event_size;
elog_fill_timestamp(event);
@ -690,6 +788,7 @@ void elog_add_event_raw(u8 event_type, void *data, u8 data_size)
/* Zero the checksum byte and then compute checksum */
elog_update_checksum(event, 0);
elog_update_checksum(event, -(elog_checksum_event(event)));
elog_put_event_buffer(event);
/* Update the ELOG state */
event_count++;

View File

@ -61,9 +61,4 @@ typedef enum elog_event_buffer_state {
ELOG_EVENT_BUFFER_CORRUPTED,
} elog_event_buffer_state;
struct elog_area {
struct elog_header header;
u8 data[0];
} __attribute__((packed));
#endif /* ELOG_INTERNAL_H_ */