blob: aea6872c075b2c55dbad74f308bc1da9c6acb754 [file] [log] [blame]
/*
* 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);
}