Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["bootloader", "kernel", "shared"]
members = ["bootloader", "kernel", "shared", "tools/theseus-qemu"]
resolver = "2"

[workspace.dependencies]
Expand All @@ -20,4 +20,4 @@ panic = "abort"
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
panic = "abort"
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ Welcome! This living wiki covers how the unified TheseusOS bootloader and kernel
- [Hardware & Drivers](hardware-and-drivers.md) — device inventory, driver manager, serial stack, and interrupts.
- [Driver Systems Deep Dive](driver-systems-deep-dive.md) — comprehensive technical guide to DMA, PCI discovery, and driver framework.
- [Development & Debugging](development-and-debugging.md) — building, running under QEMU, logging, and the serial monitor.
- [QEMU Runner](qemu-runner.md) — the `theseus-qemu` Rust runner (profiles, opt-in sockets, reproducible argv).

Looking for historical notes? Everything that pre-dates this rewrite now lives in [docs/archive](archive/).
160 changes: 160 additions & 0 deletions docs/qemu-runner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# QEMU Runner (`theseus-qemu`)

TheseusOS includes a small Rust CLI tool to **generate** and **run** the QEMU command-line in a reproducible way.

It is intended to replace (or eventually supersede) `startQemu.sh` once we’ve reached feature parity and added profiles.

## Why a Rust runner?

- **Profiles**: named, composable configurations (minimal, usb-kbd, net, storage, etc.).
- **Opt-in sockets**: when working in restricted environments (e.g. editor sandboxes), you may not be allowed to bind `/tmp` sockets. The runner makes serial/QMP/HMP endpoints explicit flags.
- **Reproducibility**: `print` and `artifact` make it easy to copy/paste or store the exact argv.

## Location

- Source: `tools/theseus-qemu/`
- Run via Cargo workspace:

```bash
cargo run -p theseus-qemu -- --help
```

## Basic usage

### Default behavior: run

Running **without** a subcommand executes QEMU:

```bash
# headed by default (QEMU window)
cargo run -p theseus-qemu --

# headless
cargo run -p theseus-qemu -- --headless
```

### Print the command instead of running

```bash
cargo run -p theseus-qemu -- print --headless
```

### Dry mode (print without requiring build artifacts)

`--dry` disables the runner’s preflight checks for required files (OVMF vars, disk image, etc.).
This is useful when you want to **see** the command, even if you can’t build/run right now.

```bash
cargo run -p theseus-qemu -- print --dry --profile min --headless
```

Notes:
- `print` and `artifact` imply `--dry` automatically.
- `--dry` does **not** make QEMU succeed if the files truly don’t exist; it only skips the runner checks.

## Profiles

Current profiles (early days):
- `default`: mirrors `startQemu.sh` device defaults (q35, nvme, root ports, xhci, virtio-gpu, virtio-net)
- `min`: minimal bring-up (no GPU/USB/NIC)
- `usb-kbd`: xHCI + usb keyboard

Example:

```bash
cargo run -p theseus-qemu -- --profile usb-kbd --headless
```

## Opt-in automation endpoints (serial/QMP/HMP)

These flags are intentionally explicit so humans can run without sockets by default, while automation can enable them.

### Serial

```bash
# serial over stdio
cargo run -p theseus-qemu -- --serial stdio

# serial over unix socket (for automation)
cargo run -p theseus-qemu -- --serial unix --serial-path /tmp/theseus-serial.sock
```

### Monitor + serial relays (recommended for interactive debugging)

TheseusOS ships systemd user units (see `scripts/`) that create stable PTY endpoints under `/tmp`:

- monitor: `/tmp/qemu-monitor` (QEMU side) and `/tmp/qemu-monitor-host` (your terminal/minicom)
- serial: `/tmp/qemu-serial` (QEMU side) and `/tmp/qemu-serial-host` (your terminal/minicom)
- debugcon: `/tmp/qemu-debugcon` (QEMU side) and `/tmp/qemu-debugcon-host` (PTY stream; use `cat`, not `tail -f`)
- optional log file (enable `qemu_debugcon_logger.service`): `/tmp/qemu-debugcon.log` (then you can `tail -f`)

