Electronics

Expressif S3 Linux Support

Adding Linux support to esp32s3

Expressif S3 Linux Support

Introduction

Embedded Linux on microcontrollers has long been a tantalizing goal for makers and engineers alike. The esp32s3-linux project by Ray Bello brings this vision closer to reality, enabling a full Linux 6.5+ system to run on the ESP32-S3 SoC. By leveraging Docker for a fully containerized build environment and integrating Espressif’s network adapter firmware, this repo streamlines the process of compiling, packaging, and flashing a Linux image - no host toolchain installation required.

In this deep-dive, we’ll explore:

  1. Why Linux on ESP32-S3?
  2. Repo Anatomy & Workflow
  3. Containerized Build System
  4. Toolchain & Kernel Configuration
  5. Partition Layout & Flashing
  6. Runtime & Networking
  7. Customization & Extensibility
  8. Troubleshooting & Best Practices
  9. Use Cases & Roadmap

1. Why Linux on ESP32-S3?

1.1 The ESP32-S3 Advantage

  • AI acceleration with vector instructions and support for TinyML.
  • Dual-core Xtensa LX7 at up to 240 MHz, 8 MB flash/PSRAM variants.
  • Built-in Wi-Fi & BLE, unlocking networked applications.

1.2 Beyond FreeRTOS

While FreeRTOS excels at real-time, single-application tasks, Linux offers:

  • Multi-process, multi-user support
  • Rich POSIX filesystem and permissions
  • Standard toolchains (GCC, BusyBox, Python, MQTT, etc.)
  • SSH, networking stacks, and user-space daemons

This allows turning an ESP32-S3 into a tiny edge server, secure gateway, or autonomous device with familiar Linux tooling.


2. Repo Anatomy & Workflow

esp32s3-linux/
├── Dockerfile
├── scripts/
│   ├── rebuild.sh
│   ├── flash.sh
│   └── ...
├── firmware/       ← prebuilt or output binaries
├── release/        ← final build artifacts
├── .vscode/        ← optional IDE configs
└── README.md
  • Dockerfile: Defines the Ubuntu 22.04-based container that compiles toolchains, kernel, and rootfs.
  • scripts/: Orchestrators for full rebuilds and flashing.
  • firmware/ & release/: Hold firmware blobs (ESP-IDF), kernel image, and filesystems.

Workflow:

  1. Clone repo & submodules (git submodule update --init --recursive).
  2. docker build -t esp32s3-linux .
  3. docker run --device=/dev/ttyUSB0 -v $PWD/release:/app/build/release -it esp32s3-linux bash
  4. Inside container: ./scripts/rebuild.sh → artifacts in /app/build/release.
  5. Exit & docker cp artifacts back to host.
  6. ./scripts/flash.sh or manual esptool.py + parttool.py.

3. Containerized Build System

3.1 Why Docker?

  • Isolation: No host pollution by cross-compilers or kernel sources.
  • Reproducibility: Exact environment versioned in Dockerfile.
  • Portability: Works on Linux, macOS, Windows (with Docker Desktop).

3.2 Key Dockerfile Stages

StagePurpose
Base SetupInstalls git, build-essential, Python, cmake
Crosstool-NGBuilds xtensa-esp32s3-uclibc toolchain
Kernel BuildClones Linux 6.5+, configures and compiles XIP kernel
RootFS & DriversBuildroot generates cramfs, merges Wi-Fi firmware
CleanupPrunes intermediate layers, retains /app/build
FROM ubuntu:22.04 AS builder
ENV DEBIAN_FRONTEND=noninteractive
 
# 1. Install dependencies
RUN apt-get update && \
    apt-get install -y git build-essential python3 python3-pip cmake libncurses-dev ...
 
# 2. Crosstool-NG for Xtensa
RUN git clone --depth=1 https://github.com/crosstool-ng/crosstool-ng.git && \
    cd crosstool-ng && ./bootstrap && ./configure --prefix=/usr/local && make -j && make install
 
