1 /*
2 * Copyright (c) 2021 NXP
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7
8 #include <zephyr/kernel.h>
9 #include <zephyr/ztest.h>
10 #include <zephyr/storage/disk_access.h>
11 #include <zephyr/device.h>
12 #include <zephyr/timing/timing.h>
13 #include <zephyr/random/random.h>
14
15 #if defined(CONFIG_DISK_DRIVER_SDMMC)
16 #define DISK_NAME "SD"
17 #elif defined(CONFIG_DISK_DRIVER_MMC)
18 #define DISK_NAME "SD2"
19 #elif defined(CONFIG_NVME)
20 #define DISK_NAME "nvme0n0"
21 #else
22 #error "No disk device defined, is your board supported?"
23 #endif
24
25 /* Assume the largest sector we will encounter is 512 bytes */
26 #define SECTOR_SIZE 512
27 #if CONFIG_SRAM_SIZE >= 512
28 /* Cap buffer size at 128 KiB */
29 #define SEQ_BLOCK_COUNT 256
30 #elif CONFIG_SOC_POSIX
31 /* Posix does not define SRAM size */
32 #define SEQ_BLOCK_COUNT 256
33 #else
34 /* Two buffers with 512 byte blocks will use half of all SRAM */
35 #define SEQ_BLOCK_COUNT (CONFIG_SRAM_SIZE / 2)
36 #endif
37 #define BUF_SIZE (SECTOR_SIZE * SEQ_BLOCK_COUNT)
38 /* Number of sequential reads to get an average speed */
39 #define SEQ_ITERATIONS 10
40 /* Number of random reads to get an IOPS calculation */
41 #define RANDOM_ITERATIONS SEQ_BLOCK_COUNT
42
43 static uint32_t chosen_sectors[RANDOM_ITERATIONS];
44
45
46 static const char *disk_pdrv = DISK_NAME;
47 static uint32_t disk_sector_count;
48 static uint32_t disk_sector_size;
49
50 static uint8_t test_buf[BUF_SIZE] __aligned(32);
51 static uint8_t backup_buf[BUF_SIZE] __aligned(32);
52
53 static bool disk_init_done;
54
55 /* Sets up test by initializing disk */
test_setup(void)56 static void test_setup(void)
57 {
58 int rc;
59 uint32_t cmd_buf;
60
61 rc = disk_access_init(disk_pdrv);
62 zassert_equal(rc, 0, "Disk access initialization failed");
63
64 rc = disk_access_status(disk_pdrv);
65 zassert_equal(rc, DISK_STATUS_OK, "Disk status is not OK");
66
67 rc = disk_access_ioctl(disk_pdrv, DISK_IOCTL_GET_SECTOR_COUNT, &cmd_buf);
68 zassert_equal(rc, 0, "Disk ioctl get sector count failed");
69
70 TC_PRINT("Disk reports %u sectors\n", cmd_buf);
71 disk_sector_count = cmd_buf;
72
73 rc = disk_access_ioctl(disk_pdrv, DISK_IOCTL_GET_SECTOR_SIZE, &cmd_buf);
74 zassert_equal(rc, 0, "Disk ioctl get sector size failed");
75 TC_PRINT("Disk reports sector size %u\n", cmd_buf);
76 disk_sector_size = cmd_buf;
77
78 /* Assume sector size is 512 bytes, it will speed up calculations later */
79 zassert_true(cmd_buf == SECTOR_SIZE,
80 "Test will fail, SECTOR_SIZE definition must be changed");
81
82 disk_init_done = true;
83 }
84
85 /* Helper function to time multiple sequential reads. Returns average time. */
read_helper(uint32_t num_blocks)86 static uint64_t read_helper(uint32_t num_blocks)
87 {
88 int rc;
89 timing_t start_time, end_time;
90 uint64_t cycles, total_ns;
91
92 /* Start the timing system */
93 timing_init();
94 timing_start();
95
96 total_ns = 0;
97 for (int i = 0; i < SEQ_ITERATIONS; i++) {
98 start_time = timing_counter_get();
99
100 /* Read from start of disk */
101 rc = disk_access_read(disk_pdrv, test_buf, 0, num_blocks);
102
103 end_time = timing_counter_get();
104
105 zassert_equal(rc, 0, "disk read failed");
106
107 cycles = timing_cycles_get(&start_time, &end_time);
108 total_ns += timing_cycles_to_ns(cycles);
109 }
110 /* Stop timing system */
111 timing_stop();
112 /* Return average time */
113 return (total_ns / SEQ_ITERATIONS);
114 }
115
ZTEST(disk_performance,test_sequential_read)116 ZTEST(disk_performance, test_sequential_read)
117 {
118 uint64_t time_ns;
119
120 if (!disk_init_done) {
121 zassert_unreachable("Disk is not initialized");
122 }
123
124 /* Start with single sector read */
125 time_ns = read_helper(1);
126
127 TC_PRINT("Average read speed over one sector: %"PRIu64" KiB/s\n",
128 ((SECTOR_SIZE * (NSEC_PER_SEC / time_ns))) / 1024);
129
130 /* Now time long sequential read */
131 time_ns = read_helper(SEQ_BLOCK_COUNT);
132
133 TC_PRINT("Average read speed over %d sectors: %"PRIu64" KiB/s\n",
134 SEQ_BLOCK_COUNT,
135 ((BUF_SIZE) * (NSEC_PER_SEC / time_ns)) / 1024);
136 }
137
138 /* Helper function to time multiple sequential writes. Returns average time. */
write_helper(uint32_t num_blocks)139 static uint64_t write_helper(uint32_t num_blocks)
140 {
141 int rc;
142 timing_t start_time, end_time;
143 uint64_t cycles, total_ns;
144
145 /* Start the timing system */
146 timing_init();
147 timing_start();
148
149 /* Read block we will overwrite, to back it up. */
150 rc = disk_access_read(disk_pdrv, backup_buf, 0, num_blocks);
151 zassert_equal(rc, 0, "disk read failed");
152
153 /* Initialize write buffer with data */
154 sys_rand_get(test_buf, num_blocks * SECTOR_SIZE);
155
156 total_ns = 0;
157 for (int i = 0; i < SEQ_ITERATIONS; i++) {
158 start_time = timing_counter_get();
159
160 rc = disk_access_write(disk_pdrv, test_buf, 0, num_blocks);
161
162 end_time = timing_counter_get();
163
164 zassert_equal(rc, 0, "disk write failed");
165
166 cycles = timing_cycles_get(&start_time, &end_time);
167 total_ns += timing_cycles_to_ns(cycles);
168 }
169 /* Stop timing system */
170 timing_stop();
171
172 /* Replace block with backup */
173 rc = disk_access_write(disk_pdrv, backup_buf, 0, num_blocks);
174 zassert_equal(rc, 0, "disk write failed");
175 /* Return average time */
176 return (total_ns / SEQ_ITERATIONS);
177 }
178
ZTEST(disk_performance,test_sequential_write)179 ZTEST(disk_performance, test_sequential_write)
180 {
181 uint64_t time_ns;
182
183 if (!disk_init_done) {
184 zassert_unreachable("Disk is not initialized");
185 }
186
187 /* Start with single sector write */
188 time_ns = write_helper(1);
189
190 TC_PRINT("Average write speed over one sector: %"PRIu64" KiB/s\n",
191 ((SECTOR_SIZE * (NSEC_PER_SEC / time_ns))) / 1024);
192
193 /* Now time long sequential write */
194 time_ns = write_helper(SEQ_BLOCK_COUNT);
195
196 TC_PRINT("Average write speed over %d sectors: %"PRIu64" KiB/s\n",
197 SEQ_BLOCK_COUNT,
198 ((BUF_SIZE) * (NSEC_PER_SEC / time_ns)) / 1024);
199 }
200
ZTEST(disk_performance,test_random_read)201 ZTEST(disk_performance, test_random_read)
202 {
203 timing_t start_time, end_time;
204 uint64_t cycles, total_ns;
205 uint32_t sector;
206 int rc;
207
208 if (!disk_init_done) {
209 zassert_unreachable("Disk is not initialized");
210 }
211
212 /* Build list of sectors to read from. */
213 for (int i = 0; i < RANDOM_ITERATIONS; i++) {
214 /* Get random num until we select a value within sector count */
215 sector = sys_rand32_get() / ((UINT32_MAX / disk_sector_count) + 1);
216 chosen_sectors[i] = sector;
217 }
218
219 /* Start the timing system */
220 timing_init();
221 timing_start();
222
223 start_time = timing_counter_get();
224 for (int i = 0; i < RANDOM_ITERATIONS; i++) {
225 /*
226 * Note: we don't check return code here,
227 * we want to do I/O as fast as possible
228 */
229 rc = disk_access_read(disk_pdrv, test_buf, chosen_sectors[i], 1);
230 }
231 end_time = timing_counter_get();
232 zassert_equal(rc, 0, "Random read failed");
233 cycles = timing_cycles_get(&start_time, &end_time);
234 total_ns = timing_cycles_to_ns(cycles);
235 /* Stop timing system */
236 timing_stop();
237
238 TC_PRINT("512 Byte IOPS over %d random reads: %"PRIu64" IOPS\n",
239 RANDOM_ITERATIONS,
240 ((uint64_t)(((uint64_t)RANDOM_ITERATIONS)*
241 ((uint64_t)NSEC_PER_SEC)))
242 / total_ns);
243 }
244
ZTEST(disk_performance,test_random_write)245 ZTEST(disk_performance, test_random_write)
246 {
247 timing_t start_time, end_time;
248 uint64_t cycles, total_ns;
249 uint32_t sector;
250 int rc;
251
252 if (!disk_init_done) {
253 zassert_unreachable("Disk is not initialized");
254 }
255
256 /* Build list of sectors to read from. */
257 for (int i = 0; i < RANDOM_ITERATIONS; i++) {
258 /* Get random num until we select a value within sector count */
259 sector = sys_rand32_get() / ((UINT32_MAX / disk_sector_count) + 1);
260 chosen_sectors[i] = sector;
261 /* Backup this sector */
262 rc = disk_access_read(disk_pdrv, &backup_buf[i * SECTOR_SIZE],
263 sector, 1);
264 zassert_equal(rc, 0, "disk read failed for random write backup");
265 }
266
267 /* Initialize write buffer with data */
268 sys_rand_get(test_buf, BUF_SIZE);
269
270 /* Start the timing system */
271 timing_init();
272 timing_start();
273
274 start_time = timing_counter_get();
275 for (int i = 0; i < RANDOM_ITERATIONS; i++) {
276 /*
277 * Note: we don't check return code here,
278 * we want to do I/O as fast as possible
279 */
280 rc = disk_access_write(disk_pdrv, &test_buf[i * SECTOR_SIZE],
281 chosen_sectors[i], 1);
282 }
283 end_time = timing_counter_get();
284 zassert_equal(rc, 0, "Random write failed");
285 cycles = timing_cycles_get(&start_time, &end_time);
286 total_ns = timing_cycles_to_ns(cycles);
287 /* Stop timing system */
288 timing_stop();
289
290 TC_PRINT("512 Byte IOPS over %d random writes: %"PRIu64" IOPS\n",
291 RANDOM_ITERATIONS,
292 ((uint64_t)(((uint64_t)RANDOM_ITERATIONS)*
293 ((uint64_t)NSEC_PER_SEC)))
294 / total_ns);
295 /* Restore backed up sectors */
296 for (int i = 0; i < RANDOM_ITERATIONS; i++) {
297 disk_access_write(disk_pdrv, &backup_buf[i * SECTOR_SIZE],
298 chosen_sectors[i], 1);
299 zassert_equal(rc, 0, "failed to write backup sector to disk");
300 }
301 }
302
disk_setup(void)303 static void *disk_setup(void)
304 {
305 test_setup();
306
307 return NULL;
308 }
309
310 ZTEST_SUITE(disk_performance, NULL, disk_setup, NULL, NULL, NULL);
311