第19章 Linux电源管理的系统架构和驱动之CPUIdle驱动

19.3 CPUIdle驱动

    目前的ARM SoC(System on Chip)大多支持几个不同的Idle级别,CPUIdle驱动子系统存在的目的就是对这些Idle状态进行管理,并根据系统的运行情况进入不同的Idle级别。具体SoC的底层CPUIdle驱动实现提供一个Idle级别表,实现各种不同Idle状态的进入和退出流程。

    对于Intel系列笔记本计算机,支持ACPI(Advanced Configuration and Power Interface,高级配置和
电源接口),一般有4个不同的C状态(其中C0为操作状态,C1是Halt状态,C2是Stop-Clock状态,C3是

Sleep状态),如表19.3所示。

表19.3 4个不同的C状态


    对于ARM,各个SoC对于Idle的实现方法差异比较大,各个SoC对于Idle的实现方法差异比较大,最简单的Idle级别将CPU核置于WFI(wait for interrupt 等待中断发生)状态,因此在默认情况下,若SoC未实现自身的芯片级CPUIdle驱动,则会进入cpu_do_idle(),对于ARM V7,其实现位于arch/arm/mm/proc-v7.S中:

/*
 *      cpu_v7_do_idle()
 *
 *      Idle the processor (eg, wait for interrupt).
 *
 *      IRQs are already disabled.
 */
ENTRY(cpu_v7_do_idle)
        dsb                                     @ WFI may enter a low-power mode
        wfi
        ret     lr

ENDPROC(cpu_v7_do_idle)

CPUIdle的核心层提供如下API以用于注册一个cpuidle_driver的实例:

linux/cpuidle.h

extern int cpuidle_register_driver(struct cpuidle_driver *drv);

drivers/cpuidle/driver.c

/**
 * cpuidle_register_driver - registers a driver
 * @drv: a pointer to a valid struct cpuidle_driver
 *
 * Register the driver under a lock to prevent concurrent attempts to
 * [un]register the driver from occuring at the same time.
 *
 * Returns 0 on success, a negative error code (returned by
 * __cpuidle_register_driver()) otherwise.
 */
int cpuidle_register_driver(struct cpuidle_driver *drv)
{

        int ret;

        spin_lock(&cpuidle_driver_lock); // 自旋锁锁定期间不允许阻塞,要求临界区比较小
        ret = __cpuidle_register_driver(drv);
        spin_unlock(&cpuidle_driver_lock);
        return ret;
}
EXPORT_SYMBOL_GPL(cpuidle_register_driver);

提供如下API来注册一个cpuidle_device:

linux/cpuidle.h

extern int cpuidle_register_device(struct cpuidle_device *dev);

drivers/cpuidle/cpuidle.c


/**
 * cpuidle_register_device - registers a CPU's idle PM feature
 * @dev: the cpu
 */
int cpuidle_register_device(struct cpuidle_device *dev)
{
        int ret = -EBUSY;

        if (!dev)
                return -EINVAL;

        mutex_lock(&cpuidle_lock);

        if (dev->registered)
                goto out_unlock;

        __cpuidle_device_init(dev);

        ret = __cpuidle_register_device(dev);
        if (ret)
                goto out_unlock;

        ret = cpuidle_add_sysfs(dev);
        if (ret)
                goto out_unregister;

        ret = cpuidle_enable_device(dev);
        if (ret)
                goto out_sysfs;

        cpuidle_install_idle_handler();

out_unlock:
        mutex_unlock(&cpuidle_lock);
        return ret;

out_sysfs:
        cpuidle_remove_sysfs(dev);
out_unregister:
        __cpuidle_unregister_device(dev);
        goto out_unlock;
}

备注:

    CPUIdle驱动必须针对每个CPU注册相应的cpuidle_device,这意味着对于多核CPU,需要针对每个CPU注册一次。

    cpuidle_register_driver()接受1个cpuidle_driver结构体的指针参数,该结构体是CPUIdle驱动的主体,
其定义如代码清单19.4所示。
代码清单19.4 cpuidle_driver结构体

struct cpuidle_driver {
const char *name;
struct module *owner;
int                     refcnt;

