feat(driver): BitScrambler support

This adds an assembler for the BitScrambler assembly language,
plus unit tests for it. It also adds the loopback driver,
which can do BitScrambler operations on memory-to-memory
transfers. Documentation is also included.
This commit is contained in:
Jeroen Domburg
2023-12-06 15:13:45 +08:00
parent cc8bef395e
commit a88e719e33
74 changed files with 3419 additions and 2 deletions

View File

@@ -0,0 +1,29 @@
idf_build_get_property(target IDF_TARGET)
set(srcs)
set(include_dirs)
set(priv_requires)
set(my_priv_requires "soc" "hal" "esp_hw_support" "esp_mm")
if(CONFIG_SOC_BITSCRAMBLER_SUPPORTED)
list(APPEND srcs "bitscrambler.c" "bitscrambler_loopback.c")
list(APPEND include_dirs "include")
endif()
# Note that (according to the docs) "The values of REQUIRES and PRIV_REQUIRES
# should not depend on any configuration choices (CONFIG_xxx macros)." We work
# around that by setting the actual priv_requires value in the target checks,
# rather than make it depend on CONFIG_SOC_BITSCRAMBLER_SUPPORTED.
if(target STREQUAL "esp32p4")
list(APPEND srcs "bitscrambler_esp32p4.c")
set(priv_requires ${my_priv_requires})
endif()
idf_component_register(SRCS ${srcs}
PRIV_REQUIRES ${priv_requires}
INCLUDE_DIRS ${include_dirs}
PRIV_INCLUDE_DIRS "priv_include"
)

View File

