《5.linux驅動開發-第5部分-5.5.linux裝置驅動模型》

第一部分、章節目錄
5.5.1.linux裝置驅動模型簡介
5.5.2.裝置驅動模型的底層架構
5.5.3.匯流排式裝置驅動組織方式
5.5.4.platform平臺匯流排簡介1
5.5.5.platform平臺匯流排工作原理2
5.5.6.platform平臺匯流排工作原理3
5.5.7.平臺匯流排實踐環節1
5.5.8.平臺匯流排實踐環節2
5.5.9.平臺匯流排實踐環節3
5.5.10.平臺匯流排實踐環節4

第二部分、章節介紹
5.5.1.linux裝置驅動模型簡介
本節介紹linux裝置驅動模型誕生的原因、技術特徵、以及其對驅動開發編寫除錯的影響,主要目的是讓大家全方位感受一下到底什麼是linux的裝置驅動模型。
5.5.2.裝置驅動模型的底層架構
本節講解裝置驅動模型的最底層實現模組,主要有三個:kobject、kobj_type、kset。
5.5.3.匯流排式裝置驅動組織方式
本節詳解linux核心中以匯流排為連線的驅動組織形式,詳細分析了類、匯流排、裝置、驅動等幾個概念及其關聯。
5.5.4.platform平臺匯流排簡介1
本節講述platform平臺匯流排的目的、意義、工作方式等,注意本節的關鍵在於platform自己本身的構建和工作原理,下節課才會講如何使用平臺匯流排來組織裝置和驅動。
5.5.5.platform平臺匯流排工作原理2
本節首先講解平臺匯流排的工作流程,然後重點講了平臺匯流排本身自己構建的過程。
5.5.6.platform平臺匯流排工作原理3
本節以核心中自帶的三星為2440寫的基於平臺匯流排的LED驅動原始碼為例,分析瞭如何在平臺匯流排下去編寫驅動程式。
5.5.7.平臺匯流排實踐環節1
本節開始實戰編寫X210下基於平臺匯流排的LED驅動,本節把上個課程的LED驅動原始碼拿來改寫成平臺匯流排制式,先實現platform_driver。
5.5.8.平臺匯流排實踐環節2
本節分析系統移植時的mach檔案,然後找到並新增LED相關的platform_device的註冊。
5.5.9.平臺匯流排實踐環節3
本節同時除錯platform_device和platform_driver,進行問題排除和程式碼測試、講解。
5.5.10.平臺匯流排實踐環節4
本節重點是硬體配置資訊中的資料如何從device端傳遞到driver端,並且被driver接收用於硬體的操作方法中。

第三部分、隨堂記錄
5.5.1.linux裝置驅動模型簡介
5.5.1.1、什麼是裝置驅動模型
(1)類class、匯流排bus、裝置device、驅動driver
(2)kobject和物件生命週期
(3)sysfs
(4)udev
5.5.1.2、為什麼需要裝置驅動模型
(1)早期核心(2.4之前)沒有統一的裝置驅動模型,但照樣可以用
(2)2.6版本中正式引入裝置驅動模型,目的是在裝置越來越多,功耗要求等新特性要求的情況下讓驅動體系更易用、更優秀。
(3)裝置驅動模型負責統一實現和維護一些特性,諸如:電源管理、熱插拔、物件生命週期、使用者空間和驅動空間的互動等基礎設施
(4)裝置驅動模型目的是簡化驅動程式編寫,但是客觀上裝置驅動模型本身設計和實現很複雜。
5.5.1.3、驅動開發的2個點
(1)驅動原始碼本身編寫、除錯。重點在於對硬體的瞭解。
(2)驅動什麼時候被安裝、驅動中的函式什麼時候被呼叫。跟硬體無關,完全和裝置驅動模型有關。

5.5.2.裝置驅動模型的底層架構
5.5.2.1、kobject
(1)定義在linux/kobject.h中
(2)各種物件最基本單元,提供一些公用型服務如:物件引用計數、維護物件連結串列、物件上鎖、對使用者空間的表示
(3)裝置驅動模型中的各種物件其內部都會包含一個kobject
(4)地位相當於面向物件體系架構中的總基類
5.5.2.2、kobj_type
(1)很多書中簡稱為ktype,每一個kobject都需要繫結一個ktype來提供相應功能
(2)關鍵點1:sysfs_ops,提供該物件在sysfs中的操作方法(show和store)
(2)關鍵點2:attribute,提供在sysfs中以檔案形式存在的屬性,其實就是應用介面
5.5.2.3、kset
(1)kset的主要作用是做頂層kobject的容器類
(2)kset的主要目的是將各個kobject(代表著各個物件)組織出目錄層次架構
(3)可以認為kset就是為了在sysfs中弄出目錄,從而讓裝置驅動模型中的多個物件能夠有層次有邏輯性的組織在一起

5.5.3.匯流排式裝置驅動組織方式
5.5.3.1、匯流排
(1)物理上的真實匯流排及其作用(英文bus)
(2)驅動框架中的匯流排式設計
(3)bus_type結構體,關鍵是match函式和uevent函式
5.5.3.2、裝置
(1)struct device是硬體裝置在核心驅動框架中的抽象
(2)device_register用於向核心驅動框架註冊一個裝置
(3)通常device不會單獨使用,而是被包含在一個具體裝置結構體中,如struct usb_device
5.5.3.3、驅動
(1)struct device_driver是驅動程式在核心驅動框架中的抽象
(2)關鍵元素1:name,驅動程式的名字,很重要,經常被用來作為驅動和裝置的匹配依據
(3)關鍵元素2:probe,驅動程式的探測函式,用來檢測一個裝置是否可以被該驅動所管理
5.5.3.4、類
(1)相關結構體:struct class 和 struct class_device
(2)udev的使用離不開class
(3)class的真正意義在於作為同屬於一個class的多個裝置的容器。也就是說,class是一種人造概念,目的就是為了對各種裝置進行分類管理。當然,class在分類的同時還對每個類貼上了一些“標籤”,這也是裝置驅動模型為我們寫驅動提供的基礎設施。
5.5.3.5、總結
(1)模型思想很重要,其實就是面向物件的思想
(2)全是結構體套結構體,對基本功(語言功底和大腦複雜度)要求很高

5.5.4.platform平臺匯流排工作原理1
5.5.4.1、何為平臺匯流排
(1)相對於usb、pci、i2c等物理匯流排來說,platform匯流排是虛擬的、抽象出來的。
(2)回顧裸機中講的,CPU與外部通訊的2種方式:地址匯流排式連線和專用介面式連線。平臺匯流排對應地址匯流排式連線裝置,也就是SoC內部整合的各種內部外設。
(3)思考:為什麼要有平臺匯流排?進一步思考:為什麼要有匯流排的概念?
5.5.4.2、平臺匯流排下管理的2員大將
(1)platform工作體系都定義在drivers/base/platform.c中
(2)兩個結構體:platform_device和platform_driver
(3)兩個介面函式:platform_device_register和platform_driver_register

    struct platform_device {
    	const char	* name;			// 平臺匯流排下裝置的名字
    	int		id;
    	struct device	dev;		// 所有裝置通用的屬性部分
    	u32		num_resources;		// 裝置使用到的resource的個數
    	struct resource	* resource;	// 裝置使用到的資源陣列的首地址
    
    	const struct platform_device_id	*id_entry;	// 裝置ID表
    
    	/* arch specific additions */
    	struct pdev_archdata	archdata;			// 自留地,用來提供擴充套件性的
    };
    
    struct platform_driver {
    	int (*probe)(struct platform_device *);		// 驅動探測函式
    	int (*remove)(struct platform_device *);	// 去掉一個裝置
    	void (*shutdown)(struct platform_device *);	// 關閉一個裝置
    	int (*suspend)(struct platform_device *, pm_message_t state);
    	int (*resume)(struct platform_device *);
    	struct device_driver driver;				// 所有裝置共用的一些屬性
    	const struct platform_device_id *id_table;	// 裝置ID表
    };

5.5.5.platform平臺匯流排工作原理2
5.5.5.1、平臺匯流排體系的工作流程
(1)第一步:系統啟動時在bus系統中註冊platform
(2)第二步:核心移植的人負責提供platform_device
(3)第三步:寫驅動的人負責提供platform_driver
(4)第四步:platform的match函式發現driver和device匹配後,呼叫driver的probe函式來完成驅動的初始化和安裝,然後裝置就工作起來了
5.5.5.2、程式碼分析:platform本身註冊
(1)每種匯流排(不光是platform,usb、i2c那些也是)都會帶一個match方法,match方法用來對匯流排下的device和driver進行匹配。理論上每種匯流排的匹配演算法是不同的,但是實際上一般都是看name的。
(2)platform_match函式就是平臺匯流排的匹配方法。該函式的工作方法是:如果有id_table就說明驅動可能支援多個裝置,所以這時候要去對比id_table中所有的name,只要找到一個相同的就匹配上了不再找了,如果找完id_table都還沒找到就說明每匹配上;如果沒有id_table或者每匹配上,那就直接對比device和driver的name,如果匹配上就匹配上了,如果還沒匹配上那就匹配失敗。

