From: Simon Glass <simon.glass@canonical.com> Add documentation for the PXE/extlinux parser API, covering both the traditional callback-based approach and the new callback-free API. The callback-free API separates parsing, loading, and booting into distinct phases, giving callers complete control over file operations. During parsing, the code collects file information (kernel, initrd, FDT, overlays) into a list that the caller can iterate over to load files manually. The documentation explains the file types, include handling, and provides an example showing typical usage of the callback-free API. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- doc/develop/bootstd/index.rst | 1 + doc/develop/bootstd/pxe_api.rst | 194 ++++++++++++++++++++++++++++++++ include/pxe_utils.h | 2 + 3 files changed, 197 insertions(+) create mode 100644 doc/develop/bootstd/pxe_api.rst diff --git a/doc/develop/bootstd/index.rst b/doc/develop/bootstd/index.rst index 342228064e6..35b7065aaad 100644 --- a/doc/develop/bootstd/index.rst +++ b/doc/develop/bootstd/index.rst @@ -9,6 +9,7 @@ Standard Boot overview extlinux pxelinux + pxe_api qfw android cros diff --git a/doc/develop/bootstd/pxe_api.rst b/doc/develop/bootstd/pxe_api.rst new file mode 100644 index 00000000000..18e4b6d8563 --- /dev/null +++ b/doc/develop/bootstd/pxe_api.rst @@ -0,0 +1,194 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +PXE Parser API +============== + +The PXE parser handles configuration files in the extlinux/syslinux format, +which is widely used for boot menus on both local storage and network-boot +environments. This document describes the internal API for parsing these +configuration files and booting the selected operating system. + +Background +---------- + +The extlinux configuration format provides a simple way to define multiple boot +options, each with its own kernel, initial ramdisk, device tree, and command +line arguments. 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 + +The parser reads this configuration, presents a menu if appropriate, and boots +the selected label by loading the kernel and associated files into memory. + +Traditional API +--------------- + +The traditional approach uses callback functions to load files as they are +needed during parsing and booting. This works well when the caller has direct +access to the storage device and wants the PXE code to handle all file +operations. + +The entry point is ``pxe_process()``, which parses the configuration file, +handles the menu interaction, and boots the selected label. Before calling +this function, the caller must set up a context using ``pxe_setup_ctx()``, +providing a callback function that knows how to read files from the +appropriate source. + +The callback function receives a filename and a memory address, and is +responsible for loading the file contents to that address. This abstraction +allows the same parsing code to work with local filesystems, network TFTP, +or any other file source. + +When the parser encounters an ``include`` directive, it automatically calls +the callback to load the included file, then parses its contents. This +happens recursively for nested includes, up to a maximum depth of 16 levels. +The caller does not need to handle includes explicitly. + +For cases where the caller wants to inspect the parsed configuration before +booting, ``pxe_probe()`` provides a way to parse and select a label without +immediately booting. The caller can then examine the selected label's +properties and call ``pxe_boot()`` when ready to proceed. + +Callback-free API +----------------- + +Some callers prefer to handle file loading themselves rather than providing +callbacks. This is particularly useful in environments where file access +requires special handling, or where the caller wants complete control over +memory allocation and file placement. + +The callback-free API separates the boot process into distinct phases, giving +the caller full visibility into what files are needed and where they should +be loaded. + +The first phase uses ``pxe_parse()`` to parse the configuration file and +return a context containing a menu structure. The caller must first load the +configuration file into memory at a known address, then pass that address +and size to the parser. The function allocates and initialises the context +internally, so there is no need to call ``pxe_setup_ctx()`` beforehand. + +Note that ``pxe_parse()`` does not process ``include`` directives +automatically, since there is no callback to load files. The caller must +handle includes explicitly after parsing, as described below. + +During parsing, the code collects information about all files referenced by +each label. These are stored in a files list within each label structure, +with each entry recording the file path and type. The types distinguish +between kernels, initial ramdisks, device trees, and device tree overlays, +allowing the caller to handle each appropriately. + +After parsing, the caller can examine the menu structure to see what labels +are available and what files each one requires. For labels that use the +``include`` directive, the caller must load each included file and call +``pxe_parse_include()`` to merge its contents into the menu. The includes +list may grow as included files reference further includes, so the caller +should process includes in a loop until none remain. + +The second phase involves loading the files for the selected label. The +caller iterates over the label's files list, loads each file to an +appropriate memory address, and calls ``pxe_load()`` to record where the +file was placed. This function simply stores the address and size in the +file structure for later use. + +The final phase boots the selected label using ``pxe_boot()``. The caller +sets ``ctx->label`` to point to the selected label, and the function +automatically retrieves the kernel, initial ramdisk, and device tree +addresses from the files list. It then invokes the boot process, which +does not return on success. + +File Types +---------- + +The files list uses an enumeration to distinguish between different file +types. ``PFT_KERNEL`` indicates the kernel image, which may be a raw binary, +a compressed image, or a FIT image containing multiple components. +``PFT_INITRD`` marks the initial ramdisk, which the kernel uses as a +temporary root filesystem during early boot. ``PFT_FDT`` identifies the +flattened device tree that describes the hardware to the kernel. Finally, +``PFT_FDTOVERLAY`` marks device tree overlay files that modify the base +device tree, typically used to enable optional hardware or adjust +configuration. + +The caller can use these types to determine appropriate load addresses for +each file, or to apply special handling such as decompression or +verification. + +Include Handling +---------------- + +Configuration files may use the ``include`` directive to incorporate +additional configuration from other files. When using the traditional API +with callbacks, includes are processed automatically during parsing. + +With the callback-free API, includes require explicit handling. After the +initial parse, the menu's includes list contains entries for each include +directive encountered. Each entry records the path to the included file +and the nesting level. + +The caller loads each included file, adds a null terminator to the buffer +since the parser expects null-terminated strings, and calls +``pxe_parse_include()`` to parse and merge the contents. This may add more +entries to the includes list if the included file itself contains include +directives. Processing continues until all includes have been handled. + +The parser enforces a maximum nesting depth to prevent infinite recursion +from circular includes. + +Example Usage +------------- + +A typical use of the callback-free API follows this pattern:: + + struct pxe_context *ctx; + struct pxe_menu *menu; + struct pxe_label *label; + struct pxe_file *file; + ulong addr = CONFIG_SYS_LOAD_ADDR; + ulong file_addr; + ulong size; + + /* Load and parse the configuration file */ + size = load_config_file("/extlinux/extlinux.conf", addr); + ctx = pxe_parse(addr, size, "/extlinux/extlinux.conf"); + menu = ctx->cfg; + + /* Process any include directives */ + for (i = 0; i < menu->includes.count; i++) { + const struct pxe_include *inc; + + inc = alist_get(&menu->includes, i, struct pxe_include); + size = load_file(inc->path, addr); + pxe_parse_include(ctx, inc, addr, size); + } + + /* Select a label (here we just take the first one) */ + label = list_first_entry(&menu->labels, struct pxe_label, list); + + /* Load all files for this label */ + file_addr = KERNEL_LOAD_ADDR; + alist_for_each(file, &label->files) { + size = load_file(file->path, file_addr); + pxe_load(file, file_addr, size); + file_addr += ALIGN(size, SZ_64K); + } + + /* Boot - pxe_boot() gets addresses from the files list */ + ctx->label = label; + pxe_boot(ctx); + + /* Clean up (only reached if boot fails) */ + pxe_cleanup(ctx); + +This approach gives the caller complete control over file loading while +still benefiting from the parser's understanding of the configuration +format. diff --git a/include/pxe_utils.h b/include/pxe_utils.h index bc9e3f29417..48d36bdd14c 100644 --- a/include/pxe_utils.h +++ b/include/pxe_utils.h @@ -23,6 +23,8 @@ * pxe, and does a tftp download of a file listed as an include file in the * middle of the parsing operation. That could be handled by refactoring it to * take a 'include file getter' function. + * + * See doc/develop/bootstd/pxe_api.rst for API documentation. */ /** -- 2.43.0