1.1设备树的引进与体验——字符设备驱动程序的三种写法

怎么写一个LED驱动程序?

  1. 看原理图,确定怎么去操作;
  2. 写驱动程序;
  3. 写测试程序;

假设2440的一个GPIO引脚,引脚上连接一个小灯,一个限流电阻,还有3.3V电源,那么要怎么让小灯亮。

从上面的描述我们知道,让引脚输出低电平就可以点亮小灯,要获得这个信息就要看原理图。

对于点灯来说,看原理图就是:

  1. 确定引脚;
  2. 看芯片手册确定怎么操作这个引脚;

之后就要写驱动程序了。

写驱动程序的人需要看原理图看芯片手册,但是对于写应用程序的人,他们不知道也不关心怎么看原理图和芯片手册,他们只需要怎么去写应用程序,怎么去实现业务需求。

驱动程序就是将应用底层分离开,起一个封装作用。让写应用程序的人可以专心负责上层需求,写驱动程序的人则负责底层实现。

在应用程序中,接口基本都是固定的,想要操作一个设备首先要open,然后有写的read函数,读的write函数,i/o操作的ioctl函数。

同样,在驱动程序中,也有与之一一对应的驱动函数。这些驱动函数将应用程序和底层硬件分离开,应用程序通过固定的接口调用底层驱动,而不是直接操作硬件。

那要怎么实现他们的分离呢?

  1. 分配一个结构体file_operations;
  2. 设置该结构体:.open = led_open,
                             .write  = led_write,
    这样调用open就相当于调用led_open函数;
  3. 注册(告诉内核);
  4. 入口函数:调用register_chrdev;
  5. 出口函数:调用unregister_chrdev;

所谓注册,就是告诉内核这个驱动程序的相关信息,把这个结构体放入一个数组里面

内核中有许多数组,这个数组里面存放着许多驱动程序,register_chrdev需要带一个参数叫主设备号,这个主设备号就是存放在数组的哪个元素,如果为0则表示由系统自动选一个空闲的位置存放。

register_chrdev会传入三个参数,分别是主设备号结构体设备名字,其中设备名字不重要

通常,在led_open中,把LED引脚配置为输出引脚(设置功能);

在led_write中,根据APP传入的值设置引脚状态(设置状态);

问:在驱动中如何指定引脚?看原理图可以知道引脚是哪个,但是怎么告诉驱动程序呢?

答:有三种方法:

  1. 传统方法:在代码中写死;
  2. 总线设备驱动模型,把驱动一分为2:
    a.led_drv.c:实现分配和注册结构体;
    b.led_dev.c:指定引脚;
  3. 使用设备树指明引脚:
    a.led_drv.c:同样是实现分配和注册结构体;
    b.jz2440.dts:指定引脚;

 这三种方法都有一个相同点,就是配置结构体,不同的是怎么去指定引脚。也就是,驱动写法:核心不变,差别在于:如何指定硬件资源。

问:那么这三种方法有什么优缺点呢?

答:假设使用同一款芯片做了两款产品,一款是TV,另一款是Camera。

这两款产品都有状态灯,不同的是TV使用的是pin1,Camera使用的是pin2,他们的连接电路相同,都是低电平点亮,高电平熄灭,不同的只是控制的引脚。

现在要写程序:

1.传统方法

首先写TV的,要写一个led_drv.c,然后:

  1. 分配一个file_operations结构体;
  2. 设置结构体,.open = led_open;(配置pin1为输出引脚,把引脚写死了)
                          .write = led_write;(根据APP传入的值设置pin1状态)
  3. 注册;
  4. 入口;
  5. 出口;

写完TV之后要写camera的,可以将上面的代码复制一遍,然后在此基础上进行修改,只要把原来的pin1改成pin2就可以了;

优点:简单;

缺点:不易扩展,每次换一个板子都要重新写代码重新编译,即使改动很小也要重做一个版本,工作量较大;

2.总线设备驱动模型

这两款产品都是使用相同的芯片做的,也就是说pin1和pin2的操作是类似的,可以将驱动分为两部分:led_dev.cled_drv.c,它们都挂在platform总线上。

led_dev.c:

指定资源,分配/设置/注册 platform_device,platform_device里面有各种资源,其中.resource 指明引脚,还有一个.name 指明名字。

创建两个类似的platform_device,TV的platform_device中.resource 为pin1,TV的platform_device中.resource 为pin2,在led_drv.c中决定使用哪个platform_device。

led_drv.c:

分配/设置/注册 platform_driver 结构体,这个结构体里面有.probe,.driver(.driver里面包含一个成员.name)。

当内核中发现有一个platform_device和一个platform_driver 的.name相同时,.proce函数就会被调用,该函数会做:

  1. 分配一个file_operations结构体;
  2. 设置结构体,.open = led_open;(配置对应引脚为输出引脚,来自平台设备,不是写死的了)
                          .write = led_write;(根据APP传入的值设置引脚状态)
  3. 注册;
  4. 入口;
  5. 出口;

就和第一种方法类似,但是引脚不是写死的了,而是来自平台设备。

优点:易扩展(对于TV和Camera,led_drv.c保持不变,唯一要修改的是led_dev.c中的资源);

缺点:稍复杂;容易产生冗余代码(TV和Camera需要两个platform_device结构体,如果有更多的改动就要有更多的platform_device,而且这些结构体都是以.c文件的形式出现的,会占用多余的空间);每换一个引脚都需要重新编译led_dev.c,即每次改动都要重新编译一个版本,增加了工作量;

由于这些缺点,linus说这种方法是垃圾。。。

3.设备树

总线设备模型的方法在于,通过c文件来指定使用的资源,这样每次改动都需要编译。

那么有没有什么其他的方法来指定使用的资源呢?可以使用设备树的方法来指定资源。

使用设备树来写程序时,同样需要两部分。

led_drv.c:

分配/设置/注册 platform_driver,这部分同总线设备模型是完全一样的。

.dts文件

内核根据该文件来分配/设置/注册platform_device。当你需要更改设置时,只需要修改.dts文件即可。

该文件最终会被编译为.dtb文件。

使用设备树,在修改配置时就不需要重新编译内核,重新编译C文件了,只要给它一个.dtb文件就可以。

优点:易扩展;无冗余代码;不需要重新编译内核/驱动,只需要提供不一样的设备树文件即可;

缺点:稍复杂;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值