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
#include
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
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
#include
#include
#include
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
阅读(1665) | 评论(0) | 转发(0) |