Skip to content

Releases

0.9.1 — a crash over SSH, and SSH on your own port

Choose a tag to compare

@paulneja paulneja released this 05 Oct 14:40
032a36b

Fixes the kernel crash right after logging in over SSH (#22), and lets SSH,
Telnet and the web page run on other ports. The kernel is
linux-esp32s3 tag
v7.2.4-esp32s3.3.

Flash the .bin attached below at 0x0, it is the whole 16 MB chip. Log in on
the serial console as root / changeme123. The first login asks for a new
password and then whether to turn on SSH, Telnet or neither; nothing listens
on the network before that.

The crash showed up with kitty's kitten ssh. Its setup script makes the
shell re-run every subshell with all its variables as arguments, and on NOMMU
the kernel copied those under the bottom of the new program's stack, over its
own memory. It went down a few seconds later somewhere else, which is why it
looked random. The stack now has room for them.

remote-login port ssh 2022 moves SSH, remote-login auth key (or
password, both) picks how it lets you in, and remote-login status shows
it all. Same for Telnet, and web-server port 8080 for the web page. It's
kept in /etc/remote-login.conf and survives an update.

This exact image passed 36/36 board tests, 11/11 extra tests, 20/20 factory
boots, and SSH on another port with keys only, password only and both from a
PC over WiFi. Free RAM after boot is 4248 kB, boot to login about 15.2 s.

Full list in the CHANGELOG, and thanks to everyone in
THANKS.

0.9 — Linux 7.2.4, and fork through the cache MMU

Choose a tag to compare

@paulneja paulneja released this 04 Oct 14:55
de2c3e1

New kernel and a much cheaper fork. Linux goes from 6.11, which stopped
getting fixes in 2024, to 7.2.4, and the kernel is now this project's own
tree: linux-esp32s3, tag
v7.2.4-esp32s3.2.

Flash the .bin attached below at 0x0, it is the whole 16 MB chip. Log in on
the serial console as root / changeme123. The first login asks for a new
password and then whether to turn on SSH, Telnet or neither; nothing listens
on the network before that. Change it later with remote-login.

fork() doesnt copy memory with interrupts off anymore. The 64 KB pages of a
forked process are swapped through the S3 cache MMU, and malloc grows the heap
64 KB at a time so most of it goes that way. The slowest switch between busy
forked shells went from 29 ms to about 5 ms, and a bash holding 1.3 MB can
still fork, the 512 KB limit only counts what gets copied now. A wrong WiFi
password is reported as one and doesnt break the next scan anymore (#20). The
kernel is 563 KB smaller.

This exact image passed 36/36 board tests, 10/10 extra tests and 20/20
factory boots. Free RAM after boot is 4204 kB, up from 3708. Boot to login is
about a second slower than 0.8.1 (15.1 s), not tracked down yet.

Full list in the CHANGELOG, and thanks to everyone in
THANKS.

0.8.1 — WiFi that survives being poked, and a USB console

Choose a tag to compare

@paulneja paulneja released this 24 Sep 04:06
202906f

Maintenance and bug fix release on top of 0.8. No big new features, just the
problems people reported after 0.8 and a few more found while testing those
fixes.

Flash the .bin attached below at 0x0, it is the whole 16 MB chip. Login
root / changeme123, change it with passwd.

WiFi no longer panics the kernel when the link goes down and up fast (#13), a
cfg80211 crash during scans is gone, and reconnecting doesnt leak memory
anymore. SSH gets a real terminal again (#9) and kitten ssh from kitty works.
The kernel console now also comes out of the chip's own USB port (#17), with
the login there off by default to save RAM. The clean build cant fill your
disk anymore (#12) and no longer misses the IDF Python environment (#16).

This exact image passed 36/36 board tests and 20/20 factory boots. Free RAM
after boot is 3708 kB.

Full list in the CHANGELOG, and thanks to everyone in
THANKS.

0.8 — the board stops crashing

Choose a tag to compare

@paulneja paulneja released this 14 Sep 21:17

Linux 6.11 running natively on an ESP32-S3. 0.8 is the release where the board
stops crashing, and where the fork backend gets the memory it was promised.
One file to flash.

Flash linux-esp32s3-native-full.bin to offset 0x0 — it is the whole 16 MB
chip: bootloader, partition table, WiFi firmware, /etc, kernel, rootfs and a
formatted, empty /home.

pip install esptool
./flash.sh -p /dev/ttyUSB0        # from a clone; images/ holds this release

Login root / changeme123. Change it with passwd. About 14 seconds from
reset to the prompt, and the console is quiet now; bootlog verbose turns it
back up.

What changed since 0.7

The crashes are found and fixed, and they were never memory. About one
factory boot in ten on 0.7 took a kernel fault in its first seconds, and
nobody knew, because the test counted silence as a pass. The ESP32-S3 has one
flash cache for both cores. Linux reads jffs2 straight through it and writes
by IPC to core 0, and nothing invalidated the cache after any write Linux
made, so the kernel went on reading stale pages, sometimes of its own code.
Four lines in the firmware fix it. Fifty-five factory boots in a row came up
clean on the fixed firmware, measured by a runner that reads dmesg and the
taint flags back instead of trusting a quiet console. That does not prove the
board never crashes; it shows the corruption that used to appear in one boot
of ten is no longer reproducible.

Every program that forks needs half the memory. The backend's page-set
exchange, which had been written off for corrupting memory, turns out never
to have: it was the flash cache all along. It ships now, and N processes share
N−1 page sets. make and jobq need none at all, because they spawn through
vfork instead of forking, and a plain foreground command in bash does the
same. MemAvailable after boot went from 1340 kB to 3744 kB; the README has the
chart, per program.

Smaller, and a good deal tighter. SLUB_TINY takes the slab from 3.7 MB
to 1.7 MB; a 512 KiB jffs2 deflate workspace that was never used is gone; the
kernel is 320 KB smaller and 1.4 MB came back on a rootfs that had 48 KB free
(iptables, lua, duplicate payloads). The four dropbear host keys every board
used to share are no longer in the image; keys are made on the board at first
boot. In the WiFi driver: a use after free at interface creation, a read past
the other core's heap, and three leaks that could leave WiFi dead until
reboot, all fixed. Both core 0 watchdogs are on, so a deadlocked WiFi core
resets the chip instead of leaving Linux with dead networking.

Things a person notices. wifi connect writes its configuration for the
first time (its heredoc never ran on the board's shell). bootlog verbose and
bootlog quiet switch the console without a rebuild and survive a reboot. An
update no longer takes the WiFi password with it. The clock survives a
reboot. DHCP's resolvers come before the public ones, so LAN names resolve.
dmesg is restricted; /dev/mem and TIOCSTI are gone.

Verified on hardware

36 of 36 board tests on a clean build of this code (8ea9011, 7b4c94e in
the repository), from a factory-fresh boot;
ten more beyond the suite (384 KiB written to jffs2, copied and read back with
the same SHA-256, dtach, cron, passwd, persistence across a reboot); 10
factory boots, 10 clean, tainted 0; then driven from another machine over
WiFi: SSH, scp, the web page. Two independent container builds of the tagged
sources differ in one file, /etc/shadow, from the random salt of the default
password. The image attached here was built by the Image workflow from the
tagged sources; the run of the board suite on these exact bytes is the next
record, and the README says so rather than implying otherwise.

sha256  05bc4cadd3231c0203f7a9b351670c459da7e42b014c3d2df61d2d83f7f0661e

The records, with the numbers and what was read back off the board, are under
build/verification/2026-09-14-*.

Limits, honestly

  • Fork is not an MMU. Still NOMMU, no hardware memory protection between
    processes. Permissions are not isolation.
  • 8 MB of RAM is the ceiling. Private memory per fork is capped at
    512 KiB, and a context switch over that much holds interrupts off for about
    21 ms. Heavy concurrency still meets the OOM killer, later than before.
  • Writable flash gets slow once JFFS2 has to reclaim blocks. Reads are
    correct now; write throughput is not fixed. Avoid heavy rewrites of /home.
  • Xtensa FDPIC binaries only. No x86 or ARM binaries. No CPython, Neovim,
    SQLite, sudo or doas. iptables and lua left in this release.

This is a research project, not a hardened product.

0.7 — fork, Bash and a userspace that fits

Choose a tag to compare

@paulneja paulneja released this 08 Sep 03:51

Linux 6.11 running natively on an ESP32-S3, now with real fork(), Bash and a
userspace you can actually work in. One file to flash.

Flash linux-esp32s3-native-full.bin to offset 0x0 — it is the whole 16 MB
chip: bootloader, partition table, WiFi firmware, /etc, kernel, rootfs and a
formatted, empty /home.

pip install esptool
./flash.sh -p /dev/ttyUSB0        # from a clone; images/ holds this release

Login root / changeme123. Change it with passwd. About 14 seconds from
reset to the prompt.

What changed since 0.6

Programs that need fork() now run. This was the wall. NOMMU Linux has no
fork(), so anything that forks — a shell pipeline, make -j2, a server
accepting a connection — simply could not exist. 0.7 adds a fork backend built
on private software memory banks: parent and child get independent state, and
the backup memory is reclaimed when nobody is sharing it any more. It is not
copy-on-write and it is not an MMU. It costs about 552 KiB per Bash fork, and
that cost is the main limit of the system.

Bash for you, BusyBox for the services. Bash 5.2.37 is the login shell;
/bin/sh stays BusyBox so the boot stays cheap. Dash 0.5.12 is there too.

Real programs under their real names. GNU Make 4.4.1, MicroPython 1.26.0
with working fork and pipe IPC, socat 1.8.1.3, nc. Make runs recipes in
parallel; MicroPython forks and talks to its children over pipes.

A system, not a demo. Editable web content in /home/www, private user
homes with Unix permissions and su/passwd, per-user cron with working
@reboot, detachable session consoles and nohup jobs that survive closing
the serial line.

Built from source in one command. ./run.sh drives everything: it checks
the environment, builds the toolchain, kernel, firmware and userspace from
pinned sources inside a container, verifies checksums, flashes and runs the
board suite. ./run.sh --all -y does the whole path unattended.

The shell gets out of the way when memory runs out. With WiFi associated
there may not be enough left for Bash to fork. When that happens the shell says
so, switches to Dash, re-runs the command that failed, and remembers the choice.
use-shell bash undoes it.

Verified on hardware

27 of 27 tests pass on this exact image, from a factory-fresh boot: memory
remapping, static and dynamic fork, process compatibility, Bash, Dash, Make,
MicroPython, socat and nc, job admission, benchmarks, users and permissions,
HTTP, cron, serial reconnection and persistence across reboots. Boot and the
whole suite finish with nothing from the OOM killer in dmesg.

sha256  e264b4abbce7610bdc0164d0f31c341c45fae98277c701039f67eded8580961d

Two independent builds of the same commit differ in exactly two places, both
metadata: the random salt Buildroot puts in /etc/shadow, and a git version
string embedded in the WiFi firmware. Zero bytes of compiled code differ.

Limits, honestly

  • Fork is not an MMU. Still NOMMU, no hardware memory protection between
    processes. Permissions are not isolation.
  • 8 MB of RAM is the ceiling. Two interactive Bash sessions do not fit.
    Heavy concurrency will hit the OOM killer.
  • Writable flash gets very slow once JFFS2 has to reclaim blocks. This is
    not new in 0.7 — it behaves the same on the 0.6 kernel and with fork disabled
    — and the cause is still open. Avoid heavy rewrites of /home.
  • Xtensa FDPIC binaries only. No x86 or ARM binaries. No CPython, Neovim,
    SQLite, sudo or doas.

This is a research project, not a hardened product.

0.6 — the clock sets itself, the web page stays on

Choose a tag to compare

@paulneja paulneja released this 31 Jul 14:17

Linux 6.11 running natively on an ESP32-S3, with WiFi. One file to flash.

Flash linux-esp32s3-native-full.bin to offset 0x0 — it is the whole 16 MB
chip: bootloader, partition table, WiFi firmware, /etc, kernel and rootfs.

pip install esptool
./flash.sh --erase         # from a clone, port autodetected

Login root / changeme123. Change it with passwd.

What changed since 0.5

Two things that had to be redone after every single boot no longer do.

The clock sets itself. This board has no RTC, so it powered on believing it
was 1 Jan 1970 — and every certificate is "not valid before" a date decades in
the future from there, so every HTTPS request failed until someone typed
date -s by hand. busybox ntpd is now built in, and a udhcpc hook runs it
the moment the interface gets an address, which is the first point at which the
query can succeed. It runs in the background, so it does not hold up a boot
that deliberately no longer waits for the network, and it cannot hang: ntpd -q
gives up on its own after 10 seconds, 50 if some server answered.

Three peers — pool.ntp.org plus Cloudflare and Google as IP literals, so
a network whose DNS is broken still gets the time. What it did is in
/var/log/network.log.

It happens after the login prompt, not before: the prompt is at ~11 s and the
time lands 20–35 s in, once WiFi has associated. Log straight in and date can
still say 1970 for a moment.

The web status page stays on. It needed httpd -h /www -p 80 typed again
after every restart. It now has the helper ssh-server always had:

web-server on          # or: off, status

The switch is a line in /etc/inetd.conf, on the writable /etc partition, so
it survives a reboot. inetd starts httpd only when someone actually connects,
so an enabled-but-unvisited page costs no process and no RAM. Still off by
default
— an unauthenticated HTTP port is not something to hand out unasked.

Two fixes worth naming

flash.sh --parts wrote three of the six pieces to the wrong place. The
offsets were typed into the script and had drifted from the partition table:
/etc landed inside the firmware partition and the kernel 128K below where the
bootloader looks for it. esptool reports success and the board never boots.
Nothing caught it because the combined image is the documented path and is
built by reading the same table. It now reads the offsets from that table too,
so the two cannot drift apart again. Only 0x0 and 0x8000 stay constants,
because they cannot come from a table that has not been located yet.

CI now checks the build against what this repository ships, rather than
only proving that it builds. The packaging job keeps a copy of the committed
images before overwriting them and prints a table of size and byte equality for
each.

Verified

On a fully erased board: flash, join WiFi from nothing, and the clock sets
itself and curl https:// works with nothing typed by hand. Enable the page,
reboot, and it is still there.

And rebuilt from this tree by GitHub Actions in Docker, from the repository
alone: all seven images come out at exactly the size of the ones committed
here, and bootloader.bin and partition-table.bin — the two that carry
nothing of the machine that built them — are byte-identical.

sha256  5947a07a238b26b5c124fc5732da7e012c4611924dd728159f48120c90bfed81

Full notes in CHANGELOG.md.

0.5 — SoftAP gone, faster boot, reproducible

Choose a tag to compare

@paulneja paulneja released this 27 Jul 02:00

Linux 6.11 running natively on an ESP32-S3, with WiFi. One file to flash.

Flash linux-esp32s3-native-full.bin to offset 0x0 — it is the whole 16 MB
chip: bootloader, partition table, WiFi firmware, /etc, kernel and rootfs.

pip install esptool
./flash.sh                 # from a clone, port autodetected

Login root / changeme123. Change it with passwd.

What changed since 0.4

The SoftAP is gone, from the kernel driver and the firmware alike. 0.3
disabled it; the code stayed behind, unreachable. It never worked — the closed
WiFi blob beaconed as WEP, and bringing the AP up wedged the firmware so the
STA could no longer scan. The driver is now pristine upstream plus the BLE
pipe, cmd.c in the firmware is byte-identical to upstream again, and the
firmware is built without SoftAP support, which frees 34 KB in a partition
that was 8% free and is now 13%.

The cold boot is 14.6 s → 11.0 s, all of it out of userspace:

  • the network no longer holds the boot (~2.2 s). It was waiting on the wrong
    thing — udhcpc started before the radio had associated. It now runs in the
    background and the interface comes up behind the login prompt.
  • S02sysctl replaced (~1.4 s). Buildroot's generic version spawns about ten
    processes to apply the single line this board has.
  • cron removed (~0.8 s). Nothing used it, and its own stop path was broken.
  • iptables no longer starts at boot (~0.6 s) but stays installed — it had an
    empty ruleset and nothing to restore.

It reproduces from a clean clone. git clone, docker build, mkdir -p build-output, docker run, make-images.sh, flash.sh — nothing else. 43
minutes of build, then a board that boots in 11 s, passes both RSA self-tests,
joins WiFi and reaches the internet. Walking that path is what found the two
defects that made it impossible before: the mount directory had to exist first,
and make-images.sh could not be pointed at what a Docker build leaves behind.

Known limitations

  • No clock. No RTC, no NTP: the board boots at 1 Jan 1970, so every
    certificate reads as not-yet-valid and curl https:// fails on every site
    until you run date -s "2026-07-26 10:00:00". Nothing else is affected —
    WiFi, ping, telnet and plain http:// work regardless.
  • The status page is manual. httpd -h /www -p 80, and it does not come
    back after a reboot.
  • RAM is tight. 7.3 MB total; an occasional command right after boot can be
    killed by the OOM killer.

Full notes in CHANGELOG.md. What the board exposes on a network
is in SECURITY.md — read it before putting one on a LAN.

0.4 — WiFi setup over Bluetooth

Choose a tag to compare

@paulneja paulneja released this 22 Jul 22:38

Join the board to a WiFi network from a phone, with no PC and no serial
cable
. It advertises over Bluetooth LE as Esp32-Linux: connect with any BLE
serial terminal, send a character, and pick a network from the list. Open
networks connect straight away, secured ones ask for the password, and it
reports the IP address it got.

Wait about 30 seconds after power-on before connecting. The Bluetooth
stack comes up long before Linux has finished booting, and connecting during
that window is unreliable. After that it works consistently.

The ESP32-S3 has no Bluetooth Classic, so this uses the Nordic UART Service
over BLE. Any BLE serial terminal app works — make sure it scans in BLE
mode, not Bluetooth Classic, or the board can never appear.

Changes since 0.3

  • WiFi setup over Bluetooth. A NimBLE peripheral on core 0 acts as a byte
    pipe to Linux (/dev/esp-ble), where a small daemon runs the scan / pick /
    password dialog through the existing wifi command. The firmware never
    drives the radio itself, so the driver state cannot desynchronise.
  • The kernel's vector address is kept in sync automatically. The firmware
    reserves Linux's exception-vector buffer at an address the linker chooses,
    and it moves whenever the firmware's size changes; the kernel has it hard
    coded. When they drifted apart, the kernel booted normally and then died the
    instant it executed userspace — no panic, nothing on the console. Packaging
    now detects that, realigns the kernel and rebuilds it, so touching the
    firmware needs no special knowledge.
  • Partition layout adjusted for the larger firmware: factory 640K → 768K,
    linux 4.5M → 4M at 0x140000, rootfs at 0x540000, home 3328K.

Flash it

One file, nothing else needed:

esptool.py -p /dev/ttyACM0 --chip esp32s3 write_flash 0x0 Linux-on-esp32-S3-v0.4.bin

or ./flash.sh from the repo. Log in as root / changeme123 and change it
with passwd. Then either wifi on the console, or Bluetooth as above.

See BLE.md for how it is built and what its limits are.

0.3 — SoftAP removed, nano, interactive wifi

Choose a tag to compare

@paulneja paulneja released this 18 Jul 00:06

Native Xtensa Linux for the ESP32-S3 — validated on real hardware (flashed clean,
boots to login, RSA 512/2048-bit self-tests pass, associates to WiFi and gets a
DHCP IP).

Changes since 0.2

  • SoftAP removed (STA only). The closed WiFi blob beaconed as WEP and, worse,
    bringing the AP up wedged the firmware so the STA could no longer scan. The
    esp32-ng driver is now hard-forced to never create espap0, and the ap
    command is gone. The board joins an existing network; it does not host one.
    This also makes STA scanning reliable.
  • nano editor. GNU nano replaces the busybox vi applet (which is disabled);
    vi is a symlink to nano.
  • Interactive wifi command. Run wifi to scan, list networks numbered, and
    pick one — open networks connect straight away, secured ones prompt for the
    password. Plus wifi connect/scan/status/disconnect; wifi-setup kept as a
    compatibility shim. (0.2's README promised wifi connect but the command did
    not exist — now it does.)
  • One full-flash image. linux-esp32s3-native-full.bin is now a complete
    16 MB image: flashing it alone erases and rewrites the whole chip, so there is
    nothing else to flash and no separate --erase step.
  • Partition layout. rootfs grown 6.5 MB → 7.5 MB (taking the space from
    /home, now 2.9 MB) to fit nano + ncurses. The rootfs offset is unchanged so
    the XIP address stays the same.
  • Reproducible build fixes (also missing from the 0.2 tag): the local changes
    are now actually wired into a fresh clone build, and make-images.sh packages a
    build into the flashable image.
  • Docs updated to match (README, ARCHITECTURE, DEVELOPMENT, NOTICE).

Flash it

Everything is in one file — download Linux-on-esp32-S3-v0.3.bin and:

esptool.py -p /dev/ttyACM0 --chip esp32s3 write_flash 0x0 Linux-on-esp32-S3-v0.3.bin

or use ./flash.sh from the repo. Log in as root / changeme123 (change it with
passwd), then wifi to get online.

0.2 — native Xtensa Linux

Choose a tag to compare

@paulneja paulneja released this 17 Jul 02:25

Linux now runs natively on the ESP32-S3. v0.1 ran Linux on a RISC-V
emulator hosted on the chip; this release replaces that with a real Linux 6.11
kernel compiled for Xtensa, executing directly on the chip's own cores. No
interpreter in the middle.

Flash one file and you have it — download the .bin below, then:

pip install esptool
esptool.py --chip esp32s3 -p /dev/ttyACM0 -b 460800 erase_flash
esptool.py --chip esp32s3 -p /dev/ttyACM0 -b 460800 write_flash 0x0 Linux-on-esp32-S3-v0.2.bin

Or clone the repo and run ./flash.sh --erase, which finds esptool and the
port by itself. Log in as root / changeme123 over the serial console at
115200 baud, and join your network with wifi connect "SSID" "PASSWORD".

Verified on a fully erased board: it boots to a login prompt, the RSA
accelerator passes its self-tests, the rootfs mounts from flash, telnet comes
up and the board reaches the internet.

What's new compared to v0.1

Native instead of emulated. The whole point. v0.1 interpreted RV32IMA
instructions in software; everything now runs as native Xtensa code.

The hardware RSA accelerator is usable from Linux. New in this release: the
chip's RSA/MPI block is wired into the Linux Crypto API as rsa-esp32s3, so
any program can use it, not just a demo. The kernel stacks
pkcs1pad(rsa-esp32s3,sha256) on top, so X.509 and signature verification go
through the hardware. A self-test runs at boot at 512 and 2048 bits. It is the
only crypto block Linux can safely claim — the WiFi firmware shares the die and
uses AES and SHA for WPA2, but never RSA.

Real WiFi, joined at runtime. wifi connect "SSID" "PASSWORD" — no
recompiling to change networks, and no credentials baked into the image. v0.1
needed the WiFi credentials compiled in.

DNS works. v0.1 reached the internet by IP only; name resolution was a
known gap. Fixed here.

More userland. Lua, a BusyBox httpd with a small status page (off by
default, start it with httpd -h /www -p 80), and curl.

Services are opt-in. Telnet is on by default; SSH ships disabled and is
enabled with ssh-server on. httpd does not autostart, so nothing you are not
using costs RAM.

One image, and a flasher. A single ~11.8 MB .bin for offset 0x0 with
bootloader, partition table, WiFi firmware, /etc, kernel and rootfs. It stops
before the /home partition, so re-flashing it leaves your data alone. The
repo also ships flash.sh, and everything needed to rebuild the image from
source.

Known limitations — please read

  • curl over HTTPS is experimental. Certificate verification is real and
    works against sites like github and google, but the CA bundle is trimmed to
    34 major roots because the full 140-cert set exhausts mbedTLS's memory on
    this board, TLS is 1.2 only, and RAM is tight. Fine for light fetches, not a
    robust tool. Expect rough edges.
  • The SoftAP does not work. The closed WiFi blob beacons as WEP instead
    of WPA2, so clients reject it or fail to get an IP. The proper fix (hostapd
    generating the beacon) is blocked by the lack of an MMU — there is no
    fork(). Note the esp32-linux SSID is visible in a scan even when
    ap status reports off
    , because the beacon comes from the firmware while
    the ap command only controls the Linux side. Do not rely on it. Use the STA
    mode above instead.
  • RAM is tight. Right after boot, while services start, a command can
    occasionally be killed by the OOM killer. Wait a few seconds and retry.
  • No MMU means no fork(): bash, python, nginx and anything else that
    depends on it cannot run. BusyBox ash and everything shipped here work
    within that.

Hardware

ESP32-S3 with 16 MB flash and 8 MB Octal PSRAM (an N16R8 module, e.g.
DevKitC-1). The 8 MB Octal PSRAM is required — it is the system RAM.

Checksum

sha256  43124f8cb96bfa4f74e507a7aa23e4e751aae8b4d5995193411d1491b955aaa5
        Linux-on-esp32-S3-v0.2.bin

Built on the Xtensa Linux, buildroot and esp-hosted work of
jcmvbkbc, and on Espressif's esp-hosted
firmware. See NOTICE for full credits and licenses.