驱动学习笔记10 内核platform机制

 4.linux内核platform机制


4.1.案例:硬件工程师将某个LED灯的GPIO引脚由GPIOC12


          修改为GPIOE4,来完成此驱动,采用ioremap操作寄存器实现
    分析:只要基于GPIOC12+ioremap实现的驱动基础上做修改即可
         于是乎将昨天ioremap驱动一点点改改.
    结论:如果基于之前的驱动进行修改,发现此驱动代码改动的位置
          比较多,如果有一个地方没有照顾到,硬件操作必然有问题
          也就是昨天的ioremap驱动代码的可移植性非常差劲
          并且发现此驱动只改纯硬件信息(物理起始地址,GPIO编号等)
          而软件代码无需改动(if...else,switch...case等)
    解决:势必想到#define来提高驱动代码的可移植性
          定义一个头文件,将可变的纯硬件信息以#define宏的形式
          表现出来,将来驱动工程师
          只需维护头文件即可,驱动代码一旦写好,无需改动!
          问:如果用#define来描述纯硬件信息,如何用#define
              来区分硬件信息的类型呢?
              例如:
              #define GPIO_1 (0xC001C000)
              #define GPIO_2 (12)
              如何知道GPIO_1是地址呢?GPIO_2是GPIO编号呢
              也许认为GPIO_2是地址呢?GPIO_1是GPIO编号呢
          答:无法知道,暴露#define缺陷是描述硬件信息时,描述的
              属性是单一的
              linux内核为了解决此问题,采用数据结构来描述纯硬件信息
              即提高了代码的可移植性,又能给硬件信息添加多种属性
              例如:
              //自行设计:描述纯硬件信息的数据结构
              struct resource {
                    unsigned long start; //起始信息
                    unsigned long end; //结束信息
                    unsigned long flags; //类型标识
                            IORESOURCE_ADDRESS:地址信息
                            IORESOURCE_GPIO:GPIO编号信息
              };
              
              //定义初始化对象描述物理地址和GPIO编号信息
              struct resource led_res[] = {
                //描述寄存器物理地址信息
                {
                    .start = 0xC001C000, //起始地址
                    .end = 0xC001C000 + 0x24, //结束地址
                    .flags = IORESOURCE_ADDRESS //地址类
                },
                //描述GPIO信息
                {
                    .start = 12,
                    .end = 12,
                    .flags = IORESOURCE_GPIO //GPIO类型
                }
              };

4.2.问:最终linux内核如何提高驱动的可移植性呢?具体如何实现呢


    答:采用分离思想
        也就是将一个驱动程序中纯硬件信息和纯软件信息进行分开
        将来驱动工程师只需维护纯硬件信息即可,纯软件代码一旦
        写好,将来无需做修改(甚至源文件都无需打开看)
        问:linux内核分离思想又是如何实现呢?
        答:采用platform机制实现,适用于所有的硬件

4.3.linux内核platform机制实现原理


    参见:ESD_LDD_DAY09.pptx,P28开始
    面试时:边说边画图

4.4.结论:如果采用platform机制实现驱动程序,只需:


    驱动工程师只需维护两个数据结构:
    struct platform_device
    struct platform_driver
    只需做:定义初始化和注册硬件节点和软件节点
    
    内核会帮你做四件事:
    1.帮你遍历dev或者drv链表
    2.帮你调用总线提供的match进行匹配
    3.如果匹配成功,内核还会帮你调用软件节点的probe函数
    4.如果匹配成功,内核还会给probe函数传递匹配成功的硬件节点首地址

4.5.详解struct platform_device


    struct platform_device {
        const char    * name;
        int        id;
        struct device    dev;
        u32        num_resources;
        struct resource    * resource;
    };    
    功能:描述dev链表上每个纯硬件节点属性
    name:指定一个硬件节点名称,用于匹配,必须初始化
    id:指定一个硬件节点编号,如果dev链表上只有一个名称为name
        的硬件节点,id=-1,如果dev链表上有多个同名的硬件节点
        通过id进行标识:id=0,1,2,....
    dev:只需关注其中的void *platform_data字段
        用于装载驱动工程师自定义的纯硬件信息
        struct device {
            void *platform_data;
        };

