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)
{
int?error;
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?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;
else?if?(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成功則綁定該設備到該驅動。
?
評論
查看更多