当程序涉及到对I/O端口进行访问时,我们总是可以看到使用 inb/outb
两个函数(或者说in/out函数家族),但是两个函数的实现,在不同处理器上的实现却有所不同,区别主要针对冯诺依曼架构(独立编址)和哈佛架构(统一编址)。还有ioread8/iowrite8
和readb/writeb
等函数,但在不同的架构上就大同小异了。
将存储器地址空间的一部分划分给I/O端口,使得我们能像访问存储器一样访问I/O地址空间
统一编址也称为“I/O内存”方式,外设寄存器位于“内存空间”
访问流程:
request_mem_region() -> ioremap() -> inb()/outb() ioread8()/iowrite8() readb()/writeb()-> iounmap() -> release_mem_region()
以arm处理器为例,2.6.32内核中,
arch/arm/include/asm/io.h
#define ioread8(p) ({ unsigned int __v = __raw_readb(p); __v; })
#define iowrite8(v,p) __raw_writeb(v, p)
#define readb(c) ({ __u8 __v = __raw_readb(__mem_pci(c)); __v; })
#define writeb(v,c) __raw_writeb(v,__mem_pci(c))
#define inb(p) ({ __u8 __v = __raw_readb(__io(p)); __v; })
#define outb(v,p) __raw_writeb(v,__io(p))
#define __raw_readb(a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a))
#define __raw_writeb(v,a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a) = (v))
将存储器地址空间和I/O地址空间分开单独编制,使用不同的总线访问
独立编址也称为“I/O端口”方式,外设寄存器位于“I/O(地址)空间”。
直接读取
访问流程:
request_region() -> inb()/outb() -> release_region()
以x86处理器为例,2.6.32内核中,
arch/x86/include/asm/io_64.h
__IN(b, "")
__OUT(b, "b", char)
#define __IN1(s) \
static inline RETURN_TYPE in##s(unsigned short port) \
{ \
RETURN_TYPE _v;
#define __IN2(s, s1, s2) \
asm volatile ("in" #s " %" s2 "1,%" s1 "0"
#define __IN(s, s1, i...) \
__IN1(s) __IN2(s, s1, "w") : "=a" (_v) : "Nd" (port), ##i); \
return _v; \
} \
__IN1(s##_p) __IN2(s, s1, "w") : "=a" (_v) : "Nd" (port), ##i); \
slow_down_io(); \
return _v; }
#define __OUT1(s, x) \
static inline void out##s(unsigned x value, unsigned short port) {
#define __OUT2(s, s1, s2) \
asm volatile ("out" #s " %" s1 "0,%" s2 "1"
#define __OUT(s, s1, x) \
__OUT1(s, x) __OUT2(s, s1, "w") : : "a" (value), "Nd" (port)); \
} \
__OUT1(s##_p, x) __OUT2(s, s1, "w") : : "a" (value), "Nd" (port)); \
slow_down_io(); \
}
用了很多宏定义,简单来说就是用了x86汇编的 in 和 out 指令
另外在 arch/x86/boot/boot.h 中也找到了 inb 和 outb 的定义,同样是用了x86汇编的 in 和 out 指令,不过 inb 和 outb 是以函数形式给出
/* Basic port I/O */
static inline void outb(u8 v, u16 port)
{
asm volatile("outb %0,%1" : : "a" (v), "dN" (port));
}
static inline u8 inb(u16 port)
{
u8 v;
asm volatile("inb %1,%0" : "=a" (v) : "dN" (port));
return v;
}
IO端口映射到IO内存(“内存空间”),再使用访问IO内存的函数来访问 IO端口。
void ioport_map(unsigned long port, unsigned int count);
通过这个函数,可以把port开始的count个连续的IO端口映射为一段“内存空间”,然后就可以在其返回的地址是像访问IO内存一样访问这些IO端口。
访问流程:
request_region() -> ioport_map() -> readb()/writeb() -> ioport_unmap()(nothing to do) -> release_region()
以x86处理器为例,2.6.32内核中,
arch/x86/include/asm/io_64.h
#define build_mmio_read(name, size, type, reg, barrier) \
static inline type name(const volatile void __iomem *addr) \
{ type ret; asm volatile("mov" size " %1,%0":reg (ret) \
:"m" (*(volatile type __force *)addr) barrier); return ret; }
#define build_mmio_write(name, size, type, reg, barrier) \
static inline void name(type val, volatile void __iomem *addr) \
{ asm volatile("mov" size " %0,%1": :reg (val), \
"m" (*(volatile type __force *)addr) barrier); }
build_mmio_read(readb, "b", unsigned char, "=q", :"memory")
build_mmio_write(writeb, "b", unsigned char, "q", :"memory")
build_mmio_read(__readb, "b", unsigned char, "=q", )
build_mmio_write(__writeb, "b", unsigned char, "q", )
#define __raw_readb __readb
#define __raw_writeb __writeb