#!/usr/bin/env bash # # selftest.sh - non-persistent self-test for the IEC listener kernel module. # # This is NOT a full install: nothing is copied into /lib/modules and no # autoload is configured. It only loads the module, tests it, and unloads it. # # Entry point: the built iec_listener.ko is already on the Pi. This script: # 1. checks the module is compatible with the running kernel (vermagic), # 2. loads it (insmod) - non-persistent, no reboot needed, # 3. runs the self-test ioctl on /dev/iec0, # 4. unloads it again (rmmod) so the Pi is left exactly as before. # # The module is never left loaded by this script; it always unloads, even if # the self-test fails or the script is interrupted. # # Usage: # sudo ./selftest.sh [path/to/iec_listener.ko] [--address N] # # Defaults: ./iec_listener.ko (or ~/iec_listener.ko), address=4 (printer). set -euo pipefail MODULE="iec_listener" ADDRESS=4 KO="" LOADED=0 # ---- 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 DATA, removes /dev/iec0)" rmmod "$MODULE" 2>/dev/null && ok "unloaded - Pi left as before" \ || info "rmmod failed; unload manually: sudo rmmod ${MODULE}" fi } trap cleanup EXIT # ---- args --------------------------------------------------------------- while [ $# -gt 0 ]; do case "$1" in --address) ADDRESS="${2:-}"; shift 2 ;; --address=*) ADDRESS="${1#*=}"; shift ;; -h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; -*) die "unknown option: $1" ;; *) KO="$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" if [ -z "$KO" ]; then for cand in "./${MODULE}.ko" "$HOME/${MODULE}.ko" "$(dirname "$0")/${MODULE}.ko"; do [ -f "$cand" ] && { KO="$cand"; break; } done fi [ -n "$KO" ] && [ -f "$KO" ] || die "module not found; pass the path, e.g. sudo $0 ~/${MODULE}.ko" command -v modinfo >/dev/null || die "modinfo not found (install kmod)" info "module: $KO" info "address: $ADDRESS" # ---- 1. compatibility check (vermagic vs running kernel) ---------------- RUNNING="$(uname -r)" ARCH="$(uname -m)" VERMAGIC="$(modinfo -F vermagic "$KO")" || die "cannot read vermagic from $KO" MOD_KVER="${VERMAGIC%% *}" # first token of vermagic = kernel version info "running kernel: $RUNNING ($ARCH)" info "module vermagic: $VERMAGIC" [ "$MOD_KVER" = "$RUNNING" ] || die \ "kernel mismatch: module built for '$MOD_KVER' but running '$RUNNING'. Rebuild against the current headers (see docs/kernel-notes.md): pkg=\"linux-headers-\$(uname -r | sed 's/.*+rpt-//')\" echo \"HEADERS_PKG=\$pkg KERNEL_VERSION=\$(dpkg-query -W -f='\${Version}' \"\$pkg\")\"" case "$VERMAGIC" in *"$ARCH"*) ;; *) die "arch mismatch: module vermagic has no '$ARCH' (wrong flavour built?)" ;; esac ok "compatible with running kernel" # ---- 2. 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 /dev/iec0 in use?)" fi info "insmod ${MODULE}.ko address=${ADDRESS}" insmod "$KO" address="$ADDRESS" || die "insmod failed; check: dmesg | tail" LOADED=1 [ -c /dev/iec0 ] || die "/dev/iec0 was not created" ok "loaded; /dev/iec0 present" # ---- 3. self-test ioctl ------------------------------------------------- info "running self-test (IEC_IOC_SELFTEST) - run with the C64 disconnected" set +e python3 - <<'PY' import array, fcntl, sys IEC_IOC_SELFTEST = 0x80044903 # _IOR('I', 3, __u32) BITS = [ ("DATA_ASSERT_OK", 0x01), # DATA read low while driven low ("DATA_FLOAT_OK", 0x02), # DATA read high after release (Hi-Z) ("ATN_RELEASED", 0x04), # ATN high (idle) ("CLK_RELEASED", 0x08), # CLK high (idle) ("RESET_RELEASED", 0x10), # RESET high (idle) ] buf = array.array("I", [0]) with open("/dev/iec0", "rb", buffering=0) as f: fcntl.ioctl(f, IEC_IOC_SELFTEST, buf, True) r = buf[0] print(" selftest = 0x%02x (%s)" % (r, "PASS" if r == 0x1F else "FAIL")) for name, bit in BITS: print(" %-15s %s" % (name, "ok" if r & bit else "MISSING")) sys.exit(0 if r == 0x1F else 1) PY ST=$? set -e # ---- 4. report (rmmod happens in the EXIT trap) ------------------------- echo if [ "$ST" -eq 0 ]; then ok "self-test PASSED (0x1F) - all four lines wired correctly" else info "self-test did not fully pass." info "On a BARE board 0x15 is expected and fine: only DATA_ASSERT_OK is" info "meaningful; ATN/RESET pass via the SoC's fixed GPIO2/3 pull-ups." info "Re-run once the level shifter is wired + powered; you want 0x1F." fi exit "$ST"