diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/drivers/elog/elog.c | 245 | ||||
-rw-r--r-- | src/drivers/elog/elog_internal.h | 5 |
2 files changed, 172 insertions, 78 deletions
diff --git a/src/drivers/elog/elog.c b/src/drivers/elog/elog.c index 654a60c540..c33544fb93 100644 --- a/src/drivers/elog/elog.c +++ b/src/drivers/elog/elog.c @@ -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) + /* Read and validate length. */ + if (rdev_readat(mirror_dev_get(), &len, offset + len_offset, size) < 0) 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) + /* Event length must be at least header size + checksum */ + if (len < (sizeof(*event) + sizeof(checksum))) 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) + if (len > MAX_EVENT_SIZE) return 0; - /* Event length must be at least header size + checksum */ - if (event->length < (sizeof(*event) + 1)) + 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); - - /* Do not de-reference anything past the area length */ - if ((offsetof(struct event_header, type) + - sizeof(event->type) - 1 + offset) >= log_size) { + 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); + + 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); + if (rdev_readat(rdev, &type, abs_offset + type_offset, + size) < 0) + break; /* Reached the end of the area */ - if (!event || event->type == ELOG_TYPE_EOL) + if (type == ELOG_TYPE_EOL) + break; + + if (rdev_readat(rdev, &len, abs_offset + len_offset, size) < 0) break; - offset += event->length; - discard_count++; + 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++; diff --git a/src/drivers/elog/elog_internal.h b/src/drivers/elog/elog_internal.h index d4fae4fe71..ef21bf0f3f 100644 --- a/src/drivers/elog/elog_internal.h +++ b/src/drivers/elog/elog_internal.h @@ -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_ */ |