1,linux驱动程序介绍
1.1 linux驱动程序在系统中的角
Linux分为用户态和内核态,一般应用程序是在用户态执行,他们通过一系列的系统调用同内核态进行交互。驱动程序是内核与硬件的接口,它把系统调用映射到具体设备对于实际硬件的特定操作上,关系如下图所
通过这种方法,应用程序就可以像操作普通文件一样操作硬件设备,用户程序只需要关心这个抽象出来的文件,而一切同硬件打交道的工作都交给了驱动程序。
1.2 linux驱动的类型
linux系统将设备分为3类:字符设备、块设备、网络设备,摄像机常用的外围设备(如I2C,串口,SPI,GPIO,PWM等)均属于字符设备,tf卡驱动属于块设备,网卡相关驱动属于网络设备。
字符设备与块设备的区别:1、字符设备是面向流的,最小访问单位是字节;而块设备是面向块的,最小访问单位是512字节或2的更高次幂。2、字符设备只能顺序按字节访问,而块设备可随机访问。3、块设备上可容纳文件系统,访问形式上,字符设备通过设备节点访问,而块设备虽然也可通过设备节点访问,但一般是通过文件系统来访问数据的。
而网络设备没有设备节点是因为网络设备是面向报文的,很难实现相关read、write等文件读写函数。所以驱动的实现也与字符设备和块设备不同。
1.3 linux驱动的一些重要概念
设备号
把所有设备都当作文件,为了管理这些设备,系统为它们各自都编了号,而每个又分为和。主设备号用来区分不同类型的设备,而次设备号用来区分同一类型内的多个设备(及其设备分区)。
在建立字符驱动时需要做的第一件事是获取设备号。设备号的分配方式一般有2种,静态分配动态分配,静态分配设备号,就是驱动程序开发者,静态地指定一个设备号。对于一部分常用的设备,linux内核开发者已经为其分配了设备号。这些设备号可以在内核源码文件中到。如果只有开发者自己使用这些设备驱动程序,那么其可以选择一个尚未使用的设备号。当添加新硬件时,很可能造成设备号冲突,影响设备的使用。为了解决手动分配设备号存在冲突的问题,内核开发者提出动态分配设备号的方法。使用该方法驱动程序在加载的时候,通过linux内核提供的专门函数动态获取设备号。
int alloc_chrdev_region(dev_t *dev,  unsigned baseminor,  unsigned count,  const char *name)
设备节点
    linux系统中对所有设备的访问都是基于文件的形式。对于每一种设备,加载驱动程序的时候都会在/dev目录下创建一个文件,这个文件就是设备节点。对于每一个设备节点,在实际运行时, linux系统通过VFS(虚拟文件系统)来完成文件的各种系统调用与具体驱动程序函数之间的映射。
    设备节点可以通过mknod命令在系统启动的时候手动创建,也可以通过udev自动创建在驱动用加入对udev的支持主要做的就是:在驱动初始化的代码里调用内核提供的API内核注册驱动信息
class_create       :    创建class 
    class_device_create    :    创建device
驱动加载会在/sys/class目录下生成该模块相关的信息,同时用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻对应的类从而创建设备节点。
驱动初始化时,需要完成以下工作:
  1,通过alloc_chrdev_region()及相关函数分配主/次设备号。
  2,使用device_create()创建/dev和/sys节点。
  3,使用cdev_init()和cdev_add()将自身注册为字符驱动程序。
