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
|
## Diagnose Failures
|
||||||
|
|
||||||
- Firmware serial output: `$XTEINK_EMU_STATE/serial.log`
|
- Firmware serial output and emulator messages: `$XTEINK_EMU_STATE/serial.log`
|
||||||
- Emulator output: `$XTEINK_EMU_STATE/qemu.log`
|
|
||||||
- Assembled flash image: `$XTEINK_EMU_STATE/flash.bin`
|
- 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.
|
- 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.
|
- 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/module.h"
|
||||||
#include "qemu/error-report.h"
|
#include "qemu/error-report.h"
|
||||||
#include "hw/hw.h"
|
#include "hw/hw.h"
|
||||||
|
#include "hw/irq.h"
|
||||||
#include "hw/sysbus.h"
|
#include "hw/sysbus.h"
|
||||||
#include "hw/misc/esp32c3_jtag.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. */
|
/* 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_REG 0x00
|
||||||
#define ESP32C3_JTAG_EP1_CONF_REG 0x04
|
#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_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)
|
static uint64_t esp32c3_jtag_read(void *opaque, hwaddr addr, unsigned int size)
|
||||||
{
|
{
|
||||||
ESP32C3UsbJtagState *s = ESP32C3_JTAG(opaque);
|
ESP32C3UsbJtagState *s = ESP32C3_JTAG(opaque);
|
||||||
(void) s;
|
switch (addr) {
|
||||||
if (addr == ESP32C3_JTAG_EP1_CONF_REG) {
|
case ESP32C3_JTAG_EP1_CONF_REG:
|
||||||
return ESP32C3_JTAG_IN_EP_DATA_FREE;
|
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)
|
static void esp32c3_jtag_write(void *opaque, hwaddr addr, uint64_t value, unsigned int size)
|
||||||
{
|
{
|
||||||
ESP32C3UsbJtagState *s = ESP32C3_JTAG(opaque);
|
ESP32C3UsbJtagState *s = ESP32C3_JTAG(opaque);
|
||||||
(void) s;
|
switch (addr) {
|
||||||
if (addr == ESP32C3_JTAG_EP1_REG) {
|
case ESP32C3_JTAG_EP1_REG:
|
||||||
fputc((int)(value & 0xff), stderr);
|
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)
|
static void esp32c3_jtag_reset_hold(Object *obj, ResetType type)
|
||||||
{
|
{
|
||||||
(void) obj;
|
ESP32C3UsbJtagState *s = ESP32C3_JTAG(obj);
|
||||||
(void) type;
|
s->int_ena = 0;
|
||||||
|
esp32c3_jtag_update_irq(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void esp32c3_jtag_realize(DeviceState *dev, Error **errp)
|
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,
|
memory_region_init_io(&s->iomem, obj, &esp32c3_jtag_ops, s,
|
||||||
TYPE_ESP32C3_JTAG, ESP32C3_JTAG_REGS_SIZE);
|
TYPE_ESP32C3_JTAG, ESP32C3_JTAG_REGS_SIZE);
|
||||||
sysbus_init_mmio(sbd, &s->iomem);
|
sysbus_init_mmio(sbd, &s->iomem);
|
||||||
|
sysbus_init_irq(sbd, &s->irq);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void esp32c3_jtag_class_init(ObjectClass *klass, void *data)
|
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);
|
sysbus_realize(SYS_BUS_DEVICE(&ms->jtag), &error_fatal);
|
||||||
MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&ms->jtag), 0);
|
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);
|
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 */
|
/* RTC CNTL realization */
|
||||||
|
|||||||
@@ -22,5 +22,7 @@
|
|||||||
typedef struct ESP32C3UsbJtagState {
|
typedef struct ESP32C3UsbJtagState {
|
||||||
SysBusDevice parent_object;
|
SysBusDevice parent_object;
|
||||||
MemoryRegion iomem;
|
MemoryRegion iomem;
|
||||||
|
qemu_irq irq;
|
||||||
|
uint32_t int_ena;
|
||||||
} ESP32C3UsbJtagState;
|
} ESP32C3UsbJtagState;
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,6 @@ def run(args, interactive):
|
|||||||
for name in ("pid", "qmp.sock", "serial.log", "qemu.log", "wait.offset"):
|
for name in ("pid", "qmp.sock", "serial.log", "qemu.log", "wait.offset"):
|
||||||
(state / name).unlink(missing_ok=True)
|
(state / name).unlink(missing_ok=True)
|
||||||
(state / "serial.log").touch()
|
(state / "serial.log").touch()
|
||||||
(state / "qemu.log").touch()
|
|
||||||
(state / "wait.offset").write_text("0\n")
|
(state / "wait.offset").write_text("0\n")
|
||||||
flash = state / "flash.bin"
|
flash = state / "flash.bin"
|
||||||
build_flash(firmware, flash)
|
build_flash(firmware, flash)
|
||||||
@@ -155,7 +154,7 @@ def run(args, interactive):
|
|||||||
"-accel", "tcg",
|
"-accel", "tcg",
|
||||||
"-L", str(require_env("XTEINK_BIOS")),
|
"-L", str(require_env("XTEINK_BIOS")),
|
||||||
"-display", "gtk" if interactive else "none",
|
"-display", "gtk" if interactive else "none",
|
||||||
"-serial", "stdio" if interactive else f"file:{state / 'serial.log'}",
|
"-serial", "stdio",
|
||||||
"-monitor", "none",
|
"-monitor", "none",
|
||||||
"-qmp", f"unix:{state / 'qmp.sock'},server=on,wait=off",
|
"-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",
|
"-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:
|
if interactive:
|
||||||
process = subprocess.Popen(command)
|
process = subprocess.Popen(command)
|
||||||
else:
|
else:
|
||||||
with (state / "qemu.log").open("ab") as qemu_log:
|
with (state / "serial.log").open("ab") as serial_log:
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
command,
|
command,
|
||||||
stdin=subprocess.DEVNULL,
|
stdin=subprocess.DEVNULL,
|
||||||
stdout=qemu_log,
|
stdout=serial_log,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
start_new_session=True,
|
start_new_session=True,
|
||||||
)
|
)
|
||||||
@@ -180,7 +179,7 @@ def run(args, interactive):
|
|||||||
deadline = time.monotonic() + 10
|
deadline = time.monotonic() + 10
|
||||||
while time.monotonic() < deadline:
|
while time.monotonic() < deadline:
|
||||||
if process.poll() is not None:
|
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}")
|
raise RuntimeError(f"QEMU exited during startup\n{message}")
|
||||||
try:
|
try:
|
||||||
qmp_command(state, "query-status")
|
qmp_command(state, "query-status")
|
||||||
|
|||||||
Reference in New Issue
Block a user