原文链接: http://www.verydemo.com/demo_c269_i824.html
一、I/O端口
二、IO内存
三、IO端口和IO内存的区分及联系
内存空间:内存地址寻址范围,32位操作系统内存空间为2的32次幂,即4G。
IO空间:X86特有的一个空间,与内存空间彼此独立的地址空间,32位X86有64K的IO空间。
IO端口:当寄存器或内存位于IO空间时,称为IO端口。一般寄存器也俗称I/O端口,或者说I/O ports,这个I/O端口可以被映射在Memory Space,也可以被映射在I/O Space。
IO内存:当寄存器或内存位于内存空间时,称为IO内存。
四、外设IO端口物理地址的编址方式
1、统一编址
RISC指令系统的CPU(如,PowerPC、m68k、ARM等)通常只实现一个物理地址空间(RAM)。在这种情况下,外设I/O端口的物理地址就被映射到CPU的单一物理地址空间中,而成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。
2、独立编址
3、优缺点
独立编址主要优点是:
1)、I/O端口地址不占用存储器空间;使用专门的I/O指令对端口进行操作,I/O指令短,执行速度快。
2)、并且由于专门I/O指令与存储器访问指令有明显的区别,使程序中I/O操作和存储器操作层次清晰,程序的可读性强。
3)、同时,由于使用专门的I/O指令访问端口,并且I/O端口地址和存储器地址是分开的,故I/O端口地址和存储器地址可以重叠,而不会相互混淆。
4)、译码电路比较简单(因为I/0端口的地址空间一般较小,所用地址线也就较少)。
其缺点是:只能用专门的I/0指令,访问端口的方法不如访问存储器的方法多。
统一编址优点:
1)、由于对I/O设备的访问是使用访问存储器的指令,所以指令类型多,功能齐全,这不仅使访问I/O端口可实现输入/输出操作,而且还可对端口内容进行算术逻辑运算,移位等等;
2)、另外,能给端口有较大的编址空间,这对大型控制系统和数据通信系统是很有意义的。
这种方式的缺点是端口占用了存储器的地址空间,使存储器容量减小,另外指令长度比专门I/O指令要长,因而执行速度较慢。
五、Linux下访问IO端口
IO region是一种IO资源,因此它可以用resource结构类型来描述。
1、I/O映射方式
inb( )、inw( )、inl( )
分别从I/O端口读取1、2或4个连续字节。后缀“b”、“w”、“l”分别代表一个字节(8位)、一个字(16位)以及一个长整型(32位)。
inb_p( )、inw_p( )、inl_p( )
分别从I/O端口读取1、2或4个连续字节,然后执行一条“哑元(dummy,即空指令)”指令使CPU暂停。
outb( )、outw( )、outl( )
分别向一个I/O端口写入1、2或4个连续字节。
outb_p( )、outw_p( )、outl_p( )
分别向一个I/O端口写入1、2或4个连续字节,然后执行一条“哑元”指令使CPU暂停。
insb( )、insw( )、insl( )
分别从I/O端口读入以1、2或4个字节为一组的连续字节序列。字节序列的长度由该函数的参数给出。
outsb( )、outsw( )、outsl( )
分别向I/O端口写入以1、2或4个字节为一组的连续字节序列。
流程如下:
- struct
resource { -
resource_size_t start;// 资源范围的开始 -
resource_size_t end;// 资源范围的结束 -
const char *name; //资源拥有者的名字 -
unsigned long flags;// 各种标志 -
struct resource *parent, *sibling, *child;// 指向资源树中父亲,兄弟和孩子的指针 - };
任何设备驱动程序都可以使用下面三个函数,传递给它们的参数为资源树的根节点和要插入的新资源数据结构的地址:
request_resource( )
allocate_resource( )
release_resource( )
内核也为以上函数定义了一些应用于I/O端口的快捷函数:request_region( )分配I/O端口的给定范围,release_region( )释放以前分配给I/O端口的范围。当前分配给I/O设备的所有I/O地址的树都可以从/proc/ioports文件中获得。
2、内存映射方式
映射函数的原型为:
void *ioport_map(unsigned long port, unsigned int count);
通过这个函数,可以把port开始的count个连续的I/O端口重映射为一段“内存空间”。然后就可以在其返回的地址上像访问I/O内存一样访问这些I/O端口。但请注意,在进行映射前,还必须通过request_region( )分配I/O端口。
当不再需要这种映射时,需要调用下面的函数来撤消:
void ioport_unmap(void *addr);
在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地址,但是宜使用Linux内核的如下一组函数来完成访问I/O内存:·读I/O内存
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
·写I/O内存
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
流程如下:
六、Linux下访问IO内存
struct resource *requset_mem_region(unsigned long start, unsigned long len,char *name);
另外,可以通过/proc/iomem查看系统给各种设备的内存范围。
要释放所申请的I/O内存,应当使用release_mem_region()函数:
void release_mem_region(unsigned long start, unsigned long len)
申请一组I/O内存后, 调用ioremap()函数:
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
其中三个参数的含义为:
phys_addr:与requset_mem_region函数中参数start相同的I/O物理地址;
size:要映射的空间的大小;
flags:要映射的IO空间的和权限有关的标志;
功能:将一个I/O地址空间映射到内核的虚拟地址空间上(通过release_mem_region()申请到的)
流程如下:
六、ioremap和ioport_map
下面具体看一下ioport_map和ioport_umap的源码:
- void
__iomem *ioport_map(unsigned long port, unsigned int nr) - {
-
if (port > PIO_MASK) -
return NULL; -
return (void __iomem *) (unsigned long) (port + PIO_OFFSET); - }
-
- void
ioport_unmap(void __iomem *addr) - {
-
- }
- unsigned
int fastcall ioread8(void __iomem *addr) - {
-
IO_COND(addr, return inb(port), return readb(addr)); - }
-
- #define
VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET) - #define
IO_COND(addr, is_pio, is_mmio) do { \ -
unsigned long port = (unsigned long __force)addr; \ -
if (port < PIO_RESERVED) { \ -
VERIFY_PIO(port); \ -
port &= PIO_MASK; \ -
is_pio; \ -
} else { \ -
is_mmio; \ -
} \ - }
while (0) -
- 展开:
- unsigned
int fastcall ioread8(void __iomem *addr) - {
-
unsigned long port = (unsigned long __force)addr; -
if( port < 0x40000UL ) { -
BUG_ON( (port & ~PIO_MASK) != PIO_OFFSET ); -
port &= PIO_MASK; -
return inb(port); -
}else{ -
return readb(addr); -
} - }
七、总结
内存的延迟时间(也就是所谓的潜伏期,从FSB到DRAM)等于下列时间的综合:
FSB同主板芯片组之间的延迟时间(±1个时钟周期),芯片组同DRAM之间的延迟时间(±1个时钟周期),RAS到CAS延迟时间:RAS(2-3个时钟周期,用于决定正确的行地址),CAS延迟时间 (2-3时钟周期,用于决定正确的列地址),另外还需要1个时钟周期来传送数据,数据从DRAM输出缓存通过芯片组到CPU的延迟时间(±2个时钟周期)。一般的说明内存延迟涉及四个参数CAS(Column Address Strobe 行地址控制器)延迟,RAS(Row Address Strobe列地址控制器)-to-CAS延迟,RAS Precharge(RAS预冲电压)延迟,Act-to-Precharge(相对于时钟下沿的数据读取时间)延迟。其中CAS延迟比较重要,它反映了内存从接受指令到完成传输结果的过程中的延迟。大家平时见到的数据3—3—3—6中,第一参数就是CAS延迟(CL=3)。当然,延迟越小速度越快。