summaryrefslogtreecommitdiff
path: root/src/drivers/spi/spi_flash_sfdp.c
blob: 46117bca3808adb07baeac86bd89f1394ed6e980 (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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include <console/console.h>
#include <spi-generic.h>
#include <spi_flash.h>
#include <types.h>

#include "spi_flash_internal.h"

/*
 * Assumptions (not) made in the code:
 * - code is supposed to be endian-safe
 * - requested SFDP data blocks are small enough to fit into the SPI controller buffer, so no
 *   spi_crop_chunk calls are needed
 *
 * Limitations:
 * - only legacy access protocol supported:
 *   - only NOR flash
 *   - read SFDP instruction is sufficient and no fetch SFDP instruction needed
 *   - always 3 byte addresses for SFDP
 *   - only 1 bit SPI mode supported
 *   - always 8 dummy cycles after the address is sent
*/

#define CMD_READ_SFDP			0x5a

static enum cb_err read_sfdp_data(const struct spi_flash *flash, uint32_t offset, size_t len,
				  uint8_t *buf)
{
	uint8_t buf_cmd[5];

	buf_cmd[0] = CMD_READ_SFDP;
	buf_cmd[1] = offset >> 16 & 0xff;
	buf_cmd[2] = offset >> 8 & 0xff;
	buf_cmd[3] = offset >> 0 & 0xff;
	buf_cmd[4] = 0; /* dummy byte */

	/* TODO: use spi_crop_chunk to break up larger transfers */
	if (spi_flash_cmd_multi(&flash->spi, buf_cmd, sizeof(buf_cmd), buf, len))
		return CB_ERR;

	return CB_SUCCESS;
}

#define SFDP_HEADER_OFFSET			0
#define SFDP_HEADER_LEN				(2 * sizeof(uint32_t))
#define SFDP_HEADER_SIGNATURE			0x50444653 /* LE: "SFDP" */
#define SFDP_HEADER_ACCESS_PROTOCOL_LEGACY	0xff
/* header byte offsets */
#define SFDP_HEADER_SIGNATURE_0			0
#define SFDP_HEADER_SIGNATURE_1			1
#define SFDP_HEADER_SIGNATURE_2			2
#define SFDP_HEADER_SIGNATURE_3			3
#define SFDP_HEADER_MINOR_REV			4
#define SFDP_HEADER_MAJOR_REV			5
#define SFDP_HEADER_PARAMETER_HEADER_NUMBER	6
#define SFDP_HEADER_ACCESS_PROTOCOL		7

static enum cb_err get_sfdp_header(const struct spi_flash *flash, uint16_t *sfdp_revision,
				   uint8_t *number_of_parameter_headers,
				   uint8_t *access_prototcol)
{
	uint8_t buf[SFDP_HEADER_LEN];
	uint32_t signature;

	if (read_sfdp_data(flash, SFDP_HEADER_OFFSET, SFDP_HEADER_LEN, buf) != CB_SUCCESS)
		return CB_ERR;

	signature = buf[SFDP_HEADER_SIGNATURE_0] | buf[SFDP_HEADER_SIGNATURE_1] << 8 |
		buf[SFDP_HEADER_SIGNATURE_2] << 16 | buf[SFDP_HEADER_SIGNATURE_3] << 24;
	*sfdp_revision = buf[SFDP_HEADER_MINOR_REV] | buf[SFDP_HEADER_MAJOR_REV] << 8;
	*number_of_parameter_headers = buf[SFDP_HEADER_PARAMETER_HEADER_NUMBER] + 1;
	*access_prototcol = buf[SFDP_HEADER_ACCESS_PROTOCOL];

	if (signature != SFDP_HEADER_SIGNATURE)
		return CB_ERR;

	/* We make the assumption in the code that the legacy access protocol is used */
	if (*access_prototcol != SFDP_HEADER_ACCESS_PROTOCOL_LEGACY)
		return CB_ERR;

	return CB_SUCCESS;
}

#define SFDP_PARAMETER_HEADER_LEN		(2 * sizeof(uint32_t))
#define SFDP_PARAMETER_HEADER_OFFSET(n)		(SFDP_HEADER_OFFSET + SFDP_HEADER_LEN + \
						 n * SFDP_PARAMETER_HEADER_LEN)
/* parameter header byte offsets */
#define SFDP_PARAMETER_HEADER_ID_LSB		0
#define SFDP_PARAMETER_HEADER_MINOR_REV		1
#define SFDP_PARAMETER_HEADER_MAJOR_REV		2
#define SFDP_PARAMETER_HEADER_LENGTH_DWORDS	3
#define SFDP_PARAMETER_HEADER_POINTER_0		4
#define SFDP_PARAMETER_HEADER_POINTER_1		5
#define SFDP_PARAMETER_HEADER_POINTER_2		6
#define SFDP_PARAMETER_HEADER_ID_MSB		7

