经常看到这些个名词,大致弄清了他们的区别和联系,以x86架构为例,简要说明下。哪里不对,还请指正。
关键词:内存空间、I/O空间,I/O port,I/O mem
1 内存空间和I/O空间
所有的设备中的每一个可访问单元,要想被CPU访问,都要有一个独立的可识别的地址,不然就混乱了。CPU所能访问的全部地址范围,被化分到内存空间和IO空间。
两个空间彼此独立,各有自己的访问方式,硬件上是怎么实现的划分暂且不管,只要告诉CPU该地址是在哪个空间,并给出地址,CPU就能通过对应的方式,最终访问到该单元。
其中:
1)内存空间
范围:一般为48根地址线表示的256T。
访问方式:通过将物理地址映射成内存空间线性地址,直接访问。即常见的user/kernel space:其中用户空间使用的线性地址范围是0~0x00007fffffffffff,内核空间线性地址范围是0xfff800000000000~0xffffffffffffffff。
2)I/O空间
范围:为16根地址线对应的64k(无论是否专门额外提供了这16根线)。
访问方式:通过将物理地址映射成I/O空间地址,采用特殊命令访问。直接映射到0x10000~0x1ffff。
可以看到,只给出物理地址而不告诉处于哪个空间,比如0x1400,是没有意义的。
2 I/O port和I/O mem
CPU外接的设备分为普通内存(理解为内存条)和外设(非内存条的其它设备)两类。
1)普通内存的可访问单元,就是映射到内存空间去访问的。
2)外设内部的可访问单元,可以映射到内存空间去访问(I/O mem),也可以映射到I/O空间访问(I/O port)。
注意:ISA设备使用的是地址低于0x10000的外设物理地址,只能映射为I/O port。其它的比如PCI设备,则映射为I/O mem。
3 内核空间下的I/O访问
3.1 I/O port的访问
1)首先申请对这块区域的占用,申请函数为request_region();
2)然后要将待访问的物理地址映射成I/O空间地址,映射函数为ioport_map(),x86下该函数的实现,就是address直接加上一个偏移量PIO_OFFSET并转为指针类型;
#define PIO_OFFSET 0x10000UL
#define PIO_MASK 0x0ffffUL
#define PIO_RESERVED 0x40000UL
void __iomem *ioport_map(unsigned long port, unsigned int nr)
{
/* 范围检查 */
if (port > PIO_MASK)
return NULL;
return (void __iomem *) (unsigned long) (port + PIO_OFFSET);
}
3)读写访问,读byte为例,是用汇编命令inb,一般封装成inb()
static inline u8 inb(u16 port)
{
u8 v;
asm volatile("inb %1,%0" : "=a" (v) : "dN" (port));
return v;
}
或者进一步封装成ioread8()等。函数源码如下:
unsigned int ioread8(void __iomem *addr)
{
/* 该接口为IO访问通用接口, 内部根据类型不同细分,
其中I/O port 使用 inb(), I/O mem 使用 readb()
*/
IO_COND(addr, return inb(port), return readb(addr));
return 0xff;
}
#define IO_COND(addr, is_pio, is_mmio) do { \
unsigned long port = (unsigned long __force)addr; \
if (port >= PIO_RESERVED) { \ /* 这里看到 I/O mem 起始的范围是 PIO_RESERVED */
is_mmio; \
} else if (port > PIO_OFFSET) { \
port &= PIO_MASK; \ /* 注意,将前面 map 时加上的 PIO_OFFSET 去除了 */
is_pio; \
} else \
bad_io_access(port, #is_pio ); \
} while (0)
3.2 I/O mem的访问
1)还是首先进行区域申请,申请函数是request_mem_region();
2)映射函数为ioremap_nocache(),该函数实现比较复杂,其返回地址即是处于vmalloc/ioremap space的地址指针(0xffffc90000000000~0xffffe8ffffffffff)。此外还有ioremap_uc/ioremap_cache...等多种映射方式。
void __iomem *ioremap_nocache(resource_size_t phys_addr, unsigned long size)
{
enum page_cache_mode pcm = _PAGE_CACHE_MODE_UC_MINUS;
return __ioremap_caller(phys_addr, size, pcm,
__builtin_return_address(0), false);
}
3)访问示例:可以使用同ioport公用的ioread8()函数,也可以使用readb()函数,也可以直接对该地址进行访问,如下:
void __iomem *ioaddr = ioremap_nocache(phys_addr, size);
// exp 1:
result = ioread8(ioaddr);
// exp 2:
result = readb(ioaddr);
// exp 3:
result = *(unsigned char *)ioaddr;
iounmap(ioaddr);
此外,实际使用时,针对I/O设备的访问,还有一些其它的封装和注意事项,比如PCI bus提供的pci_ioremap_bar(),pci_read_config_byte()等等。
4 用户空间的I/O访问
Linux的/dev/目录下分别存在一份全部mem空间和port空间的镜像,即/dev/mem和/dev/port。可以利用它们实现用户空间对I/O设备的访问。此外,设备I/O物理地址在内存空间和IO空间的信息,还会列在/proc/iomem和/proc/ioport两个文件中,内核态成功申请某一区域后,会在这两个用户文件中显示申请的结果信息。
4.1 I/O mem的访问
I/O mem的访问,主要是利用了/dev/mem,参见一个小工具devmem2,本文不再赘述。
4.2 I/O port的访问
I/O port的访问,主要是利用了/dev/port文件,示例如下:
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#define ADDR 0x400
#define LEN 0x10
int main(int argc, char *argv[])
{
char buf[LEN];
char i;
int fd = open("/dev/port", O_RDWR | O_SYNC);
lseek(fd, ADDR, SEEK_SET);
read(fd, buf, LEN);
for (i = 0; i < LEN; i ++)
printf("%02 ", buf[i]);
close(fd);
return 0;
}
补充:I/O port不能和I/O mem一样采用先mmap再访问,原因是该设备节点没有提供mmap系统调用。两个节点提供的file_operations结构是位于.\drivers\char\mem.c文件的mem_fops和port_fops局部静态变量。附全部的mem主设备节点下各子设备结构信息。
static const struct memdev {
const char *name;
umode_t mode;
const struct file_operations *fops;
struct backing_dev_info *dev_info;
} devlist[] = {
[1] = { "mem", 0, &mem_fops, &directly_mappable_cdev_bdi },
#ifdef CONFIG_DEVKMEM
[2] = { "kmem", 0, &kmem_fops, &directly_mappable_cdev_bdi },
#endif
[3] = { "null", 0666, &null_fops, NULL },
#ifdef CONFIG_DEVPORT
[4] = { "port", 0, &port_fops, NULL },
#endif
[5] = { "zero", 0666, &zero_fops, &zero_bdi },
[7] = { "full", 0666, &full_fops, NULL },
[8] = { "random", 0666, &random_fops, NULL },
[9] = { "urandom", 0666, &urandom_fops, NULL },
#ifdef CONFIG_PRINTK
[11] = { "kmsg", 0644, &kmsg_fops, NULL },
#endif
};
参考链接:
https://blog.youkuaiyun.com/ce123_zhouwei/article/details/7204458
https://blog.youkuaiyun.com/deep_pro/article/details/5315655
etc.