Cleaning Up ext4l: Organizing the Compatibility Layer

We’ve been working to improve the structure of the ext4l filesystem implementation in U-Boot, specifically targeting the large compatibility layer that allows us to reuse Linux kernel code.

We’ve just posted a new 33-patch series that reorganises the compatibility stubs, moving them out of the monolithic ext4_uboot.h and into their proper locations within include/linux/.

The Problem: A Monolithic Header

The ext4l filesystem uses a file named ext4_uboot.h to bridge the gap between U-Boot’s environment and the Linux kernel code it incorporates. To save time when porting ext4l, this header has become a dumping ground for every missing function, macro, and struct definition needed to make the code compile. It contains stubs for everything from inode operations to block device management, all mixed together.

This monolithic approach makes it difficult to see what was actually being stubbed and prevented other subsystems from using these compatibility definitions.

So why did we do it this way?

The header file has only been there a few months. Why didn’t we just put everything in the right place to begin with?

Well, it sounds easy, but actually it’s quite tricky. When porting ext4l there were thousands upon thousands of build errors and warnings. We could have brought in the Linux files one by one to try to get past the compilation step, i.e. to just be left with link errors. But the files continue a lot of stuff we don’t need in U-Boot, so that would have created lots of useless code to maintain.

Worse, most of that code would just add confusion, since there is no C implementation. Someone coming along later would see the prototype, call the function and wonder why it doesn’t work.

But the main issue was that the goal is to make the filesystem actually work. Until something is working, any stubbing or implementation work is speculative. The necessary pieces only fall into place when it is possible to mount a file system, list a directory, write to a file, etc.

So the approach chosen was to pull in, function by function, macro by macro, only what was needed to make the code build. This was still many weeks of work, not to mention around a week of time with Google Antimatter and Claude Code to build the initial prototype.

So ext4_uboot.h provided a feasible path to a functional implementation. The problem is, now we have an ext4_uboot.h hangover.

The Solution: Standard Linux Headers

This series takes a systematic approach to cleaning up this technical debt, building on half a dozen earlier series of a similar ilk. We move definitions from ext4_uboot.h to their standard Linux header locations in include/linux/

For example:

  • File and inode operations (struct file_operations, struct inode_operations) are now in linux/fs.h
  • Block device operations (blkdev_issue_discard, etc.) are now in linux/blkdev.h
  • Wait queue primitives (DEFINE_WAIT) are in linux/wait.h
  • DAX stubs are in linux/dax.h

We also created new headers where necessary, such as include/linux/fs/super_types.h for superblock operations, mirroring the kernel’s structure more closely.

Fixing list_sort

One notable fix in this series involves list_sort(). The function signature in U-Boot didn’t match the Linux kernel, lacking const qualifiers on the list heads in the comparison callback.

We updated list_sort() to match the kernel API:

typedef int (*list_cmp_func_t)(void *priv, const struct list_head *a,
                               const struct list_head *b);

This requires updating several callers across the codebase (UBIFS, EFI loader, STM32MP), but it ensures that we can use kernel code that relies on list_sort without modification.

Results

The result is a significant reduction in the size and complexity of ext4_uboot.h, shrinking it from over 550 lines to around 270 lines of essential, ext4-specific glue (at its peak the file held over 3000 lines!). The remaining code is much cleaner, with stubs residing where developers expect to find them.

This work completes the reorganisation of the ext4l compatibility layer, making future updates and maintenance significantly easier.




Multiple Vidconsole Contexts for Expo

U-Boot’s expo subsystem provides a way to create graphical menus and forms for user interaction. One key component is the textline object, which allows users to enter text, for example when typing a password or a filename.

This post describes recent work to support multiple vidconsole contexts, making it easier for expo to handle multiple independent text-input fields on the same display.

The Problem

The original textline implementation was designed around U-Boot’s command-line editor. When a textline is ‘opened’, the text is written out using vidconsole and the state is saved. When the user types a character, the state is restored, the character is written, and the state is saved again.

