145 lines
3.9 KiB
C
145 lines
3.9 KiB
C
/*
|
|
* 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 <string.h>
|
|
#include <stdio.h>
|
|
#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;
|
|
}
|