This shows you the differences between two versions of the page.
|
ass:labs-2025:03:tasks:01 [2025/08/06 12:00] florin.stancu |
ass:labs-2025:03:tasks:01 [2026/07/15 15:33] (current) florin.stancu [01. Automatically booting our Linux] |
||
|---|---|---|---|
| Line 2: | Line 2: | ||
| You are probably be tired of using u-boot CLI to load the Linux FIT uImage into RAM and booting it manually. | You are probably be tired of using u-boot CLI to load the Linux FIT uImage into RAM and booting it manually. | ||
| - | Surely, there must be something to be done to automate this... and you're right! Let's learn to do this! | + | Surely, there must be something to be done to automate this (yep, there is!). |
| - | TODO | + | First, let's learn about u-boot environment more... Start your board to a ''u-boot'' prompt and enter ''env print''... |
| + | |||
| + | <spoiler env print output...> | ||
| + | <code bash> | ||
| + | arch=arm | ||
| + | auth_os=auth_cntr ${cntr_addr} | ||
| + | baudrate=115200 | ||
| + | board=imx93_frdm | ||
| + | board_name=imx93_frdm | ||
| + | boot_a_script=load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} ${prefix}${script}; source ${scriptaddr} | ||
| + | boot_efi_binary=load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} efi/boot/bootaa64.efi; if fdt addr -q ${fdt_addr_r}; then bootefi ${kernel_addr_r} ${fdt_addr_r};else bootefi ${kernel_addr_r} ${fdtcontroladdr};fi | ||
| + | boot_efi_bootmgr=if fdt addr -q ${fdt_addr_r}; then bootefi bootmgr ${fdt_addr_r};else bootefi bootmgr;fi | ||
| + | boot_extlinux=sysboot ${devtype} ${devnum}:${distro_bootpart} any ${scriptaddr} ${prefix}${boot_syslinux_conf} | ||
| + | boot_fit=no | ||
| + | boot_net_usb_start=usb start | ||
| + | boot_os=booti ${loadaddr} - ${fdt_addr_r}; | ||
| + | boot_prefixes=/ /boot/ | ||
| + | boot_script_dhcp=boot.scr.uimg | ||
| + | boot_scripts=boot.scr.uimg boot.scr | ||
| + | boot_syslinux_conf=extlinux/extlinux.conf | ||
| + | boot_targets=mmc0 mmc1 usb0 | ||
| + | bootargs=console=ttyLP0,115200 earlycon,115200 rdinit=/linuxrc clk_ignore_unused | ||
| + | bootcmd=run sr_ir_v2_cmd;run distro_bootcmd;run bsp_bootcmd | ||
| + | bootcmd_mfg=run mfgtool_args;if iminfo ${initrd_addr}; then if test ${tee} = yes; then bootm ${tee_addr} ${initrd_addr} ${fdt_addr}; else booti ${loadaddr} ${initrd_addr} ${fdt_addr}; fi; else echo "Run fastboot ..."; fastboot auto; fi; | ||
| + | bootcmd_mmc0=devnum=0; run mmc_boot | ||
| + | bootcmd_mmc1=devnum=1; run mmc_boot | ||
| + | bootcmd_usb0=devnum=0; run usb_boot | ||
| + | bootdelay=2 | ||
| + | bootm_size=0x10000000 | ||
| + | bootscript=echo Running bootscript from mmc ...; source | ||
| + | bsp_bootcmd=echo Running BSP bootcmd ...; mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if test ${sec_boot} = yes; then if run loadcntr; then run mmcboot; else run netboot; fi; else if run loadimage; then run mmcboot; else run netboot; fi; fi; fi; fi; | ||
| + | cntr_addr=0x98000000 | ||
| + | cntr_file=os_cntr_signed.bin | ||
| + | console=ttyLP0,115200 earlycon | ||
| + | cpu=armv8 | ||
| + | distro_bootcmd=for target in ${boot_targets}; do run bootcmd_${target}; done | ||
| + | efi_dtb_prefixes=/ /dtb/ /dtb/current/ | ||
| + | emmc_dev=0 | ||
| + | ethprime=eth1 | ||
| + | fdt_addr=0x83000000 | ||
| + | fdt_addr_r=0x83000000 | ||
| + | fdt_high=0xffffffffffffffff | ||
| + | fdtfile=imx93-11x11-frdm.dtb | ||
| + | image=Image | ||
| + | initrd_addr=0x83800000 | ||
| + | initrd_high=0xffffffffffffffff | ||
| + | jh_mmcboot=setenv fdtfile ${jh_root_dtb}; setenv jh_clk clk_ignore_unused mem=1248MB kvm-arm.mode=nvhe; if run loadimage; then run mmcboot;else run jh_netboot; fi; | ||
| + | jh_netboot=setenv fdtfile ${jh_root_dtb}; setenv jh_clk clk_ignore_unused mem=1248MB kvm-arm.mode=nvhe; run netboot; | ||
| + | jh_root_dtb=imx93-11x11-frdm-root.dtb | ||
| + | kboot=booti | ||
| + | kernel_addr_r=0x80400000 | ||
| + | load_efi_dtb=load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} ${prefix}${efi_fdtfile} | ||
| + | loadaddr=0x80400000 | ||
| + | loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script}; | ||
| + | loadcntr=fatload mmc ${mmcdev}:${mmcpart} ${cntr_addr} ${cntr_file} | ||
| + | loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr_r} ${fdtfile} | ||
| + | loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image} | ||
| + | mfgtool_args=setenv bootargs console=${console},${baudrate} rdinit=/linuxrc clk_ignore_unused | ||
| + | mmc_boot=if mmc dev ${devnum}; then devtype=mmc; run scan_dev_for_boot_part; fi | ||
| + | mmcargs=setenv bootargs ${jh_clk} ${mcore_clk} console=${console} root=${mmcroot} | ||
| + | mmcautodetect=yes | ||
| + | mmcboot=echo Booting from mmc ...; run mmcargs; if test ${sec_boot} = yes; then if run auth_os; then run boot_os; else echo ERR: failed to authenticate; fi; else if test ${boot_fit} = yes || test ${boot_fit} = try; then bootm ${loadaddr}; else if run loadfdt; then run boot_os; else echo WARN: Cannot load the DT; fi; fi;fi; | ||
| + | mmcdev=0 | ||
| + | mmcpart=1 | ||
| + | mmcroot=/dev/mmcblk1p2 rootwait rw | ||
| + | netargs=setenv bootargs ${jh_clk} ${mcore_clk} console=${console} root=/dev/nfs ip=dhcp nfsroot=${serverip}:${nfsroot},v3,tcp | ||
| + | netboot=echo Booting from net ...; run netargs; if test ${ip_dyn} = yes; then setenv get_cmd dhcp; else setenv get_cmd tftp; fi; if test ${sec_boot} = yes; then ${get_cmd} ${cntr_addr} ${cntr_file}; if run auth_os; then run boot_os; else echo ERR: failed to authenticate; fi; else ${get_cmd} ${loadaddr} ${image}; if test ${boot_fit} = yes || test ${boot_fit} = try; then bootm ${loadaddr}; else if ${get_cmd} ${fdt_addr_r} ${fdtfile}; then run boot_os; else echo WARN: Cannot load the DT; fi; fi;fi; | ||
| + | nodes=/usbg1 /usbg2 /wdt-reboot /rm67199_panel /dsi-host /display-subsystem /soc@0/bus@44000000/dma-controller@44000000 /soc@0/bus@44000000/sai@443b0000 /soc@0/bus@44000000/mqs1 /soc@0/bus@44000000/bbnsm@44440000 /soc@0/bus@44000000/system-controller@44460000 /soc@0/bus@44000000/tmu@44482000 /soc@0/bus@44000000/micfil@44520000 /soc@0/bus@42000000/dma-controller@42000000 /soc@0/bus@44000000/i3c-master@44330000 /soc@0/bus@42000000/i3c-master@42520000 /soc@0/bus@42000000/sai@42650000 /soc@0/bus@42000000/sai@42660000 /soc@0/bus@42000000/mqs2 /soc@0/bus@42000000/xcvr@42680000 /soc@0/bus@42000000/flexio@425c0000 /soc@0/bus@42800000/epxp@4ae20000 /soc@0/bus@42800000/camera /soc@0/efuse@47510000 /soc@0/system-controller@4ac10000 /soc@0/ldb@4ac10020 /soc@0/phy@4ac10024 /soc@0/ele-mu /soc@0/dsi@4ae10000 /soc@0/lcd-controller@4ae30000 /soc@0/blk-ctrl@4e010000 /soc@0/memory-controller@4e300000 /soc@0/bus@44000000/i2c@44350000/pmic@25 /imx93-lpm | ||
| + | prepare_mcore=setenv mcore_clk clk-imx93.mcore_booted; | ||
| + | scan_dev_for_boot=echo Scanning ${devtype} ${devnum}:${distro_bootpart}...; for prefix in ${boot_prefixes}; do run scan_dev_for_extlinux; run scan_dev_for_scripts; done;run scan_dev_for_efi; | ||
| + | scan_dev_for_boot_part=part list ${devtype} ${devnum} -bootable devplist; env exists devplist || setenv devplist 1; for distro_bootpart in ${devplist}; do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then part uuid ${devtype} ${devnum}:${distro_bootpart} distro_bootpart_uuid ; run scan_dev_for_boot; fi; done; setenv devplist | ||
| + | scan_dev_for_efi=setenv efi_fdtfile ${fdtfile}; for prefix in ${efi_dtb_prefixes}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${efi_fdtfile}; then run load_efi_dtb; fi;done;run boot_efi_bootmgr;if test -e ${devtype} ${devnum}:${distro_bootpart} efi/boot/bootaa64.efi; then echo Found EFI removable media binary efi/boot/bootaa64.efi; run boot_efi_binary; echo EFI LOAD FAILED: continuing...; fi; setenv efi_fdtfile | ||
| + | scan_dev_for_extlinux=if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${boot_syslinux_conf}; then echo Found ${prefix}${boot_syslinux_conf}; run boot_extlinux; echo EXTLINUX FAILED: continuing...; fi | ||
| + | scan_dev_for_scripts=for script in ${boot_scripts}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${script}; then echo Found U-Boot script ${prefix}${script}; run boot_a_script; echo SCRIPT FAILED: continuing...; fi; done | ||
| + | scriptaddr=0x83500000 | ||
| + | sd_dev=1 | ||
| + | sec_boot=no | ||
| + | soc=imx9 | ||
| + | splashimage=0x90000000 | ||
| + | sr_ir_v2_cmd=cp.b ${fdtcontroladdr} ${fdt_addr_r} 0x10000;fdt addr ${fdt_addr_r};fdt resize 0x400;fdt set /soc@0/bus@44000000/i2c@44350000/gpio@34 compatible adi,adp5585;for i in ${nodes}; do fdt rm ${i}; done | ||
| + | usb_boot=usb start; if usb dev ${devnum}; then devtype=usb; run scan_dev_for_boot_part; fi | ||
| + | vendor=freescale | ||
| + | |||
| + | Environment size: 7010/16380 bytes | ||
| + | </code> | ||
| + | </spoiler> | ||
| + | |||
| + | Notice various environment variables? There are various addresses, strings and even scripts! Can you figure out the **command** executed automatically at **boot**? | ||
| + | |||
| + | We hope you're getting the same idea... what if we can modify this environment when compiling U-Boot such that it executes our own script? | ||
| + | |||
| + | The KConfig item for doing this is called something like ''DEFAULT_ENV'', try searching for it! | ||
| + | |||
| + | It allows us to specify a custom file that contains our default environment... | ||
| + | |||
| + | First, let's create this file, let's say ''mydefault.env'' inside the uboot source directory. Let's start from a minimal example: | ||
| + | |||
| + | <code C> | ||
| + | /* default u-boot environment variables */ | ||
| + | /* this file is passed through the C preprocessor (so we can use C-style macros!) */ | ||
| + | /* someone in uboot source code re-defined `linux`... */ | ||
| + | #undef linux | ||
| + | arch=arm | ||
| + | baseboard=autodetect | ||
| + | baudrate=115200 | ||
| + | mmcdev=0 | ||
| + | mmcpart=1 | ||
| + | emmc_dev=0 | ||
| + | console=ttyLP0 | ||
| + | bootargs=console=ttyLP0,115200 earlycon,115200 rdinit=/linuxrc clk_ignore_unused | ||
| + | # This is the command executed automatically when uboot starts... | ||
| + | bootcmd=echo Fastboot mode... press Ctrl-C to exit; fastboot auto | ||
| + | # This runs instead of bootcmd when booted using `uuu` via USB | ||
| + | bootcmd_mfg=run bootcmd | ||
| + | bootdelay=3 | ||
| + | # TODO: enter a valid uImage file DRAM load address (the one used with 'bootm') | ||
| + | loadaddr=TODO | ||
| + | # TODO: enter the 'load mmc 0 ...' script (will be executed using 'run loadimage') | ||
| + | loadimage=TODO | ||
| + | image=linux.itb | ||
| + | # this can be executed using 'run linux' to boot your Linux kernel! | ||
| + | linux=echo Booting Linux ...; run loadimage; bootm ${loadaddr}; | ||
| + | </code> | ||
| + | |||
| + | Notice in the original environment that you may use the ''run'' command to run scripts from other defined variables, here's its reference: | ||
| + | <code> | ||
| + | run - run commands in an environment variable | ||
| + | </code> | ||
| + | |||
| + | We can also use ''${varname}'' expressions to do variable expansion like in Linux shells! | ||
| + | |||
| + | Enter your boot script inside the ''bootcmd'' var and let's proceed with overwriting the default environment. | ||
| + | |||
| + | Recall the ''ENV_DEFAULT_ENV_TEXT_FILE'' menuconfig item? Modify it to point to your ''mydefault.env'' (you can simply use a relative path to ''u-boot''s source dir). Note that you need to enable ''ENV_USE_DEFAULT_ENV_TEXT_FILE'' checkbox first to let you modify that value! | ||
| + | |||
| + | For advanced use cases / build automation, you can override any config items by creating extra .config files and using the included ''./scripts/kconfig/merge_config.sh'' u-boot host script! | ||
| + | |||
| + | Afterwards, [re]compile u-boot, copy the newly compiled u-boot ''.bin'' files to ''imx-mkimage'' directory again (if not doing it automatically using a Makefile) and regenerate your ''flash.bin''. | ||
| + | |||
| + | Test it by booting your new firmware using ''uuu''. | ||
| + | Always check the UBoot version and build time printed on the serial console during the boot process to ensure you loaded your latest firmware image! | ||
| + | |||
| + | Try using ''run linux'' to run the ''linux'' script (if you used that), otherwise edit the ''bootcmd'' to do that automatically after the timeout! | ||
| + | Did it work? if not, you may need to repeat this process (this is where a script comes in handy!). | ||
| + | |||
| + | Note that real devices have their uboot configuration proceed automatically with booting the OS if a physical button is NOT pressed (remember those old Android phones? you could stop this process by holding several volume keys together!). You can script this using GPIO (try the ''gpio'' command!), but it's out of scope for today (we don't have any physical buttons). | ||
| + | |||
| + | === Bonus: load uboot environment from FAT32 partition === | ||
| + | |||
| + | If you wish to change without uboot's environment without recompiling u-boot itself, it's also possible (and very easy to do). | ||
| + | |||
| + | Since this is usually desirable for flexibility during upgrades, there are many ways to do this: activate one of ''CONFIG_ENV_IS_IN_*'' config options or use a startup script which loads the env from a .txt file on the FAT32 partition. We'll prefer the latter since it's more fun (& failproof on most boards!). | ||
| + | |||
| + | Basically, we will write a custom script to be executed by u-boot at startup. To do this, search for the ''PREBOOT'' config item inside u-boot's ''menuconfig''. | ||
| + | Since we added a custom default.env file, we need to activate ''USE_PREBOOT'' and specify our script as a hardcoded ''preboot='' environment variable. | ||
| + | |||
| + | Sample script: | ||
| + | <code> | ||
| + | preboot=if fatload mmc 0:1 ${loadaddr} uboot.txt; then env import -t ${loadaddr} ${filesize}; fi | ||
| + | </code> | ||
| + | |||
| + | That's it! Create this file on your board's FAT32 partition (using the same ''key=value'' assignments separated by newlines syntax) and boot it! You should now see your new environment values (''env print'')! | ||