1、Linux 设备驱动程序设计,Linux设备驱动程序设计,郗闽军 牛建伟,实验目的,学习Linux下进行驱动程序设计的原理掌握Linux设备驱动程序开发的基本过程和设计方法,实验内容,内核驱动设计入门模块方式驱动程序(5.1) 内核驱动设计实验触摸屏驱动(5.2) 写一个简单的应用程序,显示触摸位置的坐标(x,y) 开发一个LED(数码管)驱动程序,并编写一个应用程序对所开发的驱动程序进行测试(大作业) 实验实现的功能是上电复位后,数码管显示数字0-7,然后每一个数字依次闪烁一次,小数点也要点亮,即:0.1.2.3.4.5.6.7.,Linux的设备驱动程序,硬件设备与应用程序之间的一个中间软
2、件层 它使得某个特定硬件能够响应一个定义良好的内部编程接口,同时完全隐蔽了设备的工作细节 用户通过一组与具体设备无关的标准化的调用来完成相应的操作 驱动程序的任务就是把这些标准化的系统调用映射到具体设备对于实际硬件的特定操作上 驱动程序是内核的一部分,可以使用中断、DMA等操作 驱动程序在用户态和内核态之间传递数据,设备驱动程序的分类,字符设备 所有能够象字节流一样访问的设备都通过字符设备来实现 它们被映射为文件系统中的节点,通常在/dev/目录下面 一般要包含open read write close等系统调用的实现 块设备 通常是指诸如磁盘、内存、Flash等可以容纳文件系统的存储设备。
3、块设备也是通过文件系统来访问,与字符设备的区别是:内核管理数据的方式不同 它允许象字符设备一样以字节流的方式来访问,也可一次传递任意多的字节。 网络接口设备 通常它指的是硬件设备,但有时也可能是一个软件设备(如回环接口loopback),它们由内核中网络子系统驱动,负责发送和接收数据包。 它们的数据传送往往不是面向流的,因此很难将它们映射到一个文件系统的节点上。,基本概念,主设备号和次设备号 主设备号和次设备号能够唯一地标识一个设备 128(V2.0以前), 256(V2.0以后) 主设备号相同的设备使用相同的驱动程序,次设备号用于区分具体设备的实例 动态获取主设备号 Linux下对设备号的分
4、配请参考Documentation/devices.txt 设备文件 Linux使用设备文件来统一对设备的访问接口,将设备文件放在/dev/目录下 设备的命名一般为设备文件名+数字或者字母表示的子类,例如/dev/hda1, /dev/hda2等 Linux 2.4以后引入了设备文件系统(devfs)的概念,所有的设备文件作为一个可以挂装的文件系统,这样就可以被文件系统统一管理,从而设备文件就可以挂装到任何需要的地方。一般将主设备建立一个目录,再将具体的子设备文件建立在此目录下。例如,/dev/mtdblock0,基本概念,驱动程序使用的2个重要结构 struct file struct fi
5、le_operations,基本概念,struct file,基本概念,struct file数据结构 定义位于include/fs.h struct file结构与驱动相关的成员 mode_t f_mode 标识文件的读写权限 loff_t f_pos 当前读写位置 unsigned int_f_flag 文件标志,主要进行阻塞/非阻塞型操作时检查 struct file_operation * f_op 文件操作的结构指针 void * private_data 驱动程序一般将它指向已经分配的数据 struct dentry* f_dentry 文件对应的目录项结构,基本概念,设备驱动程序
6、接口( struct file_operations), 标记化方法: static struct file_operations demo_fops = owner: THIS_MODULE, write: demo_write, read: demo_read, ioctl: demo_ioctl, open: demo_open, release: demo_release, ;,基本概念,设备驱动程序接口( struct file_operations ) 通常所说的设备驱动程序接口是指struct file_operations ,它的定义位于include/linux/fs.h中。
7、 在嵌入式系统的开发中,通常只要实现如下几个接口函数就能完成系统所需要的功能 init 加载驱动程序时,内核自动调用 read 从设备中读取数据 write 向字符设备中写数据 ioctl 控制设备,实现除读写操作以外的其他控制命令 open 打开设备并进行初始化 release 关闭设备并释放资源 exit 卸载驱动程序时,内核自动调用,基本概念,驱动程序注册过程(动态分配主设备号) insmod module_name ;加载驱动程序,运行init函数(register_chrdev(dev_Major, “module_name”, * fs ) 查看/proc/devices mkno
8、d /dev/module_name c/b 主设备号 次设备号 rmmod module_name ;卸载驱动,运行 exit函数(unregister_chrdev(dev_Major, “module_name”, * fs )) 用户程序调用 Open(“/dev/module_name”, mode) ;O_RDWR Ioctl() Write() Read() Close(),开发驱动程序时须注意的事项,中断处理 中断是现代微处理器的一个重要功能 Linux驱动程序中的中断处理函数 extern int request_irq(unsigned int irq, void(*han
9、dler)(int, void*, struct pt_regs *), unsigned long flag, const char *dev_name, void *dev_id); /请求为中断号irq分配中断处理函数extern void free_irq(unsigned int, void*);/释放中断注意事项 不能向用户空间发送或者接收数据 不能执行有睡眠操作的函数 不能调用调度函数 谨慎使用全局变量(可重入) 自旋锁的使用,基本概念,字符设备的管理 驱动程序模块通过函数int register_chrdev(unsigned int major, const char *na
10、me, struct file_operations *fops) 完成向内核的注册,其中major是主设备号,name是设备名,fops是针对该设备的驱动程序的接口。 在系统中为驱动程序模块建立一个设备节点minjunRedHatAS $ mknod /dev/demo c 254 0 其中/dev/demo标识设备名为demo,“c”说明是字符设备,254是指定的主设备号, 0是次设备号,基本概念,驱动程序的编译 以demo.c为例 Makefile的形式参考实验指导书 命令行的形式 minjunRedHatAS $ armv4l-unknown-linux-gcc -Wall -c -O
11、 -D_KERNEL_ -I/home/minjun/embedded/kernel-2410s/include demo.c -o demo.o 加载驱动 minjunRedHatAS $ insmod demo.o 卸载驱动 minjunRedHatAS $ rmmod demo.o,基本概念,测试程序实例/test.c #include #include #include int main() int fd;fd=open(“/dev/demo“, O_RDWR);if(fd 0) exit(fd);/your code hereread(fd, buffer, size);write(
12、fd, buffer, size);close(fd);return 0; ,驱动程序的实现驱动程序框架,#include #include #include #include /* printk() */ #include /* everything. */ #include /* error codes */ #include /* size_t */ #include #include /* O_ACCMODE */ #include /* COPY_TO_USER */ #include /* cli(), *_flags */#define DEVICE_NAME “demo“ #d
13、efine demo_MAJOR 250 #define demo_MINOR 0static ssize_t demo_write(struct file *filp,const char *buffer, size_t count) copy_from_user(drv_buf , buffer, count);WRI_LENGTH = count;printk(“user write data to drivern“);/your code herereturn count; ,驱动程序的实现驱动程序框架,static ssize_t demo_read(struct file *fil
14、p, char *buffer, size_t count, loff_t *ppos) if(count MAX_BUF_LEN)count=MAX_BUF_LEN;copy_to_user(buffer, drv_buf,count);printk(“user read data from drivern“);return count; static int demo_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) printk(“ioctl runingn“);switc
15、h(cmd)case 1:printk(“runing command 1 n“);break;case 2:printk(“runing command 2 n“);break;default:printk(“error cmd numbern“);break;return 0; static int demo_open(struct inode *inode, struct file *file) MOD_INC_USE_COUNT;sprintf(drv_buf,“device open sucess!n“);printk(“device open sucess!n“);return 0
16、; ,驱动程序的实现驱动程序框架,static int demo_release(struct inode *inode, struct file *filp) MOD_DEC_USE_COUNT;printk(“device releasen“);return 0; static struct file_operations demo_fops = owner: THIS_MODULE,write: demo_write, read: demo_read, ioctl: demo_ioctl,open: demo_open,release: demo_release, ; static in
17、t _init demo_init(void) SET_MODULE_OWNER(,驱动程序的实现驱动程序框架,open 提供给驱动程序初始化设备的能力,为后续的操作做准备 此外一般会递增使用计数,防止文件关闭前模块被卸载 通常情况下,open完成如下工作: 递增使用计数 检查特定设备错误 如果设备是首次打开,则对其进行初始化 识别次设备号,如有必要,则修改f_op指针 分配并填写filp-private_data中的数据 release 与open正好相反 释放由open分配的filp-private_data中的数据 在最后一次关闭操作时关闭设备 使用计数减一,驱动程序的实现驱动程序框架,
18、read和write read将数据从内核拷贝到应用程序空间,write则将数据从应用程序空间拷贝到内核。 由于用户空间和内核空间的内存映射方式不同,所以在内核和用户空间传输数据的时候需要使用如下的函数 unsigned long copy_to_user(void *to, const void* from, unsigned long count); unsigned long copy_from_user(void *to, const void *from, unsigned long count); 在阻塞型IO中,read和write调用可能会出现阻塞 read调用当前无数据可读,
19、而又没有数据马上可读,这时会睡眠并且等待,write调用也会出现这样的情况 等待队列机制wait_queue_head_t;(定义在中) 如果声明了等待队列并完成初始化,进程就可以睡眠,可以调用sleep_on的不同变体来完成睡眠(函数声明位于中) 大多数情况下应使用“可中断”的函数,如interruptible_sleep_on。 睡眠进程被唤醒并不一定代表有数据,也有可能是被其他的信号唤醒,所以醒来后需要测试condition.,8段LED显示器外形原理图,一个数码管(LED)由一个8位的字节控制 当该位为“1”时点亮,为“0”时灭,LED连接原理图,LED显示驱动器,ZLG7290 I2
20、C LED/键盘 驱动器 I2C 串行接口提供键盘中断信号方便与处理器接口 可驱动8 位共阴数码管或64 只独立LED 和64 个按键 可控扫描位数可控任一数码管闪烁 提供数据译码和循环移位段寻址等控制 8 个功能键可检测任一键的连击次数 无需外接元件即直接驱LED 可扩展驱动电流和驱动电压 详细资料参加课程网站上的: zlg7290.pdf zlg7290_led.pdf,图1 ZLG7290引脚图,LED显示驱动器功能框图,IIC基地址是0x70 有24个8位寄存器(0x00x17),通过IIC总线访问,必须是字节操作 主要寄存器SystemReg 0x0FlashOnOff 0x0cSc
21、anNum 0x0dDpRam07 0x10-0x17CmdBuf02 0x7-0x8 两种控制方式: -寄存器映象控制 -命令解释控制 参考zlg7290.pdf,LED驱动程序的主要函数,static int led_write(struct file* filp, const char* buffer, size_t count, loff_t* ppos) static int led_ioctl(struct inode *inode, struct file*file, unsigned int cmd, unsigned long arg) static int led_open
22、(struct inode* inode, struct file* filp) static int led_release(struct inode* inode, struct file* filp) static int _init led_init(void) static void _exit led_exit(void) static struct file_operations led_fops = owner: THIS_MODULE,open: led_open,release: led_release,write: led_write,ioctl: led_ioctl,
23、; 可以使用IIC_Write(char baseAddr, char offSet, char data)来设置寄存器的值(字节操作),该函数在iic.h文件中定义。,LED测试应用程序框架,#include #include #include #include #include int main() int fd;int ret;if(fd=open(“/dev/led“, O_RDWR) 0) printf(“open led device failed!n“);exit(fd);ioctl();write();close(fd);return 0; ,下一次实验内容,内核驱动设计入门模块方式驱动程序(5.1) 在PC linux和开发平台上运行通过 内核驱动设计实验触摸屏驱动(5.2) 写一个简单的应用程序,显示触摸位置的坐标(x,y) 在开发平台上装载驱动程序,运行编写的测试应用程序 开发一个LED(数码管)驱动程序,并编写一个应用程序对所开发的驱动程序进行测试(如果做不完,可以在下下次实验接着做) 开发led_driver.c 开发led_test.c,