總線設備驅動模型主要包含總線、設備、驅動三個部分,總線可以是一條真實存在的總線,例如USB、I2C等典型的設備。但是對于一些設備(內部的設備)可能沒有現成的總線。Linux 2.6內核中引入了總線設備驅動模型。總線設備驅動模型與之前的三類驅動(字符、塊設備、網絡設備)沒有必然的聯系。設備只是搭載到了總線中。在 linux內核中假設存在一條虛擬總線,稱之為platform總線。
platform總線相比與常規的總線模型其優勢主要是platform總線是由內核實現的,而不用自己定義總線類型,總線設備來加載總線。platform總線是內核已經實現好的。只需要添加相應的platform device和platform driver。具體的實現過程主要包括如下的過程:
整體而言只需要完成兩個步驟,也就是設備的實現和驅動的實現,每一個實現都包括相關結構體的定義和注冊。
platform_device注冊
需要注意的是platform_device 實質上是經過處理過的設備,在platform_device結構體中存在一個設備結構體,與之前的設備存在差別的是引入了設備資源。這些設備資源就能實現對設備寄存器,中斷等資源的訪問。平臺設備的基本結構體如下:
struct platform_device {
/*設備名*/
const char * name;
/*設備ID號*/int id;
/*結構體包含一個具體的device結構體*/
struct device dev;/*資源的數量*/
u32 num_resources;/*資源結構體,用來保存硬件的資源*/
struct resource * resource;/*平臺設備的ID*/
struct platform_device_id *id_entry;
};
其中struct device 和 struct resource 是重要的結構體。struct device 在總線設備驅動模型中已經提到了。這次討論一下struct resource。
struct resource {
/*資源的起始值,如果是地址,那么是物理地址,不是虛擬地址*/
resource_size_t start;
/*資源的結束值,如果是地址,那么是物理地址,不是虛擬地址*/
resource_size_t end;
/*資源名*/
const char *name;
/*資源的標示,用來識別不同的資源*/
unsigned long flags;
/*資源指針,可以構成鏈表*/
struct resource *parent, *sibling, *child;
};
platform_device 的注冊很簡單,只需要在設備的初始化函數中首先定義相應的設備,通常采用函數platform_device *platform_device_alloc(const char *name, int id)動態申請,通常name就是需要申請的設備名,而id為-1。然后采用
int platform_device_add(struct platform_device *pdev)
或者
int platform_device_register(struct platform_device *pdev)
注冊定義好的設備即可。
同樣在退出函數中釋放注冊好的設備即可,可以采用函數:
void platform_device_unregister(struct platform_device *pdev)。
然后一個平臺設備就完成了,不需要像自己實現模型時定義相關的文件屬性等。
設備資源可以通過相關函數得到:
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
中斷資源也可以通過:
int platform_get_irq(struct platform_device *dev, unsigned int num)
資源的使用主要是驅動實現過程中需要使用到的,但是后期的使用一般需要在驅動的probe函數中實現申請中斷或者IO內存才能使用,而不能直接使用。特別是資源中的地址通常是物理地址,需要通過申請IO內存和映射完成物理到虛擬地址的轉換,便于進程的訪問。
platform_driver注冊
平臺驅動結構體platform_driver實現如下:
struct platform_driver {
/*平臺驅動需要實現的相關函數操作,
其中的前4個函數與最后一個函數與device_driver中的函數是相同的
本質是實現對device_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;
/*平臺設備ID,這與platform_device中的struct platform_device_id *id_entry是相同的主要是完成總線的匹配操作,platform總線的匹配操作第一匹配要素就是該元素。而不再是簡單的name選項。
*/
struct platform_device_id *id_table;
};通常驅動的入口函數:
int (*probe)(struct platform_device *);
當總線完成了設備的match 操作以后就會進入驅動中該函數的運行。總線函數的匹配操作如下:
static int platform_match(struct device *dev, struct device_driver *drv)
{/*得到平臺設備的指針*/
struct platform_device *pdev = to_platform_device(dev);/*得到平臺驅動指針*/
struct platform_driver *pdrv = to_platform_driver(drv);/* match against the id table first */
/*從定義上分析,id_table是首先匹配的對象,然后才是name的匹配,當ID匹配完成時就說明匹配好了*/
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
從上面的定義可以知道platform總線的匹配函數手下是比較id_table是匹配的首選項。
probe 函數稱之為探針函數,用于檢測總線上有該驅動能夠處理的設備,而remove函數則是為了說明總線上該驅動能夠處理的設備被移除。
因此這兩個函數是在平臺設備中一定要被實現的函數。
其他的函數則不一樣要求實現。
平臺驅動的設計主要是完成平臺驅動結構體的填充和注冊。
通常的平臺驅動結構體實現如下:
static struct platform_driver my_driver =
{
/*平臺驅動的probe函數實現*/
.probe = my_probe,
/*平臺驅動的remove函數實現*/
.remove = my_remove,
/*實現設備驅動的name和owner變量*/
.driver =
{
/*該參數主要實現總線中的匹配函數調用*/
.name = "my_dev",
/*該函數表示模塊的擁有者*/
.owner = THIS_MODULE,
},
};其中的my_probe和my_remove是自己定義的probe和remove函數。
最主要的是內嵌設備驅動結構體的填充,主要的填充包括name和owner兩個,當然也可以包括其他的。由于沒有填充id_table,那么 name就是總線匹配操作的第一選擇。因此如果沒有填充好id_table,那么name元素是一定要實現的,不然不能完成相應的設備驅動匹配操作。
完成platform_driver結構體的填充過后就是完成驅動的在初始化階段的注冊以及退出階段的釋放操作,基本的實現函數為:
注冊函數,通常在驅動初始化函數中調用:
int platform_driver_register(struct platform_driver *drv)
釋放函數,通常在驅動退出函數調用:
void platform_driver_unregister(struct platform_driver *drv)
完成相關的注冊以后總線、設備、驅動的大概框架就完成啦。
但是這只是常用的框架,還不能在應用程序中使用。
基于平臺驅動的設備驅動都是基于總線架構的,基本的實現過程與之前的簡單字符設備存在較大的差別,主要的區別在驅動的初始化不在是平臺設備驅動的初始化函數中實現,而是在probe函數中實現。而驅動的卸載函數則是在remove函數中實現。probe函數是平臺總線實現匹配以后首先被調用的函數,因此在其中實現字符設備、塊設備、網絡設備驅動的初始化是有意義的,這樣的設備驅動就是基于平臺總線的設備驅動,便于維護。
平臺總線驅動的注冊過程分析:
int platform_driver_register(struct platform_driver *drv)
{/*第一步,仍然是完成結構體的填充操作*/
/*驅動的總線類型*/
drv->driver.bus = &platform_bus_type;/*將自己定義的probe函數賦值給平臺驅動中設備驅動的probe函數,其他函數類似*/
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);
}/*設備驅動的probe函數的賦值過程*/
static int platform_drv_probe(struct device *_dev)
{/*得到設備對應的平臺驅動*/
struct platform_driver *drv = to_platform_driver(_dev->driver);/*得到設備的平臺設備*/
struct platform_device *dev = to_platform_device(_dev);/*下面的probe是自己實現的probe函數。具體的實現思路:
根據一般設備找對應的平臺設備,同時根據設備的驅動找到平臺驅動。
然后返回平臺驅動的probe函數(自己實現通常是初始化操作)地址。
*/
return drv->probe(dev);
}實現的總線平臺驅動模型的最簡單源碼:
平臺設備的實現:device.c
#include<linux/device.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/string.h>
#include<linux/platform_device.h>/*平臺模型驅動的平臺設備對象*/
static struct platform_device *my_device;/*初始化函數*/
static int __init my_device_init(void)
{
int ret = 0;/*采用platform_device_alloc分配一個platform_device對象
參數分別為platform_device的name,和id。
*/
my_device = platform_device_alloc("my_dev",-1);/*注冊設備,注意不是platform_device_register,將平臺設備注冊到內核中*/
ret = platform_device_add(my_device);/*如果出錯釋放相關的內存單元*/
if(ret)
{
platform_device_put(my_device);
}return ret;
}/*卸載處理函數*/
static void __exit my_device_exit(void){
platform_device_unregister(my_device);
}module_init(my_device_init);
module_exit(my_device_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("GP-<gp19861112@yahoo.com.cn>");
平臺驅動的實現:driver.c
#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/platform_device.h>
#include<linux/string.h>
/*平臺驅動中的probe和remove函數是必須實現的函數*/
/*設備驅動的探測函數,主要實現檢測總線上是否有該驅動對應的設備*/
static my_probe(struct device *dev)
{/*
如果添加實際的設備到該平臺總線設備驅動模型中,則可以在該函數
中實現具體的設備驅動函數的初始化操作,包括設備號的申請,設備
的初始化,添加。自動設備文件創建函數的添加等操作。
或者是混雜字符設備的相關初始化操作。當然結構體的相關處理仍
然采取全局變量的形式。
*/
printk("Driver found devices which this driver can be handle\n");
return 0;
}/*設備驅動的移除函數,主要檢測該驅動支持設備的移除活動檢測*/
static my_remove(struct device *dev)
{/*
如果添加實際的設備到該平臺總線設備驅動模型中,則可以在該函數
中實現具體的設備的釋放,包括設備的刪除,設備號的注銷等操作。
*/
printk("Driver found device unpluded\n");
return 0;
}static struct platform_driver my_driver =
{
/*平臺驅動的probe函數實現*/
.probe = my_probe,
/*平臺驅動的remove函數實現*/
.remove = my_remove,
/*實現設備驅動的name和owner變量*/
.driver =
{
/*該參數主要實現總線中的匹配函數調用*/
.name = "my_dev",
/*該函數表示模塊的擁有者*/
.owner = THIS_MODULE,
},
};/*初始化函數*/
static int __init my_driver_init(void)
{
/*注冊平臺驅動*/
return platform_driver_register(&my_driver);
}
/*退出函數*/
static void __exit my_driver_exit(void)
{
/*注銷平臺驅動*/
return platform_driver_unregister(&my_driver);
}/*加載和卸載*/
module_init(my_driver_init);
module_exit(my_driver_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("GP-<gp19861112@yahoo.com.cn>");
將一般設備驅動加入到總線設備模型中的相關操作是后期總結和學習的內容。設備驅動實現的實現原理還是之前的那些操作,但是初始化和推出函數發生了改變。
總結:
platform總線的驅動模型只是在一般總線模型的基礎上做了相關的延伸,實質上只要弄清除總線模型的一般原理,學習platform總線也就簡單不少。但是畢竟還是學習階段。