linux驅動開發----如何看懂LINUX驅動代碼

        對於一個從單片機驅動開發轉到linux驅動開發的人員來說,最頭疼的莫過於是linux的驅動框架了。在傳統單片機開發的過程中,都是直接操作寄存器,比喻說配置個IO口引腳輸出爲高電平,只需要向方向寄存器、數據寄存器寫入值就可以實現了,這種方法比較直觀簡單,開發人員只需要掌握C語言、原理圖以及datasheet就可以進行開發了。而linux驅動開發需要涉及的東西就比較多了,開發人員需要掌握C語言、原理圖、datasheet、驅動框架等內容,特別時驅動框架,對於剛從事Linux驅動開發的人員來說,簡直是天書,不知所云。

       也有很多開發人員很不理解,明明就是一個很簡單的led驅動程序,單片機代碼可能就10行,可是在linux驅動中卻需要幾百行去實現,這不是喫飽了沒事幹,給自己找活嗎?大部分人剛看到linux驅動代碼就頭疼,因爲代碼裏到處都是結構體、指針、回調函數,顯得代碼非常的複雜。

        但實際上,所有的驅動都是與硬件打交道的,如果說哪個驅動跟硬件一點關係都沒有,那就是耍流氓。記住一點,所有的驅動最終都要回歸到寄存器的配置,因爲寄存器的配置是唯一跟硬件相關的入口,不管你框架多複雜,結構體各種轉來轉去,最終都會落實到寄存器的配置上,所謂的框架不過是抽象出來一種模式,把各廠家的驅動與內核層的接口區分開,這樣就不需要根據驅動去修改內核接口,也就是保證了LINUX內核能夠更多的兼容不同廠家的驅動。

所有的驅動最終都要落實到寄存器的配置上,天花亂墜的框架都是紙老虎

         既然前面我們已經說了天花亂墜的框架都是紙老虎,那麼我們就一起來一次武松打虎吧。咱們先選擇一個LINUX的源文件,這裏選擇的是linux-xlnx-xilinx-v2017.4的內核源碼進行分析,以驅動的示例代碼linux-xlnx-xilinx-v2017.4\drivers\leds\leds-gpio.c來分析。整個leds-gpio.c代碼總共有286行,該從哪一行來看呢?答案就是line281:

module_platform_driver(gpio_led_driver);    //註冊gpio_led_driver驅動

module_platform_driver這是一個宏定義,追根溯源

module_platform_driver -> platform_driver_register   -> driver_register     //驅動註冊函數
                       -> platform_driver_unregister -> driver_unregister   //驅動註銷函數

        宏的作用就是定義指定名稱的平臺設備驅動註冊函數和平臺設備驅動註銷函數,並且在函數體內分別通過platform_driver_register()函數和platform_driver_unregister()函數註冊和註銷該平臺設備驅動。那麼我們再回過頭去看上面的程序,就很清楚是什麼意思了。

module_platform_driver(gpio_led_driver);    //向內核註冊gpio_led_driver設備驅動

        擒賊先擒王,咱們已經找到罪魁禍首了,就必須繼續沿着這條線索走下去。順着gpio_led_driver往下看

static struct platform_driver gpio_led_driver = {
	.probe		= gpio_led_probe,
	.shutdown	= gpio_led_shutdown,
	.driver		= {
		.name	= "leds-gpio",
		.of_match_table = of_gpio_leds_match,
	},
};

可以看到這個設備驅動的成員函數有probe函數、shutdown函數以及驅動名稱、match函數。那這些函數有什麼用呢?

  • probe函數:probe的中文意思是探針的意思,在驅動代碼裏也是這個意思,就是做一些資源處理以及基本函數的初始化工作;
  • shutdown函數:shutdown的中文意思是關機的意思,在gpio_led_driver中表示關閉led燈的意思;
  • name:就是驅動的名稱,只是個代號,沒有啥作用;
  • match函數:match的中文意思就是匹配的意思,匹配什麼呢?匹配的就是設備和驅動。

        LINUX驅動的架構就是將設備和驅動分離,通過match函數來匹配設備和驅動。不管是設備還是驅動,只有這兩者匹配上,才能正常調用probe函數。在驅動代碼裏,有這樣一段程序

static const struct of_device_id of_gpio_leds_match[] = {
	{ .compatible = "gpio-leds", },    //驅動中的設備匹配信息
	{},
};

       程序裏面的 .compatible = "gpio-leds"是核心,設備和驅動就是通過這個 .compatible 來進行匹配的。在板級設備樹文件中,描述設備的硬件信息時,必須要將 compatible = "gpio-leds"添加在設備樹節點上。如果要想正常調用probe函數,那麼設備樹的形式應該如下所示:

gpio-leds {
	compatible = "gpio-leds";    //設備樹中的節點匹配信息
	#address-cells = <1>;
	#size-cells = <0>;
    ...
    ...
    ...
};

如果在設備樹文件中添加了gpio-leds的設備節點,那麼probe函數就會被調用了,那麼咱麼繼續研究probe函數:

static int gpio_led_probe(struct platform_device *pdev)
{
	struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
            //獲取platform中device的數據

	struct gpio_leds_priv *priv;    //定義gpio_leds的結構體
	int i, ret = 0;

	if (pdata && pdata->num_leds) {    
		priv = devm_kzalloc(&pdev->dev,        //向內核申請gpio_leds結構體大小的內存
				sizeof_gpio_leds_priv(pdata->num_leds),
					GFP_KERNEL);
		if (!priv)
			return -ENOMEM;

		priv->num_leds = pdata->num_leds;        //將設備傳入的參數num_leds賦給priv結構體成員
		for (i = 0; i < priv->num_leds; i++) {    //依次在/sys/class/leds目錄下創建所有led設備
			ret = create_gpio_led(&pdata->leds[i],
					      &priv->leds[i],
					      &pdev->dev, pdata->gpio_blink_set);
			if (ret < 0)
				return ret;
		}
	} else {
		priv = gpio_leds_create(pdev);//獲取gpio資源,依次在/sys/class/leds目錄下創建所有led
		if (IS_ERR(priv))
			return PTR_ERR(priv);
	}

	platform_set_drvdata(pdev, priv);//將priv的相關信息保存到platform_device中,方便後續調用

	return 0;
}

       那麼,我們可以看到這個probe函數,裏面可能跟硬件相關的就是create_gpio_led這個函數了,那麼我們繼續沿着這個函數往下看。

create_gpio_led -> devm_gpio_request_one -> gpio_request_one -> set_bit(FLAG_ACTIVE_LOW, &desc->flags);

        很顯然,沿着create_gpio_led繼續往下分析代碼,就會發現,通過一層一層的函數調用,最終還是迴歸到設置GPIO口的輸入輸出模式、輸出高電平還是低電平的問題上,這也是gpio_leds驅動的核心,在單片機驅動開發過程中,也是設置這些寄存器來點亮LED燈的吧。但其實很多人可能有疑問了,你看這個都是對各種結構體進行賦值操作,並沒有看到對GPIO的寄存器進行賦值操作,那是如何做到配置GPIO的寄存器呢?答案就是LINUX將硬件信息與軟件層面的操作分開了,硬件信息通過設備樹來描述,在設備樹中會清楚的描述出資源的信息,比方說IO資源、中斷資源、以及總線資源等等,那麼這些資源傳遞給內核以後,就會換一種身份存在了,那就是platform_device中的信息了,所以在linux內核代碼裏你是追蹤不到對寄存器的配置的,而都是對platform_device定義的結構體進行的配置,通過映射關係,將配置的信息映射至實際的寄存器中,實現對硬件的控制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章