platfrom

 

1、概述:

通常在Linux中,把SoC系統中集成的獨立外設單元(如:I2C、IIS、RTC、看門狗等)都被當作平臺設備來處理。

從Linux2.6起,引入了一套新的驅動管理和註冊機制:Platform_devicePlatform_driver,來管理相應設備。

Linux中大部分的設備驅動,都可以使用這套機制,設備用platform_device表示,驅動用platform_driver進行註冊。

Linux platform driver機制和傳統的device_driver機制相比,一個十分明顯的優勢在於platform機制將本身的資源註冊進內核,由內核統一管理,在驅動程序中使用這些資源時通過platform_device提供的標準接口進行申請並使用。

這樣提高了驅動和資源管理的獨立性,並且擁有較好的可移植性和安全性。
platform是一個虛擬的地址總線,相比pci,usb,它主要用於描述SOC上的片上資源,比如s3c2410上集成的控制器(lcd,watchdog,rtc等),platform所描述的資源有一個共同點,就是可以在cpu的總線上直接取址。

2、platform機制分爲三個步驟
1)總線註冊階段:
內核啓動初始化時的main.c文件中的
kernel_init() ->do_basic_setup() -> driver_init() ->platform_bus_init() ->bus_register(&platform_bus_type),註冊了一條platform總線(虛擬總線)
platform總線用於連接各類採用platform機制的設備,此階段無需我們修改,由linux內核維護。
2)添加設備階段
設備註冊的時候
Platform_device_register() -> platform_device_add() -> pdev->dev.bus = &platform_bus_type -> device_add(),就這樣把設備給掛到虛擬的總線上。
本階段是增加設備到plartform總線上,我們增加硬件設備,需要修改此處信息。 
此部分操作一般arm/arm/mach-s3c2440/mach-smdk2440.c類似的文件中,需要我們根據硬件的實際需要修改相應代碼
3)驅動註冊階段:
    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),如果相符就調用really_probe(實際就是執行相應設備的platform_driver->probe(platform_device)。)
開始真正的探測,如果probe成功,則綁定設備到該驅動。
本階段是在編寫具體的驅動程序時完成,在註冊驅動時獲取步驟2中已經申請的資料,一般由程序開發者完成。

3、platform設備開發過程
    platform機制開發的並不複雜,由兩部分組成:platform_device和platfrom_driver
    通過Platform機制開發發底層驅動的大致流程爲: 
    定義 platform_device
    註冊 platform_device
    定義 platform_driver
    註冊 platform_driver
   以linux2.6.34平臺下S3C2440爲例:前兩項工作主要在arch/arm/mach-s3c2440/match-smdk2440.c與arch/arm/platform-s3c24xx/devs.c中完成,後兩項工作主要在編寫具體的驅動程序時完成

在2.6內核中platform設備用結構體platform_device來描述,該結構體定義在kernel\include\linux\platform_device.h中,
struct platform_device
 {
     const char * name;
     u32  id;
     struct device dev;
     u32  num_resources;
     struct resource * resource;
};
每個具體的驅動都對應一個這樣的結構體。 
Platform_device結構體描述了一個platform結構的設備,在其中包含了
一般設備的結構體:struct device  dev;
設備的資源結構體:struct resource * resource;
還有設備的名字:const char * name。
(注意,這個名字一定要和後面platform_driver.driver->name相同,因爲在註冊具體的設備驅動時會遍歷這個結構體查找相應的數據結構,後面會詳細講解)

