Linux驅動模型分析之platform
概述
一個現實的Linux設備和驅動通常需要掛接在一種總線上,對於本身依附於PCI、USB、I2C、SPI等設備而言,這自然不是問題。但是在嵌入式系統裏面,SOC系統中集成的獨立的外設控制器,掛接在SOC內存空間的外設卻不需要依附於此類總線。基於這一背景,Linux發明了一種虛擬總線,就是platform總線,相應設備稱爲platform_device,而驅動稱爲platform_driver。也就是說platform的設備是外設或者其控制器,就像EC外設一樣,被申請爲platform結構。
模型
platform模型關心總線,設備和驅動3個實體,總線將設備和驅動綁定。在系統每註冊一個設備的時候,會尋找與之匹配的驅動,相反的,系統每註冊一個驅動的時候,會尋找與之匹配的設備,而匹配由總線完成。
platform總線
數據結構
- 設備結構
struct device platform_bus =
{
.init_name = "platform",
};
platform也是一種設備,所以初始化一個device結構,設備名稱platform,因爲沒有指定父設備,所以註冊後將會在/sys/device/下出現一個platform目錄。
- 總線結構
struct bus_type platform_bus_type =
{
.name = "platform",
.dev_attrs = platform_dev_attrs, //設備屬性
.match =platform_match, //match函數,完成設備和驅動的匹配工作
.uevent =platform_uevent, //熱插拔操作
.pm = &platform_dev_pm_ops,
};
註冊:
在系統中platform對應的文件是drivers/base/platform.c,他不是作爲一個模塊註冊到內核的,關鍵的註冊總線的函數由系統初始化部分,對應/init/main.c中的do_basic_setup函數間接調用。
- platform總線註冊函數:__init platform_bus_init()
int __init platform_bus_init(void)
{
int err;
early_platform_cleanup();
err = device_register(&platform_bus);//總線也是設備
if(err)
return err;
err = bus_register(&platform_bus_type);//註冊platform_bus_type總線到內核
if(err)
device_unregister(&platform_bus);
return err;
}
這個函數向內核註冊了一種總線,首先由driver_init函數調用,driver_init函數由do_basic_setup函數調用,do_basic_setup函數由kernel_init調用。
所以platform總線在內核初始化的時候就註冊進了內核。
platform設備
數據結構
platform_device結構
struct platform_device
{
const char *name; //設備名
int id; //設備編號
struct device dev;
u32 num_resource; //設備使用資源數目
struct resources *resource; //設備使用資源
const struct platform_device_id *id_entry;
struct pdev_archdata archdata;
};
其中主要包含了device的屬性。
主要成員:
- const char *name;
匹配用的name。
int id;
設備id,用於給要插入該總線的具有相同name的設備編號,如果只有一個設備編號-1.u32 num_resource;
資源數struct resource *resource;
存放資源的數組
resource結構
struct resource
{
resource_size_t start; //資源起始地址
resource_size_t end; // 資源結束地址
const char *name;
unsigned long flags; //資源類型
struct resource *parent, *sibling, *child;
};
該結構體主要是資源分配。
主要成員:
- resource_size_t start;
資源的起始地址,含義隨着flags變化
resource_size_t end;
資源的結束地址,含義隨着flags變化const char *name;
匹配用的name,要和platform_device和platform_driver中的name一致。unsigned long flags;
資源類型標誌位。可以爲:IORESOURCE_IO,IORESOURCE_MEM,IORESOURCE_IRQ,IORESOURCE_DMA
如當flags爲IORESOURCE_MEM時,start和end分別表示該platform_device佔據的內存的起始地址和結束地址;
當flags爲IORESOURCE_IRQ時,start和end分別表示該platform_device使用的中斷號的起始值和結束值;如果只有一箇中斷號,開始和結束值相同。
註冊
設備註冊函數:platform_device_register
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
這個函數首先初始化了platform_device的device結構,然後調用platform_device_add,這個是註冊函數的關鍵,下面分析:
int platform_device_add(struct platform_device *pdev)
{
inti, ret = 0;
if(!pdev)
{
return -EINVALL;
}
if(!pdev -> dev.parent)
pdev -> dev.parent = &platform_bus; //總線的設備結構:platform設備的父設備一般都是platform_bus
pdev -> dev.bus = &platform_bus_type; //總線的總線結構:掛到總線上
if(pdev -> id != -1)
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); //設置設備名字
else
dev_set_name(&pdev->dev, "%s", pdev->name);
for(i = 0; i < pdev->num_resources; i++) //遍歷設備所佔資源
{
struct resource *p, *r = &pdev->resource[i];
if(r->name == NULL)
r->name = dev_name(&pdev->dev);
p = r->parent; //設備父資源
if(!p)
{
if(resource_type(r) == IORESOURCE_MEM) //如果父資源沒有定義,根據資源的類型
p = &iomem_resource; //分別賦予iomem_resource; 和ioport_resource;
else if(resource_type(r) == IORESOURCE_IO)
p = &ioport_resource;
}
if(p && insert_resource(p, r)) //調用insert_resource 插入資源
{
printk(KERN_ERR "%s:failed to claim resource %d\n", dev_name(&pdev->dev), 1);
ret = -EBUSY;
goto failed;
}
}
pr_debug("registering platform device '%s', parent at %s\n", dev_name(&pdev->dev), dev_name(pdev->dev.parent));
ret = device_add(&pdev->dev); //註冊到設備模型中
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;
}
platform驅動
數據結構
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 (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
其中包含了probe()、remove()、shutdown()、suspend()、resume()等函數,這些函數需要驅動來實現。
主要成員:
- int (probe)(struct platform_device );
這是一個匹配函數,通過比較platform_driver和platform_device的name來進行匹配。還有就是會在這個函數中初始化一些和該驅動有關的初始化。
struct device_driver driver;
在這個結構體中,主要實現const char *name和struct module *owner這兩個成員。
其中name就是用來匹配設備和驅動的。必須與device的name一致。
owner表明該模塊的擁有者,一般都是THIS_MODULE;const struct platform_device_id *id_table;
這個不需要必然實現的,但是這個實現是另外一種匹配設備和驅動的方法。通過id_table來實現匹配。但是最終還是通過名字來對應匹配,只是匹配的名字被列入一張表中,platform_device中的name和這個表中的每一個值進行比較,直到找到相同的那一個。主要是爲了實現一個驅動對應多個設備的情況。設備之間的區別通過id_table.driver_data成員進行區分,從而在連接設備和驅動的時候可以針對設備區別,對加載的驅動程序進行調整。
註冊
驅動註冊函數:platform_driver_register
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &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;
}
return driver_register(&drv->driver);
}
這個函數首先使驅動屬於platform_bus_type總線,將platform_driver結構中定義的probe,remove,shutdown賦值給device_driver結構中對應的成員,以供Linux設備模型核心調用。然後調用driver_register 將設備驅動註冊到Linux設備模型核心中。
總線,設備,驅動的整合
使用函數:platform_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);
if(pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
return (strcmp(pdev->name, drv->name) == 0);
}