@@ -0,0 +1,300 @@
/*
* SPDX-FileCopyrightText: 2024 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 "bitscrambler_loopback_private.h"
#include "soc/soc.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 9 //length of one instruction in 32-bit words as defined by HW
// 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;
}
void bitscrambler_free(bitscrambler_handle_t handle)
{
disable_clocks(handle);
if (handle->loopback) {
atomic_flag_clear(&tx_in_use);
atomic_flag_clear(&rx_in_use);
bitscrambler_loopback_free(handle);
} 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);
}
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;
}

View File

@@ -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},
};

View File

@@ -0,0 +1,273 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include "driver/bitscrambler.h"
#include "bitscrambler_private.h"
#include "bitscrambler_loopback_private.h"
#include "esp_private/gdma.h"
#include "hal/dma_types.h"
#include "hal/cache_ll.h"
#include "hal/gdma_ll.h"
#include "bitscrambler_soc_specific.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_check.h"
#include "soc/ahb_dma_struct.h"
#include "esp_heap_caps.h"
#include "esp_cache.h"
#include "esp_dma_utils.h"
const static char *TAG = "bs_loop";
//Note: given that the first member is a bitscrambler_t, this can be safely passed to
//any of the non-loopback bitscrambler functions.
typedef struct {
bitscrambler_t bs;
dma_descriptor_t *tx_desc_link; // descriptor link list, the length of the link is determined by the copy buffer size
dma_descriptor_t *rx_desc_link; // descriptor link list, the length of the link is determined by the copy buffer size
gdma_channel_handle_t tx_channel; // GDMA TX channel handle
gdma_channel_handle_t rx_channel; // GDMA RX channel handle
SemaphoreHandle_t sema_done;
size_t max_transfer_sz_bytes;
} bitscrambler_loopback_t;
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)
{
///make sure bs is indeed the first member of bitscrambler_loopback_t so we can cast it to a bitscrambler_t
_Static_assert(offsetof(bitscrambler_loopback_t, bs) == 0, "bs needs to be 1st member of bitscrambler_loopback_t");
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");
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;
uint32_t caps = (bus == SOC_GDMA_BUS_AXI) ? MALLOC_CAP_DMA_DESC_AXI : MALLOC_CAP_DMA_DESC_AHB;
size_t align = (bus == SOC_GDMA_BUS_AXI) ? 8 : 4;
bs->rx_desc_link = heap_caps_aligned_calloc(align, desc_ct, sizeof(dma_descriptor_t), caps);
bs->tx_desc_link = heap_caps_aligned_calloc(align, desc_ct, sizeof(dma_descriptor_t), caps);
if (!bs->rx_desc_link || !bs->tx_desc_link) {
ret = ESP_ERR_NO_MEM;
goto err;
}
// create TX channel and RX channel, they should reside in the same DMA pair
gdma_channel_alloc_config_t tx_alloc_config = {
.flags.reserve_sibling = 1,
.direction = GDMA_CHANNEL_DIRECTION_TX,
};
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->bs);
free(bs);
return ret;
}
//note this is never called directly; bitscrambler_free calls this to clear
//the loopback-specific things of a loopback bitscrambler.
//bitscrambler_loopback_create also calls this in an error situation, so
//we should only delete not-NULL members.
void bitscrambler_loopback_free(bitscrambler_handle_t bs)
{
bitscrambler_loopback_t *bsl = (bitscrambler_loopback_t*)bs;
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->sema_done) {
vSemaphoreDelete(bsl->sema_done);
}
free(bsl->rx_desc_link);
free(bsl->tx_desc_link);
}
static int fill_dma_links(dma_descriptor_t *link, void *buffer, size_t len_bytes, int set_eof)
{
uint8_t *buffer_p = (uint8_t*)buffer;
int link_ct = 0;
for (int p = 0; p < len_bytes; p += DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED) {
int seg_len = len_bytes - p;
if (seg_len > DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED) {
seg_len = DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED;
}
link[link_ct].dw0.size = seg_len;
link[link_ct].dw0.length = seg_len;
link[link_ct].dw0.err_eof = 0;
link[link_ct].dw0.suc_eof = 0;
link[link_ct].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
link[link_ct].buffer = &buffer_p[p];
link[link_ct].next = &link[link_ct + 1];
link_ct++;
}
link[link_ct - 1].next = NULL; //fix last entry to end transaction
if (set_eof) {
link[link_ct - 1].dw0.suc_eof = 1;
}
return link_ct;
}
/*
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);
int link_ct_in = fill_dma_links(bsl->tx_desc_link, buffer_in, length_bytes_in, 1);
int link_ct_out = fill_dma_links(bsl->rx_desc_link, buffer_out, length_bytes_out, 0);
//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(bsl->rx_desc_link, link_ct_out * sizeof(dma_descriptor_t), ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
esp_cache_msync(bsl->tx_desc_link, link_ct_in * sizeof(dma_descriptor_t), ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
esp_cache_msync(buffer_in, length_bytes_in, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
gdma_start(bsl->rx_channel, (intptr_t)bsl->rx_desc_link);
gdma_start(bsl->tx_channel, (intptr_t)bsl->tx_desc_link);
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) {
size_t l = 0;
for (int i = 0; i < link_ct_out; i++) {
l += bsl->rx_desc_link[i].dw0.length;
if (bsl->rx_desc_link[i].dw0.suc_eof) {
break;
}
}
*bytes_written = l;
}
return ret;
}

View File

@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
//This file contains private functions for interop between bitscrambler.c
//and bitscrambler_loopback.c.
#pragma once
#include "bitscrambler_private.h"
#ifdef __cplusplus
extern "C" {
#endif
void bitscrambler_loopback_free(bitscrambler_handle_t bs);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
//This file contains private functions for interop between bitscrambler.c
//and bitscrambler_loopback.c.
#pragma once
#include <stdbool.h>
#include "soc/bitscrambler_peri_select.h"
#include "hal/bitscrambler_ll.h"
typedef struct bitscrambler_t bitscrambler_t;
struct bitscrambler_t {
bitscrambler_config_t cfg;
bitscrambler_dev_t *hw;
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);

View File

@@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#include "hal/bitscrambler_types.h"
#include "soc/bitscrambler_peri_select.h"
#ifdef __cplusplus
extern "C" {
#endif
#define BITSCRAMBLER_PROGRAM(VAR, NAME) extern const uint8_t VAR[] asm("_binary_bitscrambler_program_" NAME "_start")
typedef struct bitscrambler_t *bitscrambler_handle_t;
/**
* @brief BitScrambler configuration
*/
typedef struct {
bitscrambler_direction_t dir; /*!< Direction (tx or rx) */
int attach_to; /*!< Peripheral to attach to. One of SOC_BITSCRAMBLER_ATTACH_. */
} bitscrambler_config_t;
/**
* @brief Allocate BitScrambler handle for a hardware channel
*
* @param config Configuration for requested BitScrambler
* @param[out] handle BitScrambler controller handle
*
* @return
* - ESP_OK
* - ESP_ERR_NO_MEM: No memory available
* - ESP_ERR_NOT_FOUND: No free hardware channel available
*/
esp_err_t bitscrambler_new(const bitscrambler_config_t *config, bitscrambler_handle_t *handle);
/**
* @brief Free previously allocated BitScrambler handle
*
* @param handle Previously allocated handle
*/
void bitscrambler_free(bitscrambler_handle_t handle);
/**
* @brief Load a BitScrambler binary program into BitScrambler memory
*
* @param handle BitScrambler handle
* @param program Binary program to load
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG: Not a valid or recognized BitScrambler binary, or invalid handle
*/
esp_err_t bitscrambler_load_program(bitscrambler_handle_t handle, const void *program);
/**
* @brief Load data into the Look-Up Table
*
* @param handle BitScrambler handle
* @param lut Data to load
* @param size_bytes Size of the data, in bytes
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG: Invalid handle or lut pointer
*/
esp_err_t bitscrambler_load_lut(bitscrambler_handle_t handle, void *lut, size_t size_bytes);
/**
* @brief Start executing BitScrambler program
*
* @param handle BitScrambler handle
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG: Invalid handle
*/
esp_err_t bitscrambler_start(bitscrambler_handle_t handle);
/**
* @brief Reset BitScrambler program and FIFOs for a new transaction. Note that this does not
* affect the loaded program itself.
*
* @param handle BitScrambler handle
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG: Invalid handle
*/
esp_err_t bitscrambler_reset(bitscrambler_handle_t handle);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/bitscrambler.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Create handle for BitScrambler in loopback mode
*
* @note Use bitscrambler_free to free created handle.
*
* @param[out] handle BitScrambler handle
* @param attach_to Peripheral to attach to. One of SOC_BITSCRAMBLER_ATTACH_. The BitScrambler
* must be attached to some peripheral, even in loopback mode. This peripheral does not
* need to be initialized for the BitScrambler to work. However, it can be initialized
* and will work as normal, with the exception that DMA functionality for this peripheral
* cannot be used.
* @param max_transfer_sz_bytes Maximum transfer size, in bytes, of either the incoming or outgoing data
* fed to bitscrambler_loopback_run.
*
* @returns
* - ESP_OK
* - ESP_ERR_NO_MEM: No memory available
* - ESP_ERR_NOT_FOUND: No free hardware channel available
* - ESP_ERR_INVALID_ARG: Invalid argument passed to function
* - ESP_FAIL: Bitscrambler object creation failed because of some other error
*/
esp_err_t bitscrambler_loopback_create(bitscrambler_handle_t *handle, int attach_to, size_t max_transfer_sz_bytes);
/**
* @brief Run Bitscrambler program on a data buffer
*
* @param bs BitScrambler handle. This BitScrambler should have a program loaded using
* bitscrambler_load_program()
* @param buffer_in Data to feed into the BitScrambler
* @param length_bytes_in Size of the data in buffer_in, in bytes
* @param buffer_out Buffer for BitScrambler to write processed data to
* @param length_bytes_out Size of output buffer
* @param[out] bytes_written Pointer to variable to store the size of actual data written
* to output buffer. Can be NULL if not needed.
*
* @returns
* - ESP_OK
* - ESP_ERR_INVALID_SIZE if a buffer size exceeds max_transfer_sz_bytes
* - ESP_ERR_TIMEOUT if BitScrambler program does not complete
*/
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);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_private/gdma.h"
#include "soc/bitscrambler_peri_select.h"
typedef struct {
gdma_trigger_t dma_trigger;
int bus;
} bitscrambler_periph_desc_t;
extern const bitscrambler_periph_desc_t g_bitscrambler_periph_desc[];

