1 /*
2  * Copyright (c) 2020 Vestas Wind Systems A/S
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <CANopen.h>
8 
9 #include <canopennode.h>
10 #include <zephyr/dfu/flash_img.h>
11 #include <zephyr/dfu/mcuboot.h>
12 #include <zephyr/storage/flash_map.h>
13 #include <zephyr/sys/crc.h>
14 
15 #define LOG_LEVEL CONFIG_CANOPEN_LOG_LEVEL
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_REGISTER(canopen_program);
18 
19 /* Object dictionary indexes */
20 #define OD_H1F50_PROGRAM_DATA 0x1F50
21 #define OD_H1F51_PROGRAM_CTRL 0x1F51
22 #define OD_H1F56_PROGRAM_SWID 0x1F56
23 #define OD_H1F57_FLASH_STATUS 0x1F57
24 
25 /* Common program control commands and status */
26 #define PROGRAM_CTRL_STOP           0x00
27 #define PROGRAM_CTRL_START          0x01
28 #define PROGRAM_CTRL_RESET          0x02
29 #define PROGRAM_CTRL_CLEAR          0x03
30 /* Zephyr specific program control and status */
31 #define PROGRAM_CTRL_ZEPHYR_CONFIRM 0x80
32 
33 /* Flash status bits */
34 #define FLASH_STATUS_IN_PROGRESS          BIT(0)
35 /* Flash common error bits values */
36 #define FLASH_STATUS_NO_ERROR            (0U << 1U)
37 #define FLASH_STATUS_NO_VALID_PROGRAM    (1U << 1U)
38 #define FLASH_STATUS_DATA_FORMAT_UNKNOWN (2U << 1U)
39 #define FLASH_STATUS_DATA_FORMAT_ERROR   (3U << 1U)
40 #define FLASH_STATUS_FLASH_NOT_CLEARED   (4U << 1U)
41 #define FLASH_STATUS_FLASH_WRITE_ERROR   (5U << 1U)
42 #define FLASH_STATUS_GENERAL_ADDR_ERROR  (6U << 1U)
43 #define FLASH_STATUS_FLASH_SECURED       (7U << 1U)
44 #define FLASH_STATUS_UNSPECIFIED_ERROR   (63U << 1)
45 
46 struct canopen_program_context {
47 	uint32_t flash_status;
48 	size_t total;
49 	CO_NMT_t *nmt;
50 	CO_EM_t *em;
51 	struct flash_img_context flash_img_ctx;
52 	uint8_t program_status;
53 	bool flash_written;
54 };
55 
56 static struct canopen_program_context ctx;
57 
canopen_program_set_status(uint32_t status)58 static void canopen_program_set_status(uint32_t status)
59 {
60 	ctx.program_status = status;
61 }
62 
canopen_program_get_status(void)63 static uint32_t canopen_program_get_status(void)
64 {
65 	/*
66 	 * Non-confirmed boot image takes precedence over other
67 	 * status. This must be checked on every invocation since the
68 	 * app may be using other means of confirming the image.
69 	 */
70 	if (!boot_is_img_confirmed()) {
71 		return PROGRAM_CTRL_ZEPHYR_CONFIRM;
72 	}
73 
74 	return ctx.program_status;
75 }
76 
canopen_odf_1f50(CO_ODF_arg_t * odf_arg)77 static CO_SDO_abortCode_t canopen_odf_1f50(CO_ODF_arg_t *odf_arg)
78 {
79 	int err;
80 
81 	if (odf_arg->subIndex != 1U) {
82 		return CO_SDO_AB_NONE;
83 	}
84 
85 	if (odf_arg->reading) {
86 		return CO_SDO_AB_WRITEONLY;
87 	}
88 
89 	if (canopen_program_get_status() != PROGRAM_CTRL_CLEAR) {
90 		ctx.flash_status = FLASH_STATUS_FLASH_NOT_CLEARED;
91 		return CO_SDO_AB_DATA_DEV_STATE;
92 	}
93 
94 	if (odf_arg->firstSegment) {
95 		err = flash_img_init(&ctx.flash_img_ctx);
96 		if (err) {
97 			LOG_ERR("failed to initialize flash img (err %d)", err);
98 			CO_errorReport(ctx.em, CO_EM_NON_VOLATILE_MEMORY,
99 				       CO_EMC_HARDWARE, err);
100 			ctx.flash_status = FLASH_STATUS_FLASH_WRITE_ERROR;
101 			return CO_SDO_AB_HW;
102 		}
103 		ctx.flash_status = FLASH_STATUS_IN_PROGRESS;
104 		if (IS_ENABLED(CONFIG_CANOPENNODE_LEDS)) {
105 			canopen_leds_program_download(true);
106 		}
107 		ctx.total = odf_arg->dataLengthTotal;
108 		LOG_DBG("total = %d", ctx.total);
109 	}
110 
111 	err = flash_img_buffered_write(&ctx.flash_img_ctx, odf_arg->data,
112 				       odf_arg->dataLength,
113 				       odf_arg->lastSegment);
114 	if (err) {
115 		CO_errorReport(ctx.em, CO_EM_NON_VOLATILE_MEMORY,
116 			       CO_EMC_HARDWARE, err);
117 		ctx.flash_status = FLASH_STATUS_FLASH_WRITE_ERROR;
118 		canopen_leds_program_download(false);
119 		return CO_SDO_AB_HW;
120 	}
121 
122 	if (odf_arg->lastSegment) {
123 		/* ctx.total is zero if not provided by download process */
124 		if (ctx.total != 0 &&
125 		    ctx.total != flash_img_bytes_written(&ctx.flash_img_ctx)) {
126 			LOG_WRN("premature end of program download");
127 			ctx.flash_status = FLASH_STATUS_DATA_FORMAT_ERROR;
128 		} else {
129 			LOG_DBG("program downloaded");
130 			ctx.flash_written = true;
131 			ctx.flash_status = FLASH_STATUS_NO_ERROR;
132 		}
133 
134 		canopen_program_set_status(PROGRAM_CTRL_STOP);
135 		canopen_leds_program_download(false);
136 	}
137 
138 	return CO_SDO_AB_NONE;
139 }
140 
canopen_program_cmd_stop(void)141 static inline CO_SDO_abortCode_t canopen_program_cmd_stop(void)
142 {
143 	if (canopen_program_get_status() == PROGRAM_CTRL_ZEPHYR_CONFIRM) {
144 		return CO_SDO_AB_DATA_DEV_STATE;
145 	}
146 
147 	LOG_DBG("program stopped");
148 	canopen_program_set_status(PROGRAM_CTRL_STOP);
149 
150 	return CO_SDO_AB_NONE;
151 }
152 
canopen_program_cmd_start(void)153 static inline CO_SDO_abortCode_t canopen_program_cmd_start(void)
154 {
155 	int err;
156 
157 	if (canopen_program_get_status() == PROGRAM_CTRL_ZEPHYR_CONFIRM) {
158 		return CO_SDO_AB_DATA_DEV_STATE;
159 	}
160 
161 	if (ctx.flash_written) {
162 		LOG_DBG("requesting upgrade and reset");
163 
164 		err = boot_request_upgrade(BOOT_UPGRADE_TEST);
165 		if (err) {
166 			LOG_ERR("failed to request upgrade (err %d)", err);
167 			CO_errorReport(ctx.em, CO_EM_NON_VOLATILE_MEMORY,
168 				       CO_EMC_HARDWARE, err);
169 			return CO_SDO_AB_HW;
170 		}
171 
172 		ctx.nmt->resetCommand = CO_RESET_APP;
173 	} else {
174 		LOG_DBG("program started");
175 		canopen_program_set_status(PROGRAM_CTRL_START);
176 	}
177 
178 	return CO_SDO_AB_NONE;
179 }
180 
canopen_program_cmd_clear(void)181 static inline CO_SDO_abortCode_t canopen_program_cmd_clear(void)
182 {
183 	int err;
184 
185 	if (canopen_program_get_status() != PROGRAM_CTRL_STOP) {
186 		return CO_SDO_AB_DATA_DEV_STATE;
187 	}
188 
189 	if (!IS_ENABLED(CONFIG_IMG_ERASE_PROGRESSIVELY)) {
190 		LOG_DBG("erasing flash area");
191 
192 		err = boot_erase_img_bank(FIXED_PARTITION_ID(slot1_partition));
193 		if (err) {
194 			LOG_ERR("failed to erase image bank (err %d)", err);
195 			CO_errorReport(ctx.em, CO_EM_NON_VOLATILE_MEMORY,
196 				       CO_EMC_HARDWARE, err);
197 			return CO_SDO_AB_HW;
198 		}
199 	}
200 
201 	LOG_DBG("program cleared");
202 	canopen_program_set_status(PROGRAM_CTRL_CLEAR);
203 	ctx.flash_status = FLASH_STATUS_NO_ERROR;
204 	ctx.flash_written = false;
205 
206 	return CO_SDO_AB_NONE;
207 }
208 
canopen_program_cmd_confirm(void)209 static inline CO_SDO_abortCode_t canopen_program_cmd_confirm(void)
210 {
211 	int err;
212 
213 	if (canopen_program_get_status() == PROGRAM_CTRL_ZEPHYR_CONFIRM) {
214 		err = boot_write_img_confirmed();
215 		if (err) {
216 			LOG_ERR("failed to confirm image (err %d)", err);
217 			CO_errorReport(ctx.em, CO_EM_NON_VOLATILE_MEMORY,
218 				       CO_EMC_HARDWARE, err);
219 			return CO_SDO_AB_HW;
220 		}
221 
222 		LOG_DBG("program confirmed");
223 		canopen_program_set_status(PROGRAM_CTRL_START);
224 	}
225 
226 	return CO_SDO_AB_NONE;
227 }
228 
canopen_odf_1f51(CO_ODF_arg_t * odf_arg)229 static CO_SDO_abortCode_t canopen_odf_1f51(CO_ODF_arg_t *odf_arg)
230 {
231 	CO_SDO_abortCode_t ab;
232 	uint8_t cmd;
233 
234 	if (odf_arg->subIndex != 1U) {
235 		return CO_SDO_AB_NONE;
236 	}
237 
238 	if (odf_arg->reading) {
239 		odf_arg->data[0] = canopen_program_get_status();
240 		return CO_SDO_AB_NONE;
241 	}
242 
243 	if (CO_NMT_getInternalState(ctx.nmt) != CO_NMT_PRE_OPERATIONAL) {
244 		LOG_DBG("not in pre-operational state");
245 		return CO_SDO_AB_DATA_DEV_STATE;
246 	}
247 
248 	/* Preserve old value */
249 	cmd = odf_arg->data[0];
250 	memcpy(odf_arg->data, odf_arg->ODdataStorage, sizeof(uint8_t));
251 
252 	LOG_DBG("program status = %d, cmd = %d", canopen_program_get_status(),
253 		cmd);
254 
255 	switch (cmd) {
256 	case PROGRAM_CTRL_STOP:
257 		ab = canopen_program_cmd_stop();
258 		break;
259 	case PROGRAM_CTRL_START:
260 		ab = canopen_program_cmd_start();
261 		break;
262 	case PROGRAM_CTRL_CLEAR:
263 		ab = canopen_program_cmd_clear();
264 		break;
265 	case PROGRAM_CTRL_ZEPHYR_CONFIRM:
266 		ab = canopen_program_cmd_confirm();
267 		break;
268 	case PROGRAM_CTRL_RESET:
269 		__fallthrough;
270 	default:
271 		LOG_DBG("unsupported command '%d'", cmd);
272 		ab = CO_SDO_AB_INVALID_VALUE;
273 	}
274 
275 	return ab;
276 }
277 
278 #ifdef CONFIG_BOOTLOADER_MCUBOOT
279 /** @brief Calculate crc for region in flash
280  *
281  * @param flash_area Flash area to read from, must be open
282  * @offset Offset to read from
283  * @size Number of bytes to include in calculation
284  * @pcrc Pointer to uint32_t where crc will be written if return value is 0
285  *
286  * @return 0 if successful, negative errno on failure
287  */
flash_crc(const struct flash_area * flash_area,off_t offset,size_t size,uint32_t * pcrc)288 static int flash_crc(const struct flash_area *flash_area,
289 		off_t offset, size_t size, uint32_t *pcrc)
290 {
291 	uint32_t crc = 0;
292 	uint8_t buffer[32];
293 
294 	while (size > 0) {
295 		size_t len = MIN(size, sizeof(buffer));
296 
297 		int err = flash_area_read(flash_area, offset, buffer, len);
298 
299 		if (err) {
300 			return err;
301 		}
302 
303 		crc = crc32_ieee_update(crc, buffer, len);
304 
305 		offset += len;
306 		size -= len;
307 	}
308 
309 	*pcrc = crc;
310 
311 	return 0;
312 }
313 
canopen_odf_1f56(CO_ODF_arg_t * odf_arg)314 static CO_SDO_abortCode_t canopen_odf_1f56(CO_ODF_arg_t *odf_arg)
315 {
316 	const struct flash_area *flash_area;
317 	struct mcuboot_img_header header;
318 	off_t offset = 0;
319 	uint32_t crc = 0;
320 	uint8_t fa_id;
321 	uint32_t len;
322 	int err;
323 
324 	if (odf_arg->subIndex != 1U) {
325 		return CO_SDO_AB_NONE;
326 	}
327 
328 	if (!odf_arg->reading) {
329 		/* Preserve old value */
330 		memcpy(odf_arg->data, odf_arg->ODdataStorage, sizeof(uint32_t));
331 		return CO_SDO_AB_READONLY;
332 	}
333 
334 	/* Reading from flash and calculating crc can take 100ms or more, and
335 	 * this function is called with the can od lock taken.
336 	 *
337 	 * Release the lock before performing time consuming work, and reacquire
338 	 * before return.
339 	 */
340 	CO_UNLOCK_OD();
341 
342 	/*
343 	 * Calculate the CRC32 of the image that is running or will be
344 	 * started upon receiveing the next 'start' command.
345 	 */
346 	if (ctx.flash_written) {
347 		fa_id = FIXED_PARTITION_ID(slot1_partition);
348 	} else {
349 		fa_id = FIXED_PARTITION_ID(slot0_partition);
350 	}
351 
352 	err = boot_read_bank_header(fa_id, &header, sizeof(header));
353 	if (err) {
354 		LOG_WRN("failed to read bank header (err %d)", err);
355 		CO_setUint32(odf_arg->data, 0U);
356 
357 		CO_LOCK_OD();
358 		return CO_SDO_AB_NONE;
359 	}
360 
361 	if (header.mcuboot_version != 1) {
362 		LOG_WRN("unsupported mcuboot header version %d",
363 			header.mcuboot_version);
364 		CO_setUint32(odf_arg->data, 0U);
365 
366 		CO_LOCK_OD();
367 		return CO_SDO_AB_NONE;
368 	}
369 	len = header.h.v1.image_size;
370 
371 	err = flash_area_open(fa_id, &flash_area);
372 	if (err) {
373 		LOG_ERR("failed to open flash area (err %d)", err);
374 		CO_errorReport(ctx.em, CO_EM_NON_VOLATILE_MEMORY,
375 			       CO_EMC_HARDWARE, err);
376 
377 		CO_LOCK_OD();
378 		return CO_SDO_AB_HW;
379 	}
380 
381 	err = flash_crc(flash_area, offset, len, &crc);
382 
383 	flash_area_close(flash_area);
384 
385 	if (err) {
386 		LOG_ERR("failed to read flash (err %d)", err);
387 		CO_errorReport(ctx.em, CO_EM_NON_VOLATILE_MEMORY,
388 			       CO_EMC_HARDWARE, err);
389 
390 		CO_LOCK_OD();
391 		return CO_SDO_AB_HW;
392 	}
393 
394 	CO_setUint32(odf_arg->data, crc);
395 
396 	CO_LOCK_OD();
397 	return CO_SDO_AB_NONE;
398 }
399 #endif /* CONFIG_BOOTLOADER_MCUBOOT */
400 
canopen_odf_1f57(CO_ODF_arg_t * odf_arg)401 static CO_SDO_abortCode_t canopen_odf_1f57(CO_ODF_arg_t *odf_arg)
402 {
403 	if (odf_arg->subIndex != 1U) {
404 		return CO_SDO_AB_NONE;
405 	}
406 
407 	if (!odf_arg->reading) {
408 		/* Preserve old value */
409 		memcpy(odf_arg->data, odf_arg->ODdataStorage, sizeof(uint32_t));
410 		return CO_SDO_AB_READONLY;
411 	}
412 
413 	CO_setUint32(odf_arg->data, ctx.flash_status);
414 
415 	return CO_SDO_AB_NONE;
416 }
417 
canopen_program_download_attach(CO_NMT_t * nmt,CO_SDO_t * sdo,CO_EM_t * em)418 void canopen_program_download_attach(CO_NMT_t *nmt, CO_SDO_t *sdo, CO_EM_t *em)
419 {
420 	canopen_program_set_status(PROGRAM_CTRL_START);
421 	ctx.flash_status = FLASH_STATUS_NO_ERROR;
422 	ctx.flash_written = false;
423 	ctx.nmt = nmt;
424 	ctx.em = em;
425 
426 	CO_OD_configure(sdo, OD_H1F50_PROGRAM_DATA, canopen_odf_1f50,
427 			NULL, 0U, 0U);
428 
429 	CO_OD_configure(sdo, OD_H1F51_PROGRAM_CTRL, canopen_odf_1f51,
430 			NULL, 0U, 0U);
431 
432 	if (IS_ENABLED(CONFIG_BOOTLOADER_MCUBOOT)) {
433 		CO_OD_configure(sdo, OD_H1F56_PROGRAM_SWID, canopen_odf_1f56,
434 				NULL, 0U, 0U);
435 	}
436 
437 	CO_OD_configure(sdo, OD_H1F57_FLASH_STATUS, canopen_odf_1f57,
438 			NULL, 0U, 0U);
439 }
440