Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15318478
  • 博文数量: 2005
  • 博客积分: 11986
  • 博客等级: 上将
  • 技术积分: 22535
  • 用 户 组: 普通用户
  • 注册时间: 2007-05-17 13:56
文章分类

全部博文(2005)

文章存档

2014年(2)

2013年(2)

2012年(16)

2011年(66)

2010年(368)

2009年(743)

2008年(491)

2007年(317)

分类: 嵌入式

2009-09-09 21:52:28

luther@gliethttp:~$ vim /proc/ioports

0cf8-0cff : PCI conf1

可以看到0x0cf8是x86-pc架构读取pci控制寄存器的地址寄存器,向其中写入(总线id,设备id也就是常说的slot槽id,功能id也就是常说的设备id)组合就可以唯一锁定一个func功能,
然后通过0x0cfc地址读取到
0x0cf8锁定的func功能对应的256字节的配置空间数据.


   每个 PCI 外设有一个总线号, 一个设备号, 一个功能号标识. PCI 规范允许单个系统占用多达 256 个总线, 但是因为 256 个总线对许多大系统是不够的, Linux 现在支持 PCI 域. 每个 PCI 域可以占用多达 256 个总线. 每个总线占用 32 个设备, 每个设备可以是一个多功能卡(例如一个声音设备, 带有一个附加的 CD-ROM 驱动)有最多 8 个功能.

  以 PPC8548 为例,它提供了两个寄存器来实现 Configuration 操作,分别是 CFG_ADDR 和 CFG_DATA 寄存器,如果想对某个设备发起读/写操作,则首先将该设备的(总线号,设备号,功能号)写入 CFG_ADDR 中,这代表寻址一个具体的 PCI 设备,同时在 CFG_ADDR 中写入需要操作的配置空间寄存器的编号,最后从 CFG_DATA 中读取/写入相应的数据即可。
这里的CFG_ADDR和CFG_DATA就是下面pci_controller结构体中的
cfg_addr:该属性标志了当前 PCI 控制器发起 Configuration 访问方式时所需要写入的地址空间。

cfg_data:该属性标志了当前 PCI 控制器发起 Configuration 访问方式时所需要读写的数据空间。

  识别 PCI 总线上的设备
