1. 程式人生 > >Linux:核心之解析DTS裝置樹檔案並建立裝置的過程

Linux:核心之解析DTS裝置樹檔案並建立裝置的過程

  • 核心之解析DTS裝置樹檔案並建立裝置的過程

在這裡,我分析的是核心原始碼來自谷歌官方Android7.1.2原始碼包經過打補丁包"SC60_Android7.1.2_Quectel_SDK_r270060_20180731.tar.gz"後得到的.

本文分析時使用的工具是"SourceInsight".

分析過程中抓大放小,僅分析主幹流程,細枝末節的東西未深究,如果有同志願意分享自己分析的心得,或者進行了更深層次的分析,還望貼出自己部落格連結在評論欄,或者把相關資料發到我的郵箱"[email protected]",謝謝~

  • 主幹的分析的過程

搜尋有關巨集定義DT_MACHINE_START的所有檔案發現:該巨集定義是在"kernel\msm-3.18\arch\arm\include\asm\mach"目錄的"arch.h",除此之外,其他檔案都為這個巨集定義的使用檔案,形如在目錄"kernel\msm-3.18\arch\arm\mach-xxx"下,名為"xxx.c"的檔案.這裡我用的是"kernel\msm-3.18\arch\arm\mach-msm\board-8953.c

"來切入,進行後續分析:

DT_MACHINE_START(MSM8953_DT,
	"Qualcomm Technologies, Inc. MSM 8953 (Flattened Device Tree)")
	.init_machine = msm8953_init,
	.dt_compat = msm8953_dt_match,
MACHINE_END

發現其實主要做了兩件是,一個是定義msm8953的裝置樹匹配表dt_compatmsm8953_dt_match,另一個是定義msm8953的初始化函式init_machinemsm8953_init.

先來看看msm8953的裝置樹匹配表msm8953_dt_match:

static const char *msm8953_dt_match[] __initconst = {
	"qcom,msm8953",
	"qcom,apq8053",
	NULL
};

發現msm8953_dt_match是一個char*型別的指標陣列,其中每個元素指向一個字串,譬如"qcom,msm8953".

這個字串我猜測的是用來指明要匹配的DTS檔案的,因為所有的DTS檔案都在"kernel\msm-3.18\arch\arm\boot\dts\"目錄下,所以這裡指明的相關檔案應該就是"kernel\msm-3.18\arch\arm\boot\dts\qcom"目錄下的"msm8953.dtsi"檔案;

或者檢視該DTS檔案中根節點定義的compatible屬性,發現該屬性定義為 "qcom,msm8953",看到這裡,也可能因為他跟該DTS檔案中定義的compatible屬性一致,所以才能匹配的上.但不論是前者那種我自己猜的理解方式("qcom,msm8953"是指明目錄和檔案的),還是後者的理解("qcom,msm8953"是通過DTS檔案中定義的compatible屬性進行匹配的)都能夠說明該字串的用意,貌似後者的理解更為合理一些,呵呵~

接下來繼續分析msm8953的初始化函式msm8953_init:

static void __init msm8953_init(void)
{
	board_dt_populate(NULL);
}

簡單分析後不難看出msm8953_init是初始化裝置的入口函式,而 msm8953_dt_match是裝置樹匹配的關鍵資料結構陣列.

繼續看msm8953_init函式中呼叫的板級裝置樹建立函式board_dt_populate,切換到定義處所在檔案"kernel\msm-3.18\arch\arm\mach-msm\board-dt.c":

void __init board_dt_populate(struct of_dev_auxdata *adata)
{
	of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);

	/* Explicitly parent the /soc devices to the root node to preserve
	 * the kernel ABI (sysfs structure, etc) until userspace is updated
	 */
	of_platform_populate(of_find_node_by_path("/soc"),
			     of_default_bus_match_table, adata, NULL);
}

其中兩個關鍵點,一個該函式的引數是of_device_id結構體型別的陣列定義的預設匯流排匹配表of_default_bus_match_table:

const struct of_device_id of_default_bus_match_table[] = {
	{ .compatible = "simple-bus", },
	{ .compatible = "simple-mfd", },
#ifdef CONFIG_ARM_AMBA
	{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
	{} /* Empty terminated list */
};

如程式碼所示,這是一個of_device_id結構體型別的陣列,其中為每一個元素的compatible賦值,不難理解,這個就真的是會和DTS中對應的compatible屬性進行匹配了.

另一個關鍵點是該函式呼叫的平臺裝置建立函式of_platform_populate,在檔案"kernel\msm-3.18\drivers\of\platform.c":

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_set_flag(root, OF_POPULATED_BUS);

	of_node_put(root);
	return rc;
}

 核心工作是用for_each_child_of_node函式遍歷根裝置節點下的所有的子節點,並執行of_platform_bus_create函式對每一個子節點進行平臺匯流排裝置建立:

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")) {
		/*
		 * Don't return an error here to keep compatibility with older
		 * device tree files.
		 */
		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;
		}
	}
	of_node_set_flag(bus, OF_POPULATED_BUS);
	return rc;
}

