summaryrefslogtreecommitdiffstats
path: root/src/soc/amd/common/block/apob/apob_cache.c
blob: f61785dd8ca55b356358ee19320b78d99ccd4fec (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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include <acpi/acpi.h>
#include <amdblocks/apob_cache.h>
#include <assert.h>
#include <boot_device.h>
#include <bootstate.h>
#include <commonlib/helpers.h>
#include <commonlib/region.h>
#include <console/console.h>
#include <delay.h>
#include <fmap.h>
#include <spi_flash.h>
#include <stdint.h>
#include <string.h>
#include <thread.h>
#include <timer.h>
#include <timestamp.h>

#define DEFAULT_MRC_CACHE "RW_MRC_CACHE"
/* PSP requires this value to be 64KiB */
#define DEFAULT_MRC_CACHE_SIZE 0x10000

#if !CONFIG_PSP_APOB_DRAM_ADDRESS
#error Incorrect APOB configuration setting(s)
#endif

#define APOB_SIGNATURE 0x424F5041	/* 'APOB' */

/* APOB_BASE_HEADER from AGESA */
struct apob_base_header {
	uint32_t   signature;			/* APOB signature */
	uint32_t   version;			/* Version */
	uint32_t   size;			/* APOB Size */
	uint32_t   offset_of_first_entry;	/* APOB Header Size */
};

static bool apob_header_valid(const struct apob_base_header *apob_header_ptr, const char *where)
{
	if (apob_header_ptr->signature != APOB_SIGNATURE) {
		printk(BIOS_WARNING, "Invalid %s APOB signature %x\n",
			where, apob_header_ptr->signature);
		return false;
	}

	if (apob_header_ptr->size == 0 || apob_header_ptr->size > DEFAULT_MRC_CACHE_SIZE) {
		printk(BIOS_WARNING, "%s APOB data is too large %x > %x\n",
			where, apob_header_ptr->size, DEFAULT_MRC_CACHE_SIZE);
		return false;
	}

	return true;
}

static void *get_apob_dram_address(void)
{
	/*
	 * TODO: Find the APOB destination by parsing the PSP's tables
	 * (once vboot is implemented).
	 */
	void *apob_src_ram = (void *)(uintptr_t)CONFIG_PSP_APOB_DRAM_ADDRESS;

	if (apob_header_valid(apob_src_ram, "RAM") == false)
		return NULL;

	return apob_src_ram;
}

static int get_nv_rdev(struct region_device *r)
{
	if  (fmap_locate_area_as_rdev(DEFAULT_MRC_CACHE, r) < 0) {
		printk(BIOS_ERR, "Error: No APOB NV region is found in flash\n");
		return -1;
	}

	return 0;
}

static struct apob_thread_context {
	uint8_t buffer[DEFAULT_MRC_CACHE_SIZE] __attribute__((aligned(64)));
	struct thread_handle handle;
	struct region_device apob_rdev;
} global_apob_thread;

static enum cb_err apob_thread_entry(void *arg)
{
	ssize_t size;
	struct apob_thread_context *thread = arg;

	printk(BIOS_DEBUG, "APOB thread running\n");
	size = rdev_readat(&thread->apob_rdev, thread->buffer, 0,
		    region_device_sz(&thread->apob_rdev));

	printk(BIOS_DEBUG, "APOB thread done\n");

	if (size == region_device_sz(&thread->apob_rdev))
		return CB_SUCCESS;

	return CB_ERR;
}

void start_apob_cache_read(void)
{
	struct apob_thread_context *thread = &global_apob_thread;

	if (!CONFIG(COOP_MULTITASKING))
		return;

	/* We don't perform any comparison on S3 resume */
	if (acpi_is_wakeup_s3())
		return;

	if (get_nv_rdev(&thread->apob_rdev) != 0)
		return;

	assert(ARRAY_SIZE(thread->buffer) == region_device_sz(&thread->apob_rdev));

	printk(BIOS_DEBUG, "Starting APOB preload\n");
	if (thread_run(&thread->handle, apob_thread_entry, thread))
		printk(BIOS_ERR, "Failed to start APOB preload thread\n");
}

static void *get_apob_from_nv_rdev(struct region_device *read_rdev)
{
	struct apob_base_header apob_header;

	if (rdev_readat(read_rdev, &apob_header, 0, sizeof(apob_header)) < 0) {
		printk(BIOS_ERR, "Couldn't read APOB header!\n");
		return NULL;
	}

	if (apob_header_valid(&apob_header, "ROM") == false) {
		printk(BIOS_ERR, "No APOB NV data!\n");
		return NULL;
	}

	assert(CONFIG(BOOT_DEVICE_MEMORY_MAPPED));
	return rdev_mmap_full(read_rdev);
}

/* Save APOB buffer to flash */
static void soc_update_apob_cache(void *unused)
{
	struct apob_base_header *apob_rom;
	struct region_device read_rdev, write_rdev;
	bool update_needed = false;
	const struct apob_base_header *apob_src_ram;

	/* Nothing to update in case of S3 resume. */
	if (acpi_is_wakeup_s3())
		return;

	apob_src_ram = get_apob_dram_address();
	if (apob_src_ram == NULL)
		return;

	if (get_nv_rdev(&read_rdev) != 0)
		return;

	timestamp_add_now(TS_AMD_APOB_READ_START);

	if (CONFIG(COOP_MULTITASKING) && thread_join(&global_apob_thread.handle) == CB_SUCCESS)
		apob_rom = (struct apob_base_header *)global_apob_thread.buffer;
	else
		apob_rom = get_apob_from_nv_rdev(&read_rdev);

	if (apob_rom == NULL) {
		update_needed = true;
	} else if (memcmp(apob_src_ram, apob_rom, apob_src_ram->size)) {
		printk(BIOS_INFO, "APOB RAM copy differs from flash\n");
		update_needed = true;
	} else
		printk(BIOS_DEBUG, "APOB valid copy is already in flash\n");

	if (!update_needed) {
		timestamp_add_now(TS_AMD_APOB_DONE);
		return;
	}

	printk(BIOS_SPEW, "Copy APOB from RAM %p/%#x to flash %#zx/%#zx\n",
		apob_src_ram, apob_src_ram->size,
		region_device_offset(&read_rdev), region_device_sz(&read_rdev));

	if  (fmap_locate_area_as_rdev_rw(DEFAULT_MRC_CACHE, &write_rdev) < 0) {
		printk(BIOS_ERR, "Error: No RW APOB NV region is found in flash\n");
		return;
	}

	timestamp_add_now(TS_AMD_APOB_ERASE_START);

	/* write data to flash region */
	if (rdev_eraseat(&write_rdev, 0, DEFAULT_MRC_CACHE_SIZE) < 0) {
		printk(BIOS_ERR, "Error: APOB flash region erase failed\n");
		return;
	}

	timestamp_add_now(TS_AMD_APOB_WRITE_START);

	if (rdev_writeat(&write_rdev, apob_src_ram, 0, apob_src_ram->size) < 0) {
		printk(BIOS_ERR, "Error: APOB flash region update failed\n");
		return;
	}

	timestamp_add_now(TS_AMD_APOB_DONE);

	printk(BIOS_INFO, "Updated APOB in flash\n");
}

static void *get_apob_nv_address(void)
{
	struct region_device rdev;

	if (get_nv_rdev(&rdev) != 0)
		return NULL;

	return get_apob_from_nv_rdev(&rdev);
}

void *soc_fill_apob_cache(void)
{
	/* If this is non-S3 boot, then use the APOB data placed by PSP in DRAM. */
	if (!acpi_is_wakeup_s3())
		return get_apob_dram_address();

	/*
	 * In case of S3 resume, PSP does not copy APOB data to DRAM. Thus, coreboot needs to
	 * provide the APOB NV data from RW_MRC_CACHE on SPI flash so that FSP can use it
	 * without having to traverse the BIOS directory table.
	 */
	return get_apob_nv_address();
}

/*
 * BS_POST_DEVICE was chosen because this gives start_apob_cache_read plenty of time to read
 * the APOB from SPI.
 */
BOOT_STATE_INIT_ENTRY(BS_POST_DEVICE, BS_ON_EXIT, soc_update_apob_cache, NULL);