View File

@@ -0,0 +1,18 @@
# target_bitscrambler_add_src
#
# Assemble BitScrambler sources and embed into the application.
function(target_bitscrambler_add_src s_sources)
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
spaces2list(s_sources)
foreach(source ${s_sources})
get_filename_component(source ${source} ABSOLUTE BASE_DIR ${CMAKE_CURRENT_LIST_DIR})
get_filename_component(basename ${source} NAME_WE)
set(ps_output ${CMAKE_CURRENT_BINARY_DIR}/${basename}.bsbin)
idf_build_get_property(python PYTHON)
idf_build_get_property(idf_path IDF_PATH)
add_custom_command(OUTPUT ${ps_output} DEPENDS ${source}
COMMAND ${python} ${idf_path}/tools/bsasm.py ${source} ${ps_output})
target_add_binary_data(${COMPONENT_LIB} ${ps_output} BINARY RENAME_TO bitscrambler_program_${basename})
endforeach()
endif()
endfunction()

View File

@@ -0,0 +1,5 @@
components/esp_driver_bitscrambler/test_apps/bitscrambler:
disable:
- if: SOC_BITSCRAMBLER_SUPPORTED != 1
depends_components:
- esp_driver_bitscrambler

View File

@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.16)
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_bitscrambler)

View File

