Enhance module selection process by validating vermagic against the running kernel. Add detailed error messages with suggestions for manual path overrides or rebuilding for the current kernel. Ensure robust fallback behavior and improve user guidance.
191 lines
6.8 KiB
Bash
Executable File
191 lines
6.8 KiB
Bash
Executable File
#!/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"
|
|
|
|
command -v modinfo >/dev/null || die "modinfo not found (install kmod)"
|
|
|
|
# ---- locate the module matching the running kernel ----------------------
|
|
# A released package ships one module per supported kernel under modules/.
|
|
# The build workflow names each file iec_listener_<kernel_version>.ko, with
|
|
# ':' and '/' in the version replaced by '-' (1:6.12.93-1+rpt1 ->
|
|
# iec_listener_1-6.12.93-1+rpt1.ko); local builds produce a bare
|
|
# iec_listener.ko. The glob below ($MODULE*.ko) matches both.
|
|
#
|
|
# We select by vermagic, not by filename: the package name carries a Debian
|
|
# epoch/revision (1:6.12.93-1+rpt1) that `uname -r` (6.12.93+rpt-rpi-v8) does
|
|
# not, so the .ko whose vermagic matches the running kernel is the one to load.
|
|
RUNNING="$(uname -r)"
|
|
|
|
# Where to look, de-duplicated ($(dirname "$0") is often "." when run locally).
|
|
SEARCH_DIRS=()
|
|
for d in "$(dirname "$0")/modules" "$(dirname "$0")" "./modules" "." "$HOME"; do
|
|
skip=0
|
|
for s in "${SEARCH_DIRS[@]:-}"; do [ "$s" = "$d" ] && skip=1; done
|
|
[ "$skip" = 0 ] && SEARCH_DIRS+=("$d")
|
|
done
|
|
|
|
if [ -z "$KO" ]; then
|
|
info "running kernel: $RUNNING"
|
|
for dir in "${SEARCH_DIRS[@]}"; do
|
|
[ -d "$dir" ] || continue
|
|
for cand in "$dir"/${MODULE}*.ko; do
|
|
[ -f "$cand" ] || continue
|
|
vm="$(modinfo -F vermagic "$cand" 2>/dev/null)" || continue
|
|
if [ "${vm%% *}" = "$RUNNING" ]; then
|
|
KO="$cand"; break 2
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
|
|
if [ -z "$KO" ]; then
|
|
msg="no module matching the running kernel ($RUNNING) found.
|
|
Pass a path explicitly (sudo $0 path/to/${MODULE}.ko), or rebuild for
|
|
this kernel. Modules seen:"
|
|
any=0
|
|
for dir in "${SEARCH_DIRS[@]}"; do
|
|
[ -d "$dir" ] || continue
|
|
for cand in "$dir"/${MODULE}*.ko; do
|
|
[ -f "$cand" ] || continue
|
|
vm="$(modinfo -F vermagic "$cand" 2>/dev/null)"
|
|
msg="$msg"$'\n'" $cand (built for ${vm%% *})"
|
|
any=1
|
|
done
|
|
done
|
|
[ "$any" = 1 ] || msg="$msg"$'\n'" (none)"
|
|
die "$msg"
|
|
fi
|
|
[ -f "$KO" ] || die "module not found: $KO"
|
|
|
|
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"
|