Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make DMA SPI driver aware of CPU cache, fix data corruption and other SPI issues on STM32H7 #199

Merged
merged 8 commits into from
Dec 19, 2023

Conversation

multiplemonomials
Copy link
Collaborator

@multiplemonomials multiplemonomials commented Dec 4, 2023

Summary of changes

I started this effort to track down some DMA SPI issues that were showing up in the test cases for Nucleo H743ZI. I thought there might just be some random bugs in the HAL driver, and I did in fact find two such issues. However, I also found a more serious problem: the DMA SPI code did not correctly manage the cache for Cortex-M7 devices which have a CPU cache.

For the initial DMA SPI implementation, I thought it would be enough to just invalidate the cache for the Rx buffer after the transfer is over. And this almost works -- it does ensure that the correct Rx data gets read out at the end of the transfer. However, if there are any other variables being stored in the same cache lines as the Rx data, these can actually be corrupted, because the variable could be up to date in the CPU cache but out of date in main memory, in which case the cache invalidation throws out the correct value. I actually found the issue because of a test case which declared an Rx buffer on the stack and was seeing stack corruption (!).

At its core, the issue is kinda a design problem. Mbed acts like you can just do a DMA SPI operation into any old memory buffer, but you really can't. The destination buffer has to be cache aligned in order for things to work, because we need to be able to safely cache invalidate the buffer.

So, I had to fix the design problem with an API change. I added a new CacheAlignedBuffer<DataT, BufferSize> template, and changed the SPI code to require that this structure be used when declaring rx buffers for asynchronous operations. On devices without a CPU cache (according to CMSIS definitions), the CacheAlignedBuffer template works basically like std::array, and just allocates an array of the specified type. On devices with a CPU cache, it allocates additional space (on the order of 64 bytes) to make sure that the buffer can be cache aligned. I thought about declaring CacheAlignedBuffer with alignas(32) instead of doing the alignment "manually", but believe that needs linker script support to work, and also I don't know if it would work with malloc() allocated structures.

I also had to make one more change, this time to the HAL API layer. So that we know if we need to invalidate the cache, we need some way to know if DMA actually got used for a transfer. The "DMA hint" is just that -- a hint. The HAL layer is free to always use interrupts or always use DMA if that's all that's supported. So, I updated the signature of spi_master_transfer() so that the HAL layer must return true if DMA actually got used for a transfer, and false otherwise. I then went through every HAL implementation which supports async SPI (8 of them, not counting copy pasted files) and had it return the right value based on what it supports.

With these pieces in place, I could finally implement cache management in the SPI driver. It follows the three step process outlined here, and does:

  • Clean of the Tx buffer before the transfer
  • Invalidate of the Rx buffer before the transfer
  • Invalidate of the Rx buffer after the transfer
    I wanted to do this at the driver level, since the implementation is common to all ARM processors with a CPU cache, and also it's easy to screw up so I didn't want it to be up to the HAL vendors to get right. It does require some ifdefs and some new added variables for each SPI instance supporting async, but I think it's worth it so that we can get cache management right.

Finally, after all these fixes, my entire SPI test suite is passing on STM32H743!

Impact of changes

The API for SPI::transfer() and SPI::transfer_and_wait() is changing -- a CacheAlignedBuffer structure must be passed instead of a raw pointer for the Rx buffer. This is to support MCUs where the Rx buffer has to be cache aligned when receiving memory from a peripheral with DMA.

Due to the relative obscurity and poor documentation of these APIs until recently, I doubt that a large number of applications were using them. However, applications that were using them will need updates.

Migration actions required

CacheAlignedBuffer structures will need to be declared and used for the rx_buffers when calling SPI::transfer() and SPI::transfer_and_wait().

Documentation

Included in this PR!


Pull request type

[] Patch update (Bug fix / Target update / Docs update / Test update / Refactor)
[] Feature update (New feature / Functionality change / New API)
[X] Major update (Breaking change E.g. Return code change / API behaviour change)

Test results

