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:
@@ -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.
|
||||
|
||||
+45
-9
@@ -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)
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -22,5 +22,7 @@
|
||||
typedef struct ESP32C3UsbJtagState {
|
||||
SysBusDevice parent_object;
|
||||
MemoryRegion iomem;
|
||||
qemu_irq irq;
|
||||
uint32_t int_ena;
|
||||
} ESP32C3UsbJtagState;
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user