本贴主要适用于:
1、新设备的SoC及PMU等关键外设在主线中已有支持;
2、主线中缺少对新设备的板级配置的描述;
3、但目前已经可以通过其他途径(如设备自带legacy kernel的fex等)得到设备的板级硬件配置信息的情况。
满足以上条件的情况一般是你拿到了如广告机、平板之类的缺少主线支持的设备,或者自己为某SoC新设计了板子。本贴描述如何从u-boot主线源码开始为这些新设备构建bootloader.
参考资料:
[1] Linux-sunxi wiki上的U-Boot页面
[2] Porting U-Boot and Linux on new ARM boards: a step-by-step guide
[3] Enabling LCD in u-boot Kernel 4.7.2
[4] 工厂废品小爱同学mini的重生(3)——— Uboot和硬改SD卡
离线
一、准备
1, 获取设备对应的工具链
一些主流发行版上的工具链安装可以参考Linux-sunxi wiki上的Toolchain页面。
更为简便的方式是,直接下载Linaro预先编译好的工具链,你需要从Armv7,32bit Armv8和64bit Armv8中选择你设备对应的那个下载然后解压。注意,如果选择使用预先编译好的工具链,你需要在每次打开用于构建的shell后,将解压后的工具链下的bin目录导入环境变量。以gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf为例:
export PATH=$PATH:/home/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
记得把路径换成你解压工具链的位置。另外,你也可以把该命令写入你使用的shell对应的rc文件(比如.bashrc)里,这样可以避免每次手动导入PATH。
2, 安装必要的依赖
需要的依赖和依赖对应的包名可能在不同发行版下略有不同。根据参考[1],Debian下的依赖可通过如下命令安装:
apt-get install swig python-dev
我的环境是Arch发行版的wsl,可能是因为之前做过有关内核编译的任务已经装上依赖了,没有执行此步也能正常构建。
3, 获取u-boot源码
进入工作路径,
git clone https://github.com/u-boot/u-boot.git
至此构建的准备工作已经完成。下面要为你的设备编写对应的板级配置(待续)
离线
二、编写板级配置
1, 编写设备树
设备树是主线u-boot和主线Linux中用于描述板级硬件配置信息的文件。网上关于设备树的各种文章已经很多了,可以自行阅读;但要是嫌麻烦不想读也是完全可以的,因为设备树描述文件本身是一种可读性很高的格式,主线Linux中一般又有各个SoC对应的一些设备已经写好的设备树。一般而言不会用到其余设备都没有支持的外设,所以直接参考主线中相同SoC的设备的设备树来编写你自己的就行了。当然,还是有一些基本的概念要知道:
&i2c0 {
status = "okay";
axp209: pmic@34 {
reg = <0x34>;
interrupt-parent = <&nmi_intc>;
interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
};
};
后缀为dts的文件是设备树的源代码文件,其中有很多上面这样的结构。一个大括号构成的这样的结构叫一个node,上面这个node是描述一个i2c控制器的。一个node下面有一些属性,上面这个node还有一个child-node,描述了一个PMU。注意这个PMU child-node,他在pmic之后跟了一个@,后面有一个数字34。他还有一个属性reg,值也是34。34是PMU在这个i2c总线上的地址。在其他结构中也能找到类似的描述设备在总线上或内存中的地址的值,你需要根据你设备已知的信息进行对应的修改。
在主线u-boot的source tree中,arm设备的设备树文件在arch/arm/dts/下。你可以为你的设备新建一个dts文件,然后从其他同SoC的设备的dts文件中复制你的设备上有的设备的node过来,也可以直接复制一份其他同SoC的设备的dts文件重命名,然后根据你的设备做对应修改。这里,以一个A20 SoC的板子为例,我给他的设备树命名sun7i-a20-std-dvr.dts,参照sun7i-a20-cubietruck.dts进行编辑:
编辑完成后,保存。但这时进行编译并不会生成新dts文件对应的dtb文件,必须要在同目录下的Makefile文件下对应的MACH段下添加你新建的dts文件的文件名,后缀改为dtb。同样还是上面A20板子的例子,A20的MACH为sun7i,添加sun7i-a20-std-dvr.dtb到Makefile的CONFIG_MACH_SUN7I段下:
现在你新添加的dts文件在编译时就可以生成设备树了。
note1:
dts文件最后一行有一个换行不能省略,否则不能通过编译。也就是说dts文件最后一行一定是一个空行。
note2:
要想让lcd屏在主线Linux上工作,设备树中必须添加对pwm的支持,例如:
&pwm {
pinctrl-names = "default";
pinctrl-0 = <&pwm0_pin>, <&pwm1_pin>;
status = "okay";
};
具体属性的值要根据你的设备来配置。详见参考[3]。
(待续)
最近编辑记录 SdtElectronics (2020-10-29 16:36:20)
离线
2, 编写Kconfig
Kconfig是保存u-boot和内核配置选项的文件。和编写设备树时一样,由于主线中一般有为其他相同SoC的设备编写好的Kconfig,不必深入理解他的编写规则,只需要从其他Kconfig中复制需要的配置到新建的Kconfig然后按你的设备的情况修改即可。
Kconfig文件在主线u-boot的source tree的configs/目录下。在此为你的设备新建一个Kconfig文件然后按上述方法编写,保存。
note1:
要想让lcd屏在主线Linux上工作,须在Kconfig中配置如下设置:
CONFIG_VIDEO_LCD_MODE
CONFIG_VIDEO_LCD_BL_EN
CONFIG_VIDEO_LCD_BL_PWM
示例配置(来自参考[3]):
CONFIG_VIDEO_LCD_MODE="x:800,y:480,depth:24,pclk_khz:30000,le:40,ri:40,up:29,lo:13,hs:48,vs:3,sync:3,vmode:0"
CONFIG_VIDEO_LCD_POWER="PH12"
CONFIG_VIDEO_LCD_BL_EN="PH8"
CONFIG_VIDEO_LCD_BL_PWM="PB2"
实际配置要参考你的设备情况和屏幕的datasheet编写。详见Linux-sunxi wiki上的LCD页面
离线
三、编译!
foreplay做足,终于可以开始正戏了。但开始编译之前,别忘了确认工具链目录是否已被导入PATH(见2楼)。
进入clone完成的源码目录,
make CROSS_COMPILE=arm-linux-gnueabihf- <board_name>_defconfig
<board_name>_defconfig是上一步中新建的Kconfig文件的文件名。
如果你想微调一些配置,
make CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
当然如果不想的话可以跳过这一步,使用默认的Kconfig。
然后正式开始编译:
make CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
如果你的机器性能尚可,十来分钟左右应该就能完成编译。
以全志平台为例,编译产生的bootloader是 ./u-boot-sunxi-with-spl.bin
编译产生的dtb在 ./arch/arm/dts/ 下,名称是在第二步中新建的dts文件的文件名,后缀换成dtb,例如sun7i-a20-std-dvr.dtb
note1:
上述编译过程仅适用于armhf架构的SoC,也就是32位的。适用于64位的Aarch架构的编译过程请参考Linux-sunxi的Wiki
最近编辑记录 SdtElectronics (2020-10-30 15:46:50)
离线
四、补充
本贴正文的内容应该到此结束了的,因为标题是u-boot的构建嘛,上面已经是全部过程了。但只是构建出来一个bootloader没啥用,毕竟他引导的系统才是我们真正关注的东西。而且在低内存的设备上,主线u-boot还有个坑,解决方法也在此节记录。
1, boot.cmd和boot.scr
boot.scr是指引u-boot加载内核的文件,启动时向内核传递的参数也在此定义。boot.cmd是boot.scr的源文件,编译后生成boot.scr。
下面是一段示例的boot.cmd:
setenv bootargs console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait panic=10
load mmc 0:1 0x43000000 ${fdtfile} || load mmc 0:1 0x43000000 boot/${fdtfile}
load mmc 0:1 0x42000000 zImage || load mmc 0:1 0x42000000 boot/zImage
bootz 0x42000000 - 0x43000000
其中,/dev/mmcblk0p2是rootfs所在的分区,${fdtfile}是设备树文件的名字,zImage是编译完的压缩后的内核binary,也即make zImage命令生成的内核。如果希望加载的内核是uImage,需要把以上内容中的zImage改为uImage,bootz改为bootm。
编辑完毕保存后,
mkimage -C none -A arm -T script -d boot.cmd boot.scr
产生对应的boot.scr。
2, 烧写u-boot以及为可引导的SD分区
Linux-sunxi的Wiki上有详细步骤,这里简单描述一下,以下${card}全部为sd卡的设备路径:
清理SD卡:
dd if=/dev/zero of=${card} bs=1M count=1
烧写u-boot:
dd if=u-boot-sunxi-with-spl.bin of=${card} bs=1024 seek=8
分区:
想用什么工具都行,你甚至可以在Windows下用DiskGenuis来做。
首先划分一个FAT32分区用于存放内核和其他boot需要的文件,大小为16MB,分区前部保留1MB的空间;
然后划分一个ext3或者ext4的分区,用于存放rootfs,大小足够即可,或者让他直接占满剩余空间。
复制文件:
把前面编译好的内核,boot.scr,设备树全放进刚刚分好的FAT32分区。把解压好的rootfs放进刚刚分好的ext分区。在哪找rootfs超出本贴范围太多,给个参考链接自己看吧Linux-sunxi的Wiki
然后插上sd卡祈祷他能正常boot吧。一次就能成功可以去买彩票了,如果你是巨佬当我没说。遇到问题可以Google出错时的log,或者来论坛问。
本节内容仅适用于制作可启动的SD卡。制作可启动的nand或者spi flash请自行查阅sunxi的Wiki。
3, 主线u-boot在低内存设备上的坑
本节内容主要来自参考[4]
在内存较少的设备上运行u-boot可能会遇到如下报错:
** Reading file would overwrite reserved memory **
根据参考[4],这和uboot的内存分配有关,需要对源码中的./include/configs/sunxi-common.h做如下修改:
第一处:
- #if (!defined CONFIG_MACH_SUN8I_V3S)
- /* 64MB of malloc() pool */
- #define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + (64 << 20))
- #else
/* 2MB of malloc() pool */
#define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + (2 << 20))
- #endif
第二处:
#else
/*
* 160M RAM (256M minimum minus 64MB heap + 32MB for u-boot, stack, fb, etc.
* 32M uncompressed kernel, 16M compressed kernel, 1M fdt,
* 1M script, 1M pxe and the ramdisk at the end.
*/
- #if (!defined CONFIG_MACH_SUN8I_V3S)
- #define BOOTM_SIZE __stringify(0xa000000)
- #define KERNEL_ADDR_R __stringify(SDRAM_OFFSET(2000000))
- #define FDT_ADDR_R __stringify(SDRAM_OFFSET(3000000))
- #define SCRIPT_ADDR_R __stringify(SDRAM_OFFSET(3100000))
- #define PXEFILE_ADDR_R __stringify(SDRAM_OFFSET(3200000))
- #define RAMDISK_ADDR_R __stringify(SDRAM_OFFSET(3300000))
- #else
/*
* 64M RAM minus 2MB heap + 16MB for u-boot, stack, fb, etc.
* 16M uncompressed kernel, 8M compressed kernel, 1M fdt,
* 1M script, 1M pxe and the ramdisk at the end.
*/
#define BOOTM_SIZE __stringify(0x2e00000)
#define KERNEL_ADDR_R __stringify(SDRAM_OFFSET(1000000))
#define FDT_ADDR_R __stringify(SDRAM_OFFSET(1800000))
#define SCRIPT_ADDR_R __stringify(SDRAM_OFFSET(1900000))
#define PXEFILE_ADDR_R __stringify(SDRAM_OFFSET(1A00000))
#define RAMDISK_ADDR_R __stringify(SDRAM_OFFSET(1B00000))
- #endif
#endif
开头标记"-"的行需在源码中删去。
(全文完)
离线