目前我知道有两种:Memory-mapped I/O and port-mapped I/O 。参考 wiki [1].
但我又参考了另外一片文章[2]:
内存映射 有些体系结构的 CPU (如,PowerPC 、m68k 等)通常只实现一个物理地址空间( RAM )。在这种情况下,外设 I/O 端口的物理地址就被映射到 CPU 的单一物理地址空间中,而成为存储空间的一部分。此时,CPU 可以象访问一个内存单元那样访问外设 I/O 端口,而不需要 设立专门的外设 I/O 指令。这就是所谓的“存储空间映射方式”( Memory - mapped )。ARM 体系的 CPU 均采用这一模式.
为了验证这篇文章的说法“CPU 可以象访问一个内存单元那样访问外设 I/O 端口,而不需要 设立专门的外设 I/O 指令”。我查了了一下 linux 驱动的代码。发现使用 memory-mapped I/O 的驱动访问外设是使用 readb/readw/readl 这样的接口( write 类似)。代码定义在 arch/arm/include/asm/io.h (与平台无关)
/*
* Memory access primitives
* ------------------------
*
* These perform PCI memory accesses via an ioremap region. They don't
* take an address as such, but a cookie.
*
* Again, these are defined to perform little endian accesses. See the
* IO port primitives for more information.
*/
#ifndef readl
#define readb_relaxed(c) ({ u8 __r = __raw_readb(c); __r; })
#define readw_relaxed(c) ({ u16 __r = le16_to_cpu((__force __le16) \
__raw_readw(c)); __r; })
#define readl_relaxed(c) ({ u32 __r = le32_to_cpu((__force __le32) \
__raw_readl(c)); __r; })
#define writeb_relaxed(v,c) __raw_writeb(v,c)
#define writew_relaxed(v,c) __raw_writew((__force u16) cpu_to_le16(v),c)
#define writel_relaxed(v,c) __raw_writel((__force u32) cpu_to_le32(v),c)
#define readb(c) ({ u8 __v = readb_relaxed(c); __iormb(); __v; })
#define readw(c) ({ u16 __v = readw_relaxed(c); __iormb(); __v; })
#define readl(c) ({ u32 __v = readl_relaxed(c); __iormb(); __v; })
拿 readb 分析,发现它最终会调用__raw_readb 。__raw_readb 应该是与平台实现有关的,比如
(1)arm
定义在 arch/arm/include/asm/io.h
static inline u8 __raw_readb(const volatile void __iomem *addr)
{
u8 val;
asm volatile("ldrb %0, %1"
: "=r" (val)
: "Qo" (*(volatile u8 __force *)addr));
return val;
}
这里使用了汇编指令 ldrb ( Load Register Byte (register))来读值。如此看来,在 arm 平台用 mmio 访问是不是有自己的指令呢?
(2)powerpc
定义在 arch/powerpc/include/asm/io.h 。 不管是有没有使用 Indirect IO address tokens ,还是直接访问传过来的地址。都没有像 arm 那样通过一条汇编指令来访问。powerpc 看起来是符合这篇文章的说法的。
*
* When CONFIG_PPC_INDIRECT_MMIO is set, the platform can provide hooks
* on all MMIOs. (Note that this is all 64 bits only for now)
*
* To help platforms who may need to differentiate MMIO addresses in
* their hooks, a bitfield is reserved for use by the platform near the
* top of MMIO addresses (not PIO, those have to cope the hard way).
*
* The highest address in the kernel virtual space are:
*
* d0003fffffffffff # with Hash MMU
* c00fffffffffffff # with Radix MMU
*
* The top 4 bits are reserved as the region ID on hash, leaving us 8 bits
* that can be used for the field.
*
* The direct IO mapping operations will then mask off those bits
* before doing the actual access, though that only happen when
* CONFIG_PPC_INDIRECT_MMIO is set, thus be careful when you use that
* mechanism
*
* For PIO, there is a separate CONFIG_PPC_INDIRECT_PIO which makes
* all PIO functions call through a hook.
*/
#ifdef CONFIG_PPC_INDIRECT_MMIO
#define PCI_IO_IND_TOKEN_SHIFT 52
#define PCI_IO_IND_TOKEN_MASK (0xfful << PCI_IO_IND_TOKEN_SHIFT)
#define PCI_FIX_ADDR(addr) \
((PCI_IO_ADDR)(((unsigned long)(addr)) & ~PCI_IO_IND_TOKEN_MASK))
#define PCI_GET_ADDR_TOKEN(addr) \
(((unsigned long)(addr) & PCI_IO_IND_TOKEN_MASK) >> \
PCI_IO_IND_TOKEN_SHIFT)
#define PCI_SET_ADDR_TOKEN(addr, token) \
do { \
unsigned long __a = (unsigned long)(addr); \
__a &= ~PCI_IO_IND_TOKEN_MASK; \
__a |= ((unsigned long)(token)) << PCI_IO_IND_TOKEN_SHIFT; \
(addr) = (void __iomem *)__a; \
} while(0)
#else
#define PCI_FIX_ADDR(addr) (addr)
#endif
/*
* Non ordered and non-swapping "raw" accessors
*/
static inline unsigned char __raw_readb(const volatile void __iomem *addr)
{
return *(volatile unsigned char __force *)PCI_FIX_ADDR(addr);
}
请问 v 友怎么看待这个问题呢?难道是某块代码我看错或者理解错了?
[1]: https://en.wikipedia.org/wiki/Memory-mapped_I/O_and_port-mapped_I/O
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.