平臺匯流排裝置建立的主要工作流程是,先通過of_get_property函式檢查該平臺匯流排裝置節點否具有compatible屬性,通過後開始執行of_platform_device_create_pdata函式真正的建立平臺裝置內容,到這裡其實就已經算是完成了一個裝置節點從匹配識別到最終創建出裝置內容的完整流程了,但是瞭解DTC檔案的朋友知道,這個裝置節點下往往還會有其他的裝置的子節點,然後子節點的子節點還有裝置節點......等等,以此類推組成的一個龐大的樹形分支的裝置樹結構,那怎麼辦得呢?往下看就知道了,其實很簡單,管它有多少個裝置的節點,都呼叫到我們這裡把裝置內容真正的創建出來不就解決了嗎?是的,的確如此,接下來再使用for_each_child_of_node函式遍歷當前平臺匯流排裝置節點的所有子節點,遞迴式的執行of_platform_bus_create函式進行每一個平臺匯流排裝置的建立,其中每完成一個建立,呼叫of_node_put函式釋放當前節點的記憶體.分析到這裡,我對遞迴的用法算是理解的更深刻一點了.

接下來在看看具體如何建立平臺裝置內容的of_platform_device_create_pdata函式:

static 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) ||
	    of_node_test_and_set_flag(np, OF_POPULATED))
		return NULL;

	dev = of_device_alloc(np, bus_id, parent);
	if (!dev)
		goto err_clear_flag;

	dev->dev.bus = &platform_bus_type;
	dev->dev.platform_data = platform_data;
	of_dma_configure(&dev->dev, dev->dev.of_node);
	of_msi_configure(&dev->dev, dev->dev.of_node);
	of_reserved_mem_device_init(&dev->dev);

	if (of_device_add(dev) != 0) {
		of_reserved_mem_device_release(&dev->dev);
		of_dma_deconfigure(&dev->dev);
		platform_device_put(dev);
		goto err_clear_flag;
	}

	return dev;

err_clear_flag:
	of_node_clear_flag(np, OF_POPULATED);
	return NULL;
}

其中主要是,通過of_device_is_available函式檢查裝置節點是否有效,然後執行of_device_alloc函式分配裝置記憶體,有了裝置後,緊接著對裝置的一部分內容進行初始化巴拉巴拉,最終呼叫of_device_add函式往核心中新增裝置.

再看看是如何分配裝置內容的of_device_alloc函式:

struct platform_device *of_device_alloc(struct device_node *np,
				  const char *bus_id,
				  struct device *parent)
{
	struct platform_device *dev;
	int rc, i, num_reg = 0, num_irq;
	struct resource *res, temp_res;

	dev = platform_device_alloc("", -1);
	if (!dev)
		return NULL;

	/* count the io and irq resources */
	while (of_address_to_resource(np, num_reg, &temp_res) == 0)
		num_reg++;
	num_irq = of_irq_count(np);

	/* Populate the resource table */
	if (num_irq || num_reg) {
		res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
		if (!res) {
			platform_device_put(dev);
			return NULL;
		}

		dev->num_resources = num_reg + num_irq;
		dev->resource = res;
		for (i = 0; i < num_reg; i++, res++) {
			rc = of_address_to_resource(np, i, res);
			WARN_ON(rc);
		}
		if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
			pr_debug("not all legacy IRQ resources mapped for %s\n",
				 np->name);
	}

	dev->dev.of_node = of_node_get(np);
	dev->dev.parent = parent;

	if (bus_id)
		dev_set_name(&dev->dev, "%s", bus_id);
	else
		of_device_make_bus_id(&dev->dev);

	return dev;
}

首先呼叫platform_device_alloc函式為定義的platform_device結構體型別的裝置的分配記憶體,接下來分別呼叫of_address_to_resourceof_irq_count兩個函式統計該裝置使用的地址和中斷資源個數,如果有上述資源的存在則分別呼叫of_address_to_resourceof_irq_to_resource_table兩個函式對裝置進行地址和中斷資源分配,完成上述裝置初始化工作後緊接著更進一步初始化,比如呼叫of_node_get函式初始化裝置所屬裝置樹節點,最終執行dev_set_name或者of_device_make_bus_id函式來設定裝置的名字或者設定裝置匯流排ID.

到此為止,所有主幹內容分析完畢,如果你把所有的標紅的內容整理出來,你會知道此次分析的原始碼所做的核心事件是什麼,只要是我自己親自進行的程式碼分析,我都會用心的記錄下來,願同行小夥伴自己做原始碼分析時也這樣記錄,並分享連結到評論,大家互相學習,謝謝~