This approach has several limitations. Cursor movement is inefficient: when the user presses Ctrl-A to move to the start of the line, backspaces are written to reposition the cursor. Inserting a character then requires rewriting the entire string.

More fundamentally, all text operations share the same vidconsole context, making it impossible to have multiple, truly-independent text fields on the screen at once. Characters typed into a textline also appear on the text console, which is undesirable in a graphical interface.

Looking ahead, the approach cannot easily extend to a textedit widget that needs to handle multiple lines of text with up/down navigation. The tight coupling between expo and the CLI editor becomes a significant limitation.

The Solution

The solution involves threading a vidconsole context pointer through the entire video console stack. This enables each text-input object to maintain its own cursor position, font settings, CLI line state, and cursor appearance.

The changes span several layers. At the foundation, the vidconsole uclass now maintains a list of contexts and provides functions to allocate and free them. Each context contains cursor position, font metrics, and CLI state.

All vidconsole functions now accept an optional context parameter. When NULL is passed, the default context used by the command line is selected. This maintains backward compatibility while enabling the new functionality.

At the expo level, each textline object can now have its own vidconsole context, allocated when the object is created and freed when destroyed. The CLI line state, which tracks the text buffer and cursor position within the text, moves from the scene into each text-input object.

Implementation

The series begins by adding a context parameter to low-level functions like vidconsole_put_char() and vidconsole_set_cursor_pos(). This change propagates upward through vidconsole_put_string(), the escape sequence handler, and eventually to the expo rendering code.

With the plumbing in place, the CLI line state moves from being shared at the scene level to being owned by each text-input object. This means each textline tracks its own text buffer, cursor index, and editing state independently.

The vidconsole uclass gains an alist of contexts, managed through vidconsole_ctx_new() and vidconsole_ctx_dispose(). When expo creates a text-input object, it can request a dedicated context that will be used for all rendering operations on that field.

Finally, context allocation centralises in the uclass rather than being scattered across individual drivers. This reduces code-duplication and ensures consistent setup of cursor buffers, font metrics, and initial state.

Benefits

This refactoring enables forms with multiple independent text-input fields, each maintaining its own cursor and editing state. A login form could have separate username and password fields, each responding to input independently.

The clean separation between text-input objects and the global command-line state means that typing in an expo field no longer echoes characters to the text console. The user interface becomes self-contained.

Perhaps most importantly, this architecture provides the foundation for multi-line text editing widgets. A textedit control that supports scrolling through many lines of text, with up and down arrow navigation, becomes feasible once each widget owns its own context.

The size impact is modest: approximately 300 bytes on Thumb2 for boards that enable both command-line editing and expo. This is a reasonable cost for the added functionality and cleaner architecture.

Conclusion

Adding multiple vidconsole context support to U-Boot’s expo subsystem enables more sophisticated user interfaces with multiple independent text-input fields. While the implementation required threading a context pointer through many functions, the result is a cleaner architecture that supports future enhancements like multi-line editing and independent cursor blinking per field.

The changes maintain full backward compatibility. Existing code that passes NULL for the context continues to work exactly as before, using the default context shared with the command line.




A Three-Phase PXE: Cleaning Up the Boot Process

The PXE/extlinux boot mechanism is a cornerstone of U-Boot’s standard boot flow. It parses a configuration file (like extlinux.conf), presents a menu, and loads the kernel, initrd, and device tree for the chosen option. A typical configuration file looks like this:

menu title Boot Menu
timeout 50
default linux

label linux
    menu label Ubuntu Linux
    kernel /vmlinuz
    initrd /initrd.img
    fdt /dtb/board.dtb
    fdtoverlays /dtb/overlay1.dtbo /dtb/overlay2.dtbo
    append root=/dev/sda1 quiet

While functional, the traditional implementation relied heavily on callbacks: as the parser encountered a file path (like /vmlinuz above), it would immediately call a function to load it.

We have submitted a series of 26 patches that introduces a new, cleaner API for this process.

The Problem with Callbacks

The callback-based approach mixes parsing logic with file loading logic. This makes it difficult for a caller to know what files need to be loaded without actually loading them. It also complicates environments where file access requires special handling or where the caller wants precise control over memory layout.

