feat(xteink): wire USB serial JTAG interrupts

Report SOF and serial-in-empty as always pending and route the peripheral
IRQ through the interrupt matrix so the ESP-IDF console driver progresses.
Merge emulator stdout into serial.log so crash output lands in one place.
This commit is contained in:
2026-07-26 09:21:43 -04:00
parent 5906b67287
commit 5721bd8102
5 changed files with 54 additions and 16 deletions
+1 -2
View File
@@ -70,8 +70,7 @@ Resolve script paths relative to this skill directory. `scripts/xteink-emu.sh` s
## Diagnose Failures
- Firmware serial output: `$XTEINK_EMU_STATE/serial.log`
- Emulator output: `$XTEINK_EMU_STATE/qemu.log`
- Firmware serial output and emulator messages: `$XTEINK_EMU_STATE/serial.log`
- Assembled flash image: `$XTEINK_EMU_STATE/flash.bin`
- Resolve crash addresses against the exact ELF produced with the tested binary, using the project's RISC-V `addr2line` tool.
- Preserve the SD image when reproducing persistence bugs; replace it when testing first-boot behavior.
+43 -7
View File
@@ -12,6 +12,7 @@
#include "qemu/module.h"
#include "qemu/error-report.h"
#include "hw/hw.h"
#include "hw/irq.h"
#include "hw/sysbus.h"
#include "hw/misc/esp32c3_jtag.h"
@@ -19,24 +20,57 @@
/* USB Serial JTAG Console Tap - The firmware routes ESP-IDF console output (incl. panic dumps) through this peripheral. EP1 is the byte FIFO; EP1_CONF bit1 (SERIAL_IN_EP_DATA_FREE) must read set so the driver believes the TX FIFO can always accept a byte. */
#define ESP32C3_JTAG_EP1_REG 0x00
#define ESP32C3_JTAG_EP1_CONF_REG 0x04
#define ESP32C3_JTAG_INT_RAW_REG 0x08
#define ESP32C3_JTAG_INT_ST_REG 0x0c
#define ESP32C3_JTAG_INT_ENA_REG 0x10
#define ESP32C3_JTAG_INT_CLR_REG 0x14
#define ESP32C3_JTAG_IN_EP_DATA_FREE (1u << 1)
#define ESP32C3_JTAG_SOF_INT (1u << 1)
#define ESP32C3_JTAG_SERIAL_IN_EMPTY (1u << 3)
static uint32_t esp32c3_jtag_raw_interrupts(void)
{
return ESP32C3_JTAG_SOF_INT | ESP32C3_JTAG_SERIAL_IN_EMPTY;
}
static void esp32c3_jtag_update_irq(ESP32C3UsbJtagState *s)
{
qemu_set_irq(s->irq, !!(s->int_ena & esp32c3_jtag_raw_interrupts()));
}
static uint64_t esp32c3_jtag_read(void *opaque, hwaddr addr, unsigned int size)
{
ESP32C3UsbJtagState *s = ESP32C3_JTAG(opaque);
(void) s;
if (addr == ESP32C3_JTAG_EP1_CONF_REG) {
switch (addr) {
case ESP32C3_JTAG_EP1_CONF_REG:
return ESP32C3_JTAG_IN_EP_DATA_FREE;
}
case ESP32C3_JTAG_INT_RAW_REG:
return esp32c3_jtag_raw_interrupts();
case ESP32C3_JTAG_INT_ST_REG:
return s->int_ena & esp32c3_jtag_raw_interrupts();
case ESP32C3_JTAG_INT_ENA_REG:
return s->int_ena;
default:
return 0;
}
}
static void esp32c3_jtag_write(void *opaque, hwaddr addr, uint64_t value, unsigned int size)
{
ESP32C3UsbJtagState *s = ESP32C3_JTAG(opaque);
(void) s;
if (addr == ESP32C3_JTAG_EP1_REG) {
switch (addr) {
case ESP32C3_JTAG_EP1_REG:
fputc((int)(value & 0xff), stderr);
break;
case ESP32C3_JTAG_INT_ENA_REG:
s->int_ena = value;
esp32c3_jtag_update_irq(s);
break;
case ESP32C3_JTAG_INT_CLR_REG:
esp32c3_jtag_update_irq(s);
break;
default:
break;
}
}
@@ -48,8 +82,9 @@ static const MemoryRegionOps esp32c3_jtag_ops = {
static void esp32c3_jtag_reset_hold(Object *obj, ResetType type)
{
(void) obj;
(void) type;
ESP32C3UsbJtagState *s = ESP32C3_JTAG(obj);
s->int_ena = 0;
esp32c3_jtag_update_irq(s);
}
static void esp32c3_jtag_realize(DeviceState *dev, Error **errp)
@@ -66,6 +101,7 @@ static void esp32c3_jtag_init(Object *obj)
memory_region_init_io(&s->iomem, obj, &esp32c3_jtag_ops, s,
TYPE_ESP32C3_JTAG, ESP32C3_JTAG_REGS_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
}
static void esp32c3_jtag_class_init(ObjectClass *klass, void *data)
+2
View File
@@ -555,6 +555,8 @@ static void esp32c3_machine_init(MachineState *machine)
sysbus_realize(SYS_BUS_DEVICE(&ms->jtag), &error_fatal);
MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&ms->jtag), 0);
memory_region_add_subregion_overlap(sys_mem, DR_REG_USB_SERIAL_JTAG_BASE, mr, 0);
sysbus_connect_irq(SYS_BUS_DEVICE(&ms->jtag), 0,
qdev_get_gpio_in(intmatrix_dev, ETS_USB_SERIAL_JTAG_INTR_SOURCE));
}
/* RTC CNTL realization */
+2
View File
@@ -22,5 +22,7 @@
typedef struct ESP32C3UsbJtagState {
SysBusDevice parent_object;
MemoryRegion iomem;
qemu_irq irq;
uint32_t int_ena;
} ESP32C3UsbJtagState;
+4 -5
View File
@@ -140,7 +140,6 @@ def run(args, interactive):
for name in ("pid", "qmp.sock", "serial.log", "qemu.log", "wait.offset"):
(state / name).unlink(missing_ok=True)
(state / "serial.log").touch()
(state / "qemu.log").touch()
(state / "wait.offset").write_text("0\n")
flash = state / "flash.bin"
build_flash(firmware, flash)
@@ -155,7 +154,7 @@ def run(args, interactive):
"-accel", "tcg",
"-L", str(require_env("XTEINK_BIOS")),
"-display", "gtk" if interactive else "none",
"-serial", "stdio" if interactive else f"file:{state / 'serial.log'}",
"-serial", "stdio",
"-monitor", "none",
"-qmp", f"unix:{state / 'qmp.sock'},server=on,wait=off",
"-nic", "user,model=esp32c3_wifi,mac=52:54:00:12:34:56,net=192.168.4.0/24",
@@ -167,11 +166,11 @@ def run(args, interactive):
if interactive:
process = subprocess.Popen(command)
else:
with (state / "qemu.log").open("ab") as qemu_log:
with (state / "serial.log").open("ab") as serial_log:
process = subprocess.Popen(
command,
stdin=subprocess.DEVNULL,
stdout=qemu_log,
stdout=serial_log,
stderr=subprocess.STDOUT,
start_new_session=True,
)
@@ -180,7 +179,7 @@ def run(args, interactive):
deadline = time.monotonic() + 10
while time.monotonic() < deadline:
if process.poll() is not None:
message = (state / "qemu.log").read_text(errors="replace")
message = (state / "serial.log").read_text(errors="replace")
raise RuntimeError(f"QEMU exited during startup\n{message}")
try:
qmp_command(state, "query-status")