Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a79a469aac | |||
| 27ff299f43 | |||
| a5406f503c | |||
| dafcdfbd7b | |||
| b3d63664a0 | |||
| 65ff323d50 | |||
| 3135a2502b | |||
| 2881fae5bf | |||
| f91b06da9d | |||
| 2aa5104812 | |||
| 6e5ec9b84f | |||
| 160f6bb799 | |||
| c9cfebafda | |||
| 62dff1264d | |||
| de2fb56a59 | |||
| 914c92bdea | |||
| aeb5113366 | |||
| 3aecb25a61 | |||
| fcd02b6a85 | |||
| ce621d947c |
@ -27,8 +27,14 @@ jobs:
|
|||||||
# Don't cancel the other versions if one fails to build.
|
# Don't cancel the other versions if one fails to build.
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
kernel_version:
|
# Each raspberrypi archive suite only carries its own latest kernel, so
|
||||||
- "1:6.12.93-1+rpt1"
|
# the Debian base suite is paired with the kernel version it can install:
|
||||||
|
# bookworm -> 6.12.x, trixie -> 6.18.x.
|
||||||
|
include:
|
||||||
|
- kernel_version: "1:6.12.93-1+rpt1"
|
||||||
|
suite: bookworm
|
||||||
|
- kernel_version: "1:6.18.34-1+rpt1"
|
||||||
|
suite: trixie
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -38,28 +44,40 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
HEADERS_PKG: ${{ env.HEADERS_PKG }}
|
HEADERS_PKG: ${{ env.HEADERS_PKG }}
|
||||||
KERNEL_VERSION: ${{ matrix.kernel_version }}
|
KERNEL_VERSION: ${{ matrix.kernel_version }}
|
||||||
|
DEBIAN_SUITE: ${{ matrix.suite }}
|
||||||
run: |
|
run: |
|
||||||
set -eux
|
set -eux
|
||||||
# arm64 emulation for the builder image (no-op if already registered).
|
# arm64 emulation for the builder image (no-op if already registered).
|
||||||
if [ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]; then
|
if [ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]; then
|
||||||
docker run --privileged --rm tonistiigi/binfmt --install arm64
|
docker run --privileged --rm tonistiigi/binfmt --install arm64
|
||||||
fi
|
fi
|
||||||
|
# Per-version image tag. The matrix entries can run concurrently on a
|
||||||
|
# single runner sharing one Docker daemon; a fixed tag (e.g. iec-kbuild)
|
||||||
|
# is then a shared mutable name and the builds race — whichever `docker
|
||||||
|
# build` finishes last wins the tag, so both `docker create` calls
|
||||||
|
# resolve to the same image and every job emits the same vermagic.
|
||||||
|
# A unique tag per kernel version isolates them. Docker tags allow only
|
||||||
|
# [a-zA-Z0-9._-], so map ':' '/' '+' (all present in 1:6.12.93-1+rpt1)
|
||||||
|
# to '-'; the tag just needs to be unique and valid, not reversible.
|
||||||
|
img="iec-kbuild:${KERNEL_VERSION//[:\/+]/-}"
|
||||||
# Builder image: toolchain + matching raspberrypi kernel headers.
|
# Builder image: toolchain + matching raspberrypi kernel headers.
|
||||||
docker build --platform linux/arm64 \
|
docker build --platform linux/arm64 \
|
||||||
|
--build-arg DEBIAN_SUITE="$DEBIAN_SUITE" \
|
||||||
--build-arg HEADERS_PKG="$HEADERS_PKG" \
|
--build-arg HEADERS_PKG="$HEADERS_PKG" \
|
||||||
--build-arg KERNEL_VERSION="$KERNEL_VERSION" \
|
--build-arg KERNEL_VERSION="$KERNEL_VERSION" \
|
||||||
-t iec-kbuild .
|
-t "$img" .
|
||||||
# Compile inside the container. We use `docker cp` instead of the bind
|
# Compile inside the container. We use `docker cp` instead of the bind
|
||||||
# mount that build-in-docker.sh uses for local builds: under the runner's
|
# mount that build-in-docker.sh uses for local builds: under the runner's
|
||||||
# docker-in-docker, /workspace is a volume the host daemon can't see, so
|
# docker-in-docker, /workspace is a volume the host daemon can't see, so
|
||||||
# `-v "$PWD:/build"` mounts an empty dir and make finds no Makefile.
|
# `-v "$PWD:/build"` mounts an empty dir and make finds no Makefile.
|
||||||
cid=$(docker create --platform linux/arm64 --entrypoint sleep iec-kbuild infinity)
|
cid=$(docker create --platform linux/arm64 --entrypoint sleep "$img" infinity)
|
||||||
docker start "$cid"
|
docker start "$cid"
|
||||||
docker cp ./. "$cid:/build"
|
docker cp ./. "$cid:/build"
|
||||||
docker exec "$cid" /usr/local/bin/docker-entrypoint.sh clean
|
docker exec "$cid" /usr/local/bin/docker-entrypoint.sh clean
|
||||||
docker exec "$cid" /usr/local/bin/docker-entrypoint.sh
|
docker exec "$cid" /usr/local/bin/docker-entrypoint.sh
|
||||||
docker cp "$cid:/build/iec_listener.ko" ./iec_listener.ko
|
docker cp "$cid:/build/iec_listener.ko" ./iec_listener.ko
|
||||||
docker rm -f "$cid"
|
docker rm -f "$cid"
|
||||||
|
docker rmi "$img" || true
|
||||||
|
|
||||||
- name: Stage build output (modules/iec_listener_<kernel_version>.ko)
|
- name: Stage build output (modules/iec_listener_<kernel_version>.ko)
|
||||||
working-directory: kernel
|
working-directory: kernel
|
||||||
@ -69,46 +87,101 @@ jobs:
|
|||||||
echo "kernel version $ver -> file modules/iec_listener_${safe}.ko"
|
echo "kernel version $ver -> file modules/iec_listener_${safe}.ko"
|
||||||
mkdir -p out/modules
|
mkdir -p out/modules
|
||||||
mv iec_listener.ko "out/modules/iec_listener_${safe}.ko"
|
mv iec_listener.ko "out/modules/iec_listener_${safe}.ko"
|
||||||
|
# Expose the path-friendly name for the (colon-rejecting) artifact name.
|
||||||
|
echo "SAFE_VER=$safe" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Upload build output
|
- name: Upload build output
|
||||||
uses: https://github.com/christopherHX/gitea-upload-artifact@v4
|
uses: https://github.com/christopherHX/gitea-upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
# Name by the path-friendly version so artifact names stay unique; all
|
# Name by the path-friendly version so artifact names stay unique; all
|
||||||
# of them merge back into the single modules/ folder at download time.
|
# of them merge back into the single modules/ folder at download time.
|
||||||
name: ${{ matrix.kernel_version }}
|
name: ${{ env.SAFE_VER }}
|
||||||
path: kernel/out
|
path: kernel/out
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
# Release flow only: merges all built modules into one modules/ folder, wraps it
|
# Merge all built modules into one modules/ folder, wrap it in a single
|
||||||
# in a single repo-named top folder alongside selftest.sh, zips it and attaches
|
# repo-named top folder alongside the helper scripts (selftest.sh, launch.sh)
|
||||||
# it to the Gitea release for the pushed tag. Skipped entirely on pull requests.
|
# and the iecpoc Python frontend (so launch.sh can pip-install it on the Pi).
|
||||||
release:
|
# Runs on BOTH triggers and uploads the assembled FOLDER as an artifact: on a PR
|
||||||
|
# this lets you download and smoke-test the exact tree a release would ship; on a
|
||||||
|
# tag the release job below zips this same tree and attaches it.
|
||||||
|
#
|
||||||
|
# We intentionally upload the folder, not a pre-made .zip: artifacts are always
|
||||||
|
# transported as a zip by the platform, so uploading a .zip would nest one zip
|
||||||
|
# inside another. Uploading the tree makes the platform's wrapper zip *be* the
|
||||||
|
# package archive (a single, clean layer).
|
||||||
|
package:
|
||||||
needs: build
|
needs: build
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Download all build output
|
- name: Download all build output
|
||||||
uses: actions/download-artifact@v4
|
uses: https://github.com/christopherHX/gitea-download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
# Merge every artifact back into the shared modules/ folder.
|
# Merge every per-kernel artifact back into the shared modules/ folder.
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Assemble release package
|
- name: Determine version label
|
||||||
|
id: ver
|
||||||
|
run: |
|
||||||
|
# Tags ship under the tag name; PR builds get a traceable pr<n>-<sha>.
|
||||||
|
case "${{ github.ref }}" in
|
||||||
|
refs/tags/*) v="${{ github.ref_name }}" ;;
|
||||||
|
*) v="pr${{ github.event.pull_request.number }}-$(echo "${{ github.sha }}" | cut -c1-7)" ;;
|
||||||
|
esac
|
||||||
|
echo "ver=$v" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Assemble package tree
|
||||||
run: |
|
run: |
|
||||||
repo="${{ github.event.repository.name }}"
|
repo="${{ github.event.repository.name }}"
|
||||||
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
|
||||||
(cd package && zip -r "../${repo}-${{ github.ref_name }}.zip" "$repo")
|
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 {} +
|
||||||
|
|
||||||
|
- name: Upload package
|
||||||
|
uses: https://github.com/christopherHX/gitea-upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.event.repository.name }}-${{ steps.ver.outputs.ver }}
|
||||||
|
# Upload the contents of package/, so the artifact zip contains the
|
||||||
|
# single top folder package/<repo>/ -> <repo>/...
|
||||||
|
path: package
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
# Release flow only: download the assembled tree the package job uploaded, zip it
|
||||||
|
# into the named release asset and attach it to the Gitea release for the pushed
|
||||||
|
# tag. No re-assembly here (the tree is taken as-is). Skipped on PRs.
|
||||||
|
release:
|
||||||
|
needs: package
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download package
|
||||||
|
uses: https://github.com/christopherHX/gitea-download-artifact@v4
|
||||||
|
with:
|
||||||
|
# The single artifact the package job produced for this tag; extracts to
|
||||||
|
# dist/<repo>/...
|
||||||
|
name: ${{ github.event.repository.name }}-${{ github.ref_name }}
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
- name: Zip release asset
|
||||||
|
run: |
|
||||||
|
repo="${{ github.event.repository.name }}"
|
||||||
|
(cd dist && zip -r "../${repo}-${{ github.ref_name }}.zip" "$repo")
|
||||||
|
|
||||||
- name: Publish to Gitea release
|
- name: Publish to Gitea release
|
||||||
uses: actions/gitea-release-action@v1.3.6
|
uses: https://gitea.com/actions/gitea-release-action@v1.3.6
|
||||||
with:
|
with:
|
||||||
api_key: ${{ secrets.GITEA_TOKEN }}
|
api_key: ${{ secrets.GITEA_TOKEN }}
|
||||||
files: |-
|
files: |-
|
||||||
*.zip
|
${{ github.event.repository.name }}-${{ github.ref_name }}.zip
|
||||||
|
|||||||
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.
|
||||||
|
|||||||
@ -168,14 +168,27 @@ Configurable via env vars (kernel version is configurable as requested):
|
|||||||
|
|
||||||
| Var | Default | Purpose |
|
| Var | Default | Purpose |
|
||||||
|-----|---------|---------|
|
|-----|---------|---------|
|
||||||
|
| `SUITE` | `bookworm` | Debian/RPi OS suite; **must match the kernel** — `bookworm` ⇒ 6.12.x, `trixie` ⇒ 6.18.x |
|
||||||
| `HEADERS_PKG` | `linux-headers-rpi-v8` | headers package; use `-v7`/`-v6` for 32-bit, or `raspberrypi-kernel-headers` |
|
| `HEADERS_PKG` | `linux-headers-rpi-v8` | headers package; use `-v7`/`-v6` for 32-bit, or `raspberrypi-kernel-headers` |
|
||||||
| `KERNEL_VERSION` | *(latest)* | exact version pin, e.g. `1:6.6.51-1+rpt3` |
|
| `KERNEL_VERSION` | *(latest)* | exact version pin, e.g. `1:6.6.51-1+rpt3` |
|
||||||
| `IMAGE` | `iec-kbuild` | builder image tag |
|
| `IMAGE` | `iec-kbuild` | builder image tag |
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
KERNEL_VERSION=1:6.6.51-1+rpt3 ./build-in-docker.sh
|
SUITE=trixie KERNEL_VERSION=1:6.18.34-1+rpt1 ./build-in-docker.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**`SUITE` must match `KERNEL_VERSION`.** Each raspberrypi archive suite carries
|
||||||
|
only its own latest kernel, so a trixie-era version against a `bookworm` base
|
||||||
|
fails with `Version '…' was not found`. Find the Pi's suite with
|
||||||
|
`. /etc/os-release; echo "$VERSION_CODENAME"` (or `lsb_release -cs`).
|
||||||
|
|
||||||
|
**trixie SHA1 caveat.** On trixie, apt verifies signatures with Sequoia (`sqv`),
|
||||||
|
whose crypto policy rejects SHA1 since 2026-02-01. The raspberrypi archive key's
|
||||||
|
binding self-signature is SHA1, so trixie's apt rejects the repo as *"not
|
||||||
|
signed"* (`Policy rejected … SHA1 is not considered secure …`). The Dockerfile
|
||||||
|
works around this by marking the raspberrypi repo `trusted=yes` **only when
|
||||||
|
`DEBIAN_SUITE=trixie`**; bookworm (gpgv) keeps full signature verification.
|
||||||
|
|
||||||
**Find the exact values for *your* Pi** — run this on the Pi (e.g. over SSH); it
|
**Find the exact values for *your* Pi** — run this on the Pi (e.g. over SSH); it
|
||||||
prints the two lines ready to copy into the `build-in-docker.sh` invocation:
|
prints the two lines ready to copy into the `build-in-docker.sh` invocation:
|
||||||
|
|
||||||
@ -183,19 +196,21 @@ prints the two lines ready to copy into the `build-in-docker.sh` invocation:
|
|||||||
pkg="linux-headers-$(uname -r | sed 's/.*+rpt-//')"; echo "HEADERS_PKG=$pkg KERNEL_VERSION=$(dpkg-query -W -f='${Version}' "$pkg")"
|
pkg="linux-headers-$(uname -r | sed 's/.*+rpt-//')"; echo "HEADERS_PKG=$pkg KERNEL_VERSION=$(dpkg-query -W -f='${Version}' "$pkg")"
|
||||||
```
|
```
|
||||||
|
|
||||||
On the project's Pi Zero 2 W (`chris@10.1.0.41`, kernel `6.12.93+rpt-rpi-v8`)
|
On the Pi at `chris@10.1.0.41` (a Pi 3B+, kernel `6.12.93+rpt-rpi-v8`; same
|
||||||
this currently prints:
|
arm64/`-v8` headers as the Zero 2 W) this currently prints:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
HEADERS_PKG=linux-headers-rpi-v8 KERNEL_VERSION=1:6.12.93-1+rpt1
|
HEADERS_PKG=linux-headers-rpi-v8 KERNEL_VERSION=1:6.12.93-1+rpt1
|
||||||
```
|
```
|
||||||
|
|
||||||
**vermagic caveat:** the raspberrypi apt archive normally serves only the
|
**vermagic caveat:** each raspberrypi apt archive **suite** normally serves only
|
||||||
*latest* kernel in its pool, so pinning `KERNEL_VERSION` to an old release may
|
the *latest* kernel in its pool (`bookworm` ⇒ 6.12.x, `trixie` ⇒ 6.18.x), so the
|
||||||
not be downloadable. The reliable strategy is to keep the Pi current
|
container's `SUITE` must match the Pi's release, and pinning `KERNEL_VERSION` to
|
||||||
(`sudo apt full-upgrade`) and build with the default (latest) — then the Pi and
|
an old release within a suite may not be downloadable. The reliable strategy is
|
||||||
the container agree. If you must target an older/specific kernel, copy the Pi's
|
to keep the Pi current (`sudo apt full-upgrade`) and build with the matching
|
||||||
`/lib/modules/$(uname -r)/build` tree into the container instead of using apt.
|
`SUITE` + default (latest) version — then the Pi and the container agree. If you
|
||||||
|
must target an older/specific kernel, copy the Pi's `/lib/modules/$(uname
|
||||||
|
-r)/build` tree into the container instead of using apt.
|
||||||
|
|
||||||
`uname -r` is **not** used inside the container (it reports the host kernel under
|
`uname -r` is **not** used inside the container (it reports the host kernel under
|
||||||
emulation); the entrypoint derives `KDIR` from the installed headers under
|
emulation); the entrypoint derives `KDIR` from the installed headers under
|
||||||
|
|||||||
@ -9,18 +9,38 @@
|
|||||||
# docker run --privileged --rm tonistiigi/binfmt --install arm64
|
# docker run --privileged --rm tonistiigi/binfmt --install arm64
|
||||||
#
|
#
|
||||||
# Kernel version is configurable via build args:
|
# Kernel version is configurable via build args:
|
||||||
|
# --build-arg DEBIAN_SUITE=trixie # Debian/RPi OS suite (see notes)
|
||||||
# --build-arg HEADERS_PKG=linux-headers-rpi-v8 # 64-bit Pi Zero 2 W (default)
|
# --build-arg HEADERS_PKG=linux-headers-rpi-v8 # 64-bit Pi Zero 2 W (default)
|
||||||
# --build-arg KERNEL_VERSION=1:6.6.51-1+rpt3 # optional exact pin (see notes)
|
# --build-arg KERNEL_VERSION=1:6.6.51-1+rpt3 # optional exact pin (see notes)
|
||||||
#
|
#
|
||||||
|
# DEBIAN_SUITE must match the kernel you target: each raspberrypi archive suite
|
||||||
|
# only carries its own latest kernel (bookworm -> 6.12.x, trixie -> 6.18.x), so
|
||||||
|
# pinning a trixie-era KERNEL_VERSION against a bookworm base will fail with
|
||||||
|
# "Version ... was not found". Keep this in sync with the Pi's release.
|
||||||
|
#
|
||||||
# Build context is this kernel/ directory.
|
# Build context is this kernel/ directory.
|
||||||
FROM --platform=linux/arm64 debian:bookworm
|
ARG DEBIAN_SUITE=bookworm
|
||||||
|
FROM --platform=linux/arm64 debian:${DEBIAN_SUITE}
|
||||||
|
|
||||||
|
# Re-declare after FROM so it is in scope for the RUN below (ARGs before FROM
|
||||||
|
# are only visible to the FROM line itself).
|
||||||
|
ARG DEBIAN_SUITE=bookworm
|
||||||
|
|
||||||
# The raspberrypi kernel/headers live in the raspberrypi.com archive, not Debian.
|
# The raspberrypi kernel/headers live in the raspberrypi.com archive, not Debian.
|
||||||
|
#
|
||||||
|
# On trixie, apt verifies signatures with Sequoia (sqv), whose crypto policy
|
||||||
|
# rejects SHA1 since 2026-02-01. The raspberrypi archive key's binding
|
||||||
|
# self-signature is SHA1, so trixie's apt refuses the repo as "not signed"
|
||||||
|
# (bookworm's gpgv doesn't enforce this). We mark the repo trusted=yes *only on
|
||||||
|
# trixie* to bypass that check; bookworm keeps full signature verification. The
|
||||||
|
# key is still fetched over HTTPS and pinned via signed-by where it is honoured.
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends ca-certificates curl gnupg \
|
&& apt-get install -y --no-install-recommends ca-certificates curl gnupg \
|
||||||
&& curl -fsSL https://archive.raspberrypi.com/debian/raspberrypi.gpg.key \
|
&& curl -fsSL https://archive.raspberrypi.com/debian/raspberrypi.gpg.key \
|
||||||
| gpg --dearmor -o /usr/share/keyrings/raspberrypi-archive-keyring.gpg \
|
| gpg --dearmor -o /usr/share/keyrings/raspberrypi-archive-keyring.gpg \
|
||||||
&& echo "deb [signed-by=/usr/share/keyrings/raspberrypi-archive-keyring.gpg] http://archive.raspberrypi.com/debian/ bookworm main" \
|
&& opts="signed-by=/usr/share/keyrings/raspberrypi-archive-keyring.gpg" \
|
||||||
|
&& if [ "$DEBIAN_SUITE" = "trixie" ]; then opts="$opts trusted=yes"; fi \
|
||||||
|
&& echo "deb [$opts] http://archive.raspberrypi.com/debian/ ${DEBIAN_SUITE} main" \
|
||||||
> /etc/apt/sources.list.d/raspi.list \
|
> /etc/apt/sources.list.d/raspi.list \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|||||||
@ -20,14 +20,45 @@ before connecting the real bus (see PLAN.md §10).
|
|||||||
### Running it
|
### Running it
|
||||||
|
|
||||||
`selftest.sh` is non-persistent (nothing is installed into `/lib/modules`, no
|
`selftest.sh` is non-persistent (nothing is installed into `/lib/modules`, no
|
||||||
autoload): it checks vermagic, loads the module, runs the self-test, and always
|
autoload): it detects the running kernel, picks the matching module, loads it,
|
||||||
unloads it again.
|
runs the self-test, and always unloads it again.
|
||||||
|
|
||||||
|
A released package ships several kernel builds side by side, plus the launcher
|
||||||
|
and the Python frontend:
|
||||||
|
|
||||||
|
```
|
||||||
|
comodore-iec-emu/
|
||||||
|
├── 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/
|
||||||
|
├── 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)
|
||||||
|
```
|
||||||
|
|
||||||
|
`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
|
||||||
|
`modules/` (and a few fallback locations) and selects the `.ko` whose **vermagic**
|
||||||
|
matches the running kernel. Matching is by vermagic rather than filename because
|
||||||
|
the packaged name carries a Debian epoch/revision (`1:6.12.93-1+rpt1`) that
|
||||||
|
`uname -r` (`6.12.93+rpt-rpi-v8`) does not.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ./selftest.sh # auto-finds ./ or ~/iec_listener.ko, address 4
|
sudo ./selftest.sh # auto-selects the matching module, address 4
|
||||||
sudo ./selftest.sh ~/iec_listener.ko --address 5 # runs with ~/iec_listener.ko, address 5
|
sudo ./selftest.sh --address 5 # same, address 5
|
||||||
|
sudo ./selftest.sh ~/iec_listener.ko --address 5 # force a specific module file
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If no module matches the running kernel, the script lists the modules it found
|
||||||
|
(with the kernel each was built for) and exits — rebuild for the current kernel,
|
||||||
|
or pass a path explicitly.
|
||||||
|
|
||||||
It exits non-zero unless the result is a full pass (`0x1F`), so it is usable in
|
It exits non-zero unless the result is a full pass (`0x1F`), so it is usable in
|
||||||
scripts/CI.
|
scripts/CI.
|
||||||
|
|
||||||
|
|||||||
@ -9,19 +9,22 @@
|
|||||||
# ./build-in-docker.sh clean # clean build artifacts
|
# ./build-in-docker.sh clean # clean build artifacts
|
||||||
#
|
#
|
||||||
# Configurable via environment variables:
|
# Configurable via environment variables:
|
||||||
|
# SUITE Debian/RPi OS suite (default: bookworm; use trixie for 6.18.x)
|
||||||
# HEADERS_PKG headers package (default: linux-headers-rpi-v8, 64-bit Zero 2 W)
|
# HEADERS_PKG headers package (default: linux-headers-rpi-v8, 64-bit Zero 2 W)
|
||||||
# e.g. linux-headers-rpi-v7 / -v6 for 32-bit, raspberrypi-kernel-headers
|
# e.g. linux-headers-rpi-v7 / -v6 for 32-bit, raspberrypi-kernel-headers
|
||||||
# KERNEL_VERSION exact version to pin, e.g. 1:6.6.51-1+rpt3 (default: latest in repo)
|
# KERNEL_VERSION exact version to pin, e.g. 1:6.6.51-1+rpt3 (default: latest in repo)
|
||||||
# IMAGE builder image tag (default: iec-kbuild)
|
# IMAGE builder image tag (default: iec-kbuild)
|
||||||
#
|
#
|
||||||
# NOTE: the raspberrypi apt archive generally serves only the *latest* kernel in
|
# NOTE: each raspberrypi apt archive suite serves only the *latest* kernel in its
|
||||||
# its pool, so pinning KERNEL_VERSION to an old release may fail to download. The
|
# pool (bookworm -> 6.12.x, trixie -> 6.18.x), so SUITE must match the kernel you
|
||||||
|
# target and pinning KERNEL_VERSION to an old release may fail to download. The
|
||||||
# reliable match strategy is to keep the Pi current (`sudo apt full-upgrade`) and
|
# reliable match strategy is to keep the Pi current (`sudo apt full-upgrade`) and
|
||||||
# build with the default (latest). See docs/kernel-notes.md.
|
# build with the matching SUITE + default (latest) version. See docs/kernel-notes.md.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
IMAGE="${IMAGE:-iec-kbuild}"
|
IMAGE="${IMAGE:-iec-kbuild}"
|
||||||
|
SUITE="${SUITE:-bookworm}"
|
||||||
HEADERS_PKG="${HEADERS_PKG:-linux-headers-rpi-v8}"
|
HEADERS_PKG="${HEADERS_PKG:-linux-headers-rpi-v8}"
|
||||||
KERNEL_VERSION="${KERNEL_VERSION:-}"
|
KERNEL_VERSION="${KERNEL_VERSION:-}"
|
||||||
|
|
||||||
@ -32,8 +35,9 @@ if [ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# 2. Build the builder image (installs toolchain + matching kernel headers).
|
# 2. Build the builder image (installs toolchain + matching kernel headers).
|
||||||
echo "==> building image '$IMAGE' (HEADERS_PKG=$HEADERS_PKG KERNEL_VERSION=${KERNEL_VERSION:-latest})"
|
echo "==> building image '$IMAGE' (SUITE=$SUITE HEADERS_PKG=$HEADERS_PKG KERNEL_VERSION=${KERNEL_VERSION:-latest})"
|
||||||
docker build --platform linux/arm64 \
|
docker build --platform linux/arm64 \
|
||||||
|
--build-arg DEBIAN_SUITE="$SUITE" \
|
||||||
--build-arg HEADERS_PKG="$HEADERS_PKG" \
|
--build-arg HEADERS_PKG="$HEADERS_PKG" \
|
||||||
--build-arg KERNEL_VERSION="$KERNEL_VERSION" \
|
--build-arg KERNEL_VERSION="$KERNEL_VERSION" \
|
||||||
-t "$IMAGE" .
|
-t "$IMAGE" .
|
||||||
|
|||||||
@ -108,16 +108,26 @@ static void emit_record(u8 kind, u8 value, u8 flags)
|
|||||||
|
|
||||||
/* --- busy-poll helper ---------------------------------------------------- */
|
/* --- busy-poll helper ---------------------------------------------------- */
|
||||||
/*
|
/*
|
||||||
* Spin until CLK reaches `want_asserted`, or timeout/ATN/RESET. Caller holds
|
* Spin until CLK reaches `want_asserted`, or timeout/ATN-change/RESET. Caller
|
||||||
* local IRQs disabled. Uses a microsecond budget rather than a fixed udelay so
|
* holds local IRQs disabled. Uses a microsecond budget rather than a fixed
|
||||||
* a slightly-fast C64 is handled correctly (research §1.3).
|
* udelay so a slightly-fast C64 is handled correctly (research §1.3).
|
||||||
|
*
|
||||||
|
* The ATN abort is phase-relative, and IEC_RX_ATN means "ATN changed, abort":
|
||||||
|
* - data_phase: ATN is released; abort if it ASSERTS (C64 interrupts data
|
||||||
|
* transfer with a new command).
|
||||||
|
* - command phase: ATN is asserted for the whole command byte; abort if it
|
||||||
|
* RELEASES (the command sequence is over -> hand back to the state machine).
|
||||||
|
* Treating any asserted ATN as an abort (the previous behaviour) made command
|
||||||
|
* reception impossible: ATN is legitimately low the entire command phase, so
|
||||||
|
* the very first poll aborted before a single bit was clocked.
|
||||||
*/
|
*/
|
||||||
static enum iec_rx wait_clk(bool want_asserted, unsigned int timeout_us)
|
static enum iec_rx wait_clk(bool want_asserted, unsigned int timeout_us,
|
||||||
|
bool data_phase)
|
||||||
{
|
{
|
||||||
unsigned int waited = 0;
|
unsigned int waited = 0;
|
||||||
|
|
||||||
while (iec_clk_asserted() != want_asserted) {
|
while (iec_clk_asserted() != want_asserted) {
|
||||||
if (iec_atn_asserted())
|
if (iec_atn_asserted() == data_phase)
|
||||||
return IEC_RX_ATN;
|
return IEC_RX_ATN;
|
||||||
if (iec_reset_asserted())
|
if (iec_reset_asserted())
|
||||||
return IEC_RX_RESET;
|
return IEC_RX_RESET;
|
||||||
@ -143,7 +153,7 @@ static enum iec_rx receive_byte(u8 *out, bool *eoi, bool data_phase)
|
|||||||
*eoi = false;
|
*eoi = false;
|
||||||
|
|
||||||
/* 1. READY-FOR-DATA: wait for talker to release CLK, then release DATA */
|
/* 1. READY-FOR-DATA: wait for talker to release CLK, then release DATA */
|
||||||
rc = wait_clk(false /* released */, IEC_CLK_TIMEOUT_US);
|
rc = wait_clk(false /* released */, IEC_CLK_TIMEOUT_US, data_phase);
|
||||||
if (rc != IEC_RX_OK)
|
if (rc != IEC_RX_OK)
|
||||||
return rc;
|
return rc;
|
||||||
iec_data_release();
|
iec_data_release();
|
||||||
@ -171,10 +181,12 @@ static enum iec_rx receive_byte(u8 *out, bool *eoi, bool data_phase)
|
|||||||
|
|
||||||
/* 3. RECEIVE 8 BITS, LSB first */
|
/* 3. RECEIVE 8 BITS, LSB first */
|
||||||
for (i = 0; i < 8; i++) {
|
for (i = 0; i < 8; i++) {
|
||||||
rc = wait_clk(true /* asserted: data invalid/setup */, IEC_CLK_TIMEOUT_US);
|
rc = wait_clk(true /* asserted: data invalid/setup */,
|
||||||
|
IEC_CLK_TIMEOUT_US, data_phase);
|
||||||
if (rc != IEC_RX_OK)
|
if (rc != IEC_RX_OK)
|
||||||
goto out;
|
goto out;
|
||||||
rc = wait_clk(false /* released: data valid -> sample */, IEC_CLK_TIMEOUT_US);
|
rc = wait_clk(false /* released: data valid -> sample */,
|
||||||
|
IEC_CLK_TIMEOUT_US, data_phase);
|
||||||
if (rc != IEC_RX_OK)
|
if (rc != IEC_RX_OK)
|
||||||
goto out;
|
goto out;
|
||||||
/* released(high) = bit 1, asserted(low) = bit 0 */
|
/* released(high) = bit 1, asserted(low) = bit 0 */
|
||||||
|
|||||||
@ -62,13 +62,66 @@ done
|
|||||||
case "$ADDRESS" in ''|*[!0-9]*) die "address must be 0-30 (got '$ADDRESS')" ;; esac
|
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"
|
{ [ "$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
|
if [ -z "$KO" ]; then
|
||||||
for cand in "./${MODULE}.ko" "$HOME/${MODULE}.ko" "$(dirname "$0")/${MODULE}.ko"; do
|
info "running kernel: $RUNNING"
|
||||||
[ -f "$cand" ] && { KO="$cand"; break; }
|
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
|
done
|
||||||
fi
|
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)"
|
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 "module: $KO"
|
||||||
info "address: $ADDRESS"
|
info "address: $ADDRESS"
|
||||||
|
|||||||
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