Linux字符设备驱动模型之点亮LED灯

2017年04月09日 10:45    发布者:FWW7
在前面的文章中,已经描述了整一个字符设备模型,那现在,可以来使用相应的模型进行操作硬件设备了。那么,从点亮一个LED灯开始。一、设备控制操作1.理论基础大部分驱动程序除了需要提供读写设备的能力外,还需要具备控制设备的能力。比如设置UART波特率这样的操作。实际上在Linux系统用户空间中,提供了ioctl系统调用函数来实现了控制设备的操作。其原型如下:int ioctl(int fd,unsigned long cmd,...)·fd: 要控制的设备文件描述符·cmd: 发送给设备的控制命令·…: 第3个参数是可选的参数,存在与否是依赖于控制命令(第 2 个参数 )。当应用程序在用户空间使用ioctl系统调用时,驱动程序将由如下函数来响应:(1)Linux 2.6.36版本之前的内核long (*ioctl) (struct inode* node ,struct file* filp, unsigned int cmd,unsigned long arg)(2)Linux 2.6.36版本之后的内核long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);·参数cmd: 通过应用函数ioctl传递下来的命令此函数方法由struct file_operations操作函数集实现,如下图:在此笔者所使用的是3.0.8版本的Linux内核。2.控制方法实现(1)定义命令命令从其实质而言就是一个整数, 但为了让这个整数具备更好的可读性,我们通常会把这个整数分为几个段:类型(8位),序号,参数传送方向,参数长度。·Type(类型/幻数): 表明这是属于哪个设备的命令。·Number(序号):用来区分同一设备的不同命令。·Direction:参数传送的方向,可能的值是 _IOC_NONE(没有数据传输), _IOC_READ, _IOC_WRITE(向设备写入参数)。·Size:参数长度Linux系统提供了下面的宏来帮助定义命令:·_IO(type,nr):不带参数的命令·_IOR(type,nr,datatype):从设备中读参数的命令·_IOW(type,nr,datatype):向设备写入参数的命令示例:#define MEM_MAGIC ‘m’ //定义幻数#define MEM_SET _IOW(MEM_MAGIC, 0, int)(2)实现操作在Linux内核操作函数集中的unlocked_ioctl函数的实现通常是根据命令执行的一个switch语句。但是,当命令号不能匹配任何一个设备所支持的命令时,返回-EINVAL.二、Linux内核空间操作硬件设备1.确定硬件接口在操作一个硬件设备前,必须要了解的是其所使用的硬件接口和硬件原理。在此笔者所使用的是S5PV210平台。如上图可知,LED1和LED2由CPU的GPC0_3和GPC0_4这两个I/O口控制,并且GPIO口工作在输出模式时,当输出高电平时,灯亮;输出低电平时,灯灭。如上图为GPC0口的控制寄存器GPC0CON(地址为0xe0200060),分析可知,当将位配置为时,GPC0_3和GPC0_4口工作在输出模式下。如上图为GPC0口的数据寄存器GPC0DAT(0XE0200064),当I/O口工作在输出模式时,其每一位代表着每一个GPIO口的电平配置。所以GPC0DAT寄存器的位3代表着GPC0_3口,位4代表GPC0_4口。在程序头文件led_driver.h中定义寄存器硬件地址:2.命令和设备结构体定义根据ioctl命令的定义方式,在程序头文件led_driver.h中定义命令,如下图:如上图中定义了一个led设备相关的结构体struct led_device,成员struct class  *led_class;代表led设备类,struct device *led_device;代表了的设备,unsigned int  val;代表其可能需要的配置值。对于LED的操作而言,实现可以单个点亮或熄灭LED,也可以全部点亮或熄灭LED。所以定义了__LED_SELECT枚举、ON和OFF命令,然后通过__IO()宏进行定义命令。3.设备驱动模块初始化(1)定义struct led_device结构体变量struct led_device *led_dev;和硬件操作相关变量gpc0con、gpc0dat。并为指针led_device申请内存空间。(2)静态申请主设备号其中LED_MAJOR为静态定义的主设备号,在程序头文件led_driver.h中定义。led_fops为struct file_operations操作函数集。#define        LED_MAJOR        100(3)自动创建设备节点文件首先使用class_create函数创建一个设备类,然后再根据设备类由device_create函数创建一个名为led的设备节点文件,即为当在Linux内核中加载此驱动时,会自动在用户空间/dev目录下生成的设备节点文件led。当操作失败时,处理方式如下:(4)将硬件设备操作寄存器地址映射为虚拟地址。在Linux内核中,是不允许直接操作设备的物理地址的,智能通过虚拟地址映射的方式进行操作或者配置CPU私有外设的寄存器地址,从而达到操作相关寄存器的目的。在Linux内核中提供了ioremap宏函数来实现物理地址到虚拟地址的映射。以上操作即为LED设备驱动的初始化函数led_init的实现,源码如下:4.设备模块卸载设备模块的卸载主要实现将设备号释放,销毁设备节点文件、销毁设备类,释放所申请的内存空间。5.操作函数集的实现如上图可知,一共实现了3个操作函数。分别对应于用户空间的open、close和ioctl系统调用的操作。其中led_open操作函数的实现主要进行硬件寄存器的初始化配置。如上图即是配置GPC0CON寄存器,将GPC0_3和GPC0_4口配置为输出模式,并初始化GPC0DAT寄存器,保证初始状态为灯灭。操作函数led_close的实现不实现任何操作。为空函数。6.led_ioctl操作函数的实现如上图所示,led_ioctl函数通过接受从用户空间传递而来的命令cmd,来决定LED设备的亮灭。以上这种方式是通过直接操作寄存器的方式来实现。实际上在Linux内核中提供了相应的读写寄存器的函数方法来实现操作,如下图:除了这种方法之外,要操作GOIO口,更可以使用gpio_get_value和gpio_set_value这样的函数来进行配置GPIO口,对于GPIO口的配置方式,还有更多的实现。三、Linux用户空间操作硬件设备应用程序的实现思路是,从外表为用户程序提供命令来进行操作LED灯,main函数从外表接收两个命令:命令的执行状态为:./app_led LED编号 状态·编号可取值1,2,3,分别表示LED1,LED2和全部LED。·状态可取值0和1,分别表示灯亮和灯灭。首先实现判断外表命令的输入情况,打开设备文件/dev/led,和将命令由字符转化为整数。然后就是点灯功能的实现了。四、验证现象分别编译好内涵驱动模块和应用程序,分别得到led_driver.ko文件和app_led文件,然后将他们都拷贝到开发板根文件系统。执行命令:insmod led_driver.ko插入内核驱动模块。最后执行应用程序./app_led进行操作LED。如下图:至此!整个点灯实现完成。


网友评论

FWW7 2017年04月09日
Linux驱动这块有什么不懂的,可加QQ2232894713相互交流学习