Linux系统的suspend流程分析

第1和第2节的参考链接:

http://www.wowotech.net/pm_subsystem/suspend_and_resume.html

第3节开始的参考链接:

https://www.cnblogs.com/arnoldlu/p/6344847.html

https://blog.youkuaiyun.com/tiantao2012/article/details/72621155

https://www.cnblogs.com/LoyenWang/p/11370557.html

正文

1、

Linux内核提供了三种Suspend: Freeze、Standby和STR(Suspend to RAM),在用户空间向"/sys/power/state”文件分别写入”freeze”、”standby”和”mem”,即可触发它们。在用户空间执行如下操作会调用到内核空间的相关函数:

echo "freeze" > /sys/power/state  /*Freeze*/

echo "standby" > /sys/power/state /*Standby*/

echo "mem" > /sys/power/state /*STR*/

2、

假如向节点/sys/power/state写入了mem,我们看一下具体怎么调用的

函数路径:

state_store:kernel\power\main.c

pm_suspend、valid_state:kernel\power\suspend.c

函数调用关系:

state_store
    pm_suspend
        enter_state
            valid_state  ---- 2.1节
                sys_sync /*同步文件系统*/
                suspend_prepare -----  2.2节
                suspend_devices_and_enter -----  2.3节
                suspend_finish

2.1、

static bool valid_state(suspend_state_t state)
{
    /*
     * PM_SUSPEND_STANDBY and PM_SUSPEND_MEM states need low level
     * support and need to be valid to the low level
     * implementation, no valid callback implies that none are valid.
     */
    return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}

实际上,valid_state函数是判断该平台是否支持该state的待机方式,而suspend_ops这个结构体是该平台自己实现并注册进系统的,比如我们amlogic平台的相关代码。

函数路径:

meson_pm_probe:drivers\amlogic\pm\gx_pm.c
suspend_set_ops:kernel\power\suspend.c
static const struct platform_suspend_ops meson_gx_ops = {
    .enter = meson_gx_enter,
    .prepare = meson_pm_prepare,
    .finish = meson_pm_finish,
    .valid = suspend_valid_only_mem,
};

static int __init meson_pm_probe(struct platform_device *pdev)
{
    ...
    suspend_set_ops(&meson_gx_ops);
    ...
}

而suspend_set_ops函数就将meson_gx_ops赋值给了suspend_ops结构体。

2.2、

代码路径:

suspend_prepare:kernel\power\suspend.c

函数调用关系:

suspend_prepare
    sleep_state_supported /* 检查是否提供了.enter回调函数,这个回调函数也是由平台实现 */

2.3、

代码路径:

kernel\power\suspend.c

函数调用关系:

suspend_devices_and_enter
    sleep_state_supported /*再次检查平台是否支持suspend_ops*/
    platform_suspend_begin /*如果是freeze状态,则调用平台注册的freeze_ops->begin回调函数;或则suspend_ops->begin;否则返回0*/
    suspend_console /*挂起console。该接口由"kernel\printk.c"实现,主要是hold住一个lock,该lock会阻止其它代码访问console*/
    dpm_suspend_start /*调用所有设备的->prepare和->suspend回调函数,suspend需要正常suspend的设备。suspend device可能失败,需要跳至 Recover_platform,执行recover操作*/
    suspend_enter ----- 2.3.1节

suspend_enter调用前,都是suspend前的准备工作,调用suspend_enter接口,使系统进入指定的电源状态。

2.3.1、

代码路径:

kernel\power\suspend.c

函数调用关系:

suspend_enter
    platform_suspend_prepare /*回调平台的suspend_ops->prepare函数,我的平台只是打印一些信息,没有实际动作*/
    dpm_suspend_late /*调用所有设备的->suspend_late*/
    platform_suspend_prepare_late /*如果是freeze状态,则调用平台注册的freeze_ops->prepare回调函数*/
    dpm_suspend_noirq /*调用所有设备的->suspend_noirq*/
    platform_suspend_prepare_noirq /*如果非freeze状态,并且实现了suspend_ops->prepare_late函数,则调用*/
    disable_nonboot_cpus /*禁止所有的非boot cpu,启动过程中,负责启动系统给的CPU称为boot CPU。也会失败,执行恢复操作即可*/
    arch_suspend_disable_irqs /*实际调用local_irq_disable,关全局中断。如果无法关闭,则为bug*/
    pm_wakeup_pending /*最后检查一下是否还有唤醒事件发生*/
    suspend_ops->enter /*一切顺利的话,就调用到enter回调函数进入suspend了*/

