Skip to content

Repository files navigation

WASI GPIO and CAN

This repository provides WASI component APIs for GPIO and CAN, Wasmtime host implementations for Linux and Raspberry Pi, and native/Wasm programs that exercise and benchmark both interfaces. Each host uses a TOML policy to decide which physical devices a component may open.

Repository structure

Path Contents
lib/host/wasmtime-wasi-can WASIp2 CAN WIT interfaces and their Wasmtime host implementation
lib/host/wasmtime-wasi-gpio WASIp2 and WASIp3 GPIO WIT interfaces and their Wasmtime host implementations
lib/host/sysfs-pwm-embedded-hal embedded-hal adapter for Linux sysfs PWM
lib/guest/wasi-embedded-hal Guest adapters from the WIT APIs to embedded-hal, embedded-hal-async, and embedded-can
host WASIp2 host using the Linux GPIO character-device API, including async edge waits
host-rppal WASIp2 host using rppal for synchronous Raspberry Pi GPIO access
host-p3 Async Wasmtime host used by the WASIp3 GPIO demo
gpio-demos Native and Wasm GPIO input/output examples
can-demos Native and Wasm blocking/non-blocking CAN examples
benchmarking Native and Wasm GPIO/CAN round-trip-time benchmarks and echo programs

The entire repository is one Cargo workspace. Its Cargo.toml, Cargo.lock, Makefile, and deploy.sh are all used from the repository root.

Requirements

Hardware

The supplied programs are intended for Raspberry Pi 5 boards running 64-bit Linux. They have not been validated on other systems. The programs assume these device names:

  • GPIO output: GPIO3
  • GPIO input: GPIO4 (the async native programs specifically use /dev/gpiochip0, line offset 4)
  • CAN interface: can0

To run all examples you need:

  • One Raspberry Pi 5 for the standalone demos, or two Raspberry Pi 5 boards for the physical GPIO and CAN round-trip benchmarks.
  • A 3.3 V-safe way to drive or observe GPIO3 and GPIO4. Never connect a 5 V signal directly to a Raspberry Pi GPIO.
  • For physical CAN, a CAN controller/transceiver for each participating Pi (for example an MCP2515-based board with suitable 3.3 V/5 V level shifting) and correct bus termination.
  • For the GPIO benchmark, two boards with common ground. Connect board A GPIO3 through a current-limiting resistor (the test setup uses 523 ohms) to board B GPIO4, and board B GPIO3 through another resistor to board A GPIO4. The echo program runs on one board and the benchmark on the other.

The standalone GPIO demos can instead use an LED or logic analyzer on GPIO3 and a 3.3 V input source on GPIO4. CAN send/receive demos may run in two terminals on one configured host or on two nodes on the same bus.

Software

On the build machine:

  • A stable Rust toolchain with Rust 2024 edition support
  • The wasm32-wasip2 Rust target
  • cross and its Docker or Podman backend
  • GNU Make
  • SSH and scp if using the deployment scripts

Install the Rust-specific prerequisites with:

rustup target add wasm32-wasip2
cargo install cross

On each Raspberry Pi 5:

  • 64-bit Linux with the GPIO character-device and SocketCAN interfaces
  • A driver that creates the required /dev/gpiochip* and/or can0 device
  • ip from iproute2 (and optionally can-utils) to configure and inspect CAN
  • Permission to access the GPIO devices and CAN interface

Bring up both physical CAN nodes at the bitrate compiled into the benchmark (125 kbit/s by default):

sudo ip link set can0 down 2>/dev/null || true
sudo ip link set can0 up type can bitrate 125000
ip -details link show can0

For local CAN experiments, create a virtual interface instead:

sudo modprobe vcan
sudo ip link add vcan0 type vcan
sudo ip link set vcan0 up

The demos are hard-coded to can0; using vcan0 for them requires changing that constant and rebuilding. The benchmark suite provides a separate echo-vcan0 helper.

Build

Run make at the repository root to build every host, demo, and benchmark:

make

The root Makefile also exposes smaller build targets:

make hosts          # host, host-p3, and host-rppal
make host-rppal     # one host only; host and host-p3 work the same way
make gpio-demos
make can-demos
make benchmarking