Enable them:

```bash
./scripts/install-qemu-relays.sh
```

Use them with the runner:

```bash
# single convenience flag
cargo run -p theseus-qemu -- --relays

Note: `--relays` assumes you have the relay PTYs running (see `./scripts/install-qemu-relays.sh`). It routes QEMU monitor/serial/debugcon into the QEMU-side PTYs under `/tmp/qemu-*`.

# or explicit flags
cargo run -p theseus-qemu -- --serial unix --serial-path /tmp/qemu-serial
cargo run -p theseus-qemu -- --monitor-pty
cargo run -p theseus-qemu -- --debugcon-pty
```

### QMP (machine control)

```bash
# default path
cargo run -p theseus-qemu -- --qmp

# custom path
cargo run -p theseus-qemu -- --qmp /tmp/qemu-qmp.sock
```

If you also enable the QMP relay unit, it exposes a stable host socket:
- `/tmp/qemu-qmp-host.sock` → forwards to `/tmp/qemu-qmp.sock`

### HMP unix socket (optional)

```bash
# default path
cargo run -p theseus-qemu -- --hmp

# custom path
cargo run -p theseus-qemu -- --hmp /tmp/theseus-hmp.sock
```

## Artifacts

You can emit a JSON file with the resolved argv:

```bash
cargo run -p theseus-qemu -- artifact --out build/qemu-argv.json
```

## Relationship with `startQemu.sh`

Right now `startQemu.sh` remains the historical reference implementation.

As of the `feat/theseus-qemu-parity` work, the Rust runner supports several of the practical conveniences from `startQemu.sh`:
- **Build-before-run** (default): runs `make all` before launching QEMU. Disable with `--no-build`.
- **Timeout**: `--timeout-secs N` runs QEMU under `timeout --foreground`.
- **Success marker**: if QEMU output contains the marker string (default: `Kernel environment test completed successfully`), the runner forces exit code 0. Override via `--success-marker`.
- **High-signal QEMU debug output by default** (headless): uses `-d guest_errors` unless you opt into noisier flags.

The runner still focuses on:
- stable argv generation
- profile selection
- explicit socket toggles

Next steps for parity:
- richer profiles (net/storage variants)
- optional log cleanup and artifact capture
- first-class QMP/HMP helpers (later skills will consume these)
45 changes: 45 additions & 0 deletions scripts/install-qemu-relays.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail

# Install TheseusOS QEMU relay units (user systemd)
#
# Creates persistent /tmp endpoints used for interactive debugging:
# - /tmp/qemu-serial-host (PTY) -> QEMU uses /tmp/qemu-serial
# - /tmp/qemu-monitor-host (PTY) -> QEMU uses /tmp/qemu-monitor
# - /tmp/qemu-debugcon-host (PTY) -> QEMU uses /tmp/qemu-debugcon
# - /tmp/qemu-qmp-host.sock (unix) -> QEMU uses /tmp/qemu-qmp.sock
#
# Requirements: systemd user session + socat.

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)

UNITS=(
qemu_serial_relay.service
qemu_monitor_relay.service
qemu_debugcon_relay.service
qemu_qmp_relay.service
qemu_debugcon_logger.service
)

mkdir -p "${HOME}/.config/systemd/user"

for u in "${UNITS[@]}"; do
src="${SCRIPT_DIR}/${u}"
dst="${HOME}/.config/systemd/user/${u}"
if [[ ! -f "$src" ]]; then
echo "Missing unit file: $src" >&2
exit 1
fi
cp -f "$src" "$dst"
echo "Installed $u"
done

systemctl --user daemon-reload

for u in "${UNITS[@]}"; do
systemctl --user enable --now "$u"
systemctl --user status "$u" --no-pager --full | sed -n '1,8p'
echo "---"
done

echo "✓ QEMU relay units installed and started."
13 changes: 13 additions & 0 deletions scripts/qemu_debugcon_logger.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[Unit]
Description=QEMU debugcon logger (reads PTY host end → log file)
After=default.target

[Service]
# Requires qemu_debugcon_relay.service to be running (creates /tmp/qemu-debugcon-host)
# Writes an append-only log you can `tail -f`.
ExecStart=/usr/bin/bash -lc 'set -euo pipefail; mkdir -p /tmp; : > /tmp/qemu-debugcon.log; exec stdbuf -o0 cat /tmp/qemu-debugcon-host >> /tmp/qemu-debugcon.log'
Restart=always
RestartSec=1

[Install]
WantedBy=default.target
9 changes: 4 additions & 5 deletions scripts/qemu_debugcon_relay.service
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
[Unit]
Description=Persistent QEMU Debugcon Relay (named pipe pair)
Description=Persistent QEMU debugcon relay (PTY pair)
After=default.target

[Service]
Type=simple
ExecStartPre=/usr/bin/rm -f /tmp/qemu-debugcon.in /tmp/qemu-debugcon.out
ExecStart=/usr/bin/mkfifo /tmp/qemu-debugcon.in /tmp/qemu-debugcon.out; \
exec /usr/bin/socat -u PIPE:/tmp/qemu-debugcon.in PIPE:/tmp/qemu-debugcon.out
ExecStart=/usr/bin/socat \
PTY,link=/tmp/qemu-debugcon,raw,echo=0,mode=666 \
PTY,link=/tmp/qemu-debugcon-host,raw,echo=0,mode=666
Restart=always
RestartSec=1

Expand Down
9 changes: 4 additions & 5 deletions scripts/qemu_monitor_relay.service
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
[Unit]
Description=Persistent QEMU Monitor Relay (named pipe pair)
Description=Persistent QEMU monitor relay (PTY pair)
After=default.target

[Service]
Type=simple
ExecStartPre=/usr/bin/rm -f /tmp/qemu-monitor.in /tmp/qemu-monitor.out
ExecStart=/usr/bin/mkfifo /tmp/qemu-monitor.in /tmp/qemu-monitor.out; \
exec /usr/bin/socat -u PIPE:/tmp/qemu-monitor.in PIPE:/tmp/qemu-monitor.out
ExecStart=/usr/bin/socat \
PTY,link=/tmp/qemu-monitor,raw,echo=0,mode=666 \
PTY,link=/tmp/qemu-monitor-host,raw,echo=0,mode=666
Restart=always
RestartSec=1

Expand Down
15 changes: 15 additions & 0 deletions scripts/qemu_qmp_relay.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=Persistent QEMU QMP relay (unix socket host → qemu socket)
After=default.target

[Service]
# Listens on /tmp/qemu-qmp-host.sock and forwards each connection to /tmp/qemu-qmp.sock.
# This lets tooling connect to a stable path even if QEMU restarts.
ExecStart=/usr/bin/socat \
UNIX-LISTEN:/tmp/qemu-qmp-host.sock,fork,reuseaddr,mode=666 \
UNIX-CONNECT:/tmp/qemu-qmp.sock
Restart=always
RestartSec=1

[Install]
WantedBy=default.target
9 changes: 4 additions & 5 deletions scripts/qemu_serial_relay.service
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
[Unit]
Description=Persistent QEMU Serial Relay (named pipe pair)
Description=Persistent QEMU serial relay (PTY pair)
After=default.target

[Service]
Type=simple
ExecStartPre=/usr/bin/rm -f /tmp/qemu-serial.in /tmp/qemu-serial.out
ExecStart=/usr/bin/mkfifo /tmp/qemu-serial.in /tmp/qemu-serial.out; \
exec /usr/bin/socat -u PIPE:/tmp/qemu-serial.in PIPE:/tmp/qemu-serial.out
ExecStart=/usr/bin/socat \
PTY,link=/tmp/qemu-serial,raw,echo=0,mode=666 \
PTY,link=/tmp/qemu-serial-host,raw,echo=0,mode=666
Restart=always
RestartSec=1

Expand Down
10 changes: 10 additions & 0 deletions tools/theseus-qemu/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "theseus-qemu"
version = "0.1.0"
edition = "2024"

[dependencies]
anyhow = "1.0.101"
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Loading