5.5.6.platform平臺匯流排工作原理3
5.5.6.1、以leds-s3c24xx.c為例來分析platform裝置和驅動的註冊過程
(1)platform_driver_register
(2)platform_device_register
5.5.6.2、platdata怎麼玩
(1)platdata其實就是設備註冊時提供的裝置有關的一些資料(譬如裝置對應的gpio、使用到的中斷號、裝置名稱····)
(2)這些資料在裝置和驅動match之後,會由裝置方轉給驅動方。驅動拿到這些資料後,通過這些資料得知裝置的具體資訊,然後來操作裝置。
(3)這樣做的好處是:驅動原始碼中不攜帶資料,只負責演算法(對硬體的操作方法)。現代驅動設計理念就是演算法和資料分離,這樣最大程度保持驅動的獨立性和適應性。
5.5.6.3、match函式的呼叫軌跡
5.5.6.4、probe函式的功能和意義

5.5.7.平臺匯流排實踐環節1
5.5.7.1、回顧
5.5.7.2、先初步改造新增platform_driver
(1)第1步:先修改原來的程式碼到只有led1
(2)第2步:
5.5.7.3、實驗現象分析

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <mach/leds-gpio.h>
#include <linux/slab.h>


#define X210_LED_OFF	1			// X210中LED是正極接電源,負極節GPIO
#define X210_LED_ON		0			// 所以1是滅,0是亮

struct s5pv210_gpio_led {
	struct led_classdev		 cdev;
	struct s5pv210_led_platdata	*pdata;
};

static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
	return platform_get_drvdata(dev);
}

static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{
	return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}


// 這個函式就是要去完成具體的硬體讀寫任務的
static void s5pv210_led_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	struct s5pv210_gpio_led *p = to_gpio(led_cdev);
	
	printk(KERN_INFO "s5pv210_led_set\n");
	
	// 在這裡根據使用者設定的值來操作硬體
	// 使用者設定的值就是value
	if (value == LED_OFF)
	{
		// 使用者給了個0,希望LED滅
		gpio_set_value(p->pdata->gpio, X210_LED_OFF);
	}
	else
	{
		// 使用者給的是非0,希望LED亮
		gpio_set_value(p->pdata->gpio, X210_LED_ON);
	}
}


static int s5pv210_led_probe(struct platform_device *dev)
{
	// 使用者insmod安裝驅動模組時會呼叫該函式
	// 該函式的主要任務就是去使用led驅動框架提供的設備註冊函式來註冊一個裝置
	int ret = -1;
	struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
	struct s5pv210_gpio_led *led;
	
	printk(KERN_INFO "----s5pv210_led_probe---\n");

	led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
	if (led == NULL) {
		dev_err(&dev->dev, "No memory for device\n");
		return -ENOMEM;
	}

	platform_set_drvdata(dev, led);
	

	// 在這裡去申請驅動用到的各種資源,當前驅動中就是GPIO資源
	if (gpio_request(pdata->gpio, pdata->name)) 
	{
		printk(KERN_ERR "gpio_request failed\n");
	} 
	else 
	{
		// 設定為輸出模式,並且預設輸出1讓LED燈滅
		gpio_direction_output(pdata->gpio, 1);
	}
	
	// led1
	led->cdev.name = pdata->name;
	led->cdev.brightness = 0;	
	led->cdev.brightness_set = s5pv210_led_set;
	led->pdata = pdata;
	
	ret = led_classdev_register(&dev->dev, &led->cdev);
	if (ret < 0) {
		printk(KERN_ERR "led_classdev_register failed\n");
		return ret;
	}
	
	return 0;
}

static int s5pv210_led_remove(struct platform_device *dev)
{
	struct s5pv210_gpio_led *p = pdev_to_gpio(dev);

	led_classdev_unregister(&p->cdev);
	gpio_free(p->pdata->gpio);
	kfree(p);								// kfee放在最後一步
	
	return 0;
}



static struct platform_driver s5pv210_led_driver = {
	.probe		= s5pv210_led_probe,
	.remove		= s5pv210_led_remove,
	.driver		= {
		.name		= "s5pv210_led",
		.owner		= THIS_MODULE,
	},
};

static int __init s5pv210_led_init(void)
{
	return platform_driver_register(&s5pv210_led_driver);
}

static void __exit s5pv210_led_exit(void)
{
	platform_driver_unregister(&s5pv210_led_driver);
}


module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

// MODULE_xxx這種巨集作用是用來新增模組描述資訊
MODULE_LICENSE("GPL");							// 描述模組的許可證
MODULE_AUTHOR("aston <[email protected]>");		// 描述模組的作者
MODULE_DESCRIPTION("s5pv210 led driver");		// 描述模組的介紹資訊
MODULE_ALIAS("s5pv210_led");					// 描述模組的別名資訊