[] No Tests required for this change (E.g docs only update)
[] Covered by existing mbed-os tests (Greentea or Unittest)
[X] Tests / results supplied as part of this PR
Show test log
6: [1701677033.63][CONN][RXD] >>> Running 28 test cases...
6: [1701677033.63][CONN][INF] found SYNC in stream: {{__sync;eb4bc5cb-595a-45f5-99b8-4fdec301a3c6}} it is #0 sent, queued...
6: [1701677033.63][CONN][INF] found KV pair in stream: {{__version;1.3.0}}, queued...
6: [1701677033.63][CONN][INF] found KV pair in stream: {{__timeout;45}}, queued...
6: [1701677033.63][CONN][INF] found KV pair in stream: {{__host_test_name;spi_basic_test}}, queued...
6: [1701677033.63][HTST][INF] sync KV found, uuid=eb4bc5cb-595a-45f5-99b8-4fdec301a3c6, timestamp=1701677033.630847
6: [1701677033.63][HTST][INF] DUT greentea-client version: 1.3.0
6: [1701677033.63][HTST][INF] setting timeout to: 45 sec
6: [1701677033.63][HTST][INF] host test class: '<class 'spi_basic_test.SpiBasicTestHostTest'>'
6: [1701677033.63][TEST][INF] SPI Basic Test host test setup complete.
6: [1701677033.63][HTST][INF] host test setup() call...
6: [1701677033.63][HTST][INF] CALLBACKs updated
6: [1701677033.63][HTST][INF] host test detected: spi_basic_test
6: [1701677033.64][CONN][INF] found KV pair in stream: {{__testcase_name;Send 8 Bit Data via Single Word API}}, queued...
6: [1701677033.64][CONN][INF] found KV pair in stream: {{__testcase_name;Send 16 Bit Data via Single Word API}}, queued...
6: [1701677033.65][CONN][INF] found KV pair in stream: {{__testcase_name;Send 32 Bit Data via Single Word API}}, queued...
6: [1701677033.65][CONN][INF] found KV pair in stream: {{__testcase_name;Send 8 Bit Data via Transactional API (Tx only)}}, queued...
6: [1701677033.66][CONN][INF] found KV pair in stream: {{__testcase_name;Send 16 Bit Data via Transactional API (Tx only)}}, queued...
6: [1701677033.66][CONN][INF] found KV pair in stream: {{__testcase_name;Send 32 Bit Data via Transactional API (Tx only)}}, queued...
6: [1701677033.68][CONN][INF] found KV pair in stream: {{__testcase_name;Read 8 Bit Data via Transactional API (Rx only)}}, queued...
6: [1701677033.68][CONN][INF] found KV pair in stream: {{__testcase_name;Read 16 Bit Data via Transactional API (Rx only)}}, queued...
6: [1701677033.69][CONN][INF] found KV pair in stream: {{__testcase_name;Read 32 Bit Data via Transactional API (Rx only)}}, queued...
6: [1701677033.69][CONN][INF] found KV pair in stream: {{__testcase_name;Transfer 8 Bit Data via Transactional API (Tx/Rx)}}, queued...
6: [1701677033.70][CONN][INF] found KV pair in stream: {{__testcase_name;Transfer 16 Bit Data via Transactional API (Tx/Rx)}}, queued...
6: [1701677033.71][CONN][INF] found KV pair in stream: {{__testcase_name;Transfer 32 Bit Data via Transactional API (Tx/Rx)}}, queued...
6: [1701677033.71][CONN][INF] found KV pair in stream: {{__testcase_name;Use Multiple SPI Instances (synchronous API)}}, queued...
6: [1701677033.72][CONN][INF] found KV pair in stream: {{__testcase_name;Free and Reallocate SPI Instance (synchronous API)}}, queued...
6: [1701677033.72][CONN][INF] found KV pair in stream: {{__testcase_name;Send Data via Async Interrupt API (Tx only)}}, queued...
6: [1701677033.73][CONN][INF] found KV pair in stream: {{__testcase_name;Send Data via Async Interrupt API (Rx only)}}, queued...
6: [1701677033.73][CONN][INF] found KV pair in stream: {{__testcase_name;Free and Reallocate SPI Instance with Interrupts}}, queued...
6: [1701677033.74][CONN][INF] found KV pair in stream: {{__testcase_name;Send Data via Async Interrupt API (Tx/Rx)}}, queued...
6: [1701677033.74][CONN][INF] found KV pair in stream: {{__testcase_name;Benchmark Async SPI via Interrupts}}, queued...
6: [1701677033.75][CONN][INF] found KV pair in stream: {{__testcase_name;Queueing and Aborting Async SPI via Interrupts}}, queued...
6: [1701677033.75][CONN][INF] found KV pair in stream: {{__testcase_name;Use Multiple SPI Instances with Interrupts}}, queued...
6: [1701677033.77][CONN][INF] found KV pair in stream: {{__testcase_name;Send Data via Async DMA API (Tx only)}}, queued...
6: [1701677033.77][CONN][INF] found KV pair in stream: {{__testcase_name;Send Data via Async DMA API (Rx only)}}, queued...
6: [1701677033.78][CONN][INF] found KV pair in stream: {{__testcase_name;Free and Reallocate SPI Instance with DMA}}, queued...
6: [1701677033.78][CONN][INF] found KV pair in stream: {{__testcase_name;Send Data via Async DMA API (Tx/Rx)}}, queued...
6: [1701677033.78][CONN][INF] found KV pair in stream: {{__testcase_name;Benchmark Async SPI via DMA}}, queued...
6: [1701677033.79][CONN][RXD] 
6: [1701677033.79][CONN][INF] found KV pair in stream: {{__testcase_name;Queueing and Aborting Async SPI via DMA}}, queued...
6: [1701677033.79][CONN][INF] found KV pair in stream: {{__testcase_name;Use Multiple SPI Instances with DMA}}, queued...
6: [1701677033.80][CONN][RXD] >>> Running case #1: 'Send 8 Bit Data via Single Word API'...
6: [1701677033.80][CONN][INF] found KV pair in stream: {{__testcase_start;Send 8 Bit Data via Single Word API}}, queued...
6: [1701677033.81][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677034.83][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677034.84][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677034.95][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677034.95][TEST][INF] PASS
6: [1701677034.95][SERI][TXD] {{verify_sequence;complete}}
6: [1701677034.96][CONN][INF] found KV pair in stream: {{__testcase_finish;Send 8 Bit Data via Single Word API;1;0}}, queued...
6: [1701677034.97][CONN][RXD] >>> 'Send 8 Bit Data via Single Word API': 1 passed, 0 failed
6: [1701677034.97][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677034.97][CONN][RXD]
6: [1701677034.98][CONN][RXD] >>> Running case #2: 'Send 16 Bit Data via Single Word API'...
6: [1701677034.98][CONN][INF] found KV pair in stream: {{__testcase_start;Send 16 Bit Data via Single Word API}}, queued...
6: [1701677034.98][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677035.99][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677036.00][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677036.11][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677036.11][TEST][INF] PASS
6: [1701677036.12][SERI][TXD] {{verify_sequence;complete}}
6: [1701677036.13][CONN][INF] found KV pair in stream: {{__testcase_finish;Send 16 Bit Data via Single Word API;1;0}}, queued...
6: [1701677036.14][CONN][RXD] >>> 'Send 16 Bit Data via Single Word API': 1 passed, 0 failed
6: [1701677036.14][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677036.14][CONN][RXD]
6: [1701677036.15][CONN][RXD] >>> Running case #3: 'Send 32 Bit Data via Single Word API'...
6: [1701677036.15][CONN][INF] found KV pair in stream: {{__testcase_start;Send 32 Bit Data via Single Word API}}, queued...
6: [1701677036.15][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677037.16][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677037.17][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677037.27][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677037.27][TEST][INF] PASS
6: [1701677037.28][SERI][TXD] {{verify_sequence;complete}}
6: [1701677037.29][CONN][INF] found KV pair in stream: {{__testcase_finish;Send 32 Bit Data via Single Word API;1;0}}, queued...
6: [1701677037.30][CONN][RXD] >>> 'Send 32 Bit Data via Single Word API': 1 passed, 0 failed
6: [1701677037.30][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677037.30][CONN][RXD]
6: [1701677037.31][CONN][RXD] >>> Running case #4: 'Send 8 Bit Data via Transactional API (Tx only)'...
6: [1701677037.31][CONN][INF] found KV pair in stream: {{__testcase_start;Send 8 Bit Data via Transactional API (Tx only)}}, queued...
6: [1701677037.31][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677038.32][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677038.33][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677038.43][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677038.43][TEST][INF] PASS
6: [1701677038.44][SERI][TXD] {{verify_sequence;complete}}
6: [1701677038.45][CONN][INF] found KV pair in stream: {{__testcase_finish;Send 8 Bit Data via Transactional API (Tx only);1;0}}, queued...
6: [1701677038.46][CONN][RXD] >>> 'Send 8 Bit Data via Transactional API (Tx only)': 1 passed, 0 failed
6: [1701677038.46][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677038.46][CONN][RXD]
6: [1701677038.47][CONN][RXD] >>> Running case #5: 'Send 16 Bit Data via Transactional API (Tx only)'...
6: [1701677038.47][CONN][INF] found KV pair in stream: {{__testcase_start;Send 16 Bit Data via Transactional API (Tx only)}}, queued...
6: [1701677038.48][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677039.49][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677039.50][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677039.59][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677039.59][TEST][INF] PASS
6: [1701677039.60][SERI][TXD] {{verify_sequence;complete}}
6: [1701677039.61][CONN][INF] found KV pair in stream: {{__testcase_finish;Send 16 Bit Data via Transactional API (Tx only);1;0}}, queued...
6: [1701677039.62][CONN][RXD] >>> 'Send 16 Bit Data via Transactional API (Tx only)': 1 passed, 0 failed
6: [1701677039.62][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677039.62][CONN][RXD]
6: [1701677039.63][CONN][RXD] >>> Running case #6: 'Send 32 Bit Data via Transactional API (Tx only)'...
6: [1701677039.63][CONN][INF] found KV pair in stream: {{__testcase_start;Send 32 Bit Data via Transactional API (Tx only)}}, queued...
6: [1701677039.64][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677040.65][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677040.66][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677040.76][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677040.76][TEST][INF] PASS
6: [1701677040.76][SERI][TXD] {{verify_sequence;complete}}
6: [1701677040.78][CONN][INF] found KV pair in stream: {{__testcase_finish;Send 32 Bit Data via Transactional API (Tx only);1;0}}, queued...
6: [1701677040.79][CONN][RXD] >>> 'Send 32 Bit Data via Transactional API (Tx only)': 1 passed, 0 failed
6: [1701677040.79][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677040.79][CONN][RXD]
6: [1701677040.80][CONN][RXD] >>> Running case #7: 'Read 8 Bit Data via Transactional API (Rx only)'...
6: [1701677040.80][CONN][INF] found KV pair in stream: {{__testcase_start;Read 8 Bit Data via Transactional API (Rx only)}}, queued...
6: [1701677040.81][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677041.82][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677041.83][CONN][INF] found KV pair in stream: {{print_spi_data;please}}, queued...
                                                                                                                                                         6: [1701677041.93][TEST][INF] Saw on the SPI bus: [mosi: b'afafafaf', miso: b'afafafaf']
6: [1701677041.93][SERI][TXD] {{print_spi_data;complete}}
6: [1701677041.95][CONN][INF] found KV pair in stream: {{__testcase_finish;Read 8 Bit Data via Transactional API (Rx only);1;0}}, queued...
6: [1701677041.96][CONN][RXD] >>> 'Read 8 Bit Data via Transactional API (Rx only)': 1 passed, 0 failed
6: [1701677041.96][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677041.96][CONN][RXD]
6: [1701677041.97][CONN][RXD] >>> Running case #8: 'Read 16 Bit Data via Transactional API (Rx only)'...
6: [1701677041.97][CONN][INF] found KV pair in stream: {{__testcase_start;Read 16 Bit Data via Transactional API (Rx only)}}, queued...
6: [1701677041.98][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677042.99][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677043.00][CONN][INF] found KV pair in stream: {{print_spi_data;please}}, queued...
                                                                                                                                                         6: [1701677043.10][TEST][INF] Saw on the SPI bus: [mosi: b'afafafaf', miso: b'afafafaf']
6: [1701677043.11][SERI][TXD] {{print_spi_data;complete}}
6: [1701677043.12][CONN][INF] found KV pair in stream: {{__testcase_finish;Read 16 Bit Data via Transactional API (Rx only);1;0}}, queued...
6: [1701677043.13][CONN][RXD] >>> 'Read 16 Bit Data via Transactional API (Rx only)': 1 passed, 0 failed
6: [1701677043.13][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677043.13][CONN][RXD]
6: [1701677043.14][CONN][RXD] >>> Running case #9: 'Read 32 Bit Data via Transactional API (Rx only)'...
6: [1701677043.14][CONN][INF] found KV pair in stream: {{__testcase_start;Read 32 Bit Data via Transactional API (Rx only)}}, queued...
6: [1701677043.15][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677044.16][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677044.17][CONN][INF] found KV pair in stream: {{print_spi_data;please}}, queued...
                                                                                                                                                         6: [1701677044.28][TEST][INF] Saw on the SPI bus: [mosi: b'afafafaf', miso: b'afafafaf']
