Morse Micro IoT SDK  2.9.7
test_wlan_io.c
1/*
2 * Copyright 2023 Morse Micro
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7#include "porting_assistant.h"
8#include "sdio_spi.h"
9#include "mmhal.h"
10#include "mmlog.h"
11#include "mmutils.h"
12#include "chip_cfg.h"
13
15#define BENCHMARK_ADDR_START 0x80100000
16
19#define BULK_RW_PACKET_LEN_BYTES (1496)
20
22#define BENCHMARK_WAIT_MS (2500)
23
24
26const struct chip_cfg *probed_chip_cfg;
27
35static bool validate_chip_id(uint32_t chip_id, const struct chip_cfg *chip_cfg)
36{
37 for (size_t ii = 0; ii < chip_cfg->n_valid_chip_ids; ii++)
38 {
39 if (chip_id == chip_cfg->valid_chip_ids[ii])
40 {
41 return true;
42 }
43 }
44
45 return false;
46}
47
49static volatile uint8_t irq_counter = 0;
50
55static void test_hal_irq_handle(void)
56{
57 irq_counter++;
58}
59
60TEST_STEP(test_step_mmhal_wlan_init, "WLAN HAL initialisation")
61{
62 MM_UNUSED(log_buf);
63 MM_UNUSED(log_buf_len);
64
66
67 /* We don't get indication of success or failure from mmhal_wlan_init() so we return
68 * "no result". */
69 return TEST_NO_RESULT;
70}
71
72TEST_STEP(test_step_mmhal_wlan_hard_reset, "Hard reset device")
73{
74 MM_UNUSED(log_buf);
75 MM_UNUSED(log_buf_len);
76
78
79 /*
80 * Since mmhal_wlan_hard_reset() does not return a status code we cannot verify here whether
81 * the MM chip was reset successfully at this stage, so we return "no result". To validate
82 * reset behavior an external logic analyzer can be used to probe the reset line.
83 */
84 return TEST_NO_RESULT;
85}
86
87TEST_STEP(test_step_mmhal_wlan_sdio_startup, "SDIO/SPI Startup")
88{
89 int ret;
90
91 MM_UNUSED(log_buf);
92 MM_UNUSED(log_buf_len);
93
95
96 switch (ret)
97 {
98 case 0:
99 return TEST_PASSED;
100
102 TEST_LOG_APPEND(
103 "SDIO/SPI controller hardware error. Possible causes:\n"
104 " - SDIO/SPI controller not configured correctly\n"
105 " - SDIO/SPI controller clock not enabled\n\n");
106 return TEST_FAILED;
107
109 TEST_LOG_APPEND(
110 "Timeout while executing an SDIO command. Possible causes:\n"
111 " - SDIO/SPI pins not set to correct function (e.g., output instead of alternative)\n"
112 " - SPI chip select not being asserted\n"
113 " - MM chip not powered on\n\n");
114 return TEST_FAILED;
115
117 TEST_LOG_APPEND(
118 "CRC error in command or response while executing an SDIO command. Possible causes:\n"
119 " - Noise on SPI/SDIO lines\n"
120 " - Wrong SPI device selected\n\n");
121 return TEST_FAILED;
122
124 TEST_LOG_APPEND(
125 "Timeout while transferring data. Possible causes:\n"
126 " - Communication errors due to noise on SPI/SDIO lines\n"
127 " - SPI/SDIO clock rate too low\n"
128 " - SPI/SDIO data timeout to aggressive\n\n");
129 return TEST_FAILED;
130
132 TEST_LOG_APPEND(
133 "Data underflow. Possible causes:\n"
134 " - DMA incorrectly configured\n"
135 " - Data being fed into the SPI/SDIO controller FIFO too slowly\n\n");
136 return TEST_FAILED;
137
139 TEST_LOG_APPEND(
140 "Data overrun. Possible causes:\n"
141 " - DMA incorrectly configured\n"
142 " - Data being read from the SPI/SDIO controller FIFO too slowly\n\n");
143 return TEST_FAILED;
144
145 default:
146 TEST_LOG_APPEND(
147 "An unspecified error occurred. This may be due to communications or other isuses.\n"
148 "Possible causes may include:\n"
149 " - SDIO/SPI controller not configured correctly\n"
150 " - Communication errors due to noise on SPI/SDIO lines\n\n");
151 return TEST_FAILED;
152 }
153}
154
155TEST_STEP(test_step_read_chip_id, "Read chip id from the MM chip")
156{
157 /*
158 * This step is deceptively complicated. Reading the chip id using the SDIO over SPI protocol
159 * requires a decent amount of setup. At a high-level the following gets executed:
160 *
161 * 1. The upper 16bits of the address is set into keyhole registers (relevant for CMD52/53)
162 * - This requires three (3) CMD52 writes to be achieved
163 * 2. We execute a CMD53 read
164 * - We first write a CMD53 to the chip indicating that we want to read data and how much.
165 * - We then read out the amount of data requested plus a CRC which is used to validate
166 * the data integrity.
167 *
168 * This is glossing over the details but what we want to convey here is that sdio_spi_read_le32
169 * is not just a read but a series of reads and writes.
170 */
171
172 int ret = MMHAL_SDIO_OTHER_ERROR;
173 uint32_t data;
174 int ii;
175 size_t chip_cfg_idx;
176
177 for (chip_cfg_idx = 0; chip_cfg_idx < n_chip_cfgs; chip_cfg_idx++)
178 {
179 /* MM chip requires few bytes to be written after CMD63 to get it to active state. We just
180 * attempt to read the chip id a few times. */
181 for (ii = 0; ii < 3; ii++)
182 {
183 ret = sdio_spi_read_le32(chip_cfgs[chip_cfg_idx].reg_chip_id, &data);
184 if (ret == 0)
185 {
186 break;
187 }
188 }
189
190 if (ret == 0)
191 {
192 if (validate_chip_id(data, &chip_cfgs[chip_cfg_idx]))
193 {
194 probed_chip_cfg = &chip_cfgs[chip_cfg_idx];
195 return TEST_PASSED;
196 }
198 }
199 else
200 {
201 break;
202 }
203 }
204
205 switch (ret)
206 {
208 /* We shouldn't get this error code in this test, since it should have caused the
209 * previous test to fail. */
210 TEST_LOG_APPEND(
211 "Failed to read chip id due to a command timeout. Possible causes:\n"
212 " - SPI/SDIO controller not configured correctly\n"
213 " - SPI/SDIO pins not set to correct function (e.g., output instead of alternative)\n"
214 " - SPI Chip Select not being asserted (should be low during the transfer)\n"
215 " - MM chip not powered on\n\n");
216 break;
217
219 TEST_LOG_APPEND("Failed to validate CRC for recieved data. Possible causes:\n"
220 " - Error in reading data from SPI/SDIO peripheral\n"
221 " - Possible noise on the SPI/SDIO lines causing corruption\n\n");
222 break;
223
225 TEST_LOG_APPEND("Failed to match a valid chip ID\n");
226 break;
227
228 default:
229 TEST_LOG_APPEND("Failed to read chip ID due to an unknown error\n\n");
230 break;
231 }
232
233 return TEST_FAILED;
234}
235
247bool process_sdio_spi_multi_byte_return(int ret, char *log_buf, size_t log_buf_len)
248{
249 switch (ret)
250 {
251 case 0:
252 return true;
253 break;
254
256 /* We shouldn't get this error code in this test, since it should have caused the
257 * previous test to fail. */
258 TEST_LOG_APPEND(
259 "Failed to read chip ID due to the timeout. Possible causes:\n"
260 " - SPI/SDIO controller not configured correctly\n"
261 " - SPI/SDIO pins not set to correct function (e.g., output instead of alternative)\n"
262 " - SPI Chip Select not being asserted (should be low during the transfer)\n"
263 " - MM chip not powered on\n\n");
264 break;
265
267 TEST_LOG_APPEND("Failed to validate CRC for recieved data. Possible causes:\n"
268 " - Error in reading data from SPI peripheral\n"
269 " - Possible noise on the SPI lines causing corruption\n\n");
270 break;
271
273 /* We should not reach this. */
274 TEST_LOG_APPEND("Invalid input was given to sdio_spi_read_le32().\n"
275 "Likely a NULL pointer for the data variable\n\n");
276 break;
277
278 default:
279 TEST_LOG_APPEND("Failed multi byte operation due to an unknown error\n\n");
280 }
281
282 return TEST_FAILED;
283}
284
291void populate_buffer(uint8_t *data, uint32_t length)
292{
293 uint32_t cnt;
294 for (cnt = 0; cnt < length; cnt++)
295 {
296 data[cnt] = cnt;
297 }
298}
299
309bool valid_buffer(uint8_t *data, uint32_t length, uint32_t offset)
310{
311 uint8_t value = offset;
312 uint32_t cnt;
313 for (cnt = 0; cnt < length; cnt++)
314 {
315 if (data[cnt] != value++)
316 {
317 printf("\nInvalid data at %lu (offset=%lu, expect %02x got %02x)\n",
318 cnt, offset, value, data[cnt]);
319 return false;
320 }
321 }
322 return true;
323}
324
325TEST_STEP(test_step_bulk_write_read, "Bulk write/read into the MM chip")
326{
327 int ret = MMHAL_SDIO_OTHER_ERROR;
328 bool ok;
329 enum test_result result = TEST_PASSED;
330
331 uint8_t *tx_data = (uint8_t *)mmosal_malloc(BULK_RW_PACKET_LEN_BYTES);
332 uint8_t *rx_data = (uint8_t *)mmosal_malloc(BULK_RW_PACKET_LEN_BYTES);
333 if ((tx_data == NULL) || (rx_data == NULL))
334 {
335 TEST_LOG_APPEND("Failed to allocate write/read buffers. Is there enough heap allocated?");
336 result = TEST_FAILED;
337 goto exit;
338 }
339
340 populate_buffer(tx_data, BULK_RW_PACKET_LEN_BYTES);
341
342 ret = sdio_spi_write_multi_byte(BENCHMARK_ADDR_START, tx_data, BULK_RW_PACKET_LEN_BYTES);
343 ok = process_sdio_spi_multi_byte_return(ret, log_buf, log_buf_len);
344 if (!ok)
345 {
346 TEST_LOG_APPEND("Failure during sdio_spi_write_multi_byte\n");
347 result = TEST_FAILED;
348 goto exit;
349 }
350
351 ret = sdio_spi_read_multi_byte(BENCHMARK_ADDR_START, rx_data, BULK_RW_PACKET_LEN_BYTES);
352 ok = process_sdio_spi_multi_byte_return(ret, log_buf, log_buf_len);
353 if (!ok)
354 {
355 TEST_LOG_APPEND("Failure during sdio_spi_read_multi_byte\n");
356 result = TEST_FAILED;
357 goto exit;
358 }
359
360 if (!valid_buffer(rx_data, BULK_RW_PACKET_LEN_BYTES, 0))
361 {
362 TEST_LOG_APPEND("Data read from the MM chip does not match the data written.\n");
363 result = TEST_FAILED;
364 goto exit;
365 }
366
367exit:
368 if (tx_data != NULL)
369 {
370 mmosal_free(tx_data);
371 }
372
373 if (rx_data != NULL)
374 {
375 mmosal_free(rx_data);
376 }
377
378 return result;
379}
380
381
382TEST_STEP(test_step_raw_tput, "Raw throughput test")
383{
384 /*
385 * Please note that this test is intended to give some indication of the raw throughput that can
386 * be achieved when transferring data across the bus to/from the MM chip. It serves more as an
387 * upper limit for the WLAN throughput that can be achieved. The actual throughput that can be
388 * achieved when transmitting will be lower than this. This is because there are additional
389 * overheads that are not captured as part of this test.
390 */
391 int ret = MMHAL_SDIO_OTHER_ERROR;
392 bool ok;
393 enum test_result result = TEST_PASSED;
394 unsigned offset = 0;
395 uint32_t start_time;
396 uint32_t end_time;
397 uint32_t time_taken_ms;
398 uint32_t benchmark_end_time;
399 uint32_t transaction_count = 0;
400
401 uint8_t *tx_data = (uint8_t *)mmosal_malloc(BULK_RW_PACKET_LEN_BYTES + 16);
402 uint8_t *rx_data = (uint8_t *)mmosal_malloc(BULK_RW_PACKET_LEN_BYTES);
403 if ((tx_data == NULL) || (rx_data == NULL))
404 {
405 TEST_LOG_APPEND("Failed to allocate write/read buffers. Is there enough heap allocated?");
406 result = TEST_FAILED;
407 goto exit;
408 }
409
410 populate_buffer(tx_data, BULK_RW_PACKET_LEN_BYTES + 16);
411
412 start_time = mmosal_get_time_ms();
413 benchmark_end_time = start_time + BENCHMARK_WAIT_MS;
414 while (mmosal_time_le(mmosal_get_time_ms(), benchmark_end_time))
415 {
416 offset += 4;
417 ret = sdio_spi_write_multi_byte(BENCHMARK_ADDR_START, tx_data + (offset & 15),
418 BULK_RW_PACKET_LEN_BYTES);
419 ok = process_sdio_spi_multi_byte_return(ret, log_buf, log_buf_len);
420 if (!ok)
421 {
422 TEST_LOG_APPEND("Failure during sdio_spi_write_multi_byte\n");
423 result = TEST_FAILED;
424 goto exit;
425 }
426
427 ret = sdio_spi_read_multi_byte(BENCHMARK_ADDR_START, rx_data,
428 BULK_RW_PACKET_LEN_BYTES);
429 ok = process_sdio_spi_multi_byte_return(ret, log_buf, log_buf_len);
430 if (!ok)
431 {
432 TEST_LOG_APPEND("Failure during sdio_spi_read_multi_byte\n");
433 result = TEST_FAILED;
434 goto exit;
435 }
436
437 transaction_count++;
438 }
439 end_time = mmosal_get_time_ms();
440
441 /* We are only validating the contents of the buffer once because there are already checks in
442 * place at the transport layer to validate the contents. This is in the form of CRCs. We just
443 * perform this check for sanity's sake. */
444 if (!valid_buffer(rx_data, BULK_RW_PACKET_LEN_BYTES, offset & 15))
445 {
446 TEST_LOG_APPEND("Data read from the MM chip does not match the data written.\n");
447 result = TEST_FAILED;
448 goto exit;
449 }
450
451 time_taken_ms = end_time - start_time;
452 TEST_LOG_APPEND("Note: This will not be the final WLAN TPUT. See test step for more info.\n");
453 TEST_LOG_APPEND("\tTime spent (ms): %lu\n", time_taken_ms);
454 TEST_LOG_APPEND("\tTransaction count: %lu\n", transaction_count);
455 TEST_LOG_APPEND("\tBytes xferred: %lu\n", transaction_count * 2 * BULK_RW_PACKET_LEN_BYTES);
456 TEST_LOG_APPEND("\tRaw TPUT (kbit/s): %lu\n\n",
457 (transaction_count * 2 * BULK_RW_PACKET_LEN_BYTES * 8) / time_taken_ms);
458
459exit:
460 if (tx_data != NULL)
461 {
462 mmosal_free(tx_data);
463 }
464
465 if (rx_data != NULL)
466 {
467 mmosal_free(rx_data);
468 }
469
470 return result;
471}
472
473TEST_STEP(test_step_verify_busy_pin, "Verify BUSY pin")
474{
475 /* In this We toggle the BUSY pin on the chip and expect that we can see the GPIO input
476 * on the host change and that the busy irq handler gets called. */
477 enum test_result result = TEST_PASSED;
478 uint8_t i = 0;
479 probed_chip_cfg->gpio_set_oe(probed_chip_cfg->busy_gpio_num, true);
480 probed_chip_cfg->gpio_set_value(probed_chip_cfg->busy_gpio_num, false);
481 mmhal_wlan_register_busy_irq_handler(test_hal_irq_handle);
482
484 /* Clear counter after irq_enabled to ignore potential stale interrupt. */
486 irq_counter = 0;
487 /* First toggle pin with IRQ enabled to verify the input value and irq handle call. */
488 for (i = 0; i < 2; i++)
489 {
490 probed_chip_cfg->gpio_set_value(probed_chip_cfg->busy_gpio_num, true);
493 {
494 TEST_LOG_APPEND("BUSY pin set HIGH but mmhal_wlan_busy_is_asserted() returned false "
495 "(expected true)\n\n");
496 result = TEST_FAILED;
497 goto exit;
498 }
499 probed_chip_cfg->gpio_set_value(probed_chip_cfg->busy_gpio_num, false);
502 {
503 TEST_LOG_APPEND("BUSY pin is set LOW but mmhal_wlan_busy_is_asserted() returned true "
504 "(expected false)\n\n");
505 result = TEST_FAILED;
506 goto exit;
507 }
508 }
509 if (irq_counter != 2)
510 {
511 TEST_LOG_APPEND("BUSY pin IRQ hander was not called as expected. Expected 2 invocations, "
512 "but was invoked %u times\n\n",
513 irq_counter);
514 result = TEST_FAILED;
515 goto exit;
516 }
517 /* Now toggle the pin with IRQ disabled and make sure handler isn't called. */
519 for (i = 0; i < 2; i++)
520 {
521 probed_chip_cfg->gpio_set_value(probed_chip_cfg->busy_gpio_num, true);
523 probed_chip_cfg->gpio_set_value(probed_chip_cfg->busy_gpio_num, false);
525 }
526 if (irq_counter > 2)
527 {
528 TEST_LOG_APPEND("Busy IRQ is still enabled.\n\n");
529 result = TEST_FAILED;
530 goto exit;
531 }
532
533exit:
536 irq_counter = 0;
537 return result;
538}
int mmhal_wlan_sdio_startup(void)
Perform transport specific startup.
@ MMHAL_SDIO_INVALID_ARGUMENT
Invalid argument given (e.g., incorrect buffer alignment).
Definition: mmhal_wlan.h:490
@ MMHAL_SDIO_HW_ERROR
Local hardware error (e.g., issue with SDIO controller).
Definition: mmhal_wlan.h:492
@ MMHAL_SDIO_CMD_CRC_ERROR
CRC error executing SDIO command.
Definition: mmhal_wlan.h:496
@ MMHAL_SDIO_DATA_OVERRUN
Overflow reading from SDIO controller FIFO.
Definition: mmhal_wlan.h:504
@ MMHAL_SDIO_DATA_UNDERFLOW
Underflow filling SDIO controller FIFO.
Definition: mmhal_wlan.h:502
@ MMHAL_SDIO_OTHER_ERROR
Another error not covered by the above error codes.
Definition: mmhal_wlan.h:506
@ MMHAL_SDIO_DATA_TIMEOUT
Timeout transferring data.
Definition: mmhal_wlan.h:498
@ MMHAL_SDIO_CMD_TIMEOUT
Timeout executing SDIO command.
Definition: mmhal_wlan.h:494
void mmhal_wlan_hard_reset(void)
Hard reset the chip by asserting and then releasing the reset pin.
void mmhal_wlan_register_busy_irq_handler(mmhal_irq_handler_t handler)
Register a handler for busy interrupts.
void mmhal_wlan_set_busy_irq_enabled(bool enabled)
Sets whether the busy interrupt is enabled.
bool mmhal_wlan_busy_is_asserted(void)
Tests whether the busy pin is currently asserted.
void mmhal_wlan_init(void)
Initialize the WLAN HAL.
#define mmosal_malloc(size)
Allocate memory of the given size and return a pointer to it (malloc).
Definition: mmosal.h:137
void mmosal_free(void *p)
Free the given memory allocation.
void mmosal_task_sleep(uint32_t duration_ms)
Sleep for a period of time, yielding during that time.
uint32_t mmosal_get_time_ms(void)
Get the system time in milliseconds.
static bool mmosal_time_le(uint32_t a, uint32_t b)
Check if time a is less than or equal to time b, taking into account wrapping.
Definition: mmosal.h:685
#define MM_UNUSED(_x)
Casts the given expression to void to avoid "unused" warnings from the compiler.
Definition: mmutils.h:69
const struct test_step test_step_read_chip_id
Test definition.
const struct test_step test_step_verify_busy_pin
Test definition.
const struct test_step test_step_mmhal_wlan_sdio_startup
Test definition.
const struct test_step test_step_raw_tput
Test definition.
const struct test_step test_step_bulk_write_read
Test definition.
const struct test_step test_step_mmhal_wlan_hard_reset
Test definition.
const struct test_step test_step_mmhal_wlan_init
Test definition.
Chip configuration data structure.
Definition: chip_cfg.h:16
const uint32_t * valid_chip_ids
List of valid chip IDs for this configuration.
Definition: chip_cfg.h:28
int(* gpio_set_value)(uint8_t gpio_num, bool value)
Function to set GPIO value.
Definition: chip_cfg.h:24
int(* gpio_set_oe)(uint8_t gpio_num, bool oe)
Function to set GPIO output enable.
Definition: chip_cfg.h:22
size_t n_valid_chip_ids
Number of valid chip IDs in valid_chip_ids.
Definition: chip_cfg.h:30
uint8_t busy_gpio_num
Busy GPIO number.
Definition: chip_cfg.h:26
uint32_t reg_chip_id
Address of the chip ID register.
Definition: chip_cfg.h:20