commit d9752ca39a08f395f61f0f83b1083db0a528dff4 Author: jess Date: Sat Mar 28 03:03:52 2026 -0700 Init. Blob exploits for slot4 on the s3 inspired by esp-open-mac. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81f7317 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/ +sdkconfig +sdkconfig.old +managed_components/ +dependencies.lock diff --git a/ADC/bubbles-tx/CMakeLists.txt b/ADC/bubbles-tx/CMakeLists.txt new file mode 100644 index 0000000..0b433ed --- /dev/null +++ b/ADC/bubbles-tx/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(bubbles) diff --git a/ADC/bubbles-tx/main/CMakeLists.txt b/ADC/bubbles-tx/main/CMakeLists.txt new file mode 100644 index 0000000..3f56e3f --- /dev/null +++ b/ADC/bubbles-tx/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "main.c" "hijack_tx.c" + INCLUDE_DIRS "." + REQUIRES esp_wifi esp_timer nvs_flash esp_netif esp_event esp_rom heap driver +) diff --git a/ADC/bubbles-tx/main/hijack_tx.c b/ADC/bubbles-tx/main/hijack_tx.c new file mode 100644 index 0000000..4a07950 --- /dev/null +++ b/ADC/bubbles-tx/main/hijack_tx.c @@ -0,0 +1,152 @@ +/* + * hijack_tx -- DMA hijack TX for ESP32-S3. + * + * Fires a blob TX on slot 0 with a dummy frame sized to match the real + * frame, then swaps the DMA descriptor's buffer pointer before hardware + * reads it. The blob's PLCP setup uses the correct length because the + * dummy is the same size. No PLCP patching race. + * + * Sequence: + * 1. Build dummy frame matching target length + * 2. Fire blob TX (esp_wifi_80211_tx) on slot 0 + * 3. Spin-wait for slot 0 TXQ_CTRL bits [31:30] set + * 4. Extract DMA descriptor from TXQ_CTRL[19:0] + * 5. Swap desc->buf to our frame + * 6. Wait for completion (bits [31:30] clear) + * 7. Restore descriptor to prevent blob crash + */ + +#include "hijack_tx.h" + +#include +#include +#include "esp_heap_caps.h" +#include "esp_wifi.h" +#include "esp_mac.h" +#include "esp_timer.h" +#include "esp_rom_lldesc.h" +#include "xtensa/core-macros.h" + +#define REG32(addr) (*(volatile uint32_t *)(addr)) + +#define S0_TXQ_CTRL 0x60033D08 +#define S0_PMD 0x60034320 + +#define MAX_FRAME_SIZE 512 +#define DMA_BASE 0x3FC00000 + +static uint8_t *s_dummy = NULL; +static uint8_t *s_frame_buf = NULL; +static uint8_t s_own_mac[6]; + +static uint32_t s_ok_count = 0; +static uint32_t s_miss_count = 0; +static uint32_t s_fail_count = 0; + +void hijack_init(void) +{ + if (s_frame_buf) return; + + s_frame_buf = heap_caps_calloc(1, MAX_FRAME_SIZE, + MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + s_dummy = heap_caps_calloc(1, MAX_FRAME_SIZE, + MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + assert(s_frame_buf); + assert(s_dummy); + + esp_read_mac(s_own_mac, ESP_MAC_WIFI_STA); +} + +static void build_dummy(uint16_t len) +{ + memset(s_dummy, 0, len); + s_dummy[0] = 0xD0; + memcpy(s_dummy + 4, "\xFF\xFF\xFF\xFF\xFF\xFF", 6); + memcpy(s_dummy + 10, s_own_mac, 6); + memcpy(s_dummy + 16, s_own_mac, 6); + s_dummy[24] = 127; + s_dummy[25] = 0x52; + s_dummy[26] = 0x4B; + s_dummy[27] = 0x59; +} + +int IRAM_ATTR hijack_send(const uint8_t *frame, uint16_t len) +{ + if (!s_frame_buf) return -1; + if (len == 0 || len > MAX_FRAME_SIZE) return -1; + + memcpy(s_frame_buf, frame, len); + + build_dummy(len); + + esp_wifi_80211_tx(WIFI_IF_STA, s_dummy, len, false); + + uint32_t spin_start = xthal_get_ccount(); + uint32_t spin_limit = 5000 * 240; + lldesc_t *desc = NULL; + volatile const uint8_t *orig_buf = NULL; + uint16_t orig_size = 0; + uint16_t orig_len = 0; + + while ((xthal_get_ccount() - spin_start) < spin_limit) { + uint32_t ctrl = REG32(S0_TXQ_CTRL); + if (ctrl & 0xC0000000) { + desc = (lldesc_t *)(DMA_BASE | (ctrl & 0x000FFFFF)); + + orig_buf = desc->buf; + orig_size = desc->size; + orig_len = desc->length; + + /* + * Overwrite RKY header + audio payload in the blob's own buffer. + * Bytes 0-27 (MAC header, OUI) are already correct from the dummy. + * Write frame_seq (offset 28) first as a 32-bit store — DMA reads + * linearly from offset 0, so we win the race on byte 28+ if we + * skip the header the dummy already has right. + */ + volatile uint8_t *dst = (volatile uint8_t *)orig_buf; + memcpy((void *)(dst + 28), s_frame_buf + 28, len - 28); + __asm__ __volatile__("memw" ::: "memory"); + + break; + } + } + + if (!desc) { + s_miss_count++; + return -1; + } + + int result = -1; + int64_t poll_end = esp_timer_get_time() + 200000; + while (esp_timer_get_time() < poll_end) { + if ((REG32(S0_TXQ_CTRL) & 0xC0000000) == 0) { + uint32_t pmd = REG32(S0_PMD); + uint8_t pmd_code = (pmd >> 12) & 0xF; + result = (pmd_code == 0 || pmd_code == 4) ? 0 : (int)pmd_code; + break; + } + } + + desc->buf = orig_buf; + desc->size = orig_size; + desc->length = orig_len; + __asm__ __volatile__("memw" ::: "memory"); + + if (result == 0) + s_ok_count++; + else + s_fail_count++; + + if (((s_ok_count + s_fail_count) % 100) == 0) { + uint32_t swap_us = (xthal_get_ccount() - spin_start) / 240; + printf("hijack: ok=%lu fail=%lu miss=%lu swap=%luus pmd=%d\n", + (unsigned long)s_ok_count, + (unsigned long)s_fail_count, + (unsigned long)s_miss_count, + (unsigned long)swap_us, + result); + } + + return result; +} diff --git a/ADC/bubbles-tx/main/hijack_tx.h b/ADC/bubbles-tx/main/hijack_tx.h new file mode 100644 index 0000000..7bfb836 --- /dev/null +++ b/ADC/bubbles-tx/main/hijack_tx.h @@ -0,0 +1,16 @@ +#ifndef HIJACK_TX_H +#define HIJACK_TX_H + +#include + +/* allocate DMA-capable frame buffer */ +void hijack_init(void); + +/* + * Transmit frame via slot 0 DMA hijack. + * Fires a blob TX, intercepts the DMA descriptor, swaps in our buffer. + * Returns 0 on success, -1 on miss/timeout. + */ +int hijack_send(const uint8_t *frame, uint16_t len); + +#endif diff --git a/ADC/bubbles-tx/main/main.c b/ADC/bubbles-tx/main/main.c new file mode 100644 index 0000000..58dc315 --- /dev/null +++ b/ADC/bubbles-tx/main/main.c @@ -0,0 +1,233 @@ +/* + * Bubbles -- I2S RX to WiFi TX via 802.11 action frames. + * + * Captures stereo audio from an external I2S source (32-bit slots, Philips), + * packs to 24-bit, transmits as RKY protocol action frames at 1000 pps. + * ESP32-S3 is I2S master (drives BCLK, WS). Full-duplex I2S ensures + * clock output on pins even in RX-dominant mode. + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "esp_mac.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "driver/i2s_std.h" + +#define TAG "bubbles" + +static const uint8_t BCAST[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +static const uint8_t OUI_RKY[] = {0x52, 0x4B, 0x59}; + +#define CHANNEL 1 +#define SAMPLE_RATE 48000 +#define SAMPLES_PER_PKT 48 +#define NUM_CHANNELS 2 +#define BYTES_PER_SAMPLE 6 +#define AUDIO_PAYLOAD (SAMPLES_PER_PKT * BYTES_PER_SAMPLE) + +#define TX_PERIOD_US 1000 +#define STATS_INTERVAL_MS 5000 + +#define PIN_BCLK GPIO_NUM_16 +#define PIN_WS GPIO_NUM_15 +#define PIN_DIN GPIO_NUM_18 + +typedef struct __attribute__((packed)) { + uint32_t frame_seq; + uint8_t sub_index; + uint8_t pkt_type; + uint16_t payload_len; + uint16_t burst_width; +} rky_hdr_t; + +#define MAC_HDR_LEN 24 +#define ACTION_HDR_LEN 4 +#define RKY_HDR_LEN sizeof(rky_hdr_t) +#define FRAME_OVERHEAD (MAC_HDR_LEN + ACTION_HDR_LEN + RKY_HDR_LEN) +#define MAX_FRAME_LEN (FRAME_OVERHEAD + AUDIO_PAYLOAD) + +#define I2S_READ_BYTES (SAMPLES_PER_PKT * NUM_CHANNELS * sizeof(int32_t)) + +static i2s_chan_handle_t s_tx_handle; +static i2s_chan_handle_t s_rx_handle; + +static uint8_t s_silence[SAMPLES_PER_PKT * NUM_CHANNELS * sizeof(int32_t)]; + +static void i2s_tx_task(void *arg) +{ + size_t written; + memset(s_silence, 0, sizeof(s_silence)); + while (1) { + i2s_channel_write(s_tx_handle, s_silence, sizeof(s_silence), + &written, portMAX_DELAY); + } +} + +static void i2s_init(void) +{ + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG( + I2S_NUM_0, I2S_ROLE_MASTER); + chan_cfg.dma_desc_num = 6; + chan_cfg.dma_frame_num = SAMPLES_PER_PKT; + + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &s_tx_handle, &s_rx_handle)); + + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG( + I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = PIN_BCLK, + .ws = PIN_WS, + .dout = GPIO_NUM_17, + .din = PIN_DIN, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + std_cfg.slot_cfg.slot_bit_width = I2S_SLOT_BIT_WIDTH_32BIT; + std_cfg.slot_cfg.ws_width = 32; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(s_tx_handle, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(s_rx_handle, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(s_tx_handle)); + ESP_ERROR_CHECK(i2s_channel_enable(s_rx_handle)); + + xTaskCreatePinnedToCore(i2s_tx_task, "i2s_tx", 2048, NULL, 10, NULL, 1); +} + +static void i2s_32_to_packed24(const int32_t *in, uint8_t *out, int n_stereo_frames) +{ + int j = 0; + for (int i = 0; i < n_stereo_frames * NUM_CHANNELS; i++) { + uint32_t s = (uint32_t)in[i]; + out[j++] = (s >> 24) & 0xFF; + out[j++] = (s >> 16) & 0xFF; + out[j++] = (s >> 8) & 0xFF; + } +} + +static int build_frame(uint8_t *buf, const uint8_t *src, + uint32_t seq, const uint8_t *payload, uint16_t plen) +{ + memset(buf, 0, MAC_HDR_LEN); + buf[0] = 0xD0; + memcpy(buf + 4, BCAST, 6); + memcpy(buf + 10, src, 6); + memcpy(buf + 16, src, 6); + + int off = MAC_HDR_LEN; + buf[off++] = 127; + buf[off++] = OUI_RKY[0]; + buf[off++] = OUI_RKY[1]; + buf[off++] = OUI_RKY[2]; + + rky_hdr_t *hdr = (rky_hdr_t *)(buf + off); + hdr->frame_seq = seq; + hdr->sub_index = 0; + hdr->pkt_type = 0; + hdr->payload_len = plen; + hdr->burst_width = 1; + off += RKY_HDR_LEN; + + if (payload && plen > 0) { + memcpy(buf + off, payload, plen); + off += plen; + } + return off; +} + +static void tx_task(void *arg) +{ + uint8_t mac[6]; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + + ESP_LOGI(TAG, "TX: %dkHz packed-24-bit stereo, %d bytes/pkt, %d pps", + SAMPLE_RATE / 1000, AUDIO_PAYLOAD, 1000000 / TX_PERIOD_US); + + uint8_t frame[MAX_FRAME_LEN]; + int32_t i2s_buf[SAMPLES_PER_PKT * NUM_CHANNELS]; + uint8_t packed_buf[AUDIO_PAYLOAD]; + + uint32_t seq = 0; + uint32_t ok = 0, err = 0; + int64_t last_stats = esp_timer_get_time(); + int64_t next_tx = esp_timer_get_time(); + + while (1) { + size_t bytes_read = 0; + esp_err_t rret = i2s_channel_read(s_rx_handle, i2s_buf, + I2S_READ_BYTES, + &bytes_read, + pdMS_TO_TICKS(10)); + if (rret != ESP_OK || bytes_read < I2S_READ_BYTES) + continue; + + int64_t now = esp_timer_get_time(); + if (now < next_tx) + continue; + + next_tx += TX_PERIOD_US; + if (now >= next_tx) + next_tx = now + TX_PERIOD_US; + + i2s_32_to_packed24(i2s_buf, packed_buf, SAMPLES_PER_PKT); + int flen = build_frame(frame, mac, seq, packed_buf, AUDIO_PAYLOAD); + + esp_err_t ret = esp_wifi_80211_tx(WIFI_IF_STA, frame, flen, false); + if (ret == ESP_OK) ok++; + else err++; + + seq++; + + if ((esp_timer_get_time() - last_stats) >= (int64_t)STATS_INTERVAL_MS * 1000) { + float elapsed = (float)STATS_INTERVAL_MS / 1000.f; + ESP_LOGI(TAG, "seq=%lu ok=%lu err=%lu pps=%.0f", + (unsigned long)seq, (unsigned long)ok, (unsigned long)err, + (float)(ok + err) / elapsed); + ok = err = 0; + last_stats = esp_timer_get_time(); + } + } +} + +static void wifi_init(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + + wifi_config_t sta_cfg = {0}; + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_cfg)); + ESP_ERROR_CHECK(esp_wifi_set_protocol(WIFI_IF_STA, + WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N)); + + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_ERROR_CHECK(esp_wifi_set_channel(CHANNEL, WIFI_SECOND_CHAN_NONE)); + ESP_ERROR_CHECK(esp_wifi_config_80211_tx_rate(WIFI_IF_STA, WIFI_PHY_RATE_54M)); + ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); +} + +void app_main(void) +{ + wifi_init(); + i2s_init(); + xTaskCreatePinnedToCore(tx_task, "tx", 8192, NULL, + configMAX_PRIORITIES - 1, NULL, 0); +} diff --git a/ADC/bubbles-tx/sdkconfig.defaults b/ADC/bubbles-tx/sdkconfig.defaults new file mode 100644 index 0000000..b81131e --- /dev/null +++ b/ADC/bubbles-tx/sdkconfig.defaults @@ -0,0 +1,24 @@ +CONFIG_IDF_TARGET="esp32s3" + +CONFIG_FREERTOS_HZ=1000 + +CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1=y +CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0 + +CONFIG_ESP_WIFI_STATIC_TX_BUFFER=y +CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=16 + +CONFIG_ESP_WIFI_IRAM_OPT=y +CONFIG_ESP_WIFI_RX_IRAM_OPT=y +CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y + +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 + +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=n + +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y + +# B + G + N +CONFIG_ESP_WIFI_11B_ENABLED=y diff --git a/ADC/kitty-rx/CMakeLists.txt b/ADC/kitty-rx/CMakeLists.txt new file mode 100644 index 0000000..3e611ed --- /dev/null +++ b/ADC/kitty-rx/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(kitty) diff --git a/ADC/kitty-rx/main/CMakeLists.txt b/ADC/kitty-rx/main/CMakeLists.txt new file mode 100644 index 0000000..5a8560d --- /dev/null +++ b/ADC/kitty-rx/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "main.c" "usb_audio.c" + INCLUDE_DIRS "." + PRIV_REQUIRES esp_wifi nvs_flash esp_netif esp_event esp_timer esp_hw_support soc +) diff --git a/ADC/kitty-rx/main/idf_component.yml b/ADC/kitty-rx/main/idf_component.yml new file mode 100644 index 0000000..8692421 --- /dev/null +++ b/ADC/kitty-rx/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + espressif/usb_device_uac: + version: "^1.0.0" + idf: ">=5.1" diff --git a/ADC/kitty-rx/main/main.c b/ADC/kitty-rx/main/main.c new file mode 100644 index 0000000..c09fbed --- /dev/null +++ b/ADC/kitty-rx/main/main.c @@ -0,0 +1,359 @@ +/* + * Kitty: WiFi RKY audio receiver -> USB Audio Class 2 microphone. + * + * WiFi RX path copied verbatim from lucy/receiver (dual-path: ISR hook + + * promisc callback). Output changed from I2S DMA to ring buffer -> UAC2 mic. + * + * Incoming audio is 24-bit packed (from bubbles TX). Unpacked to 16-bit + * for USB output: top 2 bytes of each 3-byte sample. + * + * Test tone: 1kHz sine injected into ring buffer when no WiFi for 500ms. + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "soc/lldesc.h" +#include "esp_private/wifi_os_adapter.h" +#include "xtensa/xtensa_api.h" +#include "esp_cpu.h" + +#include "ring_buf.h" +#include "usb_audio.h" + +#define TAG "kitty" + +#define CHANNEL 1 +#define SAMPLE_RATE 48000 +#define FRAMES_PER_PKT 48 +#define NUM_CHANNELS 2 +#define PACKED_BYTES 3 + +/* 48 stereo frames * 2ch * 3 bytes = 288 bytes packed audio per packet */ +#define AUDIO_PAYLOAD_SIZE (FRAMES_PER_PKT * NUM_CHANNELS * PACKED_BYTES) +/* 48 stereo frames * 2ch * 2 bytes = 192 bytes after 16-bit truncation */ +#define AUDIO_16BIT_SIZE (FRAMES_PER_PKT * NUM_CHANNELS * 2) + +/* Test tone: 1kHz sine at -12dBFS, injected when no WiFi for >500ms */ +#define TONE_FREQ 1000 +#define TONE_AMPLITUDE 0x1000 +#define TONE_TIMEOUT_US 500000 + +/* WiFi MAC RX DMA registers (ESP32-S3) */ +#define REG_RX_DSCR_LAST (*(volatile uint32_t *)0x60033090) + +/* DMA SRAM address range on S3 */ +#define DMA_SRAM_BASE 0x3FC88000 +#define DMA_SRAM_END 0x3FD00000 +#define DMA_ADDR_MASK 0x000FFFFF +#define DMA_ADDR_OR 0x3FC00000 + +typedef struct __attribute__((packed)) { + uint32_t frame_seq; + uint8_t sub_index; + uint8_t pkt_type; + uint16_t payload_len; + uint16_t burst_width; +} rky_hdr_t; + +#define MAC_HDR_LEN 24 +#define ACTION_HDR_LEN 4 +#define RKY_HDR_LEN sizeof(rky_hdr_t) +#define FRAME_OVERHEAD (MAC_HDR_LEN + ACTION_HDR_LEN + RKY_HDR_LEN) + +static ring_buf_t s_ring; + +static DRAM_ATTR uint8_t s_paired_mac[6]; +static volatile bool s_paired; + +static uint32_t s_stat_isr; +static uint32_t s_stat_prom; +static uint32_t s_stat_dedup; +static uint32_t s_stat_gaps; +static uint32_t s_stat_overruns; +static uint32_t s_stat_isrs; +static volatile uint32_t s_last_seq; + +static volatile int64_t s_last_rx_time; + +/* ---------- 24-bit to 16-bit unpack + ring write ---------- */ + +static inline void IRAM_ATTR unpack_24_to_16_ring(const uint8_t *packed, uint16_t packed_len) +{ + uint32_t n_samples = packed_len / PACKED_BYTES; + uint8_t tmp[AUDIO_16BIT_SIZE]; + + if (n_samples > FRAMES_PER_PKT * NUM_CHANNELS) + n_samples = FRAMES_PER_PKT * NUM_CHANNELS; + + uint32_t j = 0; + for (uint32_t i = 0; i < n_samples; i++) { + /* packed[j]=MSB packed[j+1]=mid packed[j+2]=LSB -> 16-bit LE = {mid, MSB} */ + tmp[i * 2] = packed[j + 1]; + tmp[i * 2 + 1] = packed[j]; + j += PACKED_BYTES; + } + + size_t written = ring_buf_write(&s_ring, tmp, n_samples * 2); + if (written < n_samples * 2) + s_stat_overruns++; + s_last_rx_time = esp_timer_get_time(); +} + +/* ---------- shared ingest ---------- */ + +static inline void IRAM_ATTR ingest_audio(const uint8_t *audio, uint16_t plen, + uint32_t seq, const uint8_t *sa) +{ + /* first-seen pairing */ + if (!s_paired) { + for (int i = 0; i < 6; i++) s_paired_mac[i] = sa[i]; + s_paired = true; + } else { + if (sa[0] != s_paired_mac[0] || sa[1] != s_paired_mac[1] || + sa[2] != s_paired_mac[2] || sa[3] != s_paired_mac[3] || + sa[4] != s_paired_mac[4] || sa[5] != s_paired_mac[5]) return; + } + + /* sequence dedup: both paths call ingest, skip if already seen */ + if (seq == s_last_seq && s_last_seq != 0) { + s_stat_dedup++; + return; + } + + /* gap detection */ + if (s_last_seq != 0 && seq != s_last_seq + 1) + s_stat_gaps++; + s_last_seq = seq; + + /* unpack 24-bit packed to 16-bit and write to ring buffer */ + unpack_24_to_16_ring(audio, plen); +} + +/* ---------- ISR hook ---------- */ + +static DRAM_ATTR xt_handler s_orig_isr; +static DRAM_ATTR void *s_orig_arg; +static DRAM_ATTR int s_orig_intr_num = -1; +static DRAM_ATTR uint32_t s_isr_last_raw; + +static void IRAM_ATTR wifi_isr_hook(void *arg) +{ + s_stat_isrs++; + + uint32_t raw = REG_RX_DSCR_LAST; + if (raw != s_isr_last_raw) { + s_isr_last_raw = raw; + + uint32_t addr = (raw & DMA_ADDR_MASK) | DMA_ADDR_OR; + if (addr >= DMA_SRAM_BASE && addr < DMA_SRAM_END) { + lldesc_t *desc = (lldesc_t *)addr; + + if (desc->owner == 0 && desc->length > FRAME_OVERHEAD) { + const uint8_t *buf = (const uint8_t *)desc->buf; + + if (buf[0] == 0xD0 && + buf[24] == 127 && + buf[25] == 0x52 && buf[26] == 0x4B && buf[27] == 0x59) + { + rky_hdr_t *hdr = (rky_hdr_t *)(buf + MAC_HDR_LEN + ACTION_HDR_LEN); + uint16_t plen = hdr->payload_len; + uint32_t seq = hdr->frame_seq; + const uint8_t *audio = buf + FRAME_OVERHEAD; + + if (plen > desc->length - FRAME_OVERHEAD) + plen = desc->length - FRAME_OVERHEAD; + + if (plen > 0) { + ingest_audio(audio, plen, seq, buf + 10); + s_stat_isr++; + } + } + } + } + } + + /* chain to blob's original ISR */ + if (s_orig_isr) + s_orig_isr(s_orig_arg); +} + +/* patched _set_isr: intercept when blob installs wDev_ProcessFiq */ +static void IRAM_ATTR patched_set_isr(int32_t n, void *f, void *arg) +{ + if (s_orig_intr_num < 0 && f != NULL) { + s_orig_isr = (xt_handler)f; + s_orig_arg = arg; + s_orig_intr_num = n; + xt_set_interrupt_handler(n, wifi_isr_hook, arg); + ESP_EARLY_LOGI(TAG, "captured WiFi ISR: intr=%d handler=%p arg=%p", + (int)n, f, arg); + } else { + xt_set_interrupt_handler(n, (xt_handler)f, arg); + } +} + +/* ---------- promiscuous callback (fallback path) ---------- */ + +static void IRAM_ATTR promisc_cb(void *buf, wifi_promiscuous_pkt_type_t type) +{ + wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)buf; + uint8_t *frame = pkt->payload; + int len = pkt->rx_ctrl.sig_len - 4; + + if (len < (int)FRAME_OVERHEAD) return; + if ((frame[0] & 0xFC) != 0xD0) return; + if (frame[24] != 127) return; + if (frame[25] != 0x52 || frame[26] != 0x4B || frame[27] != 0x59) return; + + rky_hdr_t *hdr = (rky_hdr_t *)(frame + MAC_HDR_LEN + ACTION_HDR_LEN); + uint16_t plen = hdr->payload_len; + uint32_t seq = hdr->frame_seq; + uint8_t *audio = frame + FRAME_OVERHEAD; + + int avail = len - (int)FRAME_OVERHEAD; + if (plen > (uint16_t)avail) plen = (uint16_t)avail; + if (plen == 0) return; + + ingest_audio(audio, plen, seq, frame + 10); + s_stat_prom++; +} + +/* ---------- WiFi init ---------- */ + +static void wifi_init(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* patch _set_isr before esp_wifi_init so we capture the blob's ISR install */ + g_wifi_osi_funcs._set_isr = patched_set_isr; + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_ERROR_CHECK(esp_wifi_set_channel(CHANNEL, WIFI_SECOND_CHAN_NONE)); + + wifi_promiscuous_filter_t filt = { + .filter_mask = WIFI_PROMIS_FILTER_MASK_ALL, + }; + ESP_ERROR_CHECK(esp_wifi_set_promiscuous_filter(&filt)); + ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(promisc_cb)); + ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); +} + +/* ---------- test tone ---------- */ + +static int16_t s_sine_table[FRAMES_PER_PKT]; + +static void tone_table_init(void) +{ + for (int i = 0; i < FRAMES_PER_PKT; i++) + s_sine_table[i] = (int16_t)(TONE_AMPLITUDE * + sinf(2.0f * M_PI * TONE_FREQ * i / SAMPLE_RATE)); +} + +static void tone_task(void *arg) +{ + TickType_t xLastWake = xTaskGetTickCount(); + uint32_t phase = 0; + bool was_toning = false; + + while (1) { + vTaskDelayUntil(&xLastWake, pdMS_TO_TICKS(1)); + + int64_t now = esp_timer_get_time(); + if ((now - s_last_rx_time) < TONE_TIMEOUT_US) { + if (was_toning) { + ESP_LOGI(TAG, "tone: WiFi data resumed, stopping tone"); + was_toning = false; + } + continue; + } + + if (!was_toning) { + ESP_LOGW(TAG, "tone: no WiFi for %dms, injecting 1kHz test tone", + (int)(TONE_TIMEOUT_US / 1000)); + was_toning = true; + phase = 0; + } + + int16_t buf[FRAMES_PER_PKT * NUM_CHANNELS]; + for (int i = 0; i < FRAMES_PER_PKT; i++) { + int16_t s = s_sine_table[(phase + i) % FRAMES_PER_PKT]; + buf[i * 2] = s; + buf[i * 2 + 1] = s; + } + phase = (phase + FRAMES_PER_PKT) % FRAMES_PER_PKT; + + ring_buf_write(&s_ring, (const uint8_t *)buf, sizeof(buf)); + } +} + +/* ---------- stats ---------- */ + +static void stats_task(void *arg) +{ + int64_t last = esp_timer_get_time(); + + while (1) { + vTaskDelay(pdMS_TO_TICKS(5000)); + + int64_t now = esp_timer_get_time(); + float elapsed = (now - last) / 1e6f; + + uint32_t fill = ring_buf_fill(&s_ring); + uint32_t total = s_stat_isr + s_stat_prom; + bool toning = (now - s_last_rx_time) >= TONE_TIMEOUT_US; + + ESP_LOGI(TAG, "isr=%lu prom=%lu dedup=%lu | gaps=%lu over=%lu | ring=%lu/%d | isrs=%lu pps=%.0f%s", + (unsigned long)s_stat_isr, + (unsigned long)s_stat_prom, + (unsigned long)s_stat_dedup, + (unsigned long)s_stat_gaps, + (unsigned long)s_stat_overruns, + (unsigned long)fill, + RING_BUF_SIZE, + (unsigned long)s_stat_isrs, + total / elapsed, + toning ? " [TONE]" : ""); + + s_stat_isr = 0; + s_stat_prom = 0; + s_stat_dedup = 0; + s_stat_gaps = 0; + s_stat_overruns = 0; + s_stat_isrs = 0; + last = now; + } +} + +/* ---------- main ---------- */ + +void app_main(void) +{ + tone_table_init(); + ring_buf_init(&s_ring); + usb_audio_init(&s_ring); + wifi_init(); + + ESP_LOGI(TAG, "rx: wifi ch=%d -> UAC2 mic 48kHz/16/stereo, isr_hook=%s, ring=%d bytes", + CHANNEL, + s_orig_intr_num >= 0 ? "active" : "inactive", + RING_BUF_SIZE); + + xTaskCreatePinnedToCore(stats_task, "stats", 4096, NULL, 5, NULL, 0); + xTaskCreatePinnedToCore(tone_task, "tone", 4096, NULL, 6, NULL, 1); +} diff --git a/ADC/kitty-rx/main/ring_buf.h b/ADC/kitty-rx/main/ring_buf.h new file mode 100644 index 0000000..6d0f2de --- /dev/null +++ b/ADC/kitty-rx/main/ring_buf.h @@ -0,0 +1,84 @@ +/* + * Lock-free SPSC ring buffer for audio transport. + * Producer: WiFi ISR/promisc callback (writes 16-bit truncated samples). + * Consumer: UAC mic callback (reads arbitrary byte counts). + * + * Byte-granularity. Single writer, single reader, no locks. + */ + +#pragma once + +#include +#include +#include +#include "esp_attr.h" + +#define RING_BUF_SIZE (48000 * 4 * 1) /* 1 second of 48kHz 16-bit stereo */ + +typedef struct { + uint8_t buf[RING_BUF_SIZE]; + volatile uint32_t wr; + volatile uint32_t rd; +} ring_buf_t; + +static inline void ring_buf_init(ring_buf_t *rb) +{ + rb->wr = 0; + rb->rd = 0; +} + +static inline uint32_t ring_buf_fill(const ring_buf_t *rb) +{ + uint32_t w = rb->wr; + uint32_t r = rb->rd; + return (w >= r) ? (w - r) : (RING_BUF_SIZE - r + w); +} + +static inline uint32_t ring_buf_free(const ring_buf_t *rb) +{ + return RING_BUF_SIZE - 1 - ring_buf_fill(rb); +} + +/* Write contiguous block. Returns bytes actually written. */ +static inline size_t IRAM_ATTR ring_buf_write(ring_buf_t *rb, const uint8_t *data, size_t len) +{ + uint32_t free = ring_buf_free(rb); + if (len > free) len = free; + if (len == 0) return 0; + + uint32_t w = rb->wr; + uint32_t tail = RING_BUF_SIZE - w; + + if (len <= tail) { + memcpy(rb->buf + w, data, len); + } else { + memcpy(rb->buf + w, data, tail); + memcpy(rb->buf, data + tail, len - tail); + } + + __asm__ __volatile__("memw" ::: "memory"); + rb->wr = (w + len) % RING_BUF_SIZE; + return len; +} + +/* Read up to len bytes. Returns bytes actually read. */ +static inline size_t ring_buf_read(ring_buf_t *rb, uint8_t *dst, size_t len) +{ + uint32_t fill = ring_buf_fill(rb); + if (len > fill) len = fill; + if (len == 0) return 0; + + uint32_t r = rb->rd; + uint32_t tail = RING_BUF_SIZE - r; + + if (len <= tail) { + memcpy(dst, rb->buf + r, len); + } else { + memcpy(dst, rb->buf + r, tail); + memcpy(dst + tail, rb->buf, len - tail); + } + + __asm__ __volatile__("memw" ::: "memory"); + rb->rd = (r + len) % RING_BUF_SIZE; + return len; +} diff --git a/ADC/kitty-rx/main/usb_audio.c b/ADC/kitty-rx/main/usb_audio.c new file mode 100644 index 0000000..738f644 --- /dev/null +++ b/ADC/kitty-rx/main/usb_audio.c @@ -0,0 +1,47 @@ +/* + * UAC2 microphone: feeds WiFi-received audio to USB host. + * + * The usb_device_uac component calls our input callback periodically + * (every CONFIG_UAC_MIC_INTERVAL_MS). We read from the shared ring buffer + * and hand it to the USB stack. If the ring buffer has insufficient data, + * we zero-fill the remainder (silence). + */ + +#include +#include "esp_log.h" +#include "usb_device_uac.h" +#include "usb_audio.h" + +#define TAG "uac" + +static ring_buf_t *s_rb; + +static esp_err_t mic_input_cb(uint8_t *buf, size_t len, size_t *bytes_read, void *ctx) +{ + size_t got = ring_buf_read(s_rb, buf, len); + if (got < len) + memset(buf + got, 0, len - got); + *bytes_read = len; + return ESP_OK; +} + +void usb_audio_init(ring_buf_t *rb) +{ + s_rb = rb; + + uac_device_config_t cfg = { + .output_cb = NULL, + .input_cb = mic_input_cb, + .set_mute_cb = NULL, + .set_volume_cb = NULL, + .cb_ctx = NULL, + }; + + esp_err_t err = uac_device_init(&cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "uac_device_init failed: %s", esp_err_to_name(err)); + return; + } + + ESP_LOGI(TAG, "UAC2 mic ready: 48kHz 16-bit stereo"); +} diff --git a/ADC/kitty-rx/main/usb_audio.h b/ADC/kitty-rx/main/usb_audio.h new file mode 100644 index 0000000..7a26aac --- /dev/null +++ b/ADC/kitty-rx/main/usb_audio.h @@ -0,0 +1,10 @@ +/* + * UAC2 microphone device via espressif/usb_device_uac. + * Presents as USB audio input: 48kHz 16-bit stereo. + */ + +#pragma once + +#include "ring_buf.h" + +void usb_audio_init(ring_buf_t *rb); diff --git a/ADC/kitty-rx/sdkconfig.defaults b/ADC/kitty-rx/sdkconfig.defaults new file mode 100644 index 0000000..ebb9eec --- /dev/null +++ b/ADC/kitty-rx/sdkconfig.defaults @@ -0,0 +1,38 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n + +CONFIG_FREERTOS_HZ=1000 + +CONFIG_ESP_WIFI_STATIC_TX_BUFFER=y +CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=8 +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=25 + +CONFIG_ESP_WIFI_IRAM_OPT=y +CONFIG_ESP_WIFI_RX_IRAM_OPT=y +CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y + +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 + +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=n + +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n + +# UAC2: mic only, 48kHz stereo 16-bit +CONFIG_UAC_SAMPLE_RATE=48000 +CONFIG_UAC_MIC_CHANNEL_NUM=2 +CONFIG_UAC_SPEAKER_CHANNEL_NUM=0 +CONFIG_UAC_MIC_INTERVAL_MS=10 +CONFIG_UAC_SUPPORT_MACOS=y + +# Pin TinyUSB + mic tasks to core 1 (WiFi ISR on core 0) +CONFIG_UAC_TINYUSB_TASK_CORE=1 +CONFIG_UAC_MIC_TASK_CORE=1 + +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y + +# Console on UART0 (GPIO43 TX, GPIO44 RX) -- TinyUSB takes USB-OTG pins +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y diff --git a/DAC/lucy-rx/CMakeLists.txt b/DAC/lucy-rx/CMakeLists.txt new file mode 100644 index 0000000..eb973e5 --- /dev/null +++ b/DAC/lucy-rx/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(receiver) diff --git a/DAC/lucy-rx/main/CMakeLists.txt b/DAC/lucy-rx/main/CMakeLists.txt new file mode 100644 index 0000000..f915661 --- /dev/null +++ b/DAC/lucy-rx/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "main.c" + INCLUDE_DIRS "." + REQUIRES esp_wifi nvs_flash esp_netif esp_event driver esp_timer esp_driver_i2s +) diff --git a/DAC/lucy-rx/main/main.c b/DAC/lucy-rx/main/main.c new file mode 100644 index 0000000..1fe353a --- /dev/null +++ b/DAC/lucy-rx/main/main.c @@ -0,0 +1,407 @@ +/* + * RKY audio receiver: WiFi -> direct I2S DMA buffer write. + * + * Dual-path RX: ISR hook (fast, ~5us) + promisc callback (fallback). + * P2M-M2P bridge: audio payload memcpy'd directly into I2S TX DMA slots. + * + * I2S: 48kHz 16-bit data in 32-bit slots, Philips framing. + * BCLK=16 WS=15 DOUT=17, no MCLK. + * Hardware left-justifies 16-bit DMA words into 32-bit I2S slots. + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "driver/i2s_std.h" +#include "soc/lldesc.h" +#include "esp_private/wifi_os_adapter.h" +#include "xtensa/xtensa_api.h" +#include "esp_cpu.h" + +#define TAG "rky-rx" + +#define CHANNEL 1 +#define SAMPLE_RATE 48000 +#define AUDIO_PAYLOAD_SIZE 192 + +#define BRIDGE_DESC_NUM 128 +#define BRIDGE_DESC_MASK (BRIDGE_DESC_NUM - 1) + +/* WiFi MAC RX DMA registers (ESP32-S3) */ +#define REG_RX_DSCR_LAST (*(volatile uint32_t *)0x60033090) + +/* DMA SRAM address range on S3 */ +#define DMA_SRAM_BASE 0x3FC88000 +#define DMA_SRAM_END 0x3FD00000 +#define DMA_ADDR_MASK 0x000FFFFF +#define DMA_ADDR_OR 0x3FC00000 + +typedef struct __attribute__((packed)) { + uint32_t frame_seq; + uint8_t sub_index; + uint8_t pkt_type; + uint16_t payload_len; + uint16_t burst_width; +} rky_hdr_t; + +#define MAC_HDR_LEN 24 +#define ACTION_HDR_LEN 4 +#define RKY_HDR_LEN sizeof(rky_hdr_t) +#define FRAME_OVERHEAD (MAC_HDR_LEN + ACTION_HDR_LEN + RKY_HDR_LEN) + +static uint8_t **s_bufs; +static uint32_t s_buf_size; + +static volatile uint32_t s_wr_slot; +static volatile uint32_t s_dma_slot; + +static DRAM_ATTR uint8_t s_paired_mac[6]; +static volatile bool s_paired; + +static uint32_t s_stat_isr; +static uint32_t s_stat_prom; +static uint32_t s_stat_dedup; +static uint32_t s_stat_gaps; +static uint32_t s_stat_overruns; +static uint32_t s_stat_sent; +static uint32_t s_stat_isrs; +static volatile uint32_t s_last_seq; + +static i2s_chan_handle_t s_tx_handle; + +/* ---------- shared ingest ---------- */ + +static inline void IRAM_ATTR ingest_audio(const uint8_t *audio, uint16_t plen, + uint32_t seq, const uint8_t *sa) +{ + /* first-seen pairing */ + if (!s_paired) { + for (int i = 0; i < 6; i++) s_paired_mac[i] = sa[i]; + s_paired = true; + } else { + if (sa[0] != s_paired_mac[0] || sa[1] != s_paired_mac[1] || + sa[2] != s_paired_mac[2] || sa[3] != s_paired_mac[3] || + sa[4] != s_paired_mac[4] || sa[5] != s_paired_mac[5]) return; + } + + /* sequence dedup: both paths call ingest, skip if already seen */ + if (seq == s_last_seq && s_last_seq != 0) { + s_stat_dedup++; + return; + } + + /* gap detection */ + if (s_last_seq != 0 && seq != s_last_seq + 1) + s_stat_gaps++; + s_last_seq = seq; + + /* ring slot */ + uint32_t next = (s_wr_slot + 1) & BRIDGE_DESC_MASK; + if (next == s_dma_slot) { + s_stat_overruns++; + return; + } + + uint32_t slot = s_wr_slot; + uint32_t copy_len = (plen <= s_buf_size) ? plen : s_buf_size; + + memcpy((void *)s_bufs[slot], audio, copy_len); + if (copy_len < s_buf_size) + memset((void *)(s_bufs[slot] + copy_len), 0, s_buf_size - copy_len); + + __asm__ __volatile__("memw" ::: "memory"); + + s_wr_slot = next; +} + +/* ---------- ISR hook ---------- */ + +static DRAM_ATTR xt_handler s_orig_isr; +static DRAM_ATTR void *s_orig_arg; +static DRAM_ATTR int s_orig_intr_num = -1; +static DRAM_ATTR uint32_t s_isr_last_raw; + +static void IRAM_ATTR wifi_isr_hook(void *arg) +{ + s_stat_isrs++; + + uint32_t raw = REG_RX_DSCR_LAST; + if (raw != s_isr_last_raw) { + s_isr_last_raw = raw; + + uint32_t addr = (raw & DMA_ADDR_MASK) | DMA_ADDR_OR; + if (addr >= DMA_SRAM_BASE && addr < DMA_SRAM_END) { + lldesc_t *desc = (lldesc_t *)addr; + + if (desc->owner == 0 && desc->length > FRAME_OVERHEAD) { + const uint8_t *buf = (const uint8_t *)desc->buf; + + if (buf[0] == 0xD0 && + buf[24] == 127 && + buf[25] == 0x52 && buf[26] == 0x4B && buf[27] == 0x59) + { + rky_hdr_t *hdr = (rky_hdr_t *)(buf + MAC_HDR_LEN + ACTION_HDR_LEN); + uint16_t plen = hdr->payload_len; + uint32_t seq = hdr->frame_seq; + const uint8_t *audio = buf + FRAME_OVERHEAD; + + if (plen > desc->length - FRAME_OVERHEAD) + plen = desc->length - FRAME_OVERHEAD; + + if (plen > 0) { + ingest_audio(audio, plen, seq, buf + 10); + s_stat_isr++; + } + } + } + } + } + + /* chain to blob's original ISR */ + if (s_orig_isr) + s_orig_isr(s_orig_arg); +} + +/* patched _set_isr: intercept when blob installs wDev_ProcessFiq */ +static void IRAM_ATTR patched_set_isr(int32_t n, void *f, void *arg) +{ + if (s_orig_intr_num < 0 && f != NULL) { + s_orig_isr = (xt_handler)f; + s_orig_arg = arg; + s_orig_intr_num = n; + xt_set_interrupt_handler(n, wifi_isr_hook, arg); + ESP_EARLY_LOGI(TAG, "captured WiFi ISR: intr=%d handler=%p arg=%p", + (int)n, f, arg); + } else { + xt_set_interrupt_handler(n, (xt_handler)f, arg); + } +} + +/* ---------- I2S on_sent callback -- GDMA TX EOF ISR ---------- */ + +static bool IRAM_ATTR on_i2s_sent(i2s_chan_handle_t handle, + i2s_event_data_t *event, + void *user_data) +{ + s_dma_slot = (s_dma_slot + 1) & BRIDGE_DESC_MASK; + s_stat_sent++; + return false; +} + +/* ---------- promiscuous callback (fallback path) ---------- */ + +static void IRAM_ATTR promisc_cb(void *buf, wifi_promiscuous_pkt_type_t type) +{ + wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)buf; + uint8_t *frame = pkt->payload; + int len = pkt->rx_ctrl.sig_len - 4; + + if (len < (int)FRAME_OVERHEAD) return; + if ((frame[0] & 0xFC) != 0xD0) return; + if (frame[24] != 127) return; + if (frame[25] != 0x52 || frame[26] != 0x4B || frame[27] != 0x59) return; + + rky_hdr_t *hdr = (rky_hdr_t *)(frame + MAC_HDR_LEN + ACTION_HDR_LEN); + uint16_t plen = hdr->payload_len; + uint32_t seq = hdr->frame_seq; + uint8_t *audio = frame + FRAME_OVERHEAD; + + int avail = len - (int)FRAME_OVERHEAD; + if (plen > (uint16_t)avail) plen = (uint16_t)avail; + if (plen == 0) return; + + ingest_audio(audio, plen, seq, frame + 10); + s_stat_prom++; +} + +/* ---------- DMA pointer extraction ---------- */ + +/* + * i2s_channel_obj_t layout (ESP-IDF v5.4, i2s_private.h): + * +16 i2s_dma_t dma: + * +4 desc_num + * +12 buf_size + * +32 bufs + */ +static void extract_dma_pointers(void) +{ + uint8_t *ch = (uint8_t *)s_tx_handle; + uint8_t *dma = ch + 16; + + uint32_t desc_num = *(uint32_t *)(dma + 4); + uint32_t buf_size = *(uint32_t *)(dma + 12); + uint8_t **bufs = *(uint8_t ***)(dma + 32); + + ESP_LOGI(TAG, "DMA: desc_num=%lu buf_size=%lu bufs=%p", + (unsigned long)desc_num, (unsigned long)buf_size, (void *)bufs); + + if (desc_num != BRIDGE_DESC_NUM || buf_size == 0 || bufs == NULL) { + ESP_LOGE(TAG, "DMA extraction failed: expected desc_num=%d got %lu", + BRIDGE_DESC_NUM, (unsigned long)desc_num); + abort(); + } + + if (!esp_ptr_internal(bufs[0])) { + ESP_LOGE(TAG, "DMA buffer not in internal SRAM: %p", (void *)bufs[0]); + abort(); + } + + s_bufs = bufs; + s_buf_size = buf_size; + s_wr_slot = 0; + s_dma_slot = 0; + + ESP_LOGI(TAG, "bridge: %lu * %lu = %lu bytes (%lums)", + (unsigned long)desc_num, + (unsigned long)buf_size, + (unsigned long)(desc_num * buf_size), + (unsigned long)(desc_num * buf_size * 1000UL / (SAMPLE_RATE * 4))); +} + +/* ---------- I2S init ---------- */ + +static void i2s_bridge_init(void) +{ + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + chan_cfg.dma_desc_num = BRIDGE_DESC_NUM; + chan_cfg.dma_frame_num = AUDIO_PAYLOAD_SIZE / 4; + chan_cfg.auto_clear_after_cb = true; + chan_cfg.auto_clear_before_cb = false; + + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &s_tx_handle, NULL)); + + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG( + I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = GPIO_NUM_16, + .ws = GPIO_NUM_15, + .dout = GPIO_NUM_17, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + + /* + * Daisy Seed (PCM3060) expects 32-bit I2S slots. + * With 16-bit data width and 32-bit slot width, the ESP32-S3 I2S hardware + * reads 16-bit words from DMA and left-justifies them in each 32-bit slot. + * DMA buffers stay 16-bit-sized; the hardware handles padding on the wire. + * + * BCLK = 48000 * 2 * 32 = 3.072 MHz + */ + std_cfg.slot_cfg.slot_bit_width = I2S_SLOT_BIT_WIDTH_32BIT; + std_cfg.slot_cfg.ws_width = 32; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(s_tx_handle, &std_cfg)); + + i2s_event_callbacks_t cbs = { + .on_sent = on_i2s_sent, + .on_recv = NULL, + .on_recv_q_ovf = NULL, + .on_send_q_ovf = NULL, + }; + ESP_ERROR_CHECK(i2s_channel_register_event_callback(s_tx_handle, &cbs, NULL)); + + extract_dma_pointers(); + + ESP_ERROR_CHECK(i2s_channel_enable(s_tx_handle)); +} + +/* ---------- WiFi init ---------- */ + +static void wifi_init(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* patch _set_isr before esp_wifi_init so we capture the blob's ISR install */ + g_wifi_osi_funcs._set_isr = patched_set_isr; + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_ERROR_CHECK(esp_wifi_set_channel(CHANNEL, WIFI_SECOND_CHAN_NONE)); + + wifi_promiscuous_filter_t filt = { + .filter_mask = WIFI_PROMIS_FILTER_MASK_ALL, + }; + ESP_ERROR_CHECK(esp_wifi_set_promiscuous_filter(&filt)); + ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(promisc_cb)); + ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); +} + +/* ---------- stats ---------- */ + +static void stats_task(void *arg) +{ + int64_t last = esp_timer_get_time(); + + while (1) { + vTaskDelay(pdMS_TO_TICKS(5000)); + + int64_t now = esp_timer_get_time(); + float elapsed = (now - last) / 1e6f; + + uint32_t wr = s_wr_slot; + uint32_t rd = s_dma_slot; + uint32_t fill = (wr >= rd) ? (wr - rd) : (BRIDGE_DESC_NUM - rd + wr); + + uint32_t total = s_stat_isr + s_stat_prom; + + ESP_LOGI(TAG, "isr=%lu prom=%lu dedup=%lu | gaps=%lu over=%lu sent=%lu fill=%lu/%d | isrs=%lu pps=%.0f", + (unsigned long)s_stat_isr, + (unsigned long)s_stat_prom, + (unsigned long)s_stat_dedup, + (unsigned long)s_stat_gaps, + (unsigned long)s_stat_overruns, + (unsigned long)s_stat_sent, + (unsigned long)fill, + BRIDGE_DESC_NUM, + (unsigned long)s_stat_isrs, + total / elapsed); + + s_stat_isr = 0; + s_stat_prom = 0; + s_stat_dedup = 0; + s_stat_gaps = 0; + s_stat_overruns = 0; + s_stat_sent = 0; + s_stat_isrs = 0; + last = now; + } +} + +/* ---------- main ---------- */ + +void app_main(void) +{ + i2s_bridge_init(); + wifi_init(); + + ESP_LOGI(TAG, "bridge rx: %dkHz 16-bit/32-slot stereo ch=%d, %d*%lu=%lu bytes, isr_hook=%s", + SAMPLE_RATE / 1000, CHANNEL, + BRIDGE_DESC_NUM, + (unsigned long)s_buf_size, + (unsigned long)(BRIDGE_DESC_NUM * s_buf_size), + s_orig_intr_num >= 0 ? "active" : "inactive"); + + xTaskCreatePinnedToCore(stats_task, "stats", 4096, NULL, 5, NULL, 0); +} diff --git a/DAC/lucy-rx/sdkconfig.defaults b/DAC/lucy-rx/sdkconfig.defaults new file mode 100644 index 0000000..cb9898e --- /dev/null +++ b/DAC/lucy-rx/sdkconfig.defaults @@ -0,0 +1,23 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n + +CONFIG_FREERTOS_HZ=1000 + +CONFIG_ESP_WIFI_STATIC_TX_BUFFER=y +CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=8 +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=25 + +CONFIG_ESP_WIFI_IRAM_OPT=y +CONFIG_ESP_WIFI_RX_IRAM_OPT=y +CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y + +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 + +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=n + +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n + +CONFIG_ESP_WIFI_11B_ENABLED=y diff --git a/DAC/ricky-tx/CMakeLists.txt b/DAC/ricky-tx/CMakeLists.txt new file mode 100644 index 0000000..6d79424 --- /dev/null +++ b/DAC/ricky-tx/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(slot4_tx) diff --git a/DAC/ricky-tx/main/CMakeLists.txt b/DAC/ricky-tx/main/CMakeLists.txt new file mode 100644 index 0000000..aa09538 --- /dev/null +++ b/DAC/ricky-tx/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "main.c" "hijack_tx.c" + INCLUDE_DIRS "." + REQUIRES esp_wifi esp_timer nvs_flash esp_netif esp_event esp_rom heap +) diff --git a/DAC/ricky-tx/main/hijack_tx.c b/DAC/ricky-tx/main/hijack_tx.c new file mode 100644 index 0000000..f7856f3 --- /dev/null +++ b/DAC/ricky-tx/main/hijack_tx.c @@ -0,0 +1,144 @@ +/* + * hijack_tx -- DMA hijack TX for ESP32-S3. + * + * Fires a blob TX on slot 0 with a dummy frame sized to match the real + * frame, then swaps the DMA descriptor's buffer pointer before hardware + * reads it. The blob's PLCP setup uses the correct length because the + * dummy is the same size. No PLCP patching race. + * + * Sequence: + * 1. Build dummy frame matching target length + * 2. Fire blob TX (esp_wifi_80211_tx) on slot 0 + * 3. Spin-wait for slot 0 TXQ_CTRL bits [31:30] set + * 4. Extract DMA descriptor from TXQ_CTRL[19:0] + * 5. Swap desc->buf to our frame + * 6. Wait for completion (bits [31:30] clear) + * 7. Restore descriptor to prevent blob crash + */ + +#include "hijack_tx.h" + +#include +#include +#include "esp_heap_caps.h" +#include "esp_wifi.h" +#include "esp_mac.h" +#include "esp_timer.h" +#include "esp_rom_lldesc.h" +#include "xtensa/core-macros.h" + +#define REG32(addr) (*(volatile uint32_t *)(addr)) + +#define S0_TXQ_CTRL 0x60033D08 +#define S0_PMD 0x60034320 + +#define MAX_FRAME_SIZE 512 +#define DMA_BASE 0x3FC00000 + +static uint8_t *s_dummy = NULL; +static uint8_t *s_frame_buf = NULL; +static uint8_t s_own_mac[6]; + +static uint32_t s_ok_count = 0; +static uint32_t s_miss_count = 0; +static uint32_t s_fail_count = 0; + +void hijack_init(void) +{ + if (s_frame_buf) return; + + s_frame_buf = heap_caps_calloc(1, MAX_FRAME_SIZE, + MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + s_dummy = heap_caps_calloc(1, MAX_FRAME_SIZE, + MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + assert(s_frame_buf); + assert(s_dummy); + + esp_read_mac(s_own_mac, ESP_MAC_WIFI_STA); +} + +static void build_dummy(uint16_t len) +{ + memset(s_dummy, 0, len); + s_dummy[0] = 0xD0; + memcpy(s_dummy + 4, "\xFF\xFF\xFF\xFF\xFF\xFF", 6); + memcpy(s_dummy + 10, s_own_mac, 6); + memcpy(s_dummy + 16, s_own_mac, 6); + s_dummy[24] = 127; + s_dummy[25] = 0x52; + s_dummy[26] = 0x4B; + s_dummy[27] = 0x59; +} + +int IRAM_ATTR hijack_send(const uint8_t *frame, uint16_t len) +{ + if (!s_frame_buf) return -1; + if (len == 0 || len > MAX_FRAME_SIZE) return -1; + + memcpy(s_frame_buf, frame, len); + + build_dummy(len); + + esp_wifi_80211_tx(WIFI_IF_STA, s_dummy, len, false); + + uint32_t spin_start = xthal_get_ccount(); + uint32_t spin_limit = 5000 * 240; + lldesc_t *desc = NULL; + volatile const uint8_t *orig_buf = NULL; + uint16_t orig_size = 0; + uint16_t orig_len = 0; + + while ((xthal_get_ccount() - spin_start) < spin_limit) { + uint32_t ctrl = REG32(S0_TXQ_CTRL); + if (ctrl & 0xC0000000) { + desc = (lldesc_t *)(DMA_BASE | (ctrl & 0x000FFFFF)); + + orig_buf = desc->buf; + orig_size = desc->size; + orig_len = desc->length; + + desc->buf = (volatile const uint8_t *)s_frame_buf; + __asm__ __volatile__("memw" ::: "memory"); + + break; + } + } + + if (!desc) { + s_miss_count++; + return -1; + } + + int result = -1; + int64_t poll_end = esp_timer_get_time() + 200000; + while (esp_timer_get_time() < poll_end) { + if ((REG32(S0_TXQ_CTRL) & 0xC0000000) == 0) { + uint32_t pmd = REG32(S0_PMD); + uint8_t pmd_code = (pmd >> 12) & 0xF; + result = (pmd_code == 0 || pmd_code == 4) ? 0 : (int)pmd_code; + break; + } + } + + desc->buf = orig_buf; + desc->size = orig_size; + desc->length = orig_len; + __asm__ __volatile__("memw" ::: "memory"); + + if (result == 0) + s_ok_count++; + else + s_fail_count++; + + if (((s_ok_count + s_fail_count) % 100) == 0) { + uint32_t swap_us = (xthal_get_ccount() - spin_start) / 240; + printf("hijack: ok=%lu fail=%lu miss=%lu swap=%luus pmd=%d\n", + (unsigned long)s_ok_count, + (unsigned long)s_fail_count, + (unsigned long)s_miss_count, + (unsigned long)swap_us, + result); + } + + return result; +} diff --git a/DAC/ricky-tx/main/hijack_tx.h b/DAC/ricky-tx/main/hijack_tx.h new file mode 100644 index 0000000..7bfb836 --- /dev/null +++ b/DAC/ricky-tx/main/hijack_tx.h @@ -0,0 +1,16 @@ +#ifndef HIJACK_TX_H +#define HIJACK_TX_H + +#include + +/* allocate DMA-capable frame buffer */ +void hijack_init(void); + +/* + * Transmit frame via slot 0 DMA hijack. + * Fires a blob TX, intercepts the DMA descriptor, swaps in our buffer. + * Returns 0 on success, -1 on miss/timeout. + */ +int hijack_send(const uint8_t *frame, uint16_t len); + +#endif diff --git a/DAC/ricky-tx/main/main.c b/DAC/ricky-tx/main/main.c new file mode 100644 index 0000000..f047ec0 --- /dev/null +++ b/DAC/ricky-tx/main/main.c @@ -0,0 +1,214 @@ +/* + * DMA hijack TX: 440Hz sine wave via slot 0 buffer swap. + * 48kHz 16-bit stereo interleaved. 48 samples/pkt = 192 bytes audio. + * 1000 packets/sec (1ms period). Uses ricky's original hijack_send. + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "esp_mac.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include "esp_event.h" + +#include "hijack_tx.h" + +#define TAG "hijack-tx" + +static const uint8_t BCAST[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +static const uint8_t OUI_RKY[] = {0x52, 0x4B, 0x59}; + +#define CHANNEL 1 +#define SAMPLE_RATE 48000 +#define SINE_FREQ 440 +#define SAMPLES_PER_PKT 48 +#define BYTES_PER_SAMPLE 4 +#define AUDIO_PAYLOAD (SAMPLES_PER_PKT * BYTES_PER_SAMPLE) + +#define TX_PERIOD_US 1000 +#define STATS_INTERVAL_MS 5000 + +typedef struct __attribute__((packed)) { + uint32_t frame_seq; + uint8_t sub_index; + uint8_t pkt_type; + uint16_t payload_len; + uint16_t burst_width; +} rky_hdr_t; + +#define MAC_HDR_LEN 24 +#define ACTION_HDR_LEN 4 +#define RKY_HDR_LEN sizeof(rky_hdr_t) +#define FRAME_OVERHEAD (MAC_HDR_LEN + ACTION_HDR_LEN + RKY_HDR_LEN) +#define MAX_FRAME_LEN (FRAME_OVERHEAD + AUDIO_PAYLOAD) + +/* pre-computed sine LUT: 4800 samples = 100 full 440Hz cycles at 48kHz */ +#define SINE_TABLE_LEN 4800 +static int16_t sine_table[SINE_TABLE_LEN]; +static uint32_t s_sample_offset = 0; + +static void init_sine_table(void) +{ + for (int i = 0; i < SINE_TABLE_LEN; i++) { + float t = (float)i / (float)SAMPLE_RATE; + sine_table[i] = (int16_t)(sinf(2.0f * M_PI * SINE_FREQ * t) * 4096.0f); + } +} + +static void gen_sine(int16_t *buf, int n_samples) +{ + for (int i = 0; i < n_samples; i++) { + int16_t v = sine_table[(s_sample_offset + i) % SINE_TABLE_LEN]; + buf[i * 2] = v; + buf[i * 2 + 1] = v; + } + s_sample_offset = (s_sample_offset + n_samples) % SINE_TABLE_LEN; +} + +static int build_frame(uint8_t *buf, const uint8_t *src, + uint32_t seq, const uint8_t *payload, uint16_t plen) +{ + memset(buf, 0, MAC_HDR_LEN); + buf[0] = 0xD0; + memcpy(buf + 4, BCAST, 6); + memcpy(buf + 10, src, 6); + memcpy(buf + 16, src, 6); + + int off = MAC_HDR_LEN; + buf[off++] = 127; + buf[off++] = OUI_RKY[0]; + buf[off++] = OUI_RKY[1]; + buf[off++] = OUI_RKY[2]; + + rky_hdr_t *hdr = (rky_hdr_t *)(buf + off); + hdr->frame_seq = seq; + hdr->sub_index = 0; + hdr->pkt_type = 0; + hdr->payload_len = plen; + hdr->burst_width = 1; + off += RKY_HDR_LEN; + + if (payload && plen > 0) { + memcpy(buf + off, payload, plen); + off += plen; + } + return off; +} + +static void tx_task(void *arg) +{ + uint8_t mac[6]; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + + init_sine_table(); + hijack_init(); + + /* prime blob TX engine -- required before hijack_send works */ + { + uint8_t dummy[64] = {0}; + dummy[0] = 0xD0; + memcpy(dummy + 4, BCAST, 6); + memcpy(dummy + 10, mac, 6); + memcpy(dummy + 16, mac, 6); + dummy[24] = 127; + dummy[25] = OUI_RKY[0]; + dummy[26] = OUI_RKY[1]; + dummy[27] = OUI_RKY[2]; + esp_wifi_80211_tx(WIFI_IF_STA, dummy, sizeof(dummy), false); + vTaskDelay(pdMS_TO_TICKS(10)); + } + + ESP_LOGI(TAG, "TX start: %dHz sine, %dkHz/16-bit stereo, %d bytes/pkt, period=%dus", + SINE_FREQ, SAMPLE_RATE / 1000, AUDIO_PAYLOAD, TX_PERIOD_US); + + uint8_t frame[MAX_FRAME_LEN]; + int16_t audio_buf[SAMPLES_PER_PKT * 2]; + + uint32_t seq = 0; + uint32_t ok = 0, err = 0, miss = 0; + int64_t dur_sum = 0, dur_min = INT64_MAX, dur_max = 0; + uint32_t dur_n = 0; + int64_t last_stats = esp_timer_get_time(); + int64_t next_tx = esp_timer_get_time(); + + while (1) { + while (esp_timer_get_time() < next_tx) { } + + next_tx += TX_PERIOD_US; + int64_t now = esp_timer_get_time(); + if (now >= next_tx) + next_tx = now + TX_PERIOD_US; + + int64_t t0 = esp_timer_get_time(); + + gen_sine(audio_buf, SAMPLES_PER_PKT); + int flen = build_frame(frame, mac, seq, + (uint8_t *)audio_buf, AUDIO_PAYLOAD); + + esp_err_t ret = esp_wifi_80211_tx(WIFI_IF_STA, frame, flen, false); + if (ret == ESP_OK) ok++; + else err++; + + seq++; + + int64_t dur = esp_timer_get_time() - t0; + dur_sum += dur; + dur_n++; + if (dur < dur_min) dur_min = dur; + if (dur > dur_max) dur_max = dur; + + if ((esp_timer_get_time() - last_stats) >= (int64_t)STATS_INTERVAL_MS * 1000) { + double avg = dur_n > 0 ? (double)dur_sum / dur_n : 0; + double rate = dur_n > 0 ? (double)dur_n / ((double)STATS_INTERVAL_MS / 1000.0) : 0; + ESP_LOGI(TAG, "seq=%lu ok=%lu err=%lu miss=%lu " + "dur avg=%.0f min=%lld max=%lld pps=%.0f", + (unsigned long)seq, + (unsigned long)ok, (unsigned long)err, + (unsigned long)miss, + avg, (long long)dur_min, (long long)dur_max, rate); + + ok = err = miss = dur_n = 0; + dur_sum = 0; + dur_min = INT64_MAX; + dur_max = 0; + last_stats = esp_timer_get_time(); + } + } +} + +static void wifi_init(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + + wifi_config_t sta_cfg = {0}; + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_cfg)); + ESP_ERROR_CHECK(esp_wifi_set_protocol(WIFI_IF_STA, + WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N)); + + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_ERROR_CHECK(esp_wifi_set_channel(CHANNEL, WIFI_SECOND_CHAN_NONE)); + ESP_ERROR_CHECK(esp_wifi_config_80211_tx_rate(WIFI_IF_STA, WIFI_PHY_RATE_54M)); + ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); +} + +void app_main(void) +{ + wifi_init(); + + ESP_LOGI(TAG, "440Hz sine TX via DMA hijack (ricky original)"); + xTaskCreatePinnedToCore(tx_task, "tx", 8192, NULL, + configMAX_PRIORITIES - 1, NULL, 0); +} diff --git a/DAC/ricky-tx/sdkconfig.defaults b/DAC/ricky-tx/sdkconfig.defaults new file mode 100644 index 0000000..b81131e --- /dev/null +++ b/DAC/ricky-tx/sdkconfig.defaults @@ -0,0 +1,24 @@ +CONFIG_IDF_TARGET="esp32s3" + +CONFIG_FREERTOS_HZ=1000 + +CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1=y +CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0 + +CONFIG_ESP_WIFI_STATIC_TX_BUFFER=y +CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=16 + +CONFIG_ESP_WIFI_IRAM_OPT=y +CONFIG_ESP_WIFI_RX_IRAM_OPT=y +CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y + +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 + +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=n + +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y + +# B + G + N +CONFIG_ESP_WIFI_11B_ENABLED=y diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..1cebcb5 --- /dev/null +++ b/LICENCE @@ -0,0 +1,12 @@ +This is free to use, without conditions. + +There is no licence here on purpose. Individuals, students, hobbyists — take what +you need, make it yours, don't think twice. You'd flatter me. + +The absence of a licence is deliberate. A licence is a legal surface. Words can be +reinterpreted, and corporations employ lawyers whose job is exactly that. Silence is +harder to exploit than language. If a company wants to use this, the lack of explicit +permission makes it just inconvenient enough to matter. + +This won't change the world. But it shifts the balance, even slightly, away from the +system that co-opts open work for closed profit. That's enough for me.