驅動中suspend函式是如何被呼叫到的。
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是系統提供的介面。下面分析這個介面的實現:
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的實現如下:
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函式,如下:
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和初始化了,實現如下:
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函式,具體實現如下:
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),實現如下:
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)
暫時記錄如下,以後再詳細分析