Embedded Linux on i.MX6ULL — From First Boot to First Driver¶
The Raw Approach: Build It Yourself, Understand It Forever¶
Target board: Point Atom (正点原子) MINI — i.MX6ULL (Cortex-A7, 696 MHz, 512 MB DDR3L). ALPHA and many other i.MX6ULL boards work with minor DT/IOMUX adjustments.
Target reader: Embedded engineer fluent in MCU / bare-metal / RTOS, new to Linux.
Host environment: Native Linux (Ubuntu 22.04 LTS or Debian stable).
Linux kernel target: v6.6 LTS. Driver-API signatures (class_create, i2c_driver.probe, i2c_driver.remove) and DTS paths (arch/arm/boot/dts/nxp/imx/) in this book follow v6.6 conventions.
Philosophy: No Yocto. No vendor BSP magic. No defconfig && make until you’ve already done it the long way. The only black box we allow ourselves is the C compiler — and even that we open up in Part VIII (Ch 122).
Scope¶
9 Parts, 160 chapters (131 numbered + 29 supplementary, “letter-suffix” convention), ~2,870 pages. The supplementary chapters (letter-suffixed: 18A, 18B, 35A, 52A, etc.) expand specific topics where the numbered chapter’s default depth isn’t enough for production work — they share a parent number and can be read independently. The numbered chapters are the required path; the supplementary chapters are recommended. Part IX is drafted but still needs hardware and QEMU lab validation.
What the book covers¶
Part I — Foundations. Host setup, the ARMv7-A architecture as it differs from Cortex-M, the i.MX6ULL SoC, the GNU toolchain, the Boot ROM’s
IVT/DCD/BootDatacontract, and a hardware bring-up checklist.Part II — Bare-metal i.MX6ULL. Build the entire stack from the reset vector up: LED in pure assembly; a C runtime with hand-written startup and linker script; a Boot-ROM-acceptable image built by our own Python tool; UART +
printf; CCM clocks; DDR3 + MMDC; exceptions and GIC; timers; MMU + caches; one chapter each of bare-metal I²C/SPI/LCD, button input, and bare-metal RTC.Part III — U-Boot, deeply. Build mainline U-Boot, recognize Part II inside its source, understand SPL, trace the boot flow line by line, port U-Boot to a custom board, bring up U-Boot on a previously unsupported SoC, master
bootcmd/bootargs/ FIT, build a multi-variant FIT image, set up the TFTP + NFS + USB-OTG development loop.Part IV — The Kernel. Build mainline Linux for i.MX6ULL, boot it from U-Boot, deep-dive on the Device Tree, trace
start_kernel()to PID 1, build an initramfs from scratch, mastermake menuconfig, learn the kernel-lifecycle decision framework (mainline / LTS / vendor BSP), validate DT bindings against YAML schemas.Part V — Root filesystem & user space. A
busybox-based hand-built rootfs;/proc/sysdevtmpfs; init systems; libc and dynamic linking; Buildroot; Ubuntu-base as a fully-featured alternative; read-only root + overlayfs for industrial deployments; containers on embedded.Part VI — Driver development. ~33 chapters covering every common subsystem from char devices and platform drivers through I²C/SPI/PWM/RTC/IIO/regmap/DMA/Net/Sound/DRM/USB, with deeper treatment of CAN, multi-touch, block devices, WIFI, cellular modems, HDMI bridges, kernel timers, async notification, watchdog, power management, PREEMPT_RT real-time, MTD/UBI, V4L2/GStreamer, and a Rust-for-Linux sidebar. One canonical example per subsystem — depth on real chips lives in Part VII.
Part VII — Device cookbook. 54 chapters, one per common device class, with 2–4 real chips compared side-by-side: schematic + DT example + driver code (or existing-driver enablement) + user-space access + lab + chip-specific pitfalls. Storage, environmental & motion sensors, ADCs/DACs, displays, cameras, audio codecs, WiFi/BT modules, LoRa/UWB/ZigBee/cellular, industrial buses (RS-485, LIN, CAN), RFID/NFC, fingerprint, smart LEDs (WS2812), motor drivers, external RTC. This is the go-to reference Part.
Part VIII — Debug, production, advanced. JTAG/OpenOCD/GDB across layers; kernel debugging without JTAG (ftrace, eBPF, kgdb); user-space debugging; a capstone custom-board port; build your own toolchain with crosstool-NG; Yocto layer development; secure boot (HAB) and OP-TEE; field updates (RAUC, SWUpdate, Mender); the mainline patch-submission workflow; CI/CD for embedded; BSP → mainline migration playbook; VSCode + gdbserver remote debug.
Part IX — Applied virtualization and mixed-criticality systems. QEMU as a buildable lab bench; ARM HYP mode without magic; Xen in QEMU; Xen on real i.MX6ULL with a tiny DomU; device ownership, DMA, and isolation limits; Jailhouse in QEMU ARM64; bare-metal/Zephyr inmate cells; STM32MP1 Linux + RTOS using the practical A7/M4 split.
How to read this book¶
Each chapter is structured the same way, so the reader always knows where to look:
What — the concrete artifact this chapter builds.
Why — what problem motivates this artifact; what the world looks like without it.
How — the mechanics, register-by-register or function-by-function.
Focus — the one or two ideas that, once internalized, unlock the next several chapters.
Lab — a hands-on deliverable. If you can’t reproduce it from a clean shell, you have not finished the chapter.
Pitfalls — the specific traps real engineers fall into here.
Going deeper — pointers to the Linux source tree, NXP reference manual sections, and seminal papers.
PART I — FOUNDATIONS¶
You are an MCU engineer. You know what a vector table is, what a linker script is, what
volatileis for. This part exists to give you names for the things Linux adds on top.
Chapter 1 — Preface and how to use this book¶
Who this book is for (the MCU/bare-metal engineer)
What we mean by “raw” and why we refuse Yocto for ~50 chapters
The lab discipline: every chapter has a deliverable; you don’t skip
How chapters depend on each other (dependency graph)
Conventions: prompt symbols, register notation, file paths
Pages: ~8
Chapter 2 — What “Embedded Linux” actually is¶
The four layers: Boot ROM → Bootloader → Kernel → User space
Kernel space vs user space; the syscall boundary
Process, thread, file descriptor — vocabulary first
How this differs from your RTOS: virtual memory, demand paging, ELF loading
Why Linux is “big” (and where its size actually lives)
Focus: the user/kernel split is the single most important concept in the whole book
Pages: ~14
Chapter 3 — Host environment setup¶
Choosing a host OS (Ubuntu 22.04 LTS); why native Linux beats WSL/VM for this work
Required host packages:
build-essential,bison,flex,libssl-dev,bc,device-tree-compiler,u-boot-tools,nfs-kernel-server,tftpd-hpa,minicom,picocom,qemu-user-staticInstalling two project-local Arm GNU toolchains:
arm-none-linux-gnueabihf-for Linux andarm-none-eabi-for bare metalSetting up TFTP, NFS, and a serial console on the host
USB-OTG flashing tools:
imx_usb_loader, NXPuuu(Universal Update Utility)A reproducible workspace layout for the rest of the book
Lab: flash a stock image to SD with
dd, boot, log in over UART — prove the pipelinePages: ~16
Chapter 4 — ARMv7-A and the Cortex-A7, for the MCU engineer¶
ARMv7-A vs ARMv7-M: what Cortex-A adds (MMU, privilege levels, generic timer, NEON, multicore option)
Exception model: USR / SYS / SVC / IRQ / FIQ / ABT / UND (and how this maps to Linux’s user/kernel split)
Banked registers, the program status register, mode switching
The generic timer and how it differs from SysTick
Cache hierarchy: L1 I/D, integrated L2 inside the Cortex-A7 MPCore (128 KB on i.MX6ULL — no external PL310), inner/outer shareable, MESI
MMU concepts: virtual address, page table walk, TLB, ASIDs, domains
NEON / VFP overview
Focus: MMU + privilege levels. Linux cannot exist without them.
Pages: ~22
Chapter 5 — A tour of the i.MX6ULL SoC¶
Block diagram and what each block does
Memory map: OCRAM, ROM, DDR aperture, peripheral regions
Clock tree at 30,000 ft: oscillators → PLLs → CCM → root clocks → peripheral gates
Power domains and the PMU
IOMUX: the universal-multiplexer pattern (and how
iomuxc.his generated)Boot fuses (eFuses) and BOOT_MODE pins
The reference manual: how to navigate ~5000 pages without drowning
Pages: ~18
Chapter 6 — The toolchain¶
gccis not one tool: cpp, cc1, as, collect2, ldbinutils:as,ld,objcopy,objdump,nm,readelf,strip,ar,addr2lineThe C library: glibc vs musl vs uClibc-ng vs newlib (and why bare-metal needs none of them)
ABI: EABI vs hard-float vs soft-float;
arm-none-linux-gnueabihfdecodedELF format: program headers vs section headers; what the loader actually reads
Linker scripts:
MEMORY,SECTIONS,VMAvsLMA,ENTRY,KEEPMake basics that matter: implicit rules, pattern rules, automatic variables,
.PHONY, recursive vs non-recursiveLab: compile a “hello world” for the host and for the target, compare
readelf -aoutputPages: ~24
Chapter 7 — The Boot ROM, IVT, DCD, and BootData¶
What the Boot ROM does on power-on (in order, with addresses)
Reading
BOOT_MODE[1:0]andBOOT_CFGpinsBoot device options: SD/eMMC, NAND, SPI-NOR, USB-SDP, EIM-NOR
The IVT (Image Vector Table): layout, every field decoded
The DCD (Device Configuration Data): a tiny scripting language the ROM executes to bring up DDR and clocks before your code runs
The BootData structure: load address and image length
USB-SDP (Serial Download Protocol): how
uuuandimx_usb_loadertalk to a brand-new chipHAB (High Assurance Boot) introduction (deep-dived in Ch. 124)
Focus: the DCD is the most under-explained feature of i.MX SoCs. Understand it and U-Boot SPL becomes obvious.
Pages: ~22
Chapter 8 — Hardware bring-up checklist¶
Unboxing the Point Atom board: physical inspection, jumpers, SD slot, OTG cable
Power rails to probe with a multimeter before applying power
UART1 wiring (TXD/RXD/GND), correct voltage levels, 115200 8N1
First-time SD card preparation (raw layout we will use throughout)
Optional: JTAG header pinout, OpenOCD interface adapter (FT2232H, J-Link)
The “I bricked it” recovery flow via USB-OTG SDP
Lab: prove you can reflash a bricked board purely over USB-OTG. You will need this skill.
Pages: ~12
PART II — BARE-METAL i.MX6ULL¶
This is the chapter set MCU engineers love and most Linux books skip. You will write a complete bare-metal stack from reset vector to interrupt-driven UART, in OCRAM and then DDR, with no help from U-Boot. By the end you will have built, by hand, every primitive U-Boot relies on.
Chapter 9 — First LED, pure assembly¶
Reset vector and where the Boot ROM jumps to
Setting up the SVC-mode stack in OCRAM
Enabling the GPIO clock via CCM_CCGR registers
IOMUX configuration for the LED pin (Point Atom schematic pin)
Direct GPIO data register write
Infinite loop (no return, no exit)
Building with
as, linking withld, image withobjcopy -O binaryLab: LED blinks. Image is < 1 KB. You compiled it from
.Sfiles only.Pages: ~16
Chapter 10 — C + startup.S + linker script¶
Why pure C cannot run yet: who sets the stack pointer, who clears
.bss, who copies.data?startup.S: stack init,.bsszero,.datacopy from LMA to VMA, branch tomainA complete linker script:
MEMORYblock,SECTIONS,_etext/_sdata/_edata/_sbss/_ebsssymbolsThe
.init,.text,.rodata,.data,.bssregions and why each exists__attribute__((section(...)))and when to reach for itMakefile that compiles
.Sand.ctogether, links, makes raw binaryLab: LED blink, now from
main()in C. Inspectreadelf -Sandobjdump -hto see your sections.Pages: ~20
Chapter 11 — Hand-building a Boot ROM-acceptable image¶
Recap of Ch. 7: what the Boot ROM expects to find at offset 0x400 of the boot media
Writing a
.cfgfile formkimage -T imximage— but first, do it manually so we know what the tool generatesWalking the IVT byte-by-byte in a hex editor
Writing a DCD script that initializes SDRAM controller for DDR3 (we’ll use NXP’s tested values for now; we derive them ourselves in Ch. 14)
Padding and offsets for the SD card layout: where the IVT lives, where the image lives
dd if=image.imx of=/dev/sdX bs=1k seek=1Lab: an image you built with no
mkimage, byte by byte, boots and blinks the LEDPitfall: off-by-one in the IVT
selfpointer is the #1 reason boards “do nothing”Pages: ~22
Chapter 12 — UART driver and printf¶
UART1 register map: USR1, URXD, UTXD, UCR1–4, UFCR, UBIR, UBMR
Computing the baud divisor from the module’s input clock
Polling-mode TX/RX
Implementing a minimal
_putchar()so we can hook upprintfA 200-line
printfclone (or use a tiny third-party one likempaland/printf) — we explicitly avoid pulling in newlibLab: debug-via-printf working over UART; replace LED blinks with status messages
Pages: ~18
Chapter 13 — CCM clock tree bring-up¶
The clock tree, drawn end-to-end for i.MX6ULL: 24 MHz XTAL → ARM PLL / System PLL → root clocks → peripheral gates
CCM, CCM_ANALOG, and PMU register groups
Booting the ARM core at 696 MHz (vs the 396 MHz default)
AHB and IPG bus clocks
The CCGR gating registers: each peripheral has 2 bits, what they mean
Side topic: how to compute power for a given clock configuration
Lab: measure with an oscilloscope (or the on-chip GPT timer) that the core really is running at the rate you set
Pages: ~22
Chapter 14 — DDR3 initialization with MMDC¶
DDR3 fundamentals refresher: banks, ranks, rows, columns, CL/tRCD/tRP/tRAS
Reading your specific DDR3 chip’s datasheet (Point Atom schematic → part number)
The MMDC (Multi-Mode DDR Controller) register groups: MDCTL, MDPDC, MDOTC, MDCFG0/1/2, MDMISC, MAARCR, MAPSR, MPPDCMPR1/2, MPWLDECTRL0/1, MPDGCTRL0/1, MPRDDLCTL, MPWRDLCTL, MPMUR0…
The DDR3 initialization sequence per JEDEC: precharge all → MR2 → MR3 → MR1 → MR0 → ZQCAL
Calibration: write leveling, DQS gating, read/write delay calibration
Using NXP’s DDR Stress Tool to verify your settings before trusting them
Translating verified register values into a DCD script
Lab: code in OCRAM jumps to DRAM (
memcpyitself to 0x80000000, runs from there)Focus: this is the chapter that distinguishes you from someone who only ever used eval boards
Pages: ~30
Chapter 15 — Exceptions and the GIC¶
The ARMv7-A exception vector table (8 entries) — write it by hand
High vs low vectors (
VBARregister)The GIC v2 (Generic Interrupt Controller): Distributor + CPU Interface, register maps
Configuring an SPI (Shared Peripheral Interrupt) and an SGI (Software-Generated Interrupt)
Top-half ISR pattern in bare-metal: save context, ack the GIC, call C handler, return from exception
Lab: UART RX interrupt driven; echo back over UART
Pages: ~26
Chapter 16 — Timers (EPIT and GPT)¶
EPIT (Enhanced Periodic Interrupt Timer): a 1 ms tick
GPT (General Purpose Timer): free-running counter for delay/timestamp
Building
udelay()andmdelay()primitivesUsing GPT as a profiling tool: cycle-count any function
Pages: ~14
Chapter 17 — MMU and caches¶
Why we want the MMU on, even in “bare-metal”: cache control
Translation regimes: TTBR0/TTBR1, 32-bit short-descriptor format
Building a first-level page table that maps SoC peripherals as Device memory and DRAM as Normal Cacheable
Domains and access permissions
Enabling the I-cache and D-cache (
SCTLRbits)Cache maintenance: invalidate, clean, clean+invalidate by VA, set/way
Measuring the performance difference with the GPT
Focus: you now possess every primitive Linux needs from firmware. Carry this fact through Part III.
Pages: ~26
Chapter 18 — Optional bare-metal peripherals¶
I²C (I2C1) to an EEPROM on the board
SPI to an external flash
LCD via eLCDIF — bring up the framebuffer manually, draw a pattern
Why we stop here and move to U-Boot
Pages: ~22
Chapter 18A — Supplementary: Project organization the “STM32-style”¶
Once a bare-metal program crosses ~500 lines, the single-file layout we used through Ch 14 stops scaling. This chapter refactors our work-so-far into proper header/source separation, register-definition macros in a single imx6ull.h, BSP folder layout (bsp/clk/, bsp/gpio/, bsp/uart/, …), and a top-level Makefile that builds the BSP and links it against main.c. Also discusses the NXP SDK’s MCIMX6Y2.h struct-based register approach as an alternative — when to adopt it, when to stay hand-rolled.
Focus: the moment you have ≥2 peripherals, organization saves more time than it costs
Lab: refactor Chapters 9–17 into a
bsp/tree; rebuild and confirm bit-identical binariesPages: ~14
Chapter 18C — Supplementary: Bare-metal RTC (SNVS)¶
The SNVS (Secure Non-Volatile Storage) is the only always-on domain on the chip; its 32-bit second counter survives main-power-off. We initialize it, set the time, sleep the rest of the SoC, wake it, read it back. Then a small sidebar on how this maps to Linux’s struct rtc_class_ops in Chapter 48.
Lab: print the wall clock every second across a deliberate brown-out
Pages: ~10
PART III — U-BOOT, DEEPLY¶
We now switch to using U-Boot — but only after re-implementing, by hand, everything it does. You will read U-Boot’s source and recognize every step.
Chapter 19 — U-Boot from source, first boot¶
Cloning mainline U-Boot (
git.denx.de)The directory layout:
arch/,board/,cmd/,common/,drivers/,lib/,include/configs/make mx6ull_14x14_evk_defconfig && make— what each step producesOutput artifacts:
u-boot.bin,u-boot.imx,SPL,u-boot-dtb.imx,MLOBurning to SD, booting, getting the
=>promptFirst commands:
printenv,bdinfo,md,mw,mtest,mmc infoLab: boot U-Boot, dump the DDR pattern with
md, compare to your bare-metal expectationsPages: ~16
Chapter 20 — U-Boot SPL: the missing link¶
Why SPL exists at all: OCRAM is 128 KB; full U-Boot is bigger; DDR isn’t up yet
SPL is your Chapter 14 productized
arch/arm/mach-imx/spl.c,board/freescale/mx6ull_14x14_evk/MX6ULL_*.cfgReading the SPL DDR setup and mapping it to MMDC registers you already know
The IVT/DCD-vs-SPL choice: when does U-Boot use DCD, when does it use SPL?
Focus: by mapping SPL onto Ch. 14, you remove the last bit of magic
Pages: ~18
Chapter 21 — U-Boot internals¶
The boot flow:
_start→reset→lowlevel_init→_main→board_init_f→ relocation →board_init_r→main_loopThe two
board_inithalves and why there are twoRelocation: copying U-Boot from its load address to high DRAM, fixing up GOT
The U-Boot environment: where it lives (mmc / SPI flash / NAND), how
saveenvworksThe command system:
U_BOOT_CMD(), howprintenvfinds commands at link timeThe driver model (DM):
UCLASS_*,udevice,driver, parse-time vs runtimeLab: add a custom command
hellothat runs from the U-Boot promptPages: ~26
Chapter 22 — Porting U-Boot to a custom board¶
Forking
mx6ull_14x14_evkinto your ownboard/<yours>/New defconfig, new device tree (
arch/arm/dts/imx6ull-yours.dts)Changing pinmux for your LEDs, buttons, MAC PHY
Re-running DDR Stress Tool with your DRAM and updating the SPL DDR config
Boot and verify
Lab: even if you use the Point Atom board, pretend it’s a custom one — change the model string, hostname, default bootcmd
Pages: ~22
Chapter 22A — Supplementary: Building i.MX6ULL U-Boot from nothing¶
Create every architecture, board, driver, Device Tree, and configuration file for a teaching i.MX6ULL platform
Expose the complete direct-register UART, clock, timer, watchdog, pin-routing, and DDR initialization code
Build the Boot ROM IVT and DCD image, write it to SD, and follow the boot path to the first prompt
Write UART1, GPT1, and USDHC2 PIO drivers directly from the i.MX6ULL register descriptions
Explain every defconfig option, build artifact, test command, and failure checkpoint
Lab: recreate i.MX6ULL support under a teaching architecture without selecting the existing i.MX6 platform
Pages: ~76
Chapter 23 — bootcmd, bootargs, FIT images¶
bootm,bootz,booti— what each expectsThe kernel cmdline syntax:
console=,root=,rootfstype=,rw,ip=,nfsroot=,init=FIT (Flattened Image Tree): kernel + DTB + initramfs in one signed bundle
mkimage -f kernel.its kernel.itbWhy FIT replaces uImage for modern systems
Lab: boot kernel with three different
bootargs(NFS root, ramdisk root, SD root) without recompiling anythingPages: ~18
Chapter 23A — Supplementary: Multi-variant FIT images and DT overlays at runtime¶
In modern shipping products one binary often serves several board variants (different displays, different I/O headers, different sensors). The mainline pattern is one FIT image carrying multiple DTBs, plus optional DT overlays applied at boot time based on a strap pin or an EEPROM-read variant ID.
Building a FIT with
images { kernel { ... } fdt-1 { ... } fdt-2 { ... } } configurations { conf-rev-a { ... } conf-rev-b { ... } }U-Boot
bootmselecting#conf-rev-afrom the cmdlineDT overlays applied by U-Boot
fdt applyReading a variant ID from EEPROM at boot (the
i2c md→setenv variant→bootmchain)Lab: one image boots correctly on three different “virtual variants” (LCD enabled, LCD disabled, alt-I²C address) selected by a U-Boot env var
Pages: ~14
Chapter 24 — Workflows: TFTP, NFS, USB-OTG¶
Iterating fast: don’t reflash, network-boot
Setting up
tftpd-hpaon the hostSetting up
nfs-kernel-serverand exporting the rootfsA canonical “edit, build,
makeinstall to NFS, reboot board” loopRecovery flow: USB-OTG SDP if SD/eMMC is corrupted
Pages: ~14
PART IV — THE KERNEL¶
The kernel is large but knowable. We boot mainline first; vendor BSPs come later, as a comparison exercise.
Chapter 25 — Building mainline Linux for i.MX6ULL¶
git clone git.kernel.org/.../linux.gitmake ARCH=arm imx_v7_defconfigThe build artifacts:
vmlinux,Image,zImage,arch/arm/boot/dts/*.dtbvmlinuxvszImage: who decompresses, when, whereModules:
make modules && make modules_install INSTALL_MOD_PATH=...Lab: produce a
zImageandimx6ull-14x14-evk.dtbthat match the U-Boot you builtPages: ~16
Chapter 26 — Booting the kernel from U-Boot¶
Loading via TFTP into RAM:
tftp 0x80800000 zImage; tftp 0x83000000 imx6ull.dtbbootz 0x80800000 - 0x83000000The first 30 lines of kernel boot log — every line decoded
“Uncompressing Linux… done, booting the kernel.” — where in the kernel source this happens (
arch/arm/boot/compressed/head.S)Pages: ~14
Chapter 27 — Device Tree: the contract between firmware and kernel¶
What problem DT solves (no more board-files, no more #ifdefs)
DTS syntax: nodes, properties, phandles, labels, references
.dtsivs.dts, includes, overlayscompatiblestrings — the single field that determines which driver bindsreg,interrupts,clocks,pinctrl-0,status— the universal propertiesWalking
imx6ull.dtsi→imx6ull-14x14-evk.dtsend-to-enddtcand thedtbs_checkflow against YAML bindingsWriting your first overlay to add a new I²C device
Focus: for an MCU engineer this is the largest mental shift. Spend extra time here.
Pages: ~30
Chapter 27A — Supplementary: DT bindings YAML + dt_binding_check¶
A 2018+ mainline-hygiene requirement. Since kernel v4.18 every new device-tree binding must ship a YAML schema and pass make dt_binding_check. Without it, your patch will not be accepted upstream. Without it, your binding can drift silently between board variants and you will not know.
Why JSON-Schema for DT bindings (vs the old
.txtfiles)A binding for a custom node, written from scratch, validated
make dt_binding_checkandmake dtbs_check— what each doesCommon errors and how to read them
Lab: write a binding for the Chapter 39 LED driver; pass
dt_binding_check; deliberately break a property and watch the error firePages: ~14
Chapter 28 — Kernel startup, traced¶
start_kernel()— read it function-by-functionsetup_arch(),setup_machine_fdt(), memblock, paging_init, mm_initrest_init()→kernel_initthread →run_init_process("/sbin/init")kthreadd, the idle thread, init’s pid 1Where
printkring buffer lives; how early-boot printk works before serial drivers existPages: ~24
Chapter 29 — Initramfs from scratch¶
What an initramfs is (cpio archive, not a filesystem image)
Building a one-binary initramfs: a single statically-linked program that prints “hello” and
reboot()sThen a BusyBox initramfs with a real shell
Embedding the initramfs in the kernel image vs loading it separately
The handoff: kernel mounts initramfs as
/, runs/initLab: boot to a shell with literally one file (
/init) in the rootfs. Nothing simpler exists.Pages: ~16
Chapter 30 — Kernel configuration deep-dive¶
make menuconfig— but reading the.config, not clicking blindlyThe big knobs:
CONFIG_PREEMPT*,CONFIG_HZ,CONFIG_TICK_ONESHOT,CONFIG_NO_HZ,CONFIG_HIGH_RES_TIMERSTracing, debug, lockdep options
Module vs built-in: when does it matter?
Generating your own custom
defconfigand saving it underarch/arm/configs/Pages: ~18
Chapter 30A — Supplementary: Kernel lifecycle — mainline, stable, LTS, vendor BSPs¶
The decision framework most readers never see laid out explicitly. Six release tracks, each with different stability/feature/security guarantees:
Mainline (Linus’s tree, ~weekly rc, 9-week cycle)
Stable (Greg KH’s tree, fixes only, ~1 month lifetime per minor)
Long-Term Support (LTS) (selected mainline releases get fixes for 2 or 6 years)
Vendor BSP (NXP, ST, TI; usually a frozen mainline + thousands of patches)
Yocto/Buildroot-curated (a vendor BSP plus a layer or two of patches, retargeted)
Distribution kernels (Debian, Ubuntu, Fedora; not for embedded targets in general)
When each is right: dev, shipping a product, shipping into critical/long-life environments
The migration cost of a vendor BSP — pinned forever to that minor version’s API
How to read a kernel release announcement and decide what it means for you
Why old kernel forks are a trap: a 2017-era kernel has missed eight years of security fixes
Lab: decide-and-defend exercise — given three product scenarios (consumer toy, factory PLC, medical device), pick a kernel track for each and write the argument
Pages: ~16
PART V — ROOT FILESYSTEM & USER SPACE¶
Chapter 31 — A root filesystem, by hand¶
The FHS (Filesystem Hierarchy Standard) cheat sheet
Building BusyBox from source, statically linked
Creating
/bin,/sbin,/etc,/dev,/proc,/sys,/tmp,/var,/root,/libA minimal
/etc/inittab,/etc/init.d/rcS,/etc/fstab,/etc/passwd,/etc/group,/etc/profileExporting via NFS, mounting from kernel cmdline
root=/dev/nfsLab: boot, get a shell, run
ls /,ps,mountPages: ~22
Chapter 32 — /proc, /sys, devtmpfs¶
/proc— the original process FS, now also a kernel-info FSUseful files:
/proc/cpuinfo,/proc/meminfo,/proc/interrupts,/proc/iomem,/proc/devices,/proc/<pid>/maps,/proc/<pid>/status/sys— the modern device model surface/sys/class/gpio/,/sys/bus/i2c/devices/,/sys/devices/platform/devtmpfsvs static/devvsmdevvsudevPages: ~18
Chapter 33 — Init systems¶
BusyBox
init: minimal, inittab-basedsysvinit/ OpenRC: the classical worldsystemd: services, sockets, targets, journals — a brief sober tourWhy an embedded target may not want any of these
Pages: ~14
Chapter 34 — libc, dynamic linking, and the loader¶
glibc vs musl vs uClibc-ng (size, license, compatibility)
ELF dynamic linking: PLT, GOT,
LD_LIBRARY_PATH,RPATHldd,readelf -d,LD_DEBUG=files/lib/ld-linux-armhf.so.3— what it actually doesStatic linking, when to use it on embedded
Lab: rebuild BusyBox dynamically against musl, compare image sizes
Pages: ~18
Chapter 35 — Buildroot, after you can do it by hand¶
Why Buildroot exists; what it really automates
make menuconfigfor BuildrootReading
output/build/andoutput/target/— recognizing every step you already did in Ch. 31Adding a custom package
Comparing the generated rootfs against your hand-built one
Pages: ~20
Chapter 35B — Supplementary: Read-only rootfs + overlayfs (the industrial pattern)¶
Every shipping industrial product does this. A read-only rootfs means: power-cycle anywhere, corrupt nothing. The overlayfs trick lets /var/log/, /etc/, and other writable subtrees live in tmpfs (lost on reboot, by design) or on a separate persistent partition.
Mounting
ext4withro; what fails (/etc/resolv.conf,/var/run/utmp,/tmp)overlayfsmount syntax:lowerdir,upperdir,workdir/etc/fstabfor an RO root + writable overlaysThe data-partition split:
/data/for app data,/var/log/for logsPower-cycle test: 100 reboots, mid-write, no corruption
Lab: convert a Buildroot rootfs to RO-with-overlays; survive 100 randomly-timed power yanks
Pages: ~16
Chapter 35C — Supplementary: Container runtimes on embedded (Podman + OCI)¶
Increasingly, shipping products use containers to isolate the application from the base system — so the same vendor BSP can host many app updates without rebuilding the rootfs. We bring up rootless Podman on the i.MX6ULL, run a tiny Alpine container, talk to a host GPIO from inside it.
Why containers on embedded: app/OS split for OTA, sandboxing, reproducibility
Podman vs Docker on small devices (footprint, rootless)
Kernel namespaces (
CONFIG_USER_NS, etc.) and what your kernel needsBind-mounting
/sys/class/gpiointo a container to talk to hardwareOCI image format; how to build one without docker on the host
Lab: boot an Alpine container on the board; from inside, blink a host LED via sysfs
Pages: ~16
Chapter 35A — Supplementary: Ubuntu-base rootfs as a peer to BusyBox/Buildroot¶
For projects where binary size is not the constraint but familiarity is (engineers used to apt-get install on their dev machines), an Ubuntu-base rootfs gives you a fully-fledged Debian-family userland on the target — apt, bash, full coreutils, glibc. We unpack ubuntu-base-22.04-arm.tar.gz, chroot into it under qemu-user-static to install packages on the host, then NFS-mount it from the target.
When to choose this vs BusyBox (size, cold-start) or Buildroot (reproducibility)
The
chroot+qemu-user-statictrick for installing target packages from the hostDHCP and
apton the targetLab: boot a target with a 600 MB Ubuntu-base rootfs and
apt install htopfrom the board itselfPages: ~16
Appendix — Userspace tooling reference¶
One table per tool family (networking, audio, BT, cellular, sensors, debug, …) with both Ubuntu-base
apt installand BuildrootBR2_PACKAGE_*lines“Install everything” cheat sheets (Ubuntu-base one-liner, Buildroot Kconfig fragment)
Per-Part subsets so a reader doing only Part VI/VII/VIII can install just what they need
Pitfalls (libgpiod v1↔v2 CLI, missing WiFi firmware, mesh-vs-classic BlueZ, etc.)
Pages: ~10
PART VI — DRIVER DEVELOPMENT¶
This is the longest and most lab-heavy Part. Each driver chapter has the same six-section shape: hardware, DT binding, driver code, user-space test, “what if I remove line X” experiment, pitfalls.
Chapter 36 — Your first kernel module¶
The Loadable Kernel Module (LKM) build system:
obj-m, Kbuild,KDIRmodule_init,module_exit,MODULE_LICENSE,MODULE_AUTHORprintklog levels, dmesginsmod,rmmod,lsmod,modinfo,depmod,modprobeCross-compiling against the kernel tree you built in Ch. 25
Lab: load a module that prints “hello from $current->comm”
Pages: ~16
Chapter 37 — Character device drivers¶
The classical chardev pattern
register_chrdev_region/alloc_chrdev_regionstruct cdevandcdev_addstruct file_operations:open,release,read,write,llseek,unlocked_ioctlcopy_to_user/copy_from_user— and why you can’t just memcpyA 200-line driver exposing a software FIFO over
/dev/myfifoPages: ~22
Chapter 38 — Auto device nodes (class + device)¶
class_create,device_createand what udev/mdev do with/sys/class/.../ueventThe uevent protocol, hotplug
Pages: ~12
Chapter 39 — The platform driver + device tree binding¶
Why platform drivers replace the old “register a chardev manually” pattern
struct platform_driver,of_match_table,probe,removeThe bind dance: DT node + driver
compatible→probe()calledplatform_get_resource,devm_ioremap,devm_request_irqAdding your driver’s DT binding under
Documentation/devicetree/bindings/Lab: an LED platform driver bound from DT; toggling via
/sysattributeFocus: once this clicks, every kernel subsystem looks the same
Pages: ~26
Chapter 40 — The misc framework (shortcut chardev)¶
When
miscdeviceis enoughA
/dev/hwrng-style driver in 80 linesPages: ~10
Chapter 41 — Concurrency¶
The four “lock” families: atomic_t, spinlock, mutex, rwlock + sequence locks
When to use each (interrupt context vs process context)
Per-CPU variables
RCU at an introductory level
Preemption rules:
preempt_disable,local_irq_saveLockdep and how to read its splats
Pages: ~24
Chapter 42 — Sleeping, waiting, polling¶
wait_queue_head_t,wait_event_interruptible,wake_upImplementing blocking
read()poll_wait,EPOLLfrom the driver sideO_NONBLOCKsemanticsPages: ~18
Chapter 43 — Interrupts (top half, bottom half, threaded)¶
request_irq,IRQF_*flags, shared interruptsTop half: keep it short, ack the device, schedule deferred work
Bottom halves: softirq, tasklet (deprecated), workqueue, threaded IRQ
DT
interruptsproperty and theinterrupt-parentchainLab: GPIO button → IRQ → workqueue → input event
Pages: ~24
Chapter 44 — The GPIO subsystem¶
The old
gpio_request/gpio_set_valueinterface vs the newgpiod_*interfacepinctrl bindings revisited
Userspace:
/sys/class/gpio(deprecated) vsgpiochipN+libgpiodPages: ~16
Chapter 46 — I²C drivers¶
Master controller (i.MX6ULL has 4 × I²C) vs client driver split
The i.MX I²C controller (I2C1..4) register-level overview
Writing a client driver for an EEPROM or sensor (e.g., AP3216 ambient light on the Point Atom board)
DT binding for I²C devices
Using
i2c-tools(i2cdetect,i2cdump,i2cset,i2cget) to validatePages: ~22
Chapter 47 — SPI drivers¶
SPI subsystem, master + slave
ECSPI controller on i.MX6ULL
Writing a driver for an SPI flash or ADC
spidevfor quick userspace prototypingPages: ~20
Chapter 48 — PWM and RTC¶
PWM framework,
pwm_chip, sysfs/sys/class/pwm/RTC framework:
rtc_class_ops,hwclock, NTP integrationPages: ~16
Chapter 49 — IIO (Industrial I/O) for ADC and sensors¶
Why IIO replaced ad-hoc sensor drivers
Channels, triggers, buffers
A driver for the on-chip ADC1 of the i.MX6ULL
Reading from
/sys/bus/iio/devices/iio:device0/in_voltage0_rawPages: ~22
Chapter 50 — regmap¶
The pattern: every device driver was duplicating “read/modify/write a register” code
regmap_init_mmio,regmap_init_i2c,regmap_init_spiregmap_update_bits, cache types, debugfs integrationRefactor an earlier chapter’s driver to use regmap
Pages: ~14
Chapter 51 — DMA¶
The DMA-API:
dma_alloc_coherentvs streamingdma_map_singleCache coherency: why the kernel cares about direction and ownership
i.MX6ULL SDMA controller overview
A DMA-driven UART example (or audio DMA preview)
Pages: ~22
Chapter 51A — Supplementary: Watchdog driver and brown-out resilience¶
No product ships without one. The i.MX6ULL WDOG1/WDOG2 modules generate a system reset if not “kicked” within a programmable window. We write the kernel driver, plumb the user-space daemon (systemd-watchdog or busybox watchdog), and design the application-level kick policy.
The kernel
watchdogframework,struct watchdog_device,wdog_ops/dev/watchdogand theioctlinterfaceWindow timing: too-fast kicks are as bad as too-slow
Boot-time vs runtime: when does the WDOG arm?
Pre-timeout warnings for graceful shutdown
Lab: application crashes → watchdog fires → board reboots into known-good state, with a counter in SNVS RAM recording the reset cause
Pages: ~16
Chapter 51B — Supplementary: Power management — runtime PM, suspend/resume, DVFS¶
Battery-powered or thermal-constrained products demand real PM. The Linux PM core supports four orthogonal mechanisms: runtime PM (per-device idle), system suspend (whole-board sleep), DVFS (frequency/voltage scaling), and CPU idle (low-power C-states).
The PM-runtime callbacks:
runtime_suspend,runtime_resume,runtime_idlepm_runtime_get_sync/pm_runtime_putdiscipline in driversSystem sleep states:
freeze,standby,mem,disk— what’s implemented on i.MX6ULLDVFS:
cpufreqgovernors (ondemand, schedutil, conservative)The CPU idle subsystem (
cpuidle); WFI as the C1 statetickless(CONFIG_NO_HZ_IDLE) for powerLab: measure the board’s current draw at 696 MHz active, 396 MHz active, idle, and suspend-to-RAM; quantify each mode’s contribution
Pages: ~22
Chapter 52 — Network driver (FEC + KSZ8081 PHY)¶
The netdev model:
struct net_device,ndo_*ops, NAPIThe FEC (Fast Ethernet Controller) on i.MX6ULL
The PHY subsystem and MDIO
DT bindings for ethernet
Bringing the interface up, running
ping,iperf,tcpdumpPages: ~26
Chapter 52A — Supplementary: PREEMPT_RT — real-time Linux as a full chapter¶
For motion control, audio, robotics, industrial protocols, mainline Linux’s PREEMPT_RT patchset turns the kernel into a deterministic real-time kernel without giving up the Linux API. Since v6.12 most of PREEMPT_RT has been merged into mainline; the rest is in flight.
What “real-time” means precisely (worst-case latency, not “fast”)
The PREEMPT_RT design: threaded IRQs, sleeping spinlocks, rt_mutex with priority inheritance, ftrace-anchored measurement
Building a PREEMPT_RT kernel on i.MX6ULL
cyclictest— the standard latency benchmarkAffinity, isolation (
isolcpus=), and theSCHED_FIFO/SCHED_DEADLINEschedulersTuning: disable CPU-idle deeper states, pin IRQs, lock memory with
mlockallWhat still cannot meet hard-RT requirements on a Cortex-A7 single-core
Lab: baseline Linux vs PREEMPT_RT side-by-side cyclictest run under network and disk load; show the worst-case latency change
Pages: ~26
Chapter 53 — Sound (ALSA / ASoC)¶
The “Sound Open Architecture” stack
ASoC: machine driver + codec driver + platform driver
SAI controller on i.MX6ULL + WM8960 codec (typical Point Atom config)
aplay,arecord,alsamixerPages: ~24
Chapter 54 — LCD framebuffer and DRM¶
The eLCDIF controller
Legacy fbdev (
/dev/fb0) vs modern DRM/KMS (/dev/dri/card0)Writing pixels from user space; testing with
fbset,modetestTouchscreen integration (GT9147 typical Point Atom)
Pages: ~22
Chapter 54A — Supplementary: MTD / UBI / UBIFS for raw NAND¶
If your product flashes raw NAND (not eMMC), you need the MTD subsystem under the kernel and UBI/UBIFS on top for wear-leveling and bad-block management.
MTD model:
struct mtd_info,mtd_read/write/eraseThe NAND subsystem (
nand_chip) on top of MTDBad-block management (BBT, OOB layout)
UBI: volumes, wear-leveling, attach/detach
UBIFS: a journaling FS on UBI
mtdinfo,ubinfo,ubiformat,ubinizeLab: flash an entire layout (SPL → U-Boot → kernel → UBI rootfs) to NAND; power-cycle 1000 times; confirm zero bit errors after wear-leveling
Pages: ~22
Chapter 54B — Supplementary: V4L2 + GStreamer for camera input (CSI)¶
The i.MX6ULL CSI is paired with an OV5640 (typical) or OV2640 sensor. V4L2 (Video4Linux 2) is the kernel framework; GStreamer is the user-space pipeline glue.
V4L2 model:
struct v4l2_device,struct video_device,v4l2_ioctl_opsThe i.MX6ULL
imx-pxpandimx-csidriversA user-space
v4l2-ctlcapture:v4l2-ctl --stream-mmap --stream-to=...GStreamer pipeline:
v4l2src ! videoconvert ! kmssink→ fullscreen cameraThe bayer/YUV/RGB conversion path
Encoding to JPEG/H.264 on i.MX6ULL (none in HW; software encoder via
libjpeg/x264)Lab: live camera preview onto the LCD via a one-line GStreamer pipeline; then capture 30 s to a file on the rootfs
Pages: ~22
Chapter 55 — USB gadget¶
USB host vs device modes; OTG role detection
libcompositeand ConfigFS-based gadgetsA “USB serial” and a “USB mass storage” gadget walkthrough
Pages: ~18
Chapter 55A — Supplementary: Kernel timers and high-resolution timers (hrtimers)¶
Three timer APIs in the kernel: legacy timer_list (jiffies-based, ms resolution), delayed_work (workqueue-driven), and hrtimer (sub-µs, the modern default for new code). When to use which; how each is implemented under the hood; how the tick subsystem multiplexes many timers on one hardware event.
mod_timer,del_timer_sync, thetimer_list.functioncallbackhrtimer_init,hrtimer_start,HRTIMER_MODE_RELvs_ABS,_PINNEDtimer_setup(the modern, type-safe API)Lab: an
hrtimer-driven 1 kHz square wave on a GPIO, measured with a scope; compare jitter vsmdelayin a kthreadPages: ~18
Chapter 55B — Supplementary: Asynchronous notification (SIGIO / fasync)¶
The fourth I/O model after blocking/non-blocking/poll: the driver delivers a SIGIO to a registered user-space process when data is ready, so the process is “informed” without ever calling read(). fasync_helper, kill_fasync, the F_SETOWN/F_SETFL O_ASYNC user-space dance.
When SIGIO matters: legacy code, signal-driven simple loops, low-latency without busy-poll
Why epoll/io_uring have mostly replaced it for new code, and why drivers still ship it
Lab: the Chapter 45 button driver gains SIGIO; a user-space program receives signals on press
Pages: ~12
Chapter 55C — Supplementary: CAN bus and FlexCAN¶
Industrial automation runs on CAN. The i.MX6ULL has two FlexCAN controllers. The Linux SocketCAN subsystem treats CAN like a network interface: ip link set can0 up type can bitrate 500000. Then cansend, candump, can-utils.
The FlexCAN controller register map and message-buffer model
SocketCAN:
PF_CAN,SOCK_RAW,struct can_frameiproute2CAN extensions;can-utilsISO-TP, CAN-FD — what i.MX6ULL supports and what it doesn’t
Lab: loopback two FlexCANs on the same board; then bridge to an external CAN node and exchange frames
Pages: ~22
Chapter 55D — Supplementary: Block device drivers¶
Char devices are sequential; block devices are random-access in fixed-size sectors. The block-I/O layer multiplexes requests from many filesystems onto one device with elevator/scheduling policies.
struct block_device,struct gendisk,struct block_device_operationsThe
request_queue,make_request_fn, modernblk_mqmulti-queueA 4 MB RAM-disk block driver in < 200 lines
How
/dev/mmcblk0and/dev/sdalook under the hoodLab: the RAM-disk works as
mkfs.ext4target and is mountablePages: ~24
Chapter 55E — Supplementary: WIFI — wpa_supplicant, USB and SDIO dongles¶
Connecting an i.MX6ULL to Wi-Fi in 2026 is rarely an in-house driver job — you pick a chip with mainline support and stand up wpa_supplicant. We walk an RTL8188EUS USB Wi-Fi dongle (cfg80211) and an RTL8189FS SDIO module (vendor staging tree) end-to-end.
Kernel:
CONFIG_CFG80211,CONFIG_WIRELESS_EXT,CONFIG_RTL8XXXUFirmware blobs in
/lib/firmware/wpa_supplicant.conf, WPA2-PSK, hidden SSIDs, EAP-PEAPwpa_cliand theD-BusinterfaceDiagnosing “no SSID found”: antenna, country code, channel-12/13, regulatory domain
Lab: the board joins your Wi-Fi from
/etc/init.d/, gets a DHCP lease, pings the gatewayPages: ~24
Chapter 55F — Supplementary: Cellular modems (PPP, ECM/NCM, GNSS)¶
Quectel EC20-style modems present themselves as USB composite devices: PPP serial, RNDIS/ECM data, NMEA GNSS. We cover all three modes, plus quectel-CM and qmi_wwan for the QMI-based path.
USB option driver, vendor/product IDs, the
option_ids[]table indrivers/usb/serial/option.cPPP path:
pppdchat script, APN, MTUECM/NCM: a virtual Ethernet over USB;
usb0GNSS:
/dev/ttyUSB1NMEA stream →gpsd→gpsmonLab: boot, modem dials, board has internet via cellular; concurrent GPS fix logged
Pages: ~22
Chapter 55G — Supplementary: Multi-touch — MT-A, MT-B protocols, GT911¶
The Linux input subsystem evolved two multi-touch protocols: Type A (per-frame, packed) and Type B (per-slot, persistent). Most modern panels use Type B. We bring up a Goodix GT911 capacitive touch controller (5-point), the typical pairing for the Point Atom MINI’s optional LCD.
I²C wiring + interrupt + reset of GT911
input_mt_init_slots,input_mt_slot,input_report_abs(ABS_MT_POSITION_X/Y)evtestand how to read a multi-touch event streamtslibuser-space library for legacy single-touch panelsLab: five fingers tracked simultaneously, printed by
evtestPages: ~18
Chapter 55H — Supplementary: RGB-to-HDMI via sii902x¶
The i.MX6ULL has no native HDMI; a parallel-RGB-to-HDMI bridge chip (Silicon Image SiI902x) is the standard solution. The kernel has a mainline driver; the lab is wiring + DT bindings + EDID-based mode negotiation.
The bridge chip’s I²C control path vs. its parallel-RGB data path
drm/bridgemodel in the kernel;drm_bridge_addEDID reading; mode selection from a list
Lab: boot to a 1080p HDMI monitor with
westonshowing color barsPages: ~16
Chapter 55I — Supplementary: Rust-for-Linux — first kernel module¶
Since Linux 6.1, Rust is a supported language for kernel modules. As of 2026 the support is still gated to a small set of subsystems but is growing fast. We write our Chapter 36 hello-LKM in Rust, as a sidebar / supplemental experiment — not because we recommend Rust for production embedded yet, but because the reader will see it land in their next kernel update.
Why Rust in the kernel (memory safety, type-state)
rust/directory tour; how the build picks up Rust sourcekernel::prelude::*,module!{}macroCompare side-by-side with the C version from Chapter 36
What’s stable, what’s nightly, what’s blocked
Lab: the Chapter 36 LKM, rewritten in Rust,
insmods andprintksPages: ~14
PART VII — DEVICE COOKBOOK¶
Part VI taught you the kernel’s driver frameworks with one good example each. This Part is the reference: for each common device class, two or three real chips compared side-by-side, with schematics, DT bindings, driver code or kernel-driver enablement, user-space access, and the pitfalls specific to each chip.
Each chapter follows a tighter template: What it does → Compare alternatives → Wiring/schematic → DT example → Driver (existing or write) → User-space access → Lab → Pitfalls. Most chapters are ~12–14 pages. Read sequentially or hop directly to the chapter for the chip in front of you — chapters in this Part are almost entirely independent of each other.
Group A — Storage devices¶
Chapter 64 — QSPI NOR flash¶
Chips: Winbond W25Q128 / W25Q64; Macronix MX25L256; Micron MT25Q. Trade-offs (size, speed, erase granularity, security features).
The i.MX6ULL QSPI controller and its limits
DT bindings for
spi-nor;m25p80; thejedec,spi-norcompatibleMTD partitions for u-boot env, kernel, dtb, rootfs slot A/B
Erasing and writing from user-space:
mtd-utils(flash_erase,nandwrite,flashcp)XIP from QSPI — when it makes sense, when it doesn’t
Lab: partition a W25Q128, store the U-Boot env on it, boot kernel from it via FIT
Pitfalls: read-only mode after lockdown; quad-mode vs single-mode performance trap
Pages: ~16
Chapter 65 — I²C / SPI EEPROM¶
Chips: Microchip AT24C02/32/256/512 (I²C); AT25-series (SPI); 25LCxx (SPI).
The
at24kernel driver and what its DT properties really do (pagesize,read-only,wp-gpios)The
nvmemframework: where EEPROM cells become typed kernel-side accessorsWriting the MAC address from EEPROM into the FEC at probe time
Wear: page-write counting, the “spread your writes” pattern
Lab: store board serial number + MAC in an EEPROM; verify FEC picks it up
Pitfalls: I²C addressing scheme variants; the 256-byte boundary “rollover” bug
Pages: ~12
Chapter 66 — SD card and eMMC deep dive¶
The MMC subsystem; SDIO is the same machinery (Ch 91 will reuse it)
Speed modes: DS, HS, HS200, HS400 — what your eMMC and your i.MX SDHC support
CMD23 vs CMD12 multi-block read terminators
eMMC partition table: boot partitions, RPMB, GP areas
Wear-levelling: what eMMC firmware does, what UBI does, what neither does
TRIM/discard for eMMC vs SD
Lab: read out an eMMC’s “device life-time estimation” via the EXT_CSD register; measure HS200 throughput vs HS
Pages: ~16
Group B — Environmental sensors¶
Chapter 67 — Temperature / humidity / pressure¶
Chips: Bosch BME280 (T/H/P I²C+SPI); Sensirion SHT3x (T/H I²C, lab-grade); ASAir AHT20 (T/H I²C, cheap).
IIO drivers for each; how user-space reads via
/sys/bus/iio/devices/Calibration registers in BME280; why the raw reading isn’t the temperature
Polling vs trigger-driven sampling
Time-averaging in user-space: simple moving average vs exponential
Lab: record T/H/P to CSV every 10 s for 24 h; plot drift
Pitfalls: self-heating in BME280 (the device’s own current warms its sensor); the “after power-on wait 2 ms” SHT3x trap
Pages: ~14
Chapter 68 — Light & color sensors¶
Chips: Rohm BH1750 (ambient lux, I²C); AMS TSL2561 (broad-band + IR-channel, I²C); Vishay VEML7700 (low-power lux).
IIO drivers,
in_illuminance_inputinterpretationThe “logarithmic eye” — what lux means and when it lies
Bonus: AMS TCS34725 (RGB color sensor) and Avago APDS-9960 (RGB + gesture); driver and DT included
Lab: an ambient-light-driven backlight regulator in user-space
Pitfalls: TSL2561’s IR-saturation regime; lens / window choice on enclosure
Pages: ~14
Chapter 69 — Air quality, gas, particulate matter¶
Chips: Sensirion SCD30 (CO₂ NDIR, I²C); AMS CCS811 (eCO₂/TVOC, I²C); Plantower PMS5003 (PM1/2.5/10, UART); Nova SDS011 (PM2.5/10, UART); MQ-x analog series + ADC.
Why “eCO₂” is not CO₂ (CCS811 estimates from VOCs and resistance drift)
NDIR true-CO₂ measurement (SCD30); cost vs accuracy
Laser-scattering PM sensors; airflow control via fan PWM
The 24-hour warm-up problem with CCS811
Lab: a 5-channel home air-quality monitor → MQTT → Grafana
Pitfalls: MQ-x cross-sensitivity (most “MQ-2 = LPG” claims are wrong); PM-sensor fan failure detection
Pages: ~14
Group C — Motion sensors¶
Chapter 70 — I²C IMUs¶
Chips: InvenSense MPU6050 (6-axis, classic); MPU9250 (9-axis with mag); InvenSense ICM-20948 (newer 9-axis).
The IIO trigger/buffer mechanism for 6-/9-axis IMUs at 1000 Hz
DMP firmware blob in MPU6050 — what it gives you (quaternions), what it costs (closed-source, less control)
The magnetometer-as-slave-I²C-master quirk in MPU9250
Tilt-compensated heading; sensor fusion via Madgwick filter in user-space
Lab: a 100 Hz orientation tracker with a quaternion-output API
Pitfalls: stale I²C addresses (0x68/0x69); gyro bias drift after temp changes
Pages: ~16
Chapter 71 — SPI IMUs (high-rate / low-noise)¶
Chips: ST LSM6DSO (6-axis); InvenSense ICM-42688 (high-rate, low-noise); Analog Devices ADXL345 (3-axis accel only).
Why SPI for IMUs above ~400 Hz
ICM-42688’s FIFO and watermark-IRQ — sampling thousands of points per second efficiently
ODR / range / filter settings
Lab: capture 1 kHz vibration data; FFT analysis for resonance detection
Pitfalls: SPI mode 3 vs mode 0 confusion; ODR vs filter-cutoff mismatch
Pages: ~14
Group D — Position & distance¶
Chapter 72 — Distance & proximity¶
Chips: STMicro VL53L0X (ToF laser, I²C); VL53L1X (longer range); HC-SR04 (ultrasonic GPIO); Sharp GP2Y0A (IR analog).
The VL53L0X firmware-loading dance
ToF lighting/glass surface failure modes
HC-SR04 vs GPIO timing — why this is hard on Linux without a high-precision timer or PRU
Lab: an “approach detector” with 3 different sensors compared side-by-side
Pitfalls: VL53L0X bus collisions (I²C address conflicts); HC-SR04 +5 V level-shifting
Pages: ~14
Chapter 73 — Magnetometer / compass¶
Chips: Honeywell HMC5883L (I²C, common but EOL); QST QMC5883L (cheap clone); Memsic MMC5983MA (newer, lower noise).
Hard-iron and soft-iron calibration; why your compass points 23° wrong from the start
IIO
magn_x/y/zoutput and thescaleattributeLab: a calibrated digital compass with tilt compensation
Pitfalls: mounting the magnetometer near a buck regulator destroys readings
Pages: ~12
Chapter 74 — Hall-effect & rotary position sensors¶
Chips: AMS AS5048A (magnetic rotary, SPI/I²C); Allegro A1324 (linear Hall analog); Infineon TLE5012 (high-rate angular sensor).
Off-axis vs on-axis sensor placement
The IIO
anglchannel and absolute-position rotary encodersHigh-rate angular sampling for motor commutation
Lab: an absolute-angle joystick using AS5048A
Pitfalls: magnet-distance sensitivity (1 mm changes everything); diametric vs axial magnets
Pages: ~12
Group E — Power & current¶
Chapter 75 — Current and power monitoring¶
Chips: TI INA219 (low-side, I²C); INA226 (improved, calibration); INA3221 (3-channel); Allegro ACS712 (Hall analog).
Shunt selection: heat, accuracy, layout
INA226 calibration register — why “current_lsb = max_current / 32768”
3-rail simultaneous monitoring via INA3221
Lab: a hot-plug power meter with 100 µA resolution
Pitfalls: shared-ground violations; the “ACS712 zero point isn’t 2.5 V” calibration
Pages: ~12
Chapter 76 — Battery fuel gauge + charger¶
Chips: Maxim MAX17048 (1-cell fuel gauge, I²C); TI BQ27441-G1; TP4056 (cheap charger); MCP73833; TI BQ24074 (path-managed charger).
The battery-empty problem and SoC (state-of-charge) estimation
Charger thermal regulation and JEITA temperature profiles
The
power_supply_classkernel framework;/sys/class/power_supply/Lab: a Linux laptop-style battery indicator on a Li-ion-powered i.MX6ULL device
Pitfalls: TP4056 mass-production cells without temperature monitoring; SoC drift over months
Pages: ~14
Group F — Specialty sensors¶
Chapter 77 — 1-Wire sensors¶
Chips: Maxim DS18B20 (digital thermometer, 1-Wire); DHT11 / DHT22 (single-wire T/H, not true 1-Wire); DS2480B (1-Wire master).
The kernel
w1subsystem and its slave driversWhy DHT11/22 is a timing-critical mess on a non-RT Linux (and why dedicated MCU helper is sometimes the answer)
Parasitic-power mode for DS18B20
Lab: a 10-sensor temperature bus with one GPIO and a long cable
Pitfalls: the “USB-Serial DS9097 emulator does the parity tricks for you” pattern
Pages: ~14
Chapter 78 — MEMS microphones (I²S)¶
Chips: InvenSense INMP441 (I²S MEMS); ICS-43434 (lower noise); SPH0645 (similar).
ASoC DAI for I²S microphones; sampling via
arecordStereo via two mono mics; beamforming basics
Lab: record 48 kHz stereo from two INMP441s; visualize FFT in real-time
Pitfalls: WS line ringing on long cables; PDM vs I²S confusion
Pages: ~12
Chapter 79 — Health sensors (HR / SpO2)¶
Chips: Maxim MAX30100 (basic); MAX30102 (improved, smaller); MAX30105 (with particle-sensing channel).
PPG (photoplethysmography) raw signal, IR vs red channel
Heart-rate extraction in user-space; SpO2 calibration constants
Lab: a finger-clip pulse oximeter with continuous HR/SpO2 over MQTT
Pitfalls: motion artifacts (this is why a Fitbit’s HR is noisy when you walk); SpO2 accuracy claims
Pages: ~12
Group G — Analog conversion & clock generation¶
Chapter 80 — External ADCs¶
Chips: TI ADS1115 (16-bit I²C); ADS1256 (24-bit SPI low-noise); Microchip MCP3008 (10-bit 8-ch SPI cheap); Analog Devices AD7606 (16-bit 8-ch parallel sampling, true simultaneous).
IIO drivers; sample rate vs ENOB trade-offs
AD7606’s true-simultaneous sampling for power-quality / vibration
Ratiometric measurements (load cell, RTD)
Lab: 8-channel strain-gauge logger with AD7606
Pitfalls: input filter loading; reference-voltage stability
Pages: ~14
Chapter 81 — External DACs + clock generators¶
Chips: Microchip MCP4725 (12-bit I²C DAC); Analog Devices AD5663 (16-bit SPI DAC); SiLabs Si5351 (programmable 3-output clock generator); CDCE9xx series.
IIO
out_voltage*for DACsSi5351 — generating arbitrary clocks for SDR or for chip evaluation
The kernel
clkframework integration with external clock chipsLab: signal generator + clock chain for an SDR front-end
Pitfalls: PLL closed-loop stability; Si5351’s “frequency = output * 2^N / M” config nightmare
Pages: ~12
Group H — Displays¶
Chapter 82 — RGB parallel LCD on LCDIF¶
Panels: ATK4384 (4.3” 480×272); ATK7016 (7” 1024×600); ATK10261 (10.1” 1280×800).
The DPI / RGB-parallel timing model: pixel clock, hsync, vsync, porches
panel-simpleand how to add a custom panelDRM/KMS basics for our case; the
imx-lcdifdriverBacklight via PWM +
pwm_blLab: add a custom 5” 800×480 panel; modetest at full refresh rate
Pitfalls: pixel-clock too fast → tearing; reversed RGB swap on the connector
Pages: ~18
Chapter 83 — SPI LCD¶
Panels: ST7789 (240×320, 16-bit/18-bit color); ILI9341 (320×240); ILI9488 (320×480).
mainline
panel-mipi-dbidriver + DRM tiny architecturefbtft legacy and why we don’t use it for new designs
Tearing-effect output and synchronized updates
Lab: a 320×240 ST7789 from
cat /dev/fb0in 30 lines of DTPitfalls: SPI clock ceiling vs panel refresh; backlight-PWM bleed
Pages: ~16
Chapter 84 — QSPI LCD¶
Panels: GC9D01, ST77916, Sitronix SF055A; round LCDs popular for smart watches
Quad-SPI mode in the i.MX6ULL QSPI controller
The high-bandwidth bytes-per-frame budget
mipi-dbi-spi quad-mode driver
Lab: drive a 240×240 round display at 60 fps with a system load of < 5 %
Pitfalls: quad mode timing margins; data-order swap
Pages: ~12
Chapter 85 — OLED & e-paper¶
Panels: SSD1306, SH1106 (small OLED I²C/SPI); SSD1322 (large yellow OLED); SSD1680 (2.9” e-paper); IT8951 (large e-paper controller).
ssd1307fblegacy framebuffer driver;repaper(e-paper DRM driver)e-paper refresh modes: full vs partial; ghosting
Lab: a desk indicator + a weather e-paper status panel
Pitfalls: OLED burn-in; e-paper partial-refresh ghost mitigation
Pages: ~14
Chapter 86 — Touch input ICs¶
Chips: Vishay TTP223 (single-key cap, GPIO output); NXP MPR121 (12-key cap, I²C); XPT2046 (4-wire resistive touch, SPI).
Adding capacitive touch buttons to a Linux input device via
gpio-keysMPR121 IIR baseline tracking; setting touch/release thresholds
4-wire resistive touch + ADS7846/XPT2046 kernel driver; calibration via
xinput_calibratorCompare with GT911 multi-touch (covered in Ch 55G — separate chip class)
Lab: retrofit a resistive panel to a 4.3” RGB LCD; calibrate in 60 s
Pitfalls: XPT2046’s interrupt-pin pulldown; resistive panels’ temperature drift
Pages: ~14
Group I — Cameras¶
Chapter 87 — Parallel CSI cameras¶
Sensors: OmniVision OV7725 (0.3 MP); OV5640 (5 MP, parallel mode); OV2640 (2 MP); GalaxyCore GC2145 (2 MP).
The i.MX6ULL parallel CSI (no MIPI-CSI on this SoC)
V4L2 sensor sub-device model
Sensor-side: PLL, clocks, exposure, gain, white balance, autofocus (OV5640)
IPU/CSI capture pipeline
Lab: a GStreamer pipeline grabbing 30 fps QVGA video from OV5640
Pitfalls: the 5 MP “still mode” of OV5640 vs streaming; FOV vs lens choice
Pages: ~18
Chapter 88 — USB UVC cameras¶
The
uvcvideodriver; what works mainstream, what doesn’tBandwidth budgeting on USB 2.0 (480 Mbps theoretical; ~30 MB/s practical) — implications for resolution × frame rate
MJPEG vs YUYV vs H.264 modes
Lab: capture from a standard webcam; record 1080p30 to disk
Pitfalls: bandwidth contention with USB WiFi or USB Ethernet; non-UVC “drivers needed” cameras
Pages: ~10
Group J — Audio devices¶
Chapter 89 — I²S audio codecs¶
Chips: Cirrus WM8960 (used on many i.MX boards); SGTL5000 (NXP); Everest ES8388 (cheap, ESP32 favourite); TI TLV320AIC3104; Realtek ALC5640.
ASoC machine + codec + cpu-DAI triplet (referenced in Ch 53)
Differences: built-in DAC quality, PGA capability, mic-preamp
Headphone-jack detection
Lab: a stereo MP3 + line-in voice-loopback program
Pitfalls: master/slave clock-mode mismatch with i.MX SSI; sample-rate clock-derivation
Pages: ~16
Chapter 90 — Digital class-D amplifiers¶
Chips: TI TAS5805M (I²C-controlled DSP amp); Maxim MAX98357A (pure I²S, no I²C); TI PCM5102A (audio DAC + headphone).
The “amp without a codec” pattern: send raw I²S, amp does the rest
TAS5805M’s DSP coefficients (EQ, compression, dynamic-range control)
The Bluetooth-speaker product pattern: A2DP source → I²S → MAX98357A
Lab: add a tunable EQ to a Bluetooth speaker
Pitfalls: ground-loop hum in class-D speakers; TAS5805M’s I²C dance during startup
Pages: ~12
Group K — WiFi modules¶
Chapter 91 — SDIO WiFi¶
Modules: AP6212 (Broadcom-based, used on Point Atom boards); Realtek RTL8189FTV; Marvell SD8801.
The
brcmfmacdriver for Broadcom;rtl8xxxuand out-of-treertl8189esFirmware loading and per-board NVRAM file
SDIO-WiFi clock and pin signal-integrity issues
Lab: boot Linux with WiFi-only networking; iperf3 over WiFi at 30+ Mbps
Pitfalls: AP6212 + Realtek firmware blob version mismatch; NVRAM regulatory-domain
Pages: ~18
Chapter 92 — USB WiFi¶
Modules: Realtek RTL8188EUS (cheap dongles); MediaTek MT7601; Ralink RT5370.
rtl8xxxu(mainline) vs out-of-treertl8188eus(better)Driver kernel-version compatibility hell
Bandwidth contention vs USB Camera
Lab: swap WiFi between three USB dongles; compare throughput and latency
Pitfalls: out-of-tree DKMS pain; ralink stale signal
Pages: ~14
Chapter 93 — Hosted WiFi via ESP32 / ESP8266¶
Modules: ESP32 (esp-hosted firmware); ESP8266 (AT-command firmware).
The
esp-hosteddriver: SPI/UART transport, Linux sees a normalwlan0AT-command mode: ESP as a TCP/IP offload engine
When this beats SDIO: legacy SoCs without SDIO, MCU+Linux co-existence
Lab: Linux WiFi via ESP32 over a single UART; iperf3 measurements
Pitfalls: AT-command flow control; esp-hosted firmware version pinning
Pages: ~14
Chapter 94 — WiFi+BT combo modules¶
Modules: AP6212 (BCM43438 — combined); Realtek RTL8723BS (SDIO combo).
The shared-antenna problem and PTA (packet-traffic arbitration)
Bringing up both Wi-Fi and BT simultaneously on a shared module
BT-over-UART + Wi-Fi-over-SDIO on the same chip
Lab: Wi-Fi tethering + BLE peripheral on the same module
Pitfalls: BT/Wi-Fi co-channel coexistence (2.4 GHz crowded)
Pages: ~12
Group L — Bluetooth & mesh¶
Chapter 95 — HCI BLE over UART/USB¶
Modules: Nordic nRF52 with Zephyr HCI firmware; Broadcom BCM4343 BT; CSR8510 USB.
The Bluetooth HCI specification; how Linux’s
blueztalks HCI over UART or USBhciattach,btmgmt,bluetoothctlBLE central vs peripheral; GATT services
Lab: a BLE peripheral that exposes a custom GATT service for temperature/humidity
Pitfalls: UART flow-control; HCI command/event timing
Pages: ~16
Chapter 96 — AT-command BLE modules¶
Modules: HM-10 (CC2540 with AT-command firmware); HC-08; JDY-08.
The “easy” path: BLE without a kernel stack — UART AT commands
Limitations: max throughput, no real GATT control, vendor-specific quirks
When this is the right choice (legacy, simplicity, BOM cost)
Lab: a UART-based “BLE serial port” data link in user-space
Pitfalls: firmware variant differences (HM-10 cloned five ways)
Pages: ~10
Chapter 97 — BLE Mesh¶
BLE Mesh protocol layers; provisioning, addressing, models
bluez-meshdaemon and themeshctltoolSample mesh networks: 20-node room lighting controller
Lab: provision and control 5 mesh nodes from a Linux gateway
Pitfalls: provisioning friction; mesh-traffic flood prevention
Pages: ~16
Group M — Long-range & specialty wireless¶
Chapter 98 — LoRa¶
Chips: Semtech SX1278 (legacy, 433/868/915 MHz); SX1262 (current); LLCC68 (cheap SX1262 alternative); EByte E22 modules.
The kernel
sx127xdriver and user-space LoRa stacksLoRaWAN vs LoRa-P2P; ChirpStack vs The Things Network
Spreading factor, bandwidth, coding rate trade-offs
Lab: a 1 km point-to-point LoRa link with retries and acknowledgement
Pitfalls: frequency-band regulation per region; antenna SWR
Pages: ~16
Chapter 99 — Sub-GHz proprietary¶
Chips: Nordic nRF24L01 (2.4 GHz proprietary, 250 kbps – 2 Mbps); TI CC1101 (sub-GHz multi-band); CC1200.
When nRF24L01 beats BLE (raw throughput, latency, multi-PRX one-PTX hub)
CC1101 for sub-GHz remote controls and “smart” home devices
Lab: a 50-node star-topology nRF24L01 sensor network
Pitfalls: crystal-frequency mismatch tank-circuit issues; ack-timing in nRF24
Pages: ~12
Chapter 100 — ZigBee / Thread / 802.15.4¶
Chips: TI CC2530 (legacy ZigBee, Z-Stack); Nordic nRF52840 (modern, OpenThread); Silicon Labs EFR32MG.
ZigBee / Thread / 802.15.4 mesh tech compared
The CC2530 as ZNP (ZigBee Network Processor) → Linux runs zigbee2mqtt
nRF52840 OpenThread Border Router for the Matter ecosystem
Lab: a 10-node ZigBee mesh in zigbee2mqtt + Home Assistant
Pitfalls: Thread certification and credential setup; ZigBee profile compatibility
Pages: ~16
Chapter 101 — UWB ranging¶
Chips: Qorvo (Decawave) DWM1000; DWM3000 (newer, AirTag-compatible UWB IC); NXP NCJ29D5.
Time-of-flight ranging principle; centimeter-accuracy indoor positioning
Two-way ranging vs TDoA
iPhone / AirTag UWB ecosystem and FiRa standard
Lab: 3-anchor 2D position estimation with sub-10-cm RMSE
Pitfalls: antenna delay calibration; multipath in real environments
Pages: ~14
Group N — Cellular¶
Chapter 102 — USB 4G LTE modems¶
Modules: Quectel EC20, EC25; SimCom SIM7600; Telit LM940.
QMI vs MBIM vs RNDIS modes (how the modem appears to Linux)
ModemManager + nm-connection
Multi-band, LTE Cat-4 vs Cat-6 vs CA
Lab: auto-failover from WiFi to LTE when WiFi drops
Pitfalls: USB power draw of LTE modems (>2A on TX); APN-vs-SIM mismatch
Pages: ~16
Chapter 103 — UART AT-command modems¶
Modules: SIMCom A7670C, SIM7600 (UART variants); Air724UG; ML302.
The PPP+chat approach for legacy modems
AT-command discovery and capabilities probing
The 2G/3G legacy chains
Lab: SMS + GPRS data on a UART-only A7670C
Pitfalls: chat script timing; carrier-specific AT command quirks
Pages: ~12
Chapter 104 — NB-IoT / Cat-M1¶
Modules: Quectel BC95, BC26; SimCom SIM7080G.
Low-power cellular IoT — when it makes sense vs LoRa vs WiFi
AT command flows for ~30 mA RX, sub-mA PSM/eDRX sleep
MQTT and CoAP profiles for NB-IoT
Lab: a battery-powered NB-IoT sensor that runs 1+ year on a 19 Ah Li-SOCl2 cell
Pitfalls: carrier band availability (NB-IoT not universal); PSM wake-up latency
Pages: ~14
Group O — Identification¶
Chapter 105 — RFID / NFC¶
Chips: NXP MFRC522 (SPI 13.56 MHz, ubiquitous Arduino-clone); PN532 (I²C/SPI/UART, NFC-A/B/F); NCV6 series (HF-only).
The MFRC522 register-level dance
libnfc + neard for high-level NFC
Mifare Classic vs DESFire — security tier comparison
Lab: an access-control reader with logging; clone-tag warning detection
Pitfalls: Mifare Classic broken-by-default cryptanalysis; NFC reading-range limits
Pages: ~14
Chapter 106 — Fingerprint sensors¶
Modules: Grow R503 (capacitive, UART); FPM10A (optical, UART); GT-521F (capacitive); AS608.
UART command protocols; template enrollment and matching
libfprint for direct USB fingerprint scanners
Privacy implications: template storage and revocation
Lab: a 2-factor login flow combining password + fingerprint
Pitfalls: sensor warm-up on first scan; template-format incompatibility across vendors
Pages: ~12
Group P — Positioning¶
Chapter 107 — GPS / GNSS + PPS time synchronization¶
Modules: u-blox NEO-6M / 8M / 9M (multi-GNSS); ATGM336H (cheap Chinese alternative); SiRFStar V; Quectel L86.
NMEA-0183 parsing; UBX binary protocol for u-blox
gpsd + chrony for sub-microsecond PPS time sync
A stratum-1 NTP server from a Pi-class device + GPS
Lab: a GPS-disciplined NTP server hitting < 1 µs offset
Pitfalls: cold/warm start times; multipath in urban canyons; PPS pulse polarity
Pages: ~14
Group Q — Industrial buses¶
Chapter 108 — RS-485 + Modbus RTU¶
Transceivers: Maxim MAX485, SP3485; Analog Devices ADM2483 (isolated); MAX13487 (auto-direction).
RS-485 physical layer: differential pair, biasing, termination, fail-safe
DE/RE direction control via GPIO or auto-direction transceivers
Linux RS-485 mode in the UART driver (
SER_RS485_ENABLED)Modbus RTU framing and libmodbus
Lab: a 5-slave Modbus RTU network with PV inverters
Pitfalls: termination resistor placement; ground reference between segments
Pages: ~14
Chapter 109 — LIN bus¶
Transceivers: NXP TJA1020, TJA1027; Microchip MCP2003B.
LIN as a “UART with break and checksum” sub-CAN bus
Master/slave roles; PID, response, classic checksum vs enhanced
Linux’s lack of native LIN — userspace SLIP-style or custom driver
Lab: an HVAC blower-control LIN slave talking to a real automotive ECU
Pitfalls: LIN frame timing tolerance; cable-length-vs-baud trade-offs
Pages: ~12
Chapter 110 — CAN deep dive¶
(Extension of Ch 55C) Transceivers: TJA1051 (5 V), TJA1463 (CAN-FD), MCP2562; SPI-CAN bridge MCP2515 (when SoC has no FlexCAN free).
CAN-FD vs classic CAN; bit-stuffing and frame-rate trade-offs
ISO-TP for ISO-15765 diagnostics
SocketCAN advanced: BCM (broadcast manager), CAN-J1939
Lab: an OBD-II diagnostic tool reading PIDs from a real car
Pitfalls: termination 60 Ω vs 120 Ω; differential signal-integrity at 1 Mbit/s
Pages: ~16
Group R — Motors & encoders¶
Chapter 111 — Quadrature encoders & rotary¶
Devices: incremental optical encoders; magnetic encoders; the
rotary_encoderdriver andgpio-keysfor low-res.Hardware quadrature decode on i.MX6ULL’s eXtended quadrature decoder (XBAR + ENC)
A 2-pin GPIO quadrature decode in software (and why it doesn’t work at high speed)
IIO
anglchannelLab: a velocity-controlled motor closed-loop with a 1024-line encoder
Pitfalls: Z-index pulse handling; debouncing mechanical encoders
Pages: ~12
Chapter 112 — Stepper & DC motor drivers¶
Drivers: TI DRV8825 (stepper, micro-stepping); Allegro A4988; Trinamic TMC2209 (silent stepper with UART config); BTS7960 (43 A H-bridge DC); DRV8302 (BLDC).
Step-Dir control via PWM/GPIO with a kernel
pwmdriverTMC2209 UART config of microstep, RMS current, stallGuard
BLDC commutation: trapezoidal vs sinusoidal vs FOC
Lab: a 3-axis CNC with TMC2209 silent steppers
Pitfalls: missed steps from EMI; back-EMF damage to drivers
Pages: ~16
Group S — Indicators & smart LEDs¶
Chapter 113 — WS2812 / SK6812 / APA102 addressable LEDs¶
Chips: WS2812B (timing-strict 1-wire); SK6812 (similar, RGBW variant); APA102 (SPI-based, no timing issues).
Three implementation approaches:
GPIO bit-bang with PREEMPT_RT — only viable for short strips
PWM + DMA — repurpose PWM for serial bitstream
SPI + DMA — encode WS2812 timing in SPI bytes; cheap and robust
APA102’s SPI native protocol; per-pixel global brightness
Lab: a 100-pixel WS2812 ring at 30 fps via SPI+DMA
Pitfalls: 5 V level shifting from 3.3 V GPIOs; long-strip data-line integrity
Pages: ~14
Chapter 114 — Beepers, relays, SSRs¶
Passive beepers via PWM; active beepers via GPIO
Mechanical relays vs MOSFETs vs SSRs — when to use which
AC SSR safety: opto-isolation, zero-cross switching, snubber circuits
Lab: a 4-channel home automation relay board with software-debounced switching
Pitfalls: relay-coil flyback; SSR sub-cycle leakage
Pages: ~10
Group T — Network & system power¶
Chapter 115 — Dual FEC + hosted Ethernet¶
Both i.MX6ULL FEC1 + FEC2 simultaneously: pin-mux, PHY-per-MAC, sep subnets
IP forwarding, bridging, and bonding between the two
Chips: WIZnet W5500 (SPI Ethernet with hardware TCP/IP); Microchip ENC28J60 (cheaper but slower).
Adding a 3rd Ethernet via SPI when you need 3+ network interfaces
Lab: a router with two Ethernet ports + one SPI Ethernet port; iperf3 throughput
Pitfalls: clock skew between PHYs; W5500 socket exhaustion
Pages: ~16
Chapter 116 — PMICs and regulator framework¶
Chips: NXP PCA9450 (i.MX-recommended PMIC); PF8200; Rohm BD71850MWV.
The
regulatorframework: consumer/provider modelVoltage sequencing for SoC + DDR + I/O rails
Dynamic voltage scaling integration with DVFS (Ch 51B)
Lab: add a PMIC to a board that previously used discrete LDOs; measure power savings
Pitfalls: boot-sequence races (kernel boots before PMIC’s voltage settles); thermal throttling
Pages: ~16
Chapter 117 — External RTC¶
Chips: Maxim DS3231 (TCXO, ±2 ppm); NXP PCF8563 (common cheap); Microchip MCP79410 (with EEPROM).
Why crystal RTCs drift (and why TCXO is worth the money for fleets)
Battery-backed time keeping; the “RTC dead but device works” failure mode
hwclock,rtc-i2cfamily of driversAlarm interrupts for wake-from-suspend (Ch 51B integration)
Lab: sub-second RTC tracking across power cycles; calibrate DS3231 over a week
Pitfalls: the i.MX6ULL internal RTC is part of SNVS and loses time without backup battery; DS3231 alarm-pin polarity
Pages: ~12
PART VIII — DEBUG, PRODUCTION, ADVANCED¶
Chapter 118 — JTAG, OpenOCD, GDB at every layer¶
Connecting a JTAG adapter (FT2232H / J-Link) to the Point Atom JTAG header
OpenOCD config for i.MX6ULL
GDB scripts for bare-metal, U-Boot (
gdb-multiarch u-boot), and the kernel (vmlinuxsymbols)Hardware breakpoints, watchpoints
Pages: ~20
Chapter 119 — Kernel debugging without JTAG¶
printkand log levelspr_debugandCONFIG_DYNAMIC_DEBUGftrace:
function,function_graph, eventstrace-cmd and KernelShark
bpftraceandbcctools (introductory)kgdb over UART
Decoding an oops:
addr2line,scripts/decode_stacktrace.sh,CONFIG_DEBUG_INFOPages: ~26
Chapter 120 — User-space debugging¶
gdbserveron target,gdb-multiarchon hoststrace,ltraceperf(sampling, counters, flamegraphs)Core dumps and
coredumpctlPages: ~20
Chapter 120A — Supplementary: Mainline patch submission workflow¶
If you write a driver in this book and it’s good, it can go upstream. We walk the entire process end-to-end on a real candidate patch (e.g., a tweak to the FEC driver, or a YAML binding for a new sensor).
git format-patchand the one-patch-per-fix disciplinescripts/checkpatch.pl --strict; the warnings that matter, the ones that don’tscripts/get_maintainer.plto find the right list and the right reviewergit send-emailsetup (the only patch-submission tool the kernel community accepts)Subject-line conventions:
[PATCH] drivers/net/ethernet/freescale/fec: ...Cover letters; v1 / v2 / v3 etiquette;
Reviewed-by,Acked-by,Tested-by,Reported-by,Suggested-by,Co-developed-byReplying to review feedback — what to do, what not to do
The Lore archive,
b4for series managementLab: prepare a real, sendable patch series for one of your earlier driver chapters (do NOT actually send it without a real bug to fix)
Pages: ~18
Chapter 121 — Capstone: custom board port¶
Take your own PCB (or rework the Point Atom into a non-trivial variant)
Port U-Boot, port kernel DT, write at least one peripheral driver for something the original board didn’t have
Reproducible build script: clean checkout → bootable SD in one command
Pages: ~30
Chapter 121A — Supplementary: CI/CD for embedded Linux¶
Modern teams build U-Boot, kernel, rootfs, and run a smoke test on the actual hardware on every commit. We set this up using GitHub Actions (or GitLab CI), a self-hosted runner with USB-OTG to a board, and a small Labgrid-style harness.
Cross-builds in CI: caching, deterministic builds,
bitbake-no-networkpatternsImage artifact storage (size budget: ~150 MB per build × 20 commits/day)
A self-hosted runner with a board on a USB hub
The minimal smoke test: boot, wait for
=>, run a 5-second sysfs check, capture serial logPass/fail signaling back to the PR
Notifications when the board farm is offline
Lab: a GitHub Actions workflow that builds U-Boot+kernel and
uuu-flashes a real board on every push tomainPages: ~18
Chapter 122 — Build your own cross-toolchain¶
Bootstrap problem: gcc needs libc, libc needs gcc, gcc needs binutils
crosstool-NG step-by-step: kernel headers → binutils → gcc stage 1 → glibc/musl → gcc stage 2
Comparing your toolchain against a pre-built Arm GNU one (size, behavior, sysroot)
Pages: ~24
Chapter 122A — Supplementary: BSP → mainline migration playbook¶
You inherited a Linux 4.1.15 vendor BSP from a previous project or a customer. The product needs to ship updates for the next eight years. You must move to a supportable mainline kernel. Here is the playbook.
Inventory: list every patch the vendor applied; classify (vendor-feature / vendor-bugfix / mainline-merged / dead-code)
The pinned-driver problem and how to break each pin
Per-subsystem upstreaming order (most-self-contained first: clk → pinctrl → gpio → i2c → … → display/network last)
Maintaining two trees during the migration (vendor “shipping” + mainline “next”)
Bisection across kernel versions when an old hack now breaks
When to not migrate (truly captive silicon with no mainline future)
A concrete worked example: NXP i.MX6ULL 4.1.15 → 6.x mainline
Pages: ~22
Chapter 123 — Yocto vs Buildroot, an honest comparison¶
The mental model: package metadata vs configuration system
Layers, recipes, BBLAYERS
When Buildroot is better; when Yocto is better; when neither is right
A single recipe written for Yocto and for Buildroot, side by side
Pages: ~22
Chapter 123A — Supplementary: Yocto layer development in depth¶
For shipping at scale, Yocto is industry standard, and the meat of using it is writing layers. We build a vendor layer (meta-mybsp), a board layer (meta-mybsp-mini), and an application layer (meta-mybsp-myapp), with the right bbappend pattern between them.
Layer anatomy:
conf/,recipes-*/,classes/,wic/bbappendand the layer-priority danceWriting a
.bbfor our LED-driver kernel module (Chapter 41)Writing a
.bbfor an in-house Qt appwicfor image layouts (partition tables, RAUC slots)Distro layers (
meta-mybsp-distro) vs board layersIMAGE_FEATURES,EXTRA_IMAGE_FEATURES,DISTRO_FEATURESThe
SRC_URIcache that makes the build reproducible without internetLab:
meta-mybsp/produces a flashable image with our Chapter 41 LED driver baked in, in < 30 minutes frombitbake core-image-minimalPages: ~26
Chapter 124 — Secure boot (HAB) and OP-TEE¶
The chain of trust: ROM → SRK fuses → CSF → signed U-Boot → signed kernel → dm-verity rootfs
HAB CST (Code Signing Tool),
csffiles, key ceremonyTrustZone primer,
monitormode, SMC callsOP-TEE basics: TA (Trusted Application) lifecycle
Pages: ~26
Chapter 125 — Field updates¶
A/B partition scheme with U-Boot
RAUC, SWUpdate, Mender — comparison
Atomic updates, rollback, fail-safe boot
Pages: ~20
Chapter 125A — Supplementary: VSCode + gdbserver remote-debug workflow¶
Many readers came here from VSCode and would rather debug there than learn tui mode. We set up gdbserver on the target, gdb-multiarch on the host, the VSCode launch.json that joins them, and the .vscode/c_cpp_properties.json that resolves headers from the cross-sysroot — so Go to Definition works on kernel sources too.
Source Insight as a faster alternative for kernel-source navigation only (read-only, but very fast)
The minimum target setup: a statically-linked
gdbserverbinary in/usr/bin/Single-stepping a kernel module loaded with
insmodLab: set a breakpoint in your Chapter 41 LED driver’s
probe(), hit it frominsmod, inspectdevPages: ~12
Chapter 126 — Closing: what to read next¶
LDD3 (still relevant where it isn’t outdated)
Bootlin training material
kernelnewbies.orgLWN — the single most useful periodical for a Linux engineer
The mailing list etiquette guide; how to send your first patch
Pages: ~6
PART IX — APPLIED VIRTUALIZATION AND MIXED-CRITICALITY SYSTEMS¶
This part is optional and advanced. It exists for readers who want real hypervisor experiments, not only HYP-mode vocabulary. The path starts in QEMU, moves to Xen on the i.MX6ULL, then uses Jailhouse and STM32MP1 where the hardware model is a better fit.
Chapter 127 — Why embedded products use hypervisors¶
The real product problems: crash isolation, security boundaries, mixed criticality, legacy BSP containment, and device ownership
Containers vs PREEMPT_RT vs TrustZone vs remoteproc/OpenAMP vs Xen vs Jailhouse
Why a hypervisor is sometimes the clean answer and sometimes architectural overkill
The rule for this part: every chapter must end with something visible and testable
Lab: choose the right isolation model for three product scenarios and defend the decision
Pages: ~14
Chapter 128 — QEMU as a virtual hardware lab¶
QEMU as a repeatable ARM lab bench, not an i.MX6ULL clone
ARMv7-A
virt, ARM64virt, and whenvexpressis usefulSerial console, memory size, CPU model, DTB, kernel, initrd, and command line as explicit hardware choices
GDB with
-S -sbefore Linux prints anythingLab: start QEMU halted, attach GDB, and explain every command-line option
Pages: ~18
Chapter 129 — Build and boot tiny Linux in QEMU¶
Build a tiny ARM Linux kernel and BusyBox/Buildroot initramfs
Direct boot:
QEMU → Linux → initramfs → /initDTB,
console=,rdinit=, and the first shellFailure-driven learning: wrong console, missing init, wrong DTB
Lab: boot to a shell, then break each input and record the symptom
Pages: ~24
Chapter 130 — U-Boot in QEMU¶
Put U-Boot in front of the QEMU Linux boot
Manual
bootz/booti: load kernel, DTB, initramfs into non-overlapping RAMbootargs, scripted boot, and FIT handoffWhy hypervisors depend on firmware doing the next-stage handoff correctly
Lab: boot Linux manually from U-Boot, then turn the commands into a repeatable script
Pages: ~20
Chapter 131 — HYP mode, stage-2 MMU, and virtual interrupts¶
PL0, PL1, PL2, HYP mode,
svc, andhvcHYP mode vs TrustZone Monitor mode
Stage-1 translation: virtual → physical
Stage-2 translation: guest physical → real physical
Virtual timer, virtual GIC, and why guests need believable interrupts
Lab: compare plain Linux and hypervisor boot logs and identify mode, timer, interrupt, and memory-map evidence
Pages: ~26
Chapter 132 — Xen in QEMU¶
Boot path:
QEMU → Xen → Dom0 LinuxXen binary, Dom0 kernel, initramfs/rootfs, DTB, and console
xl info, memory allocation, and where control moves from Xen to Dom0Keeping the first Xen boot small and serial-only
Lab: boot Xen and Dom0 in QEMU and save the annotated boot log
Pages: ~28
Chapter 133 — First DomU Linux¶
Boot path:
Xen → Dom0 → xl create → DomUDomain config explained line by line: name, kernel, ramdisk, memory, vCPUs, console, command line
Tiny BusyBox/Buildroot guest first, not a full distribution
Failure isolation: DomU can die while Dom0 survives
Lab: start, attach, inspect, shut down, and restart a DomU Linux guest
Pages: ~26
Chapter 134 — Xen on i.MX6ULL¶
Moving from QEMU to the real board
i.MX6ULL reality: one Cortex-A7, limited RAM, real DTB, real UART, real U-Boot handoff
Build Xen for ARM32 and boot Dom0 Linux on the Point Atom board
Confirm that the board demonstrates HYP-mode mechanics but is not a rich virtualization platform
Lab: boot
U-Boot → Xen → Dom0 Linuxon the i.MX6ULL and preserve the serial logPages: ~30
Chapter 135 — DomU Linux on i.MX6ULL¶
A tiny DomU on the real board: one vCPU, small RAM, initramfs, console only
xl list, DomU console,uname -ain both worldsWhat this proves: real guest isolation
What it does not prove: performance, device pass-through safety, or hard real-time behavior
Lab: boot a DomU, kill it, and prove Dom0 remains alive
Pages: ~24
Chapter 136 — Devices, memory, and DMA boundaries¶
Device ownership tables: owner, shared/not shared, DMA, reset, clock, pinctrl, debug
Why the CPU MMU does not automatically protect against DMA
UART, Ethernet, GPIO bank, SPI/I2C, timers, and interrupt ownership
Xen grant tables, event channels, virtual network, shared memory
Lab: produce device ownership tables for i.MX6ULL Xen, QEMU Xen, and STM32MP157 Linux+M4
Pages: ~26
Chapter 137 — Jailhouse in QEMU ARM64¶
Why Jailhouse is taught on QEMU ARM64 first: multiple CPUs and a known demo platform
Boot path:
QEMU ARM64 → Linux root cell → jailhouse.ko → system cell → inmate cellRoot cell vs inmate cell, CPU assignment, memory regions, interrupts, and no scheduler
Why i.MX6ULL is a poor Jailhouse target even though Cortex-A7 can implement virtualization extensions
Lab: enable Jailhouse in QEMU ARM64 and start a tiny inmate
Pages: ~28
Chapter 138 — Bare-metal or Zephyr inmate cell¶
Start with a minimal bare-metal inmate before adding Zephyr
Link address, assigned RAM, console ownership, timer, and stop/reload path
Zephyr as the RTOS-flavored inmate once the bare-metal path is understood
Shared memory and interrupt discipline
Lab: run a bare-metal inmate, then a Zephyr inmate, while Linux root cell stays alive
Pages: ~26
Chapter 139 — STM32MP1 Linux plus RTOS, the production pattern¶
Why STM32MP157 is a better Linux+RTOS teaching target than i.MX6ULL: dual A7 plus M4
Main production path: Linux on Cortex-A7, Zephyr/FreeRTOS on Cortex-M4, RPMsg/OpenAMP between them
Optional Jailhouse path for A7-to-A7 partitioning when the M4 is not enough
Decision checklist: Xen vs Jailhouse vs remoteproc/OpenAMP vs no hypervisor
Lab: boot Linux on A7 and start M4 firmware, then evaluate whether Jailhouse is justified
Pages: ~30
Total page estimate¶
Part |
Numbered |
Supplementary |
Pages |
|---|---|---|---|
I — Foundations |
8 |
— |
136 |
II — Bare-metal i.MX6ULL |
10 |
+3 (18A–C) |
252 |
III — U-Boot |
6 |
+2 (22A, 23A) |
204 |
IV — Kernel |
6 |
+2 (27A, 30A) |
148 |
V — Rootfs & user space |
5 |
+3 (35A, 35B, 35C) |
140 |
VI — Driver development |
20 |
+14 (51A/B, 52A, 54A/B, 55A–I) |
644 |
VII — Device cookbook |
54 |
— |
~735 |
VIII — Debug & advanced |
9 |
+5 (120A, 121A, 122A, 123A, 125A) |
290 |
IX — Applied virtualization |
13 |
— |
320 |
Total |
131 |
+29 |
~2,869 pages |
Dependency graph (so the reader can prune)¶
Ch1 → Ch2 → Ch3 ─┐
├→ Ch4 ─┐
├→ Ch5 ─┼→ Ch6 → Ch7 → Ch8 → Ch9 → Ch10 → Ch11 → Ch12 ─┐
└────── ┘ │
Ch13 → Ch14 → Ch15 → Ch16 → Ch17 → (Ch18 optional)
│
┌───────────────────────────────────────────┘
▼
Ch19 → Ch20 → Ch21 → Ch22 → Ch23 → Ch24
│
┌───────────────────────┘
▼
Ch25 → Ch26 → Ch27 → Ch28 → Ch29 → Ch30
│
┌───────────────────────┘
▼
Ch31 → Ch32 → Ch33 → Ch34 → (Ch35 optional)
│
┌───────────────────────┘
▼
Ch36 → Ch37 → Ch38 → Ch39 → (Ch40..Ch55 mostly independent siblings)
│
┌───────────────────────┘
▼
Ch64..Ch117 (Device Cookbook — independent chapters; pick the chip you need)
│
┌───────────────────────┘
▼
Ch118..Ch126 (cross-cutting; read alongside earlier parts)
│
▼
Ch127..Ch139 (optional virtualization path: QEMU → Xen → Jailhouse/STM32MP1)
Bare-metal Part II is the only part that can be safely skipped by a reader who only wants kernel/drivers. For your learning, do not skip it — it’s the chapter set that will set this book apart.
Where the supplementary chapters fit¶
Ch10 ──┬──► Ch18A (project organization) ──► informs every subsequent bare-metal Ch
└──► Ch11..Ch16 (proceed normally)
Ch16 ──► Ch18B (button+beep) ──► Ch17 MMU
Ch18 ──► Ch18C (bare-metal RTC)
Ch22 ──► Ch22A (optional new-SoC bring-up path) ──► Ch23
Ch35 ──► Ch35A (Ubuntu-base, optional alternative to Ch31/35)
Ch43 ──► any of Ch55A..Ch55H (siblings, independent)
Ch44..Ch55I ──► any chapter of Part VII Device Cookbook (Ch 64..Ch 117) — pick the chip class you need
Ch120 ──► Ch125A (VSCode workflow; can be read after any driver chapter)
Ch126 ──► Ch127..Ch139 (optional virtualization and mixed-criticality path)
Project decisions¶
Target board. Point Atom MINI (i.MX6ULL @ 696 MHz, 512 MiB DDR3L) is the primary reference. The guide deliberately stays general where it can — most chapters work on any i.MX6ULL board, and Parts IV–VIII (kernel, rootfs, drivers, debug, production) transfer to any Linux-capable ARM SoC. Board-specific divergences are called out inline.
Language. English only. No bilingual edition planned.
Versions targeted throughout. Linux kernel v6.6 LTS; U-Boot v2026.04 (or latest stable at read-time); GCC 13.x for the cross-toolchain; Buildroot 2026.02; Yocto scarthgap (5.0) / kirkstone (4.0) on the Buildroot/Yocto side. Driver-API signatures and DTS paths follow v6.6 conventions; pre-v6.5 kernels use different DTS layout (no
nxp/imx/prefix) and earlier driver-API forms.Code-listing license. MIT. Snippets in chapters are copy-paste-into-your-project friendly; no attribution required.
Tooling. Markdown sources + Sphinx + Furo + MyST-Parser rendered to a modern, dark-mode-aware site with hideable sidebar and VS-Code-style code highlighting. Hosted on GitHub Pages, auto-rebuilt on push. See PUBLISH.md.
Code delivery. Inline in the chapters. There is no companion
code/repository. The drivers, scripts, and configurations shown are complete enough to read, type, and adapt; the lab sections describe what to build but do not ship a reference solution.