一、准备工作

内核驱动程序的编译依赖于对应体系(arm/x86)的内核模块(对应工具链(gcc)编译过的(make modules)的内核源码),因此开始编写驱动程序之前,需要做一些工作以具备驱动程序编译条件

1.1 开发环境

参考上一篇“树莓派添加驱动程序(一)”

  1. 搭建主机(win7+vmware+raspbian buster) + 目标机(树莓派4b + raspbian buster)
  2. 在主机上添加“交叉编译工具链”
  3. 下载对应版本的内核源代码 “linux-rpi-4.19.y.zip”

1.2 编译目标体系内核源代码

  1. 解压并编译为x86版本 linux-rpi-4.19.y-x86
    1. make x86_64_defconfig
    2. make modules (或者 make bzImage modules dtbs但是编译时间特别长,编译驱动只需要make modules就够了)
  2. 编译为arm版本 linux-rpi-4.19.y-arm
    1. make bcm2709_defconfig
    2. make zImage modules dtbs

二、编写第一个驱动程序hello

hello.c

  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. static int __init hello_init(void)
  4. {
  5. printk(KERN_ALERT"Hello ccsens!\n");
  6. return 0;
  7. }
  8. static void __exit hello_exit(void)
  9. {
  10. printk(KERN_ALERT"Goodbye ccsens\n");
  11. }
  12. module_init(hello_init);
  13. module_exit(hello_exit);
  14. MODULE_LICENSE("Dual BSD/GPL");
  15. MODULE_AUTHOR("zhangsan");

2.1 使用主机自身源码进行编译

KERNELDIR := /lib/modules/uname -r/build 使用主机自身模块

ifeq ($(KERNELRELEASE),)
    KERNELDIR := /lib/modules/`uname -r`/build
    PWD := $(shell pwd)
        ARCH = x86
        CROSS_COMPILE =
modules:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE)
modules_install:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE)
clean:
        rm -rf *.o *~core.depend .*.cmd *.mod.c *.tmp_version *.order *.symvers
else
    obj-m := hello.o
endif

查看编译生成的模块信息
image.png

查看dmesg输出
image.png


2.2 编译为x86版本(使用下载的内核源码)

修改x86版本的内核源码 KERNELDIR := /home/pi/linux-rpi-4.19.y-x86

ifeq ($(KERNELRELEASE),)
    KERNELDIR := /home/pi/linux-rpi-4.19.y-x86
    PWD := $(shell pwd)
        ARCH = x86
        CROSS_COMPILE =
modules:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE)
modules_install:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE)
clean:
        rm -rf *.o *~core.depend .*.cmd *.mod.c *.tmp_version *.order *.symvers
else
    obj-m := hello.o
endif

2.3 编译为arm版本(使用下载的内核源码)

选择arm版本的内核源码

ifeq ($(KERNELRELEASE),)
    KERNELDIR := /home/pi/linux-rpi-4.19.y-arm
    PWD := $(shell pwd)
        ARCH = arm
        CROSS_COMPILE = arm-linux-gnueabihf-
modules:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE)
modules_install:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE)
clean:
        rm -rf *.o *~core.depend .*.cmd *.mod.c *.tmp_version *.order *.symvers
else
    obj-m := hello.o
endif

三、遇到问题

3.1 Invalid Module Format

insmod: ERROR: could not insert module hello.ko: Invalid module format
image.png
dmesg查看内核输出
image.png
hello: version magic ‘4.19.97 SMP mod_unload ‘ should be ‘4.19.0-6-amd64 SMP mod_unload modversions ‘

发现是内核源码version不匹配,但是大版本是匹配的(4.19.x),由于是从github/raspberrypi官方仓库拉取的内核源码,可能是树莓派官网(https://www.raspberrypi.org)提供的下载镜像(raspbian buster)并没有和github上源码保持同步。

解决思路:

  1. 大版本一致所以不会有太大差异导致内核模块无法执行
  2. 修改源码版本号与目标系统保持一致

四、vermagic的组成

4.1 vermagic定义相关文件

在$KERNEL_SRC_DIR/include/linux/vermagic中定义了vermagic的组成

/* Simply sanity version stamp for modules. */
#ifdef CONFIG_SMP
#define MODULE_VERMAGIC_SMP "SMP "
#else
#define MODULE_VERMAGIC_SMP ""
#endif
#ifdef CONFIG_PREEMPT
#define MODULE_VERMAGIC_PREEMPT "preempt "
#else
#define MODULE_VERMAGIC_PREEMPT ""
#endif
#ifdef CONFIG_MODULE_UNLOAD
#define MODULE_VERMAGIC_MODULE_UNLOAD "mod_unload "
#else
#define MODULE_VERMAGIC_MODULE_UNLOAD ""
#endif
#ifdef CONFIG_MODVERSIONS
#define MODULE_VERMAGIC_MODVERSIONS "modversions "
#else
#define MODULE_VERMAGIC_MODVERSIONS ""
#endif
#ifndef MODULE_ARCH_VERMAGIC
#define MODULE_ARCH_VERMAGIC ""
#endif
#ifdef RANDSTRUCT_PLUGIN
#include <generated/randomize_layout_hash.h>
#define MODULE_RANDSTRUCT_PLUGIN "RANDSTRUCT_PLUGIN_" RANDSTRUCT_HASHED_SEED
#else
#define MODULE_RANDSTRUCT_PLUGIN
#endif

#define VERMAGIC_STRING                         \
    UTS_RELEASE " "                         \
    MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT             \
    MODULE_VERMAGIC_MODULE_UNLOAD MODUcLE_VERMAGIC_MODVERSIONS   \
    MODULE_ARCH_VERMAGIC                        \
    MODULE_RANDSTRUCT_PLUGIN

其中的UTS_RELEASE代表内核的版本号,
在$KERNEL_SRC_DIR/include/generated/utsrelease.h中定义

define UTS_RELEASE "4.19.0-6-amd64"

utsrelease.h在编译阶段动态生成,
生成内容由$KERNEL_SRC_DIR/Makefile指定

# SPDX-License-Identifier: GPL-2.0
VERSION = 4
PATCHLEVEL = 19
SUBLEVEL = 0
EXTRAVERSION = -6-amd64
NAME = "People's Front"

4.2 vermagic举例

4.19.0-6-amd64 SMP mod_unload modversions
VERSION: 4
PATCHLEVEL: 19
SUBLEVEL: 0
EXTRAVERSION: -6-amd64
MODULE_VERMAGIC_SMP: “SMP “
MODULE_VERMAGIC_MODULE_UNLOAD: “mod_unload “
MODULE_VERMAGIC_MODVERSIONS “modversions “

其中 VERSION,PATHCHLEVEL,SUBLEVEL,EXTRAVERSION 可以在Makefile中修改
其他信息在 $KERNEL_SRC_DIR/include/linux/vermagic.h中定义

4.3 vermagic的修改

方法一:通过makemenuconfig修改
vermagic相关文件中的内容都是由makemenuconfig动态生成(决定)的,最好的办法是通过make menuconfig修改信息。比如:
内核版本可以在: General setup-> Build ID Salt中找到
vermagic其他信息可以在: Enable loadable module support 中找到
image.png

image.png

方法二:修改相关文件中的定义
有时候,是在找不到对应的make menuconfig配置,或者即使找到了,因为多出关联的原因改了没起作用,暴力办法可以通过直接修改相关文件来修改vermagic。
Makefile中修改版本号
include/linux/vermagic.h中修改其他信息