> ## Documentation Index
> Fetch the complete documentation index at: https://dragonwingdocs.qualcomm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 管理树外内核模块

大多数 Qualcomm<sup>®</sup> Linux 驱动直接从内核源码树编译。
有些驱动在源码树之外维护——称为树外(out-of-tree)或动态可加载内核模块(DLKM)驱动——它们使用独立的 `Makefile` 或 Yocto 构建系统单独构建。

Qualcomm 内核图形支持层(KGSL)GPU 驱动就是一个例子:其位于 `recipes-graphics/kgsl-dlkm/kgsl-dlkm_git.bb` 的配方将该驱动作为树外模块构建和安装。

## 构建与自动加载模块

### 独立 Makefile

创建一个通过 `$(MAKE) -C` 委托给内核构建系统的 `Makefile`:

```makefile theme={null}
all: modules
obj-m := hello.o

SRC := $(shell pwd)

modules:
	$(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules $(KBUILD_OPTIONS)

modules_install:
	$(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install
```

在调用 `make` 之前,将 `KERNEL_SRC` 设置为已配置的内核源码树路径。

### Yocto 配方

通过继承 `module` 类,将树外模块集成到 Yocto 构建中。该类会自动处理 `make modules` 和 `make modules_install`。

```bitbake theme={null}
DESCRIPTION = "${SUMMARY}"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/${LICENSE};md5=801f80980d171dd6425610833a22dbe6"

inherit module

SRC_URI += "file://Makefile \
            file://hello.c  \
            file://COPYING  \
            "
S = "${WORKDIR}"

EXTRA_OEMAKE += "MACHINE='${MACHINE}'"
MAKE_TARGETS = "modules"
MODULES_INSTALL_TARGET = "modules_install"

# Autoload the module on boot
KERNEL_MODULE_AUTOLOAD += "hello"

# The inherit of module.bbclass names packages with the "kernel-module-" prefix
RPROVIDES_${PN} += "kernel-module-hello"
```

`KERNEL_MODULE_AUTOLOAD` 变量会将模块名写入目标 rootfs 的
`/etc/modules-load.d/`,使 `systemd-modules-load` 在每次启动时插入该模块。

### 实际案例:KGSL

KGSL 配方展示了一个生产环境的树外模块:

```bitbake theme={null}
inherit module

DESCRIPTION = "Qualcomm KGSL driver for managing Adreno GPU"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://adreno.c;beginline=1;endline=1;md5=fcab174c20ea2e2bc0be64b493708266"

PV = "0.0+git"
SRCREV = "553c972604f739564d6bb70e18e3857c041984b1"
SRC_URI = " \
    git://github.com/qualcomm-linux/kgsl.git;branch=gfx-kernel.le.0.0;protocol=https \
    file://kgsl.rules \
"

do_install:append() {
    install -m 0644 ${WORKDIR}/sources/kgsl.rules -D ${D}${nonarch_base_libdir}/udev/rules.d/kgsl.rules
}

KERNEL_MODULE_PROBECONF += "msm_kgsl"
module_conf_msm_kgsl = "blacklist msm_kgsl"

FILES:${PN} += "${nonarch_base_libdir}/udev/rules.d"

COMPATIBLE_MACHINE = "^$"
COMPATIBLE_MACHINE:aarch64 = "(.*)"
```

注意 `blacklist msm_kgsl` 条目:当树外 KGSL 模块存在时,它可以防止上游树内的桩(stub)驱动被加载。

有关 Yocto 中树外模块的更多信息,请参阅
[Working with Out-of-Tree Modules](https://docs.yoctoproject.org/kernel-dev/common.html#working-with-out-of-tree-modules)。

## 模块版本管理策略

### 内核符号版本控制(MODVERSIONS)

当内核配置中设置了 `CONFIG_MODVERSIONS=y` 时,内核会为每个导出的符号嵌入一个 CRC 校验和。只有当模块的每个符号 CRC 与正在运行的内核相匹配时,该模块才会被加载,从而防止悄然插入二进制不兼容的模块。

在目标设备上验证 MODVERSIONS 是否生效:

```bash theme={null}
zcat /proc/config.gz | grep CONFIG_MODVERSIONS
```

针对不同内核树或不同 `defconfig` 构建的模块,若有任何 CRC 不匹配,加载时会产生错误:

```text theme={null}
insmod: ERROR: could not insert module hello.ko: Invalid module format
```

### vermagic 兼容性

每个 `.ko` 文件都嵌入一个 `vermagic` 字符串,编码构建时使用的内核版本、SMP 标志和编译器版本。正在运行的内核会拒绝任何 `vermagic` 不完全匹配的模块。

在部署前检查模块的 vermagic:

```bash theme={null}
modinfo hello.ko | grep vermagic
```

**表:vermagic 字段**

|   **字段**  |   **示例**  |          **说明**         |
| :-------: | :-------: | :---------------------: |
|    内核版本   |  `6.12.0` | 必须与目标设备上的 `uname -r` 匹配 |
|    SMP    |   `SMP`   |         对称多处理标志         |
| `preempt` | `preempt` |           抢占模型          |
|    编译器    |  `gcc-14` |        构建内核所用的工具链       |

### 在 Yocto 中固定版本

为确保树外模块始终针对与运行镜像相同的内核版本构建,请在模块的 `.bb` 文件中将 `DEPENDS` 和 `RDEPENDS` 设置为内核配方:

```bitbake theme={null}
DEPENDS += "virtual/kernel"
RDEPENDS_${PN} += "kernel-${KERNEL_VERSION}"
```

Yocto 的 `module` 类会自动将 `KERNEL_SRC` 和 `KERNELRELEASE` 设置为所选内核配方中的值,因此模块 Makefile 无需任何手动配置即可获取正确的头文件和符号表。