/* get_sfdp_parameter_header_by_index must be called with a valid index */
static enum cb_err get_sfdp_parameter_header_by_index(const struct spi_flash *flash,
						      uint8_t index, uint16_t *id,
						      uint16_t *revision,
						      uint8_t *length_dwords,
						      uint32_t *table_pointer)
{
	uint8_t buf[SFDP_PARAMETER_HEADER_LEN];

	if (read_sfdp_data(flash, SFDP_PARAMETER_HEADER_OFFSET(index),
		       SFDP_PARAMETER_HEADER_LEN, buf) != CB_SUCCESS)
		return CB_ERR;

	*id = buf[SFDP_PARAMETER_HEADER_ID_LSB] | buf[SFDP_PARAMETER_HEADER_ID_MSB] << 8;
	*revision = buf[SFDP_PARAMETER_HEADER_MINOR_REV] |
		buf[SFDP_PARAMETER_HEADER_MAJOR_REV] << 8;
	*length_dwords = buf[SFDP_PARAMETER_HEADER_LENGTH_DWORDS];
	*table_pointer = buf[SFDP_PARAMETER_HEADER_POINTER_0] |
		buf[SFDP_PARAMETER_HEADER_POINTER_1] << 8 |
		buf[SFDP_PARAMETER_HEADER_POINTER_2] << 16;

	/* the parameter table pointer byte address must be DWORD-aligned */
	if (!IS_ALIGNED(*table_pointer, sizeof(uint32_t)))
		return CB_ERR;

	/* TODO: check id validity? */

	return CB_SUCCESS;
}

void spi_flash_print_sfdp_headers(const struct spi_flash *flash)
{
	enum cb_err stat;
	uint16_t sfdp_rev;
	uint8_t param_header_count;
	uint8_t access_protocol;

	if (get_sfdp_header(flash, &sfdp_rev, &param_header_count, &access_protocol) !=
			CB_SUCCESS) {
		printk(BIOS_ERR, "Failed to read SFDP header from SPI flash\n");
		return;
	}

	printk(BIOS_DEBUG, "SFDP header found in SPI flash.\n");
	printk(BIOS_DEBUG, "major rev %#x, minor rev %#x, access protocol %#x, "
	       "number of headers %d\n", sfdp_rev >> 8, sfdp_rev & 0xff, access_protocol,
	       param_header_count);

	uint16_t tbl_id;
	uint16_t tbl_rev;
	uint8_t tbl_len_dwords;
	uint32_t tbl_pointer;

	for (unsigned int i = 0; i < param_header_count; i++) {
		stat = get_sfdp_parameter_header_by_index(flash, i, &tbl_id, &tbl_rev,
							  &tbl_len_dwords, &tbl_pointer);

		if (stat == CB_SUCCESS) {
			printk(BIOS_DEBUG, "SFPD header with index %d:\n", i);
			printk(BIOS_DEBUG, "  table ID %#x, major rev %#x, minor rev %#x\n",
			       tbl_id, tbl_rev >> 8, tbl_rev & 0xff);
			printk(BIOS_DEBUG, "  table pointer %#x, table length DWORDS %#x\n",
			       tbl_pointer, tbl_len_dwords);
		} else {
			printk(BIOS_ERR, "Cound't read SFPD header with index %d.\n", i);
		}
	}
}

static enum cb_err find_sfdp_parameter_header(const struct spi_flash *flash, uint16_t table_id,
					      uint16_t *revision, uint8_t *length_dwords,
					      uint32_t *table_pointer)
{
	enum cb_err stat;
	uint16_t sfdp_rev;
	uint8_t param_header_count;
	uint8_t access_protocol;

	if (get_sfdp_header(flash, &sfdp_rev, &param_header_count, &access_protocol) !=
			CB_SUCCESS)
		return CB_ERR;

	/* TODO: add version check? */

	uint16_t tbl_id;
	uint16_t tbl_rev;
	uint8_t tbl_len_dwords;
	uint32_t tbl_pointer;

	for (unsigned int i = 0; i < param_header_count; i++) {
		stat = get_sfdp_parameter_header_by_index(flash, i, &tbl_id, &tbl_rev,
							  &tbl_len_dwords, &tbl_pointer);

		if (stat == CB_SUCCESS && tbl_id == table_id) {
			*revision = tbl_rev;
			*length_dwords = tbl_len_dwords;
			*table_pointer = tbl_pointer;
			return CB_SUCCESS;
		}
	}

	printk(BIOS_WARNING, "Cound't find SFPD header with table ID %#x.\n", table_id);

	return CB_ERR;
}