自定义硬件信息


        用法如下,例如:
        //驱动工程师声明描述LED硬件信息数据结构:
        struct led_resource {
            unsigned long phy_baseaddr; //起始物理地址
            unsigned long size; //大小
            unsigned long gpio; //GPIO编号
        };
        
        //驱动工程师定义初始化LED硬件信息对象(类似白菜)
        struct led_resource led = {
            .pyh_baseaddr = 0xC001C000,
            .size = 0x24,
            .gpio = 12
        };
        
        //定义初始化硬件节点对象(类似卡车)
        struct platform_device led_dev = {
            .name = "tarena", //用于匹配
            .id = -1,
            .dev = {
                .platform_data = &led //将自定义的硬件信息添加到节点
                                      //类似把白菜放到卡车上
            }
        };

resource定义硬件信息

    num_resources:用于指示用resource描述的硬件信息的个数
                   因为别人无法通过resource指针来获取
                   resource描述硬件信息个数
                   即:num_resources = ARRAY_SIZE(led_res)
                   切记:num_resources只能和resource一起使用
                   
    resource:用于装载resource描述的硬件信息
              此数据结构struct resource是内核声明的
              供驱动使用,描述硬件信息:
              struct resource {
                      unsigned long start; //起始信息
                    unsigned long end; //结束信息
                    unsigned long flags; //类型标识
              };
              功能:内核提供的描述硬件信息的数据结构
                    类似自己写的struct led_resource 
              start:起始信息
              end:结束信息 
              flags:资源类型,宏:
                    IORESOURCE_MEM:地址类信息 
                    IORESOURCE_IRQ:GPIO编号或者中断号信息
              
              用法,以LED为例:
              //定义初始化LED硬件信息对象:(类似白菜)
              struct resource led_res[] = {
                //描述寄存器物理地址信息
                {
                    .start = 0xC001C000, //起始地址
                    .end = 0xC001C000 + 0x24, //结束地址
                    .flags = IORESOURCE_MEM //地址类
                },
                //描述GPIO信息
                {
                    .start = 12,
                    .end = 12,
                    .flags = IORESOURCE_IRQ //GPIO编号信息
                }
              };
            
            //定义初始化硬件节点对象(类似卡车)
            struct platform_device led_dev = {
                .name = "tarena", //用于匹配
                .id = -1,
                .resource = led_res, //装载resource描述的硬件信息
                                     //类似把白菜放到卡车上
                .num_resources = ARRAY_SIZE(led_res) //指定硬件信息的个数
            };
            
        总结:装载硬件信息的方法有两种:
        1.自定义描述
        2.resource描述
        两种方法可以同时使用,也可以单独使用
    
    配套函数:
    int platform_device_register(&硬件节点对象);
    功能:向内核dev链表添加硬件节点
          此时内核会帮你遍历,匹配,调用probe,传递参数
          
    void platform_device_unregister(&硬件节点对象);
    功能:从内核dev链表删除硬件节点对象

案例:利用platform机制优化LED驱动
      目前先编写纯硬件信息
上位机执行:
mkdir /opt/drivers/day10/1.0
cd /opt/drivers/day10/1.0
vim led_dev.c //描述自定义的硬件信息

cp /opt/drivers/day10/1.0 /opt/drivers/day10/2.0
cd /opt/drivers/day10/2.0

vim led_dev.c //resource描述的硬件信息  

 

 