3、

上面最后调用到suspend_ops->enter,不同的平台enter函数可能不一样,在上面讲到,是在suspend_set_ops时注册进系统的。

下面主要分析我手上的平台

代码路径:

meson_gx_enter、meson_gx_suspend:drivers\amlogic\pm\gx_pm.c
arm_cpuidle_suspend:arch\arm64\kernel\cpuidle.c

函数调用关系:

meson_gx_enter
    meson_gx_suspend
        arm_cpuidle_suspend
            int cpu = smp_processor_id();  /*获取当前是哪个CPU core*/
            return cpu_ops[cpu]->cpu_suspend(index); /*调用cpu_suspend函数*/

3.1、

上面我们可能又要遇到一个难题,这个cpu_suspend函数具体是哪个呢?很明显,这个是回调函数,想知道具体调用到哪里去了,还需要从系统初始化讲起。直接给出函数调用流程,对着代码看就一目了然了。

代码路径:

start_kernel:init\main.c
setup_arch:arch\arm64\kernel\setup.c
psci_dt_init:

函数调用关系:

start_kernel
    setup_arch
        psci_dt_init  ---  3.1.1小节

3.1.1、

这小节我们看一下psci_dt_init函数具体做了什么

static const struct of_device_id psci_of_match[] __initconst = {
    { .compatible = "arm,psci",    .data = psci_0_1_init},
    { .compatible = "arm,psci-0.2",    .data = psci_0_2_init},
    { .compatible = "arm,psci-1.0",    .data = psci_0_2_init},
    {},
};

int __init psci_dt_init(void)
{
    ...
    np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np);
    
    if (!np)
        return -ENODEV;

    init_fn = (psci_initcall_t)matched_np->data;
    return init_fn(np);
}

psci_dt_init中调用了of_find_matching_node_and_match去解析psci_of_match结构体,并找到匹配的compatible字段。比如我的

平台dts文件中有如下定义:

    psci {
        compatible = "arm,psci-0.2";
        method = "smc";
    };

所以最终调用了第二项的psci_0_2_init函数,下面我们继续分析psci_0_2_init函数。

3.2、

代码路径:

drivers\firmware\psci.c

函数调用关系:

psci_0_2_init
    psci_probe
        psci_get_version /* 获取psci的版本号,我们平台使用的是0.2版本 */
        psci_0_2_set_functions ---- 3.2.1小节

        
3.2.1、

代码很容易,就是回调函数的赋值,从下面可以看出来,.cpu_suspend对应的回调函数是psci_cpu_suspend

static void __init psci_0_2_set_functions(void)
{
    pr_info("Using standard PSCI v0.2 function IDs\n");
    psci_ops.get_version = psci_get_version;

    psci_function_id[PSCI_FN_CPU_SUSPEND] =
                    PSCI_FN_NATIVE(0_2, CPU_SUSPEND);  /* 将最终调用的suspend函数的地址赋值到psci_function_id数组中,后面还会讲到 */
    psci_ops.cpu_suspend = psci_cpu_suspend;
    
    ...
}

讲到这里,我们应该可以回答3.1小节提到的问题了。系统suspend的最后调用的:cpu_ops[cpu]->cpu_suspend(index)。应该就是对应到psci_cpu_suspend()函数了。但是我们可以发现一个小细节,cpu_suspend是指针cpu_ops的成员,但是psci_cpu_suspend却是赋值给了psci_ops指针的成员cpu_suspend。又怎么将cpu_ops->cpu_suspend和psci_ops.cpu_suspend联系起来呢?

3.3、

要回答上一小节提出的问题,我们还是直接给出函数调用关系,对着代码看就一目了然了,这个不需要研究,知道流程就可以了。

代码路径:

smp_init_cpus、smp_init_cpus:arch\arm64\kernel\smp.c
cpu_read_ops、cpu_read_enable_method、cpu_get_ops:arch\arm64\kernel\cpu_ops.c

函数流程:

start_kernel
    setup_arch
        smp_init_cpus
            smp_cpu_setup
                cpu_read_ops
                    cpu_read_enable_method /* 同样是获取DTS的值,这里返回了"psci" */
                    cpu_get_ops  ---- 3.3.1小节

