summaryrefslogtreecommitdiff
path: root/libfwupdater/src/fwupdater.c
blob: 187e703a2af197152093fd08ee6e320bf94fb870 (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
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
 * Copyright 2023 Qorvo US, Inc.
 *
 */

#ifndef __KERNEL__
#include <stddef.h>
#endif

#include <qmrom_spi.h>
#include <qmrom_log.h>
#include <qmrom_utils.h>
#include <spi_rom_protocol.h>

#include <fwupdater.h>

/* Extract from C0 rom code */
#define MAX_CERTIFICATE_SIZE 0x400
#define MAX_CHUNK_SIZE 3072
#define WAIT_SS_RDY_CHUNK_TIMEOUT 100
#define WAIT_SS_RDY_STATUS_TIMEOUT 10
#define RESULT_RETRIES 3
#define RESULT_CMD_INTERVAL_MS 50
#define CKSUM_TYPE uint32_t
#define CKSUM_SIZE (sizeof(CKSUM_TYPE))
#define TRANPORT_HEADER_SIZE (sizeof(struct stc) + CKSUM_SIZE)
#define EMERGENCY_SPI_FREQ 1000000 /* 1MHz */

#define MIN(a, b) ((a) < (b) ? (a) : (b))

#ifndef __KERNEL__
_Static_assert(MAX_CHUNK_SIZE >= CRYPTO_IMAGES_CERT_PKG_SIZE);
_Static_assert(TRANPORT_HEADER_SIZE + MAX_CERTIFICATE_SIZE < MAX_CHUNK_SIZE);
#endif

/* local stats */
static int gstats_spi_errors;
static int gstats_ss_rdy_timeouts;

static int send_data_chunks(struct qmrom_handle *handle, char *data,
			    size_t size);

int run_fwupdater(struct qmrom_handle *handle, char *fwpkg_bin, size_t size)
{
	int rc;

	gstats_spi_errors = 0;
	gstats_ss_rdy_timeouts = 0;

	if (size < sizeof(struct fw_pkg_hdr_t) +
			   sizeof(struct fw_pkg_img_hdr_t) +
			   CRYPTO_IMAGES_CERT_PKG_SIZE +
			   CRYPTO_FIRMWARE_CHUNK_MIN_SIZE) {
		LOG_ERR("Cannot extract enough data from fw package binary\n");
		return -EINVAL;
	}

	rc = send_data_chunks(handle, fwpkg_bin, size);
	if (rc) {
		LOG_ERR("Sending image failed with %d\n", rc);
		return rc;
	}
	return 0;
}

static int run_fwupdater_get_status(struct qmrom_handle *handle,
				    struct stc *hstc, struct stc *sstc,
				    struct fw_updater_status_t *status)
{
	uint32_t i = 0;
	CKSUM_TYPE *cksum = (CKSUM_TYPE *)(hstc + 1);
	bool owa;
	memset(hstc, 0, TRANPORT_HEADER_SIZE + sizeof(*status));

	while (i++ < RESULT_RETRIES) {
		// Poll the QM
		sstc->all = 0;
		hstc->all = 0;
		*cksum = 0;
		qmrom_spi_transfer(handle->spi_handle, (char *)sstc,
				   (const char *)hstc, TRANPORT_HEADER_SIZE);
		qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
					      WAIT_SS_RDY_STATUS_TIMEOUT);
		sstc->all = 0;
		hstc->all = 0;
		hstc->host_flags.pre_read = 1;
		*cksum = 0;
		qmrom_spi_transfer(handle->spi_handle, (char *)sstc,
				   (const char *)hstc, TRANPORT_HEADER_SIZE);
		// LOG_INFO("Pre-Read received:\n");
		// hexdump(LOG_INFO, sstc, sizeof(sstc));

		/* Stops the loop when QM has a result to share */
		owa = sstc->soc_flags.out_waiting;
		qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
					      WAIT_SS_RDY_STATUS_TIMEOUT);
		sstc->all = 0;
		hstc->all = 0;
		hstc->host_flags.read = 1;
		hstc->len = sizeof(*status);
		*cksum = 0;
		qmrom_spi_transfer(handle->spi_handle, (char *)sstc,
				   (const char *)hstc,
				   TRANPORT_HEADER_SIZE + sizeof(*status));
		// LOG_INFO("Read received:\n");
		// hexdump(LOG_INFO, sstc, sizeof(*hstc) + sizeof(uint32_t));
		if (owa) {
			memcpy(status, sstc->payload, sizeof(*status));
			if (status->magic == FWUPDATER_STATUS_MAGIC)
				break;
		}
		qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
					      WAIT_SS_RDY_STATUS_TIMEOUT);
		// Failed to get the status, reduces the spi speed to
		// an emergency speed to maximize the chance to get the
		// final status
		qmrom_spi_set_freq(EMERGENCY_SPI_FREQ);
		gstats_spi_errors++;
	}
	if (status->magic != FWUPDATER_STATUS_MAGIC) {
		LOG_ERR("Timedout waiting for result\n");
		return -1;
	}
	return 0;
}

static CKSUM_TYPE checksum(const void *data, const size_t size)
{
	CKSUM_TYPE cksum = 0;
	CKSUM_TYPE *ptr = (CKSUM_TYPE *)data;
	CKSUM_TYPE remainder = size & (CKSUM_SIZE - 1);
	size_t idx;

	for (idx = 0; idx < size; idx += CKSUM_SIZE, ptr++)
		cksum += *ptr;

	if (!remainder)
		return cksum;

	cksum += ((uint8_t *)data)[size - 1];
	if (remainder > 1) {
		cksum += ((uint8_t *)data)[size - 2];
		if (remainder > 2) {
			cksum += ((uint8_t *)data)[size - 3];
		}
	}

	return cksum;
}

static void prepare_hstc(struct stc *hstc, char *data, size_t len)
{
	CKSUM_TYPE *cksum = (CKSUM_TYPE *)(hstc + 1);
	void *payload = cksum + 1;

	hstc->all = 0;
	hstc->host_flags.write = 1;
	hstc->len = len + CKSUM_SIZE;
	*cksum = checksum(data, len);
#if IS_ENABLED(CONFIG_INJECT_ERROR)
	*cksum += 2;
#endif
	memcpy(payload, data, len);
}

static int xfer_payload_prep_next(struct qmrom_handle *handle,
				  const char *step_name, struct stc *hstc,
				  struct stc *sstc, struct stc *hstc_next,
				  char **data, size_t *size)
{
	int rc = 0, nb_retry = CONFIG_NB_RETRIES;
	CKSUM_TYPE *cksum = (CKSUM_TYPE *)(hstc + 1);

	do {
		int ss_rdy_rc, irq_up;
		sstc->all = 0;
		rc = qmrom_spi_transfer(handle->spi_handle, (char *)sstc,
					(const char *)hstc,
					hstc->len + sizeof(struct stc));
		if (hstc_next) {
			/* Don't wait idle, prepare the next hstc to be sent */
			size_t to_send = MIN(MAX_CHUNK_SIZE, *size);
			prepare_hstc(hstc_next, *data, to_send);
			*size -= to_send;
			*data += to_send;
			hstc_next = NULL;
		}
		ss_rdy_rc = qmrom_spi_wait_for_ready_line(
			handle->ss_rdy_handle, WAIT_SS_RDY_CHUNK_TIMEOUT);
		if (ss_rdy_rc) {
			LOG_ERR("%s Waiting for ss-rdy failed with %d (nb_retry %d , cksum 0x%x)\n",
				step_name, ss_rdy_rc, nb_retry, *cksum);
			gstats_ss_rdy_timeouts++;
			rc = -EAGAIN;
		}
		irq_up = qmrom_spi_read_irq_line(handle->ss_irq_handle);
		if ((!rc && !sstc->soc_flags.ready) || irq_up) {
			LOG_ERR("%s Retry rc %d, sstc 0x%08x, irq %d, cksum %08x\n",
				step_name, rc, sstc->all, irq_up, *cksum);
			rc = -EAGAIN;
			gstats_spi_errors++;
		}
#if IS_ENABLED(CONFIG_INJECT_ERROR)
		(*cksum)--;
#endif
	} while (rc && --nb_retry > 0);
	if (rc) {
		LOG_ERR("%s transfer failed with %d - (sstc 0x%08x)\n",
			step_name, rc, sstc->all);
	}
	return rc;
}

static int xfer_payload(struct qmrom_handle *handle, const char *step_name,
			struct stc *hstc, struct stc *sstc)
{
	return xfer_payload_prep_next(handle, step_name, hstc, sstc, NULL, NULL,
				      NULL);
}

static int send_data_chunks(struct qmrom_handle *handle, char *data,
			    size_t size)
{
	struct fw_updater_status_t status;
	uint32_t chunk_nr = 0;
	struct stc *hstc, *sstc, *hstc_current, *hstc_next;
	char *rx, *tx;
	CKSUM_TYPE *cksum;
	int rc = 0;

	qmrom_alloc(rx, MAX_CHUNK_SIZE + TRANPORT_HEADER_SIZE);
	qmrom_alloc(tx, 2 * (MAX_CHUNK_SIZE + TRANPORT_HEADER_SIZE));
	if (!rx || !tx) {
		LOG_ERR("Rx/Tx buffers allocation failure\n");
		rc = -ENOMEM;
		goto exit_nomem;
	}

	sstc = (struct stc *)rx;
	hstc = (struct stc *)tx;
	hstc_current = hstc;
	hstc_next = (struct stc *)&tx[MAX_CHUNK_SIZE + TRANPORT_HEADER_SIZE];
	cksum = (CKSUM_TYPE *)(hstc + 1);

	/* wait for the QM to be ready */
	rc = qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
					   WAIT_SS_RDY_CHUNK_TIMEOUT);
	if (rc)
		LOG_ERR("Waiting for ss-rdy failed with %d\n", rc);

	/* Sending the fw package header */
	prepare_hstc(hstc, data, sizeof(struct fw_pkg_hdr_t));
	LOG_INFO("Sending the fw package header (%zu bytes, cksum is 0x%08x)\n",
		 sizeof(struct fw_pkg_hdr_t), *cksum);
	// hexdump(LOG_INFO, hstc->payload + 4, sizeof(struct fw_pkg_hdr_t));
	rc = xfer_payload(handle, "fw package header", hstc, sstc);
	if (rc)
		goto exit;
	/* Move the data to the next offset minus the header footprint */
	size -= sizeof(struct fw_pkg_hdr_t);
	data += sizeof(struct fw_pkg_hdr_t);

	/* Sending the image header */
	prepare_hstc(hstc, data, sizeof(struct fw_pkg_img_hdr_t));
	LOG_INFO("Sending the image header (%zu bytes cksum 0x%08x)\n",
		 sizeof(struct fw_pkg_img_hdr_t), *cksum);
	// hexdump(LOG_INFO, hstc->payload + 4, sizeof(struct fw_pkg_img_hdr_t));
	rc = xfer_payload(handle, "image header", hstc, sstc);
	if (rc)
		goto exit;
	size -= sizeof(struct fw_pkg_img_hdr_t);
	data += sizeof(struct fw_pkg_img_hdr_t);

	/* Sending the cert chain */
	prepare_hstc(hstc, data, CRYPTO_IMAGES_CERT_PKG_SIZE);
	LOG_INFO("Sending the cert chain (%d bytes cksum 0x%08x)\n",
		 CRYPTO_IMAGES_CERT_PKG_SIZE, *cksum);
	rc = xfer_payload(handle, "cert chain", hstc, sstc);
	if (rc)
		goto exit;
	size -= CRYPTO_IMAGES_CERT_PKG_SIZE;
	data += CRYPTO_IMAGES_CERT_PKG_SIZE;

	/* Sending the fw image */
	LOG_INFO("Sending the image (%zu bytes)\n", size);
	LOG_DBG("Sending a chunk (%zu bytes cksum 0x%08x)\n",
		MIN(MAX_CHUNK_SIZE, size), *cksum);
	prepare_hstc(hstc_current, data, MIN(MAX_CHUNK_SIZE, size));
	size -= hstc_current->len - CKSUM_SIZE;
	data += hstc_current->len - CKSUM_SIZE;
	do {
		rc = xfer_payload_prep_next(handle, "data chunk", hstc_current,
					    sstc, hstc_next, &data, &size);
		if (rc)
			goto exit;
		chunk_nr++;
		/* swap hstcs */
		hstc = hstc_current;
		hstc_current = hstc_next;
		hstc_next = hstc;
	} while (size);

	/* Sends the last now */
	rc = xfer_payload_prep_next(handle, "data chunk", hstc_current, sstc,
				    NULL, NULL, NULL);

exit:
	// tries to get the flashing status anyway...
	rc = run_fwupdater_get_status(handle, hstc, sstc, &status);
	if (!rc) {
		if (status.status) {
			LOG_ERR("Flashing failed, fw updater status %#x (errors: sub %#x, cksum %u, rram %u, crypto %d)\n",
				status.status, status.suberror,
				status.cksum_errors, status.rram_errors,
				status.crypto_errors);
			rc = status.status;
		} else {
			if (gstats_ss_rdy_timeouts + gstats_spi_errors +
			    status.cksum_errors + status.rram_errors +
			    status.crypto_errors) {
				LOG_WARN(
					"Flashing succeeded with errors (host %u, ss_rdy_timeout %u, QM %u, cksum %u, rram %u, crypto %d)\n",
					gstats_spi_errors,
					gstats_ss_rdy_timeouts,
					status.spi_errors, status.cksum_errors,
					status.rram_errors,
					status.crypto_errors);
			} else {
				LOG_INFO(
					"Flashing succeeded without any errors\n");
			}
		}
	} else {
		LOG_ERR("run_fwupdater_get_status returned %d\n", rc);
	}
exit_nomem:
	if (rx)
		qmrom_free(rx);
	if (tx)
		qmrom_free(tx);
	return rc;
}