Skip to content

Commit

Permalink
gdma: prevent mutli-channels connect to the same peripheral
Browse files Browse the repository at this point in the history
1. add check in the gdma driver, to prevent multiple channels connecting
   to the same peripheral
2. memory copy DMA ID will occupy the peripheral's DMA ID on some ESP
   targets (e.g. esp32c3/s3). We should search for a free one when
install async memcpy driver.

Closes #10575
  • Loading branch information
suda-morris committed Feb 1, 2023
1 parent 2a9759e commit 46b6653
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 105 deletions.
92 changes: 69 additions & 23 deletions components/driver/gdma.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ struct gdma_group_t {
int group_id; // Group ID, index from 0
gdma_hal_context_t hal; // HAL instance is at group level
portMUX_TYPE spinlock; // group level spinlock
uint32_t tx_periph_in_use_mask; // each bit indicates which peripheral (TX direction) has been occupied
uint32_t rx_periph_in_use_mask; // each bit indicates which peripheral (RX direction) has been occupied
gdma_pair_t *pairs[SOC_GDMA_PAIRS_PER_GROUP]; // handles of GDMA pairs
int pair_ref_counts[SOC_GDMA_PAIRS_PER_GROUP]; // reference count used to protect pair install/uninstall
};
Expand Down Expand Up @@ -246,53 +248,97 @@ esp_err_t gdma_get_channel_id(gdma_channel_handle_t dma_chan, int *channel_id)