cross produces all AArch64 native binaries under the root target/cross/aarch64-unknown-linux-gnu/release/ directory. Its separate cache prevents container-built host tools from conflicting with the local Cargo cache. Cargo produces all Wasm components under the root target/wasm32-wasip2/release/ directory.

After building a group, use the root deployment script to copy its release artifacts to ~/tests/ on one or more SSH targets. The first argument selects the artifact group; every remaining argument is an SSH target:

./deploy.sh all pi@board-a pi@board-b
./deploy.sh host pi@board-a
./deploy.sh host-rppal pi@board-a
./deploy.sh host-p3 pi@board-a
./deploy.sh gpio-demos pi@board-a pi@board-b
./deploy.sh can-demos pi@board-a pi@board-b
./deploy.sh benchmarking pi@board-a pi@board-b

The all group deploys every host, demo, and benchmark artifact. Policies are always deployed as host-policy.toml, host-p3-policy.toml, or host-rppal-policy.toml, including when deploying an individual host.

On every target, create the directory that the host preopens for benchmark output:

mkdir -p ~/tests/results
cd ~/tests

All commands below assume ~/tests is the current directory.

Policy configuration

The WASI hosts take exactly two positional arguments:

./host <component.wasm> <policy.toml>
./host-rppal <component.wasm> <policy.toml>
./host-p3 <component.wasm> <policy.toml>

Choose the host according to the component and GPIO operation:

Executable Component model GPIO backend and support
host WASIp2 Linux GPIO character-device backend; synchronous pins and async input waits
host-rppal WASIp2 Raspberry Pi rppal backend; synchronous input/output pins only
host-p3 WASIp3 Linux GPIO character-device backend; used for the WASIp3 async-input demo

All three hosts expose CAN. host-rppal interprets policy pin names such as GPIO3 as BCM GPIO numbers; the generic hosts look up those names through the Linux GPIO character-device API. PWM continues to use the Linux sysfs PWM backend in every host.

The policy maps guest-visible capability names to host devices. A guest that calls open("can") or open-input("pin4") receives only the resource with that name; devices omitted from the policy cannot be opened.

The checked-in host/policy.toml and host-p3/policy.toml enable blocking can0/vcan0, stateful output GPIO3, and async input GPIO4. host-rppal/policy.toml instead enables synchronous input GPIO4, because the RPPAL host does not implement input-wait-pin. Deploy the policy belonging to the selected host and enable only the resource types needed by the component:

[can]
debug = false

[[can.blocking]]
ifname = "can0"       # Linux SocketCAN interface
name = "can"          # name visible to the component

# Use this entry instead for the non-blocking CAN demos.
# [[can.nonblocking]]
# ifname = "can0"
# name = "can"

[gpio]
debug = false

# Do not expose the same GPIO line through multiple resource entries
# simultaneously.

# Required by wasm-toggle.
# [[gpio.output-pin]]
# pin-name = "GPIO3"  # Linux GPIO line name
# name = "pin3"       # name visible to the component
# initial-state = "low"

# Required by wasm-toggle-stateful and the GPIO Wasm benchmark.
[[gpio.stateful-output-pin]]
pin-name = "GPIO3"
name = "pin3"
initial-state = "low"

# Required by wasm-read. This is enabled in host-rppal/policy.toml.
# [[gpio.input-pin]]
# pin-name = "GPIO4"
# name = "pin4"

# Required by the async input demos and GPIO Wasm benchmark. Not supported by
# host-rppal.
[[gpio.input-wait-pin]]
pin-name = "GPIO4"
name = "pin4"

# [[gpio.pwm-pin]]
# chip = 0
# channel = 0
# period-ns = 20_000_000
# name = "pwm"

initial-state accepts low or high. Setting debug = true preserves more detailed host-side error text in the WIT other error value; leave it disabled when details about the underlying device should not be revealed to a guest.

A GPIO line cannot normally be claimed by two resource entries simultaneously. For example, comment out stateful-output-pin before enabling output-pin for the same physical line.

Run the GPIO demos

