§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介绍

表示打开成功,返回负数表示失败。如果驱动程序没有提供open入口,则只要/dev/driver文件存在就认为打开成功。
(9) release,即close操作

设备驱动程序所提供的入口点,在设备驱动程序初始化的时候向系统进行登记,以便系统在适当的时候调用。

§4 PCI字符设备驱动程序

要设计PCI设备驱动程序,必须进一步结合硬件设备和PCI总线的特性。设计PCI设备驱动程序的重要任务是找寻相应的硬件并实现对它的访问。作为外围设备的硬件必须响应三种地址空间的访问,即内存,IO,寄存器地址空间(寄存器是CPU里面的,而内存是CPU外面数据总线上的)。前两种地址空间可以为PCI总线上的所有设备共享。寄存器空间占用物理地址,可以通过特殊的函数来访问配置寄存器。一旦可以访问配置寄存器,设备驱动程序就可以访问硬件了。每个设备的PCI配置寄存器均由256Bytes构成,其中64Bytes是标准化的,4Bytes标识了一个唯一的函数ID,通过这个ID驱动程序就可以定位该设备。

(PS:PCIPeripheral ComponentInterconnect OR Personal Computer Interface),是一种连接电子计算机主板外部设备总线标准。PCI总线地址总线数据总线是分时复用的,支持即插即用 (plug and play)、中断共享等功能。分时复用的好处是一方面可以节省接插件的引脚数,另一方面便于实现突发数据传输。数据传输时,由一个PCI设备做发起者(主控、Initiator或Master),而另一个PCI设备做目标(从设备、Target或Slave)。总线上所有时序的产生与控制都由Master来发起。PCI总线在同一时刻只能供一对设备完成传输。这就要求有一个仲裁机构来决定谁有权拿到总线的主控权。)

存取系统中的字符设备和存取系统文件一样。应用程序使用标准的系统调用来打开、读写和关闭设备,就像使用一个文件-样。当字符设备初始化时,通过向 chrdevs数组中添加一个入口,设备驱动程序在系统内核中注册。chrdevs数组由device_struct数据结构组成。设备的主设备号用来作为此chrdevs的索引,因为一个设备的主设备号是固定的。

LINUX系统里,通过调用register_chrdev向系统注册字符型设备驱动程序。register_chrdev定义为:

#include linux/fs.h

#include linux/errno.h

intregister_chrdev(unsigned int major, const char *name,struct file_operations*fops);

表示成功。返回-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

intpcibios_find_device(unsigned short vendor, unsigned short id, unsigned shortindex, 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,unsignedchar b*ptr)

int pcibios_write_config_byte(unsignedchar bus, unsigned char function,unsigned char where,unsignedchar b*ptr)

类似的还有:

个地址区,类型可以为内存区或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, structpt_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。正常中断与快中断的区别在于: 从正常中断返回时,内核可以利用机会调度更优先的进程执行; 而快中断不进行调度立即恢复被中断程序的执行.

表示成功,返回-INVAL表示irq>15或handler==NULL,返回-EBUSY表示中断已经被占用且不能共享。

中断处理函数形式为:

void xxx_irq_handler(int xxx_irq,void *dev_id, structpt_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有如下定义:

比特组,其相应取值的宏定义及含义如下表:

命令码取值宏定义及含义

比特组名称取值宏定义含义

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);

表示成功,-1失败。

§9.调用Linux内核函数

Linux有许多内核函数可以调用。例如;

1)memcpy_fromfs( *toptr, *fromptr,sizeof()); // 用于从文件系统传输数据 
2)memcpy_tofs ( *toptr, *fromptr,sizeof()); // 用于将数据传输到文件系统

#include asm/segment.h

voidmemcpy_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);

voidkfree(void * ptr);

参数len为希望申请的字节数,ptr为要释放的内存指针。priority为分配内存操作的优先级,即在没有足够空闲内存时如何操作,一般用GFP_KERNEL。 
7)与中断和内存不同,使用一个没有申请的I/O端口不会使CPU产生异常,也 
就不会导致诸如\"segmentationfault\"一类的错误发生。任何进程都可以访问任何一个I/O端口。此时系统无法保证对I/O端口的操作不会发生冲突,甚至会因此而使系统崩溃。因此,在使用I/O端口前,也应该检查此I/O端口是否已有别的程序在使用,若没有,再把此端口标记为正在使用,在使用完以后释放它。
这样需要用到如下几个函数:

int check_region(unsigned int from, unsigned int extent);

voidrequest_region(unsigned int from, unsigned int extent, const char *name);

voidrelease_region(unsigned int from, unsigned int extent);

表示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里。

Linux驱动程序接口的更多相关文章

  1. Linux驱动程序开发 - 设备控制接口

    (2008-08-08 15:02:19) 转载▼ 标签: it linux kernel driver 分类: Linux 序言设备驱动程序的一个基本功能就是管理和控制设备,同时为用户应用程序提供管 ...

  2. Linux中断(interrupt)子系统之四:驱动程序接口层 & 中断通用逻辑层【转】

    转自:http://blog.csdn.net/droidphone/article/details/7497787 在本系列文章的第一篇:Linux中断(interrupt)子系统之一:中断系统基本 ...

  3. 第六章 第一个Linux驱动程序: 统计单词个数

    一.编写Linux驱动程序的步骤 第1 步:建立Linux 驱动骨架(装载和卸载Linux 驱动) 骨架部分主要是Linux驱动的初始化和退出函数,代码如下: #include <linux/m ...

  4. Linux驱动程序学习【转】

    本文转载自: 一直在学习驱动,对于下面这篇文章,本人觉得简洁明了,基本符合我们学习驱动的进度与过程,现转发到自己的博客,希望能与更多的朋友分享. 了解Linux驱动程序技巧学习的方法很重要,学习lin ...

  5. 第六章 第一个Linux驱动程序:统计单词个数

    现在进入了实战阶段,使用统计单词个数的实例让我们了解开发和测试Linux驱动程序的完整过程.第一个Linux驱动程序是统计单词个数. 这个Linux驱动程序没有访问硬件,而是利用设备文件作为介质与应用 ...

  6. 详细讲解Linux驱动程序

    一  编写Linux驱动程序 1.建立Linux驱动骨架 Linux内核在使用驱动时需要装载与卸载驱动 装载驱动:建立设备文件.分配内存地址空间等:module_init 函数处理驱动初始化 卸载驱动 ...

  7. 第6章 第一个Linux驱动程序:统计单词个数

    编写一个Linux的一般步骤: 第1步:建立Linux驱动骨架(装载和卸载Linux驱动) 第2步:注册和注销设备文件 第3步:指定和驱动相关的信息 第4步:指定回调函数 第5步:编写业务逻辑 第6步 ...

  8. Linux 驱动程序/内核模块/ko文件

    Linux 驱动程序/内核模块/ko文件 一.内核模块加载机制 1.解析 Linux 内核可装载模块的版本检查机制 二.驱动/内核模块 编译 1.The Linux Kernel Module Pro ...

  9. 在ubuntu上为android系统编写Linux驱动程序【转】

    本文转载自:http://blog.csdn.net/luoshengyang/article/details/6568411 在智能手机时代,每个品牌的手机都有自己的个性特点.正是依靠这种与众不同的 ...

随机推荐

  1. Unity与Web结合

    偶然在论坛上看到了一篇文章,觉的挺有意思,转载一下,之前做游戏,现在做前端,这篇文章不错..转载 Unity WebPlayer 写在前面 最近在做unity与web之间通讯的项目,在网上搜索了一些资 ...

  2. [Vue]vue中各选项及钩子函数执行顺序

    在vue中,实例选项和钩子函数和{{}}表达式都是不需要手动调用就可以直接执行的. 一.生命周期图示 二.vue中各选项及钩子函数执行顺序 1.在页面首次加载执行顺序有如下: beforeCreate ...

  3. git-----初始化配置添加用户名和密码

    Git是分布式版本控制系统,GitHub 是最大的 Git 版本库托管商,是成千上万的开发者和项目能够合作进行的中心. 大部分 Git 版本库都托管在 GitHub,很多开源项目使用 GitHub 实 ...

  4. wireshark初学者使用

    介绍 Wireshark是一款网络封包分析软件,截取网络封包,显示其封包的详细信息.日常工作中用的比较多.在使用wireshark之前须了解常用的网络协议.如:tcp,http,ip,udp等.(其实 ...

  5. Effective C++学习笔记(1)

    最近刚看完Effective C++,记录一下当前几个比较常用的方法. 1.以独立语句将newed对象置入智能指针 智能指针是以对象管理资源,在构造函数中获得资源并在析构函数中释放资源​ 以下调用:​ ...

  6. IOS-程序员和设计师必备的20个CSS工具

    程序员和设计师必备的20个CSS工具   CSS工具是现今网站开发人员和设计人员使用的最必要和最重要的工具之一.这是因为这些CSS工具,可以为开发人员和设计人员简化手头的工作,大大减少web开发和设计 ...

  7. ubuntu下自动备份mysql数据库

    转载自:Mayi mysql的安装目录为:/var/lib/mysql 下面咱们来一起完成自动备份mysql. 备份目录为:/home/mydb 并且在每天下午18:30分以mysqldata_201 ...

  8. C++进阶4.C++知识整理

    C++知识整理(多益笔试) 20131012 前言: 还是关于笔试知识的整理,主要是面向对象的知识还有一些常见的语法知识. 1.还是C++内存管理的知识 C++中程序的内存分布如下: 栈:向下增长,可 ...

  9. MySQL Index Condition Pushdown

    Index Condition Pushdown (ICP)是MySQL 5.6 版本中的新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式.[Index Condition Pushdown] ...

  10. 一个好工具-everything-可以找到浏览器的所有缓存

    下载路径http://www.voidtools.com/downloads/ 我用它来寻找浏览器缓存的google瓦片.