#!/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, plain text (default) # sudo ./launch.sh --address 8 # listen as device 8 # sudo ./launch.sh -- --debug # full annotated trace (verbose) # sudo ./launch.sh -- --raw # forward --raw to iecpoc # sudo ./launch.sh --ko modules/iec_listener_1-6.12.93-1+rpt1.ko # # Any flag after -- is forwarded verbatim to iecpoc (--debug, --raw, --logfile FILE, …). # 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"