前言
做为一个各种用户现场救火解决疑难杂症的老技术搬砖人,大抵是没有太多机会整天编译内核的,但有坑总是要填的。
自从第一次编译内核至今应该快接近20年了,时间过得真快,基本原理一直都是懂的,但实际真正把编译内核这个过程玩转了的还真没多少机会,大部分要么是直接提需求研发直接给包,要么开发板或者各种版本系统的src.rpm内核包把一些都做好了,都是简单rebuild或者配置几下开几个开关就make的。但今天还是遇到问题了。
问题
项目遇到ARM架构下,服务器操作系统添加声卡,这本来是个没工作量,系统自动识别的活。但这个垃圾产品,偏偏在ARM下把声卡驱动阉割的一个不剩,所以随便你插什么声卡上去都没法去动起来。按理说这事也不复杂,config配置一下,重新编译一下驱动就好了。可好死不死,这个基于开源做的linux内核源码包还保密【中国特色的开源】,不给,没流程给你,搬砖狗没权限,只有研发有权限。
刚开始以为没得搞了,声卡驱动源码芯片厂商早就不单独提供,全部提交到内核主线了。没源码,这下怕是只有等家里的大爷们啥时候想起来了才会给我驱动了。合作伙伴客户销售都要骂人了。
但突然想起,你不给内核,我找个版本想尽的内核编译个驱动出来加载不就行了,属于曲线救国了。
于是乎下载了阿里龙蜥openanolis的内核src包开整。
过程
首先,anolis的内核有2个版本,兼容红帽和centos 4.18 以及阿里自己搞的4.19,我们的坑爹内核就是4.19的,所以最好下4.19的内核,4.19内核以及源码在Plus源,刚开始找半天没找到还以为阿里云也玩特色开源这一套呢。最后在Plus找到了。
下载下来第一步需要补依赖包,编译过程有不少依赖包需要提前补齐。补包要么直接在rpmbuild的时候根据提示来补,但这个需要手工去搞,数量还多,有个命令专门干这事
yum install yum-utils
dnf builddep ./kernel-4.19.91-26.an8.src.rpm
在补包的过程中,其中有个包名叫 redhat-rpm-config,但在uos里面,牛批的研发把这个名字给改了,叫UnionTech-rpm-config或者uos-rpm-config。这个包补不上,只能把src.rpm安装后修改SPEC文件,把依赖名字改一下了。
补完后,先测试一下,就开始直接rpmbuild -bb 开干,弄完就把声卡内核拷贝到系统内核对应的模块目录,depmod -a过后开始加载,发现报错,早在意料之中,modprobe -f snd_cmipci 发现还是报错,就有点奇怪了,按理说-f参数会忽略版本差异,相当于 以下两个参数合体了。
--force-vermagic
--force-modversion
具体报错记不住了,然后报的什么key被拒绝,当时以为是内核开了强制模块签名,结果看了下CONFIG文件
CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_ALL=y
# CONFIG_MODULE_SIG_FORCE is not set
模块签名以及所有模块签名都开了,但是强制签名验证是没开的,然后折腾一晚上查baidu,google各种模块签名的资料,都没啥头绪。晚上搞到12点关机睡觉,耽误睡眠。
这个问题在第二天接着搞的时候找到知乎的一篇文章对modprobe -f有个说明
一直加了-f ,但不知道为啥内核没开强制签名认证也会报这个错。报签名错这个问题看到这就暂时不搞了。
接下来就还是把版本号给对齐吧,于是乎就直接干掉rpmbuild下的BUILD下的目录,重新
rpmbuild -bp xxx.spec
然后遇到2个坑:
1. 网上找到别人的方法,通过修改文件把modinfo 查出来的vermagic部分和系统现有运行的内核对齐,说是修改内核源码下include/generated/utsrelease.h中的UTS_RELEASE的值。结果到这个目录下,更笨没有utsrelease.h这个文件,查了半天,发现需要编译完一遍内核后,才会生成这个文件,不编译,干净内核是没这个文件的。
这样搞完后,想着就也别整个内核全部编译了,单独编译这个模块就行了。
make CONFIG_SND_CMIPCI=m M=/root/rpmbuild/BUILD/kernel-4.19.91-26.an8/linux-4.19.91-26.an8.x86_64/sound/pci
2. 这么搞的时候,发现不是报没有Module.symvers就是找不到目标。Module.symvers可以从之前已经编译好的kernel-core二进制rpm包里面解压出来,放到内核源码目录;但找不到目标就郁闷了一阵子,后来发现还是要先编译一遍内核,或者至少编译完整内核得让他完成前面的HOSTCC部分,再来执行编译部分目录。
所以这两个坑都是需要编译一遍完整内核后,修改文件后再编译某个目录。
这样磕磕绊绊弄好vermagic部分和系统运行内核一样版本的未签名内核的时候,拷贝到系统目录depmod -a后再来modprobe,结果报的还是
snd_cmipci: disagrees about version of symbol module_layout
都要崩溃了。
后面又搜了一下
绕过kernel模块版本校验检测(转) - 阿C - 博客园转自:https://www.cnblogs.com/super-king/p/3296347.html?utm_source=tuicool kernel module version checkhttps://www.cnblogs.com/arci/p/14993050.html说是内核模块加载的时候,不管要看vermagic,还要先看模块符号的crc,然后突然想起,系统没给内核代码,但是他有内核头文件啊,前面搞哪些都是废操作啊。
成功的步骤
直接安装kernel-devel
然后
make -C /usr/src/kernels/4.19.0-91.82.112.uelc20.x86_64 M=/root/rpmbuild/BUILD/kernel-4.19.91-26.an8/linux-4.19.91-26.an8.x86_64/sound/pci modules
这里的-C 后面的路径就是kernel-devel安装后的内核头文件目录位置。
当然如果这个模块本身没有被开启还是在make后面加上CONFIG_SND_CMIPCI=m开启一下。
编译好后,从sound/pci目录下拷贝snd-cmipci.ko到对应目录,depmod -a后再次加载,终于是加载上了。
如果编译的时候,模块依赖其他模块,报什么未定义的符号之类,直接把依赖的模块编译后目录的Module.symvers文件内容>>添加到/usr/src/kernels/xxx/Module.symvers里面就行,省的去配置KBUILD_EXTMOD变量了。
其他
其实大部分串口啊,网卡这些,单独提供的源码包,和这个步骤的编译是一样的,只不过他们把M=后面的目录改成了他源码的当前路径了,不是我们内核里面的路径。
另外,dkms的编译过程和这个过程也是一模一样的。
仔细看一下sound/pci这个目录下的Makefile,其实也能看出来,
obj-$(CONFIG_SND_VIA82XX) += snd-via82xx.o
obj-$(CONFIG_SND_VIA82XX_MODEM) += snd-via82xx-modem.o
上面的这些模块,如果对应的CONFIG_SND_VIA82XX_MODEM=m,那么obj-m += 后面的模块,这也可以看出来这个目录下Makefile其实控制了相当多的模块驱动。编译命令指定的时候只能指定到目录,如果只想编译某个驱动,其实可以在这个文件里面把其余的模块相关的配置信息删掉。这样真正的只编译一个模块,加快编译速度。
交叉编译环境下,也可以单独编译模块,只需要单独指定两个变量
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
另外,确保PATH变量里能找到这个aarch64-linux-gnu-开头的命令,所以多数时候还需要设置PATH路径。
这里弄个编译例子
root@build-virtual-machine:~/jlsemi_phy_drivers_1.1.13/jlsemi_ephy_kernel_phys_1.1.13_312b5e4/source# history | grep export
1599 export ARCH=arm64
1600 export CROSS_COMPILE=aarch64-linux-gnu-
1602 export PATH=/root/phytium-linux-buildroot/output/host/opt/ext-toolchain/bin:$PATH
1623 history | grep export
root@build-virtual-machine:~/jlsemi_phy_drivers_1.1.13/jlsemi_ephy_kernel_phys_1.1.13_312b5e4/source# export CROSS_COMPILE=aarch64-none-linux-gnu-
root@build-virtual-machine:~/jlsemi_phy_drivers_1.1.13/jlsemi_ephy_kernel_phys_1.1.13_312b5e4/source# make
make -C /root/linux-d734b714dcf61afd3a3d6fd5458895a944800081/ M=/root/jlsemi_phy_drivers_1.1.13/jlsemi_ephy_kernel_phys_1.1.13_312b5e4/source modules
make[1]: Entering directory '/root/linux-d734b714dcf61afd3a3d6fd5458895a944800081'
CC [M] /root/jlsemi_phy_drivers_1.1.13/jlsemi_ephy_kernel_phys_1.1.13_312b5e4/source/jlsemi-core.o
CC [M] /root/jlsemi_phy_drivers_1.1.13/jlsemi_ephy_kernel_phys_1.1.13_312b5e4/source/jlsemi.o
LD [M] /root/jlsemi_phy_drivers_1.1.13/jlsemi_ephy_kernel_phys_1.1.13_312b5e4/source/jlsemiphy.o
MODPOST /root/jlsemi_phy_drivers_1.1.13/jlsemi_ephy_kernel_phys_1.1.13_312b5e4/source/Module.symvers
CC [M] /root/jlsemi_phy_drivers_1.1.13/jlsemi_ephy_kernel_phys_1.1.13_312b5e4/source/jlsemiphy.mod.o
LD [M] /root/jlsemi_phy_drivers_1.1.13/jlsemi_ephy_kernel_phys_1.1.13_312b5e4/source/jlsemiphy.ko
make[1]: Leaving directory '/root/linux-d734b714dcf61afd3a3d6fd5458895a944800081'
root@build-virtual-machine:~/jlsemi_phy_drivers_1.1.13/jlsemi_ephy_kernel_phys_1.1.13_312b5e4/source# ls
jlsemi.c jlsemi-core.c jlsemi-core.h jlsemi-core.o jlsemi-dt-phy.h jlsemi.o jlsemiphy.ko jlsemiphy.mod jlsemiphy.mod.c jlsemiphy.mod.o jlsemiphy.o Makefile Makefile.orig modules.order Module.symvers
如果内核头文件拷贝到aarch64的真实环境,可能还是需要在aarch64之类的异构环境提前先编译一下整个内核,因为很多Host tool工具要报错。。。。
总结
其实成功的步骤应该早就能想到的,看到报错按图索骥去搜,被误导了,然后晚上搞这些头一晕,就没了思路。两分钟能搞定的事情用了2天的空余时间,浪费。
后记
在测试机上做测试的时候,这么搞最终还是出问题了,倒不是方法有问题,而是编译的驱动要依赖其他模块,模块又依赖另一个模块,这就涉及到模块的符号导出Module.symvers文件的问题了.
这个文件在内核的头文件包里面是包含了的,但他只包含了已编译出来的模块符号及CRC值,如果我们需要使用另外版本内核源码编译到几个互相还有依赖的模块给现有内核用,后编译的模块就会出现模块符号文件无法从头文件给的Module.symvers文件获取,理论上需要把另外版本内核编译的第一个模块产生的Module.symvers文件内容给保存到系统内核头文件包的Module.symvers文件里,后面编译的模块才能正常找到他依赖的模块到处的符号,但有个变量KBUILD_EXTMOD可以用来指定模块的符号文件位置.
我在测试机上的时候的时候出现模块编译出来但驱动加载后报异常应该跟没正确处理驱动模块依赖的其他模块的符号有关,有机会再尝试下一个一个的依赖编译并指定Module.symvers位置.
后面发现这么搞没i问题了,之前有问题是因为硬件问题。换了个USB声卡一步一步编译就OK了。
参考
另外内核模块签名,单独编译,也可以体验一下签名,只是拿不到现有内核发布者的key,只能自己生成一个,如果完整编译一次完成的目录里,cert目录是有现成生成好了的,只需要用现成的工具签名一下就OK。
./sign-file sha512 ../certs/signing_key.pem ../certs/signing_key.x509 ../sound/pci/snd-cmipci.ko
参考内核官网的文档
但说实话,绝大部分客户现场遇到装不上,第一步就让关闭BIOS安全启动。后面的全都白搭。。。。
内核编译出来的版本号带dirty 或者+号处理办法
如果在内核配置里面选择了 Automatically append version information to the version string,那么编译出来会在版本号后面附加dirty字样。
如果不选择,那么后面会带一个+号。
如果不选择自动附加版本信息,同时配置了config_local_version字符串,则附加 localversion字符串后面再带一个+号。
我的诉求就是不带额外的版本号,已参考大佬的方法解决。
向linux内核版本号添加字符/为何有时会自动添加“+”号https://www.bbsmax.com/A/RnJWm1Dvdq/
要不带+号也不带其他字符串 ,直接用make LOCALVERSION="" 这样的变量就不会带任何的额外版本号了(不用变量,直接在config配置的时候,CONFIG_LOCALVERSION=""还是会带+号,空了再仔细研究)。
如果既要添加localversion 比如5.15.0-starfive版本好里面的-starfive,又不想编译出来变成5.15.0-starfive+ ,那需要在menuconfig或者.config里面配置LOCALVERSION为-starfive,又需要在编译的时候make 后面指定LOCALVERSION="",这样在git checkout出来的代码里面编译就能达到编译出来内核版本为 5.15.0-starfive的效果了。
内核firmware
对于设备驱动,厂家既要开源又要保守商业秘密,所以就底层封装成二进制的firmware,然后上层逻辑代码提交到linux内核主线。所以,对显卡,网卡等外设,除了编译内核驱动,还需要系统内存在对应的firmware,才能启动。
root@starfive:/boot# modinfo rt2800usb | more
filename: /lib/modules/5.15.0/kernel/drivers/net/wireless/ralink/rt2x00/rt2800usb.ko
license: GPL
firmware: rt2870.bin
description: Ralink RT2800 USB Wireless LAN driver.
version: 2.3.0
author: http://rt2x00.serialmonkey.com
srcversion: 3D5448FED7C97CFAE9C26D2
alias: usb:vF201p5370d*dc*dsc*dp*ic*isc*ip*in*
alias: usb:v177Fp0254d*dc*dsc*dp*ic*isc*ip*in*
alias: usb:v083ApF511d*dc*dsc*dp*ic*isc*ip*in*
alias: usb:v083ApD522d*dc*dsc*dp*ic*isc*ip*in*
比如,这个无线网卡,在写好配置文件连接SSID的时候,就需要加载rt2870.bin这个二进制firmware,否则是没法工作的。
关于rt2870.bin在系统中的路径,不同的系统还略有不同。
在我测试的这个riscv板子官方的debian系统中,位置在/lib/firmware
内核官网的firmware仓库地址
关于firmware路径的一些讨论
openeuler
openeuler的源码包在2个地方:
- 光盘内,直接安装到/usr/src下,仅打过补丁的源码,用于系统编译驱动模块等。
- 在线源,用于直接编译出内核等相关二进制包。一般用这个可以修改编译相关的内容,更灵活。
在线源的源码包内,spec文件指定了defconfig,所以,默认配置可以直接看这个文件。
make openeuler_defconfig