@@ -0,0 +1,2 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |

View File

@@ -0,0 +1,18 @@
set(srcs "test_app_main.c")
if(CONFIG_SOC_BITSCRAMBLER_SUPPORTED)
list(APPEND srcs "test_bitscrambler.c")
endif()
set(priv_requires
unity
esp_driver_bitscrambler
)
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "."
PRIV_REQUIRES ${priv_requires}
WHOLE_ARCHIVE TRUE)
target_bitscrambler_add_src("timeout.bsasm")
target_bitscrambler_add_src("trivial.bsasm")

View File

@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "unity.h"
#include "unity_test_utils.h"
#include "esp_heap_caps.h"
#include "sdkconfig.h"
#define TEST_MEMORY_LEAK_THRESHOLD (400)
void setUp(void)
{
unity_utils_record_free_mem();
}
void tearDown(void)
{
unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD);
}
void app_main(void)
{
unity_run_menu();
}

View File

@@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "unity.h"
#include "unity_test_utils.h"
#include "driver/bitscrambler.h"
#include "driver/bitscrambler_loopback.h"
#include "esp_dma_utils.h"
BITSCRAMBLER_PROGRAM(bitscrambler_program_trivial, "trivial");
BITSCRAMBLER_PROGRAM(bitscrambler_program_timeout, "timeout");
TEST_CASE("Basic BitScrambler I/O", "[bs]")
{
int len = 0x4010;
uint8_t *data_in = heap_caps_aligned_calloc(8, 1, len, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
uint8_t *data_out = heap_caps_aligned_calloc(8, 1, len, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(data_in);
TEST_ASSERT_NOT_NULL(data_out);
for (int i = 0; i < len; i++) {
data_in[i] = (uint8_t)rand();
data_out[i] = 0xFF;
}
bitscrambler_handle_t bs;
TEST_ESP_OK(bitscrambler_loopback_create(&bs, SOC_BITSCRAMBLER_ATTACH_I2S0, len));
TEST_ESP_OK(bitscrambler_load_program(bs, bitscrambler_program_trivial));
size_t res_len;
TEST_ESP_OK(bitscrambler_loopback_run(bs, data_in, len, data_out, len, &res_len));
bitscrambler_free(bs);
TEST_ASSERT_EQUAL_HEX8_ARRAY(data_in, data_out, len);
free(data_in);
free(data_out);
}
TEST_CASE("Timeout on stuck program", "[bs]")
{
int len = 4096 * 10;
uint8_t *data_in = heap_caps_aligned_calloc(8, 1, len, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
uint8_t *data_out = heap_caps_aligned_calloc(8, 1, len, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
bitscrambler_handle_t bs;
TEST_ESP_OK(bitscrambler_loopback_create(&bs, SOC_BITSCRAMBLER_ATTACH_I2S0, len));
TEST_ESP_OK(bitscrambler_load_program(bs, bitscrambler_program_timeout));
esp_err_t err = bitscrambler_loopback_run(bs, data_in, len, data_out, len, NULL);
TEST_ASSERT(err == ESP_ERR_TIMEOUT);
bitscrambler_free(bs);
free(data_in);
free(data_out);
}

View File

@@ -0,0 +1,10 @@
cfg trailing_bytes 0 #End program as soon as the input EOFs.
cfg prefetch true #We expect M0/M1 to be filled
cfg lut_width_bits 8 #Not really applicable here
#Does nothing, so host logic will go into timeout
loop:
read 0,
write 0,
jmp loop

View File

@@ -0,0 +1,11 @@
# Example bitscrambler program. Does nothing but forward all bytes.
cfg trailing_bytes 12 # Let M0/M1 empty when EOF on input is found
cfg prefetch true # We expect M0/M1 to be filled
cfg lut_width_bits 8 # Not really applicable here
loop:
set 0..31 0..31,
write 32,
read 32,
jmp loop

View File

@@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32p4
@pytest.mark.generic
def test_bitscrambler(dut: Dut) -> None:
dut.run_all_single_board_cases()

View File

@@ -0,0 +1 @@
# CONFIG_ESP_TASK_WDT_EN is not set