diff --git a/components/driver/include/esp_private/spi_slave_internal.h b/components/driver/include/esp_private/spi_slave_internal.h index aa5cd73ab8..3b5d270155 100644 --- a/components/driver/include/esp_private/spi_slave_internal.h +++ b/components/driver/include/esp_private/spi_slave_internal.h @@ -16,6 +16,7 @@ #include "sdkconfig.h" #include "esp_err.h" #include "hal/spi_types.h" +#include "driver/spi_slave.h" #ifdef __cplusplus extern "C" { @@ -37,3 +38,24 @@ extern "C" { * - ESP_OK on success */ esp_err_t spi_slave_queue_reset(spi_host_device_t host); + + +/** + * @brief Queue a SPI transaction in ISR + * + * Similar as ``spi_slave_queue_trans``, but can and can only called within an ISR, then get the transaction results + * through the transaction discriptor passed in ``spi_slave_interface_config_t::post_trans_cb``. if use this API, you + * should trigger a transaction by normal ``spi_slave_queue_trans`` once and only once to start isr + * + * If you use both ``spi_slave_queue_trans`` and ``spi_slave_queue_trans_isr`` simultaneously to transfer valid data, + * you should deal with concurrency issues on your self risk + * + * @param host SPI peripheral that is acting as a slave + * @param trans_desc Description of transaction to execute. Not const because we may want to write status back + * into the transaction description. + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_NO_MEM if trans_queue is full + * - ESP_OK on success + */ +esp_err_t spi_slave_queue_trans_isr(spi_host_device_t host, const spi_slave_transaction_t *trans_desc); diff --git a/components/driver/spi_slave.c b/components/driver/spi_slave.c index cf3bfbfeb1..dfcaa66c2b 100644 --- a/components/driver/spi_slave.c +++ b/components/driver/spi_slave.c @@ -27,6 +27,7 @@ #include "driver/spi_slave.h" #include "hal/gpio_hal.h" #include "hal/spi_slave_hal.h" +#include "esp_private/spi_slave_internal.h" #include "esp_private/spi_common_internal.h" @@ -337,6 +338,29 @@ esp_err_t SPI_SLAVE_ATTR spi_slave_queue_trans(spi_host_device_t host, const spi return ESP_OK; } +esp_err_t SPI_SLAVE_ISR_ATTR spi_slave_queue_trans_isr(spi_host_device_t host, const spi_slave_transaction_t *trans_desc) +{ + BaseType_t r; + BaseType_t do_yield = pdFALSE; + ESP_RETURN_ON_FALSE_ISR(is_valid_host(host), ESP_ERR_INVALID_ARG, SPI_TAG, "invalid host"); + ESP_RETURN_ON_FALSE_ISR(spihost[host], ESP_ERR_INVALID_ARG, SPI_TAG, "host not slave"); + ESP_RETURN_ON_FALSE_ISR(spihost[host]->dma_enabled == 0 || trans_desc->tx_buffer==NULL || esp_ptr_dma_capable(trans_desc->tx_buffer), + ESP_ERR_INVALID_ARG, SPI_TAG, "txdata not in DMA-capable memory"); + ESP_RETURN_ON_FALSE_ISR(spihost[host]->dma_enabled == 0 || trans_desc->rx_buffer==NULL || + (esp_ptr_dma_capable(trans_desc->rx_buffer) && esp_ptr_word_aligned(trans_desc->rx_buffer) && + (trans_desc->length%4==0)), + ESP_ERR_INVALID_ARG, SPI_TAG, "rxdata not in DMA-capable memory or not WORD aligned"); + ESP_RETURN_ON_FALSE_ISR(trans_desc->length <= spihost[host]->max_transfer_sz * 8, ESP_ERR_INVALID_ARG, SPI_TAG, "data transfer > host maximum"); + + r = xQueueSendFromISR(spihost[host]->trans_queue, (void *)&trans_desc, &do_yield); + if (!r) { + return ESP_ERR_NO_MEM; + } + if (do_yield) { + portYIELD_FROM_ISR(); + } + return ESP_OK; +} esp_err_t SPI_SLAVE_ATTR spi_slave_get_trans_result(spi_host_device_t host, spi_slave_transaction_t **trans_desc, TickType_t ticks_to_wait) { diff --git a/components/driver/test_apps/spi/slave/main/test_spi_slave.c b/components/driver/test_apps/spi/slave/main/test_spi_slave.c index 0857d751db..2d092a2026 100644 --- a/components/driver/test_apps/spi/slave/main/test_spi_slave.c +++ b/components/driver/test_apps/spi/slave/main/test_spi_slave.c @@ -16,6 +16,7 @@ #include "driver/spi_slave.h" #include "driver/gpio.h" #include "esp_private/cache_utils.h" +#include "esp_private/spi_slave_internal.h" #include "esp_log.h" #include "esp_rom_gpio.h" @@ -385,7 +386,6 @@ static void unaligned_test_slave(void) } TEST_CASE_MULTIPLE_DEVICES("SPI_Slave_Unaligned_Test", "[spi_ms][test_env=generic_multi_device][timeout=120]", unaligned_test_master, unaligned_test_slave); - #endif //#if (TEST_SPI_PERIPH_NUM == 1) @@ -447,31 +447,28 @@ static IRAM_ATTR void ESP_LOG_BUFFER_HEX_ISR(const char *tag, const uint8_t *buf } esp_rom_printf(DRAM_STR("\n")); } -static uint32_t slave_isr_cnt, test_fail; -static IRAM_ATTR void test_spi_slave_post_trans_cbk(spi_slave_transaction_t *curr_trans){ - slave_isr_cnt ++; +static uint32_t isr_iram_cnt, iram_test_fail; +static IRAM_ATTR void test_slave_iram_post_trans_cbk(spi_slave_transaction_t *curr_trans){ + isr_iram_cnt ++; // first trans is the trigger trans with random data by master - if(slave_isr_cnt > 1){ + if(isr_iram_cnt > 1){ ESP_LOG_BUFFER_HEX_ISR(DRAM_STR("slave tx"), curr_trans->tx_buffer, curr_trans->trans_len/8); if(memcmp(curr_trans->rx_buffer, curr_trans->user, curr_trans->trans_len/8)){ ESP_LOG_BUFFER_HEX_ISR(DRAM_STR("slave rx"), curr_trans->rx_buffer, curr_trans->trans_len/8); ESP_LOG_BUFFER_HEX_ISR(DRAM_STR("slave exp"), curr_trans->user, curr_trans->trans_len/8); - test_fail = true; + iram_test_fail = true; } } - if(slave_isr_cnt <= TEST_IRAM_TRANS_NUM) esp_rom_printf(DRAM_STR("Send signal: [Slave ready]!\n")); + if(isr_iram_cnt <= TEST_IRAM_TRANS_NUM) esp_rom_printf(DRAM_STR("Send signal: [Slave ready]!\n")); } -static IRAM_ATTR void spi_slave_trans_in_isr(void){ +static IRAM_ATTR void test_slave_isr_iram(void){ spi_bus_config_t bus_cfg = SPI_BUS_TEST_DEFAULT_CONFIG(); - spi_slave_interface_config_t slvcfg = { - .mode = 0, - .spics_io_num = SPI2_IOMUX_PIN_NUM_CS, - .flags = SPI_SLAVE_NO_RETURN_RESULT, - .queue_size = 16, - .post_trans_cb = test_spi_slave_post_trans_cbk, - }; + spi_slave_interface_config_t slvcfg = SPI_SLAVE_TEST_DEFAULT_CONFIG(); + slvcfg.flags = SPI_SLAVE_NO_RETURN_RESULT; + slvcfg.queue_size = 16; + slvcfg.post_trans_cb = test_slave_iram_post_trans_cbk; TEST_ESP_OK(spi_slave_initialize(TEST_SPI_HOST, &bus_cfg, &slvcfg, SPI_DMA_CH_AUTO)); uint8_t *slave_iram_send = heap_caps_malloc(TEST_BUFFER_SZ, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); @@ -499,11 +496,11 @@ static IRAM_ATTR void spi_slave_trans_in_isr(void){ // disable cache then send signal `ready` to start transaction spi_flash_disable_interrupts_caches_and_other_cpu(); esp_rom_printf(DRAM_STR("Send signal: [Slave ready]!\n")); - while(slave_isr_cnt < TEST_IRAM_TRANS_NUM + 1){ + while(isr_iram_cnt <= TEST_IRAM_TRANS_NUM){ esp_rom_delay_us(10); } spi_flash_enable_interrupts_caches_and_other_cpu(); - if(test_fail) TEST_FAIL(); + if(iram_test_fail) TEST_FAIL(); free(slave_iram_send); free(slave_iram_recv); @@ -511,5 +508,70 @@ static IRAM_ATTR void spi_slave_trans_in_isr(void){ spi_slave_free(TEST_SPI_HOST); } -TEST_CASE_MULTIPLE_DEVICES("SPI_Slave: Test_ISR_IRAM_disable_cache", "[spi_ms]", test_slave_iram_master_normal, spi_slave_trans_in_isr); +TEST_CASE_MULTIPLE_DEVICES("SPI_Slave: Test_ISR_IRAM_disable_cache", "[spi_ms]", test_slave_iram_master_normal, test_slave_isr_iram); + + +static uint32_t isr_trans_cnt, isr_trans_test_fail; +static IRAM_ATTR void test_trans_in_isr_post_trans_cbk(spi_slave_transaction_t *curr_trans){ + isr_trans_cnt ++; + + //first trans is the trigger trans with random data + if(isr_trans_cnt > 1){ + ESP_LOG_BUFFER_HEX_ISR(DRAM_STR("slave tx"), curr_trans->tx_buffer, curr_trans->trans_len/8); + if(memcmp(curr_trans->rx_buffer, curr_trans->user, curr_trans->trans_len/8)){ + ESP_LOG_BUFFER_HEX_ISR(DRAM_STR("slave rx"), curr_trans->rx_buffer, curr_trans->trans_len/8); + ESP_LOG_BUFFER_HEX_ISR(DRAM_STR("slave exp"), curr_trans->user, curr_trans->trans_len/8); + isr_trans_test_fail = true; + } + + curr_trans->tx_buffer = (uint8_t *)curr_trans->tx_buffer + TEST_TRANS_LEN; + curr_trans->rx_buffer = (uint8_t *)curr_trans->rx_buffer + TEST_TRANS_LEN; + curr_trans->user = (uint8_t *)curr_trans->user + TEST_TRANS_LEN; + } + + if(isr_trans_cnt <= TEST_IRAM_TRANS_NUM){ + if(ESP_OK == spi_slave_queue_trans_isr(TEST_SPI_HOST, curr_trans)){ + esp_rom_printf(DRAM_STR("Send signal: [Slave ready]!\n")); + } + else esp_rom_printf(DRAM_STR("SPI Add trans in isr fail, Queue full\n")); + } +} + +static IRAM_ATTR void spi_slave_trans_in_isr(void){ + spi_bus_config_t bus_cfg = SPI_BUS_TEST_DEFAULT_CONFIG(); + spi_slave_interface_config_t slvcfg = SPI_SLAVE_TEST_DEFAULT_CONFIG(); + slvcfg.flags = SPI_SLAVE_NO_RETURN_RESULT; + slvcfg.queue_size = 16; + slvcfg.post_trans_cb = test_trans_in_isr_post_trans_cbk; + TEST_ESP_OK(spi_slave_initialize(TEST_SPI_HOST, &bus_cfg, &slvcfg, SPI_DMA_CH_AUTO)); + + uint8_t *slave_isr_send = heap_caps_malloc(TEST_BUFFER_SZ, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + uint8_t *slave_isr_recv = heap_caps_calloc(1, TEST_BUFFER_SZ, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + uint8_t *slave_isr_exp = heap_caps_malloc(TEST_BUFFER_SZ, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL); + get_tx_buffer(1001, slave_isr_exp, slave_isr_send, TEST_BUFFER_SZ); + spi_slave_transaction_t trans_cfg = { + .tx_buffer = slave_isr_send, + .rx_buffer = slave_isr_recv, + .user = slave_isr_exp, + .length = TEST_TRANS_LEN * 8, + }; + + unity_wait_for_signal("Master ready"); + //start a trans by normal API first to trigger spi isr + spi_slave_queue_trans(TEST_SPI_HOST, &trans_cfg, portMAX_DELAY); + spi_flash_disable_interrupts_caches_and_other_cpu(); + esp_rom_printf(DRAM_STR("Send signal: [Slave ready]!\n")); + while(isr_trans_cnt <= TEST_IRAM_TRANS_NUM){ + esp_rom_delay_us(10); + } + spi_flash_enable_interrupts_caches_and_other_cpu(); + if(isr_trans_test_fail) TEST_FAIL(); + + free(slave_isr_send); + free(slave_isr_recv); + free(slave_isr_exp); + spi_slave_free(TEST_SPI_HOST); +} +TEST_CASE_MULTIPLE_DEVICES("SPI_Slave: Test_Queue_Trans_in_ISR", "[spi_ms]", test_slave_iram_master_normal, spi_slave_trans_in_isr); + #endif // CONFIG_SPI_SLAVE_ISR_IN_IRAM diff --git a/components/driver/test_apps/spi/slave/pytest_spi_slave.py b/components/driver/test_apps/spi/slave/pytest_spi_slave.py index 4b6c4670f7..ea3266097a 100644 --- a/components/driver/test_apps/spi/slave/pytest_spi_slave.py +++ b/components/driver/test_apps/spi/slave/pytest_spi_slave.py @@ -7,6 +7,7 @@ import pytest # If `test_env` is define, should not run on generic runner @pytest.mark.supported_targets @pytest.mark.generic +@pytest.mark.parametrize('config', ['defaults',], indirect=True) def test_slave_single_dev(case_tester) -> None: # type: ignore for case in case_tester.test_menu: if 'test_env' in case.attributes: