对于自己做的Jetson的板子一般需要修改设备树和驱动, 编译, 然后替换内核镜像(Image)和设备树(FDT), 参考下面的Makefile文件:
make env_depend
, 安装环境依赖make download
, 下载BSP源码(Sources包含kernel/u-boot等), 交叉编译工具链make decompress
, 解压kernel源码和交叉编译工具链make prepare_build_kernel
, 为编译做准备, 主要是把顶级Makefile和相关文件等放到一个build文件夹里make build
, 编译内核源码, 里面包含make menuconfig
, 编译出来的内核镜像为build/arch/arm64/boot/Image
, 设备树blob为build/arch/arm64/boot/dts/tegra186-quill-p3310-1000-c03-00-base.dtb
make scp
, 可以把Image和dtb传到远程TX2的home目录BOARD = jetson-tx2-devkit
PWD := $(shell pwd)
# TX2 Image and dtb
TX2_REMOTE= [email protected]
TX2_IMAGE = ${PWD}/build/arch/arm64/boot/Image
TX2_DTB = ${PWD}/build/arch/arm64/boot/dts/tegra186-quill-p3310-1000-c03-00-base.dtb
L4T_RELEASE_PACKAGE = jetson_linux_r32.6.1_aarch64.tbz2
SAMPLE_FS_PACKAGE = tegra_linux_sample-root-filesystem_r32.6.1_aarch64.tbz2
CROSS_COMPILE = ${PWD}/l4t-gcc/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
LOCALVERSION = -tegra
TEGRA_KERNEL_OUT = ${PWD}/build
RELEASE_DIR = ${PWD}/Linux_for_Tegra/kernel
.PHONY: download
download:
# L4T Driver Package (BSP), ~355MB
wget https://developer.nvidia.com/embedded/l4t/r32_release_v6.1/t186/jetson_linux_r32.6.1_aarch64.tbz2
# Sample Root Filesystem, ~1430MB
wget https://developer.nvidia.com/embedded/l4t/r32_release_v6.1/t186/tegra_linux_sample-root-filesystem_r32.6.1_aarch64.tbz2
# Jetson Linux Developer Guide (downloadable version)
wget https://developer.nvidia.com/embedded/l4t/r32_release_v6.1/nvidia_jetson_linux_driver_package.tar
# L4T Driver Package (BSP) Sources, ~174MB
wget https://developer.nvidia.com/embedded/l4t/r32_release_v6.1/sources/t186/public_sources.tbz2
# toolchain
wget http://releases.linaro.org/components/toolchain/binaries/7.3-2018.05/aarch64-linux-gnu/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu.tar.xz
.PHONY: env_depend
env_depend:
sudo apt update
sudo apt install -y qemu-user-static
sudo apt install -y build-essential bc
.PHONY: decompress
decompress:
@# ===== doc =====
mkdir -p l4t_docs & tar xvf nvidia_jetson_linux_driver_package.tar -C l4t_docs
@# ===== bsp =====
@echo "\n-->\ndecompress l4t release package\n-->\n"
@echo ${L4T_RELEASE_PACKAGE}
tar xf ${L4T_RELEASE_PACKAGE}
@# ===== rootfs =====
@# must use sudo, or apply_binaries.sh will break and tell you do this
@echo "\n--->\ndecompress sample fs package\n--->\n"
cd Linux_for_Tegra/rootfs/; \
sudo tar xpf ../../${SAMPLE_FS_PACKAGE}; \
cd .. ..
@# ===== sources =====
@echo "\n--->\nsources\n--->\n"
tar -xjf public_sources.tbz2
cd Linux_for_Tegra/source/public; \
tar xjf kernel_src.tbz2; \
cd .. .. ..
@# ===== toolchain =====
@echo "\n--->\ntoolchain\n--->\n"
mkdir -p l4t-gcc; \
cd l4t-gcc; \
tar xf ../gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu.tar.xz; \
cd ..
.PHONY: apply_binaries
apply_binaries:
cd Linux_for_Tegra; \
sudo ./apply_binaries.sh; \
cd ..
@# the last line must be Success!
.PHONY: flashing
flashing:
cd Linux_for_Tegra; \
sudo ./flash.sh ${BOARD} mmcblk0p1
.PHONY: prepare_build_kernel
prepare_build_kernel:
export CROSS_COMPILE=${CROSS_COMPILE}; \
export LOCALVERSION=${LOCALVERSION}; \
mkdir -p ${TEGRA_KERNEL_OUT}; \
cd ${PWD}/Linux_for_Tegra/source/public/kernel/kernel-4.9; \
make ARCH=arm64 O=${TEGRA_KERNEL_OUT} tegra_defconfig
.PHONY: build
build:
export CROSS_COMPILE=${CROSS_COMPILE}; \
export LOCALVERSION=${LOCALVERSION}; \
export TEGRA_KERNEL_OUT=${TEGRA_KERNEL_OUT}; \
cd ${TEGRA_KERNEL_OUT}; \
make menuconfig; \
make ARCH=arm64 O=${TEGRA_KERNEL_OUT} tegra_defconfig; \
make ARCH=arm64 O=${TEGRA_KERNEL_OUT} -j $$(nproc); \
cp ${TEGRA_KERNEL_OUT}/arch/arm64/boot/Image ${RELEASE_DIR}/; \
cp -r ${TEGRA_KERNEL_OUT}/arch/arm64/boot/dts/* ${RELEASE_DIR}/dtb/
.PHONY: scp
scp:
sudo scp ${TX2_IMAGE} ${TX2_DTB} ${TX2_REMOTE}:~
.PHONY: ssh
ssh:
ssh ${TX2_REMOTE}
方法比较多:
flash.sh
, -K
更新内核, -d
更新设备树文件extlinux.conf
, NFS挂载?extlinux.conf
, 直接替换, 这里用这种方式DTB文件所在的位置参考:
需要先进行设置
虽然默认情况下L4T不再在
/boot/extlinux/extlinux.conf
中设置FDT条目, 用户仍然可以手动设置此选项,以使更改生效
# TX2
$ sudo vi /boot/extlinux/extlinux.conf
# 添加
FDT /boot/dtb/kernel_tegra186-quill-p3310-1000-c03-00-base.dtb
使用如下
# 更新内核和设备树
# PC
sudo scp build/arch/arm64/boot/Image build/arch/arm64/boot/dts/tegra186-quill-p3310-1000-c03-00-base.dtb [email protected]:~
# TX2, 1.sh
#!/bin/bash
sudo mv ~/Image /boot
sudo mv ~/tegra186-quill-p3310-1000-c03-00-base.dtb /boot/dtb/kernel_tegra186-quill-p3310-1000-c03-00-base.dtb
# sync
sudo reboot
# 注意dtb的名字前面多了 kernel_
检查更新情况
# 时间应该比Ubuntu系统时间快12小时
# 内核
$ uname -a
Linux tx2-pc 4.9.253 #1 SMP PREEMPT Wed Jan 19 22:13:36 PST 2022 aarch64 aarch64 aarch64 GNU/Linux
# 设备树
$ dmesg | grep DTB
[ 0.164259] DTB Build time: Jan 19 2022 22:03:52
[ 0.433289] DTB Build time: Jan 19 2022 22:03:52
之前的文章里写过, 解决新电脑装旧发行版驱动问题时(如2021/20222年出的电脑装Ubuntu 16/18, 图形显示/WiFi6/蓝牙等肯定会不正常)
, 可以通过更新内核的方式进行支持, 如更新到5.11内核, 需要下载的有:
# https://blog.csdn.net/weifengdq/article/details/118915007
$ tree
.
├── linux-firmware_1.197.2_all.deb
├── linux-headers-5.11.0-051100-generic_5.11.0-051100.202102142330_amd64.deb
├── linux-headers-5.11.0-051100_5.11.0-051100.202102142330_all.deb
├── linux-image-unsigned-5.11.0-051100-generic_5.11.0-051100.202102142330_amd64.deb
└── linux-modules-5.11.0-051100-generic_5.11.0-051100.202102142330_amd64.deb
其中:
image
就是内核镜像, 毕竟最后运行的是编译好的内核镜像, 而不是源码modules
是一些可以动态加载到内核的 .ko
组件, 这些往往是通过源码编译出来的firmware
是包含某些硬件设备的部分或全部功能所需的固件二进制 blob, 通常是专有的, 因为某些硬件制造商不会发布源码(如NVIDIA显卡, Intel的Wi-Fi芯片组, headers
: 一个提供Linux内核头文件的软件包, 充当内部内核组件之间 以及 用户空间和内核之间 的接口如果你正在构建一个完整的内核,那么,显然,你需要完整的源文件,而不仅仅是头文件。
但是,如果您正在编译链接到内核的设备驱动程序或其他可加载模块,那么您只需要头文件,因此可以通过不安装完整源代码来节省空间。
我们编写内核模块时, 经常会看见
#include
#include
#include
那这些文件在哪里, PC的其实就在/lib/modules/$(uname -r)/build
文件夹里面, 如 linux/module.h
应该就对应X86
主机的 /lib/modules/5.13.0-27-generic/build/include/linux/module.h
, 那 TX2 的源码包里肯定也有 Linux_for_Tegra/source/public/kernel/kernel-4.9/include/linux/module.h
甚至可以直接在文件夹里面打开 make menuconfig
, 如电脑X86
平台的
# ubuntu20, now is 5.13.0-27-generic
cd /lib/modules/$(uname -r)/build
sudo make menuconfig
# you will find Kconfig Makefile here
下面先来写一个 x86
平台的 hello module
:
mkdir hello
cd hello
vi hello.c
hello.c
的源码和注释
//linux headers
#include
#include
#include
//加载时执行
static int hello_init(void)
{
//打印消息到内核日志缓冲区, 通常用dmesg查看
//KERN_ALERT为日志级别. 也可以用 pr_alert()
printk(KERN_ALERT "Hello Module Test\n");
pr_alert("Hello Module Test2\n");
return 0;
}
//卸载时执行
static void hello_exit(void)
{
printk(KERN_INFO "Goodbye Hello\n");
}
//传递给API
//module_init, 驱动初始化入口点
//module_exit, 驱动退出的入口点
module_init(hello_init);
module_exit(hello_exit);
//作者声明, 用 "Name " 或 "Name", 多个作者可以多个 MODULE_AUTHOR() 行
MODULE_AUTHOR("yiming");
//LICENSE声明, 有法律效应, GPL有传染性
MODULE_LICENSE("GPL");
//描述, 通过 modinfo 查看
MODULE_DESCRIPTION("First Driver");
然后是vi Makefile
obj-m:=hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
其中:
-C
, 为change directory
, 即切换目录到 /lib/modules/$(shell uname -r)/build
, 这里是 当前Linux版本 的 内核构建目录, 这里有内核的顶级MakefileM=
, 顶级Makefile的一个变量参考下面源码, 让Makefile在尝试生成模块目标之前移回指定目录, pwd
, 就指明了hello.ko生成在当前目录# 来自 /lib/modules/$(shell uname -r)/build/Makefile
# Use make M=dir or set the environment variable KBUILD_EXTMOD to specify the
# directory of external module to build. Setting M= takes precedence.
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
现在开始走一把
# hello目录
$ tree
.
├── hello.c
└── Makefile
$ make
$ tree
.
├── hello.c
├── hello.ko
├── hello.mod
├── hello.mod.c
├── hello.mod.o
├── hello.o
├── Makefile
├── modules.order
└── Module.symvers
# 查看文件类型, elf 64bit LSB 浮动程序, x86-64 平台
$ file hello.ko
hello.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=ea5c2968e3fe351b700b5e6f8978904e58254b8f, with debug_info, not stripped
# 加载hello.ko
$ sudo insmod hello.ko
# 查看最后两行
$ dmesg | tail -2
[23749.761288] Hello Module Test
[23749.761293] Hello Module Test2
# 卸载hello.ko, 可以不加后缀.ko
$ sudo rmmod hello
$ dmesg | tail -1
[23902.056184] Goodbye Hello
# 如果让dmesg直接输出到console
# sudo dmesg -n 8 或 sudo dmesg -n debug
# 恢复默认
# sudo dmesg -n 4 或
# 让dmesg一直显示不退出
$ dmesg -wH &
最后的命令还可以查看级别, 红色的alert:
参考文档中的Kernel Cusomization -> Preparing to Building External Kernel Modules 和 Building External Kernel Modules
两小节, 编译一个外部的Jetson内核模块的步骤:
module
源文件里面header, 顶级Makefile, kernel_source_tree
等, 只准备一次就可以CROSS_COMPILE
来指定交叉编译器-C
来指定顶级Makefile所在目录, 一般在内核目录, 但这里我直接丢到了top文件夹里面M=
指定module的位置新建一个jetson_hello
文件夹(/home/z/jetson/test/jetson_hello
), 还是上面的hello.c不变拷贝进去, 接着撸Makefile
obj-m:=hello.o
kernel_dir = /home/z/jetson/Linux_for_Tegra/source/public/kernel/kernel-4.9
cross_compile_dir = /home/z/jetson/l4t-gcc/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
makefile_dir := $(shell pwd)
top_makefile_dir := $(shell pwd)/top
build_dir := $(shell pwd)/build
all:
@if [ ! -d ${top_makefile_dir} ]; then \
export CROSS_COMPILE=${cross_compile_dir}; \
export LOCALVERSION=-tegra; \
mkdir -p ${top_makefile_dir}; \
cd ${kernel_dir}; \
make ARCH=arm64 O=${top_makefile_dir} tegra_defconfig; \
cd ${top_makefile_dir}; \
make ARCH=arm64 O=${top_makefile_dir} -j $$(nproc) modules_prepare; \
echo "\n=================================\n"; \
fi; \
export CROSS_COMPILE=${cross_compile_dir}; \
cd ${makefile_dir};\
mkdir -p ${build_dir}; \
make ARCH=arm64 -C ${top_makefile_dir} M=${makefile_dir} -j $$(nproc); \
mv *.mod.c *.o *.order *.symvers ${build_dir}
clean:
@# rm -rf *.mod.* *.o *.order *.symvers *.ko
rm -rf build *.ko top
其中:
obj-m
, 这里的m指module, 一般是配合Kconfig
文件在make menuconfig
里面选编译进内核(y), 还是编译成KO(m), 或者不编译(n), 这里直接指定-m
make
的时候先判断top
文件夹是否存在, 不存在就做上面的预处理那一步CROSS_COMPILE
来指定交叉编译器-j $(nproc)
, 全部CPU进场, 不过这里只有一个.c文件, 用不上测试一把
$ make
make[1]: Entering directory '/home/z/jetson/test/jetson_hello/top'
WARNING: Symbol version dump ./Module.symvers
is missing; modules will have no dependencies and modversions.
LD /home/z/jetson/test/jetson_hello/built-in.o
CC [M] /home/z/jetson/test/jetson_hello/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/z/jetson/test/jetson_hello/hello.mod.o
LD [M] /home/z/jetson/test/jetson_hello/hello.ko
make[1]: Leaving directory '/home/z/jetson/test/jetson_hello/top'
$ tree -L 2
.
├── build
│ ├── built-in.o
│ ├── hello.mod.c
│ ├── hello.mod.o
│ ├── hello.o
│ ├── modules.order
│ └── Module.symvers
├── hello.c
├── hello.ko
├── Makefile
└── top
├── arch
├── include
├── kernel
├── Makefile
├── scripts
└── source -> /home/z/jetson/Linux_for_Tegra/source/public/kernel/kernel-4.9
# ARM aarch64
$ file hello.ko
hello.ko: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), BuildID[sha1]=8f24215154dff2b07306d655040f7c29de7a5efb, with debug_info, not stripped
$ scp hello.ko [email protected]:~
$ ssh [email protected]
# TX2
$ sudo insmod hello.ko
$ sudo rmmod hello
$ dmesg
...
[26332.410359] hello: no symbol version for module_layout
[26332.416120] hello: loading out-of-tree module taints kernel.
[26332.425103] Hello Module Test
[26332.428150] Hello Module Test2
[26347.063612] Goodbye Hello
如图所示
The Linux Kernel Archives - Releases, Jetson目前的4.9版本在16年底就release了, 支持到23年初.
4.9内核的在线文档参考: Linux Kernel Documentation — The Linux Kernel documentation, 有点老了… 看着没有新的5.1x的组织的舒服…
# 显示已加载模块
lsmod
# 查看内核模块信息
modinfo
# 载入内核模块
indmod
# 卸载内核模块
rmmod
# 查看模块依赖
depmod
# 载入或移除模块
# 比较智能一点, 可以递归解析模块依赖
modprobe
# 确定文件类型, 32/64bit
file
欢迎扫描二维码关注微信公众号, 及时获取最新文章: