引言
分層就是將一個復(fù)雜的工作分成了4層, 分而做之,降低難度倒谷。每一層只專注于自己的事情, 系統(tǒng)已經(jīng)將其中的核心層和事件處理層寫好了骗绕,所以我們只需要來寫硬件相關(guān)的驅(qū)動層代碼即可。
分離是指把硬件相關(guān)的部分(驅(qū)動層)從純軟件部分(事件處理層)抽離出來,使我們只需要關(guān)注硬件相關(guān)部分代碼的編寫。具體來說就是在驅(qū)動層中使用platform機制(將所有設(shè)備掛接到一個虛擬的總線上,方便sysfs節(jié)點和設(shè)備電源的管理奔滑,使得驅(qū)動代碼,具有更好的擴展性和跨平臺性,就不會因為新的平臺而再次編寫驅(qū)動)把硬件相關(guān)的代碼(固定的,如板子的網(wǎng)卡顺少、中斷地址)和驅(qū)動(會根據(jù)程序作變動,如點哪一個燈)分離開來朋其,即要編寫兩個文件:dev.c和drv.c(platform設(shè)備和platform驅(qū)動)
接下來我們來分析platform機制以及分離概念。
1脆炎、platform機制
-
device設(shè)備:掛接在platform總線下的設(shè)備梅猿,屬于platform_device結(jié)構(gòu)體類型;
platform_device結(jié)構(gòu)體定義如下
struct platform_device { const char* name; //設(shè)備名稱秒裕,要與platform_driver的name一樣,這樣總線才能匹配成功 u32 id; //id號,插入總線下相同name的設(shè)備編號(一個驅(qū)動可以有多個設(shè)備),如果只有一個設(shè)備填-1 struct device dev; //內(nèi)嵌的具體的device結(jié)構(gòu)體,其中成員platform_data,是個void *類型,可以給平臺driver提供各種數(shù)據(jù)(比如:GPIO引腳等等) u32 num_resources; //資源數(shù)量袱蚓, struct resource * resource; //資源結(jié)構(gòu)體,保存設(shè)備的信息 };
resource結(jié)構(gòu)體定義如下
struct resource { resource_size_t start; //起始資源,如果是地址的話,必須是物理地址 resource_size_t end; //結(jié)束資源,如果是地址的話,必須是物理地址 const char *name; //資源名 unsigned long flags; //資源的標(biāo)志 //比如IORESOURCE_MEM,表示地址資源, IORESOURCE_IRQ表示中斷引腳... ... struct resource *parent, *sibling, *child; //資源拓撲指針父、兄几蜻、子,可以構(gòu)成鏈表 };
涉及到的函數(shù)如下(在dev設(shè)備的入口出口函數(shù)中用到)
int platform_device_register(struct platform_device * pdev);//注冊dev設(shè)備 int platform_device_unregister(struct platform_device * pdev);//注銷dev設(shè)備
-
driver驅(qū)動:也是掛接在platform總線下喇潘,與某類設(shè)備相應(yīng)的驅(qū)動程序,屬于platform_driver結(jié)構(gòu)體類型梭稚;
platform_driver(驅(qū)動)結(jié)構(gòu)體的定義
struct platform_driver { int (*probe)(struct platform_device *); //查詢設(shè)備的存在 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; //內(nèi)嵌的driver,其中的name成員要等于設(shè)備的名稱才能匹配 };
定義一個platform_driver(驅(qū)動)示例
struct platform_driver gpio_keys_device_driver = { .probe = gpio_keys_probe, //設(shè)備的檢測,當(dāng)匹配成功就會調(diào)用這個函數(shù)(需要自己編寫) .remove = __devexit_p(gpio_keys_remove), //刪除設(shè)備(需要自己編寫) .driver = { .name = "gpio-keys", //驅(qū)動名稱,用來與設(shè)備名稱匹配用的 } };
涉及到的函數(shù)如下
//位于init入口函數(shù)中颖低,用來注冊driver驅(qū)動 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); //注冊到driver目錄下 } int platform_driver_unregister(struct platform_driver *drv); //位于exit出口函數(shù)中,用來卸載驅(qū)動 struct resource * platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num); //獲取設(shè)備的某個資源,獲取成功,則返回一個resource資源結(jié)構(gòu)體 //參數(shù): // *dev :指向某個platform device設(shè)備 // type:獲取的資源類型 // num: type資源下的第幾個數(shù)組
-
platform總線:屬于虛擬設(shè)備總線(全局變量)弧烤,屬于platform_bus_type類型忱屑,正是通過這個總線將設(shè)備和驅(qū)動聯(lián)系了起來,屬于Linux中bus的一種。
platform_match()匹配函數(shù)
static int platform_match(struct device * dev, struct device_driver * drv) { /*找到所有的device設(shè)備*/ struct platform_device *pdev = container_of(dev, struct platform_device, dev); return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); //找BUS_ID_SIZE次 }
通過查看
/sys/bus/platform/
目錄莺戒,可以看到兩個文件:devices和drivers伴嗡。它們分別用來存放platform的設(shè)備和驅(qū)動。無論驅(qū)動或設(shè)備从铲,只要有一方注冊,就會調(diào)用platform_bus_type的.match匹配函數(shù)來尋找對方瘪校。如果匹配成功,就調(diào)用driver驅(qū)動結(jié)構(gòu)體里的.probe函數(shù)來使總線將設(shè)備和驅(qū)動聯(lián)系起來食店。
platform_bus_type的結(jié)構(gòu)體定義如下所示(位于drivers/base):
struct bus_type platform_bus_type = { .name = "platform", //設(shè)備名稱 .dev_attrs = platform_dev_attrs, //設(shè)備屬性渣淤、含獲取sys文件名,該總線會放在/sys/bus下 .match = platform_match, //匹配設(shè)備和驅(qū)動,匹配成功就調(diào)用driver的.probe函數(shù) .uevent = platform_uevent, //消息傳遞,比如熱插拔操作 .suspend = platform_suspend, //電源管理的低功耗掛起 .suspend_late = platform_suspend_late, .resume_early = platform_resume_early, .resume = platform_resume, //恢復(fù) };
2、利用platform機制吉嫩,編寫LED驅(qū)動層
- 環(huán)境:JZ2440開發(fā)板V3 + linux內(nèi)核3.4.2 + arm-linux-gcc 4.3.2
- 參考:韋東山第2期視頻
2.1 創(chuàng)建設(shè)備代碼
- 創(chuàng)建led_dev.c文件:用來指定燈的引腳地址,當(dāng)更換平臺時只需要修改這個就行嗅定。
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
static struct resource led_resource[] = { //資源數(shù)組,用來保存設(shè)備的信息
[0] = {
.start = 0x56000050, //led的寄存器GPFCON起始地址
.end = 0x56000050 + 8 - 1, // led的寄存器GPFDAT結(jié)束地址
.flags = IORESOURCE_MEM, //表示地址資源
},
[1] = {
.start = 5, //表示GPF第幾個引腳開始
.end = 5, //結(jié)束引腳
.flags = IORESOURCE_IRQ, //表示中斷資源
}
};
//釋放函數(shù)
static void led_release(struct device * dev)
{
//釋放函數(shù)自娩,必須向內(nèi)核提供一個release函數(shù), 否則卸載時,內(nèi)核找不到該函數(shù)會報錯。
}
/*分配渠退、設(shè)置一個LED設(shè)備 */
static struct platform_device led_dev = {
.name = "myled", //對應(yīng)的platform_driver驅(qū)動的名字
.id = -1, //表示只有一個設(shè)備
.num_resources = ARRAY_SIZE(led_resource), //資源數(shù)量,ARRAY_SIZE()函數(shù):獲取數(shù)量
.resource = led_resource, //資源數(shù)組led_resource
.dev = {
.release = led_release,
},
};
//入口函數(shù),注冊dev設(shè)備
static int led_dev_init(void)
{
platform_device_register(&led_dev);
return 0;
}
//出口函數(shù),注銷dev設(shè)備
static void led_dev_exit(void)
{
platform_device_unregister(&led_dev);
}
module_init(led_dev_init); //修飾入口函數(shù)
module_exit(led_dev_exit); //修飾出口函數(shù)
MODULE_LICENSE("GPL"); //聲明函數(shù)
2.2 創(chuàng)建驅(qū)動代碼
-
創(chuàng)建led_drv.c文件:用來初始化燈以及如何控制燈的邏輯,當(dāng)更換控制邏輯時,只需要修改這個就行忙迁。
1. 分配、設(shè)置一個platform_driver結(jié)構(gòu)體
#include <linux/module.h> #include <linux/version.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/sched.h> #include <linux/pm.h> #include <linux/sysctl.h> #include <linux/proc_fs.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/input.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> /* 1)先寫要注冊的led驅(qū)動:platform_driver結(jié)構(gòu)體*/ /*函數(shù)聲明*/ 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, //當(dāng)與設(shè)備匹配,則調(diào)用該函數(shù) .remove = led_remove, //刪除設(shè)備 .driver = { .name = "myled", //與設(shè)備名稱一樣 } };
編寫file_operations 結(jié)構(gòu)體以及成員函數(shù)(.open碎乃、.write)
static struct class *cls; //類,用來注冊,和注銷 static volatile unsigned long *gpio_con; //被file_operations的.open函數(shù)用 static volatile unsigned long *gpio_dat; //被file_operations的.write函數(shù)用 static int pin; //LED位于的引腳值 static int led_open(struct inode *inode, struct file *file) { *gpio_con &= ~(0x03<<(pin*2)); *gpio_con |= (0x01<<(pin*2)); return 0; } static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { char val=0; if(count!=1) return -EINVAL; copy_from_user(&val,buf,count); //從用戶(應(yīng)用層)拷貝數(shù)據(jù) if(val) //開燈 { *gpio_dat &= ~(0x1<<pin); } else { *gpio_dat |= (0x1<<pin); } return 0 ; } static struct file_operations led_fops= { .owner = THIS_MODULE, //被使用時阻止模塊被卸載 .open = led_open, .write = led_write, };
編寫.probe函數(shù)
/* 當(dāng)驅(qū)動和設(shè)備都insmod加載后,然后bus總線會匹配成功,就進入.probe函數(shù). * 里面的內(nèi)容沒有固定要求姊扔,你可以根據(jù)需要自行添加。 */ static int led_probe(struct platform_device *pdev) { printk("enter probe\n"); //調(diào)試用 /* 1.根據(jù)platform_device的資源進行ioremap */ struct resource *res; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //獲取LED寄存器地址 gpio_con = ioremap(res->start, res->end - res->start + 1); //獲取虛擬地址 gpio_dat = gpio_con + 1; res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //獲取LED引腳值 pin = res->start; /* 2.注冊字符設(shè)備驅(qū)動程序 */ major = register_chrdev(0, "myled", &led_fops); //賦入file_operations結(jié)構(gòu)體 cls = class_create(THIS_MODULE, "myled"); device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */ return 0; }
編寫.remove函數(shù)
/* 如果驅(qū)動與設(shè)備已聯(lián)系起來,當(dāng)卸載驅(qū)動時,就會調(diào)用.remove函數(shù)卸載設(shè)備 */ static int led_remove(struct platform_device *pdev) { printk("enter remove\n"); //調(diào)試用 /* 1.卸載字符設(shè)備驅(qū)動程序 */ device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "myled"); iounmap(gpio_con); //注銷虛擬地址 return 0; }
編寫drv的入口出口函數(shù)
static int led_drv_init(void) //入口函數(shù),注冊驅(qū)動 { platform_driver_register(&led_drv); return 0; } static void led_drv_exit(void) //出口函數(shù),卸載驅(qū)動 { platform_driver_unregister(&led_drv); } module_init(led_drv_init); module_exit(led_drv_exit); MODULE_LICENSE("GPL");
2.3 創(chuàng)建Make file 文件
KERN_DIR = /home/leon/linux-3.4.2 all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += led_drv.o obj-m += led_dev.o
2.4 創(chuàng)建測試文件
當(dāng)用戶輸入led_test on
時梅誓,LED點亮恰梢;輸入led_test off
時,LED熄滅梗掰。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> /* led_test on * led_test off */ int main(int argc, char **argv) { int fd; char val = 1; fd = open("/dev/led", O_RDWR); if (fd < 0) { printf("can't open!\n"); } if (argc != 2) { printf("Usage :\n"); printf("%s <on|off>\n", argv[0]); return 0; } if (strcmp(argv[1], "on") == 0) { val = 1; } else { val = 0; } write(fd, &val, 1); //將從變量val地址起始處的4個字節(jié)傳給驅(qū)動 return 0; }