前言
x86 有 I/O端口(有load store 指令)与I/O内存(没有inb outb 指令) 的概念
x86 可以只有 IO内存
x86 可以 有 两个 : IO内存 和 IO端口
arm 只有 IO内存(有load store 指令)的概念,没有I/O端口(没有inb outb 指令) 的概念
------------------------------
x86-linux 有 I/O端口与I/O内存的概念
iowrite32(xx,xx) 函数 中调用了store 指令
outl(xx,xx) 函数中调用了 iol 指令
arm-linux 有 IO端口的概念,有I/O内存的概念
iowrite32(xx,xx) 函数 中调用了 store 指令
outl(xx,xx) 函数中调用了 iowrite32(xx,xx),最终还是调用了 store 指令
首先从IO讲起,关于IO 有很多概念:IO端口 IO接口 IO内存
而我们一般放在一块讨论的是 IO端口与IO接口 IO端口与IO内存, 这两个话题中的IO端口不是一个东西
而且我们讨论的外设寄存器 是 片上外设寄存器,IO内存涉及到 类ram接口的存储器和其他类ram接口设备
而一般来说其他片外的外设如果和soc不是以类ram接口相连
而是以其他总线方式(I2C SPI MDIO)相连
则是无法通过 IO端口或者IO内存的方式访问的, 一般通过(I2C SPI MDIO)总线方式访问这些片外外设里面的寄存器(注意:片外设备中也集成有存储模块,而我们把一个单元叫做一个寄存器,这里的寄存器不同于soc中的寄存器)
I/O端口与I/O接口
I/O接口 : 通常把介于主机和外设之间的一种缓冲电路称为I/O接口电路,简称I/O接口
I/O端口 : CPU与外设进行信息交换时,各类信息通过I/O接口存入不同的寄存器中.这些寄存器被称作I/O端口。
若干端口加上相应的控制电路才构成接口
I/O端口分为 控制寄存器、状态寄存器和数据寄存器
I/O端口与I/O内存
编址指的是访问外设寄存器的方式,相对访问内存来说的.
访问外设寄存器的方式,分为两种(根据外设编址分类)
1/统一编址
内存和IO空间(外设有自己的内存、缓冲区,外设的寄存器和内存)统一编址在总线上拿东西的.
操作外设和内存的汇编指令相同,指令一般都是RISC
统一编址也称为“I/O内存”方式,外设寄存器位于“内存空间”,而内存空间的布局在数据手册中已经确定.
总之,统一编址就是将所有设备寄存器,内存空间都编到一起(例如一个空间0-4G),用load store 相关指令访问,该空间叫iomem
2/独立编址
内存和IO空间 独立编址 就是说内存一部分的地址和I/O地址是重叠的
操作外设和内存的汇编指令不同,指令一般都是CISC,soc上多一个外设就要多几个汇编指令(用来操作soc中新添加的外设,这就是soc的升级)
独立编址也称为“I/O端口”方式,外设寄存器位于“I/O(地址)空间”
总之,独立编址就是将设备寄存器,内存空间不编到一起
内存空间(例如一个空间0-4G),用load store 相关指令访问,该空间叫iomem
设备控制器空间(例如一个空间0-4G),用 in out 相关指令访问,该空间叫ioport
究竟采用哪一种取决于系统的总体设计。在一个系统中也可以同时使用两种方式,也即是说部分设备 独立编址,部分设备统一编址.
Intel的x86微处理器支持I/O 独立编址,因为它们的指令系统中都有I/O指令,并设置了可以区分I/O访问和存储器访问的控制信号引脚。
一些微处理器或单片机,为了减少引脚,减少芯片占用面积,不支持I/O独立编址,只能采用存储器统一编址,例如arm处理器.
I/O内存(统一编址)
访问I/O内存的流程是:
1/ request_mem_region()
2/ ioremap()
3/ ioread8()/iowrite16()/ioread8_rep()/iowrite8_rep()
4/iounmap()
5/release_mem_region()
I/O端口(独立编址)
通过 I/O映射方式 访问I/O端口的流程是
1/ request_region
2/ inb/outb/inw/outw/inl/outl
3/ release_region
为什么要使用内存映射方式?内存映射方式的重点是什么?
这里的内存映射方式的目的是为了封装IO端口 ,操作起来像IO内存一样操作,即用 IO内存的api ioread8()/iowrite16()/ioread8_rep()/iowrite8_rep() 操作IO端口
关键函数是 ioport_map ,这个函数实现了封装.封装之后,就可以用IO内存的api操作IO端口
ioport_map 实现了把IO端口映射到IO内存(“内存空间”)
如果不用这个函数封装,那么就要用 inb/outb/inw/outw/inl/outl 操作,这样 IO端口 和 IO内存 的接口就不一样了.
访问 内存映射方式 访问I/O端口的流程是
1/ request_region
2/ ioport_map
3/ 用IO内存的api ioread8()/iowrite16()/ioread8_rep()/iowrite8_rep()
4/ ioport_unmap
5/ release_region
注意:
- 本文中的函数的参数如果用到了地址,除了下面的函数,其他用到的地址都是虚拟地址
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
参数中用到的是物理地址,返回的是虚拟地址
而这些虚拟地址的获得有两种方案
1/ioremap
2/.h文件中的地址
#define ioread8(p) ({ unsigned int __v = __raw_readb(p); __iormb(); __v; })
static inline u8 __raw_readb(const volatile void __iomem *addr)
{
u8 val;
asm volatile("ldrb %1, %0"
: "+Qo" (*(volatile u8 __force *)addr),
"=r" (val));
return val;
}
参考资料
IO端口和IO内存的区别及分别使用的函数接口
统一编址&独立编址