| /* |
| * 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 <commonlib/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); |
| } |