linux驱动程序接口
来源:互联网 发布:美国国税局 知乎 编辑:程序博客网 时间:2024/04/29 17:05
1. Linux驱动程序接口
系统调用是操作系统内核与应用程序之间的接口,设备驱动程序则是操作系统内核与机器硬件的接口。几乎所有的系统操作最终映射到物理设备,除了CPU、内存 和少数其它设备,所有的设备控制操作都由该设备特殊的可执行代码实现,此代码就是设备驱动程序。操作系统内核需要访问两类主要设备:字符设备和块设备。与 此相关主要有两类设备驱动程序,字符设备驱动程序和块设备驱动程序。Linux(也是所有UNIX)的基本原理之一是:系统试图使它对所有各类设备的输 入、输出看起来就好象对普通文件的输入、输出一样。设备驱动程序本身具有文件的外部特征,它们都能使用象
open(),close(),read(),write()等系统调用。为使设备的存取能象文件一样处理,所有设备在目录中应有对应的文件名称,才可使用有关系统调用。
通常Linux驱动程序接口分为如下四层:
1).应用程序进程与内核的接口;
2).内核与文件系统的接口;
3).文件系统与设备驱动程序的接口;
4).设备驱动程序与硬件设备的接口。
§2. 驱动程序文件操作数据结构
每个驱动程序都有一个file-operation的数据结构,包含指向驱动程序内部函数的指针。file-operation的数据结构为:
struct file-operation {
int (*lseek)();
int (*read)();
int (*write)();
int (*readdir)();
int (*select)();
int (*ioctl)();
int (*mmap)();
int (*open)();
int (*close)();
int (*release)();
int (*fsync)();
int (*fasync)();
int (*check-media-change)();
int (*revalidate)();
}
内核中有两个表,一个用于字符设备驱动程序,一个用于块设备驱动程序。这两个表用于保存指向file-operation数据结构的指针,驱动程序内部函 数的地址保存在这一结构。内核用主设备号作为索引访问file-operation结构,可以访问驱动程序子程序地址。SBS617设备采用了PCI总线 字符设备的驱动程序实现方式。完成了设备驱动程序,经GNU软件编译,链接,产生一可加载模块,可以用于动态装入Linux操作系统内核,也可以在需要时 从内核中卸除。
§3. file_operations介绍
在结构file_operations里,指出了设备驱动程序所提供的入口点位置,分别是:
(1) lseek,移动文件指针的位置,显然只能用于可以随机存取的设备。
(2) read,进行读操作,参数buf为存放读取结果的缓冲区,count为所要读取的数据长度。返回值为负表示读取操作发生错误,否则返回实际读取的字节 数。对于字符型,要求读取的字节数和返回的实际读取字节数都必须是inode->i_blksize的的倍数。
(3) write,进行写操作,与read类似。
(4) readdir,取得下一个目录入口点,只有与文件系统相关的设备驱动程序才使用。
(5) selec,进行选择操作,如果驱动程序没有提供select入口,select操作将会认为设备已经准备好进行任何的I/O操作。
(6) ioctl,驱动程序特殊控制入口点,进行读、写以外的其它操作,参数cmd为自定义的命令。 这是很有意思的部分,之后我会详尽介绍;
(7) mmap,用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用。
(8) open,打开设备准备进行I/O操作。返回0表示打开成功,返回负数表
示失败。如果驱动程序没有提供open入口,则只要/dev/driver文件存
在就认为打开成功。
(9) release,即close操作。
设备驱动程序所提供的入口点,在设备驱动程序初始化的时候向系统进行登记,以便系统在适当的时候调用。
§4 PCI字符设备驱动程序
要设计PCI设备驱动程序,必须进一步结合硬件设备和PCI总线的特性。设计PCI设备驱动程序的重要任务是找寻相应的硬件并实现对它的访问。作为外围设 备的硬件必须响应三种地址空间的访问,即内存,IO,寄存器地址空间。前两种地址空间可以为PCI总线上的所有设备共享。寄存器空间占用物理地址,可以通 过特殊的函数来访问配置寄存器。一旦可以访问配置寄存器,设备驱动程序就可以访问硬件了。每个设备的PCI配置寄存器均由256Bytes构成,其中 64Bytes是标准化的,4Bytes标识了一个唯一的函数ID,通过这个ID驱动程序就可以定位该设备。
存取系统中的字符设备和存取系统文件一样。应用程序使用标准的系统调用来打开、读写和关闭设备,就像使用一个文件-样。当字符设备初始化时,通过向 chrdevs数组中添加一个入口,设备驱动程序在系统内核中注册。 chrdevs数组由device_struct数据结构组成。设备的主设备号用来作为此chrdevs的索引,因为一个设备的主设备号是固定的。
LINUX系统里,通过调用register_chrdev向系统注册字符型设备驱动程序。register_chrdev定义为:
#include linux/fs.h
#include linux/errno.h
int register_chrdev(unsigned int major, const char *name,
struct file_operations *fops);
其中,major是为设备驱动程序向系统申请的主设备号,如果为0则系统为此驱动程序动态地分配一个主设备号。name是设备名。fops就是前面所说的 对各个调用的入口点的说明。此函数返回0表示成功。返回-EINVAL表示申请的主设备号非法,一般来说是主设备号大于系统所允许的最大设备号。返回- EBUSY表示所申请的主设备号正在被其它设备驱动程序使用。如果是动态分配主设备号成功,此函数将返回所分配的主设备号。
§5 PCI设备启动与检测
PC主板BIOS在系统启动时,可以自动检测PCI设备并配置设备的每一地址区。当驱动程序访问设备时,它的内存、I/O地址空间已经映射到进程的地址空 间了。在驱动程序init_module()中,通过调用函数pcibios_find_device()函数返回设备在总线上的位置及函数指针,其中的 包含文件及函数原型为:
#include Linux/pci.h
#include Linux/config.h
#include Linux/bios32.h
int pcibios_find_device(unsigned short vendor,
unsigned short id, unsigned short index,
unsigned char *bus, unsigned short *function)
§6 地址空间访问
在设备驱动程序检测到设备之后,通常要从三个地址空间读写数据,其中寄存器空间的读写尤为重要,因为只有通过它驱动程序才可能找到设备内存和I/O空间的映射地址。设备驱动程序通过调用以下函数实现寄存器空间的访问,其中的包含文件及函数原型为:
#include Linux/bios32.h
int pcibios_read_config_byte( unsigned char bus,
unsigned char function,
unsigned char where,
unsigned char b*ptr)
int pcibios_write_config_byte(unsigned char bus,
unsigned char function,
unsigned char where,
unsigned char b*ptr)
类似的还有:
pcibios_read_config_word (), pcibios_write_config_word () ,
pcibios_read_config_dword () ,pcibios_write_config_dword() 调用。
PCI设备最多有6个地址区,类型可以为内存区或I/O区。接口板可以通过配置寄存器的PCI_BASE_ADDRESS_0 到PCI_BASE_ADDRESS_5来报告各地址区的实际地址位置。内存、IO空间的访问通过inb(),memcpy()等调用。当然可以通过 pcibios_read_config_byte(),pcibios_write_config_byte() 来访问配置寄存器的相应基地址值。
§7 中断处理
对中断的处理是属于系统核心的部分, PC主板BIOS为多数设备分配了一个唯一的中断号,在配置寄存器中保存, 设备驱动程序通过pcibios_read_config_byte() 函数读取相应的值,格式为:
xxx_irq=pcibios_read_config_byte(pci_bus,pci_device_fn,
PCI_INTERRUPT_LINE,
&pci_cofig->int_line)
操作系统中有中断寄存器,将特定的中断请求与中断处理函数联系在一起,当中断发生时调用相应的中断处理函数处理。Linux操作系统下可用request_irq(),free_irq( )实现中断的请求,释放,其中包含文件及形式为:
#include Linux/sched.h
int request_irq(unsigned int irq,
void (*handler)(int irq,void dev_id,struct pt_regs *regs),
unsigned long flags,
const char *device,
void *dev_id);
void free_irq(unsigned int irq, void *dev_id);
参数irq表示所要申请的硬件中断号。handler为向系统登记的中断处理子程序,中断产生时由系统来调用,调用时所带参数irq为中断号, dev_id为申请时告诉系统的设备标识,regs为中断发生时寄存器内容。device为设备名,将会出现在/proc/interrupts文件里。
flag是申请时的选项,它决定中断处理 程序的一些特性,有两种方式写中断方式设备驱动程序:即快中断方式和定时等待方式。采取快中断方式需要将request_irq()的第三个type类型 参数设为SA_INTERRUPT。 正常中断与快中断的区别在于: 从正常中断返回时,内核可以利用机会调度更优先的进程执行; 而快中断不进行调度立即恢复被中断程序的执行.
在LINUX系统中,中断可以被不同的中断处理程序共享,这要求每一个共享此中断的处理程序在申请中断时在flags里设置SA_SHIRQ,这些处理程 序之间以dev_id来区分。如果中断由某个处理程序独占,则dev_id 可以为NULL。request_irq返回0表示成功,返回-INVAL表示irq>15或 handler==NULL,返回-EBUSY表示中断已经被占用且不能共享。
中断处理函数形式为:
void xxx_irq_handler(int xxx_irq,void *dev_id,
struct pt_regs *regs)
§8 特殊控制函数ioctl()
ioctl()具有设备特殊性,不同于read() , write(),在于它允许应用程序访问、配置设备,并进入可能的操作模式。 通常的read()、write()不能使用这些控制操作。ioctl()可以控制I/O通道。设备驱动的一个特点是要与其它设备硬件交换读/写的数据并 需要同步控制。
多数的ioctl()由一系列的switch语句组成, ioctl()命令及操作选择考虑到硬件的特性和实际要实现的功能。写ioctl()程序之前,应选择相应的命令,不应该简单使用1-N的数字。选择ioctl()的命令有以下的考虑:
·首先命令码在系统中应该唯一,以避免与其它设备冲突,每个命令码应由多个比特域构成。
· 参考两个文件来帮助选择ioctl()的命令,include/asm/ioctl.h及Documentation/ioctl_number.txt 有如下定义:
命令码有四个8比特组,其相应取值的宏定义及含义如下表:
命令码取值宏定义及含义
比特组名称 取值宏定义 含义
type _IOC_TYEBITS 表示每个驱动程序唯一的类型标识
number _IOC_NRBITS 表示序列号
direction _IOC_NONE, _IOC_READ,
_IOC_WRITE,_IOC_READ|WRITE 表示数据传输的方向
size _IOC_SIZEBITS 表示传输数据的大小
在头文件< asm/ioctl.h >中定义了设置命令码的一些有用的宏:
_IO(type,nr);
_IOR(type,nr,size);
_IOW(type,nr,size);
_IOC_DIR(nr);
_IOC_TYPE(nr);
_IOC_NR(nr);
_IOC_SIZE(nr);
这些设置与具体的硬件功能有关,可以参考有关的硬件手册。通过以上方式可以设置命令、获得设备参数及实现控制操作, 完成设备驱动程序的重要功能。
对于设备驱动程序,ioctl() 函数非常重要,用户可以通过它来控制设备函数,获取状态信息,进行数据的读写。
ioctl()在用户空间的形式为:
int (*ioctl)(struct inode *inode , struct file *file ,
unsigned int cmd , unsigned long arg)
其中cmd相当于一个选择码,取决于使用的特殊控制命令,cmd命令通常在头文件中声明。直接的调用的格式为:
temp = ioctl( fd, XX_xxxx, param* );
\"fd\"是设备文件句柄。XX_xxxx 是控制码。Param是一个参数结构的指针,当调用 ioctl()时,需要理解一些特殊参数结构 ,可以参考下面的四个表格。返回值0表示成功,-1失败。
§9.调用Linux内核函数
Linux有许多内核函数可以调用。例如;
1)memcpy_fromfs( *toptr, *fromptr, sizeof());
// 用于从文件系统传输数据
2) memcpy_tofs ( *toptr, *fromptr, sizeof());
// 用于将数据传输到文件系统
#include asm/segment.h
void memcpy_fromfs(void * toptr,const void * fromptr,unsigned long n);
void memcpy_tofs(void * toptr,const void * fromptr,unsigned long n);
在用户程序调用read 、write时,因为进程的运行状态由用户态变为核心 态,地址空间也变为核心地址空间。而read、write中参数buf是指向用户程 序的私有地址空间的,所以不能直接访问,必须通过上述两个系统函数来访问用户程序的私有地址空间。memcpy_fromfs由用户程序地址空间往核心地 址空间复制,memcpy_tofs则反之。参数toptr为复制的目的指针,fromptr为源指针,n 为要复制的字节数。
3) ptr = vmalloc( sizeof() );// 动态分配内存
4) vfree( ptr ); // 动态释放内存
5) vremap( xxx_mapping[ chn ].pci_addr,
xxx_mapping[ chn ].len );
// 映射PCI地址,
chn = current_map_chn.
6)作为系统核心的一部分,设备驱动程序在申请和释放内存时不是调用malloc
和free,而调用kmalloc和kfree,定义为:
#include linux/kernel.h
void * kmalloc(unsigned int len, int priority);
void kfree(void * ptr);
参数len为希望申请的字节数,ptr为要释放的内存指针。priority为分配内存操作的优先级,即在没有足够空闲内存时如何操作,一般用GFP_KERNEL。
7)与中断和内存不同,使用一个没有申请的I/O端口不会使CPU产生异常, 也
就不会导致诸如\"segmentation fault\"一类的错误发生。任何进程都可以访问
任何一个I/O端口。此时系统无法保证对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端口之后,就可以如下几个函数来访问I/O端口:
#include asm/io.h
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端口。
9)在设备驱动程序里,一般都需要用到计时机制。在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项。
10)在设备驱动程序里,还可能会用到如下的一些系统函数:
#include asm/system.h
#define cli() __asm__ __volatile__ (\"cli\"::)
#define sti() __asm__ __volatile__ (\"sti\"::)
这两个函数负责打开和关闭中断允许。
11)在设备驱动程序里,可以调用printk来打印一些调试信息,用法与printf 类似。
printk打印的信息不仅出现在屏幕上,同时还记录在文件syslog里。
系统调用是操作系统内核与应用程序之间的接口,设备驱动程序则是操作系统内核与机器硬件的接口。几乎所有的系统操作最终映射到物理设备,除了CPU、内存 和少数其它设备,所有的设备控制操作都由该设备特殊的可执行代码实现,此代码就是设备驱动程序。操作系统内核需要访问两类主要设备:字符设备和块设备。与 此相关主要有两类设备驱动程序,字符设备驱动程序和块设备驱动程序。Linux(也是所有UNIX)的基本原理之一是:系统试图使它对所有各类设备的输 入、输出看起来就好象对普通文件的输入、输出一样。设备驱动程序本身具有文件的外部特征,它们都能使用象
open(),close(),read(),write()等系统调用。为使设备的存取能象文件一样处理,所有设备在目录中应有对应的文件名称,才可使用有关系统调用。
通常Linux驱动程序接口分为如下四层:
1).应用程序进程与内核的接口;
2).内核与文件系统的接口;
3).文件系统与设备驱动程序的接口;
4).设备驱动程序与硬件设备的接口。
§2. 驱动程序文件操作数据结构
每个驱动程序都有一个file-operation的数据结构,包含指向驱动程序内部函数的指针。file-operation的数据结构为:
struct file-operation {
int (*lseek)();
int (*read)();
int (*write)();
int (*readdir)();
int (*select)();
int (*ioctl)();
int (*mmap)();
int (*open)();
int (*close)();
int (*release)();
int (*fsync)();
int (*fasync)();
int (*check-media-change)();
int (*revalidate)();
}
内核中有两个表,一个用于字符设备驱动程序,一个用于块设备驱动程序。这两个表用于保存指向file-operation数据结构的指针,驱动程序内部函 数的地址保存在这一结构。内核用主设备号作为索引访问file-operation结构,可以访问驱动程序子程序地址。SBS617设备采用了PCI总线 字符设备的驱动程序实现方式。完成了设备驱动程序,经GNU软件编译,链接,产生一可加载模块,可以用于动态装入Linux操作系统内核,也可以在需要时 从内核中卸除。
§3. file_operations介绍
在结构file_operations里,指出了设备驱动程序所提供的入口点位置,分别是:
(1) lseek,移动文件指针的位置,显然只能用于可以随机存取的设备。
(2) read,进行读操作,参数buf为存放读取结果的缓冲区,count为所要读取的数据长度。返回值为负表示读取操作发生错误,否则返回实际读取的字节 数。对于字符型,要求读取的字节数和返回的实际读取字节数都必须是inode->i_blksize的的倍数。
(3) write,进行写操作,与read类似。
(4) readdir,取得下一个目录入口点,只有与文件系统相关的设备驱动程序才使用。
(5) selec,进行选择操作,如果驱动程序没有提供select入口,select操作将会认为设备已经准备好进行任何的I/O操作。
(6) ioctl,驱动程序特殊控制入口点,进行读、写以外的其它操作,参数cmd为自定义的命令。 这是很有意思的部分,之后我会详尽介绍;
(7) mmap,用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用。
(8) open,打开设备准备进行I/O操作。返回0表示打开成功,返回负数表
示失败。如果驱动程序没有提供open入口,则只要/dev/driver文件存
在就认为打开成功。
(9) release,即close操作。
设备驱动程序所提供的入口点,在设备驱动程序初始化的时候向系统进行登记,以便系统在适当的时候调用。
§4 PCI字符设备驱动程序
要设计PCI设备驱动程序,必须进一步结合硬件设备和PCI总线的特性。设计PCI设备驱动程序的重要任务是找寻相应的硬件并实现对它的访问。作为外围设 备的硬件必须响应三种地址空间的访问,即内存,IO,寄存器地址空间。前两种地址空间可以为PCI总线上的所有设备共享。寄存器空间占用物理地址,可以通 过特殊的函数来访问配置寄存器。一旦可以访问配置寄存器,设备驱动程序就可以访问硬件了。每个设备的PCI配置寄存器均由256Bytes构成,其中 64Bytes是标准化的,4Bytes标识了一个唯一的函数ID,通过这个ID驱动程序就可以定位该设备。
存取系统中的字符设备和存取系统文件一样。应用程序使用标准的系统调用来打开、读写和关闭设备,就像使用一个文件-样。当字符设备初始化时,通过向 chrdevs数组中添加一个入口,设备驱动程序在系统内核中注册。 chrdevs数组由device_struct数据结构组成。设备的主设备号用来作为此chrdevs的索引,因为一个设备的主设备号是固定的。
LINUX系统里,通过调用register_chrdev向系统注册字符型设备驱动程序。register_chrdev定义为:
#include linux/fs.h
#include linux/errno.h
int register_chrdev(unsigned int major, const char *name,
struct file_operations *fops);
其中,major是为设备驱动程序向系统申请的主设备号,如果为0则系统为此驱动程序动态地分配一个主设备号。name是设备名。fops就是前面所说的 对各个调用的入口点的说明。此函数返回0表示成功。返回-EINVAL表示申请的主设备号非法,一般来说是主设备号大于系统所允许的最大设备号。返回- EBUSY表示所申请的主设备号正在被其它设备驱动程序使用。如果是动态分配主设备号成功,此函数将返回所分配的主设备号。
§5 PCI设备启动与检测
PC主板BIOS在系统启动时,可以自动检测PCI设备并配置设备的每一地址区。当驱动程序访问设备时,它的内存、I/O地址空间已经映射到进程的地址空 间了。在驱动程序init_module()中,通过调用函数pcibios_find_device()函数返回设备在总线上的位置及函数指针,其中的 包含文件及函数原型为:
#include Linux/pci.h
#include Linux/config.h
#include Linux/bios32.h
int pcibios_find_device(unsigned short vendor,
unsigned short id, unsigned short index,
unsigned char *bus, unsigned short *function)
§6 地址空间访问
在设备驱动程序检测到设备之后,通常要从三个地址空间读写数据,其中寄存器空间的读写尤为重要,因为只有通过它驱动程序才可能找到设备内存和I/O空间的映射地址。设备驱动程序通过调用以下函数实现寄存器空间的访问,其中的包含文件及函数原型为:
#include Linux/bios32.h
int pcibios_read_config_byte( unsigned char bus,
unsigned char function,
unsigned char where,
unsigned char b*ptr)
int pcibios_write_config_byte(unsigned char bus,
unsigned char function,
unsigned char where,
unsigned char b*ptr)
类似的还有:
pcibios_read_config_word (), pcibios_write_config_word () ,
pcibios_read_config_dword () ,pcibios_write_config_dword() 调用。
PCI设备最多有6个地址区,类型可以为内存区或I/O区。接口板可以通过配置寄存器的PCI_BASE_ADDRESS_0 到PCI_BASE_ADDRESS_5来报告各地址区的实际地址位置。内存、IO空间的访问通过inb(),memcpy()等调用。当然可以通过 pcibios_read_config_byte(),pcibios_write_config_byte() 来访问配置寄存器的相应基地址值。
§7 中断处理
对中断的处理是属于系统核心的部分, PC主板BIOS为多数设备分配了一个唯一的中断号,在配置寄存器中保存, 设备驱动程序通过pcibios_read_config_byte() 函数读取相应的值,格式为:
xxx_irq=pcibios_read_config_byte(pci_bus,pci_device_fn,
PCI_INTERRUPT_LINE,
&pci_cofig->int_line)
操作系统中有中断寄存器,将特定的中断请求与中断处理函数联系在一起,当中断发生时调用相应的中断处理函数处理。Linux操作系统下可用request_irq(),free_irq( )实现中断的请求,释放,其中包含文件及形式为:
#include Linux/sched.h
int request_irq(unsigned int irq,
void (*handler)(int irq,void dev_id,struct pt_regs *regs),
unsigned long flags,
const char *device,
void *dev_id);
void free_irq(unsigned int irq, void *dev_id);
参数irq表示所要申请的硬件中断号。handler为向系统登记的中断处理子程序,中断产生时由系统来调用,调用时所带参数irq为中断号, dev_id为申请时告诉系统的设备标识,regs为中断发生时寄存器内容。device为设备名,将会出现在/proc/interrupts文件里。
flag是申请时的选项,它决定中断处理 程序的一些特性,有两种方式写中断方式设备驱动程序:即快中断方式和定时等待方式。采取快中断方式需要将request_irq()的第三个type类型 参数设为SA_INTERRUPT。 正常中断与快中断的区别在于: 从正常中断返回时,内核可以利用机会调度更优先的进程执行; 而快中断不进行调度立即恢复被中断程序的执行.
在LINUX系统中,中断可以被不同的中断处理程序共享,这要求每一个共享此中断的处理程序在申请中断时在flags里设置SA_SHIRQ,这些处理程 序之间以dev_id来区分。如果中断由某个处理程序独占,则dev_id 可以为NULL。request_irq返回0表示成功,返回-INVAL表示irq>15或 handler==NULL,返回-EBUSY表示中断已经被占用且不能共享。
中断处理函数形式为:
void xxx_irq_handler(int xxx_irq,void *dev_id,
struct pt_regs *regs)
§8 特殊控制函数ioctl()
ioctl()具有设备特殊性,不同于read() , write(),在于它允许应用程序访问、配置设备,并进入可能的操作模式。 通常的read()、write()不能使用这些控制操作。ioctl()可以控制I/O通道。设备驱动的一个特点是要与其它设备硬件交换读/写的数据并 需要同步控制。
多数的ioctl()由一系列的switch语句组成, ioctl()命令及操作选择考虑到硬件的特性和实际要实现的功能。写ioctl()程序之前,应选择相应的命令,不应该简单使用1-N的数字。选择ioctl()的命令有以下的考虑:
·首先命令码在系统中应该唯一,以避免与其它设备冲突,每个命令码应由多个比特域构成。
· 参考两个文件来帮助选择ioctl()的命令,include/asm/ioctl.h及Documentation/ioctl_number.txt 有如下定义:
命令码有四个8比特组,其相应取值的宏定义及含义如下表:
命令码取值宏定义及含义
比特组名称 取值宏定义 含义
type _IOC_TYEBITS 表示每个驱动程序唯一的类型标识
number _IOC_NRBITS 表示序列号
direction _IOC_NONE, _IOC_READ,
_IOC_WRITE,_IOC_READ|WRITE 表示数据传输的方向
size _IOC_SIZEBITS 表示传输数据的大小
在头文件< asm/ioctl.h >中定义了设置命令码的一些有用的宏:
_IO(type,nr);
_IOR(type,nr,size);
_IOW(type,nr,size);
_IOC_DIR(nr);
_IOC_TYPE(nr);
_IOC_NR(nr);
_IOC_SIZE(nr);
这些设置与具体的硬件功能有关,可以参考有关的硬件手册。通过以上方式可以设置命令、获得设备参数及实现控制操作, 完成设备驱动程序的重要功能。
对于设备驱动程序,ioctl() 函数非常重要,用户可以通过它来控制设备函数,获取状态信息,进行数据的读写。
ioctl()在用户空间的形式为:
int (*ioctl)(struct inode *inode , struct file *file ,
unsigned int cmd , unsigned long arg)
其中cmd相当于一个选择码,取决于使用的特殊控制命令,cmd命令通常在头文件中声明。直接的调用的格式为:
temp = ioctl( fd, XX_xxxx, param* );
\"fd\"是设备文件句柄。XX_xxxx 是控制码。Param是一个参数结构的指针,当调用 ioctl()时,需要理解一些特殊参数结构 ,可以参考下面的四个表格。返回值0表示成功,-1失败。
§9.调用Linux内核函数
Linux有许多内核函数可以调用。例如;
1)memcpy_fromfs( *toptr, *fromptr, sizeof());
// 用于从文件系统传输数据
2) memcpy_tofs ( *toptr, *fromptr, sizeof());
// 用于将数据传输到文件系统
#include asm/segment.h
void memcpy_fromfs(void * toptr,const void * fromptr,unsigned long n);
void memcpy_tofs(void * toptr,const void * fromptr,unsigned long n);
在用户程序调用read 、write时,因为进程的运行状态由用户态变为核心 态,地址空间也变为核心地址空间。而read、write中参数buf是指向用户程 序的私有地址空间的,所以不能直接访问,必须通过上述两个系统函数来访问用户程序的私有地址空间。memcpy_fromfs由用户程序地址空间往核心地 址空间复制,memcpy_tofs则反之。参数toptr为复制的目的指针,fromptr为源指针,n 为要复制的字节数。
3) ptr = vmalloc( sizeof() );// 动态分配内存
4) vfree( ptr ); // 动态释放内存
5) vremap( xxx_mapping[ chn ].pci_addr,
xxx_mapping[ chn ].len );
// 映射PCI地址,
chn = current_map_chn.
6)作为系统核心的一部分,设备驱动程序在申请和释放内存时不是调用malloc
和free,而调用kmalloc和kfree,定义为:
#include linux/kernel.h
void * kmalloc(unsigned int len, int priority);
void kfree(void * ptr);
参数len为希望申请的字节数,ptr为要释放的内存指针。priority为分配内存操作的优先级,即在没有足够空闲内存时如何操作,一般用GFP_KERNEL。
7)与中断和内存不同,使用一个没有申请的I/O端口不会使CPU产生异常, 也
就不会导致诸如\"segmentation fault\"一类的错误发生。任何进程都可以访问
任何一个I/O端口。此时系统无法保证对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端口之后,就可以如下几个函数来访问I/O端口:
#include asm/io.h
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端口。
9)在设备驱动程序里,一般都需要用到计时机制。在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项。
10)在设备驱动程序里,还可能会用到如下的一些系统函数:
#include asm/system.h
#define cli() __asm__ __volatile__ (\"cli\"::)
#define sti() __asm__ __volatile__ (\"sti\"::)
这两个函数负责打开和关闭中断允许。
11)在设备驱动程序里,可以调用printk来打印一些调试信息,用法与printf 类似。
printk打印的信息不仅出现在屏幕上,同时还记录在文件syslog里。
0
上一篇:kfifo_alloc深度解析
下一篇:linux多文件编译方法
相关热门文章
- 欢迎xiaoyu_linux在ChinaUnix...
- linux设备驱动之USB数据传输分...
- 浅析usb转serial串口设备在lin...
- [原创]到目前为止,Linux下最...
- Red Hat Linux 的主要系统目录...
- linux 常见服务端口
- xmanager 2.0 for linux配置
- 【ROOTFS搭建】busybox的httpd...
- openwrt中luci学习笔记
- 什么是shell
- linux dhcp peizhi roc
- 关于Unix文件的软链接
- 求教这个命令什么意思,我是新...
- sed -e "/grep/d" 是什么意思...
- 谁能够帮我解决LINUX 2.6 10...
给主人留下些什么吧!~~
评论热议
1 0
- Linux驱动程序接口
- Linux驱动程序接口
- linux驱动程序接口
- Linux驱动程序接口
- Linux驱动程序接口
- linux驱动程序接口
- Linux设备驱动程序与外界的接口
- Linux驱动程序开发 - 设备控制接口
- Linux驱动程序开发 - 设备控制接口
- Linux驱动程序开发 - 设备控制接口
- poll接口《来自Linux驱动程序开发实例》
- 《嵌入式系统接口设计与Linux驱动程序开发》读书笔记
- Linux驱动程序
- Linux驱动程序
- linux驱动程序
- linux驱动程序
- jdbc接口及其驱动程序
- IDE接口驱动程序移植
- vim个人使用习惯设置
- 程序员的10大谚语
- 文件系统
- ioremap_nocache函数说明
- kfifo_alloc深度解析
- linux驱动程序接口
- 简单数学模板
- linux多文件编译方法
- asp.net中验证控件的使用方法
- grep常用方法小结
- urllib2模块
- linux错误代码表
- do{}while(0)的妙用
- linux编译及启动过程分析
原创粉丝点击
热门IT博客
热门问题
老师的惩罚
人脸识别
我在镇武司摸鱼那些年
重生之率土为王
我在大康的咸鱼生活
盘龙之生命进化
天生仙种
凡人之先天五行
春回大明朝
姑娘不必设防,我是瞎子
二年级数学题下册
数学题2年级
三年级下册英语全解
二年级作文大全
二年级的数学题
二年级上数学题
二年级数学下册期末试卷
二年级下册语文期末试卷
小学二年级上册
二年级短文
初二数学上册课本
初二上册数学
八下数学书
小学数学课本
初中课文
用在在在造句二年级
二度春
古贝春42度
保健品二度春
古贝春42度浓香型
42度古贝春价格一览表
燕南春42度价格
古贝春39度浓香型价格
古贝春42度浓香型白酒
五良春52度多少钱一瓶
金谷春52度价格
今缘春35度价格表
古贝春52度价格
古贝春百年老窖38度价格
电梯内老人活春二度梅开出
二建需要哪些书
二建考那几本书
二建考试书
考二建的书
考二建买什么书
二建买什么书
二建需要看什么书
二建的书
二建书电子版
二建房建书
二建价格