From 4cfbdfbd158eb43c474b7f2fddeaeb5e8a7f8450 Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Sun, 26 Jul 2026 11:18:28 -0400 Subject: [PATCH] feat(xteink): expose the panel frame generation over QOM The wasm front end already tracked panel refreshes; make the counter unconditional and alias it onto the machine so native front ends can redraw on refresh instead of on a timer. --- hw/display/xteink_x3_eink.c | 30 ++++++++++++++++++++++-------- hw/riscv/esp32c3.c | 2 ++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/hw/display/xteink_x3_eink.c b/hw/display/xteink_x3_eink.c index 03cb622e91..da363c6d77 100644 --- a/hw/display/xteink_x3_eink.c +++ b/hw/display/xteink_x3_eink.c @@ -11,11 +11,21 @@ #include "hw/irq.h" #include "hw/display/xteink_x3_eink.h" +/* Frame Counter - Both the wasm front end and the QMP web UI poll this to redraw only on a real + * panel refresh. Only one panel is instantiated per machine, so a single counter suffices. */ +static uint32_t xteink_frame_generation; + +static void xteink_frame_generation_init(Object *obj) +{ + object_property_add_uint32_ptr(obj, "frame-generation", + &xteink_frame_generation, + OBJ_PROP_FLAG_READ); +} + #ifdef __EMSCRIPTEN__ #include static QemuConsole *xteink_wasm_console; -static uint32_t xteink_wasm_generation; EMSCRIPTEN_KEEPALIVE uintptr_t xteink_wasm_framebuffer(void) { @@ -51,17 +61,18 @@ EMSCRIPTEN_KEEPALIVE int xteink_wasm_stride(void) EMSCRIPTEN_KEEPALIVE uint32_t xteink_wasm_frame_generation(void) { - return qatomic_read(&xteink_wasm_generation); + return qatomic_read(&xteink_frame_generation); } -static void xteink_wasm_frame_updated(QemuConsole *console) +static void xteink_frame_updated(QemuConsole *console) { xteink_wasm_console = console; - qatomic_inc(&xteink_wasm_generation); + qatomic_inc(&xteink_frame_generation); } #else -static void xteink_wasm_frame_updated(QemuConsole *console) +static void xteink_frame_updated(QemuConsole *console) { + qatomic_inc(&xteink_frame_generation); } #endif @@ -73,7 +84,8 @@ static void xteink_wasm_frame_updated(QemuConsole *console) #define CMD_LUT_FIRST 0x20 #define CMD_LUT_LAST 0x24 -#define BUSY_TIME_NS (5 * SCALE_MS) +/* Mirror a real panel refresh (~1 s): shorter pulses let the completion edge pass before the firmware arms its ISR, costing a 30 s timeout. */ +#define BUSY_TIME_NS (1000 * SCALE_MS) #define XTEINK_X3_LUT_PREFIX_SIZE 13 @@ -220,7 +232,7 @@ static void xteink_x3_eink_render(XteinkX3EinkState *s) } } - xteink_wasm_frame_updated(s->console); + xteink_frame_updated(s->console); dpy_gfx_update(s->console, 0, 0, XTEINK_X3_HEIGHT, XTEINK_X3_WIDTH); } @@ -326,6 +338,7 @@ static void xteink_x3_eink_init(Object *obj) { XteinkX3EinkState *s = XTEINK_X3_EINK(obj); + xteink_frame_generation_init(obj); s->console = graphic_console_init(DEVICE(s), 0, &xteink_x3_eink_graphics_ops, s); dpy_gfx_replace_surface(s->console, @@ -378,7 +391,7 @@ static void xteink_x4_eink_render(XteinkX4EinkState *s) DisplaySurface *surface = qemu_console_surface(s->console); memset(surface_data(surface), 0xff, surface_stride(surface) * surface_height(surface)); - xteink_wasm_frame_updated(s->console); + xteink_frame_updated(s->console); dpy_gfx_update(s->console, 0, 0, 480, 800); } @@ -412,6 +425,7 @@ static void xteink_x4_eink_init(Object *obj) { XteinkX4EinkState *s = XTEINK_X4_EINK(obj); + xteink_frame_generation_init(obj); s->console = graphic_console_init(DEVICE(s), 0, &xteink_x4_eink_graphics_ops, s); dpy_gfx_replace_surface(s->console, qemu_create_displaysurface(480, 800)); diff --git a/hw/riscv/esp32c3.c b/hw/riscv/esp32c3.c index 7a583fc70a..4fa8b6e5e2 100644 --- a/hw/riscv/esp32c3.c +++ b/hw/riscv/esp32c3.c @@ -868,6 +868,8 @@ static void xteink_machine_init(MachineState *machine) DeviceState *panel = ssi_create_peripheral(ms->spi2.bus, ms->x4 ? TYPE_XTEINK_X4_EINK : TYPE_XTEINK_X3_EINK); qemu_input_handler_register(panel, &xteink_key_handler); + object_property_add_alias(OBJECT(machine), "frame-generation", + OBJECT(panel), "frame-generation"); qdev_connect_gpio_out_named(DEVICE(&ms->gpio), ESP32_GPIO_OUTPUT, 21, qdev_get_gpio_in_named(panel, SSI_GPIO_CS, 0)); qdev_connect_gpio_out_named(DEVICE(&ms->gpio), ESP32_GPIO_OUTPUT, 4,