esp_err_t gdma_connect(gdma_channel_handle_t dma_chan, gdma_trigger_t trig_periph)
{
esp_err_t ret = ESP_OK;
gdma_pair_t *pair = NULL;
gdma_group_t *group = NULL;
ESP_GOTO_ON_FALSE(dma_chan, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(dma_chan->periph_id == GDMA_INVALID_PERIPH_TRIG, ESP_ERR_INVALID_STATE, err, TAG, "channel is using by peripheral: %d", dma_chan->periph_id);
ESP_RETURN_ON_FALSE(dma_chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(dma_chan->periph_id == GDMA_INVALID_PERIPH_TRIG, ESP_ERR_INVALID_STATE, TAG, "channel is using by peripheral: %d", dma_chan->periph_id);
pair = dma_chan->pair;
group = pair->group;

dma_chan->periph_id = trig_periph.instance_id;
// enable/disable m2m mode
gdma_ll_enable_m2m_mode(group->hal.dev, pair->pair_id, trig_periph.periph == GDMA_TRIG_PERIPH_M2M);
bool periph_conflict = false;

if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_TX) {
gdma_ll_tx_reset_channel(group->hal.dev, pair->pair_id); // reset channel
if (trig_periph.periph != GDMA_TRIG_PERIPH_M2M) {
gdma_ll_tx_connect_to_periph(group->hal.dev, pair->pair_id, trig_periph.instance_id);
if (trig_periph.instance_id >= 0) {
portENTER_CRITICAL(&group->spinlock);
if (group->tx_periph_in_use_mask & (1 << trig_periph.instance_id)) {
periph_conflict = true;
} else {
group->tx_periph_in_use_mask |= (1 << trig_periph.instance_id);
}
portEXIT_CRITICAL(&group->spinlock);
}
if (!periph_conflict) {
gdma_ll_tx_reset_channel(group->hal.dev, pair->pair_id); // reset channel
gdma_ll_tx_connect_to_periph(group->hal.dev, pair->pair_id, trig_periph.periph, trig_periph.instance_id);
}
} else {
gdma_ll_rx_reset_channel(group->hal.dev, pair->pair_id); // reset channel
if (trig_periph.periph != GDMA_TRIG_PERIPH_M2M) {
gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id, trig_periph.instance_id);
if (trig_periph.instance_id >= 0) {
portENTER_CRITICAL(&group->spinlock);
if (group->rx_periph_in_use_mask & (1 << trig_periph.instance_id)) {
periph_conflict = true;
} else {
group->rx_periph_in_use_mask |= (1 << trig_periph.instance_id);
}
portEXIT_CRITICAL(&group->spinlock);
}
if (!periph_conflict) {
gdma_ll_rx_reset_channel(group->hal.dev, pair->pair_id); // reset channel
gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id, trig_periph.periph, trig_periph.instance_id);
}
}

err:
return ret;
ESP_RETURN_ON_FALSE(!periph_conflict, ESP_ERR_INVALID_STATE, TAG, "peripheral %d is already used by another channel", trig_periph.instance_id);
dma_chan->periph_id = trig_periph.instance_id;
return ESP_OK;
}

esp_err_t gdma_disconnect(gdma_channel_handle_t dma_chan)
{
esp_err_t ret = ESP_OK;
gdma_pair_t *pair = NULL;
gdma_group_t *group = NULL;
ESP_GOTO_ON_FALSE(dma_chan, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(dma_chan->periph_id != GDMA_INVALID_PERIPH_TRIG, ESP_ERR_INVALID_STATE, err, TAG, "no peripheral is connected to the channel");
ESP_RETURN_ON_FALSE(dma_chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(dma_chan->periph_id != GDMA_INVALID_PERIPH_TRIG, ESP_ERR_INVALID_STATE, TAG, "no peripheral is connected to the channel");

pair = dma_chan->pair;
group = pair->group;
int save_periph_id = dma_chan->periph_id;

dma_chan->periph_id = GDMA_INVALID_PERIPH_TRIG;
if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_TX) {
gdma_ll_tx_connect_to_periph(group->hal.dev, pair->pair_id, GDMA_INVALID_PERIPH_TRIG);
if (save_periph_id >= 0) {
portENTER_CRITICAL(&group->spinlock);
group->tx_periph_in_use_mask &= ~(1 << save_periph_id);
portEXIT_CRITICAL(&group->spinlock);
}
gdma_ll_tx_disconnect_from_periph(group->hal.dev, pair->pair_id);
} else {
gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id, GDMA_INVALID_PERIPH_TRIG);
if (save_periph_id >= 0) {
portENTER_CRITICAL(&group->spinlock);
group->rx_periph_in_use_mask &= ~(1 << save_periph_id);
portEXIT_CRITICAL(&group->spinlock);
}
gdma_ll_rx_disconnect_from_periph(group->hal.dev, pair->pair_id);
}

err:
return ret;
dma_chan->periph_id = GDMA_INVALID_PERIPH_TRIG;
return ESP_OK;
}

esp_err_t gdma_get_free_m2m_trig_id_mask(gdma_channel_handle_t dma_chan, uint32_t *mask)
{
gdma_pair_t *pair = NULL;
gdma_group_t *group = NULL;
ESP_RETURN_ON_FALSE(dma_chan && mask, ESP_ERR_INVALID_ARG, TAG, "invalid argument");

uint32_t free_mask = GDMA_LL_M2M_FREE_PERIPH_ID_MASK;
pair = dma_chan->pair;
group = pair->group;

portENTER_CRITICAL(&group->spinlock);
free_mask &= ~(group->tx_periph_in_use_mask);
free_mask &= ~(group->rx_periph_in_use_mask);
portEXIT_CRITICAL(&group->spinlock);

*mask = free_mask;
return ESP_OK;
}

esp_err_t gdma_set_transfer_ability(gdma_channel_handle_t dma_chan, const gdma_transfer_ability_t *ability)
Expand Down
45 changes: 17 additions & 28 deletions components/driver/include/esp_private/gdma.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include <stdbool.h>
#include "soc/gdma_channel.h"
#include "hal/gdma_types.h"
#include "esp_err.h"

#ifdef __cplusplus
Expand All @@ -23,34 +24,6 @@ extern "C" {
*/
typedef struct gdma_channel_t *gdma_channel_handle_t;

/**
* @brief Enumeration of peripherals which have the DMA capability
* @note Some peripheral might not be available on certain chip, please refer to `soc_caps.h` for detail.
*
*/
typedef enum {
GDMA_TRIG_PERIPH_M2M, /*!< GDMA trigger peripheral: M2M */
GDMA_TRIG_PERIPH_UHCI, /*!< GDMA trigger peripheral: UHCI */
GDMA_TRIG_PERIPH_SPI, /*!< GDMA trigger peripheral: SPI */
GDMA_TRIG_PERIPH_I2S, /*!< GDMA trigger peripheral: I2S */
GDMA_TRIG_PERIPH_AES, /*!< GDMA trigger peripheral: AES */
GDMA_TRIG_PERIPH_SHA, /*!< GDMA trigger peripheral: SHA */
GDMA_TRIG_PERIPH_ADC, /*!< GDMA trigger peripheral: ADC */
GDMA_TRIG_PERIPH_DAC, /*!< GDMA trigger peripheral: DAC */
GDMA_TRIG_PERIPH_LCD, /*!< GDMA trigger peripheral: LCD */
GDMA_TRIG_PERIPH_CAM, /*!< GDMA trigger peripheral: CAM */
GDMA_TRIG_PERIPH_RMT, /*!< GDMA trigger peripheral: RMT */
} gdma_trigger_peripheral_t;

/**
* @brief Enumeration of GDMA channel direction
*
*/
typedef enum {
GDMA_CHANNEL_DIRECTION_TX, /*!< GDMA channel direction: TX */
GDMA_CHANNEL_DIRECTION_RX, /*!< GDMA channel direction: RX */
} gdma_channel_direction_t;

/**
* @brief Collection of configuration items that used for allocating GDMA channel
*
Expand Down Expand Up @@ -325,6 +298,22 @@ esp_err_t gdma_append(gdma_channel_handle_t dma_chan);
*/
esp_err_t gdma_reset(gdma_channel_handle_t dma_chan);

/**
* @brief Get the mask of free M2M trigger IDs
*
* @note On some ESP targets (e.g. ESP32C3/S3), DMA trigger used for memory copy can be any of valid peripheral's trigger ID,
* which can bring conflict if the peripheral is also using the same trigger ID. This function can return the free IDs
* for memory copy, at the runtime.
*
* @param[in] dma_chan GDMA channel handle, allocated by `gdma_new_channel`
* @param[out] mask Returned mask of free M2M trigger IDs
* @return
* - ESP_OK: Get free M2M trigger IDs successfully
* - ESP_ERR_INVALID_ARG: Get free M2M trigger IDs failed because of invalid argument
* - ESP_FAIL: Get free M2M trigger IDs failed because of other error
*/
esp_err_t gdma_get_free_m2m_trig_id_mask(gdma_channel_handle_t dma_chan, uint32_t *mask);

#ifdef __cplusplus
}
#endif
19 changes: 13 additions & 6 deletions components/driver/test/test_gdma.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,27 @@ TEST_CASE("GDMA channel allocation", "[gdma]")
gdma_tx_event_callbacks_t tx_cbs = {};
gdma_rx_event_callbacks_t rx_cbs = {};

// install TX channels for different peripherals
// install TX channels
for (int i = 0; i < SOC_GDMA_PAIRS_PER_GROUP; i++) {
TEST_ESP_OK(gdma_new_channel(&channel_config, &tx_channels[i]));
TEST_ESP_OK(gdma_connect(tx_channels[i], GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_M2M, 0)));
TEST_ESP_OK(gdma_register_tx_event_callbacks(tx_channels[i], &tx_cbs, NULL));
};
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, gdma_new_channel(&channel_config, &tx_channels[0]));