Furthermore, analysing the existing parser revealed several memory leaks where token strings and menu structures were not being freed properly.

The New Three-Phase API

The new API separates the boot process into three distinct phases:

  1. Parse (pxe_parse): The configuration file is parsed into a menu structure. Instead of loading files immediately, the parser populates a list of required files (kernel, initrd, FDT, overlays) for each label.
  2. Load (pxe_load): The caller iterates over the file list and loads the files manually to their desired addresses. The pxe_load() helper is then called to record the size and location of each loaded file.
  3. Boot (pxe_boot): Once everything is loaded, this function performs the final boot sequence using the pre-loaded file information.

This structure gives callers complete visibility and control. For example, a caller can now inspect the menu to see which files are required for a specific boot option before deciding where to load them.

Dealing with Includes

Configuration files often use the include directive to pull in other configs. In the new model, includes are not processed automatically. Instead, parsing populates an includes list. The caller is responsible for loading these included files and calling pxe_parse_include() to merge them into the menu. This explicit handling mirrors the control provided for boot files.

Improvements and Testing

Beyond the new API, this series includes several other improvements:

  • Memory Leaks: Fixed leaks in token parsing, menu labels, and FDT overlay paths. These were trivial to find with the recent malloc additions.
  • Explicit Sizing: The parser now accepts a file size parameter, removing the requirement for configuration files to be null-terminated strings.
  • Documentation: Comprehensive documentation for the new API has been added to doc/develop/bootstd/pxe_api.rst.

Some recently added tests, such as pxe_test_sysboot_norun(), continue to verify the traditional end-to-end functionality via the sysboot command. We added a new test, pxe_test_files_api_norun(), to verify the new callback-free workflow. It parses a configuration file, manually loads the kernel, initrd, and device tree overlays, records their positions via pxe_load(), and then simulates a boot. This new test also explicitly checks for memory leaks, ensuring that pxe_cleanup() properly frees all allocated memory, including the new file lists.

While these changes increase code size slightly (around 300 bytes on Thumb2 mostly fixing memory leaks and avoid access outside the file bounds), the result is a more robust, flexible, and leak-free PXE subsystem.




Taming the Beast: Refactoring Buildman for Maintainability

Buildman is the Swiss Army knife of U-Boot development. It handles the heavy lifting of building hundreds of boards in parallel, fetching the correct toolchains, and—perhaps most importantly—analysing the impact of your patches across git history. Whether you are checking for code bloat or verifying that a refactor doesn’t break a board you don’t own, buildman is the tool that makes it possible.

However, if you have ever peered into the internals of buildman, you might have noticed that the Builder class had become… well, enormous. Over the years, it accumulated responsibilities ranging from managing threads and file I/O to formatting console output and tracking build statistics.

A recent series of 18 patches to address this technical debt. The goal: split the monolithic Builder class into smaller, more focused components.

The Problem: A Class That Did Too Much

The Builder class in tools/buildman/builder.py had grown to over 2200 lines of code. It was handling two distinct responsibilities:

  1. Orchestrating the Build: Managing threads, checking out commits, running make, and saving artifacts.
  2. Reporting Results: Processing build outcomes, calculating size deltas, tracking error lines, and formatting the summary for the user.

These two concerns were tightly coupled, making the code hard to read, test, and maintain.

The Solution: Introduce ResultHandler

This refactor extracts the result-processing logic (~900 lines) into a new class called ResultHandler. The separation is clean:

  • Builder focuses on the act of building (I/O, git operations, process management).
  • ResultHandler focuses on the presentation of the build (summaries, error reporting, bloat analysis).

How We Got There

Refactoring a core tool like buildman is risky—regressions here can break workflows for developers everywhere. We took a stepwise approach:

  1. Extract Data Structures: We moved configuration handling (Config) and build outcomes (Outcome, BoardStatus, ErrLine) into their own modules (cfgutil.py, outcome.py). This broke the circular dependencies that often plague monolithic classes.
  2. Formalise Options: Instead of passing a dozen boolean flags (like show_errors, show_bloat) around individually, we grouped them into a DisplayOptions named tuple.
  3. Create the Handler: We introduced ResultHandler and progressively moved display methods into it—starting with simple size summaries and moving up to complex error delta calculations.
  4. Clean Up: Finally, we enforced Python naming conventions by adding underscore prefixes to private members (self.col became self._col), clarifying the public API.

