Chapter 30: Kernel configuration deep-dive

rootfs: root filesystem, the directory tree mounted at / that contains /bin, /etc, /dev, and libraries. MCU bridge: Think of the rootfs as the firmware image’s file-backed runtime environment. On an MCU you link everything into flash. On Linux, programs and config live in this mounted tree.

What: the kernel’s Kconfig system, configuration editors, .config, defconfig snapshots and the options that shape an i.MX6ULL image’s capabilities and boot dependencies.

Why: earlier examples used imx_v6_v7_defconfig as a starting point. Product configuration makes choices about image size, scheduling, device support and engineering diagnostics explicit. PREEMPT_RT: the Linux real-time patch set that makes more kernel paths preemptible and reduces latency.

Focus: .config is the resolved configuration. menuconfig, xconfig and other editors operate within Kconfig dependency rules rather than independently choosing what can compile.

30.1 The Kconfig system

The kernel has ~7000 configurable options across hundreds of Kconfig files scattered through the source tree. Each is a CONFIG_FOO symbol that ends up as =y (compiled in), =m (loadable module), or absent (compiled out) in the final .config.

Five make targets cover 95% of use:

Target

What it does

make defconfig

Use the default config (arch/$ARCH/configs/defconfig)

make <name>_defconfig

Use a named per-board/per-arch starting point (e.g., imx_v6_v7_defconfig)

make menuconfig

Interactive ncurses-based config editor

make oldconfig

Update an existing .config against new Kconfig options; prompt for each new one

make savedefconfig

Distill the current .config down to the minimum diff from defaults and save to defconfig

Other UIs exist (nconfig, xconfig, gconfig), same functionality, different UIs. menuconfig is the one everyone uses.

30.2 make menuconfig tour

From a configured tree:

$ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- menuconfig

You’ll get a full-screen ncurses interface. Navigation:

  • Arrow keys: move

  • Enter: descend into a submenu

  • Space: cycle =y / =m / disabled

  • ?: show help for the current option (very useful, every Kconfig symbol has descriptive help)

  • /: search for an option by name (case-insensitive substring. Multiple matches show their location in the menu tree)

  • esc esc: go back

  • Q: quit. Prompts to save

A representative top-level menu:

  General setup  --->
  System Type  --->
  Bus support  --->
  Kernel Features  --->
  Boot options  --->
  CPU Power Management  --->
  Floating point emulation  --->
  Userspace binary formats  --->
  Power management options  --->
  [*] Networking support  --->
  Device Drivers  --->
  File systems  --->
  Security options  --->
-*- Cryptographic API  --->
  Library routines  --->
  Kernel hacking  --->

Each ---> is a submenu. Each [*] / <*> / <M> / < > is a yes/module/no option. Each (...) is a string or integer option. The structure is consistent: subsystems group their options together.

30.3 The .config file

Privilege boundary: $ means normal user. # or sudo means root and can change host or target state. After a privileged command, verify the expected device, service, or file appears before continuing. Roll back by undoing the config change or stopping the service you just enabled.

After saving in menuconfig, the entire state lives in .config at the kernel-tree root. It is plain text. Each line is one of: CONFIG_FOO=y, CONFIG_FOO=m, CONFIG_FOO="string", CONFIG_FOO=10, or # CONFIG_FOO is not set.

$ wc -l .config
2147 .config

$ head -10 .config
#
# Automatically generated file; DO NOT EDIT.
# Linux/arm 6.6.0 Kernel Configuration
#
CONFIG_CC_VERSION_TEXT="arm-none-linux-gnueabihf-gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
CONFIG_GCC_VERSION=110400
CONFIG_CLANG_VERSION=0
CONFIG_AS_IS_GNU=y
CONFIG_AS_VERSION=24200
...

You can edit .config by hand, but the comment is honest: changes you make can be silently undone the next time make runs, because the Kconfig dependencies might reject your edit. Always go through make olddefconfig or make oldconfig after a manual edit to let the system reconcile.

