All checks were successful
Build kernel module / build (1:6.12.93-1+rpt1, bookworm) (pull_request) Successful in 1m1s
Build kernel module / build (1:6.18.34-1+rpt1, trixie) (pull_request) Successful in 1m8s
Build kernel module / package (pull_request) Successful in 29s
Build kernel module / release (pull_request) Has been skipped
Adopt the confirmed-working reference's pin assignment so the module runs on that proven wiring: DATA=BCM2 (pin 3), CLK=BCM3 (pin 5), ATN=BCM4 (pin 7). RESET is kept and relocated to BCM17 (pin 11), the pin freed by moving CLK. All five GPIOs stay in bank 0, so the direct-register hot path and ATN IRQ are unchanged -- only the IEC_GPIO_* defines, the device-tree overlay, and the docs/self-test move. DATA/CLK now sit on the ARM I2C pins (GPIO2/3) with the SoC's fixed ~1.8k pull-ups; keep dtparam=i2c_arm off. The bare-board self-test expectation changes from 0x15 to 0x0b accordingly. Generated by Clanker
198 lines
7.2 KiB
Bash
Executable File
198 lines
7.2 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
|
|
if vm="$(modinfo -F vermagic "$cand" 2>/dev/null)" && [ -n "$vm" ]; then
|
|
note="built for ${vm%% *}"
|
|
else
|
|
# modinfo couldn't read it - usually not an ELF .ko at all
|
|
# (e.g. a still-zipped release file). Show what it really is.
|
|
note="UNREADABLE: $(file -b "$cand" 2>/dev/null || echo 'not a kernel module') - unzip/rebuild?"
|
|
fi
|
|
msg="$msg"$'\n'" $cand ($note)"
|
|
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 0x0b is expected and fine: DATA_FLOAT_OK + CLK_RELEASED"
|
|
info "pass via the SoC's fixed GPIO2/3 (DATA/CLK) pull-ups, and DATA_ASSERT_OK"
|
|
info "is internal; ATN/RESET read low (GPIO4/17 have no built-in pull-up)."
|
|
info "Re-run once the level shifter is wired + powered; you want 0x1F."
|
|
fi
|
|
exit "$ST"
|