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