30.4 defconfigs, capturing your customisation

A defconfig is the minimum set of options that, applied on top of the architecture’s defaults, reproduces your .config. It’s how you preserve a customisation without saving the full 2000-line .config.

$ make ARCH=arm savedefconfig
$ ls defconfig
defconfig
$ wc -l defconfig
324 defconfig

That defconfig file (324 lines vs 2147 for full .config) contains only the lines that differ from the architecture default. Save it as arch/arm/configs/myboard_defconfig, commit it to git, and anyone can reproduce your build:

$ make ARCH=arm myboard_defconfig
$ make ARCH=arm -j$(nproc) zImage

With those two commands, the second engineer has the same kernel. This is why upstream ships imx_v6_v7_defconfig instead of full .configs.

30.5 The dozen knobs that matter most

Reading every help text in menuconfig takes hours and is not the fastest way to learn what matters. Here are the dozen options that matter most on the path from defconfig to an i.MX6ULL image:

Preemption model, CONFIG_PREEMPT_*

General setup
  Preemption Model  --->
    ( ) No Forced Preemption (Server)
    (X) Voluntary Kernel Preemption (Desktop)     ← default
    ( ) Preemptible Kernel (Low-Latency Desktop)
    ( ) Fully Preemptible Kernel (Real-Time)

Determines how long kernel code can hold the CPU before letting another thread run. PREEMPT_NONE maximizes throughput, PREEMPT_VOLUNTARY (the default) is a reasonable compromise, PREEMPT improves desktop responsiveness, PREEMPT_RT turns the kernel into a real-time kernel with bounded, low latency (Chapter 52A is dedicated to this).

For a typical embedded product, PREEMPT_VOLUNTARY is fine. For motion control or audio with hard latency budgets, PREEMPT_RT. For a router pushing packets, PREEMPT_NONE.

Tick rate, CONFIG_HZ

Kernel Features
  Timer frequency  --->
    ( ) 100 Hz
    (X) 250 Hz
    ( ) 300 Hz
    ( ) 1000 Hz

How often the kernel scheduler tick fires. Higher = better latency, more overhead. 250 Hz is the modern default for non-server workloads. For battery-powered devices, lower. For low-latency, 1000 Hz.

Tickless idle, CONFIG_NO_HZ_*

General setup
  Timers subsystem
    Timer tick handling  --->
      ( ) Periodic timer ticks (constant rate, no dynticks)
      (X) Idle dynticks system (tickless idle)
      ( ) Full dynticks system (tickless)

NO_HZ_IDLE lets the CPU sleep longer when idle by stopping the tick. Saves power. The default for v6.x.

Devtmpfs, CONFIG_DEVTMPFS + CONFIG_DEVTMPFS_MOUNT

Device Drivers
  Generic Driver Options
    [*] Maintain a devtmpfs filesystem to mount at /dev
    [*]   Automount devtmpfs at /dev, after the kernel mounted the rootfs

