PlatForm設備驅動:
一、platform總線、設備與驅動
1.一個現實的Linux設備和驅動通常都需要掛接在一種總線上,對於本身依附於PCI、USB、I2C、SPI等的設備而言,這自然不是問題,
但是在嵌入式系統裏面,SoC系統中集成的獨立的外設控制器、掛接在SoC內存空間的外設等確不依附於此類總線。
基於這一背景,Linux發明了一種虛擬的總線,稱爲platform總線,相應的設備稱爲platform_device,而驅動成爲 platform_driver。
2.注意,所謂的platform_device並不是與字符設備、塊設備和網絡設備並列的概念,而是Linux系統提供的一種附加手段,
例如,在 S3C6410處理器中,把內部集成的I2C、RTC、SPI、LCD、看門狗等控制器都歸納爲platform_device,而它們本身就是字符設備。
3.基於Platform總線的驅動開發流程如下:
(1)定義初始化platform bus
(2)定義各種platform devices
(3)註冊各種platform devices
(4)定義相關platform driver
(5)註冊相關platform driver
(6)操作相關設備
4.平臺相關結構
//platform_device結構體
struct platform_device {
const char * name;/* 設備名 */
u32 id;//設備id,用於給插入給該總線並且具有相同name的設備編號,如果只有一個設備的話填-1。
struct device dev;//結構體中內嵌的device結構體。
u32 num_resources;/* 設備所使用各類資源數量 */
struct resource * resource;/*//定義平臺設備的資源*/
};
//平臺資源結構
struct resource {
resource_size_t start;//定義資源的起始地址
resource_size_t end;//定義資源的結束地址
const char *name;//定義資源的名稱
unsigned long flags;//定義資源的類型,比如MEM,IO,IRQ,DMA類型
struct resource *parent,*sibling,*child;
};
//設備的驅動:platform_driver這個結構體中包含probe()、remove()、shutdown()、suspend()、 resume()函數,通常也需要由驅動實現。
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 pm_ext_ops *pm;
struct device_driver driver;
};
//系統中爲platform總線定義了一個bus_type的實例platform_bus_type,
struct bus_type platform_bus_type ={
.name = “platform”,
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = PLATFORM_PM_OPS_PTR,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
//這裏要重點關注其match()成員函數,正是此成員表明了platform_device和platform_driver之間如何匹配。
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev;
pdev = container_of(dev, struct platform_device, dev);
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE)== 0);
}
//匹配platform_device和platform_driver主要看二者的name字段是否相同。
//對platform_device的定義通常在BSP的板文件中實現,在板文件中,將platform_device歸納爲一個數組,最終通過platform_add_devices()函數統一註冊。
//platform_add_devices()函數可以將平臺設備添加到系統中,這個函數的 原型爲:
int platform_add_devices(struct platform_device **devs,int num);
//該函數的第一個參數爲平臺設備數組的指針,第二個參數爲平臺設備的數量,它內部調用了platform_device_register()函 數用於註冊單個的平臺設備。
1. platform bus總線先被kenrel註冊。
2. 系統初始化過程中調用platform_add_devices或者platform_device_register,將平臺設備(platform devices)註冊到平臺總線中(platform bus)
3. 平臺驅動(platform driver)與平臺設備(platform device)的關聯是在platform_driver_register或者driver_register中實現,一般這個函數在驅動的初始化過程調用。
通過這三步,就將平臺總線,設備,驅動關聯起來。
二.Platform初始化
系統啓動時初始化時創建了platform_bus總線設備和platform_bus_type總線,platform總線是在內核初始化的時候就註冊進了內核。
內核初始化函數kernel_init()中調用了do_basic_setup() ,該函數中調用driver_init(),該函數中調用platform_bus_init(),我們看看platform_bus_init()函數:
int __init platform_bus_init(void)
{
interror;
early_platform_cleanup();//清除platform設備鏈表
//該函數把設備名爲platform 的設備platform_bus註冊到系統中,其他的platform的設備都會以它爲parent。它在sysfs中目錄下.即 /sys/devices/platform。
//platform_bus總線也是設備,所以也要進行設備的註冊
//struct device platform_bus ={
//.init_name ="platform",
//};
error= device_register(&platform_bus);//將平臺bus作爲一個設備註冊,出現在sys文件系統的device目錄
if(error)
return error;
//接着bus_register(&platform_bus_type)註冊了platform_bus_type總線.
/*
struct bus_type platform_bus_type ={
.name = “platform”,
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = PLATFORM_PM_OPS_PTR,
};
*/
//默認platform_bus_type中沒有定義probe函數。
error= bus_register(&platform_bus_type);//註冊平臺類型的bus,將出現sys文件系統在bus目錄下,創建一個platform的目錄,以及相關屬性文件
if(error)
device_unregister(&platform_bus);
return error;
}
//總線類型match函數是在設備匹配驅動時調用,uevent函數在產生事件時調用。
//platform_match函數在當屬於platform的設備或者驅動註冊到內核時就會調用,完成設備與驅動的匹配工作。
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 */
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);//比較設備和驅動的名稱是否一樣
}
static const struct platform_device_id *platform_match_id(struct platform_device_id *id,struct platform_device *pdev)
{
while(id->name[0]){
if(strcmp(pdev->name, id->name)== 0){
pdev->id_entry = id;
return id;
}
id++;
}
return NULL;
}
//不難看出,如果pdrv的id_table數組中包含了pdev->name,或者drv->name和pdev->name名字相同,都會認爲是匹配成功。
//id_table數組是爲了應對那些對應設備和驅動的drv->name和pdev->name名字不同的情況。
//再看看platform_uevent()函數:platform_uevent 熱插拔操作函數
static int platform_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct platform_device *pdev = to_platform_device(dev);
add_uevent_var(env,"MODALIAS=%s%s", PLATFORM_MODULE_PREFIX,(pdev->id_entry)? pdev->id_entry->name : pdev->name);
return 0;
}
//添加了MODALIAS環境變量,我們回顧一下:platform_bus. parent->kobj->kset->uevent_ops爲device_uevent_ops,bus_uevent_ops的定義如下:
static struct kset_uevent_ops device_uevent_ops ={
.filter= dev_uevent_filter,
.name = dev_uevent_name,
.uevent = dev_uevent,
};
//當調用device_add()時會調用kobject_uevent(&dev->kobj, KOBJ_ADD)產生一個事件,這個函數中會調用相應的kset_uevent_ops的uevent函數,
三.Platform設備的註冊
我們在設備模型的分析中知道了把設備添加到系統要調用device_initialize()和platform_device_add(pdev)函數。
Platform設備的註冊分兩種方式:
1.對於platform設備的初註冊,內核源碼提供了platform_device_add()函數,輸入參數platform_device可以是靜態的全局設備,它是進行一系列的操作後調用device_add()將設備註冊到相應的總線(platform總線)上,
內核代碼中platform設備的其他註冊函數都是基於這個函數,如platform_device_register()、platform_device_register_simple()、platform_device_register_data()等。
2.另外一種機制就是動態申請platform_device_alloc()一個platform_device設備,然後通過platform_device_add_resources及platform_device_add_data等添加相關資源和屬性。
無論哪一種platform_device,最終都將通過platform_device_add這冊到platform總線上。
區別在於第二步:其實platform_device_add()包括device_add(),不過要先註冊resources,然後將設備掛接到特定的platform總線。
3.第一種平臺設備註冊方式
//platform_device是靜態的全局設備,即platform_device結構的成員已經初始化完成
//直接將平臺設備註冊到platform總線上
/*platform_device_register和device_register的區別:
(1).主要是有沒有resource的區別,前者的結構體包含後面,並且增加了struct resource結構體成員,後者沒有。
platform_device_register在device_register的基礎上增加了struct resource部分的註冊。
由此。可以看出,platform_device---paltform_driver_register機制與device-driver的主要區別就在於resource。
前者適合於具有獨立資源設備的描述,後者則不是。
(2).其實linux的各種其他驅動機制的基礎都是device_driver。只不過是增加了部分功能,適合於不同的應用場合.
*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);//初始化platform_device內嵌的device
return platform_device_add(pdev);//把它註冊到platform_bus_type上
}
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
if(!pdev)
return -EINVAL;
if(!pdev->dev.parent)
pdev->dev.parent =&platform_bus;//設置父節點,即platform_bus作爲總線設備的父節點,其餘的platform設備都是它的子設備
//platform_bus是一個設備,platform_bus_type纔是真正的總線
pdev->dev.bus =&platform_bus_type;//設置platform總線,指定bus類型爲platform_bus_type
//設置pdev->dev內嵌的kobj的name字段,將platform下的名字傳到內部device,最終會傳到kobj
if(pdev->id !=-1)
dev_set_name(&pdev->dev,"%s.%d", pdev->name, pdev->id);
else
dev_set_name(&pdev->dev,"%s", pdev->name);
//初始化資源並將資源分配給它,每個資源的它的parent不存在則根據flags域設置parent,flags爲IORESOURCE_MEM,
//則所表示的資源爲I/O映射內存,flags爲IORESOURCE_IO,則所表示的資源爲I/O端口。
for(i = 0; i < pdev->num_resources; i++){
struct resource *p,*r =&pdev->resource[i];
if(r->name ==NULL)//資源名稱爲NULL則把設備名稱設置給它
r->name = dev_name(&pdev->dev);
p = r->parent;//取得資源的父節點,資源在內核中也是層次安排的
if(!p){
if(resource_type(r)== IORESOURCE_MEM)//如果父節點爲NULL,並且資源類型爲IORESOURCE_MEM,則把父節點設置爲iomem_resource
p =&iomem_resource;
elseif(resource_type(r)== IORESOURCE_IO)//否則如果類型爲IORESOURCE_IO,則把父節點設置爲ioport_resource
p =&ioport_resource;
}
//從父節點申請資源,也就是出現在父節點目錄層次下
if(p && insert_resource(p, r)){
printk(KERN_ERR "%s: failed to claim resource %d\n",dev_name(&pdev->dev), i);ret =-EBUSY;
goto failed;
}
}
pr_debug("Registering platform device '%s'. Parent at %s\n",dev_name(&pdev->dev), dev_name(pdev->dev.parent));
//device_creat() 創建一個設備並註冊到內核驅動架構...
//device_add() 註冊一個設備到內核,少了一個創建設備..
ret = device_add(&pdev->dev);//就在這裏把設備註冊到總線設備上,標準設備註冊,即在sys文件系統中添加目錄和各種屬性文件
if(ret == 0)
return ret;
failed:
while(--i >= 0){
struct resource *r =&pdev->resource[i];
unsigned long type = resource_type(r);
if(type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r);
}
return ret;
}
4.第二種平臺設備註冊方式
//先分配一個platform_device結構,對其進行資源等的初始化
//之後再對其進行註冊,再調用platform_device_register()函數
struct platform_device * platform_device_alloc(const char *name,int id)
{
struct platform_object *pa;
/*
struct platform_object {
struct platform_device pdev;
char name[1];
};
*/
pa = kzalloc(sizeof(struct platform_object)+ strlen(name), GFP_KERNEL);//該函數首先爲platform設備分配內存空間
if(pa){
strcpy(pa->name, name);
pa->pdev.name = pa->name;//初始化platform_device設備的名稱
pa->pdev.id = id;//初始化platform_device設備的id
device_initialize(&pa->pdev.dev);//初始化platform_device內嵌的device
pa->pdev.dev.release = platform_device_release;
}
return pa ?&pa->pdev :NULL;
}
//一個更好的方法是,通過下面的函數platform_device_register_simple()動態創建一個設備,並把這個設備註冊到系統中:
struct platform_device *platform_device_register_simple(const char *name,int id,struct resource *res,unsigned int num)
{
struct platform_device *pdev;
int retval;
pdev = platform_device_alloc(name, id);
if(!pdev){
retval =-ENOMEM;
goto error;
}
if(num){
retval = platform_device_add_resources(pdev, res, num);
if(retval)
goto error;
}
retval = platform_device_add(pdev);
if(retval)
goto error;
return pdev;
error:
platform_device_put(pdev);
return ERR_PTR(retval);
}
//該函數就是調用了platform_device_alloc()和platform_device_add()函數來創建的註冊platform device,函數也根據res參數分配資源,看看platform_device_add_resources()函數:
int platform_device_add_resources(struct platform_device *pdev,struct resource *res, unsigned int num)
{
struct resource *r;
r = kmalloc(sizeof(struct resource)* num, GFP_KERNEL);//爲資源分配內存空間
if(r){
memcpy(r, res, sizeof(struct resource)* num);
pdev->resource = r;//並拷貝參數res中的內容,鏈接到device並設置其num_resources
pdev-> num_resources = num;
}
return r ? 0 :-ENOMEM;
}
四.Platform設備驅動的註冊
我們在設備驅動模型的分析中已經知道驅動在註冊要調用driver_register(),
platform driver的註冊函數platform_driver_register()同樣也是進行其它的一些初始化後調用driver_register()將驅動註冊到platform_bus_type總線上.
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus =&platform_bus_type;//它將要註冊到的總線
/*設置成platform_bus_type這個很重要,因爲driver和device是通過bus聯繫在一起的,
具體在本例中是通過 platform_bus_type中註冊的回調例程和屬性來是實現的,
driver與device的匹配就是通過 platform_bus_type註冊的回調例程platform_match ()來完成的。
*/
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;
return driver_register(&drv->driver);//註冊驅動
}
//然後設定了platform_driver內嵌的driver的probe、remove、shutdown函數。
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);
return drv->probe(dev);//調用platform_driver的probe()函數,這個函數一般由用戶自己實現
//例如下邊結構,回調的是serial8250_probe()函數
/*
static struct platform_driver serial8250_isa_driver ={
.probe = serial8250_probe,
.remove = __devexit_p(serial8250_remove),
.suspend = serial8250_suspend,
.resume= serial8250_resume,
.driver ={
.name ="serial8250",
.owner = THIS_MODULE,
},
};
*/
}
static int platform_drv_remove(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->remove(dev);
}
static void platform_drv_shutdown(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
drv->shutdown(dev);
}
//總結:
1.從這三個函數的代碼可以看到,又找到了相應的platform_driver和platform_device,然後調用platform_driver的probe、remove、shutdown函數。這是一種高明的做法:
在不針對某個驅動具體的probe、remove、shutdown指向的函數,而通過上三個過度函數來找到platform_driver,然後調用probe、remove、shutdown接口。
如果設備和驅動都註冊了,就可以通過bus ->match、bus->probe或driver->probe進行設備驅動匹配了。
2.驅動註冊的時候platform_driver_register()->driver_register()->bus_add_driver()->driver_attach()->bus_for_each_dev(),
對每個掛在虛擬的platform bus的設備作__driver_attach()->driver_probe_device()->drv->bus->match()==platform_match()->比較strncmp(pdev->name, drv->name, BUS_ID_SIZE),
如果相符就調用platform_drv_probe()->driver->probe(),如果probe成功則綁定該設備到該驅動。