4.6.详解struct platform_driver


    struct platform_driver {
        struct device_driver driver;
        int (*probe)(struct platform_device *pdev);
        int (*remove)(struct platform_device *pdev);
    };    
    功能:描述软件节点信息
    driver:只关注其中的char *name字段,用于匹配 
           struct device_driver {
                const char *name;
           }
    probe:硬件节点和软件节点匹配成功,内核调用
          形参pdev指向匹配成功的硬件节点(&led_dev)
    remove:删除软件节点或者硬件节点,内核调用此函数
          形参pdev指向匹配成功的硬件节点(&led_dev)
          只调用一次
    
    配套函数: 
    //向drv链表添加软件节点
    //内核帮你做遍历,匹配,调用probe,传递参数 
    int platform_driver_register(&软件节点对象);
    
    //从drv链表删除软件节点
    void platform_driver_unregister(&软件节点对象);
      
案例:利用platform机制优化LED驱动
           现在添加软件节点的代码
上位机执行:
cd /opt/drivers/day10/1.0
vim led_drv.c //描述软件信息,自定义硬件信息
vim led_drv.c           
make


cd /opt/drivers/day10/2.0
vim led_drv.c //描述软件信息,resource描述硬件信息
vim led_drv.c

框架是一样的,resouce描述与自定义描述的不同在与probe函数内部不同,此处只是给出框架


make

下位机测试:
insmod led_dev.ko
insmod led_drv.ko 
rmmod led_dev
rmmod led_drv

 为了去除警告

 无警告

 先安装哪个都可以


4.7.probe函数所做的工作如下:
    1.通过形参pdev获取纯硬件信息
      1.获取自定义的硬件信息代码:
        struct led_resource *pdata = pdev->dev.platform_data;
        //结果:pdata=&led(位于led_dev.c)
        
      2.获取resource描述的硬件信息,用以下函数来获取:
        struct resource *platform_get_resource(
                        struct platform_device *pdev, 
                        unsigned long flags,
                        int index);
        pdev:传递匹配成功的硬件节点首地址
        flags:传递要获取的硬件信息的类型
        index:传递要获取的硬件信息类型的偏移量(坑)
        返回值:返回获取的resource硬件信息的地址
        
    2.处理硬件信息
      该地址映射的映射
      该申请的申请
      该初始化的初始化
    3.给用户提供操作接口
      注册字符设备或者混杂设备都行
    注意:2和3都是在之前驱动的入口函数完成,现在迁移到probe函数中
    remove跟probe死对头!

  1 //led_drv.c:描述软件节点的信息//自定义硬件设备时
  2 #include <linux/init.h>
  3 #include <linux/module.h>
  4 #include <linux/platform_device.h>
  5 #include <linux/fs.h>
  6 #include <linux/miscdevice.h>
  7 #include <linux/io.h>
  8 #include <linux/uaccess.h>
  9 
 10 //声明描述LED硬件信息的结构体类型
 11 struct led_resource {
 12     unsigned long gpio_phy_address; //GPIO寄存器开始物理地址
 13     unsigned long size;             //GPIO寄存器物理地址空间大小
 14     int gpio;                       //GPIO编号
 15 };
 16 
 17 static void* gpiobase;
 18 static unsigned long *gpiocaltfn0, *gpiocoutenb, *gpiocout;
 19 static int pin;
 20 
 21 #define LED_ON   0x100001
 22 #define LED_OFF  0x100002
 23 static long led_ioctl(struct file *file, unsigned int cmd, unsigned long buf) {
 24     int kindex;
 25     copy_from_user(&kindex, (int *)buf, sizeof(kindex));
 26     switch(cmd) {
 27         case LED_ON:
 28             if(kindex == 1)
 29                 *gpiocout &= ~ (1 << pin);
 30             break;
 31         case LED_OFF:
 32             if(kindex == 1)
 33                 *gpiocout |= (1 << pin);
 34             break;
 35     }
 36     printk("ALT:%#X, OUTENB:%#X, OUT:%#X\n", *gpiocaltfn0, *gpiocoutenb, *gpiocout);
 37     return 0;
 38 }
 39 
 40 static struct file_operations led_fops = {
 41     .unlocked_ioctl = led_ioctl
 42 };
 43 
 44 static struct miscdevice led_misc = {
 45     .minor = MISC_DYNAMIC_MINOR,
 46     .name = "myled",
 47     .fops = &led_fops
 48 };
 49 
 50 //硬件节点和软件节点匹配成功,内核调用此函数
 51 //形参pdev指向匹配成功的硬件节点,就是:pdev = &led_dev(位于led_dev.c)
 52 static int led_probe(struct platform_device *pdev){
 53     //1.通过pdev获取自定义的硬件信息
 54     //目标:pdata = &led(位于led_dev.c)
 55     struct led_resource *pdata = pdev->dev.platform_data;
 56     pin = pdata->gpio; // pin = 12
 57     //2.处理获取的自定义硬件信息,各种该
 58     //将寄存器的物理地址映射到内核虚拟地址上
 59     gpiobase = ioremap(pdata->gpio_phy_address, pdata->size);
 60     //换算各个寄存器的内核虚拟地址
 61     gpiocout = (unsigned long*)(gpiobase + 0x00);
 62     gpiocoutenb = (unsigned long*)(gpiobase + 0x04);
 63     gpiocaltfn0 = (unsigned long*)(gpiobase + 0x20);
 64     //四选一,二选一,默认输出1
 65     *gpiocaltfn0 &= ~(3 << (2*pin));
 66     *gpiocaltfn0 |= (1 << (2*pin));
 67     *gpiocoutenb |= (1 << pin);
 68     *gpiocout |= (1 << pin);
 69     //3.注册混杂设备
 70     misc_register(&led_misc);
 71     printk("%s\n", __func__);
 72     return 0;//成功返回0,失败返回负值
 73     }
 74 
 75 //删除硬件节点或者软件节点,内核调用此函数
 76 //形参pdev指向匹配成功的硬件节点,就是:pdev = &led_dev(位于led_dev.c)
 77 static int led_remove(struct platform_device *pdev){
 78     misc_deregister(&led_misc);
 79     *gpiocout |= (1 << pin);
 80     iounmap(gpiobase);
 81     printk("%s\n", __func__);
 82     return 0;//成功返回0,失败返回负值
 83 }
 84 
 85 //定义初始化软件节点对象
 86 static struct platform_driver led_drv = {
 87     .driver = {
 88         .name = "tarena" //用于匹配
 89     },
 90     .probe = led_probe, //硬件和软件匹配成功,内核调用此函数
 91     .remove = led_remove //删除硬件或者软件,内核调用此函数
 92 };
 93 
 94 static int led_drv_init(void) {
 95     //向内核drv链表注册软件节点,内核会帮你遍历,匹配,调用probe,传递参数
 96     platform_driver_register(&led_drv);
 97     return 0;
 98 }
 99 
100 static void led_drv_exit(void) {
101     //从内核drv链表上卸载软件节点
102     platform_driver_unregister(&led_drv);
103 
104 }
105 
106 module_init(led_drv_init);
107 module_exit(led_drv_exit);
108 MODULE_LICENSE("GPL");

resource描述硬件信息时

总结:

定义初始化硬件节点led_dev

resouce描述硬件信息                                               自定义描述硬件信息

static struct resource led_info[ ] =                             sturct  led_resource

                                                                                  static struct led_resouce led

static void led_release(struct device *dev)                static void led_release(struct device *dev)      

static struct platform_device led_dev={                    static sturct platform_device led_lev={

         .name                                                                       .name

         .id                                                                             .id

         .resource硬件信息

         .num_resources硬件信息个数

         .dev={                                                                       .dev={

                                                                                                 .platform_data = &led,

                .release=led_release去除警告                                 .release=led_release去除警告

         }                                                                                   }

}                                                                                      }

static int led_dev_init(void){

platform_device_register(&led_dev);

return 0;

}

static void led_dev_exit(void){

platform_device_unregister(&led_dev);

}         .

module_init(led_dev_init);

module_exit(led_dev_exit);

MODULE_LICENSE("GPL");

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值