Required for the modern /dev model: kernel populates /dev/* from device probes. Without it, you need udev or mdev in userspace. Always enable both.

udev: the user-space device manager that reacts to kernel device events and creates policy-driven /dev nodes.

Initramfs, CONFIG_BLK_DEV_INITRD + CONFIG_INITRAMFS_SOURCE

General setup
  [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
  ()  Initramfs source file(s)             ← optional, embeds an initramfs

These options support the initramfs designs discussed in Chapter 29. The source field embeds a cpio archive directly into zImage.

Modules, CONFIG_MODULES and CONFIG_MODULE_UNLOAD

[*] Enable loadable module support  --->
    [*]   Module unloading
    [*]   Forced module unloading      ← off in production usually

Enables =m-style driver loading. Useful for development (load/unload a driver without reboot). Off if you want a monolithic kernel.

console= defaults via DT, already covered

The kernel reads chosen.stdout-path from DT plus the console= cmdline. Both are independent of .config.

USB, CONFIG_USB + CONFIG_USB_EHCI_HCD + CONFIG_USB_GADGET

Device Drivers
  [*] USB support  --->
       <*>   Support for Host-side USB
       <*>   EHCI HCD (USB 2.0) support
       [*]   USB Gadget Support

EHCI host = USB host port on i.MX6ULL. Gadget = device-mode (Chapter 55).

Network drivers, CONFIG_FEC for the i.MX FEC

Device Drivers
  [*] Network device support  --->
       Ethernet driver support  --->
         <*>   Freescale devices  --->
                 <*>   FEC (Freescale FEC and i.MX6UL/ULL)

Without CONFIG_FEC, your Ethernet won’t work. Default imx_v6_v7_defconfig enables this. If you start from a stripped-down config you may have to add it back.

MMC / SD, CONFIG_MMC + CONFIG_MMC_SDHCI_ESDHC_IMX

Device Drivers
  <*> MMC/SD/SDIO card support  --->
        <*>   Secure Digital Host Controller Interface support
        <*>     Freescale eSDHC/uSDHC i.MX controller

Required to access SD card and eMMC.

Filesystem support, CONFIG_EXT4_FS, CONFIG_F2FS_FS, CONFIG_VFAT_FS

File systems  --->
  <*> The Extended 4 (ext4) filesystem
  <*> F2FS filesystem support
  <*> DOS/FAT/EXFAT/NT Filesystems  --->
        <*>   VFAT (Windows-95) fs support

Need at least one filesystem matching your root=. Most embedded systems use ext4 on eMMC, F2FS on eMMC for flash longevity, FAT for boot partitions.

Kernel debug options, CONFIG_DEBUG_KERNEL + CONFIG_DEBUG_INFO

Kernel hacking  --->
  [*] Kernel debugging
      Compile-time checks and compiler options  --->
        Debug information --->
          (X) Generate DWARF Version 5 debuginfo

For development, keep these on, gdb stack traces and oops decoding need DWARF symbols. For production, off, they bloat vmlinux by 100+ MB (though zImage stays small).

30.6 Building a smaller kernel

Default imx_v6_v7_defconfig produces a ~6 MB zImage. For a tighter image:

$ make ARCH=arm imx_v6_v7_defconfig
$ make ARCH=arm menuconfig

Aggressive trimming:

  • Disable unused architectures/SoCs. System Type → Multiple platform selection: turn off ARM_AT91, ARM_OMAP, ARCH_BCM, etc., keep only ARCH_MXC. Saves ~500 KB.

  • Disable unused drivers. Device Drivers → Sound card support → ALSA for SoC audio support, off if you don’t have audio. Saves ~200 KB.

ALSA: Linux’s kernel and user-space audio stack.

  • Disable unused filesystems. Only ext4 needed? Disable F2FS, FAT, etc. Saves ~100 KB.

  • Disable IPv6 / IPv4 sub-features you don’t need. TCP / UDP / unicast routing, yes. Multicast routing / fib_rules / netfilter, no. Saves ~500 KB.

  • Disable debug info. Kernel hacking → Kernel debugging → off. Cuts vmlinux size dramatically. zImage shrinks by ~1 MB. Keep your dev build separate.

A trimmed image for i.MX6ULL can reach ~3 MB. Below that requires turning off things you usually want (e.g., loadable modules → +200 KB savings, but you lose modprobe).

30.7 Useful Kconfig idioms

Find an option by name

( ) Inside menuconfig, press / and type CONFIG_FOO

The search results include the menu path. Quote: “Symbol: PREEMPT_RT [=n]. Defined at kernel/Kconfig.preempt:64. Location: Main menu → General setup → Preemption Model → ...”. You can jump there with Enter.

See dependencies

( ) Highlight an option and press ? for help; the "Depends on:" lines list its preconditions

If CONFIG_FOO depends on BAR && BAZ, enabling FOO requires both BAR and BAZ. Menuconfig hides FOO if its dependencies aren’t met. Search to find the missing dependency.

Strip a feature from a defconfig

$ make ARCH=arm imx_v6_v7_defconfig
$ ./scripts/config --disable AUDIT
$ make ARCH=arm olddefconfig

scripts/config is the headless command-line equivalent of menuconfig, invaluable for CI.

Combine multiple defconfigs

$ make ARCH=arm imx_v6_v7_defconfig
$ ./scripts/config --enable PREEMPT_RT
$ make ARCH=arm olddefconfig

You start from a known baseline and apply targeted changes. Easier to maintain than committing a full bespoke defconfig.

30.8 Key Notes

  • Kconfig resolves requested options against dependencies and defaults. A hand-edited symbol does not necessarily survive resolution or become compiled code.

  • .config records the complete resolved state, while a defconfig records a compact starting point relative to defaults. Its meaning is tied to the architecture and kernel revision.

  • Built-in support is available before module loading. Root storage and filesystem dependencies cannot live only in modules on the rootfs that they are needed to mount.

  • Configuration enables capabilities, DT describes hardware, and bootargs select behavior for a boot. Changing a root device or console argument is not the same as enabling its driver.

  • Trimming removes functionality as well as bytes. Recovery storage, consoles and optional maintenance paths remain requirements even when the normal product does not use them.

  • Preemption, timer and idle choices balance latency, overhead and power. An option name alone does not establish a measured real-time bound.

  • Debug information and runtime diagnostics serve different purposes. Host-side symbols and development-only interfaces need explicit ownership across product configurations.

30.9 Pitfalls

  • Editing .config directly and not running make oldconfig. Your edits may be silently reverted on next build because of dependency rules. Always go through Kconfig.

  • Forgetting ARCH=arm. Without it, make menuconfig shows you the host’s Kconfig (x86 or arm64), not the cross target’s. Always export.

  • make defconfig overwrites .config. If you’ve made customisations and run make defconfig, those changes are gone. Save with make savedefconfig first.

  • Different defconfigs in different parts of the tree. arch/arm/configs/imx_v6_v7_defconfig is for ARM 32-bit i.MX6/i.MX7. arch/arm64/configs/defconfig is for everything ARM 64-bit. Don’t cross them.

  • Disabling CONFIG_MMU. There is a CONFIG_MMU symbol you can clear. Don’t. The result is nommu Linux for parts without an MMU, which won’t run on Cortex-A7 anyway.

MCU bridge: Think of the MMU as a hardware address translator in front of every load/store. Cortex-M usually runs physical addresses directly. Linux relies on virtual addresses and page permissions. MMU: Memory Management Unit, hardware that translates virtual addresses to physical addresses and enforces permissions.

  • Disabling CONFIG_PREEMPT_VOLUNTARY to “make it faster”. If you turn off CONFIG_PREEMPT_VOLUNTARY, the build falls back to CONFIG_PREEMPT_NONE, which is the server profile (higher latency, higher throughput). On an interactive system this is a regression. Read the help on each Preemption Model option before changing.

30.10 Going deeper

  • Documentation/kbuild/kconfig-language.rst: the canonical Kconfig syntax reference.

  • Documentation/kbuild/kconfig.rst: operational guide for make menuconfig and friends.

  • scripts/kconfig/: the Kconfig parser and UI source. Worth a skim if you write your own Kconfig.

  • Documentation/admin-guide/: runtime-tunable options many of which correspond to .config choices.

Next chapter: Chapter 30A: Kernel lifecycle decision framework. Configuration choices sit within a longer-lived decision: which source and maintenance track can support the product’s field life? BSP: Board Support Package: vendor patches, configs, bootloader files, and scripts needed to boot one board.