The native programs access Linux devices directly. The Wasm programs must run through the matching host and policy. The table uses host-rppal for synchronous WASIp2 GPIO operations; host can run those same components when its corresponding synchronous policy resource is enabled.

Demo Policy resource Command
Poll GPIO4 once per second (native) None ./native-read
Report GPIO4 edges (native async) None ./native-async-read
Poll GPIO4 once per second (WASIp2) gpio.input-pin named pin4 ./host-rppal ./wasm-read.wasm ./host-rppal-policy.toml
Report GPIO4 edges (WASIp2) gpio.input-wait-pin named pin4 ./host ./wasm-async-read.wasm ./host-policy.toml
Report GPIO4 edges (WASIp3) gpio.input-wait-pin named pin4 ./host-p3 ./wasm-async-read-p3.wasm ./host-p3-policy.toml
Toggle GPIO3 every two seconds (native) None ./native-toggle
Toggle GPIO3 after each Enter press (native) None ./native-toggle-stateful
Toggle GPIO3 every two seconds (WASIp2) gpio.output-pin named pin3 ./host-rppal ./wasm-toggle.wasm ./host-rppal-policy.toml
Toggle GPIO3 after each Enter press (WASIp2) gpio.stateful-output-pin named pin3 ./host-rppal ./wasm-toggle-stateful.wasm ./host-rppal-policy.toml

Stop a continuous demo with Ctrl-C.

Run the CAN demos

Start a receiver first, then run the matching sender in another terminal or on another node. Each sender transmits one standard-ID frame with an eight-byte payload; receivers continue until interrupted. The table uses host-rppal; the generic host may be used instead with its matching deployed policy.

API Receiver Sender Policy for Wasm
Blocking, native ./blocking-native-recv ./blocking-native-send None
Blocking, WASIp2 ./host-rppal ./blocking-wasm-recv.wasm ./host-rppal-policy.toml ./host-rppal ./blocking-wasm-send.wasm ./host-rppal-policy.toml can.blocking named can
Non-blocking, native ./nb-native-recv ./nb-native-send None
Non-blocking, WASIp2 ./host-rppal ./nb-wasm-recv.wasm ./host-rppal-policy.toml ./host-rppal ./nb-wasm-send.wasm ./host-rppal-policy.toml can.nonblocking named can

The two Wasm processes may share the same policy file. For the non-blocking pair, replace the [[can.blocking]] entry with [[can.nonblocking]] before starting the host processes.

Run the benchmarks

Both benchmark clients perform 50,000 round trips per case, wait 100 microseconds between samples, and write one rtt_ns value per CSV row in results/. These values are compile-time constants in the benchmark sources.

CAN round-trip time

The CAN benchmark tests payload lengths 1 through 8 at the bitrate recorded by the BITRATE constant (125 kbit/s by default). It requires an echo process that receives each frame and sends it back.

On the echo node:

./echo-can0

On the benchmark node, run the native and Wasm variants separately:

./can-native-benchmarking
./host-rppal ./can-wasm-benchmarking.wasm ./host-rppal-policy.toml

The Wasm run needs can.blocking named can; the generic host may be used in place of host-rppal. Output files are named like results/native-can0-bitrate125-len8.csv and results/wasm-can0-bitrate125-len8.csv.

For a virtual CAN echo loop, run ./echo-vcan0. To benchmark vcan0, also change the native benchmark's INTERFACE and the Wasm benchmark's CAN_NAME, INTERFACE, and matching policy entry, then rebuild.

GPIO round-trip time

Wire the two boards as described in Hardware. On the echo board, run:

./gpio-echo

On the benchmark board, run the native and Wasm variants separately:

./gpio-native-benchmarking
./host ./gpio-wasm-benchmarking.wasm ./host-policy.toml

The Wasm run needs gpio.stateful-output-pin named pin3 and gpio.input-wait-pin named pin4, so it requires the generic host rather than host-rppal. Results are written to results/native-gpio-rtt.csv and results/wasm-gpio-rtt.csv.

Do not run the native and Wasm benchmark clients concurrently: they claim the same devices and use distinct sequential runs for a meaningful comparison.

License

See LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages