📄 8.html
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=gb2312">
<META NAME="GENERATOR" CONTENT="《良友》v2.1, 作者:安富国,http://winking.126.com">
<TITLE>驱动</TITLE>
</HEAD>
<BODY style="font-family: 宋体; font-size: 9pt">
<CENTER><TABLE CELLSPACING=10 CELLPADDING=10 WIDTH="60%" BGCOLOR="#FFB693" >
<TR>
<TD ALIGN=CENTER><FONT SIZE=+2><!--标题由此开始-->驱动</TD>
</TR>
</TABLE></CENTER>
<p><h3>目 录</h3>
<!--目录由此开始--><A NAME="Content" ID="Content"></A>
<OL><LI><A HREF="#I471">驱动</A></LI>
<OL><LI><A HREF="#I472">I/O端口</A></LI>
<LI><A HREF="#I473">from smth</A></LI>
<OL><LI><A HREF="#I474">基本结构</A></LI>
<LI><A HREF="#I475">驱动程序</A></LI>
<LI><A HREF="#I476">具体实现</A></LI>
</OL><LI><A HREF="#I477">PCI</A></LI>
<LI><A HREF="#I478">loopback</A></LI>
<LI><A HREF="#I479">Sis 900</A></LI>
<LI><A HREF="#I734">ISA总线DMA的实现</A></LI></OL></OL>
<hr><br><A NAME="I471" ID="I471"></A><center><b><font size=+2>驱动</font></b></center><br>
Linux系统支持三种类型的硬件设备:字符设备、块设备和网络设备。字符设备是直接读取的,不必使用缓冲区。例如,系统的串行口/dev/cua0和/dev/cua1。块设备每次只能读取一定大小的块的倍数,通常一块是512或者1024字节。块设备通过缓冲区读写,并且可以随机地读写。块设备可以通过它们的设备文件存取,但通常是通过文件系统存取。只有块设备支持挂接的文件系统。网络设备是通过BSD套接字界面存取的。<p>
Linux系统支持多种设备,这些设备的驱动程序之间有一些共同的特点:<br>
* 内核代码:设备驱动程序是系统内核的一部分,所以如果驱动程序出现错误的话,将可能严重地破坏整个系统。<br>
* 内核接口:设备驱动程序必须为系统内核或者它们的子系统提供一个标准的接口。例如,一个终端驱动程序必须为Linux内核提供一个文件I/O接口;一个SCSI设备驱动程序应该为SCSI子系统提供一个SCSI设备接口,同时SCSI子系统也应为系统内核提供文件I/O和缓冲区。<br>
* 内核机制和服务:设备驱动程序利用一些标准的内核服务,例如内存分配等。<br>
* 可装入:大多数的Linux设备驱动程序都可以在需要时装入内核,在不需要时卸载。<br>
* 可设置:Linux系统设备驱动程序可以集成为系统内核的一部分,至于哪一部分需要集成到内核中,可以在系统编译时设置。<p>
<center><A HREF="#Content">[目录]</A></center>
<hr><br><A NAME="I472" ID="I472"></A><center><b><font size=+2>I/O端口</font></b></center><br>
关键词:设备管理、驱动程序、I/O端口、资源<p>
申明:这份文档是按照自由软件开放源代码的精神发布的,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。更详细的情况请参阅GNU通用公共许可证(GPL),以及GNU自由文档协议(GFDL)。<p>
几乎每一种外设都是通过读写设备上的寄存器来进行的。外设寄存器也称为“I/O端口”,通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存器通常被连续地编址。CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O-mapped),另一种是内存映射方式(Memory-mapped)。而具体采用哪一种则取决于CPU的体系结构。<p>
有些体系结构的CPU(如,PowerPC、m68k等)通常只实现一个物理地址空间(RAM)。在这种情况下,外设I/O端口的物理地址就被映射到CPU的单一物理地址空间中,而成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。这就是所谓的“内存映射方式”(Memory-mapped)。<p>
而另外一些体系结构的CPU(典型地如X86)则为外设专门实现了一个单独地地址空间,称为“I/O地址空间”或者“I/O端口空间”。这是一个与CPU地RAM物理地址空间不同的地址空间,所有外设的I/O端口均在这一空间中进行编址。CPU通过设立专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元(也即I/O端口)。这就是所谓的“I/O映射方式”(I/O-mapped)。与RAM物理地址空间相比,I/O地址空间通常都比较小,如x86 CPU的I/O空间就只有64KB(0-0xffff)。这是“I/O映射方式”的一个主要缺点。<p>
Linux将基于I/O映射方式的或内存映射方式的I/O端口通称为“I/O区域”(I/O region)。在讨论对I/O区域的管理之前,我们首先来分析一下Linux是如何实现“I/O资源”这一抽象概念的。<p>
3.1 Linux对I/O资源的描述<p>
Linux设计了一个通用的数据结构resource来描述各种I/O资源(如:I/O端口、外设内存、DMA和IRQ等)。该结构定义在include/linux/ioport.h头文件中:<p>
<br>
struct resource {<br>
const char *name;<br>
unsigned long start, end;<br>
unsigned long flags;<br>
struct resource *parent, *sibling, *child;<br>
};<p>
各成员的含义如下:<p>
1. name指针:指向此资源的名称。<br>
2. start和end:表示资源的起始物理地址和终止物理地址。它们确定了资源的范围,也即是一个闭区间[start,end]。<br>
3. flags:描述此资源属性的标志(见下面)。<br>
4. 指针parent、sibling和child:分别为指向父亲、兄弟和子资源的指针。<p>
属性flags是一个unsigned long类型的32位标志值,用以描述资源的属性。比如:资源的类型、是否只读、是否可缓存,以及是否已被占用等。下面是一部分常用属性标志位的定义(ioport.h):<p>
<br>
/*<br>
* IO resources have these defined flags.<br>
*/<br>
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */<p>
#define IORESOURCE_IO 0x00000100 /* Resource type */<br>
#define IORESOURCE_MEM 0x00000200<br>
#define IORESOURCE_IRQ 0x00000400<br>
#define IORESOURCE_DMA 0x00000800<p>
#define IORESOURCE_PREFETCH 0x00001000 /* No side effects */<br>
#define IORESOURCE_READONLY 0x00002000<br>
#define IORESOURCE_CACHEABLE 0x00004000<br>
#define IORESOURCE_RANGELENGTH 0x00008000<br>
#define IORESOURCE_SHADOWABLE 0x00010000<br>
#define IORESOURCE_BUS_HAS_VGA 0x00080000<p>
#define IORESOURCE_UNSET 0x20000000<br>
#define IORESOURCE_AUTO 0x40000000<br>
#define IORESOURCE_BUSY 0x80000000<br>
/* Driver has marked this resource busy */<p>
<p>
指针parent、sibling和child的设置是为了以一种树的形式来管理各种I/O资源。<p>
3.2 Linux对I/O资源的管理<p>
Linux是以一种倒置的树形结构来管理每一类I/O资源(如:I/O端口、外设内存、DMA和IRQ)的。每一类I/O资源都对应有一颗倒置的资源树,树中的每一个节点都是一个resource结构,而树的根结点root则描述了该类资源的整个资源空间。<p>
基于上述这个思想,Linux在kernel/Resource.c文件中实现了对资源的申请、释放及查找等操作。<p>
3.2.1 I/O资源的申请<p>
假设某类资源有如下这样一颗资源树:<p>
节点root、r1、r2和r3实际上都是一个resource结构类型。子资源r1、r2和r3通过sibling指针链接成一条单向非循环链表,其表头由root节点中的child指针定义,因此也称为父资源的子资源链表。r1、r2和r3的parent指针均指向他们的父资源节点,在这里也就是图中的root节点。<p>
假设想在root节点中分配一段I/O资源(由图中的阴影区域表示)。函数request_resource()实现这一功能。它有两个参数:①root指针,表示要在哪个资源根节点中进行分配;②new指针,指向描述所要分配的资源(即图中的阴影区域)的resource结构。该函数的源代码如下(kernel/resource.c):<p>
<br>
int request_resource(struct resource *root, struct resource *new)<br>
{<br>
struct resource *conflict;<p>
write_lock(&resource_lock);<br>
conflict = __request_resource(root, new);<br>
write_unlock(&resource_lock);<br>
return conflict ? -EBUSY : 0;<br>
}<p>
<p>
对上述函数的NOTE如下:<p>
①资源锁resource_lock对所有资源树进行读写保护,任何代码段在访问某一颗资源树之前都必须先持有该锁。其定义如下(kernel/Resource.c):<p>
static rwlock_t resource_lock = RW_LOCK_UNLOCKED;<p>
②可以看出,函数实际上是通过调用内部静态函数__request_resource()来完成实际的资源分配工作。如果该函数返回非空指针,则表示有资源冲突;否则,返回NULL就表示分配成功。<p>
③最后,如果conflict指针为NULL,则request_resource()函数返回返回值0,表示成功;否则返回-EBUSY表示想要分配的资源已被占用。<p>
函数__request_resource()完成实际的资源分配工作。如果参数new所描述的资源中的一部分或全部已经被其它节点所占用,则函数返回与new相冲突的resource结构的指针。否则就返回NULL。该函数的源代码如下<p>
<br>
(kernel/Resource.c):<br>
/* Return the conflict entry if you can't request it */<br>
static struct resource * __request_resource<br>
(struct resource *root, struct resource *new)<br>
{<br>
unsigned long start = new->start;<br>
unsigned long end = new->end;<br>
struct resource *tmp, **p;<p>
if (end < start)<br>
return root;<br>
if (start < root->start)<br>
return root;<br>
if (end > root->end)<br>
return root;<br>
p = &root->child;<br>
for (;;) {<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -