suspend函数用于休眠,resume函数用于唤醒。下面分析驱动中的这两个函数是如何被调用到的。
驱动部分:
首先需要分析驱动的注册过程,较新的内核都是采用DTS方式来取代在内核中直接定义platform_device数据结构的注册方式,本文是基于DTS机制的内核来分析。
product对应的dts文件在编译时被编译为dtb文件,uboot在启动时候会将其地址传给内核,内核在启动过程中会去解析,具体解析是在start_kernel()->setup_arch() -->unflatten_device_tree()中具体分析可以参考网上,解析的最终结果会存放在allnodes地址处,这个allnodes随后在machine的init函数
中被使用,init函数中会根据allnodes中的节点数据组合成platform_device数据结构,然后将其注册到platform总线上,下面简要分析一下并重点关注这些初始化过程中和
pm相关的初始化。
我参与的项目中machine的init函数就是via_init_machine函数,在这个函数中就是调用了of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)这个函数来解析allnodes的。of_platform_populate是系统提供的接口。下面分析这个接口的实现:
[html] view plain copy
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child
int rc = 0
root = root ? of_node_get(root) : of_find_node_by_path("/")
if (!root)
return -EINVAL
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true)
if (rc)
break
}
of_node_put(root)
return rc
}
root最后就是取到的根节点,然后其作为参数传递给of_platform_bus_create,of_platform_device_create_pdata的实现如下:
[html] view plain copy
static int of_platform_bus_create(struct device_node *bus,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent, bool strict)
{
const struct of_dev_auxdata *auxdata
struct device_node *child
struct platform_device *dev
const char *bus_id = NULL
void *platform_data = NULL
int rc = 0
/* Make sure it has a compatible property */
if (strict &&(!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %s, no compatible prop\n",
__func__, bus->full_name)
return 0
}
auxdata = of_dev_lookup(lookup, bus)
if (auxdata) {
bus_id = auxdata->name
platform_data = auxdata->platform_data
}
if (of_device_is_compatible(bus, "arm,primecell")) {
of_amba_device_create(bus, bus_id, platform_data, parent)
return 0
}
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent)
if (!dev || !of_match_node(matches, bus))
return 0
for_each_child_of_node(bus, child) {
pr_debug(" create child: %s\n", child->full_name)
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict)
if (rc) {
of_node_put(child)
break
}
}
return rc
}
根据传入参数,我们这里直接分析of_platform_device_create_padate函数,如下:
[html] view plain copy
struct platform_device *of_platform_device_create_pdata(
struct device_node *np,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct platform_device *dev
if (!of_device_is_available(np))
return NULL
dev = of_device_alloc(np, bus_id, parent)
if (!dev)
return NULL
#if defined(CONFIG_MICROBLAZE)
dev->archdata.dma_mask = 0xffffffffUL
#endif
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32)
dev->dev.bus = &platform_bus_type
dev->dev.platform_data = platform_data
/* We do not fill the DMA ops for platform devices by default.
* This is currently the responsibility of the platform code
* to do such, possibly using a device notifier
*/
if (of_device_add(dev) != 0) {
platform_device_put(dev)
return NULL
}
return dev
}
of_platform_device_create_padate->of_device_alloc->platform_device_alloc
便在platform_device_alloc函数中进行进行alloc和初始化了,实现如下:
[html] view plain copy
struct platform_device *platform_device_alloc(const char *name, int id)
{
struct platform_object *pa
pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL)
if (pa) {
strcpy(pa->name, name)
pa->pdev.name = pa->name
pa->pdev.id = id
device_initialize(&pa->pdev.dev)
pa->pdev.dev.release = platform_device_release
arch_setup_pdev_archdata(&pa->pdev)
}
return pa ? &pa->pdev : NULL
}
可以看到有个device_initialize,这里面对pdev.dev做一些列的初始化,其中有一个函数就是device_pm_init,这个函数就是我们一直关心的device相关的pm函数,具体实现如下:
[html] view plain copy
void device_pm_init(struct device *dev)
{
dev->power.is_prepared = false
dev->power.is_suspended = false
init_completion(&dev->power.completion)
complete_all(&dev->power.completion)
dev->power.wakeup = NULL
spin_lock_init(&dev->power.lock)
pm_runtime_init(dev)
INIT_LIST_HEAD(&dev->power.entry)
dev->power.power_state = PMSG_INVALID
}
可以看见它对device和功耗相关的数据做了一些初始化,我们这里先重点关注下dev->power.entry,初始化一个链表头,所以他/它很有可能会在后面加到某个链表里面去,而那个链表应该是用来保存所有的device用的。系统中所有的platform_device都是通过这种方式注册到系统中的,那么应该所有的platform_device都会初始化一个dev->power.entry,如果到时候把所有的dev->power.entry都添加到某个链表上去,那么系统到时候查询的时候只要找到这个list head就可以找到所有的platform_device了。嗯,不过这是我们的猜测。我们接下去分析来验证下。
platform_device通过alloc之后已经初始化好了,那么接下去就可以添加到系统中了,所以我们再回头看of_platform_device_create_pdata的实现。
函数在of_device_alloc之后把dev->dev.bus赋值给了platform_bus_type,接着就调用了of_device_add函数,在of_device_add函数中最后通过device_add添加到了bus上,但是device_add中有个函数需要我们关系,就是device_pm_add(dev),实现如下:
[html] view plain copy
void device_pm_add(struct device *dev)
{
pr_debug("PM: Adding info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus", dev_name(dev))
mutex_lock(&dpm_list_mtx)
if (dev->parent &&dev->parent->power.is_prepared)
dev_warn(dev, "parent %s should not be sleeping\n",
dev_name(dev->parent))
list_add_tail(&dev->power.entry, &dpm_list)
dev_pm_qos_constraints_init(dev)
mutex_unlock(&dpm_list_mtx)
}
可以看到这里list_add_tail(&dev->power.entry, &dpm_list)这就验证了我们之前的猜测。所有注册到系统中的设备,最终都是会添加到dpm_list这条链表上。
那么系统在休眠的时候是如何通过dmp_list这表链表来suspend设备的呢?接下去就是我们要分析的电源管理部分内容。
系统电源部分:
电源管理相关文件在kernel/power目录下,前面已经分析到。系统中注册的设备都是会添加到dmp_list这条链表上的。那么睡眠的时候系统应该是会查找dmp_list这条链表,
然后通过这条链表依次去查到对应的driver,然后调用driver中的suspend方法。下面我们来验证。
2.在suspend会轮询bus下的driver,然后一次调用到driver->pm->suspend方法,然后进入休眠。
3.state_store->pm_suspend->enter_state->suspend_devices_and_enter->dpm_suspend_start->dpm_suspend->device_suspend->__device_suspend->pm_op->(ops->suspend)
本文基于 RockPI 4A 单板 Debian 系统 Linux4.4 内核介绍下睡眠唤醒( suspend/resume )的一些调试方法。
1、关闭串口睡眠
在Linux内核睡眠过程中,会先调用 suspend_console() 函数使串口进入睡眠状态,这样会导致后续设备驱动的睡眠过程不可见。可以在boot启动参数中增加 no_console_suspend 参数,显示设备驱动睡眠日志。
2、修改串口日志等级
修改串口日志打印等级,显示更多调试信息。
3、打开设备睡眠唤醒时间
设置 pm_print_times 参数,可以显示设备驱动睡眠唤醒时间,方便调试时查看哪个函数处理占用时间过长。
在调试Linux内核睡眠唤醒功能时,可以使用 RTC 做唤醒源,在系统睡眠5秒后,自动唤醒系统。
在 arch/arm64/configs/rockchip_linux_defconfig 文件中配置宏 CONFIG_PM_TEST_SUSPEND 。
唤醒日志如下:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)