// Free interrupts before installing RX interrupts to ensure enough free interrupts
for (int i = 0; i < SOC_GDMA_PAIRS_PER_GROUP; i++) {
TEST_ESP_OK(gdma_disconnect(tx_channels[i]));
TEST_ESP_OK(gdma_del_channel(tx_channels[i]));
}

// install RX channels for different peripherals
// install RX channels
channel_config.direction = GDMA_CHANNEL_DIRECTION_RX;
for (int i = 0; i < SOC_GDMA_PAIRS_PER_GROUP; i++) {
TEST_ESP_OK(gdma_new_channel(&channel_config, &rx_channels[i]));
TEST_ESP_OK(gdma_connect(rx_channels[i], GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_M2M, 0)));
TEST_ESP_OK(gdma_register_rx_event_callbacks(rx_channels[i], &rx_cbs, NULL));
}
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, gdma_new_channel(&channel_config, &rx_channels[0]));

for (int i = 0; i < SOC_GDMA_PAIRS_PER_GROUP; i++) {
TEST_ESP_OK(gdma_disconnect(rx_channels[i]));
TEST_ESP_OK(gdma_del_channel(rx_channels[i]));
}

Expand All @@ -63,7 +59,18 @@ TEST_CASE("GDMA channel allocation", "[gdma]")
TEST_ESP_OK(gdma_new_channel(&channel_config, &rx_channels[1]));
channel_config.sibling_chan = NULL;
TEST_ESP_OK(gdma_new_channel(&channel_config, &rx_channels[0]));

TEST_ESP_OK(gdma_connect(tx_channels[0], GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_SPI, 2)));
// can't connect multiple channels to the same peripheral
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, gdma_connect(tx_channels[1], GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_SPI, 2)));
TEST_ESP_OK(gdma_connect(tx_channels[1], GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_M2M, 0)));

TEST_ESP_OK(gdma_connect(rx_channels[0], GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_SPI, 2)));
// but rx and tx can connect to the same peripheral
TEST_ESP_OK(gdma_connect(rx_channels[1], GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_M2M, 0)));
for (int i = 0; i < 2; i++) {
TEST_ESP_OK(gdma_disconnect(tx_channels[i]));
TEST_ESP_OK(gdma_disconnect(rx_channels[i]));
TEST_ESP_OK(gdma_del_channel(tx_channels[i]));
TEST_ESP_OK(gdma_del_channel(rx_channels[i]));
}
Expand Down
9 changes: 7 additions & 2 deletions components/esp_hw_support/port/async_memcpy_impl_gdma.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,13 @@ esp_err_t async_memcpy_impl_init(async_memcpy_impl_t *impl)
goto err;
}

