一、platform_bus
總線是處理器和一個或多個設備之間的通道,在設備模型中, 所有的設備都通過總線相連。總線可以相互插入。設備模型展示了總線和它們所控制的設備之間的實際連接。Platform總線是2.6 kernel中最近引入的一種虛擬總線,主要用來管理CPU的片上資源,具有更好的移植性,因此在2.6 kernel中,很多驅動都用platform改寫了。
/linux/device.h
struct bus_type {
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*suspend_late)(struct device *dev, pm_message_t state);
int (*resume_early)(struct device *dev);
int (*resume)(struct device *dev);
struct bus_type_private *p;
};
/drivers/base/platform.c
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};
總線名稱是"platform",其只是bus_type的一種,定義了總線的屬性,同時platform_bus_type還有相關操作方法,如掛起、中止、匹配及hotplug事件等。總線bus是聯繫driver和device的中間樞紐。Device通過所屬的bus找到driver,由match操作方法進行匹配。
二、platform_device和device
Plarform device會有一個名字用於driver binding(在註冊driver的時候會查找driver的目標設備的bus位置,這個過程稱爲driver binding),另外IRQ以及地址空間等資源也要給出 。platform_device結構體用來描述設備的名稱、資源信息等。
/linux/platform_device.h
struct platform_device {
const char * name; //定義平臺設備的名稱,此處設備的命名應和相應驅動程序命名一致
int id;
struct device dev;
u32 num_resources;
struct resource * resource; //定義平臺設備的資源
};
在這個結構裏封裝了struct device及struct resource。可知:platform_device由device派生而來,是一種特殊的device.下面來看一下platform_device結構體中最重要的一個成員struct resource * resource。struct resource被定義在
/include/linux /ioport.h中,定義原型如下:
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;
};
這個結構表示設備所擁有的資源,即I/O端口、I/O映射內存、中斷及DMA等。這裏的地址指的是物理地址。另外還需要注意platform_device中的device結構,它詳細描述了設備的情況,其爲所有設備的基類:在/include/linux/device.h中定義了struct device結構。其中包含有所屬總線,對應的驅動指針等信息。用於匹配總線上的驅動。
三、device_register和platform_device_register
/drivers/base/core.c
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
初始化一個設備,然後加入到總線中。
/drivers/base/platform.c
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
//初始化設備的parent爲platform_bus,初始化設備的總線爲platform_bus_type。
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type;
if (pdev->id != -1)
snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,
pdev->id);
else
strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);
//設置設備struct device 的bus_id成員,留心這個地方,在以後還需要用到這個的。
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = pdev->dev.bus_id;
p = r->parent;
if (!p) {
if (r->flags & IORESOURCE_MEM)
p = &iomem_resource;
else if (r->flags & IORESOURCE_IO)
p = &ioport_resource;
}
//resources分爲兩種IORESOURCE_MEM和IORESOURCE_IO
//CPU對外設IO端口物理地址的編址方式有兩種:I/O映射方式和內存映射方式
if (p && insert_resource(p, r)) {
printk(KERN_ERR
"%s: failed to claim resource %d/n",
pdev->dev.bus_id, i);
ret = -EBUSY;
goto failed;
}
}
ret = device_add(&pdev->dev);
}
我們看到註冊一個platform device分爲了兩部分,初始化這個platform_device,然後將此platform_device添加到platform總線中。輸入參數platform_device可以是靜態的全局設備。另外一種機制就是動態申請platform_device_alloc一個platform_device設備,然後通過platform_device_add_resources及platform_device_add_data等添加相關資源和屬性。無論哪一種platform_device,最終都將通過platform_device_add註冊到platform總線上。
由platform_device_register和platform_device_add的實現可知,device_register()和platform_device_register()都會首先初始化設備,區別在於第二步:其實platform_device_add()包括device_add(),不過要先註冊resources,然後將設備掛接到特定的platform總
四、device_driver和platform driver
platform driver是與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;
};
platform_driver中包含了一個device_driver結構,可見device_driver是platform_driver的基類。
/include/linux/device.h
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
struct attribute_group **groups;
struct driver_private *p;
};
device_driver提供了一些操作接口,但其並沒有實現,相當於一些虛函數,由派生類platform_driver進行重載,無論何種類型driver都是基於device_driver派生而來的,具體的各種操作都是基於統一的基類接口的,這樣就實現了面向對象的設計。需要注意這兩個變量:name和owner。其作用主要是爲了和相關的platform_device關聯起來,owner的作用是說明模塊的所有者,驅動程序中一般初始化爲THIS_MODULE。device_driver結構中還有一個name變量。platform_driver從字面上來看就知道是設備驅動。內核正是通過這個一致性來爲驅動程序找到資源,即
platform_device中的resource。
五、driver_register和platform_driver_register。
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;
//在really_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);
}
不要被上面的platform_drv_XXX嚇倒了,它們其實很簡單,就是將struct device轉換爲struct platform_device和struct platform_driver,然後調用platform_driver中的相應接口函數。那爲什麼不直接調用platform_drv_XXX等接口呢?這就是Linux內核中面向對象的設計思想。
device_driver提供了一些操作接口,但其並沒有實現,相當於一些虛函數,由派生類platform_driver進行重載,無論何種類型的 driver都是基於device_driver派生而來的,device_driver中具體的各種操作都是基於統一的基類接口的,這樣就實現了面向對象的設計。
/drivers/base/driver.c
driver_register(struct device_driver *drv)調用
bus_add_driver(drv)
將驅動掛接到總線上,通過總線來驅動設備。
如果總線上的driver是自動probe的話,則調用
driver_attach()
將該總線上的driver和device綁定起來
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
遍歷該總線上的每一個設備,將當前driver和總線上的設備進行match,如果匹配成功,則將設備和driver綁定起來。
這一過程的具體函數調用實現爲:
driver_attach->__driver_attach->driver_probe_device(drv, dev)
driver_probe_device函數調用了
driver->bus->match,具體實現爲platform_match中簡單的進行字符串匹配,這也是我們強調platform_device和platform_driver中的name屬性需要一致的原因。
還調用了really_probe函數,在該函數中有如下一段代碼
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
如果bus和driver同時具備probe方法,則優先調用總線的probe函數。否則調用device_driver的probe函數,此probe 函數是經過各種類型的driver重載的函數,這就實現了利用基類的統一方法來實現不同的功能。對於platform_driver來說,其就是
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所定義的操作方法,這個是在定義某個platform_driver時靜態指定的操作接口。至此,platform_driver成功掛接到platform bus上了,並與特定的設備實現了綁定,並對設備進行了probe處理。
platform_driver_register在經過match匹配設備和驅動後最終調用了自定義的platform_driver中的probe函數,在該函數中一般會初始化一些資源。
六、bus、device及driver三者之間的關係
在數據結構設計上,總線、設備及驅動三者相互關聯。platform device包含device,根據device可以獲得相應的bus及driver。
設備添加到總線上後形成一個雙向循環鏈表,根據總線可以獲得其上掛接的所有device,進而獲得了 platform device。根據device也可以獲得驅動該總線上所有設備的相關driver。
platform driver包含driver,根據driver可以獲得相應的bus,進而獲得bus上所有的device,進一步獲得platform device,根據name對driver與platform device進行匹配,匹配成功後將device與相應的driver關聯起來,即實現了platform device和platform driver的關聯。
匹配成功後調用driver的probe進而調用platform driver的probe,在probe裏實現驅動特定的功能。
七、適用於plarform的驅動
platform機制將設備本身的資源註冊進內核,由內核統一管理,在驅動程序中使用這些資源時通過platform device提供的標準接口進行申請並使用。這樣提高了驅動和資源管理的獨立性,這樣擁有更好的可移植性。platform機制的本身使用並不複雜,由兩部分組成:platform_device和platfrom_driver。Platform driver通過platform bus獲取platform_device。通常情況下只要和內核本身運行依賴性不大的外圍設備,相對獨立的,擁有各自獨立的資源(地址總線和IRQs),都可以用
platform_driver來管理,而timer,irq等小系統之內的設備則最好不用platfrom_driver機制。platform_device最大的特定是CPU直接尋址設備的寄存器空間,即使對於其他總線設備,設備本身的寄存器無法通過CPU總線訪問,但總線的controller仍然需要通過platform bus來管理。
總之,platfrom_driver的根本目的是爲了統一管理系統的外設資源,爲驅動程序提供統一的接口來訪問系統資源,將驅動和資源分離,提高程序的可移植性。
八、基於platform總線的驅動開發流程
基於Platform總線的驅動開發流程如下:
定義初始化platform bus
定義各種platform devices
註冊各種platform devices
定義相關platform driver
註冊相關platform driver
操作相關設備 file_operations
九、platform_bus必須在系統註冊任何platform driver和platform device之前初始化,那麼這是如何實現的呢?
/init/main.c
執行順序:start_kernel-> rest_init-> kernel_init->do_basic_setup->driver_init->platform_bus_init
void __init driver_init(void)
{
/* These are the core pieces */
devices_init();
buses_init();
classes_init();
firmware_init();
hypervisor_init();
/* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init();
system_bus_init();
cpu_dev_init();
memory_dev_init();
}
static void __init do_basic_setup(void)
{
/* drivers will send hotplug events */
init_workqueues();
usermodehelper_init();
driver_init();
init_irq_proc();
do_initcalls();
}
可以看出platform_bus是在driver_init中初始化的,而所有的平臺設備和平臺驅動的註冊都是在do_initcalls中,滯後於platform_bus的初始化。platform_bus的初始化發生在
int __init platform_bus_init(void)
{
int error;
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
struct device platform_bus = {
.bus_id = "platform",
};
該函數創建了一個名爲 “platform”的設備,後續platform的設備都會以此爲parent。在sysfs中表示爲:所有platform類型的設備都會添加在 platform_bus所代表的目錄下,即 /sys/devices/platform下面。
十、平臺設備在linux系統下的實現過程
/arch/arm/plat-s5p/devs.c
/arch/arm/plat-s3c24xx/devs.c
中定義了系統的資源,是一個高度可移植的文件,大部分板級資源都在這裏集中定義
/arch/arm/plat-s3c24xx/devs.c
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C24XX_PA_IIC,
.end = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC,
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
}
};
struct platform_device s3c_device_i2c = {
.name = "s3c2410-i2c",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
設備名稱爲s3c2410-i2c,“-1”只有一個i2c設備,兩個資源s3c_i2c_resource,分別爲i2c控制器的寄存器空間和中斷信息。
註冊platform_device
定義了platform_device後,需要添加到系統中,就可以調用函數platform_add_devices。
/arch/arm/mach-s3c2440/mach-smdk2440.c smdk2440_devices將系統資源組織起來,統一註冊進內核。
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
};
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));將系統所有資源註冊進系統,在此之前platform bus需要初始化成功,否則無法將platform devices掛接到platform bus上。爲了保證platform driver初始化時,相關platform資源已經註冊進系統,smdk2440_machine_init需要很早執行,而其作爲平臺初始化 init_machine 時,將優先於系統所有驅動的初始化。
其調用順序如下:
1.平臺資源的初始化
smdk2440_machine_init
2.平臺總線的初始化
driver_init->platform_bus_init
3.平臺設備和平臺驅動的總線掛載。
do_initcalls 驅動程序的module_init加載過程在該函數中運行。這裏還是以ht9032c_uart.c爲例
一般情況下,當進入probe函數後,需要獲取設備的資源信息,常用獲取資源的函數主要是:
struct resource * platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
根據參數type所指定類型,例如IORESOURCE_MEM,來獲取指定的資源。
struct int platform_get_irq(struct platform_device *dev, unsigned int num);
獲取資源中的中斷號。
struct resource * platform_get_resource_byname(struct platform_device *dev, unsigned int type, char *name);
根據參數name所指定的名稱,來獲取指定的資源。
int platform_get_irq_byname(struct platform_device *dev, char *name);
根據參數name所指定的名稱,來獲取資源中的中斷號。
此probe函數獲取物理IO空間,通過request_mem_region和ioremap等操作物理地址轉換成內核中的虛擬地址,初始化I2C控制器,通過platform_get_irq或platform_get_resource得到設備的中斷號以後,就可以調用request_irq函數來向系統註冊中斷,並將此I2C控制器添加到系統中。
platform_bus,platform_driver,platform_bus
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.