Linux 驱动之基础(一)Device-I/O
Memory Mapped IO
Getting Access to the Device
This address should not be used directly. Instead, to get an address suitable for passing to the accessor functions described below, you should call ioremap(). An address suitable for accessing the device will be returned to you.
After you've finished using the device (say, in your module's exit routine), call iounmap() in order to return the address space to the kernel. Most architectures allocate new address space each time you call ioremap(), and they can run out unless you call iounmap().
Accessing the device
The part of the interface most used by drivers is reading and writing memory-mapped registers on the device. Linux provides interfaces to read and write 8-bit, 16-bit, 32-bit and 64-bit quantities. Due to a historical accident, these are named byte, word, long and quad accesses. Both read and write accesses are supported; there is no prefetch support at this time.
The functions are named readb(), readw(), readl(), readq(), readb_relaxed(), readw_relaxed(), readl_relaxed(), readq_relaxed(), writeb(), writew(), writel() and writeq().
Some devices (such as framebuffers) would like to use larger transfers than 8 bytes at a time. For these devices, the memcpy_toio(), memcpy_fromio() and memset_io() functions are provided. Do not use memset or memcpy on IO addresses; they are not guaranteed to copy data in order.
The read and write functions are defined to be ordered. That is the compiler is not permitted to reorder the I/O sequence. When the ordering can be compiler optimised, you can use __readb() and friends to indicate the relaxed ordering. Use this with care.
Port Space Accesses
Port Space Explained
Another form of IO commonly supported is Port Space. This is a range of addresses separate to the normal memory address space. Access to these addresses is generally not as fast as accesses to the memory mapped addresses, and it also has a potentially smaller address space.
Unlike memory mapped IO, no preparation is required to access port space.
Accessing Port Space
Accesses to this space are provided through a set of functions which allow 8-bit, 16-bit and 32-bit accesses; also known as byte, word and long. These functions are inb(), inw(),inl(), outb(), outw() and outl().
Some variants are provided for these functions. Some devices require that accesses to their ports are slowed down. This functionality is provided by appending a _p to the end of the function. There are also equivalents to memcpy. The ins() and outs() functions copy bytes, words or longs to the given port.
__iomem pointer tokens
The data type for an MMIO address is an __iomem qualified pointer, such as void __iomem *reg. On most architectures it is a regular pointer that points to a virtual memory address and can be offset or dereferenced, but in portable code, it must only be passed from and to functions that explicitly operated on an __iomem token, in particular the ioremap() and readl()/writel() functions. The 'sparse' semantic code checker can be used to verify that this is done correctly.
While on most architectures, ioremap() creates a page table entry for an uncached virtual address pointing to the physical MMIO address, some architectures require special instructions for MMIO, and the __iomem pointer just encodes the physical address or an offsettable cookie that is interpreted by readl()/writel().
Differences between I/O access functions
1 | readq(), readl(), readw(), readb(), writeq(), writel(), writew(), writeb() |
These are the most generic accessors, providing serialization against other MMIO accesses and DMA accesses as well as fixed endianness for accessing little-endian PCI devices and on-chip peripherals. Portable device drivers should generally use these for any access to __iomem pointers.
Note that posted writes are not strictly ordered against a spinlock, see Documentation/driver-api/io_ordering.rst.
1 | readq_relaxed(), readl_relaxed(), readw_relaxed(), readb_relaxed(), |
On architectures that require an expensive barrier for serializing against DMA, these "relaxed" versions of the MMIO accessors only serialize against each other, but contain a less expensive barrier operation. A device driver might use these in a particularly performance sensitive fast path, with a comment that explains why the usage in a specific location is safe without the extra barriers.
See memory-barriers.txt for a more detailed discussion on the precise ordering guarantees of the non-relaxed and relaxed versions.
1 | ioread64(), ioread32(), ioread16(), ioread8(), |
These are an alternative to the normal readl()/writel() functions, with almost identical behavior, but they can also operate on __iomem tokens returned for mapping PCI I/O space with pci_iomap() or ioport_map(). On architectures that require special instructions for I/O port access, this adds a small overhead for an indirect function call implemented in lib/iomap.c, while on other architectures, these are simply aliases.
1 | ioread64be(), ioread32be(), ioread16be() |
These behave in the same way as the ioread32()/iowrite32() family, but with reversed byte order, for accessing devices with big-endian MMIO registers.
1 | hi_lo_readq(), lo_hi_readq(), hi_lo_readq_relaxed(), lo_hi_readq_relaxed(), |
Some device drivers have 64-bit registers that cannot be accessed atomically on 32-bit architectures but allow two consecutive 32-bit accesses instead. Since it depends on the particular device which of the two halves has to be accessed first, a helper is provided for each combination of 64-bit accessors with either low/high or high/low word ordering. A device driver must include either <linux/io-64-nonatomic-lo-hi.h> or <linux/io-64-nonatomic-hi-lo.h> to get the function definitions along with helpers that redirect the normal readq()/writeq() to them on architectures that do not provide 64-bit access natively.
1 | __raw_readq(), __raw_readl(), __raw_readw(), __raw_readb(), |
These are low-level MMIO accessors without barriers or byteorder changes and architecture specific behavior. Accesses are usually atomic in the sense that a four-byte __raw_readl() does not get split into individual byte loads, but multiple consecutive accesses can be combined on the bus. In portable code, it is only safe to use these to access memory behind a device bus but not MMIO registers, as there are no ordering guarantees with regard to other MMIO accesses or even spinlocks. The byte order is generally the same as for normal memory, so unlike the other functions, these can be used to copy data between kernel memory and device memory.
1 | inl(), inw(), inb(), outl(), outw(), outb() |
PCI I/O port resources traditionally require separate helpers as they are implemented using special instructions on the x86 architecture. On most other architectures, these are mapped to readl()/writel() style accessors internally, usually pointing to a fixed area in virtual memory. Instead of an __iomem pointer, the address is a 32-bit integer token to identify a port number. PCI requires I/O port access to be non-posted, meaning that an outb() must complete before the following code executes, while a normal writeb() may still be in progress. On architectures that correctly implement this, I/O port access is therefore ordered against spinlocks. Many non-x86 PCI host bridge implementations and CPU architectures however fail to implement non-posted I/O space on PCI, so they can end up being posted on such hardware.
In some architectures, the I/O port number space has a 1:1 mapping to __iomem pointers, but this is not recommended and device drivers should not rely on that for portability. Similarly, an I/O port number as described in a PCI base address register may not correspond to the port number as seen by a device driver. Portable drivers need to read the port number for the resource provided by the kernel.
There are no direct 64-bit I/O port accessors, but pci_iomap() in combination with ioread64/iowrite64 can be used instead.
Device memory mapping modes
Some architectures support multiple modes for mapping device memory. ioremap_*() variants provide a common abstraction around these architecture-specific modes, with a shared set of semantics.
ioremap() is the most common mapping type, and is applicable to typical device memory (e.g. I/O registers). Other modes can offer weaker or stronger guarantees, if supported by the architecture. From most to least common, they are as follows:
ioremap()
Uncached(无缓存):
CPU-side caches are bypassed, and all reads and writes are handled directly by the device
No speculative operations(无预测执行,可以认为是无乱序执行):
the CPU may not issue a read or write to this memory, unless the instruction that does so has been reached in committed program flow.
No reordering(无重排序,可以理解为加了强内存屏障指令):
The CPU may not reorder accesses to this memory mapping with respect to each other. On some architectures, this relies on barriers in readl_relaxed()/writel_relaxed().
No repetition:
The CPU may not issue multiple reads or writes for a single program instruction.
No write-combining(无写合并,cache 的一种策略):
Each I/O operation results in one discrete read or write being issued to the device, and multiple writes are not combined into larger writes. This may or may not be enforced when using __raw I/O accessors or pointer dereferences.
Non-executable:
The CPU is not allowed to speculate instruction execution from this memory (it probably goes without saying, but you're also not allowed to jump into device memory).
ioremap_wc()
Maps I/O memory as normal memory with write combining. Unlike ioremap(),
- The CPU may speculatively issue reads from the device that the
program didn't actually execute, and may choose to basically read
whatever it wants.
- The CPU may reorder operations as long as the result is consistent
from the program's point of view.
- The CPU may write to the same location multiple times, even when the
program issued a single write.
- The CPU may combine several writes into a single larger write.
This mode is typically used for video framebuffers, where it can increase performance of writes. It can also be used for other blocks of memory in devices (e.g. buffers or shared memory), but care must be taken as accesses are not guaranteed to be ordered with respect to normal ioremap() MMIO register accesses without explicit barriers.
On a PCI bus, it is usually safe to use ioremap_wc() on MMIO areas marked as IORESOURCE_PREFETCH, but it may not be used on those without the flag. For on-chip devices, there is no corresponding flag, but a driver can use ioremap_wc() on a device that is known to be safe.
ioremap_wt()
Maps I/O memory as normal memory with write-through caching. Like ioremap_wc(), but also
The CPU may cache writes issued to and reads from the device, and serve reads from that cache.
This mode is sometimes used for video framebuffers, where drivers still expect writes to reach the device in a timely manner (and not be stuck in the CPU cache), but reads may be served from the cache for efficiency. However, it is rarely useful these days, as framebuffer drivers usually perform writes only, for which ioremap_wc() is more efficient (as it doesn't needlessly trash the cache). Most drivers should not use this.
ioremap_np()
就像 ioremap() 一样,但明确请求非发布写语义。ioremap_np() 明确请求非发布语义,这意味着写入指令将不会在设备接收到写入数据之前完成。裸的 ioremap_np() 仅在某些架构上可用;在其他架构上,它始终返回 NULL。驱动程序通常不应使用它。
ioremap_uc()
它也将内存标记为非缓存,可移植驱动程序应避免使用 ioremap_uc()。
ioremap_cache()(将 I/O 内存当作普通 RAM 使用,支持 cache)
ioremap_cache() effectively maps I/O memory as normal RAM. CPU write-back caches can be used, and the CPU is free to treat the device as if it were a block of RAM.
Architecture example
Here is how the above modes map to memory attribute settings on the ARM64 architecture:
API | Memory region type and cacheability |
ioremap_np() | Device-nGnRnE |
ioremap() | Device-nGnRE |
ioremap_uc() | (not implemented) |
ioremap_wc() | Normal-Non Cacheable |
ioremap_wt() | (not implemented; fallback to ioremap) |
ioremap_cache() | Normal-Write-Back Cacheable |
说明:ARM64 将内存分为两种一种是 Device 类型、一种是 Normal
对于 Device 内存有:
Gathering 或者 non Gathering (G or nG):
这个特性表示对多个 memory 的访问是否可以合并,如果是 nG,表示处理器必须严格按照代码中内存访问来进行,不能把两次访问合并成一次。例如:代码中有 2 次对同样的一个地址的读访问,那么处理器必须严格进行两次 read transaction。
Re-ordering (R or nR):
这个特性用来表示是否允许处理器对内存访问指令进行重排。nR 表示必须严格执行 program order。
Early Write Acknowledgement (E or nE):
PE 访问 memory 是有问有答的(更专业的术语叫做 transaction),对于 write 而言,PE 需要 write ack 操作以便确定完成一个 write transaction。为了加快写的速度,系统的中间环节可能会设定一些 write buffer。nE 表示写操作的 ack 必须来自最终的目的地而不是中间的 write buffer。
参考文献
《Linux Document》