gdma_connect(impl->rx_channel, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_M2M, 0));
gdma_connect(impl->tx_channel, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_M2M, 0));
gdma_trigger_t m2m_trigger = GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_M2M, 0);
// get a free DMA trigger ID for memory copy
uint32_t free_m2m_id_mask = 0;
gdma_get_free_m2m_trig_id_mask(impl->tx_channel, &free_m2m_id_mask);
m2m_trigger.instance_id = __builtin_ctz(free_m2m_id_mask);
gdma_connect(impl->rx_channel, m2m_trigger);
gdma_connect(impl->tx_channel, m2m_trigger);

gdma_strategy_config_t strategy_config = {
.auto_update_desc = true,
Expand Down
41 changes: 26 additions & 15 deletions components/hal/esp32c3/include/hal/gdma_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <stdint.h>
#include <stdbool.h>
#include "hal/gdma_types.h"
#include "soc/gdma_struct.h"
#include "soc/gdma_reg.h"

Expand All @@ -19,6 +20,10 @@ extern "C" {
#define GDMA_LL_RX_EVENT_MASK (0x06A7)
#define GDMA_LL_TX_EVENT_MASK (0x1958)

// any "valid" peripheral ID can be used for M2M mode
#define GDMA_LL_M2M_FREE_PERIPH_ID_MASK (0x1CD)
#define GDMA_LL_INVALID_PERIPH_ID (0x3F)

#define GDMA_LL_EVENT_TX_FIFO_UDF (1<<12)
#define GDMA_LL_EVENT_TX_FIFO_OVF (1<<11)
#define GDMA_LL_EVENT_RX_FIFO_UDF (1<<10)
Expand All @@ -34,19 +39,6 @@ extern "C" {
#define GDMA_LL_EVENT_RX_DONE (1<<0)

///////////////////////////////////// Common /////////////////////////////////////////
/**
* @brief Enable DMA channel M2M mode (TX channel n forward data to RX channel n), disabled by default
*/
static inline void gdma_ll_enable_m2m_mode(gdma_dev_t *dev, uint32_t channel, bool enable)
{
dev->channel[channel].in.in_conf0.mem_trans_en = enable;
if (enable) {
// to enable m2m mode, the tx chan has to be the same to rx chan, and set to a valid value
dev->channel[channel].in.in_peri_sel.sel = 0;
dev->channel[channel].out.out_peri_sel.sel = 0;
}
}

/**
* @brief Enable DMA clock gating
*/
Expand Down Expand Up @@ -254,9 +246,19 @@ static inline void gdma_ll_rx_set_priority(gdma_dev_t *dev, uint32_t channel, ui
/**
* @brief Connect DMA RX channel to a given peripheral
*/
static inline void gdma_ll_rx_connect_to_periph(gdma_dev_t *dev, uint32_t channel, int periph_id)
static inline void gdma_ll_rx_connect_to_periph(gdma_dev_t *dev, uint32_t channel, gdma_trigger_peripheral_t periph, int periph_id)
{
dev->channel[channel].in.in_peri_sel.sel = periph_id;
dev->channel[channel].in.in_conf0.mem_trans_en = (periph == GDMA_TRIG_PERIPH_M2M);
}

/**
* @brief Disconnect DMA RX channel from peripheral
*/
static inline void gdma_ll_rx_disconnect_from_periph(gdma_dev_t *dev, uint32_t channel)
{
dev->channel[channel].in.in_peri_sel.sel = GDMA_LL_INVALID_PERIPH_ID;
dev->channel[channel].in.in_conf0.mem_trans_en = false;
}

///////////////////////////////////// TX /////////////////////////////////////////
Expand Down Expand Up @@ -457,11 +459,20 @@ static inline void gdma_ll_tx_set_priority(gdma_dev_t *dev, uint32_t channel, ui
/**
* @brief Connect DMA TX channel to a given peripheral
*/
static inline void gdma_ll_tx_connect_to_periph(gdma_dev_t *dev, uint32_t channel, int periph_id)
static inline void gdma_ll_tx_connect_to_periph(gdma_dev_t *dev, uint32_t channel, gdma_trigger_peripheral_t periph, int periph_id)
{
(void)periph;
dev->channel[channel].out.out_peri_sel.sel = periph_id;
}

/**
* @brief Disconnect DMA TX channel from peripheral
*/
static inline void gdma_ll_tx_disconnect_from_periph(gdma_dev_t *dev, uint32_t channel)
{
dev->channel[channel].out.out_peri_sel.sel = GDMA_LL_INVALID_PERIPH_ID;
}

#ifdef __cplusplus
}
#endif

0 comments on commit 46b6653

Please sign in to comment.