        /* used by the cpuidle framework to setup the broadcast timer */
unsigned int            bctimer:1;
/* states array must be ordered in decreasing power consumption */
struct cpuidle_state states[CPUIDLE_STATE_MAX];
int state_count;
int safe_state_index;

/* the driver handles the cpus in cpumask */
struct cpumask *cpumask;
};

该结构体的关键成员是1个cpuidle_state的表,该表就是用于存储各种不同Idle级别的信息,定义如代码清单19.5所示。

代码清单19.5 cpuidle_state结构体

struct cpuidle_state {
char name[CPUIDLE_NAME_LEN]; // Idle状态的名称
char desc[CPUIDLE_DESC_LEN];Idle状态的描述

unsigned int flags;
unsigned int exit_latency; /* in US */ //退出该Idle状态需要的延迟
int power_usage; /* in mW */
unsigned int target_residency; /* in US */
bool disabled; /* disabled on all CPUs */

int (*enter)(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index);//进入该Idle状态的实现方法。

int (*enter_dead) (struct cpuidle_device *dev, int index);
};

    一个具体的AT91 SoC的CPUIdle驱动实例drivers/cpuidle/cpuidle-at91.c,有两个Idle级别,即WFI和RAM_SR,其具体实现如代码清单19.6所示。

#define AT91_MAX_STATES 2

static void (*at91_standby)(void); // 函数指针

/* Actual code that puts the SoC in different idle states */
static int at91_enter_idle(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
       int index)
{
at91_standby(); //调用函数指针
return index;
}

static struct cpuidle_driver at91_idle_driver = {
.name = "at91_idle",
.owner = THIS_MODULE,
.states[0] = ARM_CPUIDLE_WFI_STATE,
.states[1] = {
.enter = at91_enter_idle,
.exit_latency = 10,
.target_residency = 10000,
.flags = CPUIDLE_FLAG_TIME_VALID,
.name = "RAM_SR",
.desc = "WFI and DDR Self Refresh",
},
.state_count = AT91_MAX_STATES,
};

/* Initialize CPU idle by registering the idle states */
static int at91_cpuidle_probe(struct platform_device *dev)
{
at91_standby = (void *)(dev->dev.platform_data); // 给函数指针赋初值

return cpuidle_register(&at91_idle_driver, NULL); // 注册CPUIdel
}

static struct platform_driver at91_cpuidle_driver = {
.driver = {
.name = "cpuidle-at91",
.owner = THIS_MODULE,
},
.probe = at91_cpuidle_probe,
};
module_platform_driver(at91_cpuidle_driver);

在CPUIdle子系统中也有对应的governor(调节器)来抉择何时进入何种Idle级别的策略,这些governor包括CPU_IDLE_GOV_LADDER、CPU_IDLE_GOV_MENU。LADDER在进入和退出Idle级别的时候是步进的,以过去的Idle时间作为参考,适用于没有采用动态时间节拍的系统(即没有选择NO_HZ的系统),不依赖于NO_HZ配置选项;MENU总是根据预期的空闲时间直接进入目标Idle级别,依赖于内核的NO_HZ选项。

    图19.3演示LADDER步进从C0进入C3,而MENU则可能直接从C0跳入C3。


图19.3 LADDER与MENU的区别

CPUIdle子系统还通过sys向userspace导出了一些节点:

 一类是针对整个系统的/sys/devices/system/cpu/cpuidle,通过其中的current_driver、current_governor、available_governors等节点获取或设置CPUIdle的驱动信息以及governor。

一类是针对每个CPU的/sys/devices/system/cpu/cpux/cpuidle,通过子节点暴露各个在线的CPU中每个不同Idle级别的name、desc、power、latency等信息。

Linux CPUIdle子系统的总体架构,如图19.4所示。


图19.4 Linux CPUIdle子系统的整体架构


文章来源: 第19章 Linux电源管理的系统架构和驱动之CPUIdle驱动

人吐槽 人点赞

猜你喜欢

发表评论

用户名: 密码:
验证码: 匿名发表

你可以使用这些语言

查看评论:第19章 Linux电源管理的系统架构和驱动之CPUIdle驱动