refactor(bitscrambler): to use GDMA link API

This commit is contained in:
morris
2025-01-03 18:00:50 +08:00
parent 8de8558841
commit dc3f31adac
12 changed files with 177 additions and 145 deletions
@@ -0,0 +1,313 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdatomic.h>
#include "esp_log.h"
#include "driver/bitscrambler.h"
#include "bitscrambler_private.h"
#include "hal/bitscrambler_ll.h"
#include "esp_private/periph_ctrl.h"
static const char *TAG = "bitscrambler";
#define BITSCRAMBLER_BINARY_VER 1 //max version we're compatible with
#define BITSCRAMBLER_HW_REV 0
// After a reset, it can take a few cycles for the BitScrambler to actually be
// reset. We check this many times for this; if it takes longer the hardware
// is broken or something.
#define BITSCRAMBLER_RESET_ITERATIONS 10000
/*
Format of a V1 BitScrambler program image:
- Header, as defined by bitscrambler_program_hdr_t below. Size is hdr->hdr_len words.
- Program lines. A line is 9 32-bit words, we have hdr->inst_ct lines.
- LUT data. LUT is hdr->lut_word_ct 32-bit words in size.
*/
typedef struct {
uint8_t version;
uint8_t hw_rev;
uint8_t hdr_len; //in 32-bit words
uint8_t inst_ct; //0-8
uint16_t lut_word_ct; //in 32-bit words
uint8_t lut_width; //0, 1, 2
uint8_t prefetch; //prefetch enabled?
uint16_t trailing_bits; //in bits
uint8_t eof_on;
uint8_t unused;
} bitscrambler_program_hdr_t;
#define INST_LEN_WORDS BITSCRAMBLER_LL_INST_LEN_WORDS
// For now, hardware only has one TX and on RX unit. Need to make this more flexible if we get
// non-specific and/or more channels.
atomic_flag tx_in_use = ATOMIC_FLAG_INIT;
atomic_flag rx_in_use = ATOMIC_FLAG_INIT;
// Claim both TX and RX channels for loopback use
// Returns true on success, false if any of the two directions already is claimed.
static bool claim_channel_loopback(void)
{
bool old_val_tx = atomic_flag_test_and_set(&tx_in_use);
if (old_val_tx) {
return false;
}
bool old_val_rx = atomic_flag_test_and_set(&rx_in_use);
if (old_val_rx) {
atomic_flag_clear(&tx_in_use);
return false;
}
return true;
}
// Claim a channel using the direction it indicated.
// Returns true on success, false if the direction already is claimed
static bool claim_channel(bitscrambler_direction_t dir)
{
if (dir == BITSCRAMBLER_DIR_TX) {
bool old_val = atomic_flag_test_and_set(&tx_in_use);
if (old_val) {
return false;
}
} else if (dir == BITSCRAMBLER_DIR_RX) {
bool old_val = atomic_flag_test_and_set(&rx_in_use);
if (old_val) {
return false;
}
}
return true;
}
//Initialize the BitScrambler object and hardware using the given config.
static esp_err_t init_from_config(bitscrambler_t *bs, const bitscrambler_config_t *config)
{
bs->cfg = *config; //Copy config over
bs->hw = BITSCRAMBLER_LL_GET_HW(0); //there's only one as of now; if there's more, we need to handle them as a pool.
//Attach to indicated peripheral.
bitscrambler_ll_select_peripheral(bs->hw, bs->cfg.dir, config->attach_to);
bitscrambler_ll_enable(bs->hw, bs->cfg.dir);
return ESP_OK;
}
static void enable_clocks(bitscrambler_t *bs)
{
PERIPH_RCC_ACQUIRE_ATOMIC(PERIPH_BITSCRAMBLER_MODULE, ref_count) {
if (ref_count == 0) { //we're the first to enable the BitScrambler module
bitscrambler_ll_set_bus_clock_sys_enable(1);
bitscrambler_ll_reset_sys();
}
if (bs->cfg.dir == BITSCRAMBLER_DIR_RX || bs->loopback) {
bitscrambler_ll_set_bus_clock_rx_enable(1);
bitscrambler_ll_reset_rx();
}
if (bs->cfg.dir == BITSCRAMBLER_DIR_TX || bs->loopback) {
bitscrambler_ll_set_bus_clock_tx_enable(1);
bitscrambler_ll_reset_tx();
}
}
}
static void disable_clocks(bitscrambler_t *bs)
{
PERIPH_RCC_RELEASE_ATOMIC(PERIPH_BITSCRAMBLER_MODULE, ref_count) {
if (bs->cfg.dir == BITSCRAMBLER_DIR_RX || bs->loopback) {
bitscrambler_ll_set_bus_clock_rx_enable(0);
}
if (bs->cfg.dir == BITSCRAMBLER_DIR_TX || bs->loopback) {
bitscrambler_ll_set_bus_clock_tx_enable(0);
}
if (ref_count == 0) { //we're the last to disable the BitScrambler module
bitscrambler_ll_set_bus_clock_sys_enable(0);
}
}
}
//Private function: init an existing BitScrambler object as a loopback BitScrambler.
esp_err_t bitscrambler_init_loopback(bitscrambler_handle_t handle, const bitscrambler_config_t *config)
{
if (!claim_channel_loopback()) {
return ESP_ERR_NOT_FOUND;
}
assert(config->dir == BITSCRAMBLER_DIR_TX);
handle->loopback = true;
enable_clocks(handle);
esp_err_t r = init_from_config(handle, config);
//Loopback mode also needs RX channel set to the selected peripheral, even if it's not used.
bitscrambler_ll_select_peripheral(handle->hw, BITSCRAMBLER_DIR_RX, config->attach_to);
return r;
}
esp_err_t bitscrambler_new(const bitscrambler_config_t *config, bitscrambler_handle_t *handle)
{
if (!config) {
return ESP_ERR_INVALID_ARG;
}
if (!handle) {
return ESP_ERR_INVALID_ARG;
}
// Allocate memory for private data
bitscrambler_t *bs = calloc(1, sizeof(bitscrambler_t));
if (!bs) {
return ESP_ERR_NO_MEM;
}
// Claim channel
if (!claim_channel(config->dir)) {
free(bs);
return ESP_ERR_NOT_FOUND;
}
enable_clocks(bs);
// Do initialization of BS object.
esp_err_t r = init_from_config(bs, config);
if (r != ESP_OK) {
bitscrambler_free(bs);
return r;
}
// Done.
*handle = bs;
return ESP_OK;
}
esp_err_t bitscrambler_load_program(bitscrambler_handle_t bs, const void *program_bin)
{
if (!bs || !program_bin) {
return ESP_ERR_INVALID_ARG;
}
bitscrambler_program_hdr_t hdr;
//Parse the program header. There are two versions, V0 is generated by the C assembler while
//v1 is generated by the Python assembler.
int inst_len_bytes = INST_LEN_WORDS * sizeof(uint32_t); //note this is different for v1 and v0
memcpy(&hdr, program_bin, sizeof(bitscrambler_program_hdr_t));
if (hdr.version != BITSCRAMBLER_BINARY_VER) {
ESP_LOGE(TAG, "Bitscrambler binary version %d not supported!", hdr.version);
return ESP_ERR_INVALID_ARG;
}
if (hdr.hw_rev != BITSCRAMBLER_HW_REV) {
ESP_LOGE(TAG, "Bitscrambler hardware rev %d not supported!", hdr.hw_rev);
return ESP_ERR_INVALID_ARG;
}
bitscrambler_ll_set_state(bs->hw, bs->cfg.dir, BITSCRAMBLER_SET_STATE_HALT);
//Load the program
const uint8_t *p = (const uint8_t*)program_bin;
p += hdr.hdr_len * sizeof(uint32_t); //skip header
uint32_t instr[INST_LEN_WORDS];
for (int inst = 0; inst < hdr.inst_ct; inst++) {
//v0 doesn't have the words 32-bit aligned, so memcpy to work around that
memcpy(instr, p, INST_LEN_WORDS * sizeof(uint32_t));
p += inst_len_bytes;
for (int w = 0; w < INST_LEN_WORDS; w++) {
bitscrambler_ll_instmem_write(bs->hw, bs->cfg.dir, inst, w, instr[w]);
}
}
ESP_LOGD(TAG, "Loaded %d instructions", hdr.inst_ct);
//Load the LUT.
bitscrambler_ll_set_lut_width(bs->hw, bs->cfg.dir, BITSCRAMBLER_LUT_WIDTH_32BIT);
uint32_t *lut = (uint32_t*)p;
for (int w = 0; w < hdr.lut_word_ct; w++) {
bitscrambler_ll_lutmem_write(bs->hw, bs->cfg.dir, w, lut[w]);
}
//Set options from header
bitscrambler_ll_set_lut_width(bs->hw, bs->cfg.dir, hdr.lut_width);
bitscrambler_ll_set_prefetch_mode(bs->hw, bs->cfg.dir, hdr.prefetch ? BITSCRAMBLER_PREFETCH_ENABLED : BITSCRAMBLER_PREFETCH_DISABLED);
bitscrambler_ll_set_eof_mode(bs->hw, bs->cfg.dir, hdr.eof_on);
bitscrambler_ll_set_tailing_bits(bs->hw, bs->cfg.dir, hdr.trailing_bits);
//fixed options
bitscrambler_ll_set_dummy_mode(bs->hw, bs->cfg.dir, BITSCRAMBLER_DUMMY_MODE_DUMMY);
bitscrambler_ll_set_halt_mode(bs->hw, bs->cfg.dir, BITSCRAMBLER_HALT_IGNORE_WRITES);
//enable loopback mode if requested
bitscrambler_ll_enable_loopback(bs->hw, bs->loopback);
return ESP_OK;
}
esp_err_t bitscrambler_load_lut(bitscrambler_handle_t handle, void *lut, size_t size_bytes)
{
if (!handle || !lut) {
return ESP_ERR_INVALID_ARG;
}
uint32_t *lut_words = (uint32_t*)lut;
bitscrambler_lut_width_t lut_width = bitscrambler_ll_get_lut_width(handle->hw, handle->cfg.dir);
bitscrambler_ll_set_lut_width(handle->hw, handle->cfg.dir, BITSCRAMBLER_LUT_WIDTH_32BIT);
size_t size_words = (size_bytes + 3) / 4;
for (int w = 0; w < size_words; w++) {
bitscrambler_ll_lutmem_write(handle->hw, handle->cfg.dir, w, lut_words[w]);
}
bitscrambler_ll_set_lut_width(handle->hw, handle->cfg.dir, lut_width);
return ESP_OK;
}
esp_err_t bitscrambler_register_extra_clean_up(bitscrambler_handle_t handle, bitscrambler_extra_clean_up_func_t clean_up, void* user_ctx)
{
if (!handle) {
return ESP_ERR_INVALID_ARG;
}
handle->extra_clean_up = clean_up;
handle->clean_up_user_ctx = user_ctx;
return ESP_OK;
}
void bitscrambler_free(bitscrambler_handle_t handle)
{
if (!handle) {
return;
}
disable_clocks(handle);
if (handle->loopback) {
atomic_flag_clear(&tx_in_use);
atomic_flag_clear(&rx_in_use);
} else if (handle->cfg.dir == BITSCRAMBLER_DIR_TX) {
atomic_flag_clear(&tx_in_use);
} else if (handle->cfg.dir == BITSCRAMBLER_DIR_RX) {
atomic_flag_clear(&rx_in_use);
}
if (handle->extra_clean_up) {
handle->extra_clean_up(handle, handle->clean_up_user_ctx);
}
free(handle);
}
esp_err_t bitscrambler_start(bitscrambler_handle_t handle)
{
if (!handle) {
return ESP_ERR_INVALID_ARG;
}
bitscrambler_ll_set_state(handle->hw, handle->cfg.dir, BITSCRAMBLER_SET_STATE_RUN);
return ESP_OK;
}
esp_err_t bitscrambler_reset(bitscrambler_handle_t handle)
{
if (!handle) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret = ESP_OK;
bitscrambler_ll_set_state(handle->hw, handle->cfg.dir, BITSCRAMBLER_SET_STATE_HALT);
//If the halt bit is set, the Bitscrambler should (eventually) go to idle state. If it
//does not, something got stuck.
int timeout = BITSCRAMBLER_RESET_ITERATIONS;
while ((bitscrambler_ll_current_state(handle->hw, handle->cfg.dir) != BITSCRAMBLER_STATE_IDLE) && timeout != 0) {
timeout--;
}
if (timeout == 0) {
ESP_LOGE(TAG, "bitscrambler_reset: Timeout waiting for idle!");
ret = ESP_ERR_TIMEOUT;
}
//Reset the fifos & eof trace ctrs
bitscrambler_ll_reset_fifo(handle->hw, handle->cfg.dir);
bitscrambler_ll_clear_eof_trace(handle->hw, handle->cfg.dir);
return ret;
}
@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/gdma_channel.h"
#include "bitscrambler_soc_specific.h"
// Note: these are indexed by the values of the SOC_BITSCRAMBLER_ATTACH_ defines
// in soc/bitscrambler_peri_select.h
// This map is used by the bitscrambler loopback driver only.
const bitscrambler_periph_desc_t g_bitscrambler_periph_desc[] = {
[SOC_BITSCRAMBLER_ATTACH_LCD_CAM] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0), SOC_GDMA_TRIG_PERIPH_LCD0_BUS},
[SOC_BITSCRAMBLER_ATTACH_GPSPI2] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_SPI, 2), SOC_GDMA_TRIG_PERIPH_SPI2_BUS},
[SOC_BITSCRAMBLER_ATTACH_GPSPI3] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_SPI, 3), SOC_GDMA_TRIG_PERIPH_SPI3_BUS},
[SOC_BITSCRAMBLER_ATTACH_PARL_IO] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_PARLIO, 0), SOC_GDMA_TRIG_PERIPH_PARLIO0_BUS},
[SOC_BITSCRAMBLER_ATTACH_AES] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_AES, 0), SOC_GDMA_TRIG_PERIPH_AES0_BUS},
[SOC_BITSCRAMBLER_ATTACH_SHA] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_SHA, 0), SOC_GDMA_TRIG_PERIPH_SHA0_BUS},
[SOC_BITSCRAMBLER_ATTACH_ADC] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_ADC, 0), SOC_GDMA_TRIG_PERIPH_ADC0_BUS},
[SOC_BITSCRAMBLER_ATTACH_I2S0] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_I2S, 0), SOC_GDMA_TRIG_PERIPH_I2S0_BUS},
[SOC_BITSCRAMBLER_ATTACH_I2S1] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_I2S, 1), SOC_GDMA_TRIG_PERIPH_I2S1_BUS},
[SOC_BITSCRAMBLER_ATTACH_I2S2] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_I2S, 2), SOC_GDMA_TRIG_PERIPH_I2S2_BUS},
[SOC_BITSCRAMBLER_ATTACH_I3C_MST] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_I3C, 0), SOC_GDMA_TRIG_PERIPH_I3C0_BUS},
[SOC_BITSCRAMBLER_ATTACH_UHCI] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0), SOC_GDMA_TRIG_PERIPH_UHCI0_BUS},
[SOC_BITSCRAMBLER_ATTACH_RMT] = {GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_RMT, 0), SOC_GDMA_TRIG_PERIPH_RMT0_BUS},
};
@@ -0,0 +1,268 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "driver/bitscrambler.h"
#include "esp_private/gdma.h"
#include "esp_private/gdma_link.h"
#include "esp_private/bitscrambler.h"
#include "hal/dma_types.h"
#include "bitscrambler_private.h"
#include "bitscrambler_soc_specific.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "esp_cache.h"
#include "esp_dma_utils.h"
const static char *TAG = "bs_loop";
typedef struct {
bitscrambler_t bs;
gdma_channel_handle_t tx_channel; // GDMA TX channel handle
gdma_channel_handle_t rx_channel; // GDMA RX channel handle
gdma_link_list_handle_t tx_link_list; // GDMA TX link list handle, the length of the link is determined by the copy buffer size
gdma_link_list_handle_t rx_link_list; // GDMA RX link list handle, the length of the link is determined by the copy buffer size
SemaphoreHandle_t sema_done;
size_t max_transfer_sz_bytes;
} bitscrambler_loopback_t;
/// @brief make sure bs is indeed the first member of bitscrambler_loopback_t so we can cast it to a bitscrambler_t and safely passed to
// any of the non-loopback bitscrambler functions.
_Static_assert(offsetof(bitscrambler_loopback_t, bs) == 0, "bs needs to be 1st member of bitscrambler_loopback_t");
static void bitscrambler_loopback_free(bitscrambler_loopback_t *bsl);
static esp_err_t bitscrambler_loopback_cleanup(bitscrambler_handle_t bs, void* user_ctx);
static esp_err_t new_dma_channel(const gdma_channel_alloc_config_t *cfg, gdma_channel_handle_t *handle, int bus)
{
esp_err_t ret = ESP_OK;
//Note that there are chips that do not have SOC_GDMA_BUS_* defined, but those chips also do
//not have a BitScrambler.
#ifdef SOC_GDMA_BUS_AHB
if (bus == SOC_GDMA_BUS_AHB || bus == SOC_GDMA_BUS_ANY) {
ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(cfg, handle), TAG, "alloc AHB DMA channel failed");
}
#endif
#ifdef SOC_GDMA_BUS_AXI
if (bus == SOC_GDMA_BUS_AXI) {
ESP_RETURN_ON_ERROR(gdma_new_axi_channel(cfg, handle), TAG, "alloc AXI DMA channel failed");
}
#endif
return ret;
}
static IRAM_ATTR bool trans_done_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
BaseType_t higher_prio_task_awoken = pdFALSE;
bitscrambler_loopback_t *bs = (bitscrambler_loopback_t*)user_data;
xSemaphoreGiveFromISR(bs->sema_done, &higher_prio_task_awoken);
return higher_prio_task_awoken;
}
esp_err_t bitscrambler_loopback_create(bitscrambler_handle_t *handle, int attach_to, size_t max_transfer_sz_bytes)
{
if (!handle) {
return ESP_ERR_INVALID_ARG;
}
if (attach_to < 0 || attach_to > SOC_BITSCRAMBLER_ATTACH_MAX) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret = ESP_OK;
bitscrambler_loopback_t *bs = calloc(1, sizeof(bitscrambler_loopback_t));
if (!bs) {
return ESP_ERR_NO_MEM;
}
//Create the underlying BitScrambler object
bitscrambler_config_t cfg = {
.dir = BITSCRAMBLER_DIR_TX,
.attach_to = attach_to
};
ESP_GOTO_ON_ERROR(bitscrambler_init_loopback(&bs->bs, &cfg), err, TAG, "failed bitscrambler init for loopback");
// register extra cleanup function to free loopback resources
bitscrambler_register_extra_clean_up(&bs->bs, bitscrambler_loopback_cleanup, bs);
bs->sema_done = xSemaphoreCreateBinary();
if (!bs->sema_done) {
goto err;
}
bs->max_transfer_sz_bytes = max_transfer_sz_bytes;
int desc_ct = (max_transfer_sz_bytes + DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED;
int bus = g_bitscrambler_periph_desc[attach_to].bus;
size_t align = (bus == SOC_GDMA_BUS_AXI) ? 8 : 4;
// create DMA link list for TX and RX
gdma_link_list_config_t dma_link_cfg = {
.buffer_alignment = 4,
.item_alignment = align,
.num_items = desc_ct,
};
ESP_GOTO_ON_ERROR(gdma_new_link_list(&dma_link_cfg, &bs->tx_link_list), err, TAG, "failed to create TX link list");
ESP_GOTO_ON_ERROR(gdma_new_link_list(&dma_link_cfg, &bs->rx_link_list), err, TAG, "failed to create RX link list");
// create TX channel and RX channel, they should reside in the same DMA pair
gdma_channel_alloc_config_t tx_alloc_config = {
.direction = GDMA_CHANNEL_DIRECTION_TX,
.flags.reserve_sibling = 1,
};
ESP_GOTO_ON_ERROR(new_dma_channel(&tx_alloc_config, &bs->tx_channel, bus), err, TAG, "failed to create GDMA TX channel");
gdma_channel_alloc_config_t rx_alloc_config = {
.direction = GDMA_CHANNEL_DIRECTION_RX,
.sibling_chan = bs->tx_channel,
};
ESP_GOTO_ON_ERROR(new_dma_channel(&rx_alloc_config, &bs->rx_channel, bus), err, TAG, "failed to create GDMA RX channel");
gdma_connect(bs->rx_channel, g_bitscrambler_periph_desc[attach_to].dma_trigger);
gdma_connect(bs->tx_channel, g_bitscrambler_periph_desc[attach_to].dma_trigger);
gdma_strategy_config_t gdma_strategy_conf = {
.auto_update_desc = true,
.owner_check = false,
};
gdma_apply_strategy(bs->rx_channel, &gdma_strategy_conf);
gdma_apply_strategy(bs->tx_channel, &gdma_strategy_conf);
gdma_rx_event_callbacks_t rx_cbs = {
.on_recv_eof = trans_done_cb,
};
gdma_register_rx_event_callbacks(bs->rx_channel, &rx_cbs, bs);
*handle = (bitscrambler_handle_t)bs;
return ESP_OK;
err:
bitscrambler_loopback_free(bs);
free(bs);
return ret;
}
static void bitscrambler_loopback_free(bitscrambler_loopback_t *bsl)
{
if (bsl->rx_channel) {
gdma_disconnect(bsl->rx_channel);
gdma_del_channel(bsl->rx_channel);
}
if (bsl->tx_channel) {
gdma_disconnect(bsl->tx_channel);
gdma_del_channel(bsl->tx_channel);
}
if (bsl->tx_link_list) {
gdma_del_link_list(bsl->tx_link_list);
}
if (bsl->rx_link_list) {
gdma_del_link_list(bsl->rx_link_list);
}
if (bsl->sema_done) {
vSemaphoreDelete(bsl->sema_done);
}
}
static esp_err_t bitscrambler_loopback_cleanup(bitscrambler_handle_t bs, void* user_ctx)
{
bitscrambler_loopback_t *bsl = (bitscrambler_loopback_t*)user_ctx;
bitscrambler_loopback_free(bsl);
return ESP_OK;
}
/*
A BitScrambler program could theoretically take a bunch of time to run, e.g. when transferring from PSRAM to PSRAM.
However, given that this is a memory copy, it feels stupid to have a 'soft' parameter as a timeout; we do need a timeout,
however, as the BitScrambler program may be buggy and e.g. never read or write anything.
As an upper limit for a timeout, we can assume the backing memory is quad psram @ 20MHz, meaning we have a throughput
of around 10MByte/second. Any BitScrambler program is going to be lots faster than that, simply because it doesn't
have the instructions to delay writing by much. Just for safety, we can add an extra factor of 10 to that, making
us assume a minimum throughput of 1MByte/sec.
*/
#define BS_MIN_BYTES_PER_SEC 1000000
/*
We'll also add a few FreeRTOS ticks to the delay, so tiny data transfers won't have an impossibly short timeout.
*/
#define BS_TIMEOUT_BASE_TICKS 3
esp_err_t bitscrambler_loopback_run(bitscrambler_handle_t bs, void *buffer_in, size_t length_bytes_in, void *buffer_out, size_t length_bytes_out, size_t *bytes_written)
{
//Note that buffer_in and buffer_out are from the perspective of the BitScrambler,
//however tx/rx are from the perspective of the memory. So buffer_in=tx, buffer_out=rx.
esp_err_t ret = ESP_OK;
bitscrambler_loopback_t *bsl = (bitscrambler_loopback_t*)bs;
if (length_bytes_in > bsl->max_transfer_sz_bytes) {
return ESP_ERR_INVALID_SIZE;
}
if (length_bytes_out > bsl->max_transfer_sz_bytes) {
return ESP_ERR_INVALID_SIZE;
}
//Casual check to see if the buffer is aligned to cache requirements.
esp_dma_mem_info_t dma_mem_info = {
.dma_alignment_bytes = 4
};
//Note: we know the size of the data, but not of the buffer that contains it, so we set length=0.
if (!esp_dma_is_buffer_alignment_satisfied(buffer_in, 0, dma_mem_info)) {
ESP_LOGE(TAG, "buffer_in not aligned to DMA requirements");
return ESP_ERR_INVALID_ARG;
}
if (!esp_dma_is_buffer_alignment_satisfied(buffer_out, 0, dma_mem_info)) {
ESP_LOGE(TAG, "buffer_out not aligned to DMA requirements");
return ESP_ERR_INVALID_ARG;
}
gdma_reset(bsl->rx_channel);
gdma_reset(bsl->tx_channel);
bitscrambler_reset(bs);
// mount in and out buffer to the DMA link list
gdma_buffer_mount_config_t in_buf_mount_config = {
.buffer = buffer_in,
.length = length_bytes_in,
.flags = {
.mark_eof = true,
.mark_final = true,
}
};
gdma_link_mount_buffers(bsl->tx_link_list, 0, &in_buf_mount_config, 1, NULL);
gdma_buffer_mount_config_t out_buf_mount_config = {
.buffer = buffer_out,
.length = length_bytes_out,
.flags = {
.mark_eof = false,
.mark_final = true,
}
};
gdma_link_mount_buffers(bsl->rx_link_list, 0, &out_buf_mount_config, 1, NULL);
//Note: we add the ESP_CACHE_MSYNC_FLAG_UNALIGNED flag for now as otherwise esp_cache_msync will complain about
//the size not being aligned... we miss out on a check to see if the address is aligned this way. This needs to
//be improved, but potentially needs a fix in esp_cache_msync not to check the size.
esp_cache_msync(buffer_in, length_bytes_in, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
gdma_start(bsl->rx_channel, gdma_link_get_head_addr(bsl->rx_link_list));
gdma_start(bsl->tx_channel, gdma_link_get_head_addr(bsl->tx_link_list));
bitscrambler_start(bs);
int timeout_ms = (length_bytes_out + length_bytes_in) / (BS_MIN_BYTES_PER_SEC / 1000);
int timeout = pdMS_TO_TICKS(timeout_ms) + BS_TIMEOUT_BASE_TICKS;
if (!xSemaphoreTake(bsl->sema_done, timeout)) {
gdma_reset(bsl->rx_channel);
gdma_reset(bsl->tx_channel);
bitscrambler_reset(bs);
ESP_LOGE(TAG, "bitscrambler_loopback_run: timed out waiting for BitScrambler program to complete!");
ret = ESP_ERR_TIMEOUT;
}
esp_cache_msync(buffer_out, length_bytes_out, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
if (bytes_written) {
*bytes_written = gdma_link_count_buffer_size_till_eof(bsl->rx_link_list, 0);
}
return ret;
}
@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#include "hal/bitscrambler_ll.h"
#include "esp_private/bitscrambler.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct bitscrambler_t bitscrambler_t;
struct bitscrambler_t {
bitscrambler_config_t cfg;
bitscrambler_dev_t *hw;
bitscrambler_extra_clean_up_func_t extra_clean_up; // Optional extra clean-up function
void* clean_up_user_ctx; // User context for extra clean-up function
bool loopback; //true if this is a loopback bitscrambler, i.e. the RX channel is also claimed
};
esp_err_t bitscrambler_init_loopback(bitscrambler_handle_t handle, const bitscrambler_config_t *config);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_private/gdma.h"
#include "soc/bitscrambler_peri_select.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
gdma_trigger_t dma_trigger;
int bus;
} bitscrambler_periph_desc_t;
extern const bitscrambler_periph_desc_t g_bitscrambler_periph_desc[];
#ifdef __cplusplus
}
#endif