# 3. Configure & build toolchain
COPY crosstool.config /xtensa.config
RUN ct-ng build
 
# 4. Clone & build Linux kernel
RUN git clone --branch v6.5 https://github.com/torvalds/linux.git /linux && \
    cd /linux && make ARCH=xtensa esp32s3_defconfig && make -j
 
# 5. Buildroot + cramfs + firmware
...

Tip: If build times become excessive, you can leverage Docker build cache with minor tweaks to reorder install steps.


4. Toolchain & Kernel Configuration

4.1 Building the Xtensa Toolchain

  • Target triplet: xtensa-esp32s3-linux-uclibcfdpic

  • uClibc with fdpic (position-independent code) support, crucial for XIP.

  • Key ct-ng options:

    • CT_ARCH_XTENSA=y
    • CT_KERNEL_LINUX=y
    • CT_LIBC_UCLIBC=y
    • CT_LIBC_UCLIBC_FDPIC=y

4.2 Kernel Menuconfig Highlights

After make esp32s3_defconfig, run:

make ARCH=xtensa menuconfig
  • Processor type and features → Xtensa Settings

    • Enable CONFIG_MMU for Linux memory management.
  • Networking support → Wireless → esp32s3 wireless driver

    • Select ESP32S3 Wireless Firmware Loader to auto-bundle network_adapter.bin.
  • File systems → Compressed ROM file system support

    • CRAMFS for minimal footprint, or swap to JFFS2 if wear leveling needed.

5. Partition Layout & Flashing

5.1 Default Partition Table

PartitionTypeOffsetSizeDescription
bootloaderapp0x0000256 KBESP-IDF bootloader
networkdata0x400002 MBWi-Fi firmware (network_adapter.bin)
part-tabledata0x8000016 KBPartition definitions
linuxdata0xA00003.5 MBXIP kernel image
rootfsdata0x4200003.5 MBCRAMFS root filesystem
etcdata0x800000512 KBPersistent configs (e.g. wpa)

Pro tip: Adjust sizes via scripts/partition-table.csv to accommodate larger rootfs or JFFS2.

5.2 Flashing Steps

# 1. Write bootloader, network firmware, and partition table
esptool.py --chip esp32s3 -p /dev/ttyUSB0 -b 921600 \
  write_flash 0x0 bootloader.bin \
              0x40000 network_adapter.bin \
              0x80000 partition-table.bin
 
# 2. Use parttool for XIP+CRAMFS
parttool.py write_partition --partition-name linux  --input xipImage
parttool.py write_partition --partition-name rootfs  --input rootfs.cramfs
parttool.py write_partition --partition-name etc    --input etc.jffs2
  • esptool.py handles raw flash writes.
  • parttool.py (ESP-IDF) updates named partitions, preserving alignment and wear leveling.

6. Runtime & Networking

6.1 First Boot & Console

  • Serial console at 115200 baud; you should see kernel boot messages and mount of CRAMFS.
  • Default root user has no password; networking must be configured manually.

