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);
}

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