This shows you the differences between two versions of the page.
|
ass:labs-2025:02 [2026/07/14 15:47] florin.stancu |
ass:labs-2025:02 [2026/07/14 15:48] (current) florin.stancu |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ==== 01. Preparing the Linux μImage ==== | + | ~~NOTOC~~ |
| - | There are three vital components that are required to successfully boot Linux on ARM: | + | ===== 02 - Bootstrapping Linux ===== |
| - | * **Kernel image:** This should be self-evident :P | + | ===== Objectives ===== |
| - | * **Flattened Device Tree:** A blob (''.dtb'') of compiled hierarchical data that the kernel parses to identify hardware elements; | + | |
| - | * **RootFS:** A file system that can be mounted at ''/'' in order to load the **init** executable. Here, we will use a ramdisk image (which will be //%%**%%surprise pause%%**%%// stored in RAM!). | + | |
| - | Although these components can be manually loaded and configured for boot from U-Boot, it's preferable to package them together in a Flattened μImage Tree (FIT). Similarly to how **bl1** and **bl2** know how to load the binaries that comprise the FIP we generated in the previous lab, so does **bl33** know how to boot the system from a FIT. | + | * Distinguish a Linux OS's various components (kernel, rootfs, initramfs) |
| + | * Use Buildroot to make a Linux root filesystem from scratch | ||
| + | * Configure & manually compile a Linux Kernel | ||
| + | * Build everything as a single disk image | ||
| - | In the following tasks we will create each individual component and package them together. Finally, we will create a partition table (with one FAT partition) on the board's eMMC and store the FIT only to then load it and boot it from the **bl33** shell. | + | ===== Contents ===== |
| - | === Task 1.1. Compile the Linux kernel === | + | {{indexmenu>:ass:labs-2025:02:tasks|skipfile}} |
| - | Clone the kernel from the [[https://github.com/torvalds/linux/|official GitHub repo]], using the ''v7.1'' tag. | + | ===== Lecture ===== |
| - | We recommend you to use [[https://stackoverflow.com/questions/1778088/how-do-i-clone-a-single-branch-in-git|this git cloning technique]] to avoid fetching the entire git history (requiring several GBs of disk space!). | + | |
| - | Since you've built U-Boot previously, you should be somewhat familiar with the Kbuild system. Start by generating the build configuration from its [[https://github.com/torvalds/linux/tree/v6.12/arch/arm64/configs|default configuration]]. Optionally, run a ''menuconfig'' to inspect the Linux kernel options available. | + | Before beginning the tasks, please check out the [[:ass:cursuri:02|lecture slides & notes here]]. |
| - | <note warning> | + | ===== Tasks ===== |
| - | You must set the ''ARCH'' argument to the appropriate architecture **AT ALL TIMES** when invoking linux's ''make''! \\ | + | |
| - | Check out the subdirectories in ''<your-linux-src-dir>/arch/'' for possible values. \\ | + | |
| - | Also, do not forget about the ''CROSS_COMPILE'' environment variable (export it inside your terminal / Makefile). | + | |
| - | \\ | + | |
| - | Our recommendation is use your own Makefile to invoke ''make'' commands on the kernel source tree with the required arguments! | + | |
| - | </note> | + | |
| - | In order to build the kernel image, simply ''make'' with the required variables (setting ''ARCH=?'' -- see the warning above!). | + | {{namespace>:ass:labs-2025:02:tasks&nofooter&noeditbutton}} |
| - | + | ||
| - | <note tip> | + | |
| - | Paralellize the build process using ''make ... -j <num_CPUs>'' (use ''6-8'' on the VM, do not use all ''16'' as you're not alone in there!), unless you want to waste an hour for Linux to finish building. | + | |
| - | </note> | + | |
| - | + | ||
| - | While waiting for the build, explore the Linux source using your favorite code editor (especially [[https://github.com/torvalds/linux/tree/v6.12/arch/arm64/boot/dts/freescale|the device trees]])! | + | |
| - | + | ||
| - | Note that the kernel image you will be including in the FIP is called **Image**. Unlike **vmlinux** which is an ELF file (it contains some useless-for-now sections like //.debuginfo// if you want to debug the kernel), **Image** is a boot executable image, made specifically for the CPU to jump straight in and start executing code from RAM. After the build process finishes, find the **Image** file within the ''<linux-src-dir>/arch/arm64/boot/'' output directory. | + | |
| - | + | ||
| - | <note tip> | + | |
| - | **Useful tip for kernel development** (if you wish to browse / modify the kernel code): \\ | + | |
| - | After you finish an initial build of the kernel, consider running the following script: | + | |
| - | + | ||
| - | <code bash> | + | |
| - | ./scripts/clang-tools/gen_compile_commands.py | + | |
| - | </code> | + | |
| - | + | ||
| - | This will generate a ''compile_commands.json'' file that contains the **gcc** invocation cmdline. Any half decent LSP-based IDE (VSCode, [[http://astronvim.com/|AstroNVim]] -- which is installed on the armbuilder VM) will be able to (automatically) parse this file and deduce the include paths and definitions used. This will enable features like [[https://neovim.io/doc/user/lsp.html#vim.lsp.buf.definition()|go-to-definition]] between different source files and much more. | + | |
| - | + | ||
| - | Note that this script only works on the current project. For a more generic tool, try [[https://github.com/rizsotto/Bear|bear]]. Be warned though that it's LD_PRELOAD hooking of ''exec()'' calls (needed to extract the cmdargs) interferes with the [[https://www.gnu.org/software/automake/|Automake]] configuration stage. | + | |
| - | </note> | + | |
| - | + | ||
| - | === Task 1.2. Find/save the Device Tree === | + | |
| - | + | ||
| - | Luckily, the Flattened Device Tree (FDT -- ''.dtb'' file) for our platform is also included in Linux. It's name should be ''%%imx93-11x11-evk.dtb%%'' (generated from its source counterpart, [[https://github.com/torvalds/linux/blob/v6.6/arch/arm64/boot/dts/freescale/imx93-11x11-evk.dts|imx93-11x11-evk.dts]]). | + | |
| - | + | ||
| - | If for some reason it wasn't built alongside the kernel Image, you can always use the ''make ... dtbs'' command to [re]compile them. | + | |
| - | + | ||
| - | === Task 1.3. Build a Root FileSystem === | + | |
| - | + | ||
| - | The kernel alone does NOT make a useable Operating System. It needs some userspace applications to at least supply a command-line interface for the human users (we won't be building a graphical UI, as it was to be expected). This means we have to find a way to build a root filesystem containing such programs and their required dependencies (shared libraries, configuration files etc.). | + | |
| - | + | ||
| - | Usually there are two approaches to generating the root filesystem: bootstrapping from a source of pre-compiled binaries (e.g.: [[https://man.archlinux.org/man/debootstrap.8.en|debootstrap]], [[https://man.archlinux.org/man/pacstrap.8|pacstrap]], etc.) or building them yourself from source (e.g.: [[https://www.yoctoproject.org/yocto|Yocto]], [[https://buildroot.org/|Buildroot]].) The former is usually preferred when working on desktop environments. The latter allows you to fine tune everything that is installed on your system and obtain much less disk space consumption! | + | |
| - | + | ||
| - | Between Yocto and Buildroot, we generally opt for Buildroot. While Yocto has many advantages (e.g.: wide industry adoption, extensive device support, etc.), it also has a very steep learning curve and consumes significant amounts of resources (i.e.: ~50-100GB storage space for a build). Although it has a layer-based build customization feature (think Dockerfiles extending base images), we argue that this makes it more difficult to comprehend the contents of what is being built. | + | |
| - | + | ||
| - | Buildroot on the other hand is geared towards simplicity and ease of use. Being based on Kbuild and Makefile (same as Linux and U-Boot) makes it instantly familiar to most developers. And even if you are new to this, while Yocto requires you to read entire [[https://www.goodreads.com/search?q=yocto|books]] in order to utilize it properly, Buildroot can be summed up in a 1k LoC Makefile. | + | |
| - | + | ||
| - | == Download Buildroot == | + | |
| - | + | ||
| - | Clone the official [[https://github.com/buildroot/buildroot.git|Buildroot repo]]. We used the latest release, ''2026.05.x'', so make sure to fetch that branch (master should also work, but it is in continuous development and may break at any time! use the versioned branch if you want a hassle-free experience). | + | |
| - | + | ||
| - | == Create the configuration == | + | |
| - | + | ||
| - | You can find all KConfig files inside ''<buildroot-src>/configs/'' directory. There is even one for our ''imx93'' platform, but **we won't use it**! | + | |
| - | + | ||
| - | Buildroot also integrates the bootloader & kernel into its build system. However, such configurations won't work for our specific board and keeping the components separate makes it easier to appply patches and debug problems. | + | |
| - | + | ||
| - | What we want from Buildroot is a minimal root filesystem and nothing more. So we'll start from a basic ''defconfig'' (that's it!), then proceed with ''menuconfig'' and make the following changes: | + | |
| - | + | ||
| - | * Target Options -> //Aarch64 Little Endian//; | + | |
| - | * To optimize: Model -> Corted-A55; | + | |
| - | * Enter System Configuration menu. | + | |
| - | * You can change the //System Banner// to your own liking; | + | |
| - | * Make sure you have Busybox as the //Init System//; | + | |
| - | * Enable root login with password, then set your desired password on the config below... | + | |
| - | * Choose **bash** as the default shell instead of **sh**;. | + | |
| - | * Include a text editor PACKAGE (e.g.: search for ''PACKAGE_<PAKCKAGE_NAME>'', **VIM** or **nano**)! | + | |
| - | * You can optionally choose the ''python3'' package! | + | |
| - | * Generate an **uncompressed CPIO** image from the output file system (enable the ''TARGET_ROOTFS_CPIO'' option, see the Note below). | + | |
| - | + | ||
| - | <note> | + | |
| - | Note how the the configuration variables are prefixed with ''BR2_'', packages with ''BR2_PACKAGE_'' and so on! | + | |
| - | </note> | + | |
| - | + | ||
| - | <note> | + | |
| - | The CPIO archive is the preferred method of packaging the //initramfs//. We could also do it manually using the **cpio** tool (see example below) but since the option is there... why not? | + | |
| - | + | ||
| - | <code bash> | + | |
| - | cd /path/to/rootfs | + | |
| - | find . | cpio -o > /path/to/ramdisk.cpio | + | |
| - | </code> | + | |
| - | </note> | + | |
| - | + | ||
| - | Feel free to add / remove anything else you want. Anything goes as long as you end up with a functioning system. | + | |
| - | After completing Step 3, maybe return and see if there's anything left to trim down to speed up the build process. | + | |
| - | + | ||
| - | == Build & inspect the rootfs == | + | |
| - | + | ||
| - | Not much to it, really, simply invoke ''make -j <NUM.CPUS>'' (no other parameters are required for Buildroot, no ''ARCH'' like the Linux Kernel). | + | |
| - | Once everything's done, check out the ''output/'' directory. What does it contain? Where is your CPIO archive? | + | |
| - | + | ||
| - | <note important> | + | |
| - | Chances are that you're going to screw something up while playing around with the config file. Check the error message and if it's not immediately obvious try to find the makefile / script where things go awry. If a ''grep -rn ${ERROR_MESSAGE}'' doesn't help, try to run **make** with the ''V=1'' argument for verbose output, but without ''-j''! | + | |
| - | </note> | + | |
| - | + | ||
| - | === Task 1.4. Compile the final FIT Image === | + | |
| - | + | ||
| - | Like the ''flash.bin'' firmware package, we need to pack the kernel, FDT and rootfs together in a single file for convenience in booting. | + | |
| - | + | ||
| - | Create a ''staging/'' directory somewhere in your work dir and copy everything that we've obtained from the previous three tasks (see below for the files to be included inside the FIT). Then, create an Image Tree Source (ITS) file. We'll be referring to it as ''linux.its'' but the name doesn't really matter. What matters is the content: | + | |
| - | + | ||
| - | <spoiler linux.its> | + | |
| - | <code> | + | |
| - | /dts-v1/; | + | |
| - | + | ||
| - | / { | + | |
| - | description = "Linux FIT image for FRDM iMX93"; | + | |
| - | #address-cells = <1>; | + | |
| - | + | ||
| - | images { | + | |
| - | kernel { | + | |
| - | description = "Linux kernel"; | + | |
| - | data = /incbin/("Image"); | + | |
| - | type = "kernel"; | + | |
| - | arch = "arm64"; | + | |
| - | os = "linux"; | + | |
| - | compression = "none"; | + | |
| - | /* load + entrypoint address (e.g., <0x12340000>), MUST BE VALID DRAM REGION! | + | |
| - | * also, do NOT ommit the '<>' characters!) */ | + | |
| - | load = <XXX>; | + | |
| - | entry = <XXX>; | + | |
| - | }; | + | |
| - | fdt { | + | |
| - | description = "Device tree"; | + | |
| - | data = /incbin/("imx93-11x11-evk.dtb"); | + | |
| - | type = "flat_dt"; | + | |
| - | arch = "arm64"; | + | |
| - | compression = "none"; | + | |
| - | /* compute the next available DRAM address (e.g., add 64MB to the previous one) */ | + | |
| - | load = <YYY>; | + | |
| - | }; | + | |
| - | initrd { | + | |
| - | description = "Ramdisk"; | + | |
| - | data = /incbin/("rootfs.cpio"); | + | |
| - | type = "ramdisk"; | + | |
| - | arch = "arm64"; | + | |
| - | os = "linux"; | + | |
| - | compression = "none"; | + | |
| - | /* same, compute a valid next address (FDT is pretty small, couple of kilos) */ | + | |
| - | load = <ZZZ>; | + | |
| - | }; | + | |
| - | }; | + | |
| - | + | ||
| - | configurations { | + | |
| - | default = "normal-boot"; | + | |
| - | + | ||
| - | normal-boot { | + | |
| - | description = "Normal boot config"; | + | |
| - | kernel = "kernel"; | + | |
| - | fdt = "fdt"; | + | |
| - | ramdisk = "initrd"; | + | |
| - | }; | + | |
| - | }; | + | |
| - | }; | + | |
| - | </code> | + | |
| - | </spoiler> | + | |
| - | \\ | + | |
| - | + | ||
| - | Let's analyze this: | + | |
| - | + | ||
| - | * The file starts with ''/dts-v1/;'', identifying it as a Device Tree Source. | + | |
| - | * Next, we have a root ''/'' node with two child nodes: ''images'' and ''configurations''. | + | |
| - | * ''images'' contains the description of each binary that will need to be loaded by U-Boot when processing the FIT. | + | |
| - | * Each image entry contains an [[https://git.kernel.org/pub/scm/utils/dtc/dtc.git/commit/?id=e37ec7d5889fa04047daaa7a4ff55150ed7954d4|incbin]] directive that tells **mkimage** to copy paste the contents of the specified file into the output DTB. | + | |
| - | * Each image also contains a ''load'' property, specifying the address where the ''data'' will be placed. | + | |
| - | * In addition to ''load'', the kernel image also has an ''entry'' property specifying where U-Boot will jump when relinquishing control to the Linux kernel. | + | |
| - | * ''configurations'' contains sets of image configurations that can be chained. | + | |
| - | * The only configuration we have is also the ''default'': ''normal-boot''. | + | |
| - | * Notice how it has pre-defined attributes for ''kernel'', ''fdt'', ''ramdisk''. These are not simple binary blobs; instead, they each have a role to play in the boot sequence. E.g., U-Boot will know to take the ''load'' address of the ''fdt'' image and pass it via a certain register (decided by convention) to the ''kernel'', informing it where in memory to look for the FDT. | + | |
| - | + | ||
| - | Note how we replaced the ''load'' and ''entry'' addresses with placeholders such as //XXX//. Replace these with whatever addresses you want such as the binaries do not overlap once U-Boot starts loading them (e.g.: you can start from ''0x81000000'' -- 64MB offset in DRAM, then add the size of the artifact rounded to 1-64MB for the next address and so on). | + | |
| - | + | ||
| - | <note tip> | + | |
| - | Before deciding the address, find out: | + | |
| - | + | ||
| - | * at what physical address does the RAM start (check out the iMX93 Memory Map); | + | |
| - | * how much RAM do you have; | + | |
| - | * how large is each included file! | + | |
| - | + | ||
| - | One thing to note: U-Boot is also located somewhere in RAM. During its initialization stage, no matter where **bl2** placed it, it will relocate itself towards the end of RAM. Make sure not to overwrite the FIT unpacking code while unpacking the FIT :) | + | |
| - | + | ||
| - | //Hint:// ''bdinfo'' holds all the answers. | + | |
| - | </note> | + | |
| - | + | ||
| - | Once all the addresses are filled in, generate the Image Tree Blob (ITB) using **mkimage**. Best not to use the **imx-mkimage**; instead, install **mkimage** using your distro's package manager (or find it pre-compiled by U-Boot at ''<uboot-src-path>/tools/mkimage''). | + | |
| - | + | ||
| - | <code bash> | + | |
| - | ../<path-to-uboot>/tools/mkimage -f linux.its linux.itb | + | |
| - | </code> | + | |