該結構一個重要的元素是resource,該元素存入了最爲重要的設備資源信息,定義在kernel\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;  //資源鏈表指針
};
主要用於定義具體設備佔用的硬件資源(如:地址空間、中斷號等;
其中 flags位表示該資源的類型
start和end分別表示該資源的起始地址和結束地址;
要注意的是,這裏的platform_device設備的註冊過程必須在相應設備驅動加載之前被調用;
即執行platform_driver_register()之前,原因是驅動註冊時需要匹配內核中所有已註冊的設備名。

我們以內核中對SMDK2440的支持爲例觀察一下整個過程:

內核啓動過程中會調用用 arch/arm/mach-s3c2440/smdk2440_machine_init()函數進行板級硬件初始化
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
此函數中調用了platform_add_devices() -> platform_device_register()註冊platform設備
註冊順序根據同文件夾下的
static struct platform_device *smdk2440_devices[] __initdata = 
{
&s3c_device_ohci,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
&s3c_device_dm9k,
 &s3c24xx_uda134x,
&s3c_device_sdi,
};
結構體進行
這些設備的初始化一般都在arch/arm/plat-s3c24xx/devs.c下
我們以s3c_device_wdt爲例進行觀察:

/* Watchdog */
//看門狗資源結構體
static struct resource s3c_wdt_resource[] = {
[0] = {
.start = S3C24XX_PA_WATCHDOG,
.end   = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1,
.flags = IORESOURCE_MEM, //看門狗所使用的IO口範圍
},
[1] = {
.start = IRQ_WDT,
.end   = IRQ_WDT,
.flags = IORESOURCE_IRQ,  //看門狗所使用的中斷資源
}
};

//定義了一個看門狗結構體
struct platform_device s3c_device_wdt = {
.name  = "s3c2410-wdt",  //驅動名稱
.id  = -1,                           //id號,-1代表自動分配
.num_resources = ARRAY_SIZE(s3c_wdt_resource),
  //指定資源數量
.resource  = s3c_wdt_resource,             //指定資源結構體
};


platform_driver在具體的硬件設備驅動編寫中完成:
同plartform_device相似,需要定義並實現以下結構體
struct platform_driver                  (/include/linux/Platform_device.h)
{
       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結構體描述了一個platform結構的驅動。
其中除了一些函數指針外,還有一個一般驅動的device_driver結構。

/*Watchdog平臺驅動結構體,平臺驅動結構體定義在platform_device.h中,該結構體內的接口函數需要單獨實現*/
static struct platform_driver watchdog_driver = 
{
    .probe       = watchdog_probe,   /*Watchdog探測函數*/
    .remove      = __devexit_p(watchdog_remove),/*Watchdog移除函數*/
    .shutdown   = watchdog_shutdown, /*Watchdog關閉函數*/
    .suspend    = watchdog_suspend,  /*Watchdog掛起函數*/
    .resume     = watchdog_resume,   /*Watchdog恢復函數*/
    .driver     = 
    {
        /*注意這裏的名稱一定要和系統中定義平臺設備的地方一致,這樣才能把平臺設備與該平臺設備的驅動關聯起來*/
        .name   = "s3c2410-wdt",
        .owner  = THIS_MODULE,
    },
};

static int __init watchdog_init(void)
{
    /*將Watchdog註冊成平臺設備驅動*/
    return platform_driver_register(&watchdog_driver);
}

static void __exit watchdog_exit(void)
{
    /*註銷Watchdog平臺設備驅動*/
    platform_driver_unregister(&watchdog_driver);
}

module_init(watchdog_init);
module_exit(watchdog_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("linux");
MODULE_DESCRIPTION("S3C2440 Watchdog Driver");

static int __devinit watchdog_probe(struct platform_device *pdev)
{
    int ret;
    int started = 0;
    struct resource *res;/*定義一個資源,用來保存獲取的watchdog的IO資源*/
 /*在系統定義的watchdog平臺設備中獲取watchdog中斷號platform_get_irq定義在platform_device.h中*/
    wdt_irqno = platform_get_irq(pdev, 0);

 /*申請Watchdog中斷服務,這裏使用的是快速中斷:IRQF_DISABLED。中斷服務程序爲:wdt_irq,將Watchdog平臺設備pdev做參數傳遞過去了*/
    ret = request_irq(wdt_irqno, wdt_irq, IRQF_DISABLED, pdev->name, pdev);

/*獲取watchdog平臺設備所使用的IO端口資源,注意這個IORESOURCE_MEM標誌和watchdog平臺設備定義中的一致*/
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

/*申請watchdog的IO端口資源所佔用的IO空間(要注意理解IO空間和內存空間的區別),request_mem_region定義在ioport.h中*/
    wdt_mem = request_mem_region(res->start, res->end - res->start + 1, pdev->name);

/*將watchdog的IO端口占用的這段IO空間映射到內存的虛擬地址,ioremap定義在io.h中。
     注意:IO空間要映射後才能使用,以後對虛擬地址的操作就是對IO空間的操作,*/

    wdt_base = ioremap(res->start, res->end - res->start + 1);

    return ret;
}

/*Watchdog平臺驅動的設備移除接口函數的實現*/
static int __devexit wdt_remove(struct platform_device *dev)
{
  /*釋放獲取的Watchdog平臺設備的IO資源*/
    release_resource(wdt_mem);
    kfree(wdt_mem);

    wdt_mem = NULL;

 /*同watchdog_probe中中斷的申請相對應,在那裏申請中斷,這裏就釋放中斷*/
    free_irq(wdt_irqno, dev);
    wdt_irq = NULL;

 /*釋放獲取的Watchdog平臺設備的時鐘*/
    clk_disable(wdt_clock);
    clk_put(wdt_clock);

    wdt_clock = NULL;

 /*釋放Watchdog設備虛擬地址映射空間*/
    iounmap(wdt_base);
    return 0;
}


#ifdef CONFIG_PM

/*定義兩個變量來分別保存掛起時的WTCON和WTDAT值,到恢復的時候使用*/
static unsigned long wtcon_save;
static unsigned long wtdat_save;

/*Watchdog平臺驅動的設備掛起接口函數的實現*/
static int wdt_suspend(struct platform_device *dev, pm_message_t state)
{
    /*保存掛起時的WTCON和WTDAT值*/
    wtcon_save = readl(wdt_base + S3C2410_WTCON);
    wtdat_save = readl(wdt_base + S3C2410_WTDAT);

    /*停止看門狗定時器*/
    wdt_start_or_stop(0);
    return 0;
}

/*Watchdog平臺驅動的設備恢復接口函數的實現*/
static int wdt_resume(struct platform_device *dev)
{
    /*恢復掛起時的WTCON和WTDAT值,注意這個順序*/
    writel(wtdat_save, wdt_base + S3C2410_WTDAT);
    writel(wtdat_save, wdt_base + S3C2410_WTCNT);
    writel(wtcon_save, wdt_base + S3C2410_WTCON);
    return 0;
}

#else /*配置內核時沒選上電源管理,Watchdog平臺驅動的設備掛起和恢復功能均無效,這兩個函數也就無需實現了*/
    #define wdt_suspend NULL
    #define wdt_resume NULL
#endif
上一篇:從 C/C++ 程序調用 Java 代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章