1,问题描述
4.19内核集成了preempt rt patch之后,使用timedatectl和hwclock -w出错,进一步发现系统没有/dev/rtc设备
2,调试过程
1)rtc-efi驱动模块的注册流程 (init/main.c)
[ 3.297202][ 1] [<ffffffc0009d9e5c>] rtc_device_register+0x2fc/0x310
[ 3.303545][ 1] [<ffffffc0009d9f3c>] devm_rtc_device_register+0x5c/0xb8
[ 3.310149][ 1] [<ffffffc002375cc8>] efi_rtc_probe+0x74/0xa0
[ 3.315797][ 1] [<ffffffc0008cdfa8>] platform_drv_probe+0x50/0xb8
[ 3.321880][ 1] [<ffffffc0008cbf34>] driver_probe_device+0x22c/0x2c8
[ 3.328222][ 1] [<ffffffc0008cc08c>] __driver_attach+0xbc/0xc0
[ 3.334044][ 1] [<ffffffc0008c9fa8>] bus_for_each_dev+0x60/0xa0
[ 3.339952][ 1] [<ffffffc0008cb7f0>] driver_attach+0x20/0x28
[ 3.345599][ 1] [<ffffffc0008cb3f8>] bus_add_driver+0x1c8/0x230
[ 3.351508][ 1] [<ffffffc0008ccc18>] driver_register+0x60/0xf8
[ 3.357329][ 1] [<ffffffc0008ce0dc>] __platform_driver_probe+0x74/0x120
[ 3.363932][ 1] [<ffffffc002375c4c>] efi_rtc_driver_init+0x20/0x28
[ 3.370101][ 1] [<ffffffc000082184>] do_one_initcall+0x8c/0x190
[ 3.376011][ 1] [<ffffffc002331b18>] kernel_init_freeable+0x1a8/0x24c -- do_pre_smp_initcalls -- do_one_initcall
[ 3.376244][ 1] usb 3-2: new high-speed USB device number 3 using xhci_hcd
[ 3.389304][ 1] [<ffffffc000dd2860>] kernel_init+0x18/0xe0
[ 3.394779][ 1] [<ffffffc000085420>] ret_from_fork+0x10/0x30
[ 3.400433][ 1] rtc-efi rtc-efi: rtc core: registered rtc-efi as rtc0
先看一下函数do_pre_smp_initcalls
static void __init do_pre_smp_initcalls(void)
{
initcall_entry_t *fn;
trace_initcall_level("early");
for (fn = __initcall_start; fn < __initcall0_start; fn++)
do_one_initcall(initcall_from_entry(fn));
}
其中
static initcall_entry_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
/* Keep these in sync with initcalls in include/linux/init.h */
static char *initcall_level_names[] __initdata = {
"pure",
"core",
"postcore",
"arch",
"subsys",
"fs",
"device",
"late",
};
也就是说,内核会一次初始化这个8个level的模块,其中rtc-efi模块在 __initcall6_start,也就是device这个分类中。
2)rtc-efi driver的注册过程(driver/rtc/rtc-efi.c)
在该文件中,使用module_platform_driver_probe(efi_rtc_driver, efi_rtc_probe); 将efi_rtc_driver链接到initcall这个section,同时注册该驱动的probe函数,在本例中是efi_rtc_probe
#define module_platform_driver_probe(__platform_driver, __platform_probe) \
static int __init __platform_driver##_init(void) \
{ \
return platform_driver_probe(&(__platform_driver), \
__platform_probe); \
} \
module_init(__platform_driver##_init); \
其中,efi_drc_driver定义如下:
static struct platform_driver efi_rtc_driver = {
.driver = {
.name = "rtc-efi",
},
};
3)rtc-efi device的注册过程(driver/rtc/rtc-efi-platform.c)
static struct platform_device rtc_efi_dev = {
.name = "rtc-efi",
.id = -1,
};
static int __init rtc_init(void)
{
if (efi_enabled(EFI_RUNTIME_SERVICES))
if (platform_device_register(&rtc_efi_dev) < 0)
pr_err("unable to register rtc device...\n");
/* not necessarily an error */
return 0;
}
module_init(rtc_init)
将名为rtc-efi的设备注册到系统device链表中。
4)正常流程是
....do_one_initcall -- efi_rtc_driver_init -- platform_driver_probe -- device_register -- driver_attach
在driver_attach中,会调用driver_match_device -- platform_match查找platform总线上的同名device,本例是“rtc-efi”,如果可以找到,会继续调用driver_probe_device直到efi_rtc_probe -- rtc_device_register,最终看到“rtc-efi rtc-efi: rtc core: registered rtc-efi as rtc0”
5)本例的问题
在driver/rtc/rtc-efi-platform.c的rtc_init中,efi_enabled(EFI_RUNTIME_SERVICES)返回false,导致rtc-efi device并没有注册到platform总线,这个rtc-efi driver没有找到匹配的设备,从而并没有调用efi_rtc_probe函数。
那么,为什么EFI_RUNTIME_SERVICES没有enabled呢?
6)efi runtime services的启动过程
代码drivers/firmware/efi/arm-runtime.c的函数arm_enable_runtime_services在下面的代码返回:
if (efi_runtime_disabled()) {
pr_info("EFI runtime services will be disabled.\n");
return 0;
}
....
setbit(EFI_RUNTIME_SERVICES, &efi.flags)
在本例中efi_runtime_disabled()直接返回true,导致没有执行setbit(EFI_RUNTIME_SERVICES, &efi.flags),进而导致rtc-efi device并没有注册到platform总线,这个rtc-efi driver没有找到匹配的设备,从而并没有调用efi_rtc_probe函数。
7)为什么efi_runtime_disabled()会返回true,谁将efi runtime service关掉的
继续看代码drivers/firmware/efi/efi.c
bool efi_runtime_disabled(void)
{
return disable_runtime;
}
可见,该逻辑是由变量disable_runtime来控制的。
进一步分析代码:
标准linux:static bool disable_runtime;
打了preempt rt patch之后:static bool disable_runtime = IS_ENABLED(CONFIG_PREEMPT_RT_BASE);
那么disable_runtime在打了preempt rt patch之后,初始值变为了TRUE。
3,解决方案
首先,我们要关注preempt rt patch为什么默认关掉了efi runtime services,这点还有进一步向社区求证,然后,看一看是够可以打开,继续看代码drivers/firmware/efi/efi.c
static int __init parse_efi_cmdline(char *str)
{
if (!str) {
pr_warn("need at least one option\n");
return -EINVAL;
}
if (parse_option_str(str, "debug"))
set_bit(EFI_DBG, &efi.flags);
if (parse_option_str(str, "noruntime"))
disable_runtime = true;
+ if (parse_option_str(str, "runtime"))
+ disable_runtime = false;
+
return 0;
}
early_param("efi", parse_efi_cmdline);
从这段代码可以看出,preempt rt patch允许在cmdline中增加efi=runtime选项,启动efi runtime services. 同时说明,preempt rt patch也是允许使能efi runtime services的。