1 /*
2  * Copyright (c) 2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include "usbd_msg.h"
8 
9 #include <zephyr/kernel.h>
10 #include <zephyr/usb/usbd.h>
11 #include <zephyr/usb/class/usbd_dfu.h>
12 #include <zephyr/drivers/flash.h>
13 #include <zephyr/storage/flash_map.h>
14 #include <zephyr/dfu/flash_img.h>
15 
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_REGISTER(dfu_flash, CONFIG_USBD_DFU_LOG_LEVEL);
18 
19 /*
20  * This file implements the flash backend for the USB DFU implementation. The
21  * flash backend can serve up to two image slots, which are typically defined
22  * for the in-tree boards in the Zephyr project.
23  */
24 
25 struct usbd_dfu_flash_data {
26 	struct flash_img_context fi_ctx;
27 	uint32_t last_block;
28 	const uint8_t id;
29 	union {
30 		uint32_t uploaded;
31 		uint32_t downloaded;
32 	};
33 };
34 
dfu_flash_read(void * const priv,const uint32_t block,const uint16_t size,uint8_t buf[static CONFIG_USBD_DFU_TRANSFER_SIZE])35 static int dfu_flash_read(void *const priv,
36 			  const uint32_t block, const uint16_t size,
37 			  uint8_t buf[static CONFIG_USBD_DFU_TRANSFER_SIZE])
38 {
39 	struct usbd_dfu_flash_data *const data = priv;
40 	const struct flash_area *fa;
41 	uint32_t to_upload;
42 	int len;
43 	int ret;
44 
45 	if (size == 0) {
46 		/* There is nothing to upload */
47 		return 0;
48 	}
49 
50 	if (block == 0) {
51 		data->last_block = 0;
52 		data->uploaded = 0;
53 	} else {
54 		if (data->last_block + 1U != block) {
55 			return -EINVAL;
56 		}
57 
58 	}
59 
60 	ret = flash_area_open(data->id, &fa);
61 	if (ret) {
62 		return ret;
63 	}
64 
65 	if (block == 0) {
66 		LOG_DBG("Flash area size %u", fa->fa_size);
67 	}
68 
69 	to_upload = fa->fa_size - data->uploaded;
70 	if (to_upload < size) {
71 		len = to_upload;
72 	} else {
73 		len = size;
74 	}
75 
76 	ret = flash_area_read(fa, data->uploaded, buf, len);
77 	flash_area_close(fa);
78 	if (ret) {
79 		return ret;
80 	}
81 
82 	data->last_block = block;
83 	data->uploaded += size;
84 	LOG_DBG("uploaded %u block %u len %u", data->uploaded, block, len);
85 
86 	return len;
87 }
88 
dfu_flash_write(void * const priv,const uint32_t block,const uint16_t size,const uint8_t buf[static CONFIG_USBD_DFU_TRANSFER_SIZE])89 static int dfu_flash_write(void *const priv,
90 			   const uint32_t block, const uint16_t size,
91 			   const uint8_t buf[static CONFIG_USBD_DFU_TRANSFER_SIZE])
92 {
93 	struct usbd_dfu_flash_data *const data = priv;
94 	const bool flush = size == 0 ? true : false;
95 	int ret;
96 
97 	if (block == 0) {
98 		if (flash_img_init(&data->fi_ctx)) {
99 			return -EINVAL;
100 		}
101 
102 		data->last_block = 0;
103 		data->downloaded = 0;
104 
105 		if (size == 0) {
106 			/* There is nothing to download */
107 			return 0;
108 		}
109 	} else {
110 		if (data->last_block + 1U != block) {
111 			return -EINVAL;
112 		}
113 
114 	}
115 
116 	ret = flash_img_buffered_write(&data->fi_ctx, buf, size, flush);
117 	if (ret) {
118 		return ret;
119 	}
120 
121 	data->last_block = block;
122 	data->downloaded += size;
123 	LOG_DBG("downloaded %u (%u) block %u size %u", data->downloaded,
124 		flash_img_bytes_written(&data->fi_ctx), block, size);
125 
126 	return 0;
127 }
128 
dfu_flash_next(void * const priv,const enum usb_dfu_state state,const enum usb_dfu_state next)129 static bool dfu_flash_next(void *const priv,
130 			   const enum usb_dfu_state state, const enum usb_dfu_state next)
131 {
132 	if (state == DFU_MANIFEST_SYNC && next == DFU_IDLE) {
133 		LOG_DBG("Download finished");
134 	}
135 
136 	return true;
137 }
138 
139 #if FIXED_PARTITION_EXISTS(slot0_partition) && defined(CONFIG_USBD_DFU_FLASH_SLOT0)
140 static struct usbd_dfu_flash_data slot0_data = {
141 	.id = FIXED_PARTITION_ID(slot0_partition),
142 };
143 
144 USBD_DFU_DEFINE_IMG(slot0_image, "slot0_image", &slot0_data,
145 		    dfu_flash_read, dfu_flash_write, dfu_flash_next);
146 #endif
147 
148 #if FIXED_PARTITION_EXISTS(slot1_partition) && defined(CONFIG_USBD_DFU_FLASH_SLOT1)
149 static struct usbd_dfu_flash_data slot1_data = {
150 	.id = FIXED_PARTITION_ID(slot1_partition),
151 };
152 
153 USBD_DFU_DEFINE_IMG(slot1_image, "slot1_image", &slot1_data,
154 		    dfu_flash_read, dfu_flash_write, dfu_flash_next);
155 #endif
156