diff --git a/src/drivers/Kconfig b/src/drivers/Kconfig index 89cdf6a3d6..da90a03cd1 100644 --- a/src/drivers/Kconfig +++ b/src/drivers/Kconfig @@ -28,3 +28,4 @@ source src/drivers/trident/Kconfig source src/drivers/ics/Kconfig source src/drivers/spi/Kconfig source src/drivers/ipmi/Kconfig +source src/drivers/elog/Kconfig diff --git a/src/drivers/Makefile.inc b/src/drivers/Makefile.inc index 6c63a6a4ff..e10e685d47 100644 --- a/src/drivers/Makefile.inc +++ b/src/drivers/Makefile.inc @@ -28,4 +28,5 @@ subdirs-y += trident subdirs-y += ics subdirs-y += spi subdirs-y += ipmi +subdirs-y += elog subdirs-$(CONFIG_ARCH_X86) += pc80 diff --git a/src/drivers/elog/Kconfig b/src/drivers/elog/Kconfig new file mode 100644 index 0000000000..d61dd10857 --- /dev/null +++ b/src/drivers/elog/Kconfig @@ -0,0 +1,69 @@ +## +## This file is part of the coreboot project. +## +## Copyright (C) 2012 The Chromium OS Authors. All rights reserved. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; version 2 of the License. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +config ELOG + depends on SPI_FLASH + bool "Support for flash based event log" + default n + help + Enable support for flash based event logging. + +if ELOG + +config ELOG_DEBUG + bool "Enable debug output for event logging" + default n + +config ELOG_FLASH_BASE + hex "Event log offset into flash" + default 0 + help + Offset into the flash chip for the ELOG block. + This should be allocated in the FMAP. + +config ELOG_AREA_SIZE + hex "Size of Event Log area in flash" + default 0x1000 + help + This should be a multiple of flash block size. + + Default is 4K. + +config ELOG_FULL_THRESHOLD + hex "Threshold at which flash is considered full" + default 0xC00 + help + When the Event Log size is larger than this it will be shrunk + to ELOG_SHRINK_SIZE. Must be greater than ELOG_AREA_SIZE, and + ELOG_AREA_SIZE - ELOG_FULL_THRESHOLD must be greater than the + maximum event size of 128. + + Default is 75% of the log, or 3K. + +config ELOG_SHRINK_SIZE + hex "Resulting size when the event log is shrunk" + default 0x400 + help + When the Event Log is shrunk it will go to this size. + ELOG_AREA_SIZE - ELOG_SHRINK_SIZE must be less than + CONFIG_ELOG_FULL_THRESHOLD. + + Default is 1K. + +endif diff --git a/src/drivers/elog/Makefile.inc b/src/drivers/elog/Makefile.inc new file mode 100644 index 0000000000..a01841db96 --- /dev/null +++ b/src/drivers/elog/Makefile.inc @@ -0,0 +1 @@ +ramstage-$(CONFIG_ELOG) += elog.c diff --git a/src/drivers/elog/elog.c b/src/drivers/elog/elog.c new file mode 100644 index 0000000000..8e56b85242 --- /dev/null +++ b/src/drivers/elog/elog.c @@ -0,0 +1,880 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 The ChromiumOS Authors. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "elog_internal.h" + +#if CONFIG_ELOG_FLASH_BASE == 0 +#error "CONFIG_ELOG_FLASH_BASE is invalid" +#endif +#if CONFIG_ELOG_FULL_THRESHOLD >= CONFIG_ELOG_AREA_SIZE +#error "CONFIG_ELOG_FULL_THRESHOLD is larger than CONFIG_ELOG_AREA_SIZE" +#endif +#if (CONFIG_ELOG_AREA_SIZE - CONFIG_ELOG_FULL_THRESHOLD) < (MAX_EVENT_SIZE + 1) +#error "CONFIG_ELOG_FULL_THRESHOLD is too small" +#endif +#if CONFIG_ELOG_SHRINK_SIZE >= CONFIG_ELOG_AREA_SIZE +#error "CONFIG_ELOG_SHRINK_SIZE is larger than CONFIG_ELOG_AREA_SIZE" +#endif +#if (CONFIG_ELOG_AREA_SIZE - CONFIG_ELOG_SHRINK_SIZE) > \ + CONFIG_ELOG_FULL_THRESHOLD +#error "CONFIG_ELOG_SHRINK_SIZE is too large" +#endif + +#if CONFIG_ELOG_DEBUG +#define elog_debug(STR...) printk(BIOS_DEBUG, STR) +#else +#define elog_debug(STR...) +#endif + +/* + * Static variables for ELOG state + */ +static int elog_initialized; +static struct spi_flash *elog_spi; +static struct elog_descriptor elog_flash_area; +static struct elog_descriptor elog_mem_area; + +static inline struct elog_descriptor* elog_get_mem(void) +{ + return &elog_mem_area; +} + +static inline struct elog_descriptor* elog_get_flash(void) +{ + return &elog_flash_area; +} + +/* + * Convert a memory mapped flash address into a flash offset + */ +static inline u32 elog_flash_address_to_offset(u8 *address) +{ + if (!elog_spi) + return 0; + return (u32)address - ((u32)~0UL - elog_spi->size + 1); +} + +/* + * Convert a flash offset into a memory mapped flash address + */ +static inline u8* elog_flash_offset_to_address(u32 offset) +{ + if (!elog_spi) + return NULL; + return (u8*)((u32)~0UL - elog_spi->size + 1 + offset); +} + +/* + * The ELOG header is at the very beginning of the area + */ +static inline struct elog_header* +elog_get_header(struct elog_descriptor *elog) +{ + return elog->backing_store; +} + +/* + * Pointer to an event log header in the event data area + */ +static inline struct event_header* +elog_get_event_base(struct elog_descriptor *elog, u32 offset) +{ + return (struct event_header *)&elog->data[offset]; +} + +/* + * Pointer to where the next event should be stored + */ +static inline struct event_header* +elog_get_next_event_base(struct elog_descriptor *elog) +{ + return elog_get_event_base(elog, elog->next_event_offset); +} + +/* + * Pointer to the last logged event + */ +static inline struct event_header* +elog_get_last_event_base(struct elog_descriptor *elog) +{ + return elog_get_event_base(elog, elog->last_event_offset); +} + +/* + * Update the checksum at the last byte + */ +static void elog_update_checksum(struct event_header *event, u8 checksum) +{ + u8 *event_data = (u8*)event; + event_data[event->length - 1] = checksum; +} + +/* + * Simple byte checksum for events + */ +static u8 elog_checksum_event(struct event_header *event) +{ + u8 index, checksum = 0; + u8 *data = (u8*)event; + + for (index = 0; index < event->length; index++) + checksum += data[index]; + return checksum; +} + +/* + * Check if a raw buffer is filled with ELOG_TYPE_EOL byte + */ +static int elog_is_buffer_clear(u8 *base, u32 size) +{ + u8 *current = base; + u8 *end = current + size; + + elog_debug("elog_is_buffer_clear(base=0x%p size=%u)\n", base, size); + + for (; current != end; current++) { + if (*current != ELOG_TYPE_EOL) + return 0; + } + return 1; +} + +/* + * Verify whether ELOG area is filled with ELOG_TYPE_EOL byte + */ +static int elog_is_area_clear(struct elog_descriptor *elog) +{ + return elog_is_buffer_clear(elog->backing_store, elog->total_size); +} + +/* + * Check that the ELOG area has been initialized and is valid. + */ +static int elog_is_area_valid(struct elog_descriptor *elog) +{ + elog_debug("elog_is_area_valid()\n"); + + if (elog->area_state != ELOG_AREA_HAS_CONTENT) + return 0; + if (elog->header_state != ELOG_HEADER_VALID) + return 0; + if (elog->event_buffer_state != ELOG_EVENT_BUFFER_OK) + return 0; + return 1; +} + +/* + * Verify the contents of an ELOG Header structure + * Returns 1 if the header is valid, 0 otherwise + */ +static int elog_is_header_valid(struct elog_header *header) +{ + elog_debug("elog_is_header_valid()\n"); + + if (header->magic != ELOG_SIGNATURE) { + printk(BIOS_ERR, "ELOG: header magic 0x%X != 0x%X\n", + header->magic, ELOG_SIGNATURE); + return 0; + } + if (header->version != ELOG_VERSION) { + printk(BIOS_ERR, "ELOG: header version %u != %u\n", + header->version, ELOG_VERSION); + return 0; + } + if (header->header_size != sizeof(*header)) { + printk(BIOS_ERR, "ELOG: header size mismatch %u != %u\n", + header->header_size, sizeof(*header)); + return 0; + } + return 1; +} + +/* + * Validate the event header and data. + */ +static int elog_is_event_valid(struct elog_descriptor *elog, u32 offset) +{ + struct event_header *event; + + event = elog_get_event_base(elog, offset); + if (!event) + return 0; + + /* Validate event length */ + if ((offsetof(struct event_header, type) + + sizeof(event->type) - 1 + offset) >= elog->data_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) >= elog->data_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) >= elog->data_size) + return 0; + + /* Event length must be at least header size + checksum */ + if (event->length < (sizeof(*event) + 1)) + return 0; + + /* If event checksum is invalid the area is corrupt */ + if (elog_checksum_event(event) != 0) + return 0; + + /* Event is valid */ + return 1; +} + +/* + * Write 'size' bytes of data provided in 'buffer' into flash + * device at offset 'offset'. This will not erase the flash and + * it assumes the flash area is erased appropriately. + */ +static void elog_flash_write(u8 *address, u8 *buffer, u32 size) +{ + u32 offset; + + if (!address || !buffer || !size || !elog_spi) + return; + + offset = elog_flash_address_to_offset(address); + + elog_debug("elog_flash_write(address=0x%p offset=0x%08x buffer=0x%p " + "size=%u)\n", address, offset, buffer, size); + + /* Write the data to flash */ + elog_spi->write(elog_spi, offset, size, buffer); +} + +/* + * Erase the first block specified in the address. + * Only handles flash area within a single flash block. + */ +static void elog_flash_erase(u8 *address, u32 size) +{ + u32 offset; + + if (!address || !size || !elog_spi) + return; + + offset = elog_flash_address_to_offset(address); + + elog_debug("elog_flash_erase(address=0x%p offset=0x%08x size=%u)\n", + address, offset, size); + + /* Erase the sectors in this region */ + elog_spi->erase(elog_spi, offset, size); +} + +/* + * Scan the event area and validate each entry and + * update the ELOG descriptor state. + */ +static void elog_update_event_buffer_state(struct elog_descriptor *elog) +{ + u32 count = 0; + u32 offset = 0; + u32 last_offset = 0; + u32 last_event_size = 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(elog, offset); + + /* Do not de-reference anything past the area length */ + if ((offsetof(struct event_header, type) + + sizeof(event->type) - 1 + offset) >= elog->data_size) { + elog->event_buffer_state = ELOG_EVENT_BUFFER_CORRUPTED; + break; + } + + /* The end of the event marker has been found */ + if (event->type == ELOG_TYPE_EOL) + break; + + /* Validate the event */ + if (!elog_is_event_valid(elog, offset)) { + elog->event_buffer_state = ELOG_EVENT_BUFFER_CORRUPTED; + break; + } + + /* Move to the next event */ + count++; + last_offset = offset; + last_event_size = event->length; + offset += event->length; + } + + /* Ensure the remaining buffer is empty */ + if (!elog_is_buffer_clear(&elog->data[offset], + elog->data_size - offset)) + elog->event_buffer_state = ELOG_EVENT_BUFFER_CORRUPTED; + + /* Update data into elog descriptor */ + elog->event_count = count; + elog->next_event_offset = offset; + elog->last_event_offset = last_offset; + elog->last_event_size = last_event_size; +} + +static void elog_validate_and_fill(struct elog_descriptor *elog) +{ + elog_debug("elog_validate_and_fill()\n"); + + /* Check if the area is empty or not */ + if (elog_is_area_clear(elog)) { + elog->area_state = ELOG_AREA_EMPTY; + return; + } + + elog->area_state = ELOG_AREA_HAS_CONTENT; + + /* Validate the header */ + if (!elog_is_header_valid(elog->staging_header)) { + elog->header_state = ELOG_HEADER_INVALID; + return; + } + + elog->header_state = ELOG_HEADER_VALID; + elog_update_event_buffer_state(elog); +} + +/* + * Initialize a new ELOG descriptor + */ +static void elog_init_descriptor(struct elog_descriptor *elog, + elog_descriptor_type type, + u8 *buffer, u32 size, + struct elog_header *header) +{ + elog_debug("elog_init_descriptor(type=%u buffer=0x%p size=%u)\n", + type, buffer, size); + + elog->type = type; + elog->area_state = ELOG_AREA_UNDEFINED; + elog->header_state = ELOG_HEADER_INVALID; + elog->event_buffer_state = ELOG_EVENT_BUFFER_OK; + elog->backing_store = buffer; + elog->total_size = size; + + /* Get staging header from backing store */ + elog->staging_header = header; + memcpy(header, buffer, sizeof(struct elog_header)); + + /* Data starts immediately after header */ + elog->data = &buffer[sizeof(struct elog_header)]; + elog->data_size = size - sizeof(struct elog_header); + + elog->next_event_offset = 0; + elog->last_event_offset = 0; + elog->last_event_size = 0; + elog->event_count = 0; + + elog_validate_and_fill(elog); +} + +/* + * Re-initialize an existing ELOG descriptor + */ +static void elog_reinit_descriptor(struct elog_descriptor *elog) +{ + elog_debug("elog_reinit_descriptor()\n"); + elog_init_descriptor(elog, elog->type, elog->backing_store, + elog->total_size, elog->staging_header); +} + +/* + * Create ELOG descriptor data structures for all ELOG areas. + */ +static int elog_setup_descriptors(u32 flash_base, u32 area_size) +{ + struct elog_header *staging_header; + u8 *area; + + elog_debug("elog_setup_descriptors(base=0x%08x size=%u)\n", + flash_base, area_size); + + /* Prepare flash descriptors */ + if (flash_base == 0) { + printk(BIOS_ERR, "ELOG: Invalid flash base\n"); + return -1; + } + + staging_header = malloc(sizeof(struct elog_header)); + if (!staging_header) { + printk(BIOS_ERR, "ELOG: Unable to allocate header\n"); + return -1; + } + + area = elog_flash_offset_to_address(flash_base); + if (!area) { + printk(BIOS_ERR, "ELOG: Unable to determine flash address\n"); + return -1; + } + elog_init_descriptor(elog_get_flash(), ELOG_DESCRIPTOR_FLASH, + area, area_size, staging_header); + + /* Initialize the memory area to look like a cleared flash area */ + area = malloc(area_size); + if (!area) { + printk(BIOS_ERR, "ELOG: Unable to allocate mem area\n"); + return -1; + } + memset(area, ELOG_TYPE_EOL, area_size); + elog_init_descriptor(elog_get_mem(), ELOG_DESCRIPTOR_MEMORY, + area, area_size, (struct elog_header *)area); + + return 0; +} + +static void elog_flash_erase_area(void) +{ + struct elog_descriptor *elog = elog_get_flash(); + + elog_debug("elog_flash_erase_area()\n"); + + elog_flash_erase(elog->backing_store, elog->total_size); + elog_reinit_descriptor(elog); +} + +static void elog_prepare_empty(struct elog_descriptor *elog, + u8 *data, u32 data_size) +{ + struct elog_header *header; + + elog_debug("elog_prepare_empty(%u bytes)\n", data_size); + + if (!elog_is_area_clear(elog)) + return; + + /* Write out the header */ + header = elog->staging_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(elog->backing_store, (u8*)header, + header->header_size); + + /* Write out the data */ + if (data) + elog_flash_write(elog->data, data, data_size); + + elog_reinit_descriptor(elog); + + /* Clear the log if corrupt */ + if (!elog_is_area_valid(elog)) + elog_flash_erase_area(); +} + +static int elog_sync_flash_to_mem(void) +{ + struct elog_descriptor *mem = elog_get_mem(); + struct elog_descriptor *flash = elog_get_flash(); + + elog_debug("elog_sync_flash_to_mem()\n"); + + /* Fill with empty pattern first */ + memset(mem->backing_store, ELOG_TYPE_EOL, mem->total_size); + + /* Copy the header to memory */ + memcpy(mem->backing_store, flash->backing_store, + sizeof(struct elog_header)); + + /* Copy the valid flash contents to memory */ + memcpy(mem->data, flash->data, flash->next_event_offset); + + elog_reinit_descriptor(mem); + + return elog_is_area_valid(mem) ? 0 : -1; +} + +static int elog_sync_mem_to_flash(void) +{ + struct elog_descriptor *mem = elog_get_mem(); + struct elog_descriptor *flash = elog_get_flash(); + u8 *src, *dest; + u32 size; + + elog_debug("elog_sync_mem_to_flash()\n"); + + /* + * In the case of a BIOS flash the active area will be cleared. + * One can catch this case and log the proper shutdown event by + * checking if the active flash elog is empty. Note that if the + * header size changes we will have corrupted the flash area. + * However that will be corrected on the next boot. + */ + if (elog_is_area_clear(flash)) { + elog_prepare_empty(flash, + (u8*)elog_get_last_event_base(mem), + mem->last_event_size); + elog_sync_flash_to_mem(); + return 0; + } + + /* Calculate the destination and source bases */ + dest = (u8*)elog_get_next_event_base(flash); + src = (u8*)elog_get_event_base(mem, flash->next_event_offset); + + /* Calculate how much data to sync */ + size = mem->next_event_offset - flash->next_event_offset; + + /* Write the log data */ + elog_flash_write(dest, src, size); + + /* Update descriptor */ + flash->event_count = mem->event_count; + flash->next_event_offset = mem->next_event_offset; + flash->last_event_offset = mem->last_event_offset; + flash->last_event_size = mem->last_event_size; + + return 0; +} + +/* + * Called during ELOG entry handler to prepare state for flash. + */ +static int elog_flash_area_bootstrap(void) +{ + struct elog_descriptor *elog = elog_get_flash(); + + elog_debug("elog_flash_area_bootstrap()\n"); + + switch (elog->area_state) { + case ELOG_AREA_UNDEFINED: + printk(BIOS_ERR, "ELOG: flash area undefined\n"); + return -1; + + case ELOG_AREA_EMPTY: + /* Write a new header with no data */ + elog_prepare_empty(elog, NULL, 0); + break; + + case ELOG_AREA_HAS_CONTENT: + break; + } + + if (elog->header_state == ELOG_HEADER_INVALID) { + /* If the header is invalid no events can be salvaged + * so erase the entire area. */ + printk(BIOS_ERR, "ELOG: flash area header invalid\n"); + elog_flash_erase_area(); + elog_prepare_empty(elog, NULL, 0); + } + + if (elog->event_buffer_state == ELOG_EVENT_BUFFER_CORRUPTED) { + /* Wipe the source flash area */ + elog_flash_erase_area(); + elog_prepare_empty(elog, elog_get_mem()->data, + elog_get_mem()->next_event_offset); + } + + return 0; +} + +/* + * Shrink the log, deleting old entries and moving the + * remining ones to the front of the log. + */ +static int elog_shrink(void) +{ + struct elog_descriptor *mem = elog_get_mem(); + struct event_header *event; + u16 discard_count = 0; + u16 offset = 0; + + elog_debug("elog_shrink()\n"); + + if (mem->next_event_offset < CONFIG_ELOG_SHRINK_SIZE) + return 0; + + while (1) { + /* Next event has exceeded constraints */ + if (offset > CONFIG_ELOG_SHRINK_SIZE) + break; + + event = elog_get_event_base(mem, offset); + + /* Reached the end of the area */ + if (!event || event->type == ELOG_TYPE_EOL) + break; + + offset += event->length; + discard_count++; + } + + /* Erase flash area */ + elog_flash_erase_area(); + + /* Write new flash area */ + elog_prepare_empty(elog_get_flash(), + (u8*)elog_get_event_base(mem, offset), + mem->next_event_offset - offset); + + /* Update memory area from flash */ + if (elog_sync_flash_to_mem() < 0) { + printk(BIOS_ERR, "Unable to update memory area from flash\n"); + return -1; + } + + /* Add clear event */ + elog_add_event_word(ELOG_TYPE_LOG_CLEAR, offset); + + return 0; +} + +/* + * Initialize the SPI bus and probe for a flash chip + */ +static int elog_spi_init(void) +{ + elog_debug("elog_spi_init()\n"); + + /* Prepare SPI subsystem */ + spi_init(); + + /* Look for flash chip */ + elog_spi = spi_flash_probe(0, 0, 0, 0); + + return elog_spi ? 0 : -1; +} + +/* + * Clear the entire event log + */ +int elog_clear(void) +{ + struct elog_descriptor *flash = elog_get_flash(); + + elog_debug("elog_clear()\n"); + + /* Erase flash area */ + elog_flash_erase_area(); + + /* Prepare new empty area */ + elog_prepare_empty(flash, NULL, 0); + + /* Update memory area from flash */ + if (elog_sync_flash_to_mem() < 0) + return -1; + + /* Log the clear event */ + elog_add_event_word(ELOG_TYPE_LOG_CLEAR, flash->total_size); + + return 0; +} + +/* + * Event log main entry point + */ +int elog_init(void) +{ + if (elog_initialized) + return 0; + + elog_debug("elog_init()\n"); + + /* Find SPI flash chip for backing store */ + if (elog_spi_init() < 0) { + printk(BIOS_ERR, "ELOG: Unable to find SPI flash\n"); + return -1; + } + + /* Setup descriptors for flash and memory areas */ + if (elog_setup_descriptors(CONFIG_ELOG_FLASH_BASE, + CONFIG_ELOG_AREA_SIZE) < 0) { + printk(BIOS_ERR, "ELOG: Unable to initialize descriptors\n"); + return -1; + } + + /* Bootstrap the flash area */ + if (elog_flash_area_bootstrap() < 0) { + printk(BIOS_ERR, "ELOG: Unable to bootstrap flash area\n"); + return -1; + } + + /* Initialize the memory area */ + if (elog_sync_flash_to_mem() < 0) { + printk(BIOS_ERR, "ELOG: Unable to initialize memory area\n"); + return -1; + } + + elog_initialized = 1; + + printk(BIOS_INFO, "ELOG: MEM @0x%p FLASH @0x%p\n", + elog_get_mem()->backing_store, + elog_get_flash()->backing_store); + + printk(BIOS_INFO, "ELOG: areas are %d bytes, full threshold %d," + " shrink size %d\n", CONFIG_ELOG_AREA_SIZE, + CONFIG_ELOG_FULL_THRESHOLD, CONFIG_ELOG_SHRINK_SIZE); + + /* Log a clear event if necessary */ + if (elog_get_flash()->event_count == 0) + elog_add_event_word(ELOG_TYPE_LOG_CLEAR, + elog_get_flash()->total_size); + + return 0; +} + +/* + * Populate timestamp in event header with current time + */ +static void elog_fill_timestamp(struct event_header *event) +{ + event->second = cmos_read(RTC_CLK_SECOND); + event->minute = cmos_read(RTC_CLK_MINUTE); + event->hour = cmos_read(RTC_CLK_HOUR); + event->day = cmos_read(RTC_CLK_DAYOFMONTH); + event->month = cmos_read(RTC_CLK_MONTH); + event->year = cmos_read(RTC_CLK_YEAR); + + /* Basic sanity check of expected ranges */ + if (event->month > 0x12 || event->day > 0x31 || event->hour > 0x23 || + event->minute > 0x59 || event->second > 0x59) { + event->year = 0; + event->month = 0; + event->day = 0; + event->hour = 0; + event->minute = 0; + event->second = 0; + } +} + +/* + * Add an event to the memory area + */ +static int elog_add_event_mem(u8 event_type, void *data, u8 data_size) +{ + struct event_header *event; + struct elog_descriptor *mem = elog_get_mem(); + u8 event_size; + + elog_debug("elog_add_event_mem(type=%X)\n", event_type); + + /* Make sure ELOG structures are initialized */ + if (elog_init() < 0) + return -1; + + /* Header + Data + Checksum */ + event_size = sizeof(*event) + data_size + 1; + if (event_size > MAX_EVENT_SIZE) { + printk(BIOS_ERR, "ELOG: Event(%X) data size too " + "big (%d)\n", event_type, event_size); + return -1; + } + + /* Make sure event data can fit */ + if ((mem->next_event_offset + event_size) >= mem->data_size) { + printk(BIOS_ERR, "ELOG: Event(%X) does not fit\n", + event_type); + return -1; + } + + /* Fill out event data */ + event = elog_get_next_event_base(mem); + event->type = event_type; + event->length = event_size; + elog_fill_timestamp(event); + + if (data_size) + memcpy(&event[1], data, data_size); + + /* Zero the checksum byte and then compute checksum */ + elog_update_checksum(event, 0); + elog_update_checksum(event, -(elog_checksum_event(event))); + + /* Update memory descriptor parameters */ + mem->event_count++; + mem->last_event_offset = mem->next_event_offset; + mem->last_event_size = event_size; + mem->next_event_offset += event_size; + + printk(BIOS_INFO, "ELOG: Event(%X) added with size %d\n", + event_type, event_size); + return 0; +} + +void elog_add_event_raw(u8 event_type, void *data, u8 data_size) +{ + elog_debug("elog_add_event_raw(type=%X)\n", event_type); + + /* Add event to the memory area */ + if (elog_add_event_mem(event_type, data, data_size) < 0) { + printk(BIOS_ERR, "Unable to add event to memory area\n"); + return; + } + + /* Sync the memory buffer to flash */ + elog_sync_mem_to_flash(); + + /* Shrink the log if we are getting too full */ + if (elog_get_mem()->next_event_offset >= CONFIG_ELOG_FULL_THRESHOLD) + elog_shrink(); +} + +void elog_add_event(u8 event_type) +{ + elog_add_event_raw(event_type, NULL, 0); +} + +void elog_add_event_byte(u8 event_type, u8 data) +{ + elog_add_event_raw(event_type, &data, sizeof(data)); +} + +void elog_add_event_word(u8 event_type, u16 data) +{ + elog_add_event_raw(event_type, &data, sizeof(data)); +} + +void elog_add_event_dword(u8 event_type, u32 data) +{ + elog_add_event_raw(event_type, &data, sizeof(data)); +} + +void elog_add_event_wake(u8 source, u32 instance) +{ + struct elog_event_data_wake wake = { + .source = source, + .instance = instance + }; + elog_add_event_raw(ELOG_TYPE_WAKE_SOURCE, &wake, sizeof(wake)); +} diff --git a/src/drivers/elog/elog_internal.h b/src/drivers/elog/elog_internal.h new file mode 100644 index 0000000000..2a6a0b7fc4 --- /dev/null +++ b/src/drivers/elog/elog_internal.h @@ -0,0 +1,76 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 The ChromiumOS Authors. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA + */ + +#ifndef ELOG_INTERNAL_H_ +#define ELOG_INTERNAL_H_ + +/* ELOG header */ +struct elog_header { + u32 magic; + u8 version; + u8 header_size; + u8 reserved[2]; +} __attribute__ ((packed)); + +/* ELOG related constants */ +#define ELOG_SIGNATURE 0x474f4c45 /* 'ELOG' */ +#define ELOG_VERSION 1 + +typedef enum elog_descriptor_type { + ELOG_DESCRIPTOR_UNKNOWN, + ELOG_DESCRIPTOR_MEMORY, + ELOG_DESCRIPTOR_FLASH, +} elog_descriptor_type; + +typedef enum elog_area_state { + ELOG_AREA_UNDEFINED, /* Initial boot strap state */ + ELOG_AREA_EMPTY, /* Entire area is empty */ + ELOG_AREA_HAS_CONTENT, /* Area has some content */ +} elog_area_state; + +typedef enum elog_header_state { + ELOG_HEADER_INVALID, + ELOG_HEADER_VALID, +} elog_header_state; + +typedef enum elog_event_buffer_state { + ELOG_EVENT_BUFFER_OK, + ELOG_EVENT_BUFFER_CORRUPTED, +} elog_event_buffer_state; + +/* + * Internal handler for event log buffers + */ +struct elog_descriptor { + elog_descriptor_type type; + elog_area_state area_state; + elog_header_state header_state; + elog_event_buffer_state event_buffer_state; + struct elog_header *staging_header; + void *backing_store; + u8 *data; + u16 total_size; + u16 data_size; + u16 next_event_offset; + u16 last_event_offset; + u16 last_event_size; + u16 event_count; +}; + +#endif /* ELOG_INTERNAL_H_ */ diff --git a/src/include/elog.h b/src/include/elog.h new file mode 100644 index 0000000000..c6599a249c --- /dev/null +++ b/src/include/elog.h @@ -0,0 +1,119 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 The ChromiumOS Authors. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA + */ + +#ifndef ELOG_H_ +#define ELOG_H_ + +#if CONFIG_ELOG + +/* SMI command code for GSMI event logging */ +#define ELOG_GSMI_APM_CNT 0xEF + +#define MAX_EVENT_SIZE 0x7F + +/* End of log */ +#define ELOG_TYPE_EOL 0xFF + +/* + * Standard SMBIOS event log types below 0x80 + */ +#define ELOG_TYPE_UNDEFINED_EVENT 0x00 +#define ELOG_TYPE_SINGLE_BIT_ECC_MEM_ERR 0x01 +#define ELOG_TYPE_MULTI_BIT_ECC_MEM_ERR 0x02 +#define ELOG_TYPE_MEM_PARITY_ERR 0x03 +#define ELOG_TYPE_BUS_TIMEOUT 0x04 +#define ELOG_TYPE_IO_CHECK 0x05 +#define ELOG_TYPE_SW_NMI 0x06 +#define ELOG_TYPE_POST_MEM_RESIZE 0x07 +#define ELOG_TYPE_POST_ERR 0x08 +#define ELOG_TYPE_PCI_PERR 0x09 +#define ELOG_TYPE_PCI_SERR 0x0A +#define ELOG_TYPE_CPU_FAIL 0x0B +#define ELOG_TYPE_EISA_TIMEOUT 0x0C +#define ELOG_TYPE_CORRECTABLE_MEMLOG_DIS 0x0D +#define ELOG_TYPE_LOG_DISABLED 0x0E +#define ELOG_TYPE_UNDEFINED_EVENT2 0x0F +#define ELOG_TYPE_SYS_LIMIT_EXCEED 0x10 +#define ELOG_TYPE_ASYNC_HW_TIMER_EXPIRED 0x11 +#define ELOG_TYPE_SYS_CONFIG_INFO 0x12 +#define ELOG_TYPE_HDD_INFO 0x13 +#define ELOG_TYPE_SYS_RECONFIG 0x14 +#define ELOG_TYPE_CPU_ERROR 0x15 +#define ELOG_TYPE_LOG_CLEAR 0x16 +#define ELOG_TYPE_BOOT 0x17 + +/* + * Extended defined OEM event types start at 0x80 + */ + +/* OS/kernel events */ +#define ELOG_TYPE_OS_EVENT 0x81 + +/* Last event from coreboot */ +#define ELOG_TYPE_OS_BOOT 0x90 + +/* Embedded controller event */ +#define ELOG_TYPE_EC_EVENT 0x91 + +/* Power */ +#define ELOG_TYPE_POWER_FAIL 0x92 +#define ELOG_TYPE_SUS_POWER_FAIL 0x93 +#define ELOG_TYPE_PWROK_FAIL 0x94 +#define ELOG_TYPE_SYS_PWROK_FAIL 0x95 +#define ELOG_TYPE_POWER_ON 0x96 +#define ELOG_TYPE_POWER_BUTTON 0x97 +#define ELOG_TYPE_POWER_BUTTON_OVERRIDE 0x98 + +/* Reset */ +#define ELOG_TYPE_RESET_BUTTON 0x99 +#define ELOG_TYPE_SYSTEM_RESET 0x9a +#define ELOG_TYPE_RTC_RESET 0x9b +#define ELOG_TYPE_TCO_RESET 0x9c + +/* Sleep/Wake */ +#define ELOG_TYPE_ACPI_ENTER 0x9d +#define ELOG_TYPE_ACPI_WAKE 0x9e +#define ELOG_TYPE_WAKE_SOURCE 0x9f +#define ELOG_WAKE_SOURCE_PCIE 0x00 +#define ELOG_WAKE_SOURCE_PME 0x01 +#define ELOG_WAKE_SOURCE_PME_INTERNAL 0x02 +#define ELOG_WAKE_SOURCE_RTC 0x03 +#define ELOG_WAKE_SOURCE_GPIO 0x04 +#define ELOG_WAKE_SOURCE_SMBUS 0x05 +struct elog_event_data_wake { + u8 source; + u32 instance; +} __attribute__ ((packed)); + +/* Chrome OS related events */ +#define ELOG_TYPE_CROS_DEVELOPER_MODE 0xa0 +#define ELOG_TYPE_CROS_RECOVERY_MODE 0xa1 + +extern int elog_init(void); +extern int elog_clear(void); +extern void elog_add_event_raw(u8 event_type, void *data, u8 data_size); +extern void elog_add_event(u8 event_type); +extern void elog_add_event_byte(u8 event_type, u8 data); +extern void elog_add_event_word(u8 event_type, u16 data); +extern void elog_add_event_dword(u8 event_type, u32 data); +extern void elog_add_event_wake(u8 source, u32 instance); + +#endif /* !CONFIG_ELOG */ + +#endif /* ELOG_H_ */