5.5.8.平臺匯流排實踐環節2
5.5.8.1、檢查mach-x210.c中是否有led相關的platform_device
5.5.8.2、參考mach-mini2440.c中新增led的platform_device定義
5.5.8.3、測試只有platform_device沒有platform_driver時是怎樣的

//**********kernel\arch\arm\mach-s5pv210/Mach-x210.c檔案***************//
//**********************LED 的 platform device*****************************//
static struct  s3c24xx_led_data  x210_led1_pdata = {
	.name		="led0",
	.gpio		=s3c2410_GPB(5),
	.flags		= s3c24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
	.def_trigger	="heartbeat"
};

static struct platform_device real6410_device_eth = {
	.name		= "s5pv210_led",
	.id			= 1,		//-1可由系統自動分配
	.dev		= {
		.platform_data	= &x210_led1_pdata,
	},
};
//特別注意:僅此還不ok,需要新增一個.h檔案。

5.5.9.平臺匯流排實踐環節3
5.5.9.1、測試platform_device和platform_driver相遇時會怎樣
5.5.9.2、probe函式
(1)probe函式應該做什麼
(2)probe函式的資料從哪裡來
(3)程式設計實踐

5.5.10.平臺匯流排實踐環節4

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <mach/leds-gpio.h>
#include <linux/slab.h>


#define X210_LED_OFF	1			// X210中LED是正極接電源,負極節GPIO
#define X210_LED_ON		0			// 所以1是滅,0是亮

struct s5pv210_gpio_led {
	struct led_classdev		 cdev;
	struct s5pv210_led_platdata	*pdata;
};

static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
	return platform_get_drvdata(dev);
}

static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{
	return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}


// 這個函式就是要去完成具體的硬體讀寫任務的
static void s5pv210_led_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	struct s5pv210_gpio_led *p = to_gpio(led_cdev);
	
	printk(KERN_INFO "s5pv210_led_set\n");
	
	// 在這裡根據使用者設定的值來操作硬體
	// 使用者設定的值就是value
	if (value == LED_OFF)
	{
		// 使用者給了個0,希望LED滅
		gpio_set_value(p->pdata->gpio, X210_LED_OFF);
	}
	else
	{
		// 使用者給的是非0,希望LED亮
		gpio_set_value(p->pdata->gpio, X210_LED_ON);
	}
}


static int s5pv210_led_probe(struct platform_device *dev)
{
	// 使用者insmod安裝驅動模組時會呼叫該函式
	// 該函式的主要任務就是去使用led驅動框架提供的設備註冊函式來註冊一個裝置
	int ret = -1;
	struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
	struct s5pv210_gpio_led *led;
	
	printk(KERN_INFO "----s5pv210_led_probe---\n");

	led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
	if (led == NULL) {
		dev_err(&dev->dev, "No memory for device\n");
		return -ENOMEM;
	}

	platform_set_drvdata(dev, led);
	

	// 在這裡去申請驅動用到的各種資源,當前驅動中就是GPIO資源
	if (gpio_request(pdata->gpio, pdata->name)) 
	{
		printk(KERN_ERR "gpio_request failed\n");
	} 
	else 
	{
		// 設定為輸出模式,並且預設輸出1讓LED燈滅
		gpio_direction_output(pdata->gpio, 1);
	}
	
	// led1
	led->cdev.name = pdata->name;
	led->cdev.brightness = 0;	
	led->cdev.brightness_set = s5pv210_led_set;
	led->pdata = pdata;
	
	ret = led_classdev_register(&dev->dev, &led->cdev);
	if (ret < 0) {
		printk(KERN_ERR "led_classdev_register failed\n");
		return ret;
	}
	
	return 0;
}

static int s5pv210_led_remove(struct platform_device *dev)
{
	struct s5pv210_gpio_led *p = pdev_to_gpio(dev);

	led_classdev_unregister(&p->cdev);
	gpio_free(p->pdata->gpio);
	kfree(p);								// kfee放在最後一步
	
	return 0;
}



static struct platform_driver s5pv210_led_driver = {
	.probe		= s5pv210_led_probe,
	.remove		= s5pv210_led_remove,
	.driver		= {
		.name		= "s5pv210_led",
		.owner		= THIS_MODULE,
	},
};

static int __init s5pv210_led_init(void)
{
	return platform_driver_register(&s5pv210_led_driver);
}

static void __exit s5pv210_led_exit(void)
{
	platform_driver_unregister(&s5pv210_led_driver);
}


module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

// MODULE_xxx這種巨集作用是用來新增模組描述資訊
MODULE_LICENSE("GPL");							// 描述模組的許可證
MODULE_AUTHOR("aston <[email protected]>");		// 描述模組的作者
MODULE_DESCRIPTION("s5pv210 led driver");		// 描述模組的介紹資訊
MODULE_ALIAS("s5pv210_led");					// 描述模組的別名資訊