Files
gilles a8f0d6ccba Initial commit — KC868-A2 contrôleur solaire ESP32
Fonctionnalités :
- Lecture RS485 Modbus Epever Tracer 4210N (115200 bps, FC03/FC04/FC16)
- Moteur de règles JSON (LittleFS) — commande automatique des relais
- Interface web mobile-first (dashboard, règles, config, historique, EPEVER, debug)
- WiFi AP+STA simultanés avec reconnexion automatique et portail captif
- mDNS configurable (pv.local par défaut)
- Configuration registres EPEVER depuis l'UI (18 registres holding)
- Historique basse/haute résolution avec graphes canvas
- VPN WireGuard optionnel (désactivé par défaut, config via UI)
- OTA firmware + filesystem via ElegantOTA
- Deep sleep / économie d'énergie

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 19:25:01 +02:00

153 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""
Patch ESP32 QEMU source to fix WiFi/RF peripheral register stubs.
Two changes only — no new files, no meson.build modifications:
1. hw/misc/unimp.c — change unimp_read to return 0xFFFFFFFF instead of 0.
The ESP32 WiFi library busy-loops on hardware-ready bits. With the
default return-0 those loops never exit and the watchdog fires.
0xFFFF... sets all bits, so most active-high "ready/done" flags pass.
2. hw/xtensa/esp32.c — add create_unimplemented_device() calls for all
unmapped WiFi/RF peripheral registers that cause LoadStorePIFAddrError.
"""
import re
import sys
ESP32_C = 'hw/xtensa/esp32.c'
UNIMP_C = 'hw/misc/unimp.c'
# ── Stubs to register ─────────────────────────────────────────────────────────
STUB_PREFIX = 'rfstub.' # unimp_read returns 0xFF for names starting with this
STUBS = [
# (device-name, base-addr, size)
# Prefix "rfstub." distinguishes our WiFi stubs from other unimplemented
# devices — unimp_read returns 0xFF only for that prefix.
# AHB bus ─────────────────────────────────────────────────────────────────
(f'{STUB_PREFIX}wifi_modem', 0x60033C00, 0x10000),
# Data-bus (0x3FF00000) ───────────────────────────────────────────────────
(f'{STUB_PREFIX}fe2', 0x3FF45000, 0x1000),
(f'{STUB_PREFIX}fe', 0x3FF46000, 0x1000), # crash @ 0x3FF460A0
(f'{STUB_PREFIX}bt_bb', 0x3FF51000, 0x2000), # <=0x2000 avoids I2C_EXT0
(f'{STUB_PREFIX}5c', 0x3FF5C000, 0x1000), # crash @ 0x3FF5C01C
(f'{STUB_PREFIX}5d', 0x3FF5D000, 0x1000),
(f'{STUB_PREFIX}nrx', 0x3FF5E000, 0x1000), # 0x3FF5F000=TG0 untouched
(f'{STUB_PREFIX}62', 0x3FF62000, 0x1000),
(f'{STUB_PREFIX}63', 0x3FF63000, 0x1000),
(f'{STUB_PREFIX}68', 0x3FF68000, 0x1000),
(f'{STUB_PREFIX}6a', 0x3FF6A000, 0x1000),
(f'{STUB_PREFIX}6b', 0x3FF6B000, 0x1000),
(f'{STUB_PREFIX}6c', 0x3FF6C000, 0x1000),
# 0x3FF6E000 = UART2 — leave for Modbus
]
# ─────────────────────────────────────────────────────────────────────────────
# Step 1 — patch unimp_read to return 0xFFFFFFFF
# ─────────────────────────────────────────────────────────────────────────────
def patch_unimp(path):
try:
with open(path) as f:
src = f.read()
except FileNotFoundError:
print(f' ERROR: {path} not found', file=sys.stderr)
sys.exit(1)
if '~(uint64_t)0' in src or '0xffffffff' in src.lower() and 'unimp_read' in src:
print(f' skip {path} — already patched')
return
# Replace "return 0;" inside unimp_read with a prefix-checked return:
# devices named "rfstub.*" return 0xFFFFFFFF, all others return 0.
replacement = (
'return strncmp(s->name, "rfstub.", 7) == 0 ? ~(uint64_t)0 : 0;'
)
new_src = re.sub(
r'(static uint64_t unimp_read\b[^}]+?\breturn\s+)0(;)',
lambda m: m.group(0).replace('return 0;', replacement),
src,
count=1,
flags=re.DOTALL,
)
# Also ensure <string.h> is included for strncmp
if '#include <string.h>' not in new_src and 'strncmp' in new_src:
new_src = new_src.replace(
'#include "qemu/osdep.h"\n',
'#include "qemu/osdep.h"\n#include <string.h>\n',
1,
)
if new_src == src:
print(f' WARNING: pattern not found in {path}, skipping', file=sys.stderr)
return
with open(path, 'w') as f:
f.write(new_src)
print(f' patched {path}: unimp_read returns 0xFFFF for rfstub.* only')
# ─────────────────────────────────────────────────────────────────────────────
# Step 2 — register stubs in ESP32 machine
# ─────────────────────────────────────────────────────────────────────────────
def patch_esp32(path):
try:
with open(path) as f:
src = f.read()
except FileNotFoundError:
print(f' ERROR: {path} not found', file=sys.stderr)
sys.exit(1)
# Ensure hw/misc/unimp.h is included
if 'hw/misc/unimp.h' not in src:
src = src.replace(
'#include "qemu/osdep.h"\n',
'#include "qemu/osdep.h"\n#include "hw/misc/unimp.h"\n',
1,
)
print(' + added #include "hw/misc/unimp.h"')
changed = False
for name, addr, size in STUBS:
hex_addr = f'0x{addr:08X}'
if hex_addr in src or hex_addr.lower() in src:
print(f' skip {name}: already present')
continue
stub_line = (
f' create_unimplemented_device("{name}", {hex_addr}, 0x{size:X});\n'
)
m = list(re.finditer(r'create_unimplemented_device\([^;]+;\n', src))
if m:
pos = m[-1].end()
src = src[:pos] + stub_line + src[pos:]
else:
pos = src.rfind('\n}')
src = src[:pos] + '\n' + stub_line + src[pos:]
print(f' + stub {name} @ {hex_addr}')
changed = True
if changed:
with open(path, 'w') as f:
f.write(src)
print(f' wrote {path}')
else:
print(f' no changes to {path}')
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == '__main__':
print('Step 1: patch unimp_read to return 0xFFFFFFFF')
patch_unimp(UNIMP_C)
print('Step 2: register WiFi/RF stubs in ESP32 machine')
patch_esp32(ESP32_C)
print('Patch complete.')