The Result

The Builder class is now down to ~1200 lines—a much more manageable size. The display logic lives in tools/buildman/resulthandler.py, where it can be evolved independently.

While this doesn’t change the user-facing behaviour of buildman today, it makes the codebase significantly healthier and ready for future improvements. Sometimes the most important feature is code you can actually understand!




Modernizing Buildman: A Case Study in AI-Assisted Refactoring


U-Boot’s suite of Python tools—including buildman, patman, and binman—are critical parts of our development workflow. However, like any long-lived software project, technical debt accumulates. Buildman in particular was written a while ago, without much use of pylint and has grown significantly over the years. The accumulated pylint warnings, inconsistent naming conventions, and a monolithic structure make maintenance somewhat difficult.

On the other hand, cleaning up Python code is error-prone and not a lot of fun! A few recent series (board, general, builderthead and test.py) has shown a different path: extensive use of AI as a co-developer to accelerate the cleanup.

The Problem: Entropy

The buildman code has several issues typical of older Python codebases:

  • Monolithic Class: The TestBuild class had become a dumping ground for all tests, making it hard to navigate.
  • Legacy Formatting: Reliance on % string formatting instead of modern f-strings.
  • PEP 8 Violations: A mix of CamelCase and snake_case method names, missing docstrings, and indentation inconsistencies.
  • Pylint Noise: A high volume of warnings that obscured actual logic errors.

The AI Advantage

Refactoring code to fix style issues is often tedious and error-prone. For this series, we used Claude Opus 4.5 to handle the heavy lifting. The AI was particularly effective at:

  1. Mechanical Translations: Converting hundreds of lines from % formatting to f-strings and renaming variables from CamelCase to snake_case to match PEP 8 standards.
  2. Logic Extraction: Helping to identify seams in the code where the massive TestBuild class could be split into logical components.
  3. Docstring Generation: Analyzing test methods and generating accurate, compliant docstrings where they were missing.

By offloading these tasks, we can focus on the architectural decisions while the AI handled the syntax updates. Each commit in the series carries a Co-developed-by tag, acknowledging the AI’s contribution to the code generation.

Most importantly, this clean-up can be largely a background task. In between other work, switch back to the clean-up terminal every now and then and spend a few minutes looking at what was done and deciding what to do next.

Key Architectural Improvements

The most significant change in the latest series is the decomposition of the TestBuild class.

Previously, TestBuild handled everything from output parsing to toolchain management. Now it is split into focused classes:

  • TestBuildBase: Common setup and teardown logic.
  • TestBuildOutput: Tests specific to summary display and error formatting.
  • TestBuildBoards: Tests regarding board selection logic.
  • TestBuildConfig: Tests for Kconfig manipulation.
  • TestBuildMisc: Process limits and other edge cases.

This separation of concerns makes the tests easier to read and understand.

The other series linked have adopted a similar approach – splitting code into separate functions to make it easier to read and maintain.

AI coding tools have a clear advantage in refactoring. They can fairly reliably extract code into functions and make other adjustments that are beyond a mere ‘refactor’ tool in VS Code or Eclipse, for example. It is also easy to go back to an earlier commit in the series to bring in a change, then continue the rebase and have the AI deal with merge conflicts.

Conclusion

This cleanup so far brings most of the buildman code into full pylint compliance (with some pragmatic disable-directives where necessary). It serves as a good example of how AI tools can be leveraged to modernize legacy infrastructure code in the U-Boot project, turning a multi-day refactoring slog into a manageable, efficient process.




Faster Tests and Better Debugging: Improvements to Malloc and test/py


Developing for U-Boot often involves chasing down elusive memory leaks and waiting for long test suites to finish. A recent series of 29 patches is aimed squarely at improving the developer experience in these areas.