3.3.1、

static const struct cpu_operations *dt_supported_cpu_ops[] __initconst = {
    &smp_spin_table_ops,
    &cpu_psci_ops,
    NULL,
};

static const struct cpu_operations * __init cpu_get_ops(const char *name)
{
    const struct cpu_operations **ops;

    ops = acpi_disabled ? dt_supported_cpu_ops : acpi_supported_cpu_ops; /* 调用到dt_supported_cpu_ops */

    while (*ops) {
        if (!strcmp(name, (*ops)->name)) /* 因为我们的是psci,所以调用cpu_psci_ops方法 */
            return *ops;

        ops++;
    }

    return NULL;
}

而cpu_psci_ops结构体的定义是在:arch\arm64\kernel\psci.c

const struct cpu_operations cpu_psci_ops = {
    .name        = "psci",
#ifdef CONFIG_CPU_IDLE
    .cpu_init_idle    = psci_cpu_init_idle,
    .cpu_suspend    = psci_cpu_suspend_enter,
#endif
    .cpu_init    = cpu_psci_cpu_init,
    .cpu_prepare    = cpu_psci_cpu_prepare,
    .cpu_boot    = cpu_psci_cpu_boot,
#ifdef CONFIG_HOTPLUG_CPU
    .cpu_disable    = cpu_psci_cpu_disable,
    .cpu_die    = cpu_psci_cpu_die,
    .cpu_kill    = cpu_psci_cpu_kill,
#endif
};

上面的psci_cpu_suspend_enter就是对应3.2.1小节最后提到的cpu_ops->cpu_suspend,进入到psci_cpu_suspend_enter函数看:

int psci_cpu_suspend_enter(unsigned long index)
{
    int ret;
    u32 *state = __this_cpu_read(psci_power_state); /* 读取当前CPU的power state,这个值也是在DTS中定义 */
    /*
     * idle state index 0 corresponds to wfi, should never be called
     * from the cpu_suspend operations
     */
    if (WARN_ON_ONCE(!index))
        return -EINVAL;

    if (!psci_power_state_loses_context(state[index - 1]))
        ret = psci_ops.cpu_suspend(state[index - 1], 0);
    else
        ret = cpu_suspend(index, psci_suspend_finisher); /* 这个最后也会调用psci_ops.cpu_suspend */

    return ret;
}

现在一目了然了,psci_cpu_suspend_enter里面最终就是调用到了psci_ops.cpu_suspend函数,也就是3.2.1小节赋值的psci_cpu_suspend函数

4、小结

之所以有这篇文章,主要是之前遇到了一个待机的问题,在跟踪问题的过程中,跟了一遍代码流程,后面会继续写一篇文章说一下这个问题。而suspend的流程分析就到此结束了。

