驱动的platform分层分离概念及编程实例讲解

引言

分层就是将一个复杂的工作分成了4层, 分而做之,降低难度。每一层只专注于自己的事情, 系统已经将其中的核心层和事件处理层写好了,所以我们只需要来写硬件相关的驱动层代码即可。

分离是指把硬件相关的部分(驱动层)从纯软件部分(事件处理层)抽离出来,使我们只需要关注硬件相关部分代码的编写。具体来说就是在驱动层中使用platform机制(将所有设备挂接到一个虚拟的总线上,方便sysfs节点和设备电源的管理,使得驱动代码,具有更好的扩展性和跨平台性,就不会因为新的平台而再次编写驱动)把硬件相关的代码(固定的,如板子的网卡、中断地址)和驱动(会根据程序作变动,如点哪一个灯)分离开来,即要编写两个文件:dev.c和drv.c(platform设备和platform驱动)

image-20210717161931798

接下来我们来分析platform机制以及分离概念。

1、platform机制

bus-drv-dev模型

  • device设备:挂接在platform总线下的设备,属于platform_device结构体类型;

    • platform_device结构体定义如下

    • struct platform_device {
      	const char* name;   //设备名称,要与platform_driver的name一样,这样总线才能匹配成功
      	u32 id;   //id号,插入总线下相同name的设备编号(一个驱动可以有多个设备),如果只有一个设备填-1
          struct device dev;  //内嵌的具体的device结构体,其中成员platform_data,是个void *类型,可以给平台driver提供各种数据(比如:GPIO引脚等等)
      	u32 num_resources;      //资源数量,
        	struct resource  * resource;    //资源结构体,保存设备的信息
      };
      
    • resource结构体定义如下

    • struct resource {
               resource_size_t start;                    //起始资源,如果是地址的话,必须是物理地址
               resource_size_t end;                      //结束资源,如果是地址的话,必须是物理地址
               const char *name;                         //资源名
               unsigned long flags;                      //资源的标志
               //比如IORESOURCE_MEM,表示地址资源, IORESOURCE_IRQ表示中断引脚... ...
      
               struct resource *parent, *sibling, *child;   //资源拓扑指针父、兄、子,可以构成链表
      };
      
    • 涉及到的函数如下(在dev设备的入口出口函数中用到)

    • int platform_device_register(struct platform_device * pdev);//注册dev设备
      int platform_device_unregister(struct platform_device * pdev);//注销dev设备
      
  • driver驱动:也是挂接在platform总线下,与某类设备相应的驱动程序,属于platform_driver结构体类型;

    • platform_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 (*suspend_late)(struct platform_device *, pm_message_t state);
          int (*resume_early)(struct platform_device *);
          int (*resume)(struct platform_device *);           //唤醒
          struct device_driver driver;       //内嵌的driver,其中的name成员要等于设备的名称才能匹配
      };
      
    • 定义一个platform_driver(驱动)示例

    • struct platform_driver gpio_keys_device_driver = {
          .probe  = gpio_keys_probe,   //设备的检测,当匹配成功就会调用这个函数(需要自己编写)   
          .remove = __devexit_p(gpio_keys_remove), //删除设备(需要自己编写)
          .driver = {
              .name  = "gpio-keys",            //驱动名称,用来与设备名称匹配用的
          }
      };
      
    • 涉及到的函数如下

    • //位于init入口函数中,用来注册driver驱动
      int platform_driver_register(struct platform_driver *drv)
      {
         drv->driver.bus = &platform_bus_type; 	//(1)挂接到虚拟总线platform_bus_type上
         if (drv->probe)
               drv->driver.probe = platform_drv_probe;
         if (drv->remove)
              drv->driver.remove = platform_drv_remove;
         if (drv->shutdown)
              drv->driver.shutdown = platform_drv_shutdown;
         if (drv->suspend)
              drv->driver.suspend = platform_drv_suspend;
          if (drv->resume)
              drv->driver.resume = platform_drv_resume;
      
         return driver_register(&drv->driver);        //注册到driver目录下
      }
      
      
      int platform_driver_unregister(struct platform_driver *drv); //位于exit出口函数中,用来卸载驱动
      
      struct resource * platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num);
      //获取设备的某个资源,获取成功,则返回一个resource资源结构体
      //参数:
      // *dev :指向某个platform device设备
      // type:获取的资源类型
      // num: type资源下的第几个数组
      
  • platform总线:属于虚拟设备总线(全局变量),属于platform_bus_type类型,正是通过这个总线将设备和驱动联系了起来,属于Linux中bus的一种。

    • platform_match()匹配函数

    • static int platform_match(struct device * dev, struct device_driver * drv)
      {
      	/*找到所有的device设备*/
      	struct platform_device *pdev = container_of(dev, struct platform_device, dev);
      
      	return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); //找BUS_ID_SIZE次
      }
      
    • 通过查看/sys/bus/platform/目录,可以看到两个文件:devices和drivers。它们分别用来存放platform的设备和驱动。无论驱动或设备,只要有一方注册,就会调用platform_bus_type的**.match匹配函数来寻找对方。如果匹配成功,就调用driver驱动结构体里的.probe**函数来使总线将设备和驱动联系起来。

