diff --git a/.pi/skills/test-xteink-firmware/SKILL.md b/.pi/skills/test-xteink-firmware/SKILL.md index e36d5b6d48..ee2c414430 100644 --- a/.pi/skills/test-xteink-firmware/SKILL.md +++ b/.pi/skills/test-xteink-firmware/SKILL.md @@ -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. diff --git a/hw/misc/esp32c3_jtag.c b/hw/misc/esp32c3_jtag.c index 64a971f183..a6c060ac2c 100644 --- a/hw/misc/esp32c3_jtag.c +++ b/hw/misc/esp32c3_jtag.c @@ -12,31 +12,65 @@ #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" /* 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_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; } - 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) diff --git a/hw/riscv/esp32c3.c b/hw/riscv/esp32c3.c index 6f7ad5891b..7a583fc70a 100644 --- a/hw/riscv/esp32c3.c +++ b/hw/riscv/esp32c3.c @@ -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 */ diff --git a/include/hw/misc/esp32c3_jtag.h b/include/hw/misc/esp32c3_jtag.h index 8ac6ebb584..8f821dcc28 100644 --- a/include/hw/misc/esp32c3_jtag.h +++ b/include/hw/misc/esp32c3_jtag.h @@ -22,5 +22,7 @@ typedef struct ESP32C3UsbJtagState { SysBusDevice parent_object; MemoryRegion iomem; + qemu_irq irq; + uint32_t int_ena; } ESP32C3UsbJtagState; diff --git a/scripts/xteink-emu.py b/scripts/xteink-emu.py index 55ba589be2..668a19c2f9 100755 --- a/scripts/xteink-emu.py +++ b/scripts/xteink-emu.py @@ -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")