Linux下的IO port, IO mem, IO space, Mem space及访问方式

经常看到这些个名词,大致弄清了他们的区别和联系,以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.csdn.net/ce123_zhouwei/article/details/7204458 

https://blog.csdn.net/deep_pro/article/details/5315655

etc.

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值