platform_bus_type的结构体定义如下所示(位于drivers/base):

struct bus_type platform_bus_type = {              
.name         = "platform",         //设备名称
.dev_attrs    = platform_dev_attrs, //设备属性、含获取sys文件名,该总线会放在/sys/bus下
.match        = platform_match,     //匹配设备和驱动,匹配成功就调用driver的.probe函数
.uevent       = platform_uevent,    //消息传递,比如热插拔操作
.suspend      = platform_suspend,   //电源管理的低功耗挂起
.suspend_late = platform_suspend_late,  
.resume_early = platform_resume_early,
.resume       = platform_resume,  //恢复
};

2、利用platform机制,编写LED驱动层

  • 环境:JZ2440开发板V3 + linux内核3.4.2 + arm-linux-gcc 4.3.2
  • 参考:韦东山第2期视频

2.1 创建设备代码

  • 创建led_dev.c文件:用来指定灯的引脚地址,当更换平台时只需要修改这个就行。
#include <linux/module.h>
#include <linux/version.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>

static struct resource led_resource[] = { //资源数组,用来保存设备的信息
    [0] = {
        .start = 0x56000050,          //led的寄存器GPFCON起始地址
        .end   = 0x56000050 + 8 - 1,  // led的寄存器GPFDAT结束地址
        .flags = IORESOURCE_MEM,      //表示地址资源
    },
    [1] = {
        .start =  5,                  //表示GPF第几个引脚开始
        .end   = 5,                   //结束引脚
        .flags = IORESOURCE_IRQ,      //表示中断资源    
    } 
};

//释放函数
static void led_release(struct device * dev)
{
 //释放函数,必须向内核提供一个release函数, 否则卸载时,内核找不到该函数会报错。   
}


/*分配、设置一个LED设备 */
static struct platform_device led_dev = {
    .name	= "myled",	//对应的platform_driver驱动的名字
    .id     = -1,       //表示只有一个设备
    .num_resources	= ARRAY_SIZE(led_resource),	//资源数量,ARRAY_SIZE()函数:获取数量
    .resource	= led_resource,	//资源数组led_resource
    .dev = {	
    	.release = led_release,
    },
};

//入口函数,注册dev设备
static int led_dev_init(void)
{
  platform_device_register(&led_dev);
  return 0;
}

//出口函数,注销dev设备
static void led_dev_exit(void) 
{
  platform_device_unregister(&led_dev); 
}
module_init(led_dev_init);   //修饰入口函数
module_exit(led_dev_exit);  //修饰出口函数
MODULE_LICENSE("GPL");   //声明函数

