Compare commits
4 Commits
2881fae5bf
...
dafcdfbd7b
| Author | SHA1 | Date | |
|---|---|---|---|
| dafcdfbd7b | |||
| b3d63664a0 | |||
| 65ff323d50 | |||
| 3135a2502b |
@ -100,8 +100,10 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
# Release flow only: merges all built modules into one modules/ folder, wraps it
|
# Release flow only: merges all built modules into one modules/ folder, wraps it
|
||||||
# in a single repo-named top folder alongside selftest.sh, zips it and attaches
|
# in a single repo-named top folder alongside the helper scripts (selftest.sh,
|
||||||
# it to the Gitea release for the pushed tag. Skipped entirely on pull requests.
|
# launch.sh) and the iecpoc Python frontend (so launch.sh can pip-install it on
|
||||||
|
# the Pi), zips it and attaches it to the Gitea release for the pushed tag.
|
||||||
|
# Skipped entirely on pull requests.
|
||||||
release:
|
release:
|
||||||
needs: build
|
needs: build
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
@ -123,7 +125,14 @@ jobs:
|
|||||||
root="package/$repo"
|
root="package/$repo"
|
||||||
mkdir -p "$root"
|
mkdir -p "$root"
|
||||||
cp -r dist/modules "$root"/ # modules/iec_listener_<kernel_version>.ko
|
cp -r dist/modules "$root"/ # modules/iec_listener_<kernel_version>.ko
|
||||||
cp kernel/selftest.sh "$root"/ # selftest.sh only at the package root
|
cp kernel/selftest.sh "$root"/ # one-shot hardware self-test
|
||||||
|
cp launch.sh "$root"/ # load module -> run frontend -> unload
|
||||||
|
# iecpoc Python frontend + its packaging metadata. launch.sh pip-installs
|
||||||
|
# this on the Pi; the build reads pyproject.toml's readme = "README.md",
|
||||||
|
# so README.md must travel with it. __pycache__ is stripped to stay clean.
|
||||||
|
cp pyproject.toml README.md "$root"/
|
||||||
|
cp -r iecpoc "$root"/
|
||||||
|
find "$root/iecpoc" -name __pycache__ -type d -prune -exec rm -rf {} +
|
||||||
(cd package && zip -r "../${repo}-${{ github.ref_name }}.zip" "$repo")
|
(cd package && zip -r "../${repo}-${{ github.ref_name }}.zip" "$repo")
|
||||||
|
|
||||||
- name: Publish to Gitea release
|
- name: Publish to Gitea release
|
||||||
|
|||||||
33
README.md
33
README.md
@ -71,6 +71,39 @@ See [`docs/wiring.md`](docs/wiring.md) for the level-shifter circuit and the
|
|||||||
pre-C64 bring-up checklist, and [`docs/kernel-notes.md`](docs/kernel-notes.md)
|
pre-C64 bring-up checklist, and [`docs/kernel-notes.md`](docs/kernel-notes.md)
|
||||||
for the resolved kernel real-time decisions.
|
for the resolved kernel real-time decisions.
|
||||||
|
|
||||||
|
## Running the frontend on the Pi
|
||||||
|
|
||||||
|
A tagged build publishes a release zip that bundles everything needed to run on
|
||||||
|
a Pi without building anything:
|
||||||
|
|
||||||
|
```
|
||||||
|
comodore-iec-emu/
|
||||||
|
selftest.sh one-shot wiring self-test (no C64 connected)
|
||||||
|
launch.sh load module → run iecpoc → unload
|
||||||
|
pyproject.toml iecpoc packaging metadata
|
||||||
|
README.md
|
||||||
|
iecpoc/ the Python frontend
|
||||||
|
modules/ iec_listener_<kernel_version>.ko, one per supported kernel
|
||||||
|
```
|
||||||
|
|
||||||
|
Unzip it on the Pi and run **`launch.sh`**. It installs the frontend into a local
|
||||||
|
`.venv`, detects the running kernel and picks the matching `.ko` (by *vermagic*,
|
||||||
|
not filename), `insmod`s it, runs `iecpoc`, and **always `rmmod`s on exit** — so
|
||||||
|
the Pi is left exactly as before:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unzip comodore-iec-emu-*.zip && cd comodore-iec-emu
|
||||||
|
sudo ./selftest.sh # first: check the wiring (want 0x1F)
|
||||||
|
sudo ./launch.sh # then: load + run the frontend (address 4)
|
||||||
|
sudo ./launch.sh --address 8 # listen as device 8
|
||||||
|
sudo ./launch.sh -- --raw # forward extra args (after --) to iecpoc
|
||||||
|
```
|
||||||
|
|
||||||
|
The module is picked automatically; pass `--ko PATH` only to force a specific
|
||||||
|
file. `launch.sh` needs `python3` plus `python3-venv` (and network on first run
|
||||||
|
for the build backend). See [`kernel/README.md`](kernel/README.md) for the
|
||||||
|
self-test details and the per-kernel module selection.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
- **Phase 0 (userspace + tests):** complete, runs on any host.
|
- **Phase 0 (userspace + tests):** complete, runs on any host.
|
||||||
|
|||||||
@ -23,16 +23,26 @@ before connecting the real bus (see PLAN.md §10).
|
|||||||
autoload): it detects the running kernel, picks the matching module, loads it,
|
autoload): it detects the running kernel, picks the matching module, loads it,
|
||||||
runs the self-test, and always unloads it again.
|
runs the self-test, and always unloads it again.
|
||||||
|
|
||||||
A released package ships several kernel builds side by side:
|
A released package ships several kernel builds side by side, plus the launcher
|
||||||
|
and the Python frontend:
|
||||||
|
|
||||||
```
|
```
|
||||||
comodore-iec-emu/
|
comodore-iec-emu/
|
||||||
├── selftest.sh
|
├── selftest.sh # one-shot wiring self-test (this document)
|
||||||
|
├── launch.sh # load module → run the iecpoc frontend → unload
|
||||||
|
├── pyproject.toml # iecpoc packaging metadata (launch.sh pip-installs it)
|
||||||
|
├── README.md
|
||||||
|
├── iecpoc/ # the Python userspace decoder/trace
|
||||||
└── modules/
|
└── modules/
|
||||||
├── iec_listener_1-6.12.93-1+rpt1.ko # built for 6.12.x (bookworm)
|
├── iec_listener_1-6.12.93-1+rpt1.ko # built for 6.12.x (bookworm)
|
||||||
└── iec_listener_1-6.18.34-1+rpt1.ko # built for 6.18.x (trixie)
|
└── iec_listener_1-6.18.34-1+rpt1.ko # built for 6.18.x (trixie)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`selftest.sh` only verifies the wiring (it loads and immediately unloads). To
|
||||||
|
actually capture and decode C64 traffic, use **`launch.sh`**, which installs the
|
||||||
|
frontend, loads the matching module, runs `iecpoc`, and unloads on exit — see the
|
||||||
|
[top-level README](../README.md#running-the-frontend-on-the-pi).
|
||||||
|
|
||||||
You don't pick the file yourself: the script reads `uname -r`, then scans
|
You don't pick the file yourself: the script reads `uname -r`, then scans
|
||||||
`modules/` (and a few fallback locations) and selects the `.ko` whose **vermagic**
|
`modules/` (and a few fallback locations) and selects the `.ko` whose **vermagic**
|
||||||
matches the running kernel. Matching is by vermagic rather than filename because
|
matches the running kernel. Matching is by vermagic rather than filename because
|
||||||
|
|||||||
@ -107,8 +107,14 @@ if [ -z "$KO" ]; then
|
|||||||
[ -d "$dir" ] || continue
|
[ -d "$dir" ] || continue
|
||||||
for cand in "$dir"/${MODULE}*.ko; do
|
for cand in "$dir"/${MODULE}*.ko; do
|
||||||
[ -f "$cand" ] || continue
|
[ -f "$cand" ] || continue
|
||||||
vm="$(modinfo -F vermagic "$cand" 2>/dev/null)"
|
if vm="$(modinfo -F vermagic "$cand" 2>/dev/null)" && [ -n "$vm" ]; then
|
||||||
msg="$msg"$'\n'" $cand (built for ${vm%% *})"
|
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
|
any=1
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|||||||
149
launch.sh
Executable file
149
launch.sh
Executable file
@ -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] [-- <iecpoc args>]
|
||||||
|
#
|
||||||
|
# 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"
|
||||||
Loading…
x
Reference in New Issue
Block a user