混杂设备
sdk
考虑到有的系统包含很多简单字符设备驱动,单独为这些设备分配设备号比较浪费资源,同时工作量也很大,linux系统针对这些情况推出了一种叫混杂设备模型的驱动框架(miscellaneous)。混杂设备主要2个特征:1)所有的misc设备被分配同一个主设备号MISC_MAJOR(10),但是可以选择一个单独的次设备号。如果一个字符设备驱动要驱动多个设备,那么它就不应该用misc设备来实现;2)混杂设备驱动初始化时,只需要执行简单的一个注册函数,即可自动完成设备号分配设备节点创建,向内核注册等工作,极大的简化了驱动初始化流程。
硬件IO操作
IO端口与IO内存
x86体系和ARM体系的寻址方式是有差别的:
在x86下,为了能够满足CPU高速地运行,内存与CPU之间通过北桥相连并通过地址方式访问,而外设通过南桥与CPU相连并通过端口访问。
因为这两种访问方式的不同,linux分出了两种不同的访问操作:
以地址方式访问硬件——使用IO内存操作。
以端口方式访问硬件——使用IO端口操作。
在ARM下也实现了类似的操作,通过两条不同的总线(AHB BUS和APB BUS)来连接不同访问速度的外设。但是它与x86不同,无论是内存还是外设,ARM都是通过地址访问。
在ARM下,访问寄存器就像访问内存一样——从指定的寄存器地址获取数据,修改。所以,ARM下一般是使用IO内存的操作。但这并不是说IO端口的操作在ARM下不能用,它们的代码差不多,只是没有使用的必要,下面也将介绍IO内存操作。
如何使用IO内存获得硬件的地址
我们不能在linux使用实际的物理地址,要对指定的物理地址进行操作,必须要先将物理地址与虚拟地址对应,通过虚拟地址访问。于是有了以下的物理地址映射函数:
#include
void *ioremap(unsigned long phys_addr, unsigned long size);
函数传入两个参数,需要访问的物理内存(寄存器)的首地址phys_addr和这段内存区域的大小size,返回与该段物理地址对应的虚拟地址。这段地址可以多次被映射,当然,每次映射的虚拟地址也不一样。
phys = 0x200f0000; //1、指定物理地址
virt = (unsigned long)ioremap(phys, 0x0c); //2、通过ioremap获得对应的虚拟地址
//0x0c表示只要12字节的大小
GPECON = (unsigned long *)(virt + 0x40); //3、指定需要操作的寄存器的地址
*GPECON &= ~(3 << 24); //配置GPE12为输出端口
为了实现更好的移植性,上面的程序就有缺陷了。内核建议,尽量使用内核提供的内存访问接口:
#include
//从内存读取数据,返回值是指定内存地址中的值
unsigned int ioread8(void *addr)
unsigned int ioread16(void *addr)
unsigned int ioread32(void *addr)
//往指定内存地址写入数据
void iowrite8(u8 value, void *addr)
void iowrite16(u16 value, void *addr)
void iowrite32(u32 value, void *addr)
参照硬件说明,通过对寄存器的控制实现操作硬件
硬件说明文档
驱动的调试
Linux设备模型
    linux系统作为开源的下系统,支持世界上大部分的硬件,导致Linux内核看上去非常臃肿、杂乱、不易维护。为了优化,linux系统从2.6版本开始提出了全新的设备模型(也称作Driver Model)概念。设备模型将硬件设备归纳、分类,然后抽象出一套标准的数据结构和接口。驱动的开发,就简化为对内核所规定的数据结构的填充和实现。
1)Linux设备模型中包含四个重要概念:Bus、Class、Device、 Device Driver。
Bus(总线):Bus(总线)是一类特殊的设备,它是连接处理器和其它设备之间的通道(channel)。在设备模型中,所有的设备都通过总线相连,甚至是那些内部的虚拟平台总线(platform bus
)
.
Class(类):在Linux设备模型中,Class的概念非常类似面向对象程序设计中的Class(类),它主要是集合具有相似功能或属性的设备,这样就可以抽象出一套可以在多个设备之间共用的数据结构和接口函数。因而从属于相同Class的设备的驱动程序,就不再需要重复定义这些公共资源,直接从Class中继承即可。
Device(设备):抽象系统中所有的硬件设备,描述它的名字、属性、从属的Bus、从属的Class等信息。
Device Driver(驱动):Linux设备模型用Driver抽象硬件设备的驱动程序,它包含设备初始化、电源管理相关的接口实现。而Linux内核中的驱动开发,基本都围绕该抽象进行(实现所规定的接口函数)。
2)Linux设备模型的运行机制
配对(Match):当总线上添加了新设备或者新驱动函数的时候,内核会调用一次或者多次这个函数。举例,如果我现在添加了一个新的驱动函数,内核就会调用所属总线的match函
数,配对总线上所有的设备,如果驱动能够处理其中一个设备,函数返回0,告诉内核配对成功。
探测(probe) 当配对(match)成功后,内核就会执行device_driver中的probe回调函数,而该函数就是所有driver的入口,可以执行诸如硬件设备初始化、字符设备注册、设备文件操作ops注册等动作。所以说,真正的驱动函数入口是在probe函数中。
载(remove) 当该驱动函数或者驱动函数正在操作的设备被移除时,内核会调用驱动函数中的remove函数调用,进行一些设备卸载相应的操作。
platform设备
针对一些可以通过CPU直接寻址的设备(比如集成在嵌入式SOC芯片上的控制器,CPU可以直接访问其寄存器),linux内核在设备模型的基础上(device和device_driver),对这些设备进行了更进一步的封装,抽象出paltform bus、platform device和platform driver,以便驱动开发人员可以方便的开发这类设备的驱动。
paltform设备对嵌入式Linux驱动开发是非常重要的,因为我们编写的大多数设备驱动,都是为了驱动plaftom设备。
由图片可知,Platform设备在内核中的实现主要包括三个部分:
Platform Bus,基于底层bus模块,抽象出一个虚拟的Platform bus,用于挂载Platform设备; 

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。