rtc问题引起的内核驱动初始化的探究

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的。

 

 

 

 

上一篇:普通笔记本/台式机安装MacOS教程


下一篇:在Windows系统下加装macOS(黑苹果)