Linux中的I/O访问

访问I/O的方式

当程序涉及到对I/O端口进行访问时,我们总是可以看到使用 inb/outb 两个函数(或者说in/out函数家族),但是两个函数的实现,在不同处理器上的实现却有所不同,区别主要针对冯诺依曼架构(独立编址)和哈佛架构(统一编址)。还有ioread8/iowrite8readb/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(地址)空间”。

I/O端口映射 Port mapped 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;
}

内存映射 Memory mapped I/O

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

你可能感兴趣的:(linux内核)