本節目標:html
學習platform機制,如何實現驅動層分離node
1.先來看看咱們以前分析輸入子系統的分層概念,以下圖所示:linux
如上圖所示,分層就是將一個複雜的工做分紅了4層, 分而作之,下降難度,每一層專一於本身的事情, 系統只將其中的核心層和事件處理層寫好了,因此咱們只須要來寫驅動層便可,接下來咱們來分析platform機制以及分離概念數組
2.分離概念函數
優勢:post
介紹:學習
分離就是在驅動層中使用platform機制把硬件相關的代碼(固定的,如板子的網卡、中斷地址)和驅動(會根據程序做變更,如點哪個燈)分離開來,即要編寫兩個文件:dev.c和drv.c(platform設備和platform驅動)測試
3.platform機制
url
基本內容:spa
platform會存在/sys/bus/裏面
以下圖所示, platform目錄下會有兩個文件,分別就是platform設備和platform驅動
1) device設備
掛接在platform總線下的設備, platform_device結構體類型
2) driver驅動
掛接在platform總線下,是個與某種設備相對於的驅動, platform_driver結構體類型
3) platform總線
是個全局變量,爲platform_bus_type,屬於虛擬設備總線,經過這個總線將設備和驅動聯繫起來,屬於Linux中bus的一種
該platform_bus_type的結構體定義以下所示(位於drivers/base):
struct bus_type platform_bus_type = { .name = "platform", //設備名稱 .dev_attrs = platform_dev_attrs, //設備屬性、含獲取sys文件名,該總線會放在/sys/bus下 .match = platform_match, //匹配設備和驅動,匹配成功就調用driver的.probe函數 .uevent = platform_uevent, //消息傳遞,好比熱插拔操做 .suspend = platform_suspend, //電源管理的低功耗掛起 .suspend_late = platform_suspend_late, .resume_early = platform_resume_early, .resume = platform_resume, //恢復 };
驅動、設備註冊匹配圖以下所示:
只要有一方註冊,就會調用platform_bus_type的.match匹配函數,來找對方,成功就調用driver驅動結構體裏的.probe函數來使總線將設備和驅動聯繫起來
4.實例-分析driver驅動:
咱們以/drivers/input/keybard/gpio_keys.c內核自帶的示例程序爲例,
它的代碼中只有driver驅動,由於是個示例程序,因此沒有device硬件設備代碼
4.1發如今gpio_keys.c中有1個全局變量driver驅動:
struct platform_driver gpio_keys_device_driver = { //定義一個platform_driver類型驅動 .probe = gpio_keys_probe, //設備的檢測,當匹配成功就會調用這個函數(須要本身編寫) .remove = __devexit_p(gpio_keys_remove), //刪除設備(須要本身編寫) .driver = { .name = "gpio-keys", //驅動名稱,用來與設備名稱匹配用的 } };
4.2而後來找找這個gpio_keys_device_driver被誰用到
發如今驅動層init入口函數中經過platform_driver_register()來註冊diver驅動
在驅動層exit出口函數中經過platform_driver_unregister()函數來註銷diver驅動
代碼以下:
static int __init gpio_keys_init(void) //init出口函數 { return platform_driver_register(&gpio_keys_device_driver); //註冊driver驅動 } static void __exit gpio_keys_exit(void) //exit出口函數 { platform_driver_unregister(&gpio_keys_device_driver); //註銷driver驅動 }
3.3咱們進來platform_driver_register(),看它是如何註冊diver的,註冊到哪裏?
platform_driver_register()函數以下:
int platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type; //(1)掛接到虛擬總線platform_bus_type上 if (drv->probe) drv->driver.probe = platform_drv_probe; if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; if (drv->suspend) drv->driver.suspend = platform_drv_suspend; if (drv->resume) drv->driver.resume = platform_drv_resume; return driver_register(&drv->driver); //(2) 註冊到driver目錄下 }
(1) 掛接到虛擬總線platform_bus_type上,而後會調用platform_bus_type下的platform_match匹配函數,來匹配device和driver的名字,其中driver的名字以下圖所示:
platform_match()匹配函數以下所示:
static int platform_match(struct device * dev, struct device_driver * drv) { /*找到全部的device設備*/ struct platform_device *pdev = container_of(dev, struct platform_device, dev); return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); //找BUS_ID_SIZE次 }
若名字匹配成功,則調用device的.probe成員函數
(2)而後放到/sys/bus/platform/driver目錄下,其中driver_register()函數就是用來建立dirver目錄的
5. 使用platform機制,編寫LED驅動層
首先建立設備代碼和驅動代碼:led_dev.c 、led_drv.c
led_dev.c用來指定燈的引腳地址,當更換平臺時,只須要修改這個就行
led_drv.c用來初始化燈以及如何控制燈的邏輯,當更換控制邏輯時,只須要修改這個就行
6.編寫led.dev.c
6.1編寫led_dev.c以前先來看看platform_device結構體和要使用的函數:
platform_device結構體以下:
struct platform_device { const char * name; //設備名稱,要與platform_driver的name同樣,這樣總線才能匹配成功 u32 id; //id號,插入總線下相同name的設備編號(一個驅動能夠有多個設備),若是隻有一個設備填-1 struct device dev; //內嵌的具體的device結構體,其中成員platform_data,是個void *類型,能夠給平臺driver提供各類數據(好比:GPIO引腳等等) u32 num_resources; //資源數量, struct resource * resource; //資源結構體,保存設備的信息 };
其中resource資源結構體,以下:
struct resource { resource_size_t start; //起始資源,若是是地址的話,必須是物理地址 resource_size_t end; //結束資源,若是是地址的話,必須是物理地址 const char *name; //資源名 unsigned long flags; //資源的標誌 //好比IORESOURCE_MEM,表示地址資源, IORESOURCE_IRQ表示中斷引腳... ... struct resource *parent, *sibling, *child; //資源拓撲指針父、兄、子,能夠構成鏈表 };
要用的函數以下,在dev設備的入口出口函數中用到
int platform_device_register(struct platform_device * pdev); //註冊dev設備 int platform_device_register(struct platform_device * pdev); //註銷dev設備
6.2接下來開始寫代碼
1)先寫要註冊的led設備:platform_device結構體
#include <linux/module.h> #include <linux/version.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/interrupt.h> #include <linux/list.h> #include <linux/timer.h> #include <linux/init.h> #include <linux/serial_core.h> #include <linux/platform_device.h> static struct resource led_resource[] = { //資源數組 [0] = { .start = 0x56000050, //led的寄存器GPFCON起始地址 .end = 0x56000050 + 8 - 1, // led的寄存器GPFDAT結束地址 .flags = IORESOURCE_MEM, //表示地址資源 }, [1] = { .start = 5, //表示GPF第幾個引腳開始 .end = 5, //結束引腳 .flags = IORESOURCE_IRQ, //表示中斷資源
} }; static void led_release(struct device * dev) //釋放函數 {} static struct platform_device led_dev = { .name = "myled", //對應的platform_driver驅動的名字 .id = -1, //表示只有一個設備 .num_resources = ARRAY_SIZE(led_resource), //資源數量,ARRAY_SIZE()函數:獲取數量 .resource = led_resource, //資源數組led_resource .dev = { .release = led_release, //釋放函數,必須向內核提供一個release函數, 、 //不然卸載時,內核找不到該函數會報錯 }, };
2)最後寫出口入口函數:
static int led_dev_init(void) //入口函數,註冊dev設備 { platform_device_register(&led_dev); return 0; } static void led_dev_exit(void) //出口函數,註銷dev設備 { platform_device_unregister(&led_dev); } module_init(led_dev_init); //修飾入口函數 module_exit(led_dev_exit); //修飾出口函數 MODULE_LICENSE("GPL"); //聲明函數
7.編寫led.drv.c
7.1編寫led_dev.c以前先來看看platform_device結構體和要使用的函數:
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 (*suspend_late)(struct platform_device *, pm_message_t state); int (*resume_early)(struct platform_device *); int (*resume)(struct platform_device *); //喚醒 struct device_driver driver; //內嵌的driver,其中的name成員要等於設備的名稱才能匹配 };
int platform_driver_register(struct platform_driver *drv); //註冊驅動 platform_driver_unregister(struct platform_driver *drv); //卸載驅動 struct resource * platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num); //獲取設備的某個資源,獲取成功,則返回一個resource資源結構體 //參數: // *dev :指向某個platform device設備 // type:獲取的資源類型 // num: type資源下的第幾個數組
7.2接下來開始寫代碼
1)先寫要註冊的led驅動:platform_driver結構體
/*函數聲明*/ static int led_remove(struct platform_device *led_dev); static int led_probe(struct platform_device *led_dev); struct platform_driver led_drv = { .probe = led_probe, //當與設備匹配,則調用該函數 .remove = led_remove, //刪除設備 .driver = { .name = "myled", //與設備名稱同樣 } };
2)寫file_operations 結構體、以及成員函數(.open、.write)、.probe函數、
當驅動和設備都insmod加載後,而後bus總線會匹配成功,就進入.probe函數,
在.probe函數中便使用platform_get_resource()函數獲取LED的地址和引腳,而後初始化LED,並註冊字符設備和設備節點"led"
static struct class *cls; //類,用來註冊,和註銷 static volatile unsigned long *gpio_con; //被file_operations的.open函數用 static volatile unsigned long *gpio_dat; //被file_operations的.write函數用 static int pin; //LED位於的引腳值 static int led_open(struct inode *inode, struct file *file) { *GPFcon&=~(0x03<<(LED_PIN*2)); *GPFcon|=(0x01<<(LED_PIN*2)); return 0; } static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val=0; if(count!=1) return -EINAL; copy_from_user(&val,buf,count); //從用戶(應用層)拷貝數據 if(val) //開燈 { *GPFdat&=~(0x1<<LED_PIN); } else { *GPFdat |= (0x1<<LED_PIN); } return 0 ; } static struct file_operations led_fops= { .owner = THIS_MODULE, //被使用時阻止模塊被卸載 .open = led_open, .write = led_write, }; static int led_probe(struct platform_device *pdev) { struct resource *res; printk("enter probe\n"); /* 根據platform_device的資源進行ioremap */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //獲取寄存器地址 gpio_con = ioremap(res->start, res->end - res->start + 1); //獲取虛擬地址 gpio_dat = gpio_con + 1; res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //獲取引腳值 pin = res->start; /* 註冊字符設備驅動程序 */ major = register_chrdev(0, "myled", &led_fops); //賦入file_operations結構體 cls = class_create(THIS_MODULE, "myled"); class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */ return 0; }
3)寫.remove函數
若是驅動與設備已聯繫起來,當卸載驅動時,就會調用.remove函數卸載設備
和.probe函數同樣,註冊了什麼就卸載什麼即可
static int led_remove(struct platform_device *pdev) { /* 卸載字符設備驅動程序 */ printk("enter remove\n"); class_device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "myled"); iounmap(gpio_con); //註銷虛擬地址 return 0; }
4)最後寫drv的入口出口函數
static int led_drv_init(void) //入口函數,註冊驅動 { platform_driver_register(&led_drv); return 0; } static void led_drv_exit(void) //出口函數,卸載驅動 { platform_driver_unregister(&led_drv); } module_init(led_drv_init); module_exit(led_drv_exit); MODULE_LICENSE("GPL");
8.測試運行
1)以下圖,咱們先掛載dev設備模塊,和咱們以前分析的同樣,它在platform/devices目錄下生成一個"myled"設備
2)以下圖,咱們再來掛載drv驅動模塊,一樣的在platform/drivers目錄下生成一個"myled"驅動,devices目錄下的"myled"設備匹配成功,進入.probe函數建立設備,接下來就可使用應用程序來控制led燈了
3)以下圖,卸載驅動時,也會進入.remove函數卸載設備
接下來開始學習: