Check. From what I know, the world controller is designed to stop the 'user' program from accessing all that - see the repo I linked earlier for examples.
It seems like there is an undocumented possibility to write/read any memory (including non-existent one) and read\write all 0x600dxxxx memory region. Currently I am conducting tests to see if WCL is affected by this.
BTW Here is the code to play with, function int bkdma_exec() performs these specific operations.
Code: Select all
// This code demonstrates a "backup" DMA engine use on ESP32-S3.
//
// This peripheral is used to copy (backup) data from MMIO registers
// (both contiguous blocks and fragments) into SRAM and back. It has nothing to do with GDMA
//
// This code and information here are experimental
//
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
extern int ets_printf(const char *, ... );
// BK DMA peripheral registers:
//
// The BK DMA MMIO region starts at address 0x6001A000 and goes at least up to 0x6001A028
// Access to this region is only possible via 32-bit aligned operations,
// so all registers are accessed as uint32_t
// Main control/configuration register BKDMA_CMD_CFG_REG
//
#define BKDMA_CMD_CFG_REG ((volatile uint32_t *)0x6001a000)
# define BKDMA_CC_MODE_BITMAP 8 // Enable "bitmap" mode
# define BKDMA_CC_CMD_START 0x20000000 // Start DMA
# define BKDMA_CC_MODE_MMIO2RAM 0x40000000 // Transfer direction
# define BKDMA_CC_CMD_LATCH 0x80000000 // Probably: latch config into internal shadow registers
// Addresses: MMIO and SRAM for data transfer
//
#define BKDMA_MMIO_ADDR_REG ((volatile uint32_t *)0x6001a004) // address like 0x600xxxxx
#define BKDMA_SRAM_ADDR_REG ((volatile uint32_t *)0x6001a008) // regular RAM address
// "Bitmap" mode: not fully studied/tested, so not used here.
//
// Probably: 128 bits across 4 registers define which MMIO registers inside a 4 KB window to read.
// Each bit corresponds to one 32-bit word: 128 bits × 32 bytes = 4 KB (one memory page). Or may be not :)
//
#define BKDMA_BITMAP0_REG ((volatile uint32_t *)0x6001a00c) // bits 0..31
#define BKDMA_BITMAP1_REG ((volatile uint32_t *)0x6001a010) // bits 32..63
#define BKDMA_BITMAP2_REG ((volatile uint32_t *)0x6001a014) //
#define BKDMA_BITMAP3_REG ((volatile uint32_t *)0x6001a018) // bits ..127
// Status register (contains the IDLE bit)
//
#define BKDMA_STATUS_REG ((volatile uint32_t *)0x6001a01c)
# define BKDMA_S_IDLE 1 // LSB: 1 = IDLE, 0 = BUSY
// Second command register: purpose unclear, but looks like an "enable"
// Automatically resets after operation completes
//
#define BKDMA_CMD2_REG ((volatile uint32_t *)0x6001a028)
# define BKDMA_C2_EN 0x00000001
// This register is documented in the ESP32-S3 TRM: System Configuration Register
// (TRM v1.7, section "4.3.5.1 Module/Peripheral Address Mapping")
// Used to enable/disable clocking for the backup DMA peripheral
//
#define SYSTEM_PERIP_CLK_EN1_REG ((volatile uint32_t *)0x600c001c)
# define SYSTEM_PERI_BACKUP_CLK_EN 0x00000001
// Copy a memory block between MMIO registers and SRAM
//
// Example: copy 0x6003500 ... 0x6003510 into a buffer:
// bkdma_exec((void *)0x6003500, my_buf, 4, true);
//
// Example: write buffer into 0x6003500 ... 0x6003510
// bkdma_exec((void *)0x6003500, my_buf, 4, false);
//
// NOTE1: does not raise exceptions or generate interrupts, even if `ram` address is invalid
// NOTE2: if `mmio` is not within 0x6xxxxxxx range, the function effectively behaves like
// a "special" memset(mmio, 0, count * 4). No exceptions are raised.
//
int bkdma_exec(void *mmio, // `mmio` : MMIO address, e.g. 0x60035000
void *ram, // `ram` : SRAM address (e.g. static buffer)
uint8_t count, // `count` : number of 32-bit words
bool mmio2ram // `mmio2ram` : true = MMIO→SRAM, false = SRAM→MMIO
) {
uint32_t tmp;
// Parameter validation: alignment, non-zero size
//
if (mmio == NULL ||
ram == NULL ||
count == 0 ||
((uintptr_t)mmio & 3) != 0 ||
((uintptr_t)ram & 3) != 0) {
ets_printf("bkdma_exec(): bad count / bad address / bad alignment: %x, %x, %u\r\n",
(unsigned int)mmio, (unsigned int)ram, count);
return -1;
}
// Enable BK DMA clock
*SYSTEM_PERIP_CLK_EN1_REG = *SYSTEM_PERIP_CLK_EN1_REG | SYSTEM_PERI_BACKUP_CLK_EN;
// Configure "simple" mode (no bitmap)
*BKDMA_CMD_CFG_REG = *BKDMA_CMD_CFG_REG & ~BKDMA_CC_MODE_BITMAP;
// Set transfer direction (MMIO→SRAM or SRAM→MMIO)
if (mmio2ram)
tmp = *BKDMA_CMD_CFG_REG | BKDMA_CC_MODE_MMIO2RAM;
else
tmp = *BKDMA_CMD_CFG_REG & ~BKDMA_CC_MODE_MMIO2RAM;
*BKDMA_CMD_CFG_REG = tmp;
// Write MMIO and SRAM addresses
*BKDMA_MMIO_ADDR_REG = (uintptr_t)mmio;
*BKDMA_SRAM_ADDR_REG = (uintptr_t)ram;
// Set transfer size (in 32-bit words), 10-bit field → up to ~4 KB
*BKDMA_CMD_CFG_REG = (*BKDMA_CMD_CFG_REG & 0xE007FFFF) | ((count << 19) & 0x1FF80000);
// Prepare and start:
// Naming might be off - I basically made it up based on observed behavior.
// Well, not entirely made up - reverse-engineered the logic a bit, but still could be wrong.
// Doesn’t affect functionality though.
// I don’t know exactly which bit starts the DMA, but to trigger it,
// all three writes must be done in this exact order.
//
*BKDMA_CMD2_REG = *BKDMA_CMD2_REG | BKDMA_C2_EN;
*BKDMA_CMD_CFG_REG = *BKDMA_CMD_CFG_REG | BKDMA_CC_CMD_LATCH;
*BKDMA_CMD_CFG_REG = *BKDMA_CMD_CFG_REG | BKDMA_CC_CMD_START;
// Wait for completion (IDLE)
do { /* spin */ } while ((*BKDMA_STATUS_REG & BKDMA_S_IDLE) == 0);
// Stop and "de-initialize"
*BKDMA_CMD_CFG_REG = *BKDMA_CMD_CFG_REG & ~BKDMA_CC_CMD_START;
*BKDMA_CMD_CFG_REG = *BKDMA_CMD_CFG_REG & ~BKDMA_CC_CMD_LATCH;
// Disable BK DMA clock
*SYSTEM_PERIP_CLK_EN1_REG = *SYSTEM_PERIP_CLK_EN1_REG & (~SYSTEM_PERI_BACKUP_CLK_EN);
// Done!
return 0;
}