diff --git a/.gitignore b/.gitignore index 54ec0ec6b7..6b9bd05f70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /GNUmakefile /build/ /build-native/ +/dist/ /subprojects/.wraplock /.cache/ /.vscode/ diff --git a/Makefile b/Makefile index b65b0bd41a..72b08a9dae 100644 --- a/Makefile +++ b/Makefile @@ -26,12 +26,16 @@ quiet-command-run = $(if $(V),,$(if $2,printf " %-7s %s\n" $2 $3 && ))$1 quiet-@ = $(if $(V),,@) quiet-command = $(quiet-@)$(call quiet-command-run,$1,$2,$3) -UNCHECKED_GOALS := TAGS gtags cscope ctags dist \ +UNCHECKED_GOALS := TAGS gtags cscope ctags dist xteink \ help check-help print-% \ docker docker-% lcitool-refresh vm-help vm-test vm-build-% all: -.PHONY: all clean distclean recurse-all dist msi FORCE +.PHONY: all clean distclean recurse-all dist msi xteink FORCE + +xteink: + @mkdir -p dist + nix build .#qemu-native-xteink --builders '' --out-link dist/qemu-native # Don't try to regenerate Makefile or configure # We don't generate any of them diff --git a/README.xteink.md b/README.xteink.md index edaa818b87..a1e5c9f949 100644 --- a/README.xteink.md +++ b/README.xteink.md @@ -26,10 +26,11 @@ nix develop ninja -C build qemu-system-riscv32 ``` -Or build the native xteink target directly: +Or build the native xteink target into `dist/qemu-native` for direct script use: ```sh -nix build .#qemu-native-xteink +make xteink +uv run --script scripts/xteink-emu.py --help ``` ### Agent emulator diff --git a/flake.nix b/flake.nix index b833853800..317d7b0f9f 100644 --- a/flake.nix +++ b/flake.nix @@ -94,7 +94,7 @@ export XTEINK_QEMU=${qemu-native-xteink}/bin/qemu-system-riscv32 export XTEINK_BIOS=${qemu-native-xteink}/share/qemu export XTEINK_BOOTLOADER=${./scripts/assets/esp32c3-bootloader.bin} - export XTEINK_PARTITIONS=${./scripts/assets/crosspoint-partitions.bin} + export XTEINK_PARTITIONS=${./scripts/assets/xteink-partitions.bin} exec uv run --script ${./scripts/xteink-emu.py} "$@" ''; }; diff --git a/scripts/assets/crosspoint-partitions.bin b/scripts/assets/xteink-partitions.bin similarity index 100% rename from scripts/assets/crosspoint-partitions.bin rename to scripts/assets/xteink-partitions.bin diff --git a/scripts/xteink-emu.py b/scripts/xteink-emu.py index e9ce835cc9..f344d993f5 100755 --- a/scripts/xteink-emu.py +++ b/scripts/xteink-emu.py @@ -20,6 +20,13 @@ FLASH_SIZE = 16 * 1024 * 1024 APP_OFFSET = 0x10000 PARTITIONS_OFFSET = 0x8000 RELEASED_ADC = 4095 +PROJECT_ROOT = Path(__file__).resolve().parent.parent +DEFAULT_PATHS = { + "XTEINK_QEMU": PROJECT_ROOT / "dist/qemu-native/bin/qemu-system-riscv32", + "XTEINK_BIOS": PROJECT_ROOT / "dist/qemu-native/share/qemu", + "XTEINK_BOOTLOADER": PROJECT_ROOT / "scripts/assets/esp32c3-bootloader.bin", + "XTEINK_PARTITIONS": PROJECT_ROOT / "scripts/assets/xteink-partitions.bin", +} BUTTONS = { "left": ("/machine/adc", "adci[2]", 2242, RELEASED_ADC), "right": ("/machine/adc", "adci[2]", 5, RELEASED_ADC), @@ -40,10 +47,10 @@ def state_dir(args): def require_env(name): - value = os.environ.get(name) - if not value: - raise RuntimeError(f"{name} is not set; run xteink-emu through its Nix app") - return Path(value) + path = Path(os.environ.get(name, DEFAULT_PATHS[name])) + if not path.exists(): + raise RuntimeError(f"{path} does not exist; run 'make xteink'") + return path def pid_from(state): @@ -174,6 +181,13 @@ def start(args): raise RuntimeError(f"QEMU exited during startup\n{message}") try: qmp_command(state, "query-status") + if args.power_hold_ms: + path, prop, pressed, released = BUTTONS["power"] + qmp_command(state, "qom-set", {"path": path, "property": prop, "value": pressed}) + try: + time.sleep(args.power_hold_ms / 1000) + finally: + qmp_command(state, "qom-set", {"path": path, "property": prop, "value": released}) print(f"started PID {process.pid}") print(f"state: {state}") print(f"serial log: {state / 'serial.log'}") @@ -238,6 +252,12 @@ def button(args): state = state_dir(args) require_running(state) path, prop, pressed, released = BUTTONS[args.name] + if args.down: + qmp_command(state, "qom-set", {"path": path, "property": prop, "value": pressed}) + return + if args.up: + qmp_command(state, "qom-set", {"path": path, "property": prop, "value": released}) + return qmp_command(state, "qom-set", {"path": path, "property": prop, "value": pressed}) try: time.sleep(args.hold_ms / 1000) @@ -265,6 +285,7 @@ def parser(): add_state(command) command.add_argument("--firmware", required=True) command.add_argument("--sdcard", required=True) + command.add_argument("--power-hold-ms", type=int, default=2000) command.set_defaults(handler=start) command = commands.add_parser("stop") @@ -281,6 +302,9 @@ def parser(): add_state(command) command.add_argument("name", choices=BUTTONS) command.add_argument("--hold-ms", type=int, default=100) + button_mode = command.add_mutually_exclusive_group() + button_mode.add_argument("--down", action="store_true", help="press and hold") + button_mode.add_argument("--up", action="store_true", help="release") command.set_defaults(handler=button) command = commands.add_parser("screenshot") @@ -296,6 +320,8 @@ def main(): raise RuntimeError("--timeout must not be negative") if getattr(args, "hold_ms", 0) < 0: raise RuntimeError("--hold-ms must not be negative") + if getattr(args, "power_hold_ms", 0) < 0: + raise RuntimeError("--power-hold-ms must not be negative") args.handler(args)