Morse Micro IoT SDK  2.10.4
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 "mmlog.h"
10#include "mmutils.h"
11#include "chip_cfg.h"
12#include "mmhal_wlan.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
25const struct chip_cfg *probed_chip_cfg;
26
34static bool validate_chip_id(uint32_t chip_id, const struct chip_cfg *chip_cfg)
35{
36 for (size_t ii = 0; ii < chip_cfg->n_valid_chip_ids; ii++)
37 {
38 if (chip_id == chip_cfg->valid_chip_ids[ii])
39 {
40 return true;
41 }
42 }
43
44 return false;
45}
46
48static volatile uint8_t irq_counter = 0;
49
54static void test_hal_irq_handle(void)
55{
56 irq_counter++;
57}
58
59TEST_STEP(test_step_mmhal_wlan_init, "WLAN HAL initialisation")
60{
61 MM_UNUSED(log_buf);
62 MM_UNUSED(log_buf_len);
63
65
66 /* We don't get indication of success or failure from mmhal_wlan_init() so we return
67 * "no result". */
68 return TEST_NO_RESULT;
69}
70
71TEST_STEP(test_step_mmhal_wlan_hard_reset, "Hard reset device")
72{
73 MM_UNUSED(log_buf);
74 MM_UNUSED(log_buf_len);
75
77
78 /*
79 * Since mmhal_wlan_hard_reset() does not return a status code we cannot verify here whether
80 * the MM chip was reset successfully at this stage, so we return "no result". To validate
81 * reset behavior an external logic analyzer can be used to probe the reset line.
82 */
83 return TEST_NO_RESULT;
84}
85
86TEST_STEP(test_step_mmhal_wlan_sdio_startup, "SDIO/SPI Startup")
87{
88 int ret;
89
90 MM_UNUSED(log_buf);
91 MM_UNUSED(log_buf_len);
92
94
95 switch (ret)
96 {
97 case 0:
98 return TEST_PASSED;
99
101 TEST_LOG_APPEND("SDIO/SPI controller hardware error. Possible causes:\n"
102 " - SDIO/SPI controller not configured correctly\n"
103 " - SDIO/SPI controller clock not enabled\n\n");
104 return TEST_FAILED;
105
107 TEST_LOG_APPEND("Timeout while executing an SDIO command. Possible causes:\n"
108 " - SDIO/SPI pins not set to correct function (e.g., output instead of "
109 "alternative)\n"
110 " - SPI chip select not being asserted\n"
111 " - MM chip not powered on\n\n");
112 return TEST_FAILED;
113
115 TEST_LOG_APPEND("CRC error in command or response while executing an SDIO command. "
116 "Possible causes:\n"
117 " - Noise on SPI/SDIO lines\n"
118 " - Wrong SPI device selected\n\n");
119 return TEST_FAILED;
120
122 TEST_LOG_APPEND("Timeout while transferring data. Possible causes:\n"
123 " - Communication errors due to noise on SPI/SDIO lines\n"
124 " - SPI/SDIO clock rate too low\n"
125 " - SPI/SDIO data timeout to aggressive\n\n");
126 return TEST_FAILED;
127
129 TEST_LOG_APPEND("Data underflow. Possible causes:\n"
130 " - DMA incorrectly configured\n"
131 " - Data being fed into the SPI/SDIO controller FIFO too slowly\n\n");
132 return TEST_FAILED;
133
135 TEST_LOG_APPEND("Data overrun. Possible causes:\n"
136 " - DMA incorrectly configured\n"
137 " - Data being read from the SPI/SDIO controller FIFO too slowly\n\n");
138 return TEST_FAILED;
139
140 default:
141 TEST_LOG_APPEND("An unspecified error occurred. This may be due to communications or "
142 "other isuses.\n"
143 "Possible causes may include:\n"
144 " - SDIO/SPI controller not configured correctly\n"
145 " - Communication errors due to noise on SPI/SDIO lines\n\n");
146 return TEST_FAILED;
147 }
148}
149
150TEST_STEP(test_step_read_chip_id, "Read chip id from the MM chip")
151{
152 /*
153 * This step is deceptively complicated. Reading the chip id using the SDIO over SPI protocol
154 * requires a decent amount of setup. At a high-level the following gets executed:
155 *
156 * 1. The upper 16bits of the address is set into keyhole registers (relevant for CMD52/53)
157 * - This requires three (3) CMD52 writes to be achieved
158 * 2. We execute a CMD53 read
159 * - We first write a CMD53 to the chip indicating that we want to read data and how much.
160 * - We then read out the amount of data requested plus a CRC which is used to validate
161 * the data integrity.
162 *
163 * This is glossing over the details but what we want to convey here is that sdio_spi_read_le32
164 * is not just a read but a series of reads and writes.
165 */
166
167 int ret = MMHAL_SDIO_OTHER_ERROR;
168 uint32_t data;
169 int ii;
170 size_t chip_cfg_idx;
171
172 for (chip_cfg_idx = 0; chip_cfg_idx < n_chip_cfgs; chip_cfg_idx++)
173 {
174 /* MM chip requires few bytes to be written after CMD63 to get it to active state. We just
175 * attempt to read the chip id a few times. */
176 for (ii = 0; ii < 3; ii++)
177 {
178 ret = sdio_spi_read_le32(chip_cfgs[chip_cfg_idx].reg_chip_id, &data);
179 if (ret == 0)
180 {
181 break;
182 }
183 }
184
185 if (ret == 0)
186 {
187 if (validate_chip_id(data, &chip_cfgs[chip_cfg_idx]))
188 {
189 probed_chip_cfg = &chip_cfgs[chip_cfg_idx];
190 TEST_LOG_APPEND("\tChip ID: 0x%04lx\n\n", data);
191 return TEST_PASSED;
192 }
194 }
195 else
196 {
197 break;
198 }
199 }
200
201 switch (ret)
202 {
204 /* We shouldn't get this error code in this test, since it should have caused the
205 * previous test to fail. */
206 TEST_LOG_APPEND(
207 "Failed to read chip id due to a command timeout. Possible causes:\n"
208 " - SPI/SDIO controller not configured correctly\n"
209 " - SPI/SDIO pins not set to correct function (e.g., output instead of "
210 "alternative)\n"
211 " - SPI Chip Select not being asserted (should be low during the transfer)\n"
212 " - MM chip not powered on\n\n");
213 break;
214
216 TEST_LOG_APPEND("Failed to validate CRC for recieved data. Possible causes:\n"
217 " - Error in reading data from SPI/SDIO peripheral\n"
218 " - Possible noise on the SPI/SDIO lines causing corruption\n\n");
219 break;
220
222 TEST_LOG_APPEND("Failed to match a valid chip ID\n");
223 break;
224
225 default:
226 TEST_LOG_APPEND("Failed to read chip ID due to an unknown error\n\n");
227 break;
228 }
229
230 return TEST_FAILED;
231}
232
244bool process_sdio_spi_multi_byte_return(int ret, char *log_buf, size_t log_buf_len)
245{
246 switch (ret)
247 {
248 case 0:
249 return true;
250 break;
251
253 /* We shouldn't get this error code in this test, since it should have caused the
254 * previous test to fail. */
255 TEST_LOG_APPEND(
256 "Failed to read chip ID due to the timeout. Possible causes:\n"
257 " - SPI/SDIO controller not configured correctly\n"
258 " - SPI/SDIO pins not set to correct function (e.g., output instead of "
259 "alternative)\n"
260 " - SPI Chip Select not being asserted (should be low during the transfer)\n"
261 " - MM chip not powered on\n\n");
262 break;
263
265 TEST_LOG_APPEND("Failed to validate CRC for recieved data. Possible causes:\n"
266 " - Error in reading data from SPI peripheral\n"
267 " - Possible noise on the SPI lines causing corruption\n\n");
268 break;
269
271 /* We should not reach this. */
272 TEST_LOG_APPEND("Invalid input was given to sdio_spi_read_le32().\n"
273 "Likely a NULL pointer for the data variable\n\n");
274 break;
275
276 default:
277 TEST_LOG_APPEND("Failed multi byte operation due to an unknown error\n\n");
278 }
279
280 return TEST_FAILED;
281}
282
289void populate_buffer(uint8_t *data, uint32_t length)
290{
291 uint32_t cnt;
292 for (cnt = 0; cnt < length; cnt++)
293 {
294 data[cnt] = cnt;
295 }
296}
297
307bool valid_buffer(uint8_t *data, uint32_t length, uint32_t offset)
308{
309 uint8_t value = offset;
310 uint32_t cnt;
311 for (cnt = 0; cnt < length; cnt++)
312 {
313 if (data[cnt] != value++)
314 {
315 printf("\nInvalid data at %lu (offset=%lu, expect %02x got %02x)\n",
316 cnt,
317 offset,
318 value,
319 data[cnt]);
320 return false;
321 }
322 }
323 return true;
324}
325
326TEST_STEP(test_step_bulk_write_read, "Bulk write/read into the MM chip")
327{
328 int ret = MMHAL_SDIO_OTHER_ERROR;
329 bool ok;
330 enum test_result result = TEST_PASSED;
331
332 uint8_t *tx_data = (uint8_t *)mmosal_malloc(BULK_RW_PACKET_LEN_BYTES);
333 uint8_t *rx_data = (uint8_t *)mmosal_malloc(BULK_RW_PACKET_LEN_BYTES);
334 if ((tx_data == NULL) || (rx_data == NULL))
335 {
336 TEST_LOG_APPEND("Failed to allocate write/read buffers. Is there enough heap allocated?");
337 result = TEST_FAILED;
338 goto exit;
339 }
340
341 populate_buffer(tx_data, BULK_RW_PACKET_LEN_BYTES);
342
343 ret = sdio_spi_write_multi_byte(BENCHMARK_ADDR_START, tx_data, BULK_RW_PACKET_LEN_BYTES);
344 ok = process_sdio_spi_multi_byte_return(ret, log_buf, log_buf_len);
345 if (!ok)
346 {
347 TEST_LOG_APPEND("Failure during sdio_spi_write_multi_byte\n");
348 result = TEST_FAILED;
349 goto exit;
350 }
351
352 ret = sdio_spi_read_multi_byte(BENCHMARK_ADDR_START, rx_data, BULK_RW_PACKET_LEN_BYTES);
353 ok = process_sdio_spi_multi_byte_return(ret, log_buf, log_buf_len);
354 if (!ok)
355 {
356 TEST_LOG_APPEND("Failure during sdio_spi_read_multi_byte\n");
357 result = TEST_FAILED;
358 goto exit;
359 }
360
361 if (!valid_buffer(rx_data, BULK_RW_PACKET_LEN_BYTES, 0))
362 {
363 TEST_LOG_APPEND("Data read from the MM chip does not match the data written.\n");
364 result = TEST_FAILED;
365 goto exit;
366 }
367
368exit:
369 if (tx_data != NULL)
370 {
371 mmosal_free(tx_data);
372 }
373
374 if (rx_data != NULL)
375 {
376 mmosal_free(rx_data);
377 }
378
379 return result;
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,
418 tx_data + (offset & 15),
419 BULK_RW_PACKET_LEN_BYTES);
420 ok = process_sdio_spi_multi_byte_return(ret, log_buf, log_buf_len);
421 if (!ok)
422 {
423 TEST_LOG_APPEND("Failure during sdio_spi_write_multi_byte\n");
424 result = TEST_FAILED;
425 goto exit;
426 }
427
428 ret = sdio_spi_read_multi_byte(BENCHMARK_ADDR_START, rx_data, 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:510
@ MMHAL_SDIO_HW_ERROR
Local hardware error (e.g., issue with SDIO controller).
Definition: mmhal_wlan.h:512
@ MMHAL_SDIO_CMD_CRC_ERROR
CRC error executing SDIO command.
Definition: mmhal_wlan.h:516
@ MMHAL_SDIO_DATA_OVERRUN
Overflow reading from SDIO controller FIFO.
Definition: mmhal_wlan.h:524
@ MMHAL_SDIO_DATA_UNDERFLOW
Underflow filling SDIO controller FIFO.
Definition: mmhal_wlan.h:522
@ MMHAL_SDIO_OTHER_ERROR
Another error not covered by the above error codes.
Definition: mmhal_wlan.h:526
@ MMHAL_SDIO_DATA_TIMEOUT
Timeout transferring data.
Definition: mmhal_wlan.h:518
@ MMHAL_SDIO_CMD_TIMEOUT
Timeout executing SDIO command.
Definition: mmhal_wlan.h:514
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:138
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:687
#define MM_UNUSED(_x)
Casts the given expression to void to avoid "unused" warnings from the compiler.
Definition: mmutils.h:70
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:15
const uint32_t * valid_chip_ids
List of valid chip IDs for this configuration.
Definition: chip_cfg.h:27
int(* gpio_set_value)(uint8_t gpio_num, bool value)
Function to set GPIO value.
Definition: chip_cfg.h:23
int(* gpio_set_oe)(uint8_t gpio_num, bool oe)
Function to set GPIO output enable.
Definition: chip_cfg.h:21
size_t n_valid_chip_ids
Number of valid chip IDs in valid_chip_ids.
Definition: chip_cfg.h:29
uint8_t busy_gpio_num
Busy GPIO number.
Definition: chip_cfg.h:25
uint32_t reg_chip_id
Address of the chip ID register.
Definition: chip_cfg.h:19