1. 程式人生 > >驅動中suspend函式是如何被呼叫到的。

驅動中suspend函式是如何被呼叫到的。

android系統摁下電源鍵後會讓系統進入休眠以達到節電的目的。核心驅動中和休眠相關的就是suspend和resume函式。

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)

暫時記錄如下,以後再詳細分析