working wasm

This commit is contained in:
2026-07-20 14:25:43 -04:00
parent d4e53ae638
commit a87c9576fc
8 changed files with 399 additions and 218 deletions
+18 -5
View File
@@ -9,7 +9,7 @@ QEMU_WASM_SRC ?= _scratch/qemu-wasm-src
WASM_OUT ?= dist/wasm
VARIANT ?= x3
.PHONY: firmware sdimage qemu qemu-wasm web web-test run run-upstream chip clean
.PHONY: firmware sdimage blank-sd-asset qemu qemu-wasm web web-test run run-upstream chip clean
firmware:
esptool --chip esp32c3 merge-bin -o flash.bin \
@@ -23,11 +23,17 @@ firmware:
sdimage:
scripts/mksd.sh $(SD_SRC) sd.img
blank-sd-asset:
@tmpdir=$$(mktemp -d); trap 'rm -rf "$$tmpdir"' EXIT; \
mkdir "$$tmpdir/empty"; \
SIZE_MB=64 scripts/mksd.sh "$$tmpdir/empty" "$$tmpdir/sd.img"; \
gzip -n -9 -c "$$tmpdir/sd.img" > web/assets/blank-sd.img.gz
qemu:
QEMU_SRC=$(QEMU_SRC) scripts/build-qemu.sh
qemu-wasm:
QEMU_WASM_SRC=$(QEMU_WASM_SRC) WASM_OUT=$(WASM_OUT) JOBS=2 scripts/build-qemu-wasm.sh
QEMU_WASM_SRC=$(QEMU_WASM_SRC) WASM_OUT=$(WASM_OUT) scripts/build-qemu-wasm.sh
# Serve over HTTPS by default so headless/LAN access is cross-origin isolated. Override with WEB_TLS=.
WEB_TLS ?= --tls
@@ -39,9 +45,16 @@ web-test:
# Select X4 with `make run VARIANT=x4`. Add `-display none` for headless.
run: qemu
$(QEMU_BIN) -machine xteink,variant=$(VARIANT) -L $(QEMU_SRC)/pc-bios \
-serial stdio -drive file=flash.bin,if=mtd,format=raw \
$(if $(wildcard sd.img),-drive file=sd.img,if=sd,format=raw)
@sd=sd.img; tmpdir=; \
if [ ! -f "$$sd" ]; then \
tmpdir=$$(mktemp -d); mkdir "$$tmpdir/empty"; sd="$$tmpdir/sd.img"; \
SIZE_MB=64 scripts/mksd.sh "$$tmpdir/empty" "$$sd" >/dev/null; \
echo "Using a blank ephemeral SD card; writes will be discarded when QEMU exits."; \
fi; \
trap 'rm -rf "$$tmpdir"' EXIT; \
$(QEMU_BIN) -machine xteink,variant=$(VARIANT) -L $(QEMU_SRC)/pc-bios \
-serial stdio -drive file=flash.bin,if=mtd,format=raw \
-drive file="$$sd",if=sd,format=raw
# Unpatched release binary retained as the fast generic ESP32-C3 baseline.
run-upstream:
+251 -153
View File
@@ -1,7 +1,7 @@
diff --git a/accel/tcg/cputlb.c b/accel/tcg/cputlb.c
diff --git c/accel/tcg/cputlb.c w/accel/tcg/cputlb.c
index b76a4eac4e..a673eb2eb7 100644
--- a/accel/tcg/cputlb.c
+++ b/accel/tcg/cputlb.c
--- c/accel/tcg/cputlb.c
+++ w/accel/tcg/cputlb.c
@@ -225,7 +225,7 @@ static void tlb_mmu_resize_locked(CPUTLBDesc *desc, CPUTLBDescFast *fast,
rate = desc->window_max_entries * 100 / old_size;
@@ -11,10 +11,10 @@ index b76a4eac4e..a673eb2eb7 100644
} else if (rate < 30 && window_expired) {
size_t ceil = pow2ceil(desc->window_max_entries);
size_t expected_rate = desc->window_max_entries * 100 / ceil;
diff --git a/accel/tcg/ldst_common.c.inc b/accel/tcg/ldst_common.c.inc
diff --git c/accel/tcg/ldst_common.c.inc w/accel/tcg/ldst_common.c.inc
index ebbf380d76..3ff0b97f27 100644
--- a/accel/tcg/ldst_common.c.inc
+++ b/accel/tcg/ldst_common.c.inc
--- c/accel/tcg/ldst_common.c.inc
+++ w/accel/tcg/ldst_common.c.inc
@@ -12,32 +12,52 @@
* Load helpers for tcg-ldst.h
*/
@@ -143,11 +143,41 @@ index ebbf380d76..3ff0b97f27 100644
}
/*
diff --git a/accel/tcg/tb-maint.c b/accel/tcg/tb-maint.c
index cc0f5afd47..575be0e359 100644
--- a/accel/tcg/tb-maint.c
+++ b/accel/tcg/tb-maint.c
@@ -854,7 +854,11 @@ static inline void tb_remove_from_jmp_list(TranslationBlock *orig, int n_orig)
diff --git c/accel/tcg/tb-maint.c w/accel/tcg/tb-maint.c
index cc0f5afd47..1095dfd912 100644
--- c/accel/tcg/tb-maint.c
+++ w/accel/tcg/tb-maint.c
@@ -755,6 +755,18 @@ static void tb_remove(TranslationBlock *tb)
}
#endif /* CONFIG_USER_ONLY */
+#if !defined(CONFIG_TCG_INTERPRETER) && defined(EMSCRIPTEN)
+#include "../../tcg/wasm32.h"
+
+static void wasm_remove_tb_instance(void *p, uint32_t hash, void *userp)
+{
+ TranslationBlock *tb = p;
+ (void)hash;
+ (void)userp;
+ remove_tb(tb->tc.ptr);
+}
+#endif
+
/* flush all the translation blocks */
static void do_tb_flush(CPUState *cpu, run_on_cpu_data tb_flush_count)
{
@@ -771,6 +783,10 @@ static void do_tb_flush(CPUState *cpu, run_on_cpu_data tb_flush_count)
tcg_flush_jmp_cache(cpu);
}
+#if defined(EMSCRIPTEN) && !defined(CONFIG_TCG_INTERPRETER)
+ qht_iter(&tb_ctx.htable, wasm_remove_tb_instance, NULL);
+ flush_tb_instances();
+#endif
qht_reset_size(&tb_ctx.htable, CODE_GEN_HTABLE_SIZE);
tb_remove_all();
@@ -854,7 +870,11 @@ static inline void tb_remove_from_jmp_list(TranslationBlock *orig, int n_orig)
void tb_reset_jump(TranslationBlock *tb, int n)
{
uintptr_t addr = (uintptr_t)(tb->tc.ptr + tb->jmp_reset_offset[n]);
@@ -159,15 +189,7 @@ index cc0f5afd47..575be0e359 100644
}
/* remove any jumps to the TB */
@@ -875,10 +879,18 @@ static inline void tb_jmp_unlink(TranslationBlock *dest)
qemu_spin_unlock(&dest->jmp_lock);
}
+#if !defined(CONFIG_TCG_INTERPRETER) && defined(EMSCRIPTEN)
+#include "../../tcg/wasm32.h"
+#endif
+
static void tb_jmp_cache_inval_tb(TranslationBlock *tb)
@@ -879,6 +899,10 @@ static void tb_jmp_cache_inval_tb(TranslationBlock *tb)
{
CPUState *cpu;
@@ -178,10 +200,10 @@ index cc0f5afd47..575be0e359 100644
if (tb_cflags(tb) & CF_PCREL) {
/* A TB may be at any virtual address */
CPU_FOREACH(cpu) {
diff --git a/accel/tcg/tcg-accel-ops-mttcg.c b/accel/tcg/tcg-accel-ops-mttcg.c
diff --git c/accel/tcg/tcg-accel-ops-mttcg.c w/accel/tcg/tcg-accel-ops-mttcg.c
index 49814ec4af..0715610d37 100644
--- a/accel/tcg/tcg-accel-ops-mttcg.c
+++ b/accel/tcg/tcg-accel-ops-mttcg.c
--- c/accel/tcg/tcg-accel-ops-mttcg.c
+++ w/accel/tcg/tcg-accel-ops-mttcg.c
@@ -61,12 +61,18 @@ static void mttcg_force_rcu(Notifier *notify, void *data)
* variable current_cpu can be used deep in the code to find the
* current CPUState for a given thread.
@@ -202,10 +224,10 @@ index 49814ec4af..0715610d37 100644
assert(tcg_enabled());
g_assert(!icount_enabled());
diff --git a/backends/meson.build b/backends/meson.build
diff --git c/backends/meson.build w/backends/meson.build
index da714b93d1..9b88d22685 100644
--- a/backends/meson.build
+++ b/backends/meson.build
--- c/backends/meson.build
+++ w/backends/meson.build
@@ -12,8 +12,10 @@ system_ss.add([files(
if host_os != 'windows'
@@ -219,10 +241,10 @@ index da714b93d1..9b88d22685 100644
endif
if host_os == 'linux'
system_ss.add(files('hostmem-memfd.c'))
diff --git a/block/file-posix.c b/block/file-posix.c
diff --git c/block/file-posix.c w/block/file-posix.c
index 90fa54352c..0fa9cc31b6 100644
--- a/block/file-posix.c
+++ b/block/file-posix.c
--- c/block/file-posix.c
+++ w/block/file-posix.c
@@ -110,6 +110,10 @@
#include <sys/diskslice.h>
#endif
@@ -256,17 +278,17 @@ index 90fa54352c..0fa9cc31b6 100644
/*
* parse_zone - Fill a zone descriptor
diff --git a/configs/devices/riscv32-softmmu/xteink.mak b/configs/devices/riscv32-softmmu/xteink.mak
diff --git c/configs/devices/riscv32-softmmu/xteink.mak w/configs/devices/riscv32-softmmu/xteink.mak
new file mode 100644
index 0000000000..a9179b4119
--- /dev/null
+++ b/configs/devices/riscv32-softmmu/xteink.mak
+++ w/configs/devices/riscv32-softmmu/xteink.mak
@@ -0,0 +1 @@
+CONFIG_RISCV_ESP32C3=y
diff --git a/configure b/configure
diff --git c/configure w/configure
index 18336376bf..4a4b9e7754 100755
--- a/configure
+++ b/configure
--- c/configure
+++ w/configure
@@ -340,7 +340,9 @@ int main(void) { return 0; }
EOF
}
@@ -298,10 +320,10 @@ index 18336376bf..4a4b9e7754 100755
! test -d "$source_path/linux-user/include/host/$host_arch" ||
! test -d "$source_path/common-user/host/$host_arch"; }; then
error_exit "linux-user/include/host/$host_arch does not exist." \
diff --git a/disas/disas-host.c b/disas/disas-host.c
diff --git c/disas/disas-host.c w/disas/disas-host.c
index 8146fafe80..2ee0605dba 100644
--- a/disas/disas-host.c
+++ b/disas/disas-host.c
--- c/disas/disas-host.c
+++ w/disas/disas-host.c
@@ -42,7 +42,7 @@ static void initialize_debug_host(CPUDebug *s)
#else
s->info.endian = BFD_ENDIAN_LITTLE;
@@ -311,10 +333,10 @@ index 8146fafe80..2ee0605dba 100644
s->info.print_insn = print_insn_tci;
#elif defined(__i386__)
s->info.mach = bfd_mach_i386_i386;
diff --git a/hw/adc/esp32c3_adc.c b/hw/adc/esp32c3_adc.c
diff --git c/hw/adc/esp32c3_adc.c w/hw/adc/esp32c3_adc.c
index 89087429c7..4d38b3e13c 100644
--- a/hw/adc/esp32c3_adc.c
+++ b/hw/adc/esp32c3_adc.c
--- c/hw/adc/esp32c3_adc.c
+++ w/hw/adc/esp32c3_adc.c
@@ -27,7 +27,7 @@ static uint64_t esp32c3_adc_read(void *opaque, hwaddr addr, unsigned size)
}
if (addr == ADC_DATA1 || addr == ADC_DATA2) {
@@ -324,11 +346,30 @@ index 89087429c7..4d38b3e13c 100644
: ADC_MAX;
}
return s->regs[addr / 4];
diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 46dbe7c521..4f05caf34e 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -856,28 +856,18 @@ static void complete_collecting_data(Flash *s)
diff --git c/hw/block/m25p80.c w/hw/block/m25p80.c
index 46dbe7c521..1ee6f8ce7e 100644
--- c/hw/block/m25p80.c
+++ w/hw/block/m25p80.c
@@ -222,7 +222,8 @@ static const FlashPartInfo known_devices[] = {
{ INFO("is25lp016d", 0x9d6015, 0, 64 << 10, 32, ER_4K) },
{ INFO("is25lp032", 0x9d6016, 0, 64 << 10, 64, ER_4K) },
{ INFO("is25lp064", 0x9d6017, 0, 64 << 10, 128, ER_4K) },
- { INFO("is25lp128", 0x9d6018, 0, 64 << 10, 256, ER_4K) },
+ { INFO("is25lp128", 0x9d6018, 0, 64 << 10, 256, ER_4K),
+ .sfdp_read = m25p80_sfdp_is25lp128 },
{ INFO("is25lp256", 0x9d6019, 0, 64 << 10, 512, ER_4K) },
{ INFO("is25wp032", 0x9d7016, 0, 64 << 10, 64, ER_4K) },
{ INFO("is25wp064", 0x9d7017, 0, 64 << 10, 128, ER_4K) },
@@ -385,6 +386,8 @@ typedef enum {
READ_FSR = 0x70,
RDCR = 0x15,
RDSFDP = 0x5a,
+ PROGRAM_ERASE_SUSPEND = 0x75,
+ PROGRAM_ERASE_RESUME = 0x7a,
READ = 0x03,
READ4 = 0x13,
@@ -856,28 +859,18 @@ static void complete_collecting_data(Flash *s)
break;
case RDID_90:
case RDID_AB:
@@ -367,10 +408,49 @@ index 46dbe7c521..4f05caf34e 100644
break;
case RDSFDP:
diff --git a/hw/display/xteink_x3_eink.c b/hw/display/xteink_x3_eink.c
@@ -1414,6 +1407,9 @@ static void decode_new_cmd(Flash *s, uint32_t value)
}
break;
case NOP:
+ case PROGRAM_ERASE_SUSPEND:
+ case PROGRAM_ERASE_RESUME:
+ /* Program and erase complete synchronously in this model. */
break;
case EN_4BYTE_ADDR:
s->four_bytes_address_mode = true;
diff --git c/hw/block/m25p80_sfdp.c w/hw/block/m25p80_sfdp.c
index a03a291a09..6e01178299 100644
--- c/hw/block/m25p80_sfdp.c
+++ w/hw/block/m25p80_sfdp.c
@@ -479,3 +479,12 @@ static const uint8_t sfdp_is25wp256[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};
define_sfdp_read(is25wp256);
+
+uint8_t m25p80_sfdp_is25lp128(uint32_t addr)
+{
+ /* The sibling parts share SFDP data except for the density field. */
+ if (addr == 0x37) {
+ return 0x07;
+ }
+ return m25p80_sfdp_is25wp256(addr);
+}
diff --git c/hw/block/m25p80_sfdp.h w/hw/block/m25p80_sfdp.h
index 35785686a0..e6bfdfa7e8 100644
--- c/hw/block/m25p80_sfdp.h
+++ w/hw/block/m25p80_sfdp.h
@@ -28,6 +28,7 @@ uint8_t m25p80_sfdp_w25q512jv(uint32_t addr);
uint8_t m25p80_sfdp_w25q80bl(uint32_t addr);
uint8_t m25p80_sfdp_w25q01jvq(uint32_t addr);
+uint8_t m25p80_sfdp_is25lp128(uint32_t addr);
uint8_t m25p80_sfdp_is25wp256(uint32_t addr);
#endif
diff --git c/hw/display/xteink_x3_eink.c w/hw/display/xteink_x3_eink.c
index b699abae4e..8a177e9159 100644
--- a/hw/display/xteink_x3_eink.c
+++ b/hw/display/xteink_x3_eink.c
--- c/hw/display/xteink_x3_eink.c
+++ w/hw/display/xteink_x3_eink.c
@@ -9,6 +9,60 @@
#include "hw/irq.h"
#include "hw/display/xteink_x3_eink.h"
@@ -448,10 +528,10 @@ index b699abae4e..8a177e9159 100644
dpy_gfx_update(s->console, 0, 0, 480, 800);
}
diff --git a/hw/gpio/esp32_gpio.c b/hw/gpio/esp32_gpio.c
diff --git c/hw/gpio/esp32_gpio.c w/hw/gpio/esp32_gpio.c
index 9ec8bd9244..754990968c 100644
--- a/hw/gpio/esp32_gpio.c
+++ b/hw/gpio/esp32_gpio.c
--- c/hw/gpio/esp32_gpio.c
+++ w/hw/gpio/esp32_gpio.c
@@ -38,10 +38,20 @@ static void esp32_gpio_drive_outputs(Esp32GpioState *s)
}
}
@@ -493,10 +573,10 @@ index 9ec8bd9244..754990968c 100644
default:
return 0;
}
diff --git a/hw/misc/esp32c3_cache.c b/hw/misc/esp32c3_cache.c
diff --git c/hw/misc/esp32c3_cache.c w/hw/misc/esp32c3_cache.c
index 1879b5c2df..e3f09cc51b 100644
--- a/hw/misc/esp32c3_cache.c
+++ b/hw/misc/esp32c3_cache.c
--- c/hw/misc/esp32c3_cache.c
+++ w/hw/misc/esp32c3_cache.c
@@ -58,7 +58,6 @@ static inline uint32_t esp32c3_read_mmu_value(ESP32C3CacheState *s, hwaddr reg_a
static inline void esp32c3_write_mmu_value(ESP32C3CacheState *s, hwaddr reg_addr, uint32_t value)
@@ -533,10 +613,10 @@ index 1879b5c2df..e3f09cc51b 100644
}
static void esp32c3_cache_init(Object *obj)
diff --git a/hw/misc/esp_sha.c b/hw/misc/esp_sha.c
diff --git c/hw/misc/esp_sha.c w/hw/misc/esp_sha.c
index c9fa2e610a..cc186595eb 100644
--- a/hw/misc/esp_sha.c
+++ b/hw/misc/esp_sha.c
--- c/hw/misc/esp_sha.c
+++ w/hw/misc/esp_sha.c
@@ -22,46 +22,71 @@
#define SHA_WARNING 0
#define SHA_DEBUG 0
@@ -626,10 +706,10 @@ index c9fa2e610a..cc186595eb 100644
.len = sizeof(struct sha512_state)
},
};
diff --git a/hw/riscv/esp32c3.c b/hw/riscv/esp32c3.c
diff --git c/hw/riscv/esp32c3.c w/hw/riscv/esp32c3.c
index 2f1ccbfbb0..febc96bfe3 100644
--- a/hw/riscv/esp32c3.c
+++ b/hw/riscv/esp32c3.c
--- c/hw/riscv/esp32c3.c
+++ w/hw/riscv/esp32c3.c
@@ -57,6 +57,10 @@
#include "hw/sd/sd.h"
#include "hw/net/can/esp32c3_twai.h"
@@ -752,10 +832,10 @@ index 2f1ccbfbb0..febc96bfe3 100644
/* X3 exposes the fingerprint chips; X4 omits them so stock firmware's
* all-NAK probe selects X4. The X4 panel is a blank protocol stub. */
diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h
diff --git c/include/exec/exec-all.h w/include/exec/exec-all.h
index 2e4c4cc4b4..99e6da08c9 100644
--- a/include/exec/exec-all.h
+++ b/include/exec/exec-all.h
--- c/include/exec/exec-all.h
+++ w/include/exec/exec-all.h
@@ -453,7 +453,7 @@ void tb_invalidate_phys_range(tb_page_addr_t start, tb_page_addr_t last);
void tb_set_jmp_target(TranslationBlock *tb, int n, uintptr_t addr);
@@ -765,20 +845,20 @@ index 2e4c4cc4b4..99e6da08c9 100644
extern __thread uintptr_t tci_tb_ptr;
# define GETPC() tci_tb_ptr
#else
diff --git a/include/hw/gpio/esp32_gpio.h b/include/hw/gpio/esp32_gpio.h
diff --git c/include/hw/gpio/esp32_gpio.h w/include/hw/gpio/esp32_gpio.h
index e2f52ad702..721f5de5df 100644
--- a/include/hw/gpio/esp32_gpio.h
+++ b/include/hw/gpio/esp32_gpio.h
--- c/include/hw/gpio/esp32_gpio.h
+++ w/include/hw/gpio/esp32_gpio.h
@@ -33,3 +33,5 @@ typedef struct Esp32GpioState {
typedef struct Esp32GpioClass {
SysBusDeviceClass parent_class;
} Esp32GpioClass;
+
+void esp32_gpio_set_input_level(Esp32GpioState *s, int pin, bool level);
diff --git a/include/qemu/atomic.h b/include/qemu/atomic.h
diff --git c/include/qemu/atomic.h w/include/qemu/atomic.h
index 7a3f2e6576..50dc235043 100644
--- a/include/qemu/atomic.h
+++ b/include/qemu/atomic.h
--- c/include/qemu/atomic.h
+++ w/include/qemu/atomic.h
@@ -75,7 +75,6 @@
#else
# define ATOMIC_REG_SIZE sizeof(void *)
@@ -856,10 +936,10 @@ index 7a3f2e6576..50dc235043 100644
qatomic_cmpxchg__nocheck(ptr, old, new); \
})
diff --git a/include/qemu/cacheflush.h b/include/qemu/cacheflush.h
diff --git c/include/qemu/cacheflush.h w/include/qemu/cacheflush.h
index ae20bcda73..3f414fb7c9 100644
--- a/include/qemu/cacheflush.h
+++ b/include/qemu/cacheflush.h
--- c/include/qemu/cacheflush.h
+++ w/include/qemu/cacheflush.h
@@ -19,7 +19,7 @@
* mappings of the same physical page(s).
*/
@@ -869,10 +949,10 @@ index ae20bcda73..3f414fb7c9 100644
static inline void flush_idcache_range(uintptr_t rx, uintptr_t rw, size_t len)
{
diff --git a/include/qemu/int128.h b/include/qemu/int128.h
diff --git c/include/qemu/int128.h w/include/qemu/int128.h
index 174bd7dafb..0305e262ef 100644
--- a/include/qemu/int128.h
+++ b/include/qemu/int128.h
--- c/include/qemu/int128.h
+++ w/include/qemu/int128.h
@@ -8,7 +8,7 @@
* But libffi does not support __int128_t, and therefore cannot pass
* or return values of this type, force use of the Int128 struct.
@@ -882,10 +962,10 @@ index 174bd7dafb..0305e262ef 100644
typedef __int128_t Int128;
typedef __int128_t __attribute__((aligned(16))) Int128Aligned;
diff --git a/include/qemu/osdep.h b/include/qemu/osdep.h
diff --git c/include/qemu/osdep.h w/include/qemu/osdep.h
index fdff07fd99..e95faa4478 100644
--- a/include/qemu/osdep.h
+++ b/include/qemu/osdep.h
--- c/include/qemu/osdep.h
+++ w/include/qemu/osdep.h
@@ -133,7 +133,7 @@ QEMU_EXTERN_C int daemon(int, int);
#include <setjmp.h>
#include <signal.h>
@@ -920,10 +1000,10 @@ index fdff07fd99..e95faa4478 100644
struct iovec {
void *iov_base;
size_t iov_len;
diff --git a/include/tcg/helper-info.h b/include/tcg/helper-info.h
diff --git c/include/tcg/helper-info.h w/include/tcg/helper-info.h
index 909fe73afa..c5064473ac 100644
--- a/include/tcg/helper-info.h
+++ b/include/tcg/helper-info.h
--- c/include/tcg/helper-info.h
+++ w/include/tcg/helper-info.h
@@ -9,7 +9,7 @@
#ifndef TCG_HELPER_INFO_H
#define TCG_HELPER_INFO_H
@@ -949,10 +1029,10 @@ index 909fe73afa..c5064473ac 100644
unsigned typemask : 32;
unsigned flags : 8;
diff --git a/include/tcg/tcg-opc.h b/include/tcg/tcg-opc.h
diff --git c/include/tcg/tcg-opc.h w/include/tcg/tcg-opc.h
index 546eb49c11..834757c3a4 100644
--- a/include/tcg/tcg-opc.h
+++ b/include/tcg/tcg-opc.h
--- c/include/tcg/tcg-opc.h
+++ w/include/tcg/tcg-opc.h
@@ -305,7 +305,7 @@ DEF(last_generic, 0, 0, 0, TCG_OPF_NOT_PRESENT)
#include "tcg-target.opc.h"
#endif
@@ -962,10 +1042,10 @@ index 546eb49c11..834757c3a4 100644
/* These opcodes are only for use between the tci generator and interpreter. */
DEF(tci_movi, 1, 0, 1, TCG_OPF_NOT_PRESENT)
DEF(tci_movl, 1, 0, 1, TCG_OPF_NOT_PRESENT)
diff --git a/include/tcg/tcg.h b/include/tcg/tcg.h
diff --git c/include/tcg/tcg.h w/include/tcg/tcg.h
index 9b82844dab..2cdfe0617b 100644
--- a/include/tcg/tcg.h
+++ b/include/tcg/tcg.h
--- c/include/tcg/tcg.h
+++ w/include/tcg/tcg.h
@@ -1010,7 +1010,7 @@ static inline size_t tcg_current_code_size(TCGContext *s)
#define TB_EXIT_IDXMAX 1
#define TB_EXIT_REQUESTED 3
@@ -975,10 +1055,10 @@ index 9b82844dab..2cdfe0617b 100644
uintptr_t tcg_qemu_tb_exec(CPUArchState *env, const void *tb_ptr);
#else
typedef uintptr_t tcg_prologue_fn(CPUArchState *env, const void *tb_ptr);
diff --git a/meson.build b/meson.build
diff --git c/meson.build w/meson.build
index 7f28871776..6e558969d4 100644
--- a/meson.build
+++ b/meson.build
--- c/meson.build
+++ w/meson.build
@@ -45,9 +45,9 @@ genh = []
qapi_trace_events = []
@@ -1067,10 +1147,10 @@ index 7f28871776..6e558969d4 100644
# slirp < 4.7 is incompatible with CFI support in QEMU. This is because
# it passes function pointers within libslirp as callbacks for timers.
diff --git a/meson_options.txt b/meson_options.txt
diff --git c/meson_options.txt w/meson_options.txt
index 5eeaf3eee5..76f7a65b40 100644
--- a/meson_options.txt
+++ b/meson_options.txt
--- c/meson_options.txt
+++ w/meson_options.txt
@@ -34,7 +34,7 @@ option('fuzzing_engine', type : 'string', value : '',
option('trace_file', type: 'string', value: 'trace',
description: 'Trace file prefix for simple backend')
@@ -1080,10 +1160,10 @@ index 5eeaf3eee5..76f7a65b40 100644
value: 'auto', description: 'coroutine backend to use')
# Everything else can be set via --enable/--disable-* option
diff --git a/os-posix.c b/os-posix.c
diff --git c/os-posix.c w/os-posix.c
index 43f9a43f3f..595fa47866 100644
--- a/os-posix.c
+++ b/os-posix.c
--- c/os-posix.c
+++ w/os-posix.c
@@ -148,11 +148,13 @@ static void change_process_uid(void)
exit(1);
}
@@ -1098,10 +1178,10 @@ index 43f9a43f3f..595fa47866 100644
} else {
if (setgroups(1, &user_gid) < 0) {
error_report("Failed to setgroups(1, [%d])",
diff --git a/qom/object.c b/qom/object.c
diff --git c/qom/object.c w/qom/object.c
index 9edc06d391..c3d657e6cb 100644
--- a/qom/object.c
+++ b/qom/object.c
--- c/qom/object.c
+++ w/qom/object.c
@@ -1197,7 +1197,8 @@ GSList *object_class_get_list(const char *implements_type,
return list;
}
@@ -1121,10 +1201,10 @@ index 9edc06d391..c3d657e6cb 100644
}
Object *object_ref(void *objptr)
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
diff --git c/scripts/meson-buildoptions.sh w/scripts/meson-buildoptions.sh
index a8066aab03..bd3ae90008 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
--- c/scripts/meson-buildoptions.sh
+++ w/scripts/meson-buildoptions.sh
@@ -80,7 +80,7 @@ meson_options_help() {
printf "%s\n" ' --tls-priority=VALUE Default TLS protocol/cipher priority string'
printf "%s\n" ' [NORMAL]'
@@ -1134,10 +1214,10 @@ index a8066aab03..bd3ae90008 100644
printf "%s\n" ' --with-pkgversion=VALUE use specified string as sub-version of the'
printf "%s\n" ' package'
printf "%s\n" ' --with-suffix=VALUE Suffix for QEMU data/modules/config directories'
diff --git a/system/memory.c b/system/memory.c
diff --git c/system/memory.c w/system/memory.c
index 85f6834cb3..3958314765 100644
--- a/system/memory.c
+++ b/system/memory.c
--- c/system/memory.c
+++ w/system/memory.c
@@ -1636,7 +1636,7 @@ bool memory_region_init_resizeable_ram(MemoryRegion *mr,
return true;
}
@@ -1147,10 +1227,10 @@ index 85f6834cb3..3958314765 100644
bool memory_region_init_ram_from_file(MemoryRegion *mr,
Object *owner,
const char *name,
diff --git a/system/physmem.c b/system/physmem.c
diff --git c/system/physmem.c w/system/physmem.c
index 75389064a8..addd5d0c4b 100644
--- a/system/physmem.c
+++ b/system/physmem.c
--- c/system/physmem.c
+++ w/system/physmem.c
@@ -1235,7 +1235,7 @@ long qemu_maxrampagesize(void)
return pagesize;
}
@@ -1178,10 +1258,10 @@ index 75389064a8..addd5d0c4b 100644
} else if (block->fd >= 0) {
qemu_ram_munmap(block->fd, block->host, block->max_length);
close(block->fd);
diff --git a/tcg/meson.build b/tcg/meson.build
diff --git c/tcg/meson.build w/tcg/meson.build
index 69ebb4908a..0894a577c2 100644
--- a/tcg/meson.build
+++ b/tcg/meson.build
--- c/tcg/meson.build
+++ w/tcg/meson.build
@@ -15,11 +15,18 @@ tcg_ss.add(files(
'tcg-op-vec.c',
))
@@ -1201,10 +1281,10 @@ index 69ebb4908a..0894a577c2 100644
endif
tcg_ss.add(when: libdw, if_true: files('debuginfo.c'))
diff --git a/tcg/region.c b/tcg/region.c
diff --git c/tcg/region.c w/tcg/region.c
index 478ec051c4..41895c68fa 100644
--- a/tcg/region.c
+++ b/tcg/region.c
--- c/tcg/region.c
+++ w/tcg/region.c
@@ -568,7 +568,7 @@ static int alloc_code_gen_buffer_anon(size_t size, int prot,
return prot;
}
@@ -1232,10 +1312,10 @@ index 478ec051c4..41895c68fa 100644
if (tcg_splitwx_diff == 0) {
need_prot |= host_prot_read_exec();
}
diff --git a/tcg/tcg.c b/tcg/tcg.c
diff --git c/tcg/tcg.c w/tcg/tcg.c
index 6dc3072f92..214e66e9d6 100644
--- a/tcg/tcg.c
+++ b/tcg/tcg.c
--- c/tcg/tcg.c
+++ w/tcg/tcg.c
@@ -131,6 +131,10 @@ static void tcg_out_goto_tb(TCGContext *s, int which);
static void tcg_out_op(TCGContext *s, TCGOpcode opc,
const TCGArg args[TCG_MAX_OP_ARGS],
@@ -1734,12 +1814,12 @@ index 6dc3072f92..214e66e9d6 100644
return tcg_current_code_size(s);
}
diff --git a/tcg/wasm32.c b/tcg/wasm32.c
diff --git c/tcg/wasm32.c w/tcg/wasm32.c
new file mode 100644
index 0000000000..dfcbf1493b
index 0000000000..55ad790f17
--- /dev/null
+++ b/tcg/wasm32.c
@@ -0,0 +1,1230 @@
+++ w/tcg/wasm32.c
@@ -0,0 +1,1246 @@
+/*
+ * Tiny Code Generator for QEMU
+ *
@@ -1794,7 +1874,7 @@ index 0000000000..dfcbf1493b
+ const import_vec_size = memory_v.getInt32(wasm_begin + wasm_size, true);
+ const import_vec_begin = wasm_begin + wasm_size + 4;
+
+ const wasm = HEAP8.subarray(wasm_begin, wasm_begin + wasm_size);
+ const wasm = Uint8Array.from(HEAP8.subarray(wasm_begin, wasm_begin + wasm_size));
+ var helper = {};
+ for (var i = 0; i < import_vec_size / 4; i++) {
+ helper[i] = wasmTable.get(memory_v.getInt32(import_vec_begin + i * 4, true));
@@ -1821,6 +1901,19 @@ index 0000000000..dfcbf1493b
+ return 0;
+});
+
+EM_JS(void, flush_tb_instances, (), {
+ const tb = Module.__wasm32_tb;
+ if (!tb) {
+ return;
+ }
+ const memory_v = new DataView(HEAP8.buffer);
+ const remove_n = memory_v.getInt32(tb.to_remove_instance_idx_ptr, true);
+ for (var i = 0; i < remove_n * 4; i += 4) {
+ removeFunction(memory_v.getInt32(tb.to_remove_instance_ptr + i, true));
+ }
+ memory_v.setInt32(tb.to_remove_instance_idx_ptr, 0, true);
+});
+
+__thread bool initdone = false;
+__thread int cur_core_num = -1;
+__thread int export_vec_off = -1;
@@ -1838,6 +1931,9 @@ index 0000000000..dfcbf1493b
+ return;
+ }
+ *slot = 0;
+ if (to_remove_instance_idx == TO_REMOVE_INSTANCE_SIZE) {
+ flush_tb_instances();
+ }
+ to_remove_instance[to_remove_instance_idx++] = f;
+}
+
@@ -2970,12 +3066,12 @@ index 0000000000..dfcbf1493b
+}
+
+#endif
diff --git a/tcg/wasm32.h b/tcg/wasm32.h
diff --git c/tcg/wasm32.h w/tcg/wasm32.h
new file mode 100644
index 0000000000..1861510f0b
index 0000000000..1527bf4875
--- /dev/null
+++ b/tcg/wasm32.h
@@ -0,0 +1,44 @@
+++ w/tcg/wasm32.h
@@ -0,0 +1,46 @@
+#ifndef TCG_WASM32_H
+#define TCG_WASM32_H
+
@@ -3015,16 +3111,18 @@ index 0000000000..1861510f0b
+
+void remove_tb(void *tb_ptr);
+
+void flush_tb_instances(void);
+
+void init_wasm32();
+
+#define INSTANTIATE_NUM 1500
+
+#endif
diff --git a/tcg/wasm32/tcg-target-con-set.h b/tcg/wasm32/tcg-target-con-set.h
diff --git c/tcg/wasm32/tcg-target-con-set.h w/tcg/wasm32/tcg-target-con-set.h
new file mode 100644
index 0000000000..f50a6793b7
--- /dev/null
+++ b/tcg/wasm32/tcg-target-con-set.h
+++ w/tcg/wasm32/tcg-target-con-set.h
@@ -0,0 +1,15 @@
+/*
+ * C_On_Im(...) defines a constraint set with <n> outputs and <m> inputs.
@@ -3041,22 +3139,22 @@ index 0000000000..f50a6793b7
+C_O2_I1(r, r, r)
+C_O2_I2(r, r, r, r)
+C_O2_I4(r, r, r, r, r, r)
diff --git a/tcg/wasm32/tcg-target-con-str.h b/tcg/wasm32/tcg-target-con-str.h
diff --git c/tcg/wasm32/tcg-target-con-str.h w/tcg/wasm32/tcg-target-con-str.h
new file mode 100644
index 0000000000..56d7aff291
--- /dev/null
+++ b/tcg/wasm32/tcg-target-con-str.h
+++ w/tcg/wasm32/tcg-target-con-str.h
@@ -0,0 +1,5 @@
+/*
+ * Define constraint letters for register sets:
+ * REGS(letter, register_mask)
+ */
+REGS('r', MAKE_64BIT_MASK(0, TCG_TARGET_NB_REGS))
diff --git a/tcg/wasm32/tcg-target-reg-bits.h b/tcg/wasm32/tcg-target-reg-bits.h
diff --git c/tcg/wasm32/tcg-target-reg-bits.h w/tcg/wasm32/tcg-target-reg-bits.h
new file mode 100644
index 0000000000..196395aef9
--- /dev/null
+++ b/tcg/wasm32/tcg-target-reg-bits.h
+++ w/tcg/wasm32/tcg-target-reg-bits.h
@@ -0,0 +1,6 @@
+#ifndef TCG_TARGET_REG_BITS_H
+#define TCG_TARGET_REG_BITS_H
@@ -3064,11 +3162,11 @@ index 0000000000..196395aef9
+#define TCG_TARGET_REG_BITS 64
+
+#endif
diff --git a/tcg/wasm32/tcg-target.c.inc b/tcg/wasm32/tcg-target.c.inc
diff --git c/tcg/wasm32/tcg-target.c.inc w/tcg/wasm32/tcg-target.c.inc
new file mode 100644
index 0000000000..9e82297a3f
--- /dev/null
+++ b/tcg/wasm32/tcg-target.c.inc
+++ w/tcg/wasm32/tcg-target.c.inc
@@ -0,0 +1,3824 @@
+/*
+ * Tiny Code Generator for QEMU
@@ -6894,11 +6992,11 @@ index 0000000000..9e82297a3f
+static inline void tcg_target_qemu_prologue(TCGContext *s)
+{
+}
diff --git a/tcg/wasm32/tcg-target.h b/tcg/wasm32/tcg-target.h
diff --git c/tcg/wasm32/tcg-target.h w/tcg/wasm32/tcg-target.h
new file mode 100644
index 0000000000..7346c3a76e
--- /dev/null
+++ b/tcg/wasm32/tcg-target.h
+++ w/tcg/wasm32/tcg-target.h
@@ -0,0 +1,153 @@
+/*
+ * Tiny Code Generator for QEMU
@@ -7053,10 +7151,10 @@ index 0000000000..7346c3a76e
+#define TCG_TARGET_HAS_MEMORY_BSWAP 0
+
+#endif /* TCG_TARGET_H */
diff --git a/util/cacheflush.c b/util/cacheflush.c
diff --git c/util/cacheflush.c w/util/cacheflush.c
index a08906155a..babbb644e5 100644
--- a/util/cacheflush.c
+++ b/util/cacheflush.c
--- c/util/cacheflush.c
+++ w/util/cacheflush.c
@@ -225,7 +225,7 @@ static void __attribute__((constructor)) init_cache_info(void)
* Architecture (+ OS) specific cache flushing mechanisms.
*/
@@ -7066,11 +7164,11 @@ index a08906155a..babbb644e5 100644
/* Caches are coherent and do not require flushing; symbol inline. */
diff --git a/util/coroutine-fiber.c b/util/coroutine-fiber.c
diff --git c/util/coroutine-fiber.c w/util/coroutine-fiber.c
new file mode 100644
index 0000000000..2ce1575362
--- /dev/null
+++ b/util/coroutine-fiber.c
+++ w/util/coroutine-fiber.c
@@ -0,0 +1,123 @@
+/*
+ * emscripten fiber coroutine initialization code
@@ -7195,10 +7293,10 @@ index 0000000000..2ce1575362
+
+ return self && self->caller;
+}
diff --git a/util/int128.c b/util/int128.c
diff --git c/util/int128.c w/util/int128.c
index df6c6331bd..c442ff4da8 100644
--- a/util/int128.c
+++ b/util/int128.c
--- c/util/int128.c
+++ w/util/int128.c
@@ -144,7 +144,7 @@ Int128 int128_rems(Int128 a, Int128 b)
return r;
}
@@ -7208,10 +7306,10 @@ index df6c6331bd..c442ff4da8 100644
Int128 int128_divu(Int128 a_s, Int128 b_s)
{
diff --git a/util/meson.build b/util/meson.build
diff --git c/util/meson.build w/util/meson.build
index 5d8bef9891..e107fbfb69 100644
--- a/util/meson.build
+++ b/util/meson.build
--- c/util/meson.build
+++ w/util/meson.build
@@ -11,7 +11,9 @@ if host_os != 'windows'
endif
util_ss.add(files('compatfd.c'))
@@ -7223,10 +7321,10 @@ index 5d8bef9891..e107fbfb69 100644
freebsd_dep = []
if host_os == 'freebsd'
freebsd_dep = util
diff --git a/util/mmap-alloc.c b/util/mmap-alloc.c
diff --git c/util/mmap-alloc.c w/util/mmap-alloc.c
index ed14f9c64d..8c8708ca4d 100644
--- a/util/mmap-alloc.c
+++ b/util/mmap-alloc.c
--- c/util/mmap-alloc.c
+++ w/util/mmap-alloc.c
@@ -250,6 +250,11 @@ void *qemu_ram_mmap(int fd,
uint32_t qemu_map_flags,
off_t map_offset)
@@ -7247,10 +7345,10 @@ index ed14f9c64d..8c8708ca4d 100644
}
void qemu_ram_munmap(int fd, void *ptr, size_t size)
diff --git a/util/oslib-posix.c b/util/oslib-posix.c
diff --git c/util/oslib-posix.c w/util/oslib-posix.c
index 11b35e48fb..760c3d4f4c 100644
--- a/util/oslib-posix.c
+++ b/util/oslib-posix.c
--- c/util/oslib-posix.c
+++ w/util/oslib-posix.c
@@ -58,6 +58,7 @@
#include <lwp.h>
#endif
+11 -8
View File
@@ -10,7 +10,7 @@ TAG="esp-develop-9.2.2-20260417"
COMMIT="40edccac415693c5130f91c01d84176ae6008566"
QEMU_WASM_COMMIT="0ef7b4e2814b231705d8371dd7997f5b72e70baf"
IMAGE="xteink-qemu-wasm-build"
JOBS="${JOBS:-2}"
JOBS="${JOBS:-$(nproc 2>/dev/null || echo 2)}"
if [ -n "${CONTAINER_ENGINE:-}" ]; then
ENGINE="$CONTAINER_ENGINE"
@@ -30,19 +30,22 @@ if [ "$(git rev-parse HEAD)" != "$COMMIT" ]; then
exit 1
fi
if [ ! -f hw/display/xteink_x3_eink.c ] || [ ! -f tcg/wasm32.c ]; then
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "QEMU source has changes that conflict with the WASM patches" >&2
exit 1
fi
# Re-apply Patches On Drift - The source tree is a build artifact of the patches; reset and re-apply whenever a patch is newer than the last apply so `make web` never silently rebuilds stale sources.
STAMP=".xteink-patch-stamp"
if [ ! -f hw/display/xteink_x3_eink.c ] || [ ! -f tcg/wasm32.c ] \
|| [ "$ROOT/qemu/patches/0001-xteink-machine.patch" -nt "$STAMP" ] \
|| [ "$ROOT/qemu/patches/0002-qemu-wasm.patch" -nt "$STAMP" ]; then
git reset --hard "$COMMIT"
git clean -fdx -e build
git apply "$ROOT/qemu/patches/0001-xteink-machine.patch"
git apply --whitespace=nowarn "$ROOT/qemu/patches/0002-qemu-wasm.patch"
touch "$STAMP"
fi
printf 'Building Emscripten environment (qemu-wasm %s)\n' "$QEMU_WASM_COMMIT"
"$ENGINE" build -t "$IMAGE" -f "$ROOT/qemu/wasm/Dockerfile" "$ROOT/qemu/wasm"
FLAGS="-O3 -Wno-error=unused-command-line-argument -matomics -mbulk-memory -DNDEBUG -DG_DISABLE_ASSERT -D_GNU_SOURCE -sASYNCIFY=1 -pthread -sPROXY_TO_PTHREAD=1 -sFORCE_FILESYSTEM -sALLOW_TABLE_GROWTH -sINITIAL_MEMORY=512MB -sWASM_BIGINT -sMALLOC=mimalloc -sEXPORT_ES6=1 -sASYNCIFY_IMPORTS=ffi_call_js"
FLAGS="-O3 -Wno-error=unused-command-line-argument -matomics -mbulk-memory -DNDEBUG -DG_DISABLE_ASSERT -D_GNU_SOURCE -sASYNCIFY=1 -pthread -sPROXY_TO_PTHREAD=1 -sFORCE_FILESYSTEM -sALLOW_TABLE_GROWTH -sINITIAL_MEMORY=64MB -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=1GB -sSTACK_SIZE=1048576 -sWASM_BIGINT -sMALLOC=mimalloc -sEXPORT_ES6=1 -sASYNCIFY_IMPORTS=ffi_call_js"
if [ ! -f build/build.ninja ]; then
"$ENGINE" run --rm -v "$SRC:/qemu" "$IMAGE" bash -lc "
@@ -62,7 +65,7 @@ if [ ! -f build/build.ninja ]; then
--disable-tools \\
--extra-cflags='$FLAGS' \\
--extra-cxxflags='$FLAGS' \\
--extra-ldflags='-sEXPORTED_RUNTIME_METHODS=getTempRet0,setTempRet0,addFunction,removeFunction,TTY,FS'
--extra-ldflags='-sINITIAL_MEMORY=64MB -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=1GB -sEXPORTED_RUNTIME_METHODS=getTempRet0,setTempRet0,addFunction,removeFunction,TTY,FS'
"
fi
+5 -1
View File
@@ -1,6 +1,7 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { pathToFileURL } from 'node:url';
import { gunzipSync } from 'node:zlib';
const artifactRoot = process.argv[2];
if (!artifactRoot) {
@@ -9,12 +10,14 @@ if (!artifactRoot) {
const moduleUrl = pathToFileURL(`${artifactRoot}/qemu-system-riscv32.js`);
const rom = new Uint8Array(await readFile(`${artifactRoot}/esp32c3-rom.bin`));
const sdCard = gunzipSync(await readFile('web/assets/blank-sd.img.gz'));
const { default: createQemu } = await import(moduleUrl);
const options = {
arguments: [
'-S', '-machine', 'xteink,variant=x3', '-L', '/bios',
'-S', '-machine', 'xteink,variant=x3', '-accel', 'tcg,tb-size=16', '-L', '/bios',
'-display', 'none', '-serial', 'null', '-nic', 'none',
'-drive', 'file=/flash.bin,if=mtd,format=raw',
'-drive', 'file=/sd.img,if=sd,format=raw',
],
mainScriptUrlOrBlob: moduleUrl.href,
};
@@ -22,6 +25,7 @@ options.preRun = [() => {
options.FS.mkdir('/bios');
options.FS.writeFile('/bios/esp32c3-rom.bin', rom);
options.FS.writeFile('/flash.bin', new Uint8Array(16 * 1024 * 1024));
options.FS.writeFile('/sd.img', sdCard);
}];
const module = await createQemu(options);
+11 -1
View File
@@ -1,5 +1,6 @@
import assert from 'node:assert/strict';
import { BUTTONS, setButton, mergeFirmware } from '../web/app.js';
import { readFile } from 'node:fs/promises';
import { BUTTONS, setButton, mergeFirmware, loadEphemeralSdCard } from '../web/app.js';
// Merge - app image is placed at 0x10000 in a 0xFF-erased 16 MB flash with bootloader and partitions.
const bootloader = new Uint8Array([1, 2, 3]);
@@ -14,6 +15,15 @@ assert.equal(flash[0x10000], 0xe9);
assert.equal(flash[0x20000], 0xff);
assert.throws(() => mergeFirmware(new Uint8Array([0x00]), bootloader, partitions), /0xE9/);
const compressedSd = await readFile('web/assets/blank-sd.img.gz');
const fetchSd = async () => new Response(compressedSd);
const firstSd = await loadEphemeralSdCard(import.meta.url, fetchSd);
const secondSd = await loadEphemeralSdCard(import.meta.url, fetchSd);
assert.equal(firstSd.length, 64 * 1024 * 1024);
assert.deepEqual(firstSd.slice(510, 512), new Uint8Array([0x55, 0xaa]));
firstSd[0] ^= 0xff;
assert.notEqual(firstSd[0], secondSd[0]);
const calls = [];
const module = {
_xteink_wasm_set_adc: (...args) => calls.push(['adc', ...args]),
+70 -19
View File
@@ -38,6 +38,18 @@ async function fetchBin(url) {
return new Uint8Array(await response.arrayBuffer());
}
export async function loadEphemeralSdCard(assetRoot = import.meta.url, fetchFn = fetch) {
const response = await fetchFn(new URL('assets/blank-sd.img.gz', assetRoot));
if (!response.ok) {
throw new Error(`Could not load blank SD card (${response.status}).`);
}
if (!response.body || typeof DecompressionStream === 'undefined') {
throw new Error('This browser cannot decompress the blank SD card image.');
}
const stream = response.body.pipeThrough(new DecompressionStream('gzip'));
return new Uint8Array(await new Response(stream).arrayBuffer());
}
// A full 16 MB image is used as-is; anything else is treated as a CrossPoint app image and merged with the bundled bootloader and partition table.
async function prepareFirmware(bytes, assetRoot) {
if (bytes.length === FLASH_SIZE) {
@@ -154,14 +166,19 @@ function enableControls(module) {
}
let logEl = null;
let persistLog = false;
const logBuffer = [];
const LOG_MAX_LINES = 1000;
const DEBUG_LOG_KEY = 'xteink-debug-log';
function logLine(message) {
logBuffer.push(message);
if (logBuffer.length > LOG_MAX_LINES) {
logBuffer.splice(0, logBuffer.length - LOG_MAX_LINES);
}
if (persistLog) {
sessionStorage.setItem(DEBUG_LOG_KEY, logBuffer.join('\n'));
}
if (logEl) {
const atBottom = logEl.scrollTop + logEl.clientHeight >= logEl.scrollHeight - 4;
logEl.textContent = logBuffer.join('\n');
@@ -171,32 +188,39 @@ function logLine(message) {
}
}
async function boot(file, variant, setStatus) {
async function boot(file, variant, setStatus, sdCardProvider = loadEphemeralSdCard) {
if (!crossOriginIsolated) {
throw new Error('This emulator requires COOP/COEP headers. Start it with `make web`.');
}
setStatus('Loading firmware…');
setStatus('Loading firmware and ephemeral SD card…');
const firmware = await prepareFirmware(new Uint8Array(await file.arrayBuffer()), import.meta.url);
const artifactRoot = new URL('../dist/wasm/', import.meta.url);
const qemuUrl = new URL('qemu-system-riscv32.js', artifactRoot);
const rom = new Uint8Array(await fetch(new URL('esp32c3-rom.bin', artifactRoot)).then(response => {
if (!response.ok) {
throw new Error(`Could not load ESP32-C3 ROM (${response.status}).`);
}
return response.arrayBuffer();
}));
const [firmware, rom, sdCard] = await Promise.all([
prepareFirmware(new Uint8Array(await file.arrayBuffer()), import.meta.url),
fetchBin(new URL('esp32c3-rom.bin', artifactRoot)),
sdCardProvider(import.meta.url),
]);
const sdMessage = 'Using a blank ephemeral SD card; writes will be discarded when the emulator restarts.';
console.log(sdMessage);
logLine(sdMessage);
const { default: createQemu } = await import(qemuUrl);
const qemuLogFlags = new URL(location.href).searchParams.has('trace')
? 'guest_errors,unimp,in_asm'
: 'guest_errors,unimp';
const options = {
arguments: [
'-machine', `xteink,variant=${variant}`,
'-accel', 'tcg,tb-size=16',
'-icount', 'shift=auto,align=off,sleep=off',
'-L', '/bios',
'-display', 'none',
'-serial', 'stdio',
'-nic', 'none',
'-d', 'guest_errors,unimp',
'-d', qemuLogFlags,
'-drive', 'file=/flash.bin,if=mtd,format=raw',
'-drive', 'file=/sd.img,if=sd,format=raw',
],
locateFile: path => new URL(path, artifactRoot).href,
mainScriptUrlOrBlob: qemuUrl.href,
@@ -208,6 +232,7 @@ async function boot(file, variant, setStatus) {
options.FS.mkdir('/bios');
options.FS.writeFile('/bios/esp32c3-rom.bin', rom);
options.FS.writeFile('/flash.bin', firmware);
options.FS.writeFile('/sd.img', sdCard);
}];
setStatus('Starting emulator…');
@@ -222,8 +247,15 @@ function initialize() {
const status = document.querySelector('#status');
const canvas = document.querySelector('#screen');
logEl = document.querySelector('#log');
const params = new URL(location.href).searchParams;
persistLog = params.has('firmware');
if (persistLog) {
logBuffer.push(...(sessionStorage.getItem(DEBUG_LOG_KEY) || '').split('\n').filter(Boolean));
logEl.textContent = logBuffer.join('\n');
}
document.querySelector('#log-clear').addEventListener('click', () => {
logBuffer.length = 0;
sessionStorage.removeItem(DEBUG_LOG_KEY);
logEl.textContent = '';
});
@@ -232,19 +264,15 @@ function initialize() {
status.classList.toggle('error', error);
};
form.addEventListener('submit', async event => {
event.preventDefault();
const file = firmwareInput.files[0];
if (!file) {
setStatus('Choose a firmware image first.', true);
return;
}
const start = async (file, variant = variantInput.value) => {
bootButton.disabled = true;
firmwareInput.disabled = true;
variantInput.disabled = true;
try {
const module = await boot(file, variantInput.value, setStatus);
const module = await boot(file, variant, setStatus);
if (persistLog) {
window.__xteinkModule = module;
}
startDisplayLoop(module, canvas, setStatus, () => enableControls(module));
} catch (error) {
console.error(error);
@@ -253,7 +281,30 @@ function initialize() {
firmwareInput.disabled = false;
variantInput.disabled = false;
}
};
form.addEventListener('submit', event => {
event.preventDefault();
const file = firmwareInput.files[0];
if (!file) {
setStatus('Choose a firmware image first.', true);
return;
}
start(file);
});
const firmwareUrl = params.get('firmware');
if (firmwareUrl) {
fetch(firmwareUrl)
.then(response => {
if (!response.ok) {
throw new Error(`Could not load debug firmware (${response.status}).`);
}
return response.blob();
})
.then(blob => start(new File([blob], 'firmware.bin'), params.get('variant') || 'x3'))
.catch(error => setStatus(error.message || String(error), true));
}
}
if (typeof document !== 'undefined') {
+14 -13
View File
@@ -31,21 +31,21 @@
<p id="status" class="status" role="status">Waiting for firmware.</p>
<div class="workspace">
<section class="device" aria-label="Emulated xteink device">
<div class="screen-shell">
<canvas id="screen" width="528" height="792" aria-label="E-ink display"></canvas>
</div>
<div id="controls" class="controls" aria-label="Device controls">
<button type="button" data-button="back" disabled>Back</button>
<div class="dpad">
<button type="button" class="up" data-button="up" disabled aria-label="Up"></button>
<button type="button" class="left" data-button="left" disabled aria-label="Left"></button>
<button type="button" class="confirm" data-button="confirm" disabled>OK</button>
<button type="button" class="right" data-button="right" disabled aria-label="Right"></button>
<button type="button" class="down" data-button="down" disabled aria-label="Down"></button>
<div class="device-frame" aria-label="Device controls">
<button type="button" class="btn-power" data-button="power" disabled aria-label="Power"></button>
<button type="button" class="btn-side btn-up" data-button="up" disabled aria-label="Up"></button>
<div class="screen-shell">
<canvas id="screen" width="528" height="792" aria-label="E-ink display"></canvas>
</div>
<button type="button" class="btn-side btn-down" data-button="down" disabled aria-label="Down"></button>
<div class="btn-bottom">
<button type="button" data-button="back" disabled aria-label="Back"></button>
<button type="button" data-button="confirm" disabled aria-label="OK"></button>
<button type="button" data-button="left" disabled aria-label="Left"></button>
<button type="button" data-button="right" disabled aria-label="Right"></button>
</div>
<button type="button" class="power" data-button="power" disabled>Power</button>
</div>
</section>
@@ -56,6 +56,7 @@
</div>
<pre id="log" class="log" role="log" aria-live="polite"></pre>
</section>
</div>
</main>
<script type="module" src="app.js"></script>
+19 -18
View File
@@ -43,34 +43,35 @@ button.active { transform: translateY(2px); background: #d2752b; }
.status { min-height: 1.5rem; margin: 1rem 0; }
.status.error { color: #a52d20; font-weight: 700; }
.device {
.workspace { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(1.5rem, 4vw, 3rem); align-items: start; }
.device { display: flex; justify-content: center; }
.device-frame {
display: grid;
grid-template-columns: minmax(18rem, 33rem) minmax(14rem, 20rem);
gap: clamp(1.5rem, 5vw, 4rem);
align-items: center;
grid-template-columns: auto minmax(14rem, 30rem) auto;
grid-template-rows: auto auto auto;
gap: .6rem;
align-items: stretch;
justify-items: stretch;
}
.screen-shell { padding: 1.1rem; border-radius: 1.2rem; background: #30312e; box-shadow: 0 1rem 2.5rem #5d584c40; }
.screen-shell { grid-column: 2; grid-row: 2; padding: 1.1rem; border-radius: 1.2rem; background: #30312e; box-shadow: 0 1rem 2.5rem #5d584c40; }
canvas { display: block; width: 100%; height: auto; background: #fff; image-rendering: pixelated; }
.controls { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; align-items: center; }
.controls > button { min-height: 4rem; }
.power { background: #8e3228; }
.dpad { grid-column: 1 / -1; display: grid; grid-template: repeat(3, 4.25rem) / repeat(3, 4.25rem); justify-content: center; }
.dpad button { border-radius: 50%; padding: .25rem; }
.up { grid-area: 1 / 2; }
.left { grid-area: 2 / 1; }
.confirm { grid-area: 2 / 2; }
.right { grid-area: 2 / 3; }
.down { grid-area: 3 / 2; }
.device-frame button { min-height: 0; border-radius: .6rem; }
.btn-power { grid-column: 2; grid-row: 1; height: 1.6rem; }
.btn-side { grid-row: 2; width: 1.6rem; }
.btn-up { grid-column: 1; }
.btn-down { grid-column: 3; }
.btn-bottom { grid-column: 2; grid-row: 3; display: grid; grid-template-columns: repeat(4, 1fr); gap: .6rem; }
.btn-bottom button { height: 1.6rem; }
@media (max-width: 48rem) {
main { padding-top: 1.5rem; }
.device { grid-template-columns: 1fr; }
.controls { max-width: 24rem; width: 100%; margin: 0 auto; }
.workspace { grid-template-columns: 1fr; }
}
.log-panel { margin-top: 2rem; }
.log-panel { min-width: 0; }
.log-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: .5rem; font-size: .85rem; color: #b9b3a4; }
.log-header button { min-height: auto; padding: .25rem .75rem; font-size: .8rem; }
.log { margin: 0; max-height: 20rem; overflow: auto; padding: .75rem 1rem; border-radius: .6rem; background: #1c1d1a; color: #d6d0c2; font: .78rem/1.4 ui-monospace, monospace; white-space: pre-wrap; word-break: break-word; }