2.2 创建驱动代码

  • 创建led_drv.c文件:用来初始化灯以及如何控制灯的逻辑,当更换控制逻辑时,只需要修改这个就行。

    • 1. 分配、设置一个platform_driver结构体

    • #include <linux/module.h>
      #include <linux/version.h>
      
      #include <linux/init.h>
      #include <linux/fs.h>
      #include <linux/interrupt.h>
      #include <linux/irq.h>
      #include <linux/sched.h>
      #include <linux/pm.h>
      #include <linux/sysctl.h>
      #include <linux/proc_fs.h>
      #include <linux/delay.h>
      #include <linux/platform_device.h>
      
      #include <linux/input.h>
      #include <asm/uaccess.h>
      #include <asm/irq.h>
      #include <asm/io.h>
           
      /* 1)先写要注册的led驱动:platform_driver结构体*/
      
      /*函数声明*/
      static  int led_remove(struct platform_device *led_dev);
      static  int led_probe(struct platform_device *led_dev);
      
      struct platform_driver led_drv = {
          .probe	= led_probe,        //当与设备匹配,则调用该函数
          .remove = led_remove,       //删除设备
          .driver = {
              .name     = "myled",    //与设备名称一样
          }
      };
      
    • 编写file_operations 结构体以及成员函数(.open、.write)

    • static struct class *cls;                //类,用来注册,和注销
      static volatile unsigned long *gpio_con; //被file_operations的.open函数用
      static volatile unsigned long *gpio_dat; //被file_operations的.write函数用
      static int pin;                          //LED位于的引脚值
      
      static int led_open(struct inode *inode, struct file  *file)
       {
             *gpio_con &= ~(0x03<<(pin*2));
             *gpio_con |= (0x01<<(pin*2));   
             return 0;
       } 
      
      static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
      {
             char val=0;
             if(count!=1)
                 return -EINVAL;
             copy_from_user(&val,buf,count);      //从用户(应用层)拷贝数据       
      
             if(val)      //开灯
             {
             		*gpio_dat &= ~(0x1<<pin);
             }
             else
             {
             		*gpio_dat |= (0x1<<pin);
             }   
             return 0 ;
      }
      
      
      static struct  file_operations led_fops= {
          .owner  =   THIS_MODULE,     //被使用时阻止模块被卸载
          .open   =   led_open,     
          .write  =   led_write,   
        };
      
    • 编写.probe函数

    • /* 当驱动和设备都insmod加载后,然后bus总线会匹配成功,就进入.probe函数.
       * 里面的内容没有固定要求,你可以根据需要自行添加。
       */
      static int led_probe(struct platform_device *pdev)
      {
             printk("enter probe\n");	//调试用
      
             /* 1.根据platform_device的资源进行ioremap */
             struct resource  *res;
             res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取LED寄存器地址
             gpio_con = ioremap(res->start, res->end - res->start + 1); //获取虚拟地址
             gpio_dat = gpio_con + 1;
      
             res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);   //获取LED引脚值
             pin = res->start;
      
             /* 2.注册字符设备驱动程序 */
             major = register_chrdev(0, "myled", &led_fops);   //赋入file_operations结构体
             cls = class_create(THIS_MODULE, "myled");
             device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
             return 0;
      }
      
    • 编写.remove函数

    • /* 如果驱动与设备已联系起来,当卸载驱动时,就会调用.remove函数卸载设备 */
      static int led_remove(struct platform_device *pdev)
      {
             printk("enter remove\n"); //调试用
          
             /* 1.卸载字符设备驱动程序 */
             device_destroy(cls, MKDEV(major, 0));
             class_destroy(cls);
             unregister_chrdev(major, "myled");
      
             iounmap(gpio_con);       //注销虚拟地址
             return 0;
      }
      
    • 编写drv的入口出口函数

    • static int led_drv_init(void)	//入口函数,注册驱动
      {
      	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");
      

2.3 创建Make file 文件

  • KERN_DIR = /home/leon/linux-3.4.2
    
    all:
    	make -C $(KERN_DIR) M=`pwd` modules 
    
    clean:
    	make -C $(KERN_DIR) M=`pwd` modules clean
    	rm -rf modules.order
    
    obj-m	+= led_drv.o
    obj-m	+= led_dev.o
    

2.4 创建测试文件

当用户输入led_test on时,LED点亮;输入led_test off时,LED熄灭。

  • #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    /* led_test on
     * led_test off
    */
    int main(int argc, char **argv)
    {
    	int fd;
    	char val = 1;
    	fd = open("/dev/led", O_RDWR);
    	if (fd < 0)
    	{
    		printf("can't open!\n");
    	}
    	if (argc != 2)
    	{
    		printf("Usage :\n");
    		printf("%s <on|off>\n", argv[0]);
    		return 0;
    	}
    
    	if (strcmp(argv[1], "on") == 0)
    	{
    		val  = 1;
    	}
    	else
    	{
    		val = 0;
    	}
    	
    	write(fd, &val, 1);	//将从变量val地址起始处的4个字节传给驱动
    	return 0;
    }
    

2.5 实验结果

image-20210717234851686

3、小结:

分层分离驱动

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值