#define SFDP_PARAMETER_ID_RPMC			0xff03

#define SFDP_RPMC_TABLE_LENGTH_DWORDS		2
#define SFDP_RPMC_TABLE_SUPPORTED_MAJOR_REV	1

/* RPMC parameter table byte offsets and fields */
#define SFDP_RPMC_TABLE_CONFIG					0
#define   SFDP_RPMC_TABLE_CONFIG_FLASH_HARDENING_BIT		BIT(0)
#define   SFDP_RPMC_TABLE_CONFIG_MONOTONIC_COUNTER_SIZE_BIT	BIT(1)
#define   SFDP_RPMC_TABLE_CONFIG_BUSY_POLLING_METHOD		BIT(2)
#define   SFDP_RPMC_TABLE_CONFIG_RESERVED			BIT(3)
#define   SFDP_RPMC_TABLE_CONFIG_RESERVED_VALUE			0x08
#define   SFDP_RPMC_TABLE_CONFIG_NUM_COUNTERS_MASK		0xf0
#define   SFDP_RPMC_TABLE_CONFIG_NUM_COUNTERS_SHIFT		4
#define SFDP_RPMC_TABLE_RPMC_OP1				1
#define SFDP_RPMC_TABLE_RPMC_OP2				2
#define SFDP_RPMC_TABLE_UPDATE_RATE				3
#define   SFDP_RPMC_TABLE_UPDATE_RATE_MASK			0x0f
#define   SFDP_RPMC_TABLE_UPDATE_RATE_RESERVED_MASK		0xf0
#define   SFDP_RPMC_TABLE_UPDATE_RATE_RESERVED_VALUE		0xf0
#define SFDP_RPMC_TABLE_READ_COUNTER_POLLING_DELAY		4
#define SFDP_RPMC_TABLE_WRITE_COUNTER_POLLING_SHORT_DELAY	5
#define SFDP_RPMC_TABLE_WRITE_COUNTER_POLLING_LONG_DELAY	6
#define SFDP_RPMC_TABLE_RESERVED_BYTE				7
#define   SFDP_RPMC_TABLE_RESERVED_BYTE_VALUE			0xff

static uint64_t calc_rpmc_update_rate_s(uint8_t val)
{
	/* val is at most 15, so this won't overflow */
	return 5 * 1 << (val & SFDP_RPMC_TABLE_UPDATE_RATE_MASK);
}

#define SPDF_RPMC_DELAY_VALUE_MASK		0x1f
#define SPDF_RPMC_DELAY_UNIT_MASK		0x60
#define SPDF_RPMC_DELAY_UNIT_SHIFT		5
#define SPDF_RPMC_DELAY_SHORT_UNIT_0_US		1	/* 1us */
#define SPDF_RPMC_DELAY_SHORT_UNIT_1_US		16	/* 16us */
#define SPDF_RPMC_DELAY_SHORT_UNIT_2_US		128	/* 128us */
#define SPDF_RPMC_DELAY_SHORT_UNIT_3_US		1000	/* 1ms */
#define SPDF_RPMC_DELAY_LONG_UNIT_0_US		1000	/* 1ms */
#define SPDF_RPMC_DELAY_LONG_UNIT_1_US		16000	/* 16ms */
#define SPDF_RPMC_DELAY_LONG_UNIT_2_US		128000	/* 128ms */
#define SPDF_RPMC_DELAY_LONG_UNIT_3_US		1000000	/* 1s */

static uint64_t calc_rpmc_short_delay_us(uint8_t val)
{
	const uint8_t value = val & SPDF_RPMC_DELAY_VALUE_MASK;
	const uint8_t shift = (val & SPDF_RPMC_DELAY_UNIT_MASK) >> SPDF_RPMC_DELAY_UNIT_SHIFT;
	uint64_t multiplier;

	switch (shift) {
	case 0:
		multiplier = SPDF_RPMC_DELAY_SHORT_UNIT_0_US;
		break;
	case 1:
		multiplier = SPDF_RPMC_DELAY_SHORT_UNIT_1_US;
		break;
	case 2:
		multiplier = SPDF_RPMC_DELAY_SHORT_UNIT_2_US;
		break;
	default:
		multiplier = SPDF_RPMC_DELAY_SHORT_UNIT_3_US;
		break;
	}

	return value * multiplier;
}