<think>嗯,我现在需要分析Linux下的SDIO接口WiFi驱动。首先,我应该从整体上了解SDIO在Linux中的位置和作用。SDIO(Secure Digital Input Output)是一种扩展接口,常用于连接WiFi、蓝牙等设备。在Linux驱动架构中,SDIO属于MMC子系统的一部分,因为SDIO是基于SD标准扩展而来的。 接下来,我需要了解SDIO WiFi驱动的基本结构。Linux的驱动模型通常分为总线、设备和驱动三个部分。对于SDIO来说,总线驱动由MMC子系统处理,负责检测SDIO设备,并注册到系统中。当检测到SDIO设备后,会通过设备树或ACPI进行匹配,加载对应的驱动。 然后,WiFi驱动本身应该作为一个SDIO客户端驱动存在。这个驱动需要注册到SDIO总线上,当匹配到对应的设备ID时,就会被绑定。这时候,驱动会初始化硬件,配置中断、DMA等资源,并注册网络设备接口。 具体到代码层面,可能需要查看drivers/net/wireless目录下的相关驱动,比如ath6kl、b43-sdio等。这些驱动通常会使用sdio_register_driver()函数注册自己,并提供.id_table来声明支持的设备。在.probe回调函数中,驱动会进行初始化,比如分配net_device结构,配置MAC地址,设置操作函数集(如open、stop、xmit等)。 中断处理也是关键部分。SDIO设备通常通过中断线通知主机有数据到达。驱动需要在.probe中注册中断处理函数,处理接收数据包等操作。此外,SDIO的读写操作可能需要使用sdio_readb()、sdio_writeb()等函数,或者块传输函数进行数据传输。 还有电源管理和错误处理需要考虑。SDIO设备可能进入低功耗模式,驱动需要正确实现suspend和resume回调。同时,传输错误或设备断开的情况也需要处理,避免系统崩溃。 最后,调试和日志输出也是分析驱动的重要部分。通过dmesg查看内核日志,确认驱动是否正确加载,设备是否被识别,以及数据传输过程中是否有错误发生。此外,使用工具如iwconfig、ifconfig来测试WiFi接口是否正常工作。 不过,在分析过程中可能会遇到一些问题,比如设备ID不匹配导致驱动未加载,或者中断处理不正确导致数据接收失败。这时候需要仔细检查设备树配置,确认中断号和寄存器地址是否正确,以及驱动代码中是否有资源申请失败的情况。</think>以下是对Linux SDIO接口WiFi驱动的分层次分析,按系统架构逐步展开: 一、SDIO子系统基础架构 1. 硬件层 - SDIO物理接口基于SD 2.0规范扩展 - 工作时钟频率范围:0-50MHz - 支持1-bit/4-bit传输模式 2. Linux系统组成 $$sdio\_core.c \rightarrow MMC/SD核心层$$ $$sdio\_bus.c \rightarrow SDIO总线抽象层$$ $$sdio\_io.c \rightarrow 寄存器访问接口$$ 二、驱动加载流程分析 1. 总线枚举阶段 - MMC控制器检测卡插入(通过GPIO中断或轮询) - 发送CMD5命令验证SDIO设备存在 - 读取CIS(Card Information Structure)获取设备信息 2. 驱动匹配过程 ```c static struct sdio_device_id wifi_ids[] = { { SDIO_DEVICE(MANUFACTURER_ID, PRODUCT_ID) }, { } }; static struct sdio_driver wifi_driver = { .probe = wifi_probe, .remove = wifi_remove, .id_table = wifi_ids, .drv = { .name = "sdio_wifi", } }; ``` 三、关键驱动组件实现 1. 设备初始化(probe函数) - 申请SDIO功能编号:`sdio_claim_host()` - 配置块传输模式:`sdio_set_block_size()` - 使能功能中断:`sdio_claim_irq()` 2. 数据传输通道 ```c // 寄存器读写示例 u8 val = sdio_readb(func, REG_ADDR, &ret); sdio_writeb(func, val, REG_ADDR, &ret); // 块传输示例 sdio_memcpy_fromio(func, buffer, addr, len); ``` 四、中断处理机制 1. 中断注册流程 ```c sdio_claim_irq(func, wifi_interrupt); ``` 2. 典型中断处理内容 - 数据接收就绪 - 传输完成通知 - 低功耗状态唤醒 五、与网络子系统对接 1. 网络设备注册 ```c struct net_device *netdev = alloc_etherdev(sizeof(struct wifi_priv)); netdev->netdev_ops = &wifi_netdev_ops; register_netdev(netdev); ``` 2. 数据包传输路径 - 发送:`ndo_start_xmit()` -> SDIO块写入 - 接收:中断触发 -> SDIO块读取 -> `netif_rx()` 六、电源管理实现 1. 低功耗状态转换 ```c static int wifi_suspend(struct device *dev) { sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); return 0; } ``` 七、典型问题排查方法 1. 设备识别失败检查 - `dmesg | grep mmc` 查看总线枚举日志 - 验证CIS信息是否正确解析 2. 数据传输异常处理 - 使用`mmc-utils`工具进行寄存器调试 - 检查DMA对齐要求(常见4字节对齐限制) 八、主流芯片驱动参考 1. Qualcomm Atheros AR6xxx系列 - 驱动路径:`drivers/net/wireless/ath/ath6kl/` 2. Broadcom BCM43xx系列 - 驱动路径:`drivers/net/wireless/broadcom/b43/` 建议分析具体驱动时: 1. 从`module_init`入口开始跟踪初始化流程 2. 重点关注`sdio_ops`结构体中的回调函数实现 3. 结合芯片手册对照寄存器操作逻辑 4. 使用`ftrace`跟踪中断触发频率和数据传输路径
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值