用可加载模块替换内核内置模块

Replace kernel builtin module with a loadable one

我开发了一个内核模块来管理一个 nf4 标签作为一个字符设备。

我在内核之外开发了这个模块,并在开发阶段将其编译为可加载内核模块(即 .ko)进行了测试。

一旦驱动程序可以正常运行并且足够稳定,我就使用补丁将其插入 linux 内核源代码 (v4.9.30),以便将其构建为内核的一部分。

这里我的情况是 加载 内核在引导时探测模块,因为它是内置的并且出现在设备树中。

现在,我想尝试对驱动程序进行一些改进,但我不想将这些更改直接实施到内核中。

所以我想将驱动程序代码集成到 linux 内核中,但不在启动时插入。 为此,我刚刚将带有 status = "disable"; 的驱动程序状态字段更改为设备树,实际上该模块在启动时不再插入。

但是我无法插入修改后的可加载模块。我在插入时有一个 ENODEV,这是由于 platform_device 没有在探测函数中找到。

我不明白的是,当设备树除了状态字段值之外没有更改时,为什么找不到平台设备。


编辑: 添加有关情况的精确度

经过更多探索后,我必须确定我什至没有进入回调 nf4_probe

在将 platform_driver_probe 实现(参见 here)检查到 v4.9.30 内核源代码之后,错误似乎来自这里:

if (code == 0 && list_empty(&drv->driver.p->klist_devices.k_list))
    retval = -ENODEV;

通过从命令行检查设备树,我可以看到设备被定义为目录 /proc/device-tree/nf4tag 存在并且填充了与设备树中的值相对应的值。


编辑: 在@sawdust 的回答后添加关于问题目的的精确度

我显然误解了 status=disable 意味着该设备根本不存在于硬件配置中。虽然它只是描述是否应该探测驱动程序。

为了让我的 objective 更清楚,我确实将驱动程序编码为正确的模块并编译为我正在使用的内核的可加载模块。

但是我不想重新编译内核来测试我所做的每一个更改。所以我的目标是只重新编译 .ko 直到我的修改完成,然后,一旦一切都完成,使用补丁将这些修改添加到内置模块。

通过这种工作方式,我可以只重建 .ko 并将其插入我的目标平台,而不是为每次修改重新编译内核。

所以要恢复我的问题应该是:

如何在不重新编译内核禁用内置模块的情况下用可加载模块替换内置模块?

除了禁用编译到内核的内置模块外,也许没有解决这个问题的方法。

What I don't understand is why the platform device is not beeing found when the device-tree hasn't been changed except for status field value.

您似乎误解了 status = "disable" 属性的实际含义。
除了它意味着内核应该"not having it inserted at boot",禁用节点意味着设备根本不是当前硬件配置的一部分.
驱动程序,无论是内置还是可加载模块,都不会被探测,因为它已针对当前配置被禁用。

如果您希望您的驱动程序(无论是内置的还是可加载的模块)处于当前配置中,则在其设备树节点中具有 status = "okay" 属性。

IOW 设备树用于向内核描述当前的硬件配置。
不要尝试使用设备树来控制可加载模块(因为它不能)。

Here I'm in the situation where the module is loaded at boot by kernel as it's builtin and it appears into the device tree.

这种说法毫无意义,因为您似乎同时将驱动程序描述为内置模块和可加载模块。
内置驱动程序不必 "loaded" 即可调用其探测例程。
因为驱动程序可以是内置的或可加载的,所以 "loading" 和 "probing" 是两个不同的阶段,不应混淆。

So I would like to keep the driver's code integrated into linux kernel but not having it inserted at boot.

您似乎将 Linux 内核的概念与源代码树混为一谈。
"Driver's code integrated into linux kernel" 通常会被解释为内置驱动程序,即驱动程序已链接,并且是在启动时加载的内核映像的一部分。
而存储在内核源代码树中的驱动程序代码没有指定它是内置模块还是可加载模块。许多驱动程序(和其他类型的模块)都可以构建,构建配置指定了哪个。

如果您希望您的驱动程序成为可加载模块,那么(而不是更改设备树):

一个。您需要将驱动程序编码为适当的模块;
b.您需要修改 Kconfig 文件以在内置模块或可加载模块之间进行选择(即 tristatebool 选择规范)。
C。配置内核以将您的驱动程序构建为可加载模块。


作为可加载模块并在设备树中定义的设备驱动程序仍然可以在引导期间自动加载和探测。 您可能必须使用模块黑名单来防止这种情况发生。


** 附录 **

How to replace a builtin module with a loadable one without recompiling kernel to disable the builtin module?

你不能。这就是为什么 Kconfig 会强制您在可加载模块 (m) 或内置 (y) 之间做出选择,如果您希望构建该驱动程序的话。

... then, once everything has been done, add those modifications to the builtin moduleusing a patch.

这没有任何意义,因为您只需要一份驱动程序源代码副本即可构建可加载模块或驱动程序的内置版本。

With this way of working I can just rebuild the .ko and insert it on my target platform instead of recompiling a kernel for each modification.

看来您将驱动程序集成到内核源代码中的方式有​​问题。
您实际上做了什么将您的驱动程序集成到内核源代码树中?
KconfigMakefile 你修改了哪个驱动?
您为驱动程序创建了哪些新的 CONFIG_* 符号?

是的,您必须 "recompile for each modification",但是 make 足够聪明,可以只重建必要的部分。当您知道只有您的可加载驱动程序已被修改时,您可以使用 make modules 进一步缩短内核重建时间。


总结

  • 如果不重新编译内核以禁用内置模块,则无法使用树外可加载模块。

但是

  • 仅在 M 处使用 tristate 重新编译内核一次并将模块列入内核启动行的黑名单成功。
  • 仅在 n 处使用 tristate 重新编译内核一次成功。

因此内核必须至少重新编译一次,但随后可以使用编译为可加载模块的树外驱动程序,而无需删除集成到 linux 源中的代码。