diff options
Diffstat (limited to 'src/drivers/spi/flashconsole.c')
-rw-r--r-- | src/drivers/spi/flashconsole.c | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/src/drivers/spi/flashconsole.c b/src/drivers/spi/flashconsole.c new file mode 100644 index 0000000000..e73af22951 --- /dev/null +++ b/src/drivers/spi/flashconsole.c @@ -0,0 +1,142 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2015 Google Inc. + * + * 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. + */ + +#include <arch/early_variables.h> +#include <region.h> +#include <boot_device.h> +#include <fmap.h> +#include <console/console.h> +#include <console/flash.h> +#include <string.h> + +#define LINE_BUFFER_SIZE 128 +#define READ_BUFFER_SIZE 0x100 + +static const struct region_device *g_rdev_ptr CAR_GLOBAL; +static struct region_device g_rdev CAR_GLOBAL; +static uint8_t g_line_buffer[LINE_BUFFER_SIZE] CAR_GLOBAL; +static size_t g_offset CAR_GLOBAL; +static size_t g_line_offset CAR_GLOBAL; + +void flashconsole_init(void) +{ + struct region_device *rdev = car_get_var_ptr(&g_rdev); + uint8_t buffer[READ_BUFFER_SIZE]; + size_t size; + size_t offset = 0; + size_t len = READ_BUFFER_SIZE; + size_t i; + + if (fmap_locate_area_as_rdev_rw("CONSOLE", rdev)) { + printk(BIOS_INFO, "Can't find 'CONSOLE' area in FMAP\n"); + return; + } + size = region_device_sz(rdev); + + /* + * We need to check the region until we find a 0xff indicating + * the end of a previous log write. + * We can't erase the region because one stage would erase the + * data from the previous stage. Also, it looks like doing an + * erase could completely freeze the SPI controller and then + * we can't write anything anymore (apparently might happen if + * the sector is already erased, so we would need to read + * anyways to check if it's all 0xff). + */ + for (i = 0; i < len && offset < size; ) { + // Fill the buffer on first iteration + if (i == 0) { + len = min(READ_BUFFER_SIZE, size - offset); + if (rdev_readat(rdev, buffer, offset, len) != len) + return; + } + if (buffer[i] == 0xff) { + offset += i; + break; + } + // If we're done, repeat the process for the next sector + if (++i == READ_BUFFER_SIZE) { + offset += len; + i = 0; + } + } + // Make sure there is still space left on the console + if (offset >= size) { + printk(BIOS_INFO, "No space left on 'console' region in SPI flash\n"); + return; + } + + car_set_var(g_offset, offset); + /* Set g_rdev_ptr last so tx_byte doesn't get executed early */ + car_set_var(g_rdev_ptr, rdev); +} + +void flashconsole_tx_byte(unsigned char c) +{ + const struct region_device *rdev = car_get_var(g_rdev_ptr); + uint8_t *line_buffer; + size_t offset; + size_t len; + size_t region_size; + + if (!rdev) + return; + + line_buffer = car_get_var_ptr(g_line_buffer); + offset = car_get_var(g_offset); + len = car_get_var(g_line_offset); + region_size = region_device_sz(rdev); + + line_buffer[len++] = c; + car_set_var(g_line_offset, len); + + if (len >= LINE_BUFFER_SIZE || + offset + len >= region_size || c == '\n') { + flashconsole_tx_flush(); + } +} + +void flashconsole_tx_flush(void) +{ + const struct region_device *rdev = car_get_var(g_rdev_ptr); + uint8_t *line_buffer = car_get_var_ptr(g_line_buffer); + size_t offset = car_get_var(g_offset); + size_t len = car_get_var(g_line_offset); + size_t region_size; + + if (!rdev) + return; + + /* Prevent any recursive loops in case the spi flash driver + * calls printk (in case of transaction timeout or + * any other error while writing) */ + car_set_var(g_rdev_ptr, NULL); + + region_size = region_device_sz(rdev); + if (offset + len >= region_size) + len = region_size - offset; + + if (rdev_writeat(rdev, line_buffer, offset, len) != len) + rdev = NULL; + + // If the region is full, stop future write attempts + if (offset + len >= region_size) + rdev = NULL; + + car_set_var(g_offset, offset + len); + car_set_var(g_line_offset, 0); + + car_set_var(g_rdev_ptr, rdev); +} |