diff --git a/doc/source/build_system.rst b/doc/source/build_system.rst
index ecda494f2..f5d8019cd 100644
--- a/doc/source/build_system.rst
+++ b/doc/source/build_system.rst
@@ -71,8 +71,8 @@ Everything is anchored on the repository root. Source ``env.sh`` once per shell:
This exports ``IB_ROOT_DIR``, sets ``BBPATH``/``BUILDDIR`` to ``build/``, and
prepends ``scripts/`` and the bundled ``bitbake`` to ``PATH`` so the active tree
-wins. From then on the ``build.sh`` / ``deploy.sh`` / ``st.sh`` / ``stg.sh``
-commands are on the path. See :ref:`user_guide` for the end-to-end walkthrough.
+wins. From then on the ``build.sh`` / ``deploy.sh`` / ``st.sh`` commands are on
+the path. See :ref:`user_guide` for the end-to-end walkthrough.
Meta-layers
===========
diff --git a/doc/source/display_input.rst b/doc/source/display_input.rst
index de5e88b00..3825f7477 100644
--- a/doc/source/display_input.rst
+++ b/doc/source/display_input.rst
@@ -143,25 +143,23 @@ it once per refresh through the ``GET_STATE`` ioctl, and the driver scales the r
relative PL050 mouse (``kmi1.c``) is kept in the tree but its dts node is
``status = "disabled"`` so there is a single ``/dev/mouse``.
-The host GTK window (``stg.sh``)
-================================
+The host GTK window (``st.sh -d``)
+==================================
-The graphical launcher ``stg.sh`` starts QEMU with the **GTK** display
-backend (``-display gtk,zoom-to-fit=off``):
+By default ``st.sh`` is headless (``-display none``): no window, serial console
+only, used for non-graphical work and CI. Pass **-d** to open the QEMU **GTK**
+window that presents the guest PL111 CLCD (``-display gtk,zoom-to-fit=off``):
* **GTK, not SDL** — the SDL backend does not present the PL111 console surface
(the window stays black even though the framebuffer is rendered correctly);
GTK shows it, and its *View* menu lists every console.
-* **XWayland for HiDPI** — ``stg.sh`` exports ``GDK_BACKEND=x11`` (plus
- ``GDK_SCALE=1``). On a fractionally-scaled HiDPI **Wayland** panel, GTK reports
- pointer coordinates in a different scale than the framebuffer surface, so the
- absolute mapping comes out *offset* (host and guest cursors shifted) — fine on a
- 1× external monitor, wrong on the laptop panel. Routing GTK through XWayland
- gives a uniform pointer-to-surface mapping, so the cursors coincide on every
- monitor. It is harmless on a native X11 session.
-
-``st.sh`` is the headless sibling (``-display none``): no window, console
-only, used for non-graphical work and CI.
+* **XWayland for HiDPI** — with ``-d``, ``st.sh`` exports ``GDK_BACKEND=x11``
+ (plus ``GDK_SCALE=1``). On a fractionally-scaled HiDPI **Wayland** panel, GTK
+ reports pointer coordinates in a different scale than the framebuffer surface,
+ so the absolute mapping comes out *offset* (host and guest cursors shifted) —
+ fine on a 1× external monitor, wrong on the laptop panel. Routing GTK through
+ XWayland gives a uniform pointer-to-surface mapping, so the cursors coincide on
+ every monitor. It is harmless on a native X11 session.
.. _console_sigint:
diff --git a/doc/source/img/gen_so3_diagrams.py b/doc/source/img/gen_so3_diagrams.py
index 2492ccb10..bac953b92 100644
--- a/doc/source/img/gen_so3_diagrams.py
+++ b/doc/source/img/gen_so3_diagrams.py
@@ -348,7 +348,7 @@ def xml(self):
dep = p.box(420, 420, 250, 64,
"scripts/deploy.sh bsp-so3\n→ sdcard FAT (+ flash0.img)", WHITE, 10)
run = p.box(770, 420, 270, 64,
- "scripts/st.sh / stg.sh\n→ QEMU (EL1 / EL2 / ATF)", WHITE, 10)
+ "scripts/st.sh [-d]\n→ QEMU (EL1 / EL2 / ATF)", WHITE, 10)
# the band feeds do_itb: drop from the band's lower-left border straight down.
p.edge(rec, itb, "", ARR + "exitX=0.15;exitY=1;entryX=0.5;entryY=0;")
p.edge(itb, dep, "", ARR + "exitX=1;exitY=0.5;entryX=0;entryY=0.5;")
@@ -379,7 +379,7 @@ def xml(self):
qmsd = p.box(430, 320, 280, 44, "pl050 mouse @ 0x08802000 (disabled)", NONE, 9)
quart= p.box(430, 380, 280, 46, "pl011 UART @ 0x09000000 (SPI 1)", HW, 9)
# Host column
-p.box(780, 60, 300, 470, "Host (scripts/stg.sh)", CONT, 12, 1)
+p.box(780, 60, 300, 470, "Host (scripts/st.sh -d)", CONT, 12, 1)
hwin = p.box(810, 110, 240, 80, "GTK window\n-display gtk,zoom-to-fit=off\nGDK_BACKEND=x11 (HiDPI/Wayland)", NEUTRAL, 9)
hptr = p.box(810, 250, 240, 64, "host pointer → absolute\n1:1 mapping, no grab / no warp", NEUTRAL, 9)
hterm= p.box(810, 380, 240, 46, "terminal (-serial mon:stdio)", NEUTRAL, 9)
diff --git a/doc/source/img/so3.drawio b/doc/source/img/so3.drawio
index f2fdfecc5..3dcfb561f 100644
--- a/doc/source/img/so3.drawio
+++ b/doc/source/img/so3.drawio
@@ -1,2 +1,2 @@
\ No newline at end of file
+(guest-boot x0/x1)" style="edgeStyle=orthogonalEdgeStyle;rounded=1;html=1;endArrow=block;strokeColor=#444444;fontSize=10;" edge="1" parent="1" source="c4" target="c5">
\ No newline at end of file
diff --git a/doc/source/img/so3_build.png b/doc/source/img/so3_build.png
index f273c77f3..06c516b5a 100644
Binary files a/doc/source/img/so3_build.png and b/doc/source/img/so3_build.png differ
diff --git a/doc/source/img/so3_io.png b/doc/source/img/so3_io.png
index e2904b638..aeef57ba2 100644
Binary files a/doc/source/img/so3_io.png and b/doc/source/img/so3_io.png differ
diff --git a/doc/source/lvgl.rst b/doc/source/lvgl.rst
index bc4d32133..7d4d45d8d 100644
--- a/doc/source/lvgl.rst
+++ b/doc/source/lvgl.rst
@@ -83,12 +83,12 @@ Running LVGL under QEMU
=======================
Build the user space with the LVGL applications and a framebuffer-enabled kernel
-configuration, then launch the **graphical** emulator (the framebuffer needs a
-real window):
+configuration, then launch the emulator in **graphical** mode with ``-d`` (the
+framebuffer needs a real window):
.. code-block:: bash
- stg.sh
+ st.sh -d
QEMU opens a GTK window that shows the PL111 framebuffer; launch an LVGL
application from the ``so3%`` prompt to draw into it, for example::
diff --git a/doc/source/user_guide.rst b/doc/source/user_guide.rst
index 79b417afc..e62eb2c43 100644
--- a/doc/source/user_guide.rst
+++ b/doc/source/user_guide.rst
@@ -34,7 +34,7 @@ The most relevant top-level directories are::
so3/ # the SO3 kernel (so3/so3) and user space (so3/usr), plus dts
build/ # the Infrabase build: meta-* layers, conf/local.conf, bitbake work tree
- scripts/ # build.sh, deploy.sh, st.sh, stg.sh, updiff.sh, …
+ scripts/ # build.sh, deploy.sh, st.sh, updiff.sh, …
filesystem/ # the virtual SD-card image used by QEMU
u-boot/ # fetched U-Boot (patched)
qemu/ # fetched QEMU (patched) -> qemu/build/qemu-system-*
@@ -105,12 +105,12 @@ Launch scripts
:header-rows: 1
:widths: 16 84
- * - Script
+ * - Command
- Use
* - ``st.sh``
- **headless** run (``-display none``) — serial console only. The default for
non-graphical work and CI.
- * - ``stg.sh``
+ * - ``st.sh -d``
- **graphical** run — a GTK window for the PL111 framebuffer (LVGL, ``fb_test``).
See :ref:`display_input`.
diff --git a/docker/scripts/run.sh b/docker/scripts/run.sh
index a593c4abe..fdab44468 100755
--- a/docker/scripts/run.sh
+++ b/docker/scripts/run.sh
@@ -40,8 +40,8 @@ fi
# This launcher boots a STANDALONE SO3 image at EL1 (plain -M virt). It cannot
# boot an AVZ (EL2 hypervisor) or an ATF/flash0 (EL3) deployment — those need
-# QEMU started with virtualization=on / secure=on, which scripts/st.sh and
-# scripts/stg.sh select from the deployed ITS. The lv_perf images deploy a
+# QEMU started with virtualization=on / secure=on, which scripts/st.sh selects
+# from the deployed ITS. The lv_perf images deploy a
# standalone ITS, so this guard only ever trips when run by hand on a host tree
# configured for AVZ/ATF.
SO3_ITS=$(grep -E "^IB_TARGET_ITS:so3:${PLATFORM}\b" build/conf/local.conf 2>/dev/null \
@@ -55,7 +55,7 @@ if [ -n "$why" ]; then
echo "Error: the deployment is not standalone — $why." >&2
echo " run.sh boots SO3 standalone at EL1 only. For an AVZ (EL2) or ATF" >&2
echo " (EL3) build, use the host launchers instead, which select the EL" >&2
- echo " from the ITS: scripts/st.sh (headless) / scripts/stg.sh (GTK)." >&2
+ echo " from the ITS: scripts/st.sh (headless) / scripts/st.sh -d (GTK)." >&2
exit 1
fi
diff --git a/scripts/st.sh b/scripts/st.sh
index 707cedc80..7b581e21b 100755
--- a/scripts/st.sh
+++ b/scripts/st.sh
@@ -12,6 +12,23 @@
QEMU_AUDIO_DRV="none"
GDB_PORT_BASE=1234
+
+# Parse our own options (currently just -d) out of the argument list before
+# what's left is forwarded to QEMU as USR_OPTION.
+WITH_DISPLAY=0
+POSARGS=()
+for _a in "$@"; do
+ case "$_a" in
+ -d) WITH_DISPLAY=1 ;;
+ -h|--help)
+ echo "Usage: $(basename "$0") [-d] [qemu-option]"
+ echo " -d graphical: open the QEMU GTK window showing the guest PL111/LVGL screen"
+ echo " (default: headless, serial console only)"
+ exit 0 ;;
+ *) POSARGS+=("$_a") ;;
+ esac
+done
+set -- "${POSARGS[@]}"
USR_OPTION=$1
# QEMU_BIN is selected per IB_PLATFORM below (qemu-system-aarch64 for
# virt64, qemu-system-arm for virt32).
@@ -46,6 +63,24 @@ launch_qemu() {
SO3_ITS=$(grep -E "^IB_TARGET_ITS:so3:${IB_PLATFORM}\b" build/conf/local.conf | awk -F'"' '{print $2}' | tail -1)
LINUX_ITS=$(grep -E "^IB_TARGET_ITS:linux:${IB_PLATFORM}\b" build/conf/local.conf | awk -F'"' '{print $2}' | tail -1)
+ # Display mode. Default: headless (serial console only, -display none). With
+ # -d: open the QEMU GTK window that presents the guest PL111 CLCD (the LVGL
+ # screen). SO3 drives PL111 + PL050 (wired unconditionally into '-M virt' by
+ # the so3 QEMU patch) and has no virtio-gpu, so no extra device flags are
+ # needed — just switch the display backend. Use GTK, not SDL: SDL leaves the
+ # PL111 console black, GTK presents it (and its View menu lists every
+ # console). On a fractionally-scaled HiDPI Wayland panel, route GTK through
+ # XWayland (GDK_BACKEND=x11) so the so3,absmouse absolute pointer maps 1:1
+ # onto the guest surface; harmless on a real X11 session.
+ if [ "$WITH_DISPLAY" == "1" ]; then
+ DISPLAY_OPT="-display gtk,zoom-to-fit=off"
+ export GDK_BACKEND=x11
+ export GDK_SCALE=1
+ export GDK_DPI_SCALE=1
+ else
+ DISPLAY_OPT="-display none"
+ fi
+
if [ "$IB_PLATFORM" == "virt64" ]; then
QEMU_BIN="$IB_ROOT_DIR/qemu/build/qemu-system-aarch64"
echo Starting on virt64
@@ -97,7 +132,7 @@ launch_qemu() {
-device virtio-blk-device,drive=hd0 \
-drive if=none,file=filesystem/sdcard.img.virt64,id=hd0,format=raw,file.locking=off \
-m 1024 \
- -display none \
+ ${DISPLAY_OPT} \
-netdev user,id=n1,hostfwd=tcp::2222-:22 \
-device virtio-net-device,netdev=n1,mac=${QEMU_MAC_ADDR} \
-gdb tcp::${GDB_PORT}
@@ -120,7 +155,7 @@ launch_qemu() {
-device virtio-blk-device,drive=hd0 \
-drive if=none,file=filesystem/sdcard.img.virt32,id=hd0,format=raw,file.locking=off \
-m 1024 \
- -display none \
+ ${DISPLAY_OPT} \
-netdev user,id=n1,hostfwd=tcp::2222-:22 \
-device virtio-net-device,netdev=n1,mac=${QEMU_MAC_ADDR} \
-gdb tcp::${GDB_PORT}
diff --git a/scripts/stg.sh b/scripts/stg.sh
deleted file mode 100755
index 31a59244f..000000000
--- a/scripts/stg.sh
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/bin/bash
-
-# Copyright (c) 2025-2026 EDGEMTech SA
-
-# Resolve project root from this script's own location, cd there, and
-# source env.sh — prompting the user first if the parent shell points
-# at a different tree. Every relative path below (filesystem/...,
-# build/conf/local.conf) is anchored on that root. See
-# scripts/common/setup_env.sh.
-
-. "$(cd "$(dirname "$(command -v -- "$0")")" && pwd)/common/setup_env.sh"
-
-QEMU_AUDIO_DRV="none"
-GDB_PORT_BASE=1234
-USR_OPTION=$1
-# QEMU_BIN is selected per IB_PLATFORM below (qemu-system-aarch64 for
-# virt64, qemu-system-arm for virt32).
-
-N_QEMU_INSTANCES=`ps -A | grep qemu-system | wc -l`
-
-launch_qemu() {
- QEMU_MAC_ADDR="$(printf 'DE:AD:BE:EF:%02X:%02X\n' $((N_QEMU_INSTANCES)) $((N_QEMU_INSTANCES)))"
-
- GDB_PORT=$((${GDB_PORT_BASE} + ${N_QEMU_INSTANCES}))
-
- echo -e "\033[01;36mMAC addr: " ${QEMU_MAC_ADDR} "\033[0;37m"
- echo -e "\033[01;36mGDB port: " ${GDB_PORT} "\033[0;37m"
-
- while IFS= read -r line; do
- # Check if the line starts with "IB_PLATFORM"
- if [[ $line == IB_PLATFORM* ]]; then
- # Extract the value between the quotes
- value=$(echo "$line" | awk -F'"' '{print $2}')
-
- # Set the IB_PLATFORM variable to the extracted value
- IB_PLATFORM="$value"
- break
- fi
- done < build/conf/local.conf
-
- # Detect a SO3-on-AVZ boot from the selected (uncommented) so3 ITS — AVZ
- # is an EL2 hypervisor, so QEMU must expose EL2 (virtualization=on).
- SO3_ITS=$(grep -E "^IB_TARGET_ITS:so3:${IB_PLATFORM}\b" build/conf/local.conf | awk -F'"' '{print $2}' | tail -1)
-
- if [ "$IB_PLATFORM" != "virt64" ] && [ "$IB_PLATFORM" != "virt32" ]; then
- echo "ERROR: stg.sh only supports IB_PLATFORM=virt64 or virt32, but" >&2
- echo " build/conf/local.conf has IB_PLATFORM=\"$IB_PLATFORM\"." >&2
- echo "" >&2
- echo " Edit build/conf/local.conf to set" >&2
- echo " IB_PLATFORM ?= \"virt64\" (or \"virt32\")" >&2
- echo " then rebuild+redeploy before retrying stg.sh." >&2
- exit 1
- fi
-
- # SO3 now uses the ABSOLUTE pointer (so3,absmouse — see the so3 QEMU patch
- # and devices/input/absmouse.c), so QEMU runs in absolute pointer mode: the
- # host pointer maps 1:1 onto the guest with NO grab and NO warp. This sheds
- # the relative-mouse grab workaround (grab-on-hover) entirely.
- #
- # GDK_BACKEND=x11 is still needed on a FRACTIONALLY-SCALED HiDPI Wayland
- # panel: there QEMU's GTK reports pointer coordinates in a different scale
- # than the framebuffer surface, so the absolute mapping comes out OFFSET
- # (host vs guest cursor shifted) — fine on a 1x external monitor, shifted
- # on the laptop. Routing GTK through XWayland gives a uniform pointer-to-
- # surface mapping, so the cursors line up on every monitor. (GDK_SCALE just
- # keeps the window native-sized; not load-bearing for the mapping.)
- # Harmless on a real X11 session.
- export GDK_BACKEND=x11
- export GDK_SCALE=1
- export GDK_DPI_SCALE=1
-
- if [ "$IB_PLATFORM" == "virt64" ]; then
- QEMU_BIN="$IB_ROOT_DIR/qemu/build/qemu-system-aarch64"
- echo Starting on virt64
- # See st.sh for rationale and for the flash0.img-presence boot-mode
- # heuristic (AVZ chain vs bare U-Boot).
- # Like virt32, SO3 drives the PL111 CLCD + PL050 keyboard/mouse that the
- # so3 QEMU patch wires unconditionally into '-M virt' (virt64.dts has the
- # clcd@08800000 / pl050 nodes). It has NO virtio-gpu driver, so we do not
- # add virtio-gpu/keyboard/mouse here. Use the GTK backend: SDL does not
- # present the PL111 console's surface (the window stays black even though
- # the framebuffer is rendered); GTK shows it and its View menu lists every
- # console.
-
- if [ -f filesystem/flash0.img ]; then
- # ATF/OP-TEE chain (flash0/FIP): EL3 (secure=on) + EL2.
- MACHINE_OPT="-M virt,virtualization=on,gic-version=2,secure=on"
- BOOT_OPT="-drive if=pflash,format=raw,file=filesystem/flash0.img"
- elif [[ "$SO3_ITS" == *avz* ]]; then
- # SO3-on-AVZ via the ITS, no ATF: AVZ runs at EL2, so QEMU must
- # expose it (virtualization=on). U-Boot here is virt64_defconfig
- # (EL2-aware) and hands off to AVZ at EL2.
- echo "AVZ guest (ITS=$SO3_ITS) — enabling EL2 (virtualization=on)"
- MACHINE_OPT="-M virt,gic-version=2,virtualization=on"
- BOOT_OPT="-kernel u-boot/u-boot"
- else
- MACHINE_OPT="-M virt,gic-version=2"
- BOOT_OPT="-kernel u-boot/u-boot"
- fi
- ${QEMU_BIN} $@ ${USR_OPTION} \
- -smp 4 \
- -serial mon:stdio \
- ${MACHINE_OPT} -cpu cortex-a72 \
- ${BOOT_OPT} \
- -device virtio-blk-device,drive=hd0 \
- -drive if=none,file=filesystem/sdcard.img.virt64,id=hd0,format=raw,file.locking=off \
- -display gtk,zoom-to-fit=off \
- -m 1024 \
- -netdev user,id=n1,hostfwd=tcp::2222-:22 \
- -device virtio-net-device,netdev=n1,mac=${QEMU_MAC_ADDR} \
- -gdb tcp::${GDB_PORT}
- fi
-
- if [ "$IB_PLATFORM" == "virt32" ]; then
- QEMU_BIN="$IB_ROOT_DIR/qemu/build/qemu-system-arm"
- echo Starting on virt32
- # Graphical sibling of st.sh's virt32 branch: bare U-Boot chain
- # (no ATF / flash on this platform), cortex-a15.
- # NOTE: SO3 drives the PL111 CLCD + PL050 keyboard/mouse that the
- # so3 QEMU patch wires unconditionally into '-M virt' (see boot log:
- # pl111_init / pl050_init_*). It has NO virtio-gpu driver, so we must
- # NOT add virtio-gpu/keyboard/mouse here.
- # Use the GTK backend: its View menu lists every graphic console, so the
- # PL111 panel is reachable even if QEMU registers more than one console.
- ${QEMU_BIN} $@ ${USR_OPTION} \
- -smp 4 \
- -serial mon:stdio \
- -M virt -cpu cortex-a15 \
- -kernel u-boot/u-boot \
- -device virtio-blk-device,drive=hd0 \
- -drive if=none,file=filesystem/sdcard.img.virt32,id=hd0,format=raw,file.locking=off \
- -display gtk,zoom-to-fit=off \
- -m 1024 \
- -netdev user,id=n1,hostfwd=tcp::2222-:22 \
- -device virtio-net-device,netdev=n1,mac=${QEMU_MAC_ADDR} \
- -gdb tcp::${GDB_PORT}
- fi
-
- QEMU_RESULT=$?
-}
-
-launch_qemu