This series introduces powerful new malloc debugging tools, optimises the video subsystem, and significantly improves the performance of the test/py infrastructure.

Deep Dive into Malloc Debugging

Finding memory leaks in a bootloader can be painful. This series adds several features to the malloc subsystem, particularly for sandbox, to make this process more transparent.

Malloc Traffic Log

A new logging feature records every malloc, free, and realloc call into a circular buffer. This includes the pointer address, allocation size, and the caller’s backtrace.

You can control this via the new malloc log command:

=> malloc log start
Malloc logging started
=> ... run commands that might leak ...
=> malloc log stop
Malloc logging stopped
=> malloc log
Malloc log: 29 entries (max 524288, total 29)
 Seq  Type              Ptr       Size  Caller
----  --------  ----------------  --------  ------
   0  free              16a016e0         0  free_pipe_list:2001  -parse_stream_outer:3208 -parse_file_outer:3300
   1  alloc             16a01b90        20  hush_file_init:3277  -parse_file_outer:3295 -run_pipe_real:1986
...

Each entry shows the operation type, the address and size and the caller trace, so you can figure out where it happened.

File Output for Heap Dumps

To make comparing heap states easier, new functions malloc_dump_to_file() and malloc_log_to_file() allow you to write the heap state directly to a file on the host system (when running U-Boot sandbox). This enables a simple workflow for finding leaks:

  1. Dump heap to before.txt
  2. Run your test case
  3. Dump heap to after.txt
  4. Use diff or comm on the host to identify pointers that persist.

Performance Improvements

Video and Truetype Optimization

Rendering text on the video console was identified as a performance bottleneck during testing.

  • Truetype Scratch Buffer: The stb_truetype library previously performed around 5 allocations per character. We added a pre-allocated scratch buffer, drastically reducing malloc pressure.
  • Full-line Copy: The video_flush_copy() function is now optimised to detect full-line damage, allowing for single memcpy calls instead of looping pixel-by-pixel or small chunks.

Accelerating test/py

Running the full test suite can be CPU intensive. This series addresses inefficiencies in how test/py handles console output.

  • VT100 Filtering: The logic to filter escape sequences was previously O(N^2). It has been optimised to filter only new data, significantly reducing overhead on verbose tests.
  • CPU Usage: A tiny delay has been added before polling the console pipe to allow data to accumulate (up to 4KB). This simple change reduced CPU usage by approximately 30% during intensive output operations.

Additional Features

  • Gprof Support: You can now build sandbox with GPROF=1 to generate profiling data (gmon.out) for analysis with standard GNU tools.
  • Runtime Mcheck Disable: A new -M flag allows you to disable mcheck heap protection at runtime, useful when the overhead interferes with specific timing-sensitive tests.

These changes make the U-Boot sandbox a faster, more observable environment for development!




Silencing the Sphinx: Cleaner Documentation Builds

If you have ever run make htmldocs in U-Boot, you are likely familiar with the “wall of text” it produces. Between the standard Sphinx output, sub-make messages, and custom progress indicators, the build process has traditionally been very noisy.

While verbose output can be useful for debugging the toolchain itself, it is a hindrance when you are just trying to write documentation. The sheer volume of text makes it difficult to spot legitimate warnings and errors, which often get buried in the scroll.

A recent 5-part series in Concept addresses this. The goal is simple: if the build prints something, it should be something that requires your attention.

What changed?

The series cleans up the output in three specific ways:

  1. Enabling Quiet Mode: We now pass the -q flag to SPHINXOPTS and -s to the sub-make invocations. This suppresses the standard “reading sources” and “picking up dependencies” log lines.
  2. Removing Informational Clutter: We dropped the explicit print statement regarding CJK (Chinese/Japanese/Korean) font support in conf.py. The functionality remains, but we don’t need to be told about it on every run.
  3. Dropping Custom Progress Bars: The SPHINX and PARSE progress messages defined in the Makefiles have been removed.

The Result

The documentation build is now silent by default. This aligns the htmldocs target with the philosophy of the rest of the Kbuild system: silence is golden.

