Native RISC-V NOMMU Linux 6.18 on the M5Stack Tab5 (ESP32-P4 + ESP32-C6): a console on the 5" display, typed on the Tab5 Keyboard, with Wi-Fi, SSH, BusyBox and MicroPython. No emulator: the kernel runs on the P4's RISC-V core.
This is a fork of
tsuyoshi-otake/m5stamp-p4-c6-linux,
which runs the same Linux on the M5Stamp-P4. The Tab5 support is selected
with EASYSTICK_BOARD=tab5; the Stamp-P4 build is unchanged and its
documentation follows below. Details of the port are in
docs/tab5.md.
| Display | 720x1280 ST7123 panel over MIPI-DSI, Linux console (fbcon) in landscape, 80x22 with Terminus 16x32 |
| Keyboard | Tab5 Keyboard (A164) over I2C, as a Linux input device; Sym/Aa layers, Ctrl, Alt |
| Wi-Fi | ESP32-C6 over SDIO (ESP-Hosted NG): SoftAP by default, or station mode from /boot/config.txt |
| Shell | root shell on the display (tty1) and on the USB-C serial console; Dropbear SSH as user m5 |
| Userland | BusyBox (vi, awk, wget, ...), MicroPython (python3), NTP clock |
Not supported: the ILI9881C and ST7121 panel revisions (only ST7123), touch, audio, camera, the USB-A host port, battery and power button handling, and the second CPU core.
This replaces the Tab5's firmware. Read docs/tab5.md before flashing; it has the commands.
- Back up the factory flash (16 MB,
esptool.py read_flash), so the original firmware can be restored. - Update the C6 once. The Linux driver needs ESP-Hosted NG on the C6, and the Tab5 ships with ESP-Hosted-MCU. The C6 has no USB port; a temporary P4 program sends the new firmware over SDIO. After this the factory firmware has no Wi-Fi until the C6 gets ESP-Hosted-MCU again.
- Flash the Linux image (16 MB, at
0x0) over the USB-C port.
Images come from the tab5-build GitHub Actions workflow
(tab5-assets: the Linux image, the C6 firmware and the C6 OTA host).
There is no release yet.
- The display shows a root shell; press Enter to start it.
=and:are on the Sym layer of the keyboard. - Wi-Fi and SSH credentials are generated on the first boot:
cat /boot/credentials.txt. The board starts as a SoftAP (m5-XXXX, 192.168.4.1);ssh m5@192.168.4.1. - To join your network, edit
/boot/config.txtwithviand setwifi_ssid=,wifi_psk=andcountry=(remove the leading#). The change is saved to flash and applied about 3 s after you save; the C6 is 2.4 GHz only.
The tab5-build workflow builds everything on a GitHub runner (about an
hour). Locally, with the ESP-IDF and Buildroot prerequisites of the
upstream build below:
EASYSTICK_BOARD=tab5 bash linux/build-c68.sh --vendor <out> --profile m3-labFor kernel driver work, tools/tab5-kernel-build.sh rebuilds only the
kernel from the workflow's tab5-kernel-kit artifact in a few minutes, and
a DTS change needs only the DTB partition (0xf10000).
- tsuyoshi-otake/m5stamp-p4-c6-linux: the Linux distribution this fork builds on.
- mrbreaker/why2025-linux: the ESP32-P4 NOMMU kernel port and the MIPI-DSI DRM driver the display uses.
- Apache NuttX
esp32p4-tab5and M5Stack's ESP-BSP: the ST7123 init table and timings. - M5Stack: the Tab5 schematic and the Tab5 Keyboard I2C protocol.
A reproducible RISC-V NOMMU Linux 6.18 distribution (SMP kernel; the shipped image runs on one core, dual-core is experimental, see below) for the M5Stack Stamp-P4 carrier board paired with Stamp-AddOn C6 (via 4-bit SDIO interface).
Boots out-of-the-box into Wi-Fi SoftAP mode with automatic DHCP, Dropbear SSH server, BusyBox vi, MicroPython (python3), and a writable OverlayFS on /home, /root, and /var/lib.
Live SSH session on M5Stamp ESP32-P4 + C6 from an experimental dual-core run (CPU0 and CPU1) via SDIO Wi-Fi; the shipped image boots with maxcpus=1.
- SoC Target: ESP32-P4 (Dual-Core RV32IMAC / ilp32, CPU clock 360 MHz as reported by the boot-shim, 32 MiB PSRAM, 16 MiB SPI Flash).
- SMP (experimental): The kernel is built with SMP (
Linux 6.18.35 #1 SMP riscv32 GNU/Linux), but the shipped DTB boots withmaxcpus=1, so only CPU0 is online (/sys/devices/system/cpu/online=0). Withmaxcpus=2, about one boot in five stalls during userland start (issue #5): CPU1 stops accepting any interrupt (timer and IPI stay pending and enabled) and the rest of the system waits on it. Addingisolcpus=1to the kernel command line avoided the stall in 17 of 17 boots and a 568 s soak, but leaves CPU1 almost unused; seelinux/tests/m3-lab-hardware-validation-2026-10-14.md. A stalled board may need a re-flash over USB.maxcpusin/boot/config.txtis informational and is not read by anything. - Wi-Fi Companion: ESP32-C6 (M5Stack Stamp-AddOn C6) connected via 4-bit SDIO (5 MHz by default, up to 40 MHz with
sdio_mhz=, see SDIO clock). - Out-of-the-Box SoftAP:
- Broadcasts a per-device SSID
m5-XXXXwith a WPA2-PSK passphrase generated on first boot (see Per-device credentials). - Integrated
udhcpdDHCP server assigning client IPs in192.168.4.2β192.168.4.20(Gateway:192.168.4.1).
- Broadcasts a per-device SSID
- Pre-installed Developer Tools:
- SSH Server: Password-enabled Dropbear on TCP port 22 (user
m5, per-device password fromcredentials.txt). inetd accepts up to 20 new connections a minute, then stops listening for 60 s. viEditor: Full-featured BusyBoxviwith search, undo, visual buffers, and syntax assistance.- Shell commands: the main BusyBox carries the shell and the commands scripts use (
grep,sed,find,ps,df,dmesg,ping,nc, ...). The m3-lab image addsbusybox-tools, a third BusyBox with about 65 everyday commands the main one leaves out to stay under the 512 KB NOMMU allocation:awk,less,top,free,uptime,pgrep/pkill,watch,timeout,stat,diff,patch,hexdump,xxd,tar,gzip,unzip,wget(HTTP only),nslookup,traceroute,ipand more. It is loaded only while one of them runs (about 430 KB of the user pool).sudo,nano,curl,file,digandlsblkare not available: they need an MMU, a wide-character toolchain or more RAM than the board has. The SSH login message (/etc/motd) lists the common commands by group;busybox --listandbusybox-tools --listprint everything. - MicroPython (
python3): MicroPython v1.22.2 compiled with bFLT Load-to-Ram (-r) and position-independent code (-fPIC) for stable NOMMU execution. It has the unix port's standard modules (time,socket,select,json,re,struct,random,hashlib,binascii,collections,asyncio, ...) without threads, TLS,ffi,framebufor complex numbers, to stay under the 512 KB NOMMU allocation. - Pico-style autorun:
main.py,main.shor a bFLTmainsaved on the USB drive runs asm5at boot and restarts when a new version is saved (details). - Writable Filesystem (OverlayFS): While the root filesystem is SquashFS (read-only for durability), RAM-backed OverlayFS is mounted on
/home,/root, and/var/libso users can create and edit scripts during the current boot. Its contents are lost when power is removed. - USB Mass Storage (
/bootdrive, optional hardware): With the optional EasyStick carrier, its Type-A USB connection (GPIO26/27) exposes a FAT-formatted 256KB/bootconfiguration drive to Windows/Mac/Linux hosts. Save a file and, a few seconds later, the board validates the drive, applies the settings and writes it to redundant A/B flash slots. No eject or reboot is needed.
- SSH Server: Password-enabled Dropbear on TCP port 22 (user
- Kernel & SoC Stabilization:
- Full-Speed DWC2 USB OTG driver hardened with PIO mode and dedicated TX FIFO empty interrupt management, eliminating descriptor DMA coherency panics and enabling stable multi-block transfers.
- Signal delivery
mcausehardening (0014). - L2 unified cache & DMA reentrancy hardening (
0015). - Systimer 52-bit atomic multi-word read and missed-alarm mitigation (
0016). - Dual-core systimer lock and CPU1 context switch tracking (
0042,0044,0045,0046). - USB CDC-ACM 64-byte TX FIFO bounded polling (
0062), eliminating full-screen editor (vi) and TUI terminal lockups. - Early CRNG entropy initialization via
seedrngon OverlayFS, preventinggetrandom()system call stalls. - SDIO DW-MMC host controller driver enhancements with software DTO and buffer pacing (
0032,0033,0060,0061). - NOMMU system-call pointers (
0071): a pointer that is not in PSRAM or the memory-mapped root filesystem now fails withEFAULT. Before, the kernel read it itself;viwith no file name passes a NULL path and froze the board until the watchdog reset it. - NOMMU user memory: programs load into a 10 MB reserved pool in two regions (6 MB + 4 MB,
0070) instead of the fragmented page allocator, so SSH logins,S40network restartand console commands no longer fail withnommu: Allocation of length β¦ failed(#23). Each BusyBox process still takes about 500 KB, because bFLT programs do not share their text. The m3-lab image therefore runs init, inetd, syslogd and udhcpd frombusybox-daemons, a second BusyBox built with only those four applets (192 KB each). At idle that leaves about 1 MB more of the pool free, which is one more concurrent SSH session (5β6 instead of 4β5;linux/tests/busybox-daemons-2026-10-16.md).MemAvailabledoes not change, because it does not count the pool.
Linux / macOS serial examples, how to open the console without resetting the board, troubleshooting and flash backup / recovery are in docs/troubleshooting.md.
- C6 firmware: Linux expects the ESP32-C6 to run ESP-Hosted NG 1.0.6 (the driver logs
ESP-Hosted Version: NG-1.0.6.0.1). The release images flash the P4 only; a C6 running another version may fail to attach over SDIO. Check the version in the boot log: a mismatch is logged asIncompatible ESP Host-firmware release detectedwith both versions, and no log line at all means the C6 is not running ESP-Hosted NG. See Updating the C6. - SDIO clock: the driver requests 5 MHz unless
sdio_mhz=inconfig.txtpicks 1, 2, 10, 20 or 40 (applied at the next boot, logged asES_SDIO_CLOCK_APPLY requested_mhz=...in dmesg). The DTS caps the bus at 40 MHz, and the MMC core enumerates the C6 at 20 MHz before the driver sets its rate. Measured in station mode over the home Wi-Fi (linux/tests/sdio-clock-2026-10-16.md): PC to board over raw TCP 300β330 KB/s at 5 MHz, 410β440 KB/s at 40 MHz; SSH 210β220 KB/s at 5 MHz, about 250 KB/s at 40 MHz, where Dropbear's crypto on the single 360 MHz core is the limit. 40 MHz passed the boot checks on 10 resets; it has not been soaked for hours, so 5 MHz stays the default (#21). - Secure Boot and flash encryption are disabled. Physical access allows reading the flash (including Wi-Fi credentials in the boot FAT) and writing arbitrary firmware. See SECURITY.md.
- Wi-Fi regulatory domain: no
country=is set by default, so the world regulatory domain applies. Setcountry=inconfig.txtfor your region. - Wi-Fi security: WPA2-PSK only. Protected management frames are opt-in (
wifi_pmf=1); in station mode the v0.2.2 image joined an access point with it, while a SoftAP client negotiating PMF has not been tested. WPA3-SAE is not built.
The C6 on the Stamp AddOn has no USB port of its own; it is updated over the SDIO link from the P4 with a temporary P4 updater image, after which the Linux image is written back.
- Build the C6 image:
bash linux/m2/build-c6.sh --vendor <out>(needs the locked ESP-IDF;EASYSTICK_C6_BT=0leaves out BLE, which Linux does not use).<out>/C6_PATCH_MANIFEST.txtrecords thenetwork_adapter.binSHA-256 and the applied patches. - Build the updater:
bash linux/m2/build-ota-host.sh <external-source-root> <out>/build/network_adapter.bin <ota-out>(the source root holdsesp_idfand anesp_hosted_mcucheckout). It uses ESP-Hosted-MCU'shost_performs_slave_otaexample. - Save a full 16 MB readback of the P4 first, then write the updater with
linux/m2/ota-host/flash-ota-host.ps1(it refuses to run without-AllowCandidateWriteand a matching stock readback). The updater sends the C6 image over SDIO on its next boot. - Write the Linux release image again and check the boot log for
ESP-Hosted Version: NG-1.0.6.0.1.
tools/readback-c6-ng.ps1 reads the C6 flash through a C6 programmer port, if you have one wired, so the original C6 firmware can be kept. The SDIO update path was used during bring-up (linux/tests/wlan0-regression-bisect-2026-08-12.md); it is not part of the release workflow.
| Service | SSID / Host | Username | Password | Notes |
|---|---|---|---|---|
| Wi-Fi SoftAP | m5-XXXX |
β | psk= in credentials.txt |
WPA2-PSK, 2.4 GHz 802.11b/g/n, channel 1/6/11 |
SSH (dropbear) |
192.168.4.1:22 |
m5 |
password= in credentials.txt |
Default user with home directory /home/m5 |
| Runtime status | SSH / serial | β | β | Read /run/easystick-status.txt; the USB FAT image is not modified while exported |
| Serial Console | COMx (115200 8N1) |
(root shell) | (none) | ttyGS1 over USB-Serial/JTAG. Press Enter to activate |
Every board gets its own credentials on first boot; no SSID or password is shared between boards.
- On first boot
S05easystick-tmpfswritescredentials.txtto the USB drive:device_id=,password=(SSH/console password of userm5), and, whilewpa_supplicant.confis still the shipped default,ssid=m5-<device_id>andpsk=. Thatwpa_supplicant.confis rewritten with the new SSID and passphrase, andchannel=(1, 6 or 11, chosen from the device id) is added toconfig.txt. - The same values are printed once on the serial console as
ES_CREDENTIALS NEW: ...and are readable as root in/tmp/credentials.txt. The serial log then contains the password; redact that line before sharing logs. - The generated files are written to flash during that first boot (
ES_CREDENTIALS: saved to flashin the serial log). password=is applied to userm5at every boot. To change it, editpassword=(8-64 characters, no:) incredentials.txtand save it; the board applies it a few seconds later.passwdover SSH only lasts until the next boot.- There is no forced password change at first login: the password is already random per device, and the image has no built-in fallback password. If
password=cannot be applied (ES_CREDENTIALSon the console), password login form5stays disabled; SSH keys still work. channel=(1-13) inconfig.txtsets the SoftAP channel without editingwpa_supplicant.conf.- A custom
wpa_supplicant.conf(one that is not the shippedm5/m5stamp-p4-c6default) is never overwritten; onlypassword=is generated then. - Factory reset: put an empty file named
factory-reset(orfactory-reset.txt) on the USB drive. A few seconds later the board reboots, the drive is restored to the shipped defaults (ES_FACTORY_RESETin the serial log), and new credentials are generated and saved like on any first boot. This is the only drive change that reboots the board. - SSH keys: put an OpenSSH
authorized_keysfile on the USB drive; a few seconds later it is installed as/home/m5/.ssh/authorized_keys, and again at every boot (ES_SSH_KEYSin the serial log). With keys installed,ssh_password_auth=noinconfig.txtmakes Dropbear accept keys only. Without any installed key that setting is ignored so a mistake cannot lock you out.
v0.2.5 is built and published the same way as v0.2.2 to v0.2.4: the assets are the CI build that was flashed and checked on hardware, and the tag run refuses any asset whose hash is not in
linux/tests/v0.2.5-hardware-validation-2026-10-17.md. Changes since v0.2.4 (#34):
- Pico-style autorun:
main.py,main.shor a bFLTmainsaved on the USB drive runs asm5at boot and restarts when a new version is saved (details). Changing only theautorun=lines inconfig.txtdoes not reload Wi-Fi.- MicroPython now has the unix port's standard modules (
time,socket,json,asyncio, ...); the previous build had onlyosandsys.- About 65 more commands after login (
awk,less,top,free,tar,wget,timeout, ...) frombusybox-tools, and an SSH login message that lists them.timeoutis patched to work on NOMMU.- Fixed: after Wi-Fi settings were changed from the drive,
/etc/resolv.confwas readable only by root until the next boot, som5could not resolve host names.Known issue in v0.2.3 and earlier:
vistarted without a file name freezes the board until the watchdog resets it; startviwith a file name (vi notes.txt) on those releases.v0.2.2 added the A/B
/bootslots and metadata (boot_alt,bootmeta*) that thev0.2.1-smpbinaries lacked (#8), a debug bundle (System.map,vmlinux) and the GPL corresponding source (*-legal-info.tar.gz, #31).
Download either the monolithic 16MB raw flash image or the partition zip package from the Releases page:
m5stamp-p4-c6-linux-smp-v0.2.5-16mb.bin.gz: Single 16MB raw flash image (simplest, flash to0x0).m5stamp-p4-c6-linux-smp-v0.2.5.zip: Individual partition binaries with automated PowerShell flash script.
Connect the M5Stamp-P4 via its USB-C port to your computer. Identify your serial port (e.g., COM10 on Windows or /dev/ttyACM0 on Linux/macOS).
gzip -d m5stamp-p4-c6-linux-smp-v0.2.5-16mb.bin.gz
esptool.py --chip esp32p4 --port COM10 --baud 460800 write_flash 0x0 m5stamp-p4-c6-linux-smp-v0.2.5-16mb.bin.\flash-candidate.ps1 -Port COM10 -AllowCandidateWriteDo not use -PreserveBoot for the first upgrade from v0.2.1-smp or older, which have
no A/B slots. It is only for reflashing a v0.2.2 or later installation whose A/B slots and
metadata are already initialized.
esptool.py --chip esp32p4 --port COM10 --baud 460800 \
--before default_reset --after no_reset write_flash -z \
0x002000 bootloader.bin \
0x008000 partition-table.bin \
0x010000 boot-shim.bin \
0x090000 Image \
0x810000 rootfs.squashfs \
0x0f10000 easystick-stamp-p4.dtb \
0x0f40000 boot.img
esptool.py --chip esp32p4 --port COM10 --baud 460800 \
--before default_reset --after hard_reset erase_region 0xfc0000 0x2000| Offset | File | Description |
|---|---|---|
0x002000 |
bootloader.bin |
ESP-IDF 1st stage bootloader |
0x008000 |
partition-table.bin |
ESP32-P4 Flash partition table |
0x010000 |
boot-shim.bin |
Secondary boot-shim (PSRAM, SDIO matrix, CPU1 release, jump to Linux) |
0x090000 |
Image |
Linux 6.18 SMP RISC-V kernel binary |
0x810000 |
rootfs.squashfs |
Read-only SquashFS root filesystem with Buildroot userland |
0x0f10000 |
easystick-stamp-p4.dtb |
Compiled Flattened Device Tree for Stamp-P4 |
0x0f40000 |
boot.img |
256KB FAT12 /boot slot 0 exposed via USB Mass Storage (Type-A) |
0x0f80000 |
β | Inactive 256KB /boot slot used for power-loss-safe updates |
0x0fc0000 |
β | Two redundant 4KB A/B metadata sectors at 0xfc0000 and 0xfc1000 |
- After flashing, the Stamp-P4 automatically reboots and starts SoftAP within ~10 seconds.
- Optional USB Mass Storage Configuration: If using the EasyStick carrier, disconnect the Stamp-P4 USB-C cable after flashing, then plug the carrier's USB Type-A connection into your PC. Do not power USB-A and USB-C simultaneously. A 256KB FAT drive (volume
EASYSTICK) will appear withcmdline.txt,config.txt, andwpa_supplicant.conf. Without this carrier or equivalent USB wiring, skip this step and use Wi-Fi/SSH or the USB-C serial console.- Edit and save the configuration. Once the host has not written to the drive for 3 seconds,
easystick-bootsynccopies the image, validates the FAT snapshot, applies it and writes it to flash (Saved to flashin the serial log, about 2 seconds). No eject or reboot is needed; wait about 5 seconds after saving before unplugging. The host's Eject / Safely Remove action still works and imports at once, which is the safe choice for hosts that cache writes (macOS and Linux may hold them until the eject). - Invalid or incomplete FAT images are rejected and the previous runtime configuration remains active; the next save is checked again.
- Live network status is available as
/run/easystick-status.txtover SSH or serial. The exported USB drive is strictly dedicated to boot configuration (cmdline.txt,config.txt,wpa_supplicant.conf). - Network settings (
wpa_supplicant.conf,config.txt),password=andauthorized_keystake effect at once.sdio_mhz=,ssh_password_auth=andcmdline.txtare read at boot, so they apply from the next boot. - The kernel (
/sys/kernel/easystick/boot_commit, patch0072) writes the image to the inactive flash slot, verifies its CRC, and only then writes the metadata that switches slots, in the format the boot shim checks at every boot. Power loss during the write leaves the previous slot active. If the kernel cannot write flash, the image is staged in PSRAM and the board reboots so the boot shim writes it (Rebooting to commit the image to flash);auto_reboot=noinconfig.txtturns that fallback reboot off.
- Edit and save the configuration. Once the host has not written to the drive for 3 seconds,
- Open
credentials.txton the USB drive (or read theES_CREDENTIALS NEWline in the serial log) and connect to the Wi-Fi SSIDssid=with the passphrasepsk=. - Your device will receive an IP address such as
192.168.4.2. - Open a terminal and SSH into the board:
ssh m5@192.168.4.1 # Password: password= from credentials.txt - You can now edit files and run Python programs immediately:
vi hello.py python3 hello.py
The EasyStick Stamp-P4 Rev0.15 carrier is an optional convenience board for using the USB mass-storage configuration drive without separately wiring the Stamp-P4's Full-Speed USB signals. It is not required for Linux boot, Wi-Fi, SSH, or the Stamp-P4 USB-C serial console; the Stamp-AddOn C6 and its SDIO connection are still required for Wi-Fi.
- Easy configuration from a PC: plug the carrier's USB-A connection into
your computer to access the
EASYSTICKdrive with the release firmware. - Separate USB functions: the carrier routes GPIO26 (Dβ) / GPIO27 (D+) for mass storage. The Stamp-P4's USB-C flashing/serial connection is not this mass-storage port.
- Configuration storage, not a general-purpose USB disk: the drive is 256KB; a saved change is written to flash a few seconds later (procedure above). It does not expose the root filesystem or RAM-backed home directory.
- Open hardware files: schematic PDF, Gerber ZIP, checksums, and assembly notes, licensed under MIT. Rev0.15 has been manufactured and used successfully by the project owner.
Use only one USB power input at a time. The carrier is a separate hardware option, not a required purchase or a component included in the firmware download. Equivalent hardware must provide the appropriate USB data, ground, power, and protection connections; GPIO wiring alone is not sufficient.
The system boots by default into SoftAP mode (per-device SSID m5-XXXX and passphrase, see credentials.txt), allowing immediate configuration from any Wi-Fi-enabled phone or computer without needing a local Wi-Fi router.
However, you can dynamically connect the Stamp-P4 to your existing Wi-Fi network (Station mode), or customize the SoftAP parameters, directly from the Linux terminal without re-flashing or re-building!
Add these lines to config.txt on the USB drive and save it:
wifi_ssid=YOUR_WIFI_SSID
wifi_psk=YOUR_WIFI_PASSWORDThe board then joins that network as a station (leave wifi_psk out for an open network). With wifi_mode=ap the same two lines define the SoftAP instead (channel= picks the channel). When set, these lines take precedence over wpa_supplicant.conf. Invalid values (PSK not 8-63 characters, SSID over 32 bytes, " or \ in either) are reported on the serial console as M3-lab: CONFIG ERROR: ... and the board keeps using wpa_supplicant.conf.
wifi_pmf=1 adds optional protected management frames (802.11w, ieee80211w=1 with WPA-PSK WPA-PSK-SHA256) to the generated config, for both station and SoftAP. Peers without PMF still connect. In station mode it was tested with the v0.2.2 image (the board joined, got an address and set the clock from NTP); a client negotiating PMF with the SoftAP has not been tested. If the board stops joining, remove the line.
sdio_mhz=40 (or 1, 2, 5, 10, 20) sets the SDIO clock to the C6 from the next boot; see SDIO clock. Other values are ignored with M3-lab: ignoring sdio_mhz=... on the console.
auto_reboot=no matters only when the kernel cannot write flash: it stops the fallback reboot that saves the drive. The change is still applied at once but lives only in PSRAM until a manual reboot; unplugging the board discards it.
country=JP (any two-letter code) in config.txt sets the Wi-Fi regulatory domain (M3-lab: regulatory domain JP on the console); country= inside wpa_supplicant.conf works too. Without it the board uses the restrictive world domain. Set it to the country where the board is used.
The board has no RTC. The clock starts at the image build time; after a station-mode DHCP lease it is set once from pool.ntp.org (M3-lab: clock set from NTP on the console). ntp_server= in config.txt picks another server and ntp_server=none turns this off.
By default the kernel feeds the hardware watchdog, so the board resets only when the kernel itself hangs. watchdog=1 in config.txt hands the watchdog to a userland feeder: the board then also resets about 120 s (the DTS timeout-sec) after userland stops being scheduled, or about 3 minutes after the SSH server (inetd) disappears and does not come back (60 s of checks, then the 120 s timeout). If the feeder itself is killed, the kernel resumes feeding and the board does not reset. wpa_supplicant and udhcpd are restarted automatically if they exit.
From the terminal (either over serial console or via SSH over default SoftAP 192.168.4.1), run the following commands:
# 1. Stop active SoftAP and DHCP server
killall udhcpd wpa_supplicant 2>/dev/null || true
ifconfig wlan0 down
# 2. Write your Wi-Fi credentials to a config file (/tmp is writable)
cat << 'EOF' > /tmp/wpa_supplicant.conf
network={
ssid="YOUR_WIFI_SSID"
psk="YOUR_WIFI_PASSWORD"
}
EOF
# 3. Switch interface to managed (station) mode and connect
iw dev wlan0 set type managed
ifconfig wlan0 up
wpa_supplicant -B -i wlan0 -c /tmp/wpa_supplicant.conf
# 4. Wait a few seconds for link association and request an IP via DHCP
sleep 4
udhcpc -i wlan0 -n -t 5 -q
ifconfig wlan0Once connected, ifconfig wlan0 will show your new local IP address (e.g. 192.168.1.150 or 10.255.10.x). You can now SSH directly from any PC on your local LAN:
ssh m5@<YOUR_NEW_IP>
# Password: password= from credentials.txtThe board also answers mDNS for <hostname>.local (easystick.local unless hostname= in config.txt says otherwise), so ssh m5@easystick.local works from hosts with mDNS (macOS, most Linux desktops, Windows 10+). Only the name is published; there is no service discovery.
(Note: To test link state manually, run iw dev wlan0 link to view connection status, SSID, signal level, and BSSID).
If you wish to configure a custom SoftAP SSID or passphrase:
# 1. Stop current Wi-Fi services
killall udhcpd wpa_supplicant 2>/dev/null || true
ifconfig wlan0 down
# 2. Write custom SoftAP configuration (mode=2 enables AP)
cat << 'EOF' > /tmp/wpa_custom_ap.conf
update_config=0
ap_scan=1
network={
ssid="MyCustomAP"
mode=2
frequency=2437
key_mgmt=WPA-PSK
proto=RSN
pairwise=CCMP
group=CCMP
psk="MySecurePassword123"
}
EOF
# 3. Bring up interface in AP mode & restart DHCP server
iw dev wlan0 set type __ap
ifconfig wlan0 192.168.4.1 netmask 255.255.255.0 up
wpa_supplicant -B -i wlan0 -c /tmp/wpa_custom_ap.conf
udhcpd -S /etc/udhcpd.conf &The distribution includes a full-featured BusyBox vi text editor compiled with syntax highlighting helpers, visual blocks, search, and multi-level undo.
Combined with the writable RAM-backed OverlayFS on /home/m5 and /root, you can author, edit, and modify scripts just like on a standard desktop Linux system.
cd /home/m5
vi script.py- Command Mode (default): Keypresses are interpreted as navigation or action commands (e.g., delete line, search, save).
- Insert Mode: Keypresses insert actual text into the document.
| Key | Mode | Description |
|---|---|---|
i |
Command β Insert | Start inserting text before the cursor |
a |
Command β Insert | Start inserting text after the cursor |
o |
Command β Insert | Open a new line below and start inserting |
Esc |
Insert β Command | Exit insert mode back to command mode |
:w |
Command | Save (write) current file to disk |
:wq or ZZ |
Command | Save file and quit vi |
:q! |
Command | Force quit without saving changes |
dd |
Command | Delete (cut) current line |
yy |
Command | Copy (yank) current line |
p |
Command | Paste copied/cut line below cursor |
u |
Command | Undo last action |
/pattern |
Command | Search forward for pattern (press n for next match) |
:set number |
Command | Show line numbers (:set nonumber to turn off) |
- Open a new shell script in your home directory:
vi /home/m5/sysinfo.sh
- Press
ito enter Insert mode. - Type the following text:
#!/bin/sh echo "=== System Information ===" uname -a echo "=== Memory Usage ===" free -m echo "=== Disk & OverlayFS Status ===" df -h
- Press
Escto return to Command mode. - Type
:wqand press Enter to save and exit. - Make it executable and run it:
chmod +x /home/m5/sysinfo.sh /home/m5/sysinfo.sh
The image comes pre-installed with MicroPython v1.22.2 compiled specifically for RISC-V RV32 NOMMU architectures using bFLT Load-to-Ram (-r), position-independent code (-fPIC), and an expanded 32 KB execution stack.
Available modules: os, sys, time (sleep, sleep_ms, ticks_ms, localtime), socket, select, json, re, struct, random, math, hashlib, binascii, collections, array, io, errno, heapq, gc, platform, termios and asyncio. Threads (_thread), TLS (ssl), ffi, framebuf, cmath and deflate are not built in. The kernel has no IPv6, so pick the IPv4 entry from socket.getaddrinfo():
import socket
ai = [a for a in socket.getaddrinfo("example.com", 80, 0, socket.SOCK_STREAM)
if a[0] == socket.AF_INET][0]
s = socket.socket(ai[0], ai[1], ai[2])
s.connect(ai[-1])It is accessible under both micropython and the standard python3 command.
Launch the interactive REPL anytime by running python3:
$ python3
MicroPython v1.22.2 on 2026-10-17; linux [GCC 13.4.0] version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> 2 ** 16
65536
>>> import sys
>>> print("Platform:", sys.platform)
Platform: linux
>>> print("Version :", sys.version)
Version : 3.4.0; MicroPython v1.22.2 on 2026-10-17
>>> [x * x for x in range(8)]
[0, 1, 4, 9, 16, 25, 36, 49](Press Ctrl-D to exit the REPL and return to the shell).
Execute quick commands or inspect the system with the -c flag:
# Print system information
python3 -c "import os; print('Uname:', os.uname())"
# Perform a quick math calculation
python3 -c "print('Sum 1..10000:', sum(range(10001)))"Create a Python script using vi:
vi /home/m5/prime_bench.pyInsert the following code:
import time
import sys
print("=============================================")
print(" M5Stamp ESP32-P4 + C6 MicroPython Benchmark ")
print("=============================================")
print("System:", sys.platform)
print("Python:", sys.version)
start = time.time()
primes = []
limit = 3000
for num in range(2, limit):
is_prime = True
for p in primes:
if p * p > num:
break
if num % p == 0:
is_prime = False
break
if is_prime:
primes.append(num)
elapsed = time.time() - start
print("\nResults:")
print(f" Calculated {len(primes)} primes up to {limit}")
print(f" Largest prime found: {primes[-1]}")
print(f" Elapsed time : {elapsed:.3f} seconds")
print("=============================================")Save and exit (:wq), then execute it with:
python3 /home/m5/prime_bench.pyBecause /home/m5 is backed by OverlayFS in RAM, file I/O operations work naturally:
# Write a file
with open("/home/m5/log.txt", "w") as f:
f.write("Stamp-P4 Linux record: System operational\n")
# Read it back
with open("/home/m5/log.txt", "r") as f:
print("Logged message:", f.read().strip())Put a program on the EASYSTICK drive (m3-lab image) and save: it runs as
user m5 at every boot, and a saved new version replaces the running one a
few seconds later, with no eject or reboot. The first of these runs:
| File on the drive | Runs as |
|---|---|
main.py |
python3 main.py (MicroPython) |
main.sh |
sh main.sh (BusyBox shell; CRLF line ends are fine) |
main |
a RISC-V NOMMU flat binary (bFLT, see below) |
- The other
*.pyfiles at the top of the drive and alib/folder are copied along, soimport mymoduleworks. The copy,/run/autorun, is the working directory and belongs tom5; nothing is written back to the drive. - Output goes to
/run/autorun.log(64 KB, one older copy in/run/autorun.log.1). Over SSH:easystick-autorun logfollows it,easystick-autorun status|stop|start|restartcontrols the program. - A program that exits stays stopped, as on a Pico. Saving the drive restarts it
only when the program or its settings changed; editing
credentials.txtalone leaves it running. config.txtsettings:autorun=offturns autorun off,autorun=NAMEruns that file from the drive instead (.py,.shor a binary),autorun_restart=yesrestarts the program 5 s after it exits.- To stop a program for good, delete or rename it on the drive and save.
The drive holds 256 KB in total, so programs stay small. The MicroPython here
is the Unix port: it has time, socket, json, asyncio and the other
modules listed above, but no machine.Pin or other GPIO access. A program can use files, sockets
and the other Linux interfaces that m5 can use. A program that uses up the
memory can also starve sshd; removing it from the drive recovers.
A binary must be a bFLT file built for this NOMMU CPU. An ordinary Linux ELF
does not run here, and autorun logs not a riscv32 NOMMU bFLT binary instead
of starting it. Build with the Buildroot toolchain from a source build
(output/host/bin):
riscv32-linux-gcc -Os -fPIC -Wl,-elf2flt="-r -s 16384" -o main hello.c
head -c 4 main # bFLT-fPIC is required: without it the binary loads but crashes on its first
data access. A printf/sleep hello world is about 85 KB.
| Signal | ESP32-P4 Pin | ESP32-C6 Pin | Function |
|---|---|---|---|
| C6_PU / RESET | GPIO 42 | CHIP_PU | Reset line for C6 slave (controlled by boot-shim) |
| SDIO_CLK | GPIO 43 | GPIO 19 | SDIO Clock |
| SDIO_CMD | GPIO 44 | GPIO 18 | SDIO Command |
| SDIO_DAT0 | GPIO 45 | GPIO 21 | SDIO Data 0 |
| SDIO_DAT1 / IRQ | GPIO 46 | GPIO 22 | SDIO Data 1 / Interrupt |
| SDIO_DAT2 | GPIO 47 | GPIO 23 | SDIO Data 2 |
| SDIO_DAT3 | GPIO 48 | GPIO 15 | SDIO Data 3 |
Bringing up native Linux on early RISC-V NOMMU silicon often uncovers subtle interaction issues between hardware errata and kernel subsystems.
During initial development, a critical failure known as the "SSH Wedge" manifested: executing any non-interactive SSH command (ssh m5@host id) or child process termination caused an immediate communication lockup, followed by a hardware watchdog reset (rst:0x7) ~120β160s later.
While initially suspected to be an SDIO bus saturation or ESP-Hosted-NG flow-control overflow, extensive post-mortem debugging via RTC crash capsules and local loopback reproduction uncovered an unhandled Load access fault in rb_erase() during child process exit (timerqueue_del() -> rb_erase()).
This was resolved by integrating three essential ESP32-P4 SoC hardening patches:
- Signal
mcausehardening (0014): Fixes interrupt masking register corruption onSIGCHLDreturns. - L2 Unified Cache hardening (
0015): Prevents silent memory corruption by eliminating blind L2 cache invalidation without write-back. - Systimer atomic read hardening (
0016): Eliminates torn reads on 52-bit hardware counters and missed timer alarms.
π Read the complete engineering post-mortem and root-cause analysis in docs/deep-dive-ssh-wedge-and-soc-hardening.md.
Optional EasyStick Rev0.15 schematics and PCB Gerbers are available under the MIT License, with assembly and reproduction notes.
.
βββ board/ # Hardware contract, pinouts, and MIT-licensed schematics/Gerbers
βββ docs/ # Architecture, troubleshooting, and post-mortem deep dives
β βββ deep-dive-ssh-wedge-and-soc-hardening.md
βββ linux/ # Linux kernel, Buildroot, boot-shim, and integration
β βββ boot-shim/ # ESP-IDF 2nd stage bootloader (PSRAM/SDIO/flash setup)
β βββ buildroot-external/# Buildroot package definitions & rootfs overlay
β β βββ package/
β β βββ esp-hosted-ng/ # SDIO Wi-Fi kernel driver with AP mode support
β β βββ micropython-nommu/ # MicroPython port configured for NOMMU bFLT
β βββ dts/ # Device Tree Source (Stamp-P4)
β βββ kernel-patches/ # Target-specific Linux kernel & SMP hardening patches
β βββ c68/ # Dual-core SMP kernel config fragments & patch renderers
β βββ m3-lab/ # Lab profile overlays (OverlayFS, SoftAP, udhcpd, vi)
β βββ build-m1.sh # Reproducible end-to-end containerized build script
β βββ flash-candidate.ps1# Flashing script for esptool
β βββ flash-layout.json # Flash partition map
βββ tests/ # Real hardware validation captures, test scripts, and records
βββ tools/ # Environment validation, UART capture, and flashing utilities
βββ vendor/ # Pinned upstream Git submodules
βββ versions.lock.json # Immutable commit and toolchain hashes
βββ board-contract.json # Pin assignment & electrical contract
The build pipeline is fully automated and uses Docker to provide a reproducible Debian build environment:
git clone --recursive https://github.com/tsuyoshi-otake/m5stamp-p4-c6-linux.git
cd m5stamp-p4-c6-linux
# Start containerized build
./linux/build-cmd53-bb.sh --vendor /path/to/output --profile m3-lab --smpThis compiles:
- ESP-IDF
boot-shimfor dual-core CPU1 release and SDIO initialization - Linux 6.18 SMP kernel with RISC-V P4 patches and Stamp-P4 Device Tree
- Buildroot root filesystem with Busybox (vi, udhcpd), Dropbear SSH, MicroPython, and ESP-Hosted-NG (
esp32_sdio.ko) - Combined image artifacts ready for flashing
Raw 16MB Monolithic Image:
1081d463e45fc2074124fbcc3e055ac608770a9e86f0f77255cd0837fc094d91 m5stamp-p4-c6-linux-smp-v0.2.5-16mb.bin.gz
58de1b7bfe5cc0095d728e078134e07776e4f659f6c89fe3cb807517538d734b m5stamp-p4-c6-linux-smp-v0.2.5-16mb.bin (uncompressed)
Partition Archive:
3c9349822d915ea5f37e077c12162286e9292e8174b88dd6b19f2770324336c0 m5stamp-p4-c6-linux-smp-v0.2.5.zip
Individual Partitions:
bb3b7a865118960ac5668dd69b3c56a0e06d7350455b4270ddbf97347fc4bd98 bootloader.bin
7937656f29960d53b63010b51836e233d092e067e320cde5778ec43e04218870 partition-table.bin
4c5b438c339df936b9456e6588b642eb0c4eba779a080333ddd3555c8c135ef3 boot-shim.bin
10a8a1505c6f9836932c49e32e17d8ad0a7ae61205f43ee28cd7c384d301337e Image
57157678ce36b2aa0bc507c3335b31fa70dc0ed71fdc0d97dc4fe4cf76c3af83 rootfs.squashfs
a12b18e1347ade36c499c79338ff7125bd4140bf56c64ecfedce8f93b4d3a852 easystick-stamp-p4.dtb
62d9816fa7156d42cf2c9752f2804e69345109486a15f416925b5ca1a9bcc992 boot.img
The images contain third-party software under its own licenses. Corresponding source for GPL components is built from the revisions pinned in versions.lock.json plus the patches and configs under linux/.
| Component | License |
|---|---|
Linux 6.18.35 + patches, esp32_sdio.ko |
GPL-2.0 |
| BusyBox | GPL-2.0 |
| uClibc-ng | LGPL-2.1 |
| Dropbear | MIT |
| wpa_supplicant | BSD-3-Clause |
| ESP-IDF (boot-shim) | Apache-2.0 |
ESP-Hosted NG host driver (esp32_sdio.ko) |
GPL-2.0-only (shared headers GPL-2.0-only or Apache-2.0) |
ESP-Hosted NG C6 firmware (network_adapter.bin) |
Apache-2.0 |
Releases carry β¦-legal-info.tar.gz, Buildroot's make legal-info output for the image: the source archives, patches, configs and license texts of every package in the rootfs and the kernel (manifest.csv lists them). Sources of build-host tools (host-sources) are left out because they are not shipped. Build it locally with EASYSTICK_LEGAL_INFO=1 bash linux/build-c68.sh β¦; it lands in <vendor>/buildroot/legal-info. The boot shim and the C6 firmware are built from the ESP-IDF and ESP-Hosted revisions in versions.lock.json. The image itself lists the same pointers in /usr/share/licenses/README.
The project's own files are licensed under GPL-2.0-only (LICENSE, #31), except where a file carries its own SPDX-License-Identifier: the boot shim (linux/boot-shim/) is Apache-2.0 OR GPL-2.0-only, and the schematics and Gerbers under board/hardware/ are MIT (board/hardware/LICENSE).
- why2025-linux by @mrbreaker for initial ESP32-P4 NOMMU bring-up reference.
- Espressif ESP-Hosted for the SDIO Wi-Fi slave stack.
- The Buildroot and Linux kernel communities.