6: [1701677044.28][SERI][TXD] {{print_spi_data;complete}}
6: [1701677044.29][CONN][INF] found KV pair in stream: {{__testcase_finish;Read 32 Bit Data via Transactional API (Rx only);1;0}}, queued...
6: [1701677044.30][CONN][RXD] >>> 'Read 32 Bit Data via Transactional API (Rx only)': 1 passed, 0 failed
6: [1701677044.30][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677044.30][CONN][RXD]
6: [1701677044.31][CONN][RXD] >>> Running case #10: 'Transfer 8 Bit Data via Transactional API (Tx/Rx)'...
6: [1701677044.31][CONN][INF] found KV pair in stream: {{__testcase_start;Transfer 8 Bit Data via Transactional API (Tx/Rx)}}, queued...
6: [1701677044.32][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677045.35][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677045.36][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677045.45][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677045.45][TEST][INF] PASS
6: [1701677045.45][SERI][TXD] {{verify_sequence;complete}}
6: [1701677045.46][CONN][INF] found KV pair in stream: {{__testcase_finish;Transfer 8 Bit Data via Transactional API (Tx/Rx);1;0}}, queued...
6: [1701677045.47][CONN][RXD] >>> 'Transfer 8 Bit Data via Transactional API (Tx/Rx)': 1 passed, 0 failed
6: [1701677045.47][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677045.47][CONN][RXD]
6: [1701677045.48][CONN][RXD] >>> Running case #11: 'Transfer 16 Bit Data via Transactional API (Tx/Rx)'...
6: [1701677045.48][CONN][INF] found KV pair in stream: {{__testcase_start;Transfer 16 Bit Data via Transactional API (Tx/Rx)}}, queued...
6: [1701677045.49][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677046.51][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677046.52][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677046.61][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677046.61][TEST][INF] PASS
6: [1701677046.62][SERI][TXD] {{verify_sequence;complete}}
6: [1701677046.63][CONN][INF] found KV pair in stream: {{__testcase_finish;Transfer 16 Bit Data via Transactional API (Tx/Rx);1;0}}, queued...
6: [1701677046.64][CONN][RXD] >>> 'Transfer 16 Bit Data via Transactional API (Tx/Rx)': 1 passed, 0 failed
6: [1701677046.64][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677046.64][CONN][RXD]
6: [1701677046.65][CONN][RXD] >>> Running case #12: 'Transfer 32 Bit Data via Transactional API (Tx/Rx)'...
6: [1701677046.67][CONN][INF] found KV pair in stream: {{__testcase_start;Transfer 32 Bit Data via Transactional API (Tx/Rx)}}, queued...
6: [1701677046.67][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677047.68][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677047.69][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677047.79][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677047.79][TEST][INF] PASS
6: [1701677047.80][SERI][TXD] {{verify_sequence;complete}}
6: [1701677047.81][CONN][INF] found KV pair in stream: {{__testcase_finish;Transfer 32 Bit Data via Transactional API (Tx/Rx);1;0}}, queued...
6: [1701677047.82][CONN][RXD] >>> 'Transfer 32 Bit Data via Transactional API (Tx/Rx)': 1 passed, 0 failed
6: [1701677047.82][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677047.82][CONN][RXD]
6: [1701677047.83][CONN][RXD] >>> Running case #13: 'Use Multiple SPI Instances (synchronous API)'...
6: [1701677047.83][CONN][INF] found KV pair in stream: {{__testcase_start;Use Multiple SPI Instances (synchronous API)}}, queued...
6: [1701677047.84][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677048.86][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677048.87][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677048.96][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677048.96][TEST][INF] PASS
6: [1701677048.97][SERI][TXD] {{verify_sequence;complete}}
6: [1701677048.98][CONN][INF] found KV pair in stream: {{__testcase_finish;Use Multiple SPI Instances (synchronous API);1;0}}, queued...
6: [1701677048.99][CONN][RXD] >>> 'Use Multiple SPI Instances (synchronous API)': 1 passed, 0 failed
6: [1701677048.99][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677048.99][CONN][RXD]
6: [1701677049.01][CONN][RXD] >>> Running case #14: 'Free and Reallocate SPI Instance (synchronous API)'...
6: [1701677049.01][CONN][INF] found KV pair in stream: {{__testcase_start;Free and Reallocate SPI Instance (synchronous API)}}, queued...
6: [1701677049.02][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677050.03][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677050.04][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677050.14][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677050.14][TEST][INF] PASS
6: [1701677050.15][SERI][TXD] {{verify_sequence;complete}}
6: [1701677050.16][CONN][INF] found KV pair in stream: {{__testcase_finish;Free and Reallocate SPI Instance (synchronous API);1;0}}, queued...
6: [1701677050.17][CONN][RXD] >>> 'Free and Reallocate SPI Instance (synchronous API)': 1 passed, 0 failed
6: [1701677050.17][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677050.17][CONN][RXD]
6: [1701677050.18][CONN][RXD] >>> Running case #15: 'Send Data via Async Interrupt API (Tx only)'...
6: [1701677050.18][CONN][INF] found KV pair in stream: {{__testcase_start;Send Data via Async Interrupt API (Tx only)}}, queued...
6: [1701677050.19][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677051.20][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677051.21][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677051.30][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677051.30][TEST][INF] PASS
6: [1701677051.30][SERI][TXD] {{verify_sequence;complete}}
6: [1701677051.31][CONN][INF] found KV pair in stream: {{__testcase_finish;Send Data via Async Interrupt API (Tx only);1;0}}, queued...
6: [1701677051.33][CONN][RXD] >>> 'Send Data via Async Interrupt API (Tx only)': 1 passed, 0 failed
6: [1701677051.33][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677051.33][CONN][RXD]
6: [1701677051.34][CONN][RXD] >>> Running case #16: 'Send Data via Async Interrupt API (Rx only)'...
6: [1701677051.34][CONN][INF] found KV pair in stream: {{__testcase_start;Send Data via Async Interrupt API (Rx only)}}, queued...
6: [1701677051.35][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677052.36][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677052.37][CONN][RXD] Got: ff ff ff ff
                                                                                                            6: [1701677052.37][CONN][INF] found KV pair in stream: {{print_spi_data;please}}, queued...
    6: [1701677052.47][TEST][INF] Saw on the SPI bus: [mosi: b'ffffffff', miso: b'ffffffff']
6: [1701677052.47][SERI][TXD] {{print_spi_data;complete}}
6: [1701677052.48][CONN][INF] found KV pair in stream: {{__testcase_finish;Send Data via Async Interrupt API (Rx only);1;0}}, queued...
6: [1701677052.49][CONN][RXD] >>> 'Send Data via Async Interrupt API (Rx only)': 1 passed, 0 failed
6: [1701677052.49][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677052.49][CONN][RXD]
6: [1701677052.51][CONN][RXD] >>> Running case #17: 'Free and Reallocate SPI Instance with Interrupts'...
6: [1701677052.51][CONN][INF] found KV pair in stream: {{__testcase_start;Free and Reallocate SPI Instance with Interrupts}}, queued...
6: [1701677052.52][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677053.53][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677053.54][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677053.65][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677053.65][TEST][INF] PASS
6: [1701677053.66][SERI][TXD] {{verify_sequence;complete}}
6: [1701677053.67][CONN][INF] found KV pair in stream: {{__testcase_finish;Free and Reallocate SPI Instance with Interrupts;1;0}}, queued...
6: [1701677053.68][CONN][RXD] >>> 'Free and Reallocate SPI Instance with Interrupts': 1 passed, 0 failed
6: [1701677053.68][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677053.68][CONN][RXD]
6: [1701677053.69][CONN][RXD] >>> Running case #18: 'Send Data via Async Interrupt API (Tx/Rx)'...
6: [1701677053.69][CONN][INF] found KV pair in stream: {{__testcase_start;Send Data via Async Interrupt API (Tx/Rx)}}, queued...
6: [1701677053.69][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677054.70][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677054.71][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677054.81][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677054.81][TEST][INF] PASS
6: [1701677054.81][SERI][TXD] {{verify_sequence;complete}}
6: [1701677054.82][CONN][INF] found KV pair in stream: {{__testcase_finish;Send Data via Async Interrupt API (Tx/Rx);1;0}}, queued...
6: [1701677054.83][CONN][RXD] >>> 'Send Data via Async Interrupt API (Tx/Rx)': 1 passed, 0 failed
6: [1701677054.83][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677054.83][CONN][RXD]
6: [1701677054.84][CONN][RXD] >>> Running case #19: 'Benchmark Async SPI via Interrupts'...
6: [1701677054.84][CONN][INF] found KV pair in stream: {{__testcase_start;Benchmark Async SPI via Interrupts}}, queued...
6: [1701677054.85][CONN][RXD] Transferred 32 bytes @ 1000kHz in 428us, with 420us occurring in the background.
6: [1701677054.87][CONN][RXD] Note: Based on the byte count and frequency, the theoretical best time for this SPI transaction is 256us
6: [1701677054.87][CONN][RXD] Note: the above background time does not include overhead from interrupts, which may be significant.
6: [1701677054.88][CONN][RXD] >>> 'Benchmark Async SPI via Interrupts': 1 passed, 0 failed
6: [1701677054.88][CONN][INF] found KV pair in stream: {{__testcase_finish;Benchmark Async SPI via Interrupts;1;0}}, queued...
6: [1701677054.89][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677054.89][CONN][RXD]
6: [1701677054.89][CONN][RXD] >>> Running case #20: 'Queueing and Aborting Async SPI via Interrupts'...
6: [1701677054.90][CONN][INF] found KV pair in stream: {{__testcase_start;Queueing and Aborting Async SPI via Interrupts}}, queued...
6: [1701677054.90][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677055.91][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677055.95][CONN][INF] found KV pair in stream: {{verify_queue_and_abort_test;please}}, queued...
                                                                                                                                                                      6: [1701677056.02][TEST][INF]
 Saw on the SPI bus: [mosi: b'010200000000000000000000000000000000000000000102000000000000000000000000000000000000000000000000000000000000', miso: b'010200000000000000000000000000000000000000000102000000000000000000000000000000000000000000000000000000000000']