Now, when you run the docs build, you can be confident that any output appearing in your terminal is a warning or an error that needs to be fixed. This should make it significantly easier to maintain clean documentation and spot regressions in CI.




Modernising Allocation: U-Boot Upgrades to dlmalloc 2.8.6

For over two decades—since 2002—U-Boot has relied on version 2.6.6 of Doug Lea’s malloc (dlmalloc, old docs) to handle dynamic memory allocation. While reliable, the codebase was showing its age.

In a massive 37-patch series, we have finally updated the core allocator to dlmalloc 2.8.6. This update brings modern memory efficiency algorithms, better security checks, and a cleaner codebase, all while fighting to keep the binary size footprint minimal for constrained devices.

Why the Update?

The move to version 2.8.6 isn’t just about bumping version numbers. The new implementation offers specific technical advantages:

  • Improved Binning: The algorithm for sorting free chunks is more sophisticated, leading to better memory efficiency and less fragmentation.
  • Overflow Protection: Robust checks via MAX_REQUEST prevent integer overflows during allocation requests, improving security.
  • Reduced Data Usage: The old pre-initialised av_[] array (which lived in the data section) has been replaced with a _sm_ struct in the BSS (Block Started by Symbol) section. This change reduces usage in the data section by approximately 1.5KB, a significant win for boards with constraints on initialised data.

The Battle for Code Size

One of the biggest challenges in embedded development is code size. Out of the box, the newer dlmalloc was significantly larger than the old version—a non-starter for SPL (Secondary Program Loader) where every byte counts.

To combat this, the patch series a Kconfig option to strip down the allocator for space-constrained SPL builds (specifically via CONFIG_SYS_MALLOC_SMALL).

Key optimisations include:

  1. NO_TREE_BINS: Disables complex binary-tree bins for large allocations (>256 bytes), falling back to a simple doubly-linked list. This trades O(log n) performance for O(n) but saves ~1.25KB of code.
  2. SIMPLE_MEMALIGN: Simplifies the logic for aligned allocations, removing complex retry fallback mechanisms that are rarely needed in SPL. This saves ~100-150 bytes.
  3. NO_REALLOC_IN_PLACE: Disables the logic that attempts to resize a memory block in place. Instead, it always allocates a new block and copies data. This saves ~500 bytes.

With these adjustments enabled, the new implementation is actually smaller than the old 2.6.6 version on architectures like Thumb2.

Keeping U-Boot Features Alive

This wasn’t a clean upstream import. Over the last 20 years, U-Boot accrued many custom modifications to dlmalloc. This series carefully ports these features to the new engine:

  • Pre-relocation Malloc: Support for malloc_simple, allowing memory allocation before the main heap is initialised.
  • Valgrind Support: Annotations that allow developers to run U-Boot sandbox under Valgrind to detect memory leaks and errors.
  • Heap Protection: Integration with mcheck to detect buffer overruns and heap corruption.

New Documentation and Tests

To ensure stability, a new test suite (test/common/malloc.c) was added to verify edge cases, realloc behaviours, and large allocations. Additionally, comprehensive documentation has been added to doc/develop/malloc.rst, explaining the differences between pre- and post-relocation allocation and how to tune the new Kconfig options.

Next Steps

The legacy allocator is still available via CONFIG_SYS_MALLOC_LEGACY for compatibility testing, but new boards are encouraged to use the default 2.8.6 implementation.




Cleaning up the Nulls: Introducing ofnode Stubs for Non-DT Builds

In the world of U-Boot, the Device Model (DM) and Device Tree (DT) are the standard for hardware description. However, U-Boot runs on a massive variety of hardware, including constrained systems where full Device Tree support (OF_REAL) might be disabled.

A recent patch cleans up how the core handles these “no-Device-Tree” scenarios, ensuring that code remains clean, compilable, and safe even when the DT is missing.

The Problem: When the Tree Falls

When OF_REAL is disabled, there is logically no point in trying to find nodes, read properties, or traverse a tree—everything is effectively null.