Capture of kernel boot

ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x2b (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3808,len:0x1064
load:0x403c9700,len:0x9e0
load:0x403cc700,len:0x291c
entry 0x403c98d8
etc ptr = 0x420b0000
linux ptr = 0x42120000
rootfs ptr = 0x42480000
[    0.000000] Ignoring boot parameters at (ptrval)
[    0.000000] Linux version 6.5.0 (esp32@buildkitsandbox) (xtensa-esp32s3-linux-uclibcfdpic-gcc (crosstool-NG UNKNOWN) 14.0.0 20231029 (experimental), GNU ld (crosstool-NG UNKNOWN) 2.40.50.20230424) #1 PREEMPT Sat Dec 30 22:10:43 UTC 2023
[    0.000000] config ID: c2f0fffe:23090f1f
[    0.000000] earlycon: esp32s3uart0 at MMIO32 0x60000000 (options '115200n8,40000000')
[    0.000000] printk: bootconsole [esp32s3uart0] enabled
[    0.000000] **********************************************************
[    0.000000] **   NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE   **
[    0.000000] **                                                      **
[    0.000000] ** This system shows unhashed kernel memory addresses   **
[    0.000000] ** via the console, logs, and other interfaces. This    **
[    0.000000] ** might reduce the security of your system.            **
[    0.000000] **                                                      **
[    0.000000] ** If you see this message and you are not debugging    **
[    0.000000] ** the kernel, report this immediately to your system   **
[    0.000000] ** administrator!                                       **
[    0.000000] **                                                      **
[    0.000000] **   NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE   **
[    0.000000] **********************************************************
[    0.000000] Zone ranges:
[    0.000000]   Normal   [mem 0x000000003d800000-0x000000003dffffff]
[    0.000000] Movable zone start for each node
[    0.000000] Early memory node ranges
[    0.000000]   node   0: [mem 0x000000003d800000-0x000000003dffffff]
[    0.000000] Initmem setup node 0 [mem 0x000000003d800000-0x000000003dffffff]
[    0.000000] pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768
[    0.000000] pcpu-alloc: [0] 0 
[    0.000000] Kernel command line: earlycon=esp32s3uart,mmio32,0x60000000,115200n8,40000000 console=ttyS0,115200n8 debug rw root=mtd:rootfs no_hash_pointers 
[    0.000000] Dentry cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[    0.000000] Inode-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[    0.000000] Built 1 zonelists, mobility grouping off.  Total pages: 2032
[    0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off
[    0.000000] virtual kernel memory layout:
[    0.000000]     lowmem  : 0x3d800000 - 0x3e000000  (    8 MB)
[    0.000000]     .text   : 0x42120000 - 0x4234eb30  ( 2234 kB)
[    0.000000]     .rodata : 0x4234f000 - 0x423a9000  (  360 kB)
[    0.000000]     .data   : 0x3d800000 - 0x3d87c6a0  (  497 kB)
[    0.000000]     .init   : 0x3d87c6a0 - 0x3d87f890  (   12 kB)
[    0.000000]     .bss    : 0x3d87f890 - 0x3d8b6410  (  218 kB)
[    0.000000] Memory: 7340K/8192K available (2234K kernel code, 497K rwdata, 360K rodata, 88K init, 218K bss, 852K reserved, 0K cma-reserved)
[    0.000000] SLUB: HWalign=16, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
[    0.000000] rcu: Preemptible hierarchical RCU implementation.
[    0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 10 jiffies.
[    0.000000] NR_IRQS: 33
[    0.000000] rcu: srcu_init: Setting srcu_struct sizes based on contention.
[    0.000000] Calibrating CPU frequency 160.00 MHz
[    0.000000] clocksource: ccount: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 11945377789 ns
[    0.000072] sched_clock: 32 bits at 160MHz, resolution 6ns, wraps every 13421772796ns
[    0.009515] Calibrating delay loop (skipped)... 160.00 BogoMIPS preset
[    0.013414] pid_max: default: 4096 minimum: 301
[    0.021355] Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[    0.025454] Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[    0.080105] rcu: Hierarchical SRCU implementation.
[    0.080894] rcu: 	Max phase no-delay instances is 1000.
[    0.094345] devtmpfs: initialized
[    0.120763] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns
[    0.122090] futex hash table entries: 16 (order: -5, 192 bytes, linear)
[    0.140884] NET: Registered PF_NETLINK/PF_ROUTE protocol family
[    0.172668] platform soc: Fixed dependency cycle(s) with /soc/intc@600c2000
[    0.258813] clocksource: Switched to clocksource ccount
[    0.313741] NET: Registered PF_INET protocol family
[    0.322241] IP idents hash table entries: 2048 (order: 2, 16384 bytes, linear)
[    0.348130] tcp_listen_portaddr_hash hash table entries: 1024 (order: 0, 4096 bytes, linear)
[    0.351909] Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear)
[    0.356779] TCP established hash table entries: 1024 (order: 0, 4096 bytes, linear)
[    0.365414] TCP bind hash table entries: 1024 (order: 1, 8192 bytes, linear)
[    0.372388] TCP: Hash tables configured (established 1024 bind 1024)
[    0.381244] UDP hash table entries: 256 (order: 0, 4096 bytes, linear)
[    0.384350] UDP-Lite hash table entries: 256 (order: 0, 4096 bytes, linear)
[    0.395525] NET: Registered PF_UNIX/PF_LOCAL protocol family
[    0.408650] RPC: Registered named UNIX socket transport module.
[    0.410967] RPC: Registered udp transport module.
[    0.411567] RPC: Registered tcp transport module.
[    0.414526] RPC: Registered tcp-with-tls transport module.
[    0.420964] RPC: Registered tcp NFSv4.1 backchannel transport module.
[    0.492979] Initialise system trusted keyrings
[    0.497341] workingset: timestamp_bits=30 max_order=11 bucket_order=0
[    0.508093] NFS: Registering the id_resolver key type
[    0.511341] Key type id_resolver registered
[    0.512153] Key type id_legacy registered
[    0.514392] jffs2: version 2.2. (NAND) © 2001-2006 Red Hat, Inc.
[    0.520392] Key type asymmetric registered
[    0.522123] Asymmetric key parser 'x509' registered
[    0.528317] io scheduler mq-deadline registered
[    0.531553] io scheduler kyber registered
[    2.546851] 60000000.serial: ttyS0 at MMIO 0x60000000 (irq = 1, base_baud = 2500000) is a ESP32S3 UART
[    2.550374] printk: console [ttyS0] enabled
[    2.550374] printk: console [ttyS0] enabled
[    2.554144] printk: bootconsole [esp32s3uart0] disabled
[    2.554144] printk: bootconsole [esp32s3uart0] disabled
[    2.590659] random: crng init done
[    2.629608] physmap-flash 42000000.flash: physmap platform flash device: [mem 0x42000000-0x42ffffff]
[    2.641292] 6 esp32 partitions found on MTD device 42000000.flash
[    2.642209] Creating 6 MTD partitions on "42000000.flash":
[    2.642660] 0x00000000a000-0x00000000f000 : "nvs"
[    2.647174] mtd: partition "nvs" doesn't start on an erase/write block boundary -- force read-only
[    2.693280] 0x00000000f000-0x000000010000 : "phy_init"
[    2.694124] mtd: partition "phy_init" doesn't start on an erase/write block boundary -- force read-only
[    2.732090] 0x000000010000-0x0000000b0000 : "factory"
[    2.766305] 0x0000000b0000-0x000000120000 : "etc"
[    2.801095] 0x000000120000-0x000000480000 : "linux"
[    2.834277] 0x000000480000-0x000000800000 : "rootfs"
[    2.882388] ESP chipset detected [esp32-s3]
[    2.931293] NET: Registered PF_INET6 protocol family
[    3.022795] Segment Routing with IPv6
[    3.024454] In-situ OAM (IOAM) with IPv6
[    3.031104] sit: IPv6, IPv4 and MPLS over IPv4 tunneling driver
[    3.057075] NET: Registered PF_PACKET protocol family
[    3.058493] Key type dns_resolver registered
[    3.270662] Loading compiled-in X.509 certificates
[    3.366752] cfg80211: Loading compiled-in X.509 certificates for regulatory database
[    3.405487] Loaded X.509 cert 'sforshee: 00b28ddf47aef9cea7'
[    3.410518] clk: Disabling unused clocks
[    3.422854] platform regulatory.0: Direct firmware load for regulatory.db failed with error -2
[    3.423965] cfg80211: failed to load regulatory.db
[    3.427494] cramfs: checking physical address 0x42480000 for linear cramfs image
[    3.435310] cramfs: linear cramfs image on mtd:rootfs appears to be 3196 KB in size
[    3.444737] VFS: Mounted root (cramfs filesystem) readonly on device 31:5.
[    3.451448] devtmpfs: mounted
[    3.453509] Freeing unused kernel image (initmem) memory: 8K
[    3.456449] This architecture does not have kernel memory protection.
[    3.464430] Run /sbin/init as init process
[    3.466950]   with arguments:
[    3.471466]     /sbin/init
[    3.472581]   with environment:
[    3.475670]     HOME=/
[    3.478015]     TERM=linux
Starting syslogd: OK
Starting klogd: OK
Running sysctl: OK
seedrng: can't create directory '/var/lib/seedrng': Read-only file system
Starting network: Successfully initialized wpa_supplicant
OK
Starting inetd: OK

Welcome to Buildroot

buildroot login: 

6.2 Wi-Fi Setup

  1. Create /etc/wpa_supplicant.conf:

    ctrl_interface=/var/run/wpa_supplicant
    network={
        ssid="YourSSID"
        psk="YourPassword"
        key_mgmt=WPA-PSK
    }
  2. Bring up interface:

    ip link set espsta0 up
    wpa_supplicant -B -i espsta0 -c /etc/wpa_supplicant.conf
    udhcpc -i espsta0  # DHCP client
    ping 8.8.8.8       # Verify connectivity

7. Customization & Extensibility

7.1 Adding Packages

  • Enter the container, then make menuconfig under the Buildroot directory.
  • Enable additional BusyBox applets, Python support, MQTT clients, or even a lightweight SSH daemon.

7.2 Kernel Modules

  • Configure external modules via Makefile overlays in linux/drivers/.

  • Rebuild only the modules to speed iteration:

    cd /linux
    make modules
    make modules_install INSTALL_MOD_PATH=/app/build/release

7.3 GUI & Edge-AI

  • While XIP kernels limit RAM use, you can mount an SD card via SPI for larger workloads.
  • Experiment with TinyML frameworks by cross-compiling TensorFlow Lite Micro.

8. Troubleshooting & Best Practices

IssueRemedy
Permission denied on /dev/ttysudo usermod -aG dialout $USER & relogin
Bad partition tableVerify scripts/partition-table.csv offsets and sizes
Page allocation failures (dmesg)Reduce init-time workload, or add swap-like filesystem via JFFS2
Wi-Fi firmware load errorEnsure network_adapter.bin matches kernel’s CONFIG version
  • Enable logging: Append loglevel=8 to kernel cmdline via parttool.py.
  • Serial break: If kernel panics, use screen’s “Ctrl-A k” to reset board.

9. Use Cases & Roadmap

  1. IoT Gateway: Run MQTT, Node-RED, or Home Assistant Light on S3-sized hardware.
  2. Edge Compiler: Compile small C apps directly on device via gcc in rootfs.
  3. Secure Controller: Leverage Linux capabilities (namespaces, cgroups) for containerized tasks.
  4. Tiny GUI: Add an SPI-connected LCD and run a minimal X server or framebuffer app.

Future Directions

  • Upstream integration of S3 support in Buildroot and mainline Linux.
  • Automated CI pipelines for nightly Docker image builds.
  • SD-card based expandable storage and U-Boot bootloader support.
  • Prebuilt Docker images on Docker Hub with tags per kernel version.

Conclusion

The esp32s3-linux repository encapsulates a robust, Docker-powered toolchain and build system that brings Linux 6.5+ to the ESP32-S3 platform. By modularizing kernel, rootfs, and firmware builds - and automating flashing - this project lowers the barrier for embedded Linux enthusiasts. Whether you’re experimenting with edge AI, lightweight servers, or simply pushing the limits of microcontrollers, Ray Bello’s fork provides a solid foundation to build upon.

Feel free to clone the repo, customize the kernel, add new user-space packages, and share your findings with the community!