|
1.I/O端口状态检测 在使用I/O端口前,也应该检查此I/O端口是否已有别的程序在使用,若没有,再把此端口标记为正在使用,在使用完以后释放它。 这样需要用到如下几个函数: int check_region(unsigned int from, unsigned int extent); void request_region(unsigned int from, unsigned int extent,const char *name); void release_region(unsigned int from, unsigned int extent); 调用这些函数时的参数为: ? from表示所申请的I/O端口的起始地址; ? extent为所要申请的从from开始的端口数; ? name为设备名,将会出现在/proc/ioports文件里; ? check_region返回0表示I/O端口空闲,否则为正在被使用。 在申请了I/O端口之后,可以借助asm/io.h中的如下几个函数来访问I/O端口: inline unsigned int inb(unsigned short port); inline unsigned int inb_p(unsigned short port); inline void outb(char value, unsigned short port); inline void outb_p(char?value, unsigned short port); 其中inb_p和outb_p插入了一定的延时以适应某些低速的I/O端口。
2.时钟函数 在设备驱动程序中,一般都需要用到计时机制。在Linux系统中,时钟是由系统接管的,设备驱动程序可以向系统申请时钟。与时钟有关的系统调用有: #include <asm/param.h> #include <linux/timer.h> void add_timer(struct timer_list * timer); //添加计时器 int del_timer(struct timer_list * timer); //删除计时器 inline void init_timer(struct timer_list * timer); //初始化计时器 其中结构体struct timer_list的定义为: struct timer_list { struct timer_list *next; struct timer_list *prev; unsigned long expires; unsigned long data; void (*function)(unsigned long d); }; 其中,expires是要执行function的时间。系统核心有一个全局变量jiffies表示当前时间,一般在调用add_timer时jiffies=JIFFIES+num,表示在num个系统最小时间间隔后执行function函数。系统最小时间间隔与所用的硬件平台有关,在核心里定义了常数HZ表示一秒内最小时间间隔的数目,则num*HZ表示num秒。系统计时到预定时间就调用function,并把此子程序从定时队列里删除,可见,如果想要每隔一定时间间隔执行一次的话,就必须在function里再一次调用add_timer。function的参数d即为timer里面的data项。 3.内存操作函数 作为系统核心的一部分,设备驱动程序在申请和释放内存时不是调用malloc和free,而代之以调用kmalloc和kfree,它们在linux/kernel.h中被定义为: void * kmalloc(unsigned int len, int priority); void kfree(void * obj); 参数len为希望申请的字节数,obj为要释放的内存指针。priority为分配内存操作的优先级,即在没有足够空闲内存时如何操作,一般由取值GFP_KERNEL解决即可。
4.复制函数 在用户程序调用read、write时,因为进程的运行状态由用户态变为核心态,地址空间也变为核心地址空间。由于read、write中参数buf是指向用户程序的私有地址空间的,所以不能直接访问,必须通过下面两个系统函数来访问用户程序的私有地址空间。 #include <asm/segment.h> void memcpy_fromfs(void * to,const void * from,unsigned long n); void memcpy_tofs(void * to,const void * from,unsigned long n); memcpy_fromfs由用户程序地址空间往核心地址空间复制,memcpy_tofs则反之。参数to为复制的目的指针,from为源指针,n为要复制的字节数。 在设备驱动程序里,可以调用printk来打印一些调试信息,printk的用法与printf类似。printk打印的信息不仅出现在屏幕上,同时还记录在文件syslog里。
5.入口函数 在编写模块程序时,必须提供两个函数,一个是int init_module(),在加载此模块的时候自动调用,负责进行设备驱动程序的初始化工作。init_module()返回0,表示初始化成功,返回负数表示失败,它在内核中注册一定的功能函数。在注册之后,如果有程序访问内核模块的某个功能,内核将查表获得该功能的位置,然后调用功能函数。init_module()的任务就是为以后调用模块的函数做准备。 另一个函数是void cleanup_module(),该函数在模块被卸载时调用,负责进行设备驱动程序的清除工作。这个函数的功能是取消init_module()所做的事情,把init_module()函数在内核中注册的功能函数完全卸载,如果没有完全卸载,在此模块下次调用时,将会因为有重名的函数而导致调入失败。 在2.3版本以上的Linux内核中,提供了一种新的方法来命名这两个函数。例如,可以定义init_my()代替init_module()函数,定义exit_my()代替cleanup_module()函数,然后在源代码文件末尾使用下面的语句: module_init(init_my); module_exit(exit_my); 这样做的好处是,每个模块都可以有自己的初始化和卸载函数的函数名,多个模块在调试时不会有重名的问题。
6.模块加载与卸载 虽然模块作为内核的一部分,但并未被编译到内核中,它们被分别编译和链接成目标文件。Linux中模块可以用C语言编写,用gcc命令编译成模块*.o,在命令行里加上-c的参数和“-D__KERNEL__-DMODULE”参数。然后用depmod -a 使此模块成为可加载模块。模块用insmod命令加载,用rmmod命令来卸载,这两个命令分别调用init_module()和cleanup_ module()函数,还可以用lsmod命令来查看所有已加载的模块的状态。
LED驱动程序代码分析 1.系统资源和宏定义 #define DEVICE_NAME "leds" //定义led设备的名字 #define LED_MAJOR 231 //定义led设备的主设备号 static unsigned led_table[]= //I/O操作led设备对应的硬件寄存器 { GPIO_B7, GPIO_B8, GPIO_B9, GPIO_B10 }; 2.入口函数 模块的入口函数leds_init(),所做的工作是点亮I/O端口对应的二极管,这些二级管可以作为状态指示灯用.register_chrdev()完成字符设备在系统中的注册,并建立与文件系统的并联. static int __init leds_init(void) { int ret; int i; //在内核中注册设备 ret=register_chrdev(LED_MAJOR,DEVICE_NAME,&leds_fops); if(ret<0) { printk(DEVICE_NAME "Can't register major number\n"); return ret; } devfs_handle=devfs_register(NULL,DEVICE_NAME,DEVFS_FL_DEFAULT,LED_MAJOR,0,S_IFCHR|S_IRUSR|S_IWUSR,&matrix4_leds_fops,NULL); //使用宏进行端口初始化,set_gpio_ctrl和write_gpio_bit均为宏定义 for(i=0;i<8;i++) { set_gpio_ctrl(led_table[i]|GPIO_PULLUP_EN|GPIO_MODE_OUT); write_gpio_bit(led_table[i],1); } printk(DEVICE_NAME "initialized\n"); return 0; } 卸载模块调用leds_exit()函数,执行后设备处于空闲状态 static void __exit led_exit(void) { devfs_unregister(devfs_handler); unregister_chrdev(LED_MAJOR,DEVICE_NAME); //注销字符设备 } 3.ioctl,I/O控制执行读写之外的操作,通过参数cmd设定命令参数 static int leds_ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned longarg) { switch(cmd){ case 0: case 1: if(arg>4) { return -EINVAL; } write_gpio_bit(led_table[arg],!cmd); default: return -EINVAL; } } 4.文件系统接口定义 static struct file_operations leds_fops={ owner:THIS_MODULE, ioctl:leds_ioctl, }; 5.模块化 module_init(leds_init); module_exit(leds_exit); MODULE_LICENSE("GPL"); 用insmod命令加载模块时,调用module_init() 用rmmod命令卸载模块时,调用module_exit() 加载运行LED驱动程序 1.应用程序设计 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> int main(int argc,char **argv) { int on; int led_no; int fd; if(argc!=3 || sscanf(argv[1],"%d",&led_no)!=1 || sscanf(argv[2],"%d",&on)!=1 || on<0 || on>1 || led_no<0 || led_no>3) { fprintf(stderr,"Usage:ledtest led_no 0|1\n"); exit(1); } if(fd<0) { perror("open device leds"); exit(1); } ioctl(fd,on,led_on); close(fd); return 0; } 分析:该程序首先读取命令行的参数输入,其中参数argv[1]赋值给led_no,表示发光二级管的序号,argv[2]赋值给no.led_no的取值为1~3,on取值为0或1,0表示熄灭LED,1表示点亮LED. 参数输入后通过fd=open("/dev/leds",0)打开设备文件,在保证参数输入正确和设备文件正确打开后,通过语句ioctl(fd,on,led_on)实现系统调用ioctl并通过输入的参数控制LED并在最后关闭句柄. 2.加载驱动 首先编写Makefile文件 INCLUDE=/usr/linux/include EXTRA_CFLAGS=-D_KERNEL_ -DMODULE -I $(INCLUDE) -O2 -Wall -O all:led.o ledtest led.o:led.c arm-linux-gcc $(CFLAGS) $(EXTRA_CFLAGS) -c leds.c -o leds.o ledtest:ledtest.c arm-linux-gcc -g led.c -o ledtest .PHONY:clean clean: -rm -f leds.o ledtest 生成文件复制到目标板的/lib目录下 安装模块 insmod /lib/leds.o 删除模块 rmmod leds.o
|