Previously, handling this required scattering #ifdef guards throughout driver code or dealing with linking errors if a driver attempted to call an ofnode function that wasn’t compiled in. This made the codebase harder to read and harder to maintain.

The Solution: Static Inline Stubs

The patch, dm: core: Create ofnode stubs when OF_REAL is disabled introduces a comprehensive set of static inline “stub” functions in include/dm/ofnode.h.

Here is the logic:

  1. Check Configuration: The header now checks #if CONFIG_IS_ENABLED(OF_REAL).
  2. Provide Stubs: If OF_REAL is off, it defines dummy versions of functions like ofnode_read_u32 or ofnode_next_subnode.
  3. Safe Returns: These stubs return safe default values—typically NULL, false, -ENOSYS, or -EINVAL—allowing the compiler to optimise the code paths without breaking the build.

This allows drivers to call ofnode functions blindly. If the DT is missing, the function simply returns an error code, and the driver handles it gracefully rather than the build failing.

The Cost: Pushing the Limits

While this approach significantly cleans up driver code, it comes with a trade-off. include/dm/ofnode.h has grown significantly with this patch.

As noted in the commit, the file is becoming “a little convoluted” due to the sheer volume of inline implementations and dual definitions. We are likely reaching the limit of how many static inlines can reasonably live in this single header! While this solution is better than the alternative (broken builds or messy #ifdefs in drivers), future work may require splitting the header file to keep the core API definitions digestible and organised.

Key Refinements

The patch also includes some “plumbing” adjustments:

  • Macro Relocation: Iterator macros like ofnode_for_each_compatible_node() were moved outside the OF_REAL condition. Since they rely on the newly created stubs, these loops will now simply do nothing on a non-DT system, rather than causing compiler errors.
  • Edge-Case Support: A specific adjustment in drivers/Makefile ensures ofnode.o is compiled when OF_REAL is enabled but the full DM is not, preserving necessary support for boards like the kontron-sl-mx6ul.



Introducing Codman: A Deep Dive into U-Boot Build Analysis

U-Boot is a massive project. With thousands of files, nearly endless configuration possibilities, and complex Kconfig dependencies, a single board configuration often only compiles a small fraction of the total source tree.

For developers and maintainers, this complexity often leads to difficult questions:

  • “I just enabled CONFIG_CMD_NET; how much code did that actually add?”
  • “How much bloat would I remove by disabling CONFIG_CMDLINE?”
  • “Which specific lines of this driver are active for my board?”

Simply searching for CONFIG_ macros or header inclusions is rarely enough. The build logic takes many forms—Makefile rules, #ifdefs, IS_ENABLED(), and static inlines—making static analysis tricky.

Enter Codman (Code Manager), a new tool designed to cut through this complexity by analysing the actual build artefacts generated by the compiler.

What is Codman?

Codman is a Python-based tool located in tools/codman/. It works out exactly which source files and lines of code are compiled and used for a specific board. It works by:

  1. Building the specified board (or using an existing build).
  2. Parsing .cmd files to find which source files were compiled.
  3. Analysing the source code (using a specialized unifdef) or object files (using DWARF tables) to figure out exactly which lines made it into the final binary.

Feature Highlight: Impact Analysis

One of Codman’s most powerful features is Impact Analysis. This allows you to explore “what if” scenarios without manually editing defconfig files or running menuconfig.

Using the -a (adjust) flag, you can modify the Kconfig configuration on the fly before the analysis runs. This is perfect for seeing exactly how much code a specific feature adds.

Example: Checking the impact of USB To enable the USB subsystem on the sandbox board and see how the code stats change:

./tools/codman/codman.py -b sandbox -a CMD_USB stats

Example: Disabling Networking To see what code remains active when networking is explicitly disabled:

./tools/codman/codman.py -b sandbox -a ~NET,NO_NET stats

Visualising the Build

Codman provides several ways to view your build data.

1. High-Level Statistics The stats command gives you a bird’s-eye view of your build size.

$ codman -b qemu-x86 stats
======================================================================
FILE-LEVEL STATISTICS
======================================================================
Total source files:    14114
Used source files:      1046 (7.4%)
Unused source files:   13083 (92.7%)

Total lines of code:  3646331
Used lines of code:    192543 (5.3%)

2. Directory Breakdown Use dirs to see which subsystems are contributing the most weight to your board.

$ codman dirs
BREAKDOWN BY TOP-LEVEL DIRECTORY
Directory        Files    Used  %Used  %Code     kLOC    Used
-------------------------------------------------------------
arch               234     156     67     72     12.3     8.9
board              123      45     37     25      5.6     1.4
cmd                 89      67     75     81      3.4     2.8
...

You can also break down the information by showing subdirectories (-s) or even individual files (-f).

$ codman -n -b qemu-x86 dirs --subdirs  -f
=======================================================================================
BREAKDOWN BY TOP-LEVEL DIRECTORY
=======================================================================================
Directory                                  Files    Used  %Used  %Code     kLOC    Used
---------------------------------------------------------------------------------------
arch/x86/cpu                                  20      15     75     85      3.8     3.2
  start.S                                    318     190   59.7     128
  cpu.c                                      399     353   88.5      46
  mp_init.c                                  902     877   97.2      25
  turbo.c                                    103      92   89.3      11
  lapic.c                                    158     156   98.7       2
  resetvec.S                                  18      18  100.0       0
  pci.c                                      100     100  100.0       0
  mtrr.c                                     455     455  100.0       0
  start16.S                                  123     123  100.0       0
  sipi_vector.S                              215     215  100.0       0
  ioapic.c                                    36      36  100.0       0
  call32.S                                    61      61  100.0       0
  qfw_cpu.c                                   86      86  100.0       0
  irq.c                                      366     366  100.0       0
  cpu_x86.c                                   99      99  100.0       0
arch/x86/cpu/i386                              4       4    100     98      1.4     1.4
  cpu.c                                      649     630   97.1      19
  interrupt.c                                630     622   98.7       8
  call64.S                                    92      92  100.0       0
  setjmp.S                                    65      65  100.0       0
arch/x86/cpu/intel_common                     18       6     33     23      3.3     0.8
  microcode.c                                187     183   97.9       4
  pch.c                                       23      23  100.0       0
  lpc.c                                      100     100  100.0       0

3. Line-by-Line Detail Perhaps the most useful feature for debugging configuration issues is the detail view. It shows you exactly which lines are active or inactive within a file.

$ codman -b qemu-x86 detail common/main.c
...
    24 | static void run_preboot_environment_command(void)
    25 | {
    26 | 	char *p;
    27 | 
    28 | 	p = env_get("preboot");
    29 | 	if (p != NULL) {
    30 | 		int prev = 0;
    31 | 
-   32 | 		if (IS_ENABLED(CONFIG_AUTOBOOT_KEYED))
-   33 | 			prev = disable_ctrlc(1); /* disable Ctrl-C checking */
    34 | 
    35 | 		run_command_list(p, -1, 0);
    36 | 
-   37 | 		if (IS_ENABLED(CONFIG_AUTOBOOT_KEYED))
-   38 | 			disable_ctrlc(prev);	/* restore Ctrl-C checking */
    39 | 	}
    40 | }
    41 | 
...

(Lines marked with - are not included in the build and show in a different colour)

Under the Hood: Unifdef vs. DWARF

Codman supports two methods for analysis:

  • Unifdef (Default): Simulates the C preprocessor to determine which lines are active based on CONFIG_ settings. It is fast (leveraging multiprocessing) and provides a great preprocessor-level view. It uses a patched version of unifdef that supports U-Boot’s IS_ENABLED() macros.
  • DWARF (-w): Rebuilds the project with debug info and analyses the DWARF line number tables. This is highly accurate for executable code but won’t count declarations or comments.

Getting Started

# Basic stats for sandbox
./tools/codman/codman.py -b sandbox stats

# Find unused files
./tools/codman/codman.py -b sandbox unused

# Extract only used sources to a new directory (great for minimal distributions)
./tools/codman/codman.py -b sandbox copy-used /tmp/minimal-tree

Check the documentation for more details!