summaryrefslogtreecommitdiffstats
path: root/src/drivers/spi/flashconsole.c
blob: 2a0d2358b9f21f4ab8d95d3111ea50d595b66282 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 * This file is part of the coreboot project.
 *
 * 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>

#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);
}