Linux內核platform機制

一.linux內核platform機制

1.1.回顧ioremap實現開關燈驅動
需求:
硬件LED的GPIO由GPC0_3,GPC0_4更換爲GPF1_4,GPF1_5
通過分析代碼可知:
1.一個完整的驅動包括兩部分:純硬件和純軟件
2.之前的驅動如果硬件發生變化,代碼幾乎重頭都要進行檢查和修改,增大了驅動開發的工作量,可移植性非常差;
3.如果要優化此問題,內核提出分離思想,把純軟件和純硬件的信息進行分離,將來驅動開發者的重心放在純硬件上,軟件一經寫好,無需在改動;

1.2.linux內核分離思想
將硬件和軟件進行分離;
分離思想的實現依靠platform機制,platform依賴設備-總線-驅動編程模型;

1.3.設備-總線-驅動編程模型:
實現的步驟參見PPT即可


總結:如果要採用設備-總線-驅動編程模型實現一個設備驅動,只需關注:
struct platform_device
struct platform_driver

問:如何使用以上兩個數據結構呢?

1.4.struct platform_device
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource *resource;
...
}
struct device {
void *platform_data;
};
struct resource {
resource_size_t start; //硬件的起始信息
resource_size_t end;//硬件的結束信息
unsigned long flags; //硬件信息的標誌
有兩種標誌:
IORESOURCE_MEM:內存資源,例如寄存器地址
IORESOURCE_IRQ: IO資源,例如中斷號,GPIO編號
};

數據結構作用:描述硬件外設的純硬件信息
成員:
name:硬件節點的名稱,相當重要,用於匹配
id:硬件節點的編號,如果只有一個硬件信息,一般賦值給-1,如果有多個同名的硬件信息,通過id來區分:0,1,2,3,...
dev:其中利用platform_data來裝載驅動開發者自定義的硬件信息(struct led_resource, struct btn_resource,將自己聲明的描述硬件信息添加到platform_data即可)
resource:指向resource類型的硬件信息
num_resourcs:resource類型的硬件信息的個數

注意:驅動開發者將來用resource來描述硬件還是用自定義的數據結構來描述硬件信息都可以,可以單獨使用,也可以同時使用;

註冊方法:
platform_device_register
卸載方法:
platform_device_unregister

案例

利用platform_device描述硬件信息

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

//聲明描述LED硬件相關的數據結構(自定義)
struct led_resource {
    unsigned long phys_addr; //保存起始物理地址
    int size; //物理內存大小
    int pin; //GPIO編號
};

//定義初始化自定義的LED硬件信息
static struct led_resource led_info = {
    .phys_addr = 0xE0200060,
    .size = 8,
    .pin = 3 //GPC0_3
};

//定義初始化resource類型的硬件信息(resource描述)
static struct resource led_res[] = {
    //描述寄存器地址信息
    [0] = {
        .start = 0xE0200060, //寄存器起始地址
        .end = 0xE0200060 + 8, //寄存器結束地址
        .flags = IORESOURCE_MEM //標記爲內存資源
    },
    //描述GPIO編號信息
    [1] = {
        .start = 3, //GPC0_3
        .end = 3, //GPC0_3
        .flags = IORESOURCE_IRQ //標記爲IO資源
    }
};

static void led_release(struct device *dev) {} 

//定義初始化描述LED硬件節點
static struct platform_device led_dev = {
    .name = "myled", //用於匹配
    .id = -1, //硬件節點編號
    .dev = {
        .release = led_release, //解除警告
        .platform_data = &led_info //裝載自定義的硬件信息
    },
    .resource = led_res, //裝載resource類型的硬件信息
    .num_resources = ARRAY_SIZE(led_res) //resource類型的硬件個數
};

static int led_dev_init(void)
{
    //註冊硬件節點到總線的dev鏈表
    //並且內核幫你遍歷drv鏈表,進行匹配
    //如果匹配成功,內核調用軟件節點的probe函數
    //把led_dev首地址傳遞給probe函數
    platform_device_register(&led_dev);
    printk("led_dev = %#x\n", &led_dev);
    return 0;
}

static void led_dev_exit(void)
{
    //從dev鏈表卸載硬件節點
    //調用軟件節點的remove函數,remove的形參也執行led_dev
    platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
  1.5.struct platform_driver {
int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
struct device_driver driver;
};


struct device_driver {
char *name; //用於匹配
};


說明:描述軟件信息
成員:
probe:軟件和硬件匹配成功,內核調用,pdev指向匹配成功的硬件信息
remove:刪除軟件或者硬件,內核調用,pdev指向匹配成功的硬件信息
driver:其中的name用於匹配

註冊方法:
platform_driver_register
卸載方法:
platform_driver_unregister

注意:probe和remove做什麼事,完全由驅動開發者來決定;

總結:probe一般做三件事:
1.通過pdev指針獲取硬件信息
有自定義的硬件信息
有resource類型的硬件信息
2.處理硬件信息
地址映射
申請GPIO資源
註冊中斷
配置GPIO爲輸出等
3.註冊硬件操作方法
註冊一個字符設備
註冊一個混雜設備

remove是probe函數的死對頭!

案例:利用platform_driver描述軟件信息
實驗步驟:
1.insmod led_dev.ko //先添加硬件
2.insmod led_drv.ko //在添加軟件
3.rmmod led_dev //先卸載硬件
4.rmmod led_drv //在卸載軟件

5.insmod led_drv.ko //先添加軟件
6.insmod led_dev.ko //再添加硬件
7.rmmod led_drv //先卸載軟件

8.rmmod led_dev //再卸載硬件        

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/io.h>
#include <linux/uaccess.h>

//定義LED開關命令
#define LED_ON  0x100001
#define LED_OFF 0x100002

struct led_resource {
    unsigned long phys_addr;
    int size;
    int pin
};

static unsigned long *gpiocon, *gpiodata;
static int g_pin;

static long led_ioctl(struct file *file,
                        unsigned int cmd,
                        unsigned long arg)
{
    //分配內核緩衝
    int kindex;
    //拷貝用戶數據到內核
    copy_from_user(&kindex, (int *)arg, sizeof(kindex));
    //解析命令
    switch(cmd) {
        case LED_ON:
                if (kindex == 1)
                    *gpiodata |= (1 << g_pin);
            break;
        case LED_OFF:
                if (kindex == 1)
                    *gpiodata &= ~(1 << g_pin);
            break;
    }
    printk("%s:配置寄存器=%#x, 數據寄存器=%#x\n", 
                        __func__, *gpiocon, *gpiodata);
    return 0;
}

//定義初始化硬件操作方法
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = led_ioctl
};

//定義初始化混雜設備對象
static struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "myled",
    .fops = &led_fops
};

//匹配成功調用,pdev指向匹配成功的硬件節點(led_dev)
static int led_probe(struct platform_device *pdev)
{
    //1.獲取自定義的硬件信息
    //pdata指向led_dev.c的led_info
    struct led_resource *pdata =
                                pdev->dev.platform_data;

    //2.處理硬件信息
    //將物理地址映射到內核空間
    gpiocon = ioremap(pdata->phys_addr, pdata->size);
    gpiodata = gpiocon + 1;
    g_pin = pdata->pin;

    //配置GPC0_3輸出口,輸出0
    *gpiocon &= ~(0xf << (g_pin *4));
    *gpiocon |= (1 << (g_pin * 4));
    *gpiodata &= ~(1 << g_pin);

    //3.註冊混雜設備
    misc_register(&led_misc);
    return 0; //成功返回0,失敗返回負值
}
//卸載軟件或者硬件調用,pdev指向匹配成功的硬件節點(led_dev)
static int led_remove(struct platform_device *pdev)
{
    //1.卸載混雜設備
    misc_deregister(&led_misc);
    //2.解除映射
    iounmap(gpiocon);
    return 0; //成功返回0,失敗返回負值
}

//定義初始化軟件節點
static struct platform_driver led_drv = {
    .driver = {
        .name = "myled", //用於匹配
    },
    .probe = led_probe,//匹配成功,內核調用
    .remove = led_remove //卸載軟件或者硬件,內核調用
};
static int led_drv_init(void)
{
    //添加軟件節點到內核
    //內核幫你遍歷dev鏈表,進行匹配
    //匹配成功,內核調用probe函數,把硬件節點的首地址傳遞probe函數
    platform_driver_register(&led_drv);
    return 0;
}
static void led_drv_exit(void)
{
    //卸載
    platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");


  內核提供了獲取resource描述的硬件信息函數:
struct resource *platform_get_resource(
struct platform_device *dev,
unsigned int type,
unsigned int num)


函數功能:
通過pdev指針(指向匹配成功的硬件節點)獲取resource類型的硬件信息
參數:
dev:就是pdev
type:資源的類型:
IORESOURCE_MEM或者IORESOURCE_IRQ
num:同類硬件資源的偏移量
返回值:返回硬件信息的首地址

例如:
static struct resource led_res[] = {
//描述寄存器地址信息
[0] = {
.start = 0xE0200060, //寄存器起始地址
.end = 0xE0200060 + 8 - 1, //寄存器結束地址
.flags = IORESOURCE_MEM //標記爲內存資源
},
//描述GPIO編號信息
[1] = {
.start = 3, //GPC0_3
.end = 3, //GPC0_3
.flags = IORESOURCE_IRQ //標記爲IO資源
},
//描述寄存器地址信息
[2] = {
.start = 0xE0200090, //寄存器起始地址
.end = 0xE0200090 + 8, //寄存器結束地址
.flags = IORESOURCE_MEM //標記爲內存資源
},
//描述GPIO編號信息
[3] = {
.start = 4, //GPC0_4
.end = 4, //GPC0_4
.flags = IORESOURCE_IRQ //標記爲IO資源
},
};

在probe函數中獲取:
int led_probe(pdev) {
struct resource *preg_res;
struct resource *preg1_res;
struct resource *ppin_res;
struct resource *ppin1_res;
preg_res =
platform_get_resource(pdev, IORESOURCE_MEM, 0);
preg1_res =
platform_get_resource(pdev, IORESOURCE_MEM, 1);
ppin_res =
platform_get_resource(pdev, IORESOURCE_IRQ, 0);
ppin1_res =
platform_get_resource(pdev, IORESOURCE_IRQ, 1);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章