1. 程式人生 > 實用技巧 >Linux 驅動框架---platform 匯流排驅動和裝置

Linux 驅動框架---platform 匯流排驅動和裝置

Linux系統的驅動框架主要就是三個主要部分組成,驅動、匯流排、裝置而,現在常見的嵌入式SOC已經不是單純的CPU的概念了,他們片上集成了很多外設電路,這些外設都掛接在SOC內部的總線上,不同與IIC、SPI和USB等這一類實際存在匯流排,系統內的匯流排實際上是CPU的內部匯流排,所以Linux為了統一驅動模型在系統在啟動時初始化了一條虛擬匯流排,他是一個抽象的匯流排稱之為platform匯流排,在實現程式碼的/drivers/base/platform.c中。今天就來學習這一類驅動的框架結構。

匯流排的具體實現

struct bus_type platform_bus_type = {
    .name        
= "platform", .dev_groups = platform_dev_groups,(屬性) .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops, };

結合前面的分析,Linux下的裝置都應該有所屬的bus_type(dev.bus)這個bus_type就抽象了他們共通的一些“屬性”和“方法”。platform他在包含一個普通的device的基礎上


由增加了一些平臺裝置需要的資料如下

struct platform_device {
    const char    *name;
    int        id;bool        id_auto;
    struct device    dev;
    u32        num_resources;
    struct resource    *resource;

    const struct platform_device_id    *id_entry;/*記錄和驅動的匹配表id_table中匹配的哪一個表項指標*/

    /* MFD cell pointer 
*/ struct mfd_cell *mfd_cell; /* arch specific additions */ struct pdev_archdata archdata; 這個引數一般都指向這個結構體實體本身地址 };

值得一提的是其中ID引數如果是-1則包含的裝置名就是 platform_device .name,如果為-2則會自動分配platform裝置ID具體是通過platform.c中的一個函式實現具體參考原始碼,否則其他引數則就按"%s.%d", pdev->name, pdev->id 格式格式化裝置名。一般註冊平臺裝置需要初始化的內容主要有name resource,有時還需要指定內涵dev的platform_data,這一部分資料常常被驅動使用,這也是Linux 驅動和裝置分離的一部分體現。

platform_device

device註冊新增

這裡只是簡單羅列函式呼叫過程,這一部分實際上是Device註冊的過程的一個封裝,具體內部操作可以草考Linux設備註冊。這一部分如果前面device的註冊理解的比較透徹這一部分就很好理解了。

platform_device_register
  1、device_initialize(&pdev->dev);
  2、arch_setup_pdev_archdata(空函式)
  3、platform_driver_add
    1、pdev->dev.parent = &platform_bus;指定父裝置為platform裝置匯流排
    2、pdev->dev.bus = &platform_bus_type;
    3、設定裝置名稱三種情況(-1 -2(申請ID) other)
4、裝置資源管理
5、呼叫device_add(pdev->dev)

device解除安裝過程

platform_device_unregister
  1、platform_device_del
    1、釋放platform_device id
    2、device_del(pdev->dev)
  2、platform_device_put
  3、put_device

platform_driver

同理樣platform_driver 也是一個包含了device_driver 的結構體如下:

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;
    bool prevent_deferred_probe;
};

從結構體可以看出平臺裝置驅動提供了一些操作介面和一個platform_device_id 型別的相容性匹配表(後面分析)。其次是結構體中的操作介面函式其在內部的device_driver結構體內也是有一套類似的操作介面;其他的電源管理現在已經很少用驅動中的介面了而轉而使用內涵的device_driver驅動中的dev_pm_ops結構體中的介面來實現。

driver註冊新增

__platform_driver_register(drv, THIS_MODULE)
  1、drv->driver.bus = platform_bus_type;
  2、如果平臺驅動中的xx_probe或xx_remove等為空則指定drv->driver.同名介面指標為platform驅動預設行為(僅支援acpi方式匹配)。
  3、driver_register

driver註冊移除

platform_device_unregister
  1、platform_driver_unregister
    1、driver_unregister

驅動和裝置的匹配

無論上面的註冊裝置還是註冊驅動,最後都是要呼叫匯流排型別的mach函式進行驅動和裝置的匹配,這也是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);

    /* Attempt an OF style match first */
    /* 採用裝置樹的相容方式匹配驅動和裝置 */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* 採用ACPI的方式匹配驅動和裝置 */
    if (acpi_driver_match_device(dev, drv))
        return 1;

    /* 通過驅動和裝置的mach表來匹配驅動和裝置 */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* 最後就是按驅動和裝置名稱是否相同來判斷當前驅動是否支援這個裝置 */
    return (strcmp(pdev->name, drv->name) == 0);
}

從這個函式我們可以知道platform的driver和device的匹配就是通過以上四種規則來進行匹配的,前兩種方式暫時不深究學到再來看;除此之外這個函式還告訴我們一個核心機制
如果驅動指定了mach_id_table則驅動將放棄名稱相同匹配機制這一點需要重點記住。具體這個mach函式是在何時呼叫的參考Linux device的分析。其中相容ID的匹配表格式是

struct platform_device_id {
    char name[PLATFORM_NAME_SIZE];
    kernel_ulong_t driver_data;
};

具體的匹配規則也很簡單就是使用ID表內的名稱來和裝置名比較具體看程式碼,比較簡單,需要注意的是這裡還將匹配的id 表的控制代碼儲存在platform device的id_entry項上,id_table裡常常帶一個long型的driver_data資料儲存驅動資料

static const struct platform_device_id *platform_match_id(
            const 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;
}

具體例項分析

下面通過位元組實現一個platformdevice來匹配核心的一個三星的led驅動,核心程式碼是3-16-57版本驅動在drivers\leds\leds-s3c24xx.c。

static struct platform_driver s3c24xx_led_driver = {
    .probe        = s3c24xx_led_probe,
    .remove        = s3c24xx_led_remove,
    .driver        = {
        .name        = "s3c24xx_led",
        .owner        = THIS_MODULE,
    },
};

主要分析其s3c24xx_led_probe函式的執行過程就能明白對應的裝置應該如何新增。通過驅動的宣告我得出結論,這個驅動除了裝置樹和ACPI的方式匹配裝置外就只能通過名稱來匹配裝置了,所以先定義裝置如下然後慢慢填充。

static struct platform_device tiny210_device_led []= {
    .name    = "s3c24xx_led",
    .id        = 0,
};

然後在看s3c24xx_led_probe函式都是怎樣處理的

static int s3c24xx_led_probe(struct platform_device *dev)
{
    struct s3c24xx_led_platdata *pdata = dev_get_platdata(&dev->dev);
    /* 首先獲取 platform_data 這個我還沒定義所以後面需要定義 */
    struct s3c24xx_gpio_led *led;
    int ret;
    /* 申請驅動私有資料結構體 */
    led = devm_kzalloc(&dev->dev, sizeof(struct s3c24xx_gpio_led),
               GFP_KERNEL);
    if (!led)
        return -ENOMEM;
    /* 將私有資料結構體繫結到device的driver_data成員上方便使用 */
    platform_set_drvdata(dev, led);
    /* 這裡涉及LED class 子系統的內容 可以暫時當作黑盒 */
    led->cdev.brightness_set = s3c24xx_led_set;
    led->cdev.default_trigger = pdata->def_trigger;
    led->cdev.name = pdata->name;
    led->cdev.flags |= LED_CORE_SUSPENDRESUME;
    /* 繫結platform_data 到私有資料結構 */
    led->pdata = pdata;

    ret = devm_gpio_request(&dev->dev, pdata->gpio, "S3C24XX_LED");
    if (ret < 0)
        return ret;

    /* no point in having a pull-up if we are always driving */
    /* GPIO 子系統內容 配置對應的GPIO */
    s3c_gpio_setpull(pdata->gpio, S3C_GPIO_PULL_NONE);
    /* 如果裝置定義時指定了這個標誌則會執行下面的設定將GPIO配置為輸入方向 */
    if (pdata->flags & S3C24XX_LEDF_TRISTATE)
        /* GPIO 子系統內容 配置對應的GPIO方向為輸入 一般底層由晶片廠商實現 */
        gpio_direction_input(pdata->gpio);
    else
        /* 第二個引數是保證LED在預設狀態下是不點亮的 */
        gpio_direction_output(pdata->gpio,
            pdata->flags & S3C24XX_LEDF_ACTLOW ? 1 : 0);

    /* register our new led device */
    /* 這裡涉及LED class 子系統的內容 可以暫時當作黑盒 */
    ret = led_classdev_register(&dev->dev, &led->cdev);
    if (ret < 0)
        dev_err(&dev->dev, "led_classdev_register failed\n");

    return ret;
}

到此LED驅動的匹配操作就完了,除了中間涉及Linux 的led class 和 gpio 子系統的內容外還是很簡單的所以接下來完善我的LED裝置
增加驅動所需的資料

struct s3c24xx_led_platdata led_data
{
    .gpio  = S5PV210_GPJ2(0),(gpio 子系統)
    .flags = S3C24XX_LEDF_ACTLOW,(驅動的私有標誌,指明LED開啟時的IO電平)
    .name  = "led",
    .def_trigger = "",
};
static struct platform_device tiny210_device_led []= {
    .name        = "s3c24xx_led",
    .id        = 0,
    .dev        ={
        .platform_data = &led_data,
    },
};

flags 的內容是後來補上的他的意思就是led在低電平時點亮,不要這個標誌LED預設狀態是開啟的這和具體的硬體有關。

最後將裝置以模組的形式加入。
然後在/sys/class/leds/ 下將看到一個led0檔案他是一個符合連結指向/devices/platform/latform/s3c24xx_led.0/leds/led0
進入 會看到
brightness max_brightness subsystem uevent
device power trigger
這些就是ledclass的內容的,通過向brightness寫資料就可以控制LED的開啟和關閉了。也可以直接使用指令碼

echo 0 > brightness led燈就亮了
echo 1 >brightness led燈就滅了。

綜上Linux下的platform匯流排出現的意義就是統一linux下的驅動模型,即裝置、驅動、匯流排其中匯流排負責裝置和驅動的匹配。一般Linux下的複雜驅動都是基於platform匯流排的
通過驅動的probe函式進行呼叫其他子系統的介面實習更加複雜的驅動模型。而platform_driver和platform_device都是在Linux device和device_driver之上封裝的所以需要在明白
Linux device和device_driver 的相關機制之上來理解就更加容易了。

附裝置新增原始碼,不過原始碼後來我又添加了三個LED。

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/delay.h>

#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <asm/setup.h>
#include <asm/mach-types.h>

#include <mach/map.h>
#include <plat/gpio-cfg.h>
#include <plat/devs.h>
#include <plat/cpu.h>
#include <linux/platform_data/leds-s3c24xx.h>



static struct s3c24xx_led_platdata led_data[]  = {
    [0]={
        .gpio  = S5PV210_GPJ2(0),
        .flags = S3C24XX_LEDF_ACTLOW,
        .name  = "led0",
        .def_trigger = "",
    },
    [1]={
        .gpio  = S5PV210_GPJ2(1),
        .name  = "led1",
        .flags = S3C24XX_LEDF_ACTLOW,
        .def_trigger = "",
    },
    [2]={
        .gpio  = S5PV210_GPJ2(2),
        .name  = "led2",
        .flags = S3C24XX_LEDF_ACTLOW,
        .def_trigger = "",
    },
    [3]={
        .gpio  = S5PV210_GPJ2(3),
        .name  = "led3",
        .flags = S3C24XX_LEDF_ACTLOW,
        .def_trigger = "",
    },
    
};

static struct platform_device tiny210_device_led []= {
    [0]={
        .name        = "s3c24xx_led",
        .id        = 0,
        .dev        ={
            .platform_data = &led_data[0],
            .devt = MAJOR(22),
        },
    },
    [1]={
        .name        = "s3c24xx_led",
        .id        = 1,
        .dev        ={
            .platform_data = &led_data[1],
            .devt = MAJOR(22),
        },
    },
    [2]={
        .name        = "s3c24xx_led",
        .id        = 2,
        .dev        ={
            .platform_data = &led_data[2],
            .devt = MAJOR(22),
        },
    },
    [3]={
        .name        = "s3c24xx_led",
        .id        = 3,
        .dev        ={
            .platform_data = &led_data[3],
            .devt = MAJOR(22),
        },
    }
    
};


static int __init platform_led_init(void)
{
    int i;

    for(i=0;i<ARRAY_SIZE(tiny210_device_led);i++){
        if(platform_device_register(&tiny210_device_led[i])<0)
        {
            printk(KERN_ERR "tiny210_device_led %d Fail\n",i);
            return -1;
        }
    }
    printk(KERN_INFO "tiny210_device_led Succse\n");
    return 0;

}

static void __exit platform_led_exit(void)
{
    int i;
    for(i=0;i<ARRAY_SIZE(tiny210_device_led);i++){
        platform_device_unregister(&tiny210_device_led[i]);
    }
}

module_init(platform_led_init);
module_exit(platform_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("smile@shanghai");
platform-leds