PCI 总线扫描的原理是从总线 0 扫描到总线 255,对于每条总线,系统都会扫描所有(总线号,设备号,功能号),通过 Configuration 方式读出每个设备的 Device ID 和 Vendor ID 寄存器,如果这两个寄存器的值是个有效值(非 0xFFFF),则说明当前设备是个有效的 PCI 设备/桥。进而再读取该设备的 Header Type 寄存器,如果该寄存器为 1,则表示当前设备是 PCI 桥,否则是 PCI 设备。
[http://blog.chinaunix.net/u1/38994/showart_2045054.html]

   配置访问空间

当系统需要访问 PCI 设备时,它需要产生 Configuration、Memory 或者 IO 的读写操作,对于 Memory/IO 的访问方式来说,它们需要定义一个地址范围,落在这个地址范围的操作会被认为是相应的 Memory/IO 的读写操作。

通常 PCI 设备提供了最多6组 Base Address 寄存器,在 PCI 总线扫描时,每当扫描出一个可用的 PCI 设备后,会对该设备的 Base Address 寄存器进行 Memory/IO 访问空间的配置。

而对于 PCI 桥来说,它只提供了2组 Base Address 寄存器,当 PCI 总线扫描出一个 PCI 桥后,也会对该桥的 Base Address 寄存器进行 Memory/IO 访问空间的配置。

需要注意的是,在构建系统之初,需要明确当前系统的地址范围,划分出特定的物理地址作为 PCI Memory 或者 PCI IO 空间,在给 PCI 设备/桥进行访问空间配置时,就是取事先约定的地址空间中的某段地址进行配置,所有设备/桥的访问地址不能冲突。定义系统的 Memory/IO 访问空间是在 mpc85xx_setup_hose() 函数中提供的(位于 arch/ppc/syslib/ppc85xx_setup.c 中)。

PCI 相关数据结构

Linux 提供了三类数据结构用以描述 PCI 控制器、PCI 设备以及 PCI 总线。

PCI 控制器

PCI 控制器用 pci_controller 结构来描述,它有以下几个主要的属性:

  • index:该属性标志 PCI 控制器的编号。
  • next:该属性指向下一个 PCI 控制器,通过 next 属性,PCI 控制器可以形成一个单向链表。
  • first_busno:该属性标志了连接在该控制器上第一条总线的编号。
  • last_busno:该属性标志了连接在该控制器上最后一条总线的编号。
  • ops:该属性标志了当前 PCI 控制器所对应的 PCI 配制空间读写操作函数。
  • io_space:该属性标志了当前 PCI 控制器所支持的 IO 地址空间。
  • mem_space:该属性标志了当前 PCI 控制器所支持的 Memory 地址区间。
  • cfg_addr:该属性标志了当前 PCI 控制器发起 Configuration 访问方式时所需要写入的地址空间。
  • cfg_data:该属性标志了当前 PCI 控制器发起 Configuration 访问方式时所需要读写的数据空间。
  • bus:该属性标志了当前 PCI 控制器所连接的 PCI 总线,它对应的数据结构是 pci_bus。

PCI 总线

PCI 总线用 pci_bus 结构来描述,它有以下几个主要的属性:

  • parent:可通过该属性索引到上层 PCI 总线。
  • self:该属性标志了连接的上行 PCI 桥(对应的数据结构是 pci_dev)。
  • children:该属性标志了总线连接的所有 PCI 子总线链表。
  • devices:该属性标志了总线连接的所有 PCI 设备链表。
  • ops:该属性标志了总线上所有 PCI 设备的配制空间读写操作函数。
  • number:该属性标志了当前 PCI 总线的编号。
  • primary:该属性标志了 PCI 上行总线编号。
  • secondary:该属性标志了 PCI 下行总线编号。
  • subordinate:该属性标志了能够访问到的最大总线编号。
  • resource:该属性标志了 Memory/IO 地址空间。

PCI 设备

PCI 设备用 pci_dev 结构来描述,它有以下几个主要的属性:

  • global_list:Linux 定义了一个全局列表来索引所有的 PCI 设备,该属性标志了这个全局列表的首指针。
  • bus:该属性标志了当前设备所在的 PCI 总线(对应的数据结构是 pci_bus)。
  • devfn:该属性标志了设备编号和功能编号。
  • vendor:该属性标志了供应商编号。
  • device:该属性标志了设备编号。
  • driver:该属性标志了设备对应的驱动代码(对应的数据结构是 pci_driver)。
  • irq:该属性标志了中断号。
  • resource:该属性标志了 Memory/IO 地址区间。

    当显示硬件地址时, 它可被显示为 2 个值( 一个 8-位总线号和一个 8-位 设备和功能号), 作为 3 个值( bus, device, 和 function), 或者作为 4 个值(domain, bus, device, 和 function); 所有的值常常用 16 进制显示.例如, /proc/bus/pci/devices 使用一个单个 16-位 字段(来便于分析和排序), 而 /proc/bus/busnumber 划分地址为 3 个字段. 下面内容显示了这些地址如何显示, 只显示了输出行的开始:$ lspci | cut -d: -f1-3
0000:00:00.0 Host bridge
0000:00:00.1 RAM memory
0000:00:00.2 RAM memory
0000:00:02.0 USB Controller
0000:00:04.0 Multimedia audio controller
0000:00:06.0 Bridge
0000:00:07.0 ISA bridge
0000:00:09.0 USB Controller
0000:00:09.1 USB Controller
0000:00:09.2 USB Controller
0000:00:0c.0 CardBus bridge
0000:00:0f.0 IDE interface
0000:00:10.0 Ethernet controller
0000:00:12.0 Network controller
0000:00:13.0 FireWire (IEEE 1394)
0000:00:14.0 VGA compatible controller
$ cat /proc/bus/pci/devices | cut -f1
0000
0001
0002
0010
0020
0030
0038
0048
0049
004a
0060
0078
0080
0090
0098
00a0
$ tree /sys/bus/pci/devices/
/sys/bus/pci/devices/
|-- 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0
|-- 0000:00:00.1 -> ../../../devices/pci0000:00/0000:00:00.1
|-- 0000:00:00.2 -> ../../../devices/pci0000:00/0000:00:00.2
|-- 0000:00:02.0 -> ../../../devices/pci0000:00/0000:00:02.0
|-- 0000:00:04.0 -> ../../../devices/pci0000:00/0000:00:04.0
|-- 0000:00:06.0 -> ../../../devices/pci0000:00/0000:00:06.0
|-- 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0
|-- 0000:00:09.0 -> ../../../devices/pci0000:00/0000:00:09.0
|-- 0000:00:09.1 -> ../../../devices/pci0000:00/0000:00:09.1
|-- 0000:00:09.2 -> ../../../devices/pci0000:00/0000:00:09.2
|-- 0000:00:0c.0 -> ../../../devices/pci0000:00/0000:00:0c.0
|-- 0000:00:0f.0 -> ../../../devices/pci0000:00/0000:00:0f.0
|-- 0000:00:10.0 -> ../../../devices/pci0000:00/0000:00:10.0
|-- 0000:00:12.0 -> ../../../devices/pci0000:00/0000:00:12.0
|-- 0000:00:13.0 -> ../../../devices/pci0000:00/0000:00:13.0
`-- 0000:00:14.0 -> ../../../devices/pci0000:00/0000:00:14.0
所有的 3 个设备列表都以相同顺序排列, 因为 lspci 使用 /proc 文件作为它的信息源. 拿 VGA 视频控制器作一个例子,0x00a意思是0000:00:14.0当划分为域(16位),总线(8位),设备(5位)和功能(3位).每个外设板的硬件电路回应查询,固定在3个地址空间:内存位置,I/O端口,和配置寄存器.前2个地址空间由所有在同一个PCI总线上的设备共享(即,当你存取一个内存位置,在那个 PCI 总线上的所有的设备在同一时间都看到总线周期).配置空间,另外的,采用地理式寻址.配置只一次一个插槽地查询地址,因此它们从不冲突.

$ tree /sys/bus/pci/devices/0000:00:10.0
/sys/bus/pci/devices/0000:00:10.0
|-- class
|-- config
|-- detach_state
|-- device
|-- irq
|-- power
| `-- state
|-- resource
|-- subsystem_device
|-- subsystem_vendor
`-- vendor
文件 config 是一个二进制文件, 它允许原始的 PCI 配置信息从设备中读出(就象由 /proc/bus/pci/*/* 提供的一样). 文件 verndor, subsytem_device, subsystem_vernder, 和 class 都指的是这个 PCI 设备的特定值( 所有的 PCI 设备都提供这个信息). 文件 irq 显示分配给这个 PCI 设备的当前的 IRQ, 并且文件 resource 显示这个设备分配的当前内存资源.

    对于中断, PCI 是容易处理的. 在 Linux 启动时, 计算机的固件已经分配一个唯一的中断号给设备, 并且驱动只需要使用它. 中断号被存储于配置寄存器 60 (PCI_INTERRUPT_LINE), 它是一个字节宽. 这允许最多 256 个中断线, 但是实际的限制依赖于使用CPU. 驱动不必费心去检查中断号, 因为在 PCI_INTERRUPT_LINE 中找到的值保证是正确的一个.如果设备不支持中断, 寄存器 61 (PCI_INTERRUPT_PIN) 是 0; 否则, 它是非零的值. 但是, 因为驱动知道设备是否是被中断驱动的, 它常常不需要读 PCI_INTERRUPT_PIN.因此, 用来处理中断的 PCI 特定的代码需要读配置字节来获得保存在一个局部变量中的中断号, 如同在下面代码中显示的. 除此之外, 在第 10 章的信息适用.result = pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &myirq);
if (result)
{
        /* deal with error */
}
本节剩下的提供了额外的信息给好奇的读者, 但是对编写程序不必要.一个 PCI 连接器有 4 个中断线, 并且外设板可使用任何一个或者多个. 每个管脚被独立连接到主板的中断控制器中, 因此中断可被共享而没有任何电路上的问题. 中断控制器接着负责映射中断线(引脚)到处理器的硬件; 这种依赖平台的操作留给控制器以便在总线自身上获得平台独立性.位于 PCI_INTERRUPT_PIN 的只读的配置寄存器用来告知计算机实际上使用哪个管脚. 值得记住每个设备板可有多到 8 个设备; 每个设备使用一个单个中断脚并且在它的配置寄存器中报告它. 在同一个设备板上的不同设备可使用不同的中断脚或者共享同一个.PCI_INTERRUPT_LINE 寄存器, 另一方面, 是读/写的. 当启动计算机, 固件扫描它的 PCI 设备并为每个设备设置寄存器固件中断脚是如何连接给它的 PCI 槽位. 这个值由固件分配, 因为只有固件知道主板如何连接不同的中断脚到处理器. 对于设备驱动, 但是, PCI_INTERRUPT_LINE 寄存器是只读的. 有趣的是, 近期的 Linux 内核版本在某些情况下可分配中断线, 不用依靠 BIOS.

识别 PCI 总线上的设备

PCI 总线扫描的原理是从总线 0 扫描到总线 255,对于每条总线,系统都会扫描所有(总线号,设备号,功能号),通过 Configuration 方式读出每个设备的 Device ID 和 Vendor ID 寄存器,如果这两个寄存器的值是个有效值(非 0xFFFF),则说明当前设备是个有效的 PCI 设备/桥。进而再读取该设备的 Header Type 寄存器,如果该寄存器为 1,则表示当前设备是 PCI 桥,否则是 PCI 设备。


9.2.1. I/O 端口分配如同你可能希望的, 你不应当离开并开始抨击 I/O 端口而没有首先确认你对这些端口有唯一的权限. 内核提供了一个注册接口以允许你的驱动来声明它需要的端口. 这个接口中的核心的函数是 request_region:
#include
struct resource *request_region(unsigned long first, unsigned long n, const char *name);
这个函数告诉内核, 你要使用 n 个端口, 从 first 开始. name 参数应当是你的设备的名子. 如果分配成功返回值是非 NULL. 如果你从 request_region 得到 NULL, 你将无法使用需要的端口. 所有的的端口分配显示在 /proc/ioports 中. 如果你不能分配一个需要的端口组, 这是地方来看看谁先到那里了.当你用完一组 I/O 端口(在模块卸载时, 也许), 应当返回它们给系统, 使用:
void release_region(unsigned long start, unsigned long n); 
还有一个函数以允许你的驱动来检查是否一个给定的 I/O 端口组可用:
int check_region(unsigned long first, unsigned long n); 
这里, 如果给定的端口不可用, 返回值是一个负错误码. 这个函数是不推荐的, 因为它的返回值不保证是否一个分配会成功; 检查和后来的分配不是一个原子的操作. 我们列在这里因为几个驱动仍然在使用它, 但是你调用一直使用 request_region, 它进行要求的加锁来保证分配以一个安全的原子的方式完成.9.2.2. 操作 I/O 端口在驱动硬件请求了在它的活动中需要使用的 I/O 端口范围之后, 它必须读且/或写到这些端口. 为此, 大部分硬件区别8-位, 16-位, 和 32-位端口. 常常你无法混合它们, 象你正常使用系统内存存取一样.[33]一个 C 程序, 因此, 必须调用不同的函数来存取不同大小的端口. 如果在前一节中建议的, 只支持唯一内存映射 I/O 寄存器的计算机体系伪装端口 I/O , 通过重新映射端口地址到内存地址, 并且内核向驱动隐藏了细节以便易于移植. Linux 内核头文件(特别地, 体系依赖的头文件 ) 定义了下列内联函数来存取 I/O 端口:unsigned inb(unsigned port); void outb(unsigned char byte, unsigned port); 读或写字节端口( 8 位宽 ). port 参数定义为 unsigned long 在某些平台以及 unsigned short 在其他的上. inb 的返回类型也是跨体系而不同的.unsigned inw(unsigned port); void outw(unsigned short word, unsigned port); 这些函数存取 16-位 端口( 一个字宽 ); 在为 S390 平台编译时它们不可用, 它只支持字节 I/O.unsigned inl(unsigned port); void outl(unsigned longword, unsigned port); 这些函数存取 32-位 端口. longword 声明为或者 unsigned long 或者 unsigned int, 根据平台. 如同字 I/O, "Long" I/O 在 S390 上不可用.

9.4.1. I/O 内存分配和映射I/O 内存区必须在使用前分配. 分配内存区的接口是( 在 定义):
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
这个函数分配一个 len 字节的内存区, 从 start 开始. 如果一切顺利, 一个 非NULL 指针返回; 否则返回值是 NULL. 所有的 I/O 内存分配来 /proc/iomem 中列出.内存区在不再需要时应当释放:
void release_mem_region(unsigned long start, unsigned long len); 
还有一个旧的检查 I/O 内存区可用性的函数:
int check_mem_region(unsigned long start, unsigned long len); 
但是, 对于 check_region, 这个函数是不安全和应当避免的.在存取内存之前, 分配 I/O 内嵌不是唯一的要求的步骤. 你必须也保证这个 I/O 内存已经对内核是可存取的. 使用 I/O 内存不只是解引用一个指针的事情; 在许多系统, I/O 内存根本不是可以这种方式直接存取的. 因此必须首先设置一个映射. 这是 ioremap 函数的功能, 在第 1 章的 "vmalloc 及其友"一节中介绍的. 这个函数设计来特别的安排虚拟地址给 I/O 内存区.一旦装备了 ioremap (和iounmap), 一个设备驱动可以存取任何 I/O 内存地址, 不管是否它是直接映射到虚拟地址空间. 记住, 但是, 从 ioremap 返回的地址不应当直接解引用; 相反, 应当使用内核提供的存取函数. 在我们进入这些函数之前, 我们最好回顾一下 ioremap 原型和介绍几个我们在前一章略过的细节.这些函数根据下列定义调用:
#include
void *ioremap(unsigned long phys_addr, unsigned long size);
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
void iounmap(void * addr);
首先, 你注意新函数 ioremap_nacache. 我们在第 8 章没有涉及它, 因为它的意思是明确地硬件相关的. 引用自一个内核头文件:"It’s useful if some control registers are in such an area, and write combining or read caching is not desirable.". 实际上, 函数实现在大部分计算机平台上与 ioremap 一致: 在所有 I/O 内存已经通过非缓冲地址可见的地方, 没有理由使用一个分开的, 非缓冲 ioremap 版本.9.4.2. 存取 I/O 内存在一些平台上, 你可能逃过作为一个指针使用 ioremap 的返回值的惩罚. 这样的使用不是可移植的, 并且, 更加地, 内核开发者已经努力来消除任何这样的使用. 使用 I/O 内存的正确方式是通过一系列为此而提供的函数(通过 定义的).从 I/O 内存读, 使用下列之一:
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
这里, addr 应当是从 ioremap 获得的地址(也许与一个整型偏移); 返回值是从给定 I/O 内存读取的.有类似的一系列函数来写 I/O 内存:
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
如果你必须读和写一系列值到一个给定的 I/O 内存地址, 你可以使用这些函数的重复版本:
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
 
void ioread32_rep(void *addr, void *buf, unsigned long count);
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
这些函数读或写 count 值从给定的 buf 到 给定的 addr. 注意 count 表达为在被写入的数据大小; ioread32_rep 读取 count 32-位值从 buf 开始.上面描述的函数进行所有的 I/O 到给定的 addr. 如果, 相反, 你需要操作一块 I/O 地址, 你可使用下列之一:
void memset_io(void *addr, u8 value, unsigned int count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
这些函数行为如同它们的 C 库类似物.如果你通览内核源码, 你可看到许多调用旧的一套函数, 当使用 I/O 内存时. 这些函数仍然可以工作, 但是它们在新代码中的使用不鼓励. 除了别的外, 它们较少安全因为它们不进行同样的类型检查. 但是, 我们在这里描述它们:
unsigned readb(address);
unsigned readw(address);
unsigned readl(address); 
这些宏定义用来从 I/O 内存获取 8-位, 16-位, 和 32-位 数据值.
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address); 
如同前面的函数, 这些函数(宏)用来写 8-位, 16-位, 和 32-位数据项.一些 64-位平台也提供 readq 和 writeq, 为 PCI 总线上的 4-字(8-字节)内存操作. 这个 4-字 的命名是一个从所有的真实处理器有 16-位 字的时候的历史遗留. 实际上, 用作 32-位 值的 L 命名也已变得不正确, 但是命名任何东西可能使事情更混淆.9.4.3. 作为 I/O 内存的端口一些硬件有一个有趣的特性: 一些版本使用 I/O 端口, 而其他的使用 I/O 内存. 输出给处理器的寄存器在任一种情况中相同, 但是存取方法是不同的. 作为一个使驱动处理这类硬件的生活容易些的方式, 并且作为一个使 I/O 端口和内存存取的区别最小化的方法, 2.6 内核提供了一个函数, 称为 ioport_map:
void *ioport_map(unsigned long port, unsigned int count); 
这个函数重映射 count I/O 端口和使它们出现为 I/O 内存. 从这点以后, 驱动可以在返回的地址上使用 ioread8 和其友并且根本忘记它在使用 I/O 端口.这个映射应当在它不再被使用时恢复:
void ioport_unmap(void *addr); 
这些函数使 I/O 端口看来象内存. 但是, 注意 I/O 端口必须仍然使用 request_region 在它们以这种方式被重映射前分配.9.4.4. 重用 short 为 I/O 内存short 例子模块, 在存取 I/O 端口前介绍的, 也能用来存取 I/O 内存. 为此, 你必须告诉它使用 I/O 内存在加载时; 还有, 你需要改变基地址来使它指向你的 I/O 区.例如, 这是我们如何使用 short 来点亮调试 LED, 在一个 MIPS 开发板上:
mips.root# ./short_load use_mem=1 base=0xb7ffffc0
mips.root# echo -n 7 > /dev/short0
使用 short 给 I/O 内存是与它用在 I/O 端口上同样的.下列片段显示了 short 在写入一个内存位置时用的循环:
while (count--) {
 iowrite8(*ptr++, address);
    wmb(); 

注意, 这里使用一个写内存屏障. 因为在很多体系上 iowrites8 可能转变为一个直接赋值, 需要内存屏障来保证以希望的顺序来发生.short 使用 inb 和 outb 来显示它如何完成. 对于读者它可能是一个直接的练习, 但是, 改变 short 来使用 ioport_map 重映射 I/O 端口, 并且相当地简化剩下的代码.9.4.5. 在 1 MB 之下的 ISA 内存一个最著名的 I/O 内存区是在个人计算机上的 ISA 范围. 这是在 640 KB(0xA0000)和 1 MB(0x100000)之间的内存范围. 因此, 它正好出现于常规内存 RAM 中间. 这个位置可能看起来有点奇怪; 它是一个在 1980 年代早期所作的决定的产物, 当时 640 KB 内存看来多于任何人可能用到的大小.这个内存方法属于非直接映射的内存类别. [36]你可以读/写几个字节在这个内存范围, 如同前面解释的使用 short 模块, 就是, 通过在加载时设置 use_mem.尽管 ISA I/O 内存只在 x86-类 计算机中存在, 我们认为值得用几句话和一个例子驱动.我们不会谈论 PCI 在本章, 因为它是最干净的一类 I/O 内存: 一旦你知道内存地址, 你可简单地重映射和存取它. PCI I/O 内存的"问题"是它不能为本章提供一个能工作的例子, 因为我们不能事先知道你的 PCI 内存映射到的物理地址, 或者是否它是安全的来存取任一这些范围. 我们选择来描述 ISA 内存范围, 因为它不但少干净并且更适合运行例子代码.为演示存取 ISA 内存, 我们还使用另一个 silly 小模块( 例子源码的一部分). 实际上, 这个称为 silly, 作为 Simple Tool for Unloading and Printing ISA Data 的缩写, 或者如此的东东.模块补充了 short 的功能, 通过存取整个 384-KB 内存空间和通过显示所有的不同 I/O 功能. 它特有 4 个设备节点来进行同样的任务, 使用不同的数据传输函数. silly 设备作为一个 I/O 内存上的窗口, 以类似 /dev/mem 的方式. 你可以读和写数据, 并且lseek 到一个任意 I/O 内存地址.因为 silly 提供了对 ISA 内存的存取, 它必须开始于从映射物理 ISA 地址到内核虚拟地址. 在 Linux 内核的早期, 一个人可以简单地安排一个指针给一个感兴趣的 ISA 地址, 接着直接对它解引用. 在现代世界, 但是, 我们必须首先使用虚拟内存系统和重映射内存范围. 这个映射使用 ioremap 完成, 如同前面为 short 解释的:
#define ISA_BASE 0xA0000
#define ISA_MAX 0x100000 /* for general memory access */
 
/* this line appears in silly_init */
io_base = ioremap(ISA_BASE, ISA_MAX - ISA_BASE); 
ioremap 返回一个指针值, 它能被用来使用 ioread8 和其他函数, 在"存取 I/O 内存"一节中解释.让我们回顾我们的例子模块来看看这些函数如何被使用. /dev/sillyb, 特有次编号 0, 存取 I/O 内存使用 ioread8 和 iowrite8. 下列代码显示了读的实现, 它使地址范围 0xA0000-0xFFFF 作为一个虚拟文件在范围 0-0x5FFF. 读函数构造为一个 switch 语句在不同存取模式上; 这是 sillyb 例子:
case M_8:
 while (count) {
 *ptr = ioread8(add);
 add++;
 count--;
 ptr++;
 
 }
 break;
实际上, 这不是完全正确. 内存范围是很小和很频繁的使用, 以至于内核在启动时建立页表来存取这些地址. 但是, 这个用来存取它们的虚拟地址不是同一个物理地址, 并且因此无论如何需要 ioremap.下 2 个设备是 /dev/sillyw (次编号 1) 和 /dev/silly1 (次编号 2). 它们表现象 /dev/sillyb, 除了它们使用 16-位 和 32-位 函数. 这是 sillyl 的写实现, 又一次部分 switch:
case M_32:
 while (count >= 4) {
 iowrite8(*(u32 *)ptr, add);
 add += 4;
 count -= 4;
 ptr += 4;
 
 }
 break;
最后的设备是 /dev/sillycp (次编号 3), 它使用 memcpy_*io 函数来进行同样的任务. 这是它的读实现的核心:
case M_memcpy:
 memcpy_fromio(ptr, add, count);
 break;
因为 ioremap 用来提供对 ISA 内存区的存取, silly 必须调用 iounmap 当模块卸载时:
iounmap(io_base); 



摘自:linux设备驱动第三版[中文].chm
阅读(2053) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~