diff --git a/launch.sh b/launch.sh new file mode 100755 index 0000000..2780313 --- /dev/null +++ b/launch.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +# +# launch.sh - load the IEC listener module, run the userspace frontend, unload. +# +# This is the "run the whole thing" entry point for a released package. Unlike +# selftest.sh (a one-shot wiring check that loads and immediately unloads), this +# keeps the module loaded for as long as the frontend runs. In order it: +# 1. installs the iecpoc Python frontend into a local virtualenv (.venv), +# 2. detects the running kernel and picks the matching iec_listener_*.ko, +# 3. insmods it (creating /dev/iec0), +# 4. runs `iecpoc`, streaming the decoded C64 traffic to the terminal, +# 5. on exit (Ctrl-C / EOF / error) ALWAYS rmmods again - the Pi is left +# exactly as before. +# +# The module is never left loaded by this script; the EXIT trap unloads it even +# if the frontend crashes or you interrupt it. +# +# Usage: +# sudo ./launch.sh [--address N] [--device PATH] [--ko FILE] [-- ] +# +# Examples: +# sudo ./launch.sh # address 4, /dev/iec0, annotated trace +# sudo ./launch.sh --address 8 # listen as device 8 +# sudo ./launch.sh -- --raw # forward --raw to iecpoc +# sudo ./launch.sh --ko modules/iec_listener_1-6.12.93-1+rpt1.ko +# +# Defaults: address=4 (printer), device=/dev/iec0. + +set -euo pipefail + +MODULE="iec_listener" +ADDRESS=4 +DEVICE="/dev/iec0" +KO="" +VENV=".venv" +LOADED=0 +declare -a PASS=() + +# Run relative to the script's own directory so modules/, iecpoc/, pyproject.toml +# and .venv resolve the same way whether invoked as ./launch.sh or by full path. +HERE="$(cd "$(dirname "$0")" && pwd)" +cd "$HERE" + +# ---- pretty output (color only on a tty) -------------------------------- +if [ -t 1 ]; then + C_OK=$'\033[32m'; C_ERR=$'\033[31m'; C_INFO=$'\033[36m'; C_RST=$'\033[0m' +else + C_OK=""; C_ERR=""; C_INFO=""; C_RST="" +fi +ok() { printf '%s OK %s %s\n' "$C_OK" "$C_RST" "$*"; } +info() { printf '%s ==>%s %s\n' "$C_INFO" "$C_RST" "$*"; } +die() { printf '%sFAIL%s %s\n' "$C_ERR" "$C_RST" "$*" >&2; exit 1; } + +# ---- always unload on the way out --------------------------------------- +cleanup() { + if [ "$LOADED" = 1 ] && lsmod | grep -q "^${MODULE}\b"; then + info "rmmod ${MODULE} (releases the bus, removes ${DEVICE})" + rmmod "$MODULE" 2>/dev/null && ok "unloaded - Pi left as before" \ + || info "rmmod failed; unload manually: sudo rmmod ${MODULE}" + fi +} +trap cleanup EXIT + +# ---- args --------------------------------------------------------------- +# Anything unrecognised (and everything after a literal --) is forwarded to +# iecpoc, so e.g. --raw / --logfile FILE just work. +while [ $# -gt 0 ]; do + case "$1" in + -a|--address) ADDRESS="${2:-}"; shift 2 ;; + --address=*) ADDRESS="${1#*=}"; shift ;; + -d|--device) DEVICE="${2:-}"; shift 2 ;; + --device=*) DEVICE="${1#*=}"; shift ;; + --ko) KO="${2:-}"; shift 2 ;; + --ko=*) KO="${1#*=}"; shift ;; + --) shift; PASS+=("$@"); break ;; + -h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) PASS+=("$1"); shift ;; + esac +done + +# ---- preconditions ------------------------------------------------------ +[ "$(id -u)" -eq 0 ] || die "must run as root (use: sudo $0 ...)" +case "$ADDRESS" in ''|*[!0-9]*) die "address must be 0-30 (got '$ADDRESS')" ;; esac +{ [ "$ADDRESS" -ge 0 ] && [ "$ADDRESS" -le 30 ]; } || die "address out of range 0-30: $ADDRESS" +command -v modinfo >/dev/null || die "modinfo not found (install kmod)" +command -v python3 >/dev/null || die "python3 not found (sudo apt install python3)" + +# ---- 1. python frontend: venv + install (once) -------------------------- +# iecpoc declares no third-party runtime deps, so this is offline-safe except +# for the build backend (setuptools), which pip fetches the first time. The +# install is skipped on later runs once the entry point exists. +[ -f "$HERE/pyproject.toml" ] || die "pyproject.toml not found next to $0; package incomplete?" +if [ ! -x "$VENV/bin/iecpoc" ]; then + if [ ! -x "$VENV/bin/python" ]; then + info "creating virtualenv: $VENV" + python3 -m venv "$VENV" || die "could not create venv (sudo apt install python3-venv)" + fi + info "installing iecpoc frontend into $VENV" + "$VENV/bin/pip" install --quiet --disable-pip-version-check . \ + || die "pip install failed (needs network for the build backend, or: sudo apt install python3-pip)" + ok "frontend installed" +fi + +# ---- 2. pick the module matching the running kernel ---------------------- +# Same vermagic-based selection as selftest.sh: match by what the .ko was built +# against (uname -r), not by filename. See selftest.sh for the full rationale. +RUNNING="$(uname -r)" +if [ -z "$KO" ]; then + info "running kernel: $RUNNING" + for dir in "$HERE/modules" "$HERE" "$HERE/kernel"; do + [ -d "$dir" ] || continue + for cand in "$dir"/${MODULE}*.ko; do + [ -f "$cand" ] || continue + vm="$(modinfo -F vermagic "$cand" 2>/dev/null)" && [ -n "$vm" ] || continue + if [ "${vm%% *}" = "$RUNNING" ]; then KO="$cand"; break 2; fi + done + done +fi +[ -n "$KO" ] && [ -f "$KO" ] || die \ +"no module matching the running kernel ($RUNNING) found. + Pass one with --ko PATH, or run ./selftest.sh to list what's available." + +VERMAGIC="$(modinfo -F vermagic "$KO" 2>/dev/null)" || die "cannot read vermagic from $KO" +[ "${VERMAGIC%% *}" = "$RUNNING" ] || die \ +"kernel mismatch: module built for '${VERMAGIC%% *}' but running '$RUNNING'." +info "module: $KO" + +# ---- 3. load (reload if a stale copy is already in) --------------------- +if lsmod | grep -q "^${MODULE}\b"; then + info "a copy is already loaded; removing it first" + rmmod "$MODULE" || die "rmmod failed (is ${DEVICE} in use?)" +fi +info "insmod ${MODULE} address=${ADDRESS}" +insmod "$KO" address="$ADDRESS" || die "insmod failed; check: dmesg | tail" +LOADED=1 +[ -c "$DEVICE" ] || die "${DEVICE} was not created" +ok "loaded; ${DEVICE} present" + +# ---- 4. run the frontend (foreground; rmmod runs in the EXIT trap) ------- +info "running iecpoc (Ctrl-C to stop) - address ${ADDRESS}, device ${DEVICE}" +echo +set +e +"$VENV/bin/iecpoc" --address "$ADDRESS" --device "$DEVICE" "${PASS[@]}" +ST=$? +set -e + +echo +info "frontend exited (status $ST); unloading module" +exit "$ST"