Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2150
  • 博文数量: 2
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 30
  • 用 户 组: 普通用户
  • 注册时间: 2015-06-15 08:45
文章分类
文章存档

2015年(2)

我的朋友

分类: 嵌入式

2015-06-30 11:37:26

众所周知,cpu访问并控制外设是通过读写外设寄存器实现的,那么寄存器的地址必然需要映射到cpu的地址空间中去(外设寄存器也称为I/O端口),因此有两种映射方式:I/O独立编址和I/O统一编址。

1.I/O独立编址。
统一和独立是相对于内存来说的,独立编址又称为端口映射,就是I/O端口自己独立使用一段地址空间,这段地址不在内存空间范围内,是给外设单独划分的一段地址,如x86采用的是独立编址。内核中将I/O端口映射并获取访问地址的接口是:

#include<linux/ioport.h>
struct resource *request_region(unsigned long first, unsigned long n, const char *name);
void release_region(unsigned long start, unsigned long n);
此函数告诉内核,要使用first开始的n个端口,name是设备的名称,即把这个设备映射到处理器的I/O访问区域中。
映射完成后,访问这些端口的接口是:
void outb(unsigned char byte, unsigned port);
void outw(unsigned char byte, unsigned port);
void outl(unsigned char byte, unsigned port);

或者:
unsigned inb(unsigned port);
unsigned inw(unsigned port);
unsigned inl(unsigned port);

这些操作都是一次只能传输一个数据,下面是串操作接口:
void insb(unsigned port, void *addr, unsigned long count);
void insw(unsigned port, void *addr, unsigned long count);
void insl(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);

从地址addr开始连续读写count数目的字节,只对单一端口port读写。

上面这些接口都是给驱动程序用的,即在内核中用,如果需要在用户空间访问I/O端口,需要满足以下条件,才能使用这些接口,这些接口在GNUC的库<sys/io.h>中定义。
  • 编译程序时,必须用-O强制内联函数展开。
  • 须用ioperm(单个端)或iopl(整个I/O空间)系统调用获取对端口进行I/O操作的权限,这两个函数是x86平台特有的。
  • 必须以root身份运行的程序才能调用ioperm和iopl。
如果宿主平台没有ioperm、iopl系统调用,也可以通过/dev/port设备文件来访问I/O端口。

2.I/O统一编址
统一编址又称I/O内存映射,就是把外设映射到内存空间,cpu不管访问外设还是内存,指令、方式都是一致的,如ARM、powerPC都是使用此种方式。I/O内存仅仅是类似RAM的一个区域,这种内存有很多用途,如存放视频数据、以太网数据包等。根据平台和总线的不同,I/O内存可能是也可能不是通过页表访问的,如果是通过页表访问,内核需要先调用ioremap安排物理地址使其对设备驱动程序可见。内核中I/O内存的分配映射接口:

struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
void release_mem_region(unsigned long start, unsigned long len);
该函数从start(物理地址)开始分配len字节长的区域,所有I/O内存分配情况可以从/proc/iomem中得到。系统在分配完之后内核还不能访问,还需要映射:

#include<asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
void iounmap(void *addr);


注意获取到的地址虽然和内存没什么区别,但是不能直接引用相应的指针,需要用以下接口访问:
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);

void iowrite8(u8 value, void *addr);
void iowrite16(u8 value, void *addr);
void iowrite32(u8 value, void *addr);


有的硬件比较特殊,某些版本用I/O端口,有些版本用I/O内存,所有为了方便,2.6内核提供了一个接口重新映射端口到内存:

void *ioport_map(unsigned long port, unsigned int count);
void ioport_unmap(void *addr);

该函数重新映射count个端口,使其看起来像I/O内存,这样驱动程序就可以在返回的地址上使用ioread8()等内存操作函数,而不必理会I/O端口和I/O内存之间的区别了。


I/O端口的访问和内存的最主要区别就是I/O操作具有边际效应,即必须按照指令发出的顺序依次执行,不能乱序或同时进行,否则达不到处理器要求的操作过程,而内存操作不同,为了提高效率,编译器一般会做些优化,如利用高速缓存保存数值、重新排序读写指令等,最终得到正确结果即可,中间的写操作可以等到最后刷到内存中。
阅读(452) | 评论(0) | 转发(0) |
0

上一篇:linux内存管理相关总结

下一篇:没有了

给主人留下些什么吧!~~