diff --git a/Makefile.inc b/Makefile.inc index 1e65228841..99110ed538 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -790,9 +790,19 @@ FMAP_BIOS_SIZE := $(shell echo $(CONFIG_CBFS_SIZE) | tr A-F a-f) # position and size of flashmap, relative to BIOS_BASE FMAP_FMAP_BASE := 0 FMAP_FMAP_SIZE := 0x100 +# position, size and entry line of CONSOLE relative to BIOS_BASE, if enabled +ifeq ($(CONFIG_CONSOLE_SPI_FLASH),y) +FMAP_CONSOLE_BASE := $(call int-add, $(FMAP_FMAP_BASE) $(FMAP_FMAP_SIZE)) +FMAP_CONSOLE_SIZE := $(CONFIG_CONSOLE_SPI_FLASH_BUFFER_SIZE) +FMAP_CONSOLE_ENTRY := CONSOLE@$(FMAP_CONSOLE_BASE) $(FMAP_CONSOLE_SIZE) +else # ifeq ($(CONFIG_CONSOLE_SPI_FLASH),y) +FMAP_CONSOLE_BASE := 0 +FMAP_CONSOLE_SIZE := 0 +FMAP_CONSOLE_ENTRY := +endif # ifeq ($(CONFIG_CONSOLE_SPI_FLASH),y) # position and size of CBFS, relative to BIOS_BASE -FMAP_CBFS_BASE := $(FMAP_FMAP_SIZE) -FMAP_CBFS_SIZE := $(call int-subtract, $(FMAP_BIOS_SIZE) $(FMAP_FMAP_SIZE)) +FMAP_CBFS_BASE := $(call int-add, $(FMAP_CONSOLE_SIZE) $(FMAP_FMAP_SIZE)) +FMAP_CBFS_SIZE := $(call int-subtract, $(FMAP_BIOS_SIZE) $(FMAP_CBFS_BASE)) else # ifeq ($(CONFIG_ARCH_X86),y) DEFAULT_FLASHMAP:=$(top)/util/cbfstool/default.fmd # entire flash @@ -805,8 +815,18 @@ FMAP_BIOS_SIZE := $(CONFIG_CBFS_SIZE) # position and size of flashmap, relative to BIOS_BASE FMAP_FMAP_BASE := 0x20000 FMAP_FMAP_SIZE := 0x100 +# position, size and entry line of CONSOLE relative to BIOS_BASE, if enabled +ifeq ($(CONFIG_CONSOLE_SPI_FLASH),y) +FMAP_CONSOLE_BASE := $(call int-add, $(FMAP_FMAP_BASE) $(FMAP_FMAP_SIZE)) +FMAP_CONSOLE_SIZE := $(CONFIG_CONSOLE_SPI_FLASH_BUFFER_SIZE) +FMAP_CONSOLE_ENTRY := CONSOLE@$(FMAP_CONSOLE_BASE) $(FMAP_CONSOLE_SIZE) +else # ifeq ($(CONFIG_CONSOLE_SPI_FLASH),y) +FMAP_CONSOLE_BASE := 0 +FMAP_CONSOLE_SIZE := 0 +FMAP_CONSOLE_ENTRY := +endif # ifeq ($(CONFIG_CONSOLE_SPI_FLASH),y) # position and size of CBFS, relative to BIOS_BASE -FMAP_CBFS_BASE := $(call int-add,$(FMAP_FMAP_BASE) $(FMAP_FMAP_SIZE)) +FMAP_CBFS_BASE := $(call int-add,$(FMAP_FMAP_BASE) $(FMAP_FMAP_SIZE) $(FMAP_CONSOLE_SIZE)) FMAP_CBFS_SIZE := $(call int-subtract,$(FMAP_BIOS_SIZE) $(FMAP_CBFS_BASE)) endif # ifeq ($(CONFIG_ARCH_X86),y) @@ -817,6 +837,7 @@ $(obj)/fmap.fmd: $(top)/Makefile.inc $(DEFAULT_FLASHMAP) $(obj)/config.h -e "s,##BIOS_SIZE##,$(FMAP_BIOS_SIZE)," \ -e "s,##FMAP_BASE##,$(FMAP_FMAP_BASE)," \ -e "s,##FMAP_SIZE##,$(FMAP_FMAP_SIZE)," \ + -e "s,##CONSOLE_ENTRY##,$(FMAP_CONSOLE_ENTRY)," \ -e "s,##CBFS_BASE##,$(FMAP_CBFS_BASE)," \ -e "s,##CBFS_SIZE##,$(FMAP_CBFS_SIZE)," \ $(DEFAULT_FLASHMAP) > $@.tmp diff --git a/src/console/Kconfig b/src/console/Kconfig index caf91ab252..c0e4a8c8b0 100644 --- a/src/console/Kconfig +++ b/src/console/Kconfig @@ -233,6 +233,37 @@ config CONSOLE_CBMEM_DUMP_TO_UART endif +config CONSOLE_SPI_FLASH + bool "SPI Flash console output" + default n + select BOOT_DEVICE_SPI_FLASH_RW_NOMMAP_EARLY if !COMMON_CBFS_SPI_WRAPPER + help + Send coreboot debug output to the SPI Flash in the FMAP CONSOLE area + + This option can cause premature wear on the SPI flash and should not + be used as a normal means of debugging. It is only to be enabled and + used when porting a new motherboard which has no other console + available (no UART, no POST, no cbmem access(non bootable)). Since + a non bootable machine will require the use of an external SPI Flash + programmer, the developer can grab the console log at the same time. + + The flash console will not be erased on reboot, so once it is full, + the flashconsole driver will stop writing to it. This is to avoid + wear on the flash, and to avoid erasing sectors (which may freeze + the SPI controller on skylake). + + The 'CONSOLE' area can be extracted from the FMAP with : + cbfstool rom.bin read -r CONSOLE -f console.log + +config CONSOLE_SPI_FLASH_BUFFER_SIZE + hex "Room allocated for console output in FMAP" + default 0x20000 + depends on CONSOLE_SPI_FLASH + help + Space allocated for console output storage in FMAP. The default + value (128K or 0x20000 bytes) is large enough to accommodate + even the BIOS_SPEW level. + config CONSOLE_QEMU_DEBUGCON bool "QEMU debug console output" depends on BOARD_EMULATION_QEMU_X86 diff --git a/src/console/console.c b/src/console/console.c index 7b0dfc2fb1..877c8dc960 100644 --- a/src/console/console.c +++ b/src/console/console.c @@ -21,6 +21,7 @@ #include #include #include +#include #include void console_hw_init(void) @@ -33,6 +34,7 @@ void console_hw_init(void) __ne2k_init(); __usbdebug_init(); __spiconsole_init(); + __flashconsole_init(); } void console_tx_byte(unsigned char byte) @@ -53,6 +55,7 @@ void console_tx_byte(unsigned char byte) __ne2k_tx_byte(byte); __usb_tx_byte(byte); __spiconsole_tx_byte(byte); + __flashconsole_tx_byte(byte); } void console_tx_flush(void) @@ -60,6 +63,7 @@ void console_tx_flush(void) __uart_tx_flush(); __ne2k_tx_flush(); __usb_tx_flush(); + __flashconsole_tx_flush(); } void console_write_line(uint8_t *buffer, size_t number_of_bytes) diff --git a/src/drivers/spi/Makefile.inc b/src/drivers/spi/Makefile.inc index c1bf307cc5..c594d4e74c 100644 --- a/src/drivers/spi/Makefile.inc +++ b/src/drivers/spi/Makefile.inc @@ -7,6 +7,14 @@ ramstage-y += spiconsole.c smm-$(CONFIG_DEBUG_SMI) += spiconsole.c endif +ifeq ($(CONFIG_CONSOLE_SPI_FLASH),y) +bootblock-y += flashconsole.c +romstage-y += flashconsole.c +ramstage-y += flashconsole.c +smm-$(CONFIG_DEBUG_SMI) += flashconsole.c + +endif + bootblock-y += spi-generic.c bootblock-$(CONFIG_COMMON_CBFS_SPI_WRAPPER) += cbfs_spi.c bootblock-$(CONFIG_SPI_FLASH) += spi_flash.c 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 +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/src/include/console/flash.h b/src/include/console/flash.h new file mode 100644 index 0000000000..07bc724b19 --- /dev/null +++ b/src/include/console/flash.h @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#ifndef CONSOLE_FLASH_H +#define CONSOLE_FLASH_H 1 + +#include +#include + +void flashconsole_init(void); +void flashconsole_tx_byte(unsigned char c); +void flashconsole_tx_flush(void); + +#define __CONSOLE_FLASH_ENABLE__ IS_ENABLED(CONFIG_CONSOLE_SPI_FLASH) + +#if __CONSOLE_FLASH_ENABLE__ +static inline void __flashconsole_init(void) { flashconsole_init(); } +static inline void __flashconsole_tx_byte(u8 data) +{ + flashconsole_tx_byte(data); +} +static inline void __flashconsole_tx_flush(void) +{ + flashconsole_tx_flush(); +} +#else +static inline void __flashconsole_init(void) {} +static inline void __flashconsole_tx_byte(u8 data) {} +static inline void __flashconsole_tx_flush(void) {} +#endif /* __CONSOLE_FLASH_ENABLE__ */ + + +#endif /* CONSOLE_FLASH_H */ diff --git a/util/cbfstool/default-x86.fmd b/util/cbfstool/default-x86.fmd index f344ab2086..1b6cfc8ba6 100644 --- a/util/cbfstool/default-x86.fmd +++ b/util/cbfstool/default-x86.fmd @@ -10,6 +10,7 @@ FLASH@##ROM_BASE## ##ROM_SIZE## { BIOS@##BIOS_BASE## ##BIOS_SIZE## { FMAP@##FMAP_BASE## ##FMAP_SIZE## + ##CONSOLE_ENTRY## COREBOOT(CBFS)@##CBFS_BASE## ##CBFS_SIZE## } } diff --git a/util/cbfstool/default.fmd b/util/cbfstool/default.fmd index 32ddfa4074..50b9bf0bd1 100644 --- a/util/cbfstool/default.fmd +++ b/util/cbfstool/default.fmd @@ -13,6 +13,7 @@ FLASH@##ROM_BASE## ##ROM_SIZE## { BIOS@##BIOS_BASE## ##BIOS_SIZE## { BOOTBLOCK 128K FMAP@##FMAP_BASE## ##FMAP_SIZE## + ##CONSOLE_ENTRY## COREBOOT(CBFS)@##CBFS_BASE## ##CBFS_SIZE## } }