static uint64_t calc_rpmc_long_delay_us(uint8_t val)
{
	const uint8_t value = val & SPDF_RPMC_DELAY_VALUE_MASK;
	const uint8_t shift = (val & SPDF_RPMC_DELAY_UNIT_MASK) >> SPDF_RPMC_DELAY_UNIT_SHIFT;
	uint64_t multiplier;

	switch (shift) {
	case 0:
		multiplier = SPDF_RPMC_DELAY_LONG_UNIT_0_US;
		break;
	case 1:
		multiplier = SPDF_RPMC_DELAY_LONG_UNIT_1_US;
		break;
	case 2:
		multiplier = SPDF_RPMC_DELAY_LONG_UNIT_2_US;
		break;
	default:
		multiplier = SPDF_RPMC_DELAY_LONG_UNIT_3_US;
		break;
	}

	return value * multiplier;
}

enum cb_err spi_flash_get_sfdp_rpmc(const struct spi_flash *flash,
				    struct sfdp_rpmc_info *rpmc_info)
{
	uint16_t rev;
	uint8_t length_dwords;
	uint32_t table_pointer;
	uint8_t buf[SFDP_RPMC_TABLE_LENGTH_DWORDS * sizeof(uint32_t)];

	if (find_sfdp_parameter_header(flash, SFDP_PARAMETER_ID_RPMC, &rev, &length_dwords,
				       &table_pointer) != CB_SUCCESS)
		return CB_ERR;

	if (length_dwords != SFDP_RPMC_TABLE_LENGTH_DWORDS)
		return CB_ERR;

	if (rev >> 8 != SFDP_RPMC_TABLE_SUPPORTED_MAJOR_REV) {
		printk(BIOS_ERR, "Unsupprted major RPMC table revision %#x\n", rev >> 8);
		return CB_ERR;
	}

	if (read_sfdp_data(flash, table_pointer, sizeof(buf), buf) != CB_SUCCESS)
		return CB_ERR;

	if ((buf[SFDP_RPMC_TABLE_CONFIG] & SFDP_RPMC_TABLE_CONFIG_RESERVED) !=
	    SFDP_RPMC_TABLE_CONFIG_RESERVED_VALUE ||
	    (buf[SFDP_RPMC_TABLE_UPDATE_RATE] & SFDP_RPMC_TABLE_UPDATE_RATE_RESERVED_MASK) !=
	    SFDP_RPMC_TABLE_UPDATE_RATE_RESERVED_VALUE ||
	    buf[SFDP_RPMC_TABLE_RESERVED_BYTE] != SFDP_RPMC_TABLE_RESERVED_BYTE_VALUE) {
		printk(BIOS_ERR, "Unexpected reserved values in RPMC table\n");
		return CB_ERR;
	}

	rpmc_info->flash_hardening = !!(buf[SFDP_RPMC_TABLE_CONFIG] &
					SFDP_RPMC_TABLE_CONFIG_FLASH_HARDENING_BIT);
	rpmc_info->monotonic_counter_size = (buf[SFDP_RPMC_TABLE_CONFIG] &
					    SFDP_RPMC_TABLE_CONFIG_FLASH_HARDENING_BIT) ?
					    SFDP_RPMC_COUNTER_BITS_RESERVED :
					    SFDP_RPMC_COUNTER_BITS_32;
	rpmc_info->busy_polling_method = (buf[SFDP_RPMC_TABLE_CONFIG] &
					 SFDP_RPMC_TABLE_CONFIG_BUSY_POLLING_METHOD) ?
					 SFDP_RPMC_POLL_READ_STATUS :
					 SFDP_RPMC_POLL_OP2_EXTENDED_STATUS;
	rpmc_info->number_of_counters = ((buf[SFDP_RPMC_TABLE_CONFIG] &
					SFDP_RPMC_TABLE_CONFIG_NUM_COUNTERS_MASK) >>
					SFDP_RPMC_TABLE_CONFIG_NUM_COUNTERS_SHIFT) + 1;
	rpmc_info->op1_write_command = buf[SFDP_RPMC_TABLE_RPMC_OP1];
	rpmc_info->op2_read_command = buf[SFDP_RPMC_TABLE_RPMC_OP2];
	rpmc_info->update_rate_s = calc_rpmc_update_rate_s(buf[SFDP_RPMC_TABLE_UPDATE_RATE] &
							   SFDP_RPMC_TABLE_UPDATE_RATE_MASK);
	rpmc_info->read_counter_polling_delay_us = calc_rpmc_short_delay_us(
				buf[SFDP_RPMC_TABLE_READ_COUNTER_POLLING_DELAY]);
	rpmc_info->write_counter_polling_short_delay_us = calc_rpmc_short_delay_us(
				buf[SFDP_RPMC_TABLE_WRITE_COUNTER_POLLING_SHORT_DELAY]);
	rpmc_info->write_counter_polling_long_delay_us = calc_rpmc_long_delay_us(
				buf[SFDP_RPMC_TABLE_WRITE_COUNTER_POLLING_LONG_DELAY]);
	return CB_SUCCESS;
}