linux io端口地址范围,linux中的I/O地址空间

#include

unsigned

inb(unsigned port);

void

outb(unsigned char byte, unsigned

port);

unsigned

inw(unsigned port);

void

outw(unsigned short word, unsigned

port);

unsigned

inl(unsigned port);

void

outl(unsigned doubleword, unsigned port);inb( )/outb( )、inw( )/outw(

)、inl( )/outl(

)分别从I/O端口读取/写入1、2或4个连续字节。后缀“b”、“w”、“l”分别代表一个字节(8位)、一个字(16位)以及一个长整型(32位)

上面六个函数名用_p结尾,inb_p(

)/outb_p( )、inw_p( )/outw_p( )、inl_p( )/outl_p(

)分别从I/O端口读取/写入1、2或4个连续字节,然后执行一条“哑元(dummy,即空指令)”指令使CPU暂停。

void

insb(unsigned port, void *addr, unsigned long

count);

void

outsb(unsigned port, void *addr, unsigned long

count);

void

insw(unsigned port, void *addr, unsigned long

count);

void

outsw(unsigned port, void *addr, unsigned long

count);

void

insl(unsigned port, void *addr, unsigned long

count);

void

outsl(unsigned port, void *addr, unsigned long

count);insb( )/outsb( )、insw(

)/outsw( )、insl( )/outsl(

)分别从I/O端口读入/写入以1、2或4个字节为一组的连续字节序列。字节序列的长度由该函数的参数给出。

虽然访问I/O端口非常简单,但是检测哪些I/O端口已经分配给I/O设备可能就不这么简单了,对基于ISA总线的系统来说更是如此。通常,I/O设备驱动程序为了探测硬件设备,需要盲目地向某一I/O端口写入数据;但是,如果其他硬件设备已经使用这个端口,那么系统就会崩溃。为了防止这种情况的发生,内核使用“资源(resource)”来记录分配给每个硬件设备的I/O端口。

资源表示某个实体的一部分,这部分被互斥地分配给设备驱动程序。在这里,资源表示I/O端口地址的一个范围。每个资源对应的信息存放在resource数据结构中:struct

resource {

resource_size_t start;

resource_size_t

end;

const char

*name;

unsigned long

flags;

struct resource *parent, *sibling,

*child;

};

所有的同种资源都插入到一个树型数据结构(父亲、兄弟和孩子)中;例如,表示I/O端口地址范围的所有资源都包括在一个根节点为ioport_resource的树中。

节点的孩子被收集在一个链表中,其第一个元素由child指向。sibling字段指向链表中的下一个节点。

为什么使用树?例如,考虑一下IDE硬盘接口所使用的I/O端口地址-比如说从0xf000 到

0xf00f。那么,start字段为0xf000 且end

字段为0xf00f的这样一个资源包含在树中,控制器的常规名字存放在name字段中。但是,IDE设备驱动程序需要记住另外的信息,也就是IDE链主盘使用0xf000

到 0xf007的子范围,从盘使用0xf008 到

0xf00f的子范围。为了做到这点,设备驱动程序把两个子范围对应的孩子插入到从0xf000 到

0xf00f的整个范围对应的资源下。一般来说,树中的每个节点肯定相当于父节点对应范围的一个子范围。I/O端口资源树(ioport_resource)的根节点跨越了整个I/O地址空间(从端口0到65535)。

任何设备驱动程序都可以使用下面三个函数,传递给它们的参数为资源树的根节点和要插入的新资源数据结构的地址:request_resource()、allocate_resource()、release_resource()

内核也为以上函数定义了一些应用于I/O端口的:

#include

struct

resource *request_region(unsigned long start, unsigned long n,

char*name);

void

release_region(unsigned long start, unsigned long

n);

当前分配给I/O设备的所有I/O地址的树都可以从/proc/ioports文件中获得。

3. 访问I/O内存

Linux内核也提供了一组函数申请和释放某一范围的I/O内存:struct resource *requset_mem_region(unsigned long start, unsigned

long len,char *name);

这个函数从内核申请len个内存地址(在3G~4G之间的虚地址),而这里的start为I/O物理地址,name为设备的名称。注意:如果分配成功,则返回非NULL,否则,返回NULL。

另外,可以通过/proc/iomem查看系统给各种设备的内存范围。

要释放所申请的I/O内存,应当使用release_mem_region()函数:

void release_mem_region(unsigned long start, unsigned long

len)

申请一组I/O内存后, 还必须建立映射,调用ioremap()函数:

void

* ioremap(unsigned long phys_addr, unsigned long size, unsigned

long flags);

其中三个参数的含义为:

phys_addr:与requset_mem_region函数中参数start相同的I/O物理地址;

size:要映射的空间的大小;

flags:要映射的IO空间的和权限有关的标志;

返回值为映射后的虚拟内存地址

功能: 将一个I/O地址空间映射到内核的虚拟地址空间上(通过requset_mem_region()申请到的)

在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地址,但是工程师宜使用Linux内核的如下一组函数来完成访问I/O内存:·

读I/O内存

unsigned

int ioread8(void *addr);

unsigned

int ioread16(void *addr);

unsigned

int ioread32(void *addr);

与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):

unsigned

readb(address);

unsigned

readw(address);

unsigned

readl(address);

·写I/O内存

void

iowrite8(u8 value, void *addr);

void

iowrite16(u16 value, void *addr);

void

iowrite32(u32 value, void *addr);

与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):

void

writeb(unsigned value, address);

void

writew(unsigned value, address);

void

writel(unsigned value, address);

在从给定的buf向给定的I/O内存addr读取或写入count个值,count以被写入的数据大小为单位表示void

ioread8_rep(void *addr, void *buf, unsigned long

count);

void

ioread16_rep(void *addr, void *buf, unsigned long

count);

void

ioread32_rep(void *addr, void *buf, unsigned long

count);

void

iowrite8_rep(void *addr, const void *buf, unsigned long

count);

void

iowrite16_rep(void *addr, const void *buf, unsigned long

count);

void iowrite32_rep(void *addr, const void *buf, unsigned

long count);

在I/O内存上执行操作:

memset_io(address, value,

count);memcpy_fromio(dest, source,

nbytes);

memcpy_toio(dest,

source, nbytes);

4.把I/O端口映射到内存空间-访问I/O端口的另一种方式

映射函数的原型为:

void

*ioport_map(unsigned long port, unsigned int

count);

通过这个函数,可以把port开始的count个连续的I/O端口重映射为一段“内存空间”。然后就可以在其返回的地址上像访问I/O内存一样(ioread/iowrite函数组)访问这些I/O端口。

但请注意,在进行映射前,还必须通过request_region(

)分配I/O端口。

当不再需要这种映射时,需要调用下面的函数来撤消:

void

ioport_unmap(void *addr);

为什么要申请虚拟内存然后才进行映射?

留给读者的思考:直接访问I/O端口、把I/O端口映射到内存进行访问,以及访问I/O内存,三者之间有什么区别,在驱动程序开发中如何具体应用?

参考:陈莉君老师的博客-驱动开发中的I/O地址空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值