6: [1701677056.02][SERI][TXD] {{verify_queue_and_abort_test;pass}}
6: [1701677056.04][CONN][INF] found KV pair in stream: {{__testcase_finish;Queueing and Aborting Async SPI via Interrupts;1;0}}, queued...
6: [1701677056.05][CONN][RXD] >>> 'Queueing and Aborting Async SPI via Interrupts': 1 passed, 0 failed
6: [1701677056.05][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677056.05][CONN][RXD]
6: [1701677056.06][CONN][RXD] >>> Running case #21: 'Use Multiple SPI Instances with Interrupts'...
6: [1701677056.06][CONN][INF] found KV pair in stream: {{__testcase_start;Use Multiple SPI Instances with Interrupts}}, queued...
6: [1701677056.07][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677057.09][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677057.10][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: Correct number of messages
6: First message looks OK
6: Second message looks OK
6: [1701677057.20][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677057.20][TEST][INF] PASS
6: [1701677057.20][SERI][TXD] {{verify_sequence;complete}}
6: [1701677057.21][CONN][INF] found KV pair in stream: {{__testcase_finish;Use Multiple SPI Instances with Interrupts;1;0}}, queued...
6: [1701677057.22][CONN][RXD] >>> 'Use Multiple SPI Instances with Interrupts': 1 passed, 0 failed
6: [1701677057.22][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677057.22][CONN][RXD]
6: [1701677057.23][CONN][RXD] >>> Running case #22: 'Send Data via Async DMA API (Tx only)'...
6: [1701677057.23][CONN][INF] found KV pair in stream: {{__testcase_start;Send Data via Async DMA API (Tx only)}}, queued...
6: [1701677057.23][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677058.24][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677058.25][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677058.35][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677058.35][TEST][INF] PASS
6: [1701677058.36][SERI][TXD] {{verify_sequence;complete}}
6: [1701677058.37][CONN][INF] found KV pair in stream: {{__testcase_finish;Send Data via Async DMA API (Tx only);1;0}}, queued...
6: [1701677058.38][CONN][RXD] >>> 'Send Data via Async DMA API (Tx only)': 1 passed, 0 failed
6: [1701677058.38][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677058.38][CONN][RXD]
6: [1701677058.39][CONN][RXD] >>> Running case #23: 'Send Data via Async DMA API (Rx only)'...
6: [1701677058.39][CONN][INF] found KV pair in stream: {{__testcase_start;Send Data via Async DMA API (Rx only)}}, queued...
6: [1701677058.39][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677059.40][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677059.41][CONN][RXD] Got: ff ff ff ff
                                                                                                            6: [1701677059.41][CONN][INF] found KV pair in stream: {{print_spi_data;please}}, queued...
    6: [1701677059.51][TEST][INF] Saw on the SPI bus: [mosi: b'ffffffff', miso: b'ffffffff']
6: [1701677059.51][SERI][TXD] {{print_spi_data;complete}}
6: [1701677059.52][CONN][INF] found KV pair in stream: {{__testcase_finish;Send Data via Async DMA API (Rx only);1;0}}, queued...
6: [1701677059.53][CONN][RXD] >>> 'Send Data via Async DMA API (Rx only)': 1 passed, 0 failed
6: [1701677059.53][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677059.53][CONN][RXD]
6: [1701677059.54][CONN][RXD] >>> Running case #24: 'Free and Reallocate SPI Instance with DMA'...
6: [1701677059.54][CONN][INF] found KV pair in stream: {{__testcase_start;Free and Reallocate SPI Instance with DMA}}, queued...
6: [1701677059.54][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677060.55][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677060.56][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677060.66][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677060.66][TEST][INF] PASS
6: [1701677060.67][SERI][TXD] {{verify_sequence;complete}}
6: [1701677060.68][CONN][INF] found KV pair in stream: {{__testcase_finish;Free and Reallocate SPI Instance with DMA;1;0}}, queued...
6: [1701677060.69][CONN][RXD] >>> 'Free and Reallocate SPI Instance with DMA': 1 passed, 0 failed
6: [1701677060.69][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677060.69][CONN][RXD]
6: [1701677060.70][CONN][RXD] >>> Running case #25: 'Send Data via Async DMA API (Tx/Rx)'...
6: [1701677060.70][CONN][INF] found KV pair in stream: {{__testcase_start;Send Data via Async DMA API (Tx/Rx)}}, queued...
6: [1701677060.70][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677061.71][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677061.72][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: [1701677061.82][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677061.82][TEST][INF] PASS
6: [1701677061.83][SERI][TXD] {{verify_sequence;complete}}
6: [1701677061.84][CONN][INF] found KV pair in stream: {{__testcase_finish;Send Data via Async DMA API (Tx/Rx);1;0}}, queued...
6: [1701677061.85][CONN][RXD] >>> 'Send Data via Async DMA API (Tx/Rx)': 1 passed, 0 failed
6: [1701677061.85][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677061.85][CONN][RXD]
6: [1701677061.86][CONN][RXD] >>> Running case #26: 'Benchmark Async SPI via DMA'...
6: [1701677061.86][CONN][INF] found KV pair in stream: {{__testcase_start;Benchmark Async SPI via DMA}}, queued...
6: [1701677061.87][CONN][RXD] Transferred 32 bytes @ 1000kHz in 425us, with 419us occurring in the background.
6: [1701677061.87][CONN][RXD] Note: Based on the byte count and frequency, the theoretical best time for this SPI transaction is 256us
6: [1701677061.88][CONN][RXD] Note: the above background time does not include overhead from interrupts, which may be significant.
6: [1701677061.89][CONN][RXD] >>> 'Benchmark Async SPI via DMA': 1 passed, 0 failed
6: [1701677061.89][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677061.89][CONN][RXD]
6: [1701677061.89][CONN][INF] found KV pair in stream: {{__testcase_finish;Benchmark Async SPI via DMA;1;0}}, queued...
6: [1701677061.91][CONN][RXD] >>> Running case #27: 'Queueing and Aborting Async SPI via DMA'...
6: [1701677061.91][CONN][INF] found KV pair in stream: {{__testcase_start;Queueing and Aborting Async SPI via DMA}}, queued...
6: [1701677061.92][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677062.93][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677062.98][CONN][INF] found KV pair in stream: {{verify_queue_and_abort_test;please}}, queued...
                                                                                                                                                                      6: [1701677063.05][TEST][INF]
 Saw on the SPI bus: [mosi: b'010200000000000000000000000000000000000000000102000000000000000000000000000000000000000000000000000000000000', miso: b'010200000000000000000000000000000000000000000102000000000000000000000000000000000000000000000000000000000000']
6: [1701677063.05][SERI][TXD] {{verify_queue_and_abort_test;pass}}
6: [1701677063.06][CONN][INF] found KV pair in stream: {{__testcase_finish;Queueing and Aborting Async SPI via DMA;1;0}}, queued...
6: [1701677063.07][CONN][RXD] >>> 'Queueing and Aborting Async SPI via DMA': 1 passed, 0 failed
6: [1701677063.07][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677063.07][CONN][RXD]
6: [1701677063.08][CONN][RXD] >>> Running case #28: 'Use Multiple SPI Instances with DMA'...
6: [1701677063.08][CONN][INF] found KV pair in stream: {{__testcase_start;Use Multiple SPI Instances with DMA}}, queued...
6: [1701677063.08][CONN][INF] found KV pair in stream: {{start_recording_spi;please}}, queued...
6: [1701677064.09][SERI][TXD] {{start_recording_spi;complete}}
                                                              6: [1701677064.10][CONN][INF] found KV pair in stream: {{verify_sequence;standard_word}}, queued...
                                                                                                                                                                 6: Correct number of messages
6: First message looks OK
6: Second message looks OK
6: [1701677064.23][TEST][INF] Saw on the SPI bus: [mosi: b'01020408', miso: b'01020408']
6: [1701677064.23][TEST][INF] PASS
6: [1701677064.24][SERI][TXD] {{verify_sequence;complete}}
6: [1701677064.25][CONN][INF] found KV pair in stream: {{__testcase_finish;Use Multiple SPI Instances with DMA;1;0}}, queued...
6: [1701677064.26][CONN][RXD] >>> 'Use Multiple SPI Instances with DMA': 1 passed, 0 failed
6: [1701677064.26][CONN][RXD] <greentea test suite>:0::PASS
6: [1701677064.26][CONN][RXD]
6: [1701677064.26][CONN][RXD] >>> Test cases: 28 passed, 0 failed
6: [1701677064.26][CONN][RXD]
6: [1701677064.26][CONN][RXD] -----------------------
6: [1701677064.27][CONN][RXD] 0 Tests 0 Failures 0 Ignored
6: [1701677064.27][CONN][RXD] OK
6: [1701677064.27][CONN][INF] found KV pair in stream: {{__testcase_summary;28;0}}, queued...
6: [1701677064.27][CONN][INF] found KV pair in stream: {{end;success}}, queued...
6: [1701677064.27][CONN][INF] found KV pair in stream: {{__exit;0}}, queued...
6: [1701677064.27][HTST][INF] __exit(0)
6: [1701677064.28][HTST][INF] __notify_complete(True)
6: [1701677064.28][HTST][INF] __exit_event_queue received
6: [1701677064.28][HTST][INF] test suite run finished after 30.64 sec...
6: [1701677064.28][CONN][INF] received special event '__host_test_finished' value='True', finishing
6: [1701677064.43][HTST][INF] CONN exited with code: 0
6: [1701677064.43][HTST][INF] Some events in queue
6: [1701677064.43][HTST][INF] stopped consuming events
6: [1701677064.43][HTST][INF] host test result() call skipped, received: True
6: [1701677064.43][HTST][INF] calling blocking teardown()
6: [1701677064.43][HTST][INF] teardown() finished
6: [1701677064.44][HTST][INF] {{result;success}}
1/1 Test #6: test-testshield-spi-basic ........   Passed   43.11 sec

---------------------------------------------------------------------------------------------------------------- ### Reviewers

@JojoS62
Copy link

JojoS62 commented Dec 4, 2023

hard stuff, hard to review.

thought about declaring CacheAlignedBuffer with alignas(32) instead of doing the alignment "manually", but believe that needs linker script support to work, and also I don't know if it would work with malloc() allocated structures.

from my experience, alignas works fine (just check the mapfile) and does not need linker script support. You'll need the linker script when put data in defined sections with attribute(section(".some_section")).

There is a runtime function for getting aligned memory, this is working now in current gcc versions. But these memory blocks are not working with the Mbed heap tracing, because the memory pointer can be modified and the heap trace relies on some allocation internals. I have an open issue for this.
Hope it helps and doesn't let you step into the same traps.

drivers/include/drivers/SPI.h Show resolved Hide resolved
drivers/source/SPI.cpp Show resolved Hide resolved
@multiplemonomials
Copy link
Collaborator Author

Hey @JojoS62 , great to have you back! Thanks for looking at this PR, you're right it's a tough issue and took some thought to work out.

What is this heap function to allocate aligned memory? I'll check it out! But, I still think I might stick with how I have it now unless there's an issue with the current implementation, because I like the fact that CacheAlignedBuffers can be allocated as globals or from the heap without worrying about special aligned allocations.

@JojoS62
Copy link

JojoS62 commented Dec 5, 2023

@multiplemonomials this is the function: https://en.cppreference.com/w/c/memory/aligned_alloc
It is for a long time in C++11, but is not implemented in all runtimes, ARMCC was still lacking it when I checked the last time. In newlib it was implemented wrong, and now it is fixed and uses internally the longer available memalign.
And this is the issue that I mentioned: ARMmbed#15346

@multiplemonomials
Copy link
Collaborator Author

@Ky-Ng @JojoS62 I've updated this MR to split CacheAlignedBuffer into two variants: StaticCacheAlignedBuffer, which allocates from fixed backing memory, and DynamicCacheAlignedBuffer, which allocates off the heap. This makes it possible to create buffers whose size is only known at runtime (which you couldn't do in the initial implementation). I'd rather not depend on the C library functionality at this point since it sounds like it isn't very well supported across platforms.

Copy link

@Ky-Ng Ky-Ng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just had a few questions on the use of static and inline functions. Other than that, the PR is amazing! Excited to use the STM32H7.

Thanks @multiplemonomials!

@multiplemonomials multiplemonomials merged commit 69d95b5 into master Dec 19, 2023
9 checks passed
@multiplemonomials multiplemonomials deleted the dev/dma-spi-cache-control branch December 19, 2023 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants