1 /*
2 * Copyright (c) 2023 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/ztest.h>
8 #include <zephyr/storage/flash_map.h>
9 #include <zephyr/bluetooth/mesh.h>
10
11 #include "mesh/blob.h"
12
13 #define SLOT1_PARTITION slot1_partition
14 #define SLOT1_PARTITION_ID FIXED_PARTITION_ID(SLOT1_PARTITION)
15 #define SLOT1_PARTITION_SIZE FIXED_PARTITION_SIZE(SLOT1_PARTITION)
16 /* Chunk size is set to value that is not multiple of 4, to verify that chunks are written correctly
17 * even if they are not aligned with word length used in flash
18 */
19 #define CHUNK_SIZE 65
20
21 static struct bt_mesh_blob_io_flash blob_flash_stream;
22
chunk_size(const struct bt_mesh_blob_block * block,uint16_t chunk_idx)23 static size_t chunk_size(const struct bt_mesh_blob_block *block,
24 uint16_t chunk_idx)
25 {
26 if ((chunk_idx == block->chunk_count - 1) &&
27 (block->size % CHUNK_SIZE)) {
28 return block->size % CHUNK_SIZE;
29 }
30
31 return CHUNK_SIZE;
32 }
33
block_size_to_log(size_t size)34 static uint8_t block_size_to_log(size_t size)
35 {
36 uint8_t block_size_log = 0;
37
38 while (size > 1) {
39 size = size / 2;
40 block_size_log++;
41 }
42
43 return block_size_log;
44 }
45
46 ZTEST_SUITE(blob_io_flash, NULL, NULL, NULL, NULL, NULL);
47
ZTEST(blob_io_flash,test_chunk_read)48 ZTEST(blob_io_flash, test_chunk_read)
49 {
50 const struct flash_area *fa = NULL;
51 struct bt_mesh_blob_xfer xfer;
52 struct bt_mesh_blob_block block = { 0 };
53 struct bt_mesh_blob_chunk chunk = { 0 };
54 size_t remaining = SLOT1_PARTITION_SIZE;
55 off_t block_idx = 0;
56 uint16_t chunk_idx = 0;
57 uint8_t chunk_data[CHUNK_SIZE];
58 uint8_t test_data[SLOT1_PARTITION_SIZE];
59 uint8_t ctrl_data[SLOT1_PARTITION_SIZE];
60 size_t tests_data_offset = 0;
61 int i, err;
62
63 /* Fill test data with pattern */
64 for (i = 0; i < SLOT1_PARTITION_SIZE; i++) {
65 test_data[i] = i % 0xFF;
66 }
67
68 err = flash_area_open(SLOT1_PARTITION_ID, &fa);
69 zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
70
71 err = flash_area_flatten(fa, 0, ARRAY_SIZE(ctrl_data));
72 zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
73
74 err = flash_area_write(fa, 0, test_data, ARRAY_SIZE(ctrl_data));
75 zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
76
77 err = flash_area_read(fa, 0, ctrl_data, ARRAY_SIZE(ctrl_data));
78 zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
79
80 zassert_mem_equal(ctrl_data, test_data, ARRAY_SIZE(ctrl_data),
81 "Incorrect data written into flash");
82
83 memset(ctrl_data, 0, SLOT1_PARTITION_SIZE);
84
85 flash_area_close(fa);
86
87 err = bt_mesh_blob_io_flash_init(&blob_flash_stream,
88 SLOT1_PARTITION_ID, 0);
89 zassert_equal(err, 0, "BLOB I/O init failed with err=%d", err);
90
91 err = blob_flash_stream.io.open(&blob_flash_stream.io, &xfer, BT_MESH_BLOB_READ);
92 zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
93
94 chunk.data = chunk_data;
95
96 /* Simulate reading whole partition divided into blocks and chunk of maximum sizes */
97 while (remaining > 0) {
98 block.chunk_count =
99 DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX,
100 CHUNK_SIZE);
101 block.size = remaining > CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
102 ? CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
103 : remaining;
104
105 /* BLOB Client should do nothing to flash area as it's in read mode */
106 err = blob_flash_stream.io.block_start(&blob_flash_stream.io, &xfer, &block);
107 zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
108
109 /* `block_start` in write mode will erase flash pages that can fit block.
110 * Assert that at least block size of data was not erased in read mode
111 */
112 flash_area_read(blob_flash_stream.area, block.offset, ctrl_data, block.size);
113 zassert_mem_equal(ctrl_data, &test_data[block.offset], block.size,
114 "Flash data was altered by `block_start` in read mode");
115
116 memset(ctrl_data, 0, SLOT1_PARTITION_SIZE);
117
118 block.offset = block_idx * (1 << block_size_to_log(block.size));
119
120 for (i = 0; i < block.chunk_count; i++) {
121 chunk.size = chunk_size(&block, chunk_idx);
122 chunk.offset = CHUNK_SIZE * chunk_idx;
123
124 err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
125 zassert_equal(err, 0, "BLOB I/O read failed with err=%d off=%d len=%d",
126 err, block.offset + chunk.offset, chunk.size);
127
128 zassert_mem_equal(&chunk_data, &test_data[tests_data_offset], chunk.size,
129 "Incorrect data written into flash");
130 chunk_idx++;
131
132 remaining -= chunk.size;
133 tests_data_offset += chunk.size;
134 }
135 block_idx++;
136 chunk_idx = 0;
137 }
138
139 /* We read whole sector as BLOB. Try to increment every offset by one and read,
140 * which should attempt to read outside flash area
141 */
142 chunk.offset++;
143 err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
144 zassert_false(err == 0, "Read outside flash area successful");
145
146 chunk.offset--;
147 block.offset++;
148 err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
149 zassert_false(err == 0, "Read outside flash area successful");
150
151 block.offset--;
152 blob_flash_stream.offset++;
153 err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
154 zassert_false(err == 0, "Read outside flash area successful");
155
156 blob_flash_stream.io.close(&blob_flash_stream.io, &xfer);
157 }
158
ZTEST(blob_io_flash,test_chunk_write)159 ZTEST(blob_io_flash, test_chunk_write)
160 {
161 struct bt_mesh_blob_xfer xfer;
162 struct bt_mesh_blob_block block = { 0 };
163 struct bt_mesh_blob_chunk chunk = { 0 };
164 size_t remaining = SLOT1_PARTITION_SIZE;
165 off_t block_idx = 0;
166 uint16_t chunk_idx = 0;
167 uint8_t chunk_data[CHUNK_SIZE];
168 /* 3 is maximum length of padding at the end of written chunk */
169 uint8_t chunk_ctrl_data[CHUNK_SIZE + 3];
170 uint8_t end_padding_len;
171 uint8_t test_data[SLOT1_PARTITION_SIZE];
172 uint8_t erased_block_data[CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX];
173 uint8_t ctrl_data[SLOT1_PARTITION_SIZE];
174 size_t tests_data_offset = 0;
175 int i, j, err;
176
177 /* Fill test data with pattern */
178 for (i = 0; i < SLOT1_PARTITION_SIZE; i++) {
179 test_data[i] = i % 0xFF;
180 }
181
182 err = bt_mesh_blob_io_flash_init(&blob_flash_stream,
183 SLOT1_PARTITION_ID, 0);
184 zassert_equal(err, 0, "BLOB I/O init failed with err=%d", err);
185
186 err = blob_flash_stream.io.open(&blob_flash_stream.io, &xfer, BT_MESH_BLOB_WRITE);
187 zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
188
189 chunk.data = chunk_data;
190
191 memset(erased_block_data, flash_area_erased_val(blob_flash_stream.area),
192 CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX);
193 /* Simulate writing whole partition divided into blocks and chunk of maximum sizes */
194 while (remaining > 0) {
195 block.chunk_count =
196 DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX,
197 CHUNK_SIZE);
198 block.size = remaining > CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
199 ? CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
200 : remaining;
201 block.offset = block_idx * (1 << block_size_to_log(block.size));
202
203 err = blob_flash_stream.io.block_start(&blob_flash_stream.io, &xfer, &block);
204 zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
205
206 flash_area_read(blob_flash_stream.area, block.offset,
207 ctrl_data, block.size);
208
209 zassert_mem_equal(ctrl_data, erased_block_data, block.size,
210 "Flash data was not erased by `block_start` in write mode");
211
212 memset(ctrl_data, 0, SLOT1_PARTITION_SIZE);
213
214 for (i = 0; i < block.chunk_count; i++) {
215 chunk.size = chunk_size(&block, chunk_idx);
216 chunk.offset = CHUNK_SIZE * chunk_idx;
217
218 memcpy(chunk.data,
219 &test_data[chunk.offset + block.offset],
220 chunk.size);
221
222 err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
223 zassert_equal(err, 0, "BLOB I/O write failed with err=%d", err);
224
225 /* To calculate end padding length we must calculate size of whole buffer
226 * and subtract start offset length and chunk size
227 */
228 end_padding_len =
229 ROUND_UP((block.offset + chunk.offset) %
230 flash_area_align(blob_flash_stream.area) +
231 chunk.size, flash_area_align(blob_flash_stream.area)) -
232 (block.offset + chunk.offset) %
233 flash_area_align(blob_flash_stream.area) - chunk.size;
234
235 flash_area_read(blob_flash_stream.area, block.offset + chunk.offset,
236 chunk_ctrl_data, chunk.size + end_padding_len);
237
238 zassert_mem_equal(chunk_ctrl_data, chunk_data, chunk.size,
239 "Incorrect data written into flash");
240
241 /* Assert that nothing was written into end padding */
242 for (j = 1; j <= end_padding_len; j++) {
243 zassert_equal(chunk_ctrl_data[chunk.size + j],
244 flash_area_erased_val(blob_flash_stream.area));
245 }
246 chunk_idx++;
247
248 remaining -= chunk.size;
249 tests_data_offset += chunk.size;
250 }
251
252 block_idx++;
253 chunk_idx = 0;
254 }
255
256 flash_area_read(blob_flash_stream.area, 0, ctrl_data, SLOT1_PARTITION_SIZE);
257 zassert_mem_equal(ctrl_data, test_data, SLOT1_PARTITION_SIZE,
258 "Incorrect chunks written into flash");
259
260 /* We wrote whole sector as BLOB. Try to increment every offset by one and write,
261 * which should attempt to write outside flash area
262 */
263 chunk.offset++;
264 err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
265 zassert_false(err == 0, "Write outside flash area successful");
266
267 chunk.offset--;
268 block.offset++;
269 err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
270 zassert_false(err == 0, "Write outside flash area successful");
271
272 block.offset--;
273 blob_flash_stream.offset++;
274 err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
275 zassert_false(err == 0, "Write outside flash area successful");
276
277 blob_flash_stream.io.close(&blob_flash_stream.io, &xfer);
278 }
279