您的位置:首页 > 其它

内存映射-IO空间-ioremap-iounremap

2010-05-03 22:53 351 查看
<!--
/* Font Definitions */
@font-face
{font-family:宋体;
panose-1:2 1 6 0 3 1 1 1 1 1;
mso-font-alt:SimSun;
mso-font-charset:134;
mso-generic-font-family:auto;
mso-font-pitch:variable;
mso-font-signature:3 135135232 16 0 262145 0;}
@font-face
{font-family:"/@宋体";
panose-1:2 1 6 0 3 1 1 1 1 1;
mso-font-charset:134;
mso-generic-font-family:auto;
mso-font-pitch:variable;
mso-font-signature:3 135135232 16 0 262145 0;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-parent:"";
margin:0cm;
margin-bottom:.0001pt;
text-align:justify;
text-justify:inter-ideograph;
mso-pagination:none;
font-size:10.5pt;
mso-bidi-font-size:12.0pt;
font-family:"Times New Roman";
mso-fareast-font-family:宋体;
mso-font-kerning:1.0pt;}
/* Page Definitions */
@page
{mso-page-border-surround-header:no;
mso-page-border-surround-footer:no;}
@page Section1
{size:595.3pt 841.9pt;
margin:72.0pt 89.85pt 72.0pt 89.85pt;
mso-header-margin:42.55pt;
mso-footer-margin:49.6pt;
mso-paper-source:0;
layout-grid:15.6pt;}
div.Section1
{page:Section1;}
-->




1
)关于
IO
与内存空间:


X86
处理器中存在着
I/O
空间的概念,
I/O
空间是相对于内存空间而言的,它通过特定的指令
in

out
来访问。端口号标识了外设的寄存器地址。
Intel
语法的
in

out
指令格式为:

IN
累加器
,{
端口号
│DX}

OUT{
端口号
│DX},
累加器

目前,大多数嵌入式微控制器如
ARM

PowerPC
等中并不提供
I/O
空间,而仅存在内存空间。内存空间可以直接通过地址、指针来访问,程序和程序运行中使用的变量和其他数据都存在于内存空间中。

即便是在
X86
处理器中,虽然提供了
I/O
空间,如果由我们自己设计电路板,外设仍然可以只挂接在内存空间。此时,
CPU
可以像访问一个内存单元那样访问外设
I/O
端口,而不需要设立专门的
I/O
指令。因此,内存空间是必须的,而
I/O
空间是可选的。


2

inb

outb



Linux
设备驱动中,宜使用
Linux
内核提供的函数来访问定位于
I/O
空间的端口,这些函数包括:

·
读写字节端口(
8
位宽)

unsignedinb(unsignedport);

voidoutb(unsignedcharbyte,unsignedport);

·
读写字端口(
16
位宽)

unsignedinw(unsignedport);

voidoutw(unsignedshortword,unsignedport);

·
读写长字端口(
32
位宽)

unsignedinl(unsignedport);

voidoutl(unsignedlongword,unsignedport);

·
读写一串字节

voidinsb(unsignedport,void*addr,unsignedlongcount);

voidoutsb(unsignedport,void*addr,unsignedlongcount);

·insb()
从端口
port
开始读
count
个字节端口,并将读取结果写入
addr
指向的内存;
outsb()

addr
指向的内存的
count
个字节连续地写入
port
开始的端口。

·
读写一串字

voidinsw(unsignedport,void*addr,unsignedlongcount);

voidoutsw(unsignedport,void*addr,unsignedlongcount);

·
读写一串长字

voidinsl(unsignedport,void*addr,unsignedlongcount);

voidoutsl(unsignedport,void*addr,unsignedlongcount);

上述各函数中
I/O
端口号
port
的类型高度依赖于具体的硬件平台,因此,只是写出了
unsigned



3

readb

writeb:

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

·

I/O
内存

unsignedintioread8(void*addr);

unsignedintioread16(void*addr);

unsignedintioread32(void*addr);

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

unsignedreadb(address);

unsignedreadw(address);

unsignedreadl(address);

·

I/O
内存

voidiowrite8(u8value,void*addr);

voidiowrite16(u16value,void*addr);

voidiowrite32(u32value,void*addr);

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

voidwriteb(unsignedvalue,address);

voidwritew(unsignedvalue,address);

voidwritel(unsignedvalue,address);


4
)把
I/O
端口映射到

内存空间
”:

void*ioport_map(unsignedlongport,unsignedintcount);

通过这个函数,可以把
port
开始的
count
个连续的
I/O
端口重映射为一段

内存空间

。然后就可以在其返回的地址上像访问
I/O
内存一样访问这些
I/O
端口。当不再需要这种映射时,需要调用下面的函数来撤消:

voidioport_unmap(void*addr);

实际上,分析
ioport_map()
的源代码可发现,所谓的映射到内存空间行为实际上是给开发人员制造的一个

假象

,并没有映射到内核虚拟地址,仅仅是为了让工程师可使用统一的
I/O
内存访问接口访问
I/O
端口。



11.2.7
I/O
空间的映射

很多硬件设备都有自己的内存,通常称之为
I/O
空间。例如,所有比较新的图形卡都有几
MB

RAM
,称为显存,用它来存放要在屏幕上显示的屏幕影像。

1
.地址映射

根据设备和总线类型的不同,
PC
体系结构中的
I/O
空间可以在三个不同的物理地址范围之间进行映射:


1
)对于连接到
ISA
总线上的大多数设备

I/O
空间通常被映射到从
0xa0000

0xfffff
的物理地址范围,这就在
640K

1MB
之间留出了一段空间,这就是所谓的






2
)对于使用
VESA
本地总线(
VLB
)的一些老设备

这是主要由图形卡使用的一条专用总线:
I/O
空间被映射到从
0xe00000

0xffffff
的地址范围中,也就是
14MB

16MB
之间。因为这些设备使页表的初始化更加复杂,因此已经不生产这种设备。


3
)对于连接到
PCI
总线的设备

I/O
空间被映射到很大的物理地址区间,位于
RAM
物理地址的顶端。这种设备的处理比较简单。

2
.访问
I/O
空间

内核如何访问一个
I/O
空间单元?让我们从
PC
体系结构开始入手,这个问题很容易就可以解决,之后我们再进一步讨论其他体系结构。

不要忘了内核程序作用于虚拟地址,因此
I/O
空间单元必须表示成大于
PAGE_OFFSET
的地址。在后面的讨论中,我们假设
PAGE_OFFSET
等于
0xc0000000
,也就是说,内核虚拟地址是在第
4G


内核驱动程序必须把
I/O
空间单元的物理地址转换成内核空间的虚拟地址。在
PC
体系结构中,这可以简单地把
32
位的物理地址和
0xc0000000
常量进行或运算得到。例如,假设内核需要把物理地址为
0x000b0fe4

I/O
单元的值存放在
t1
中,把物理地址为
0xfc000000

I/O
单元的值存放在
t2
中,就可以使用下面的表达式来完成这项功能:



t1=*((unsignedchar*)(0xc00b0fe4));

t2=*((unsignedchar*)(0xfc000000));



在第六章我们已经介绍过
,
在初始化阶段
,
内核已经把可用的
RAM
物理地址映射到虚拟地址空间第
4G
的最初部分。因此,分页机制把出现在第一个语句中的虚拟地址
0xc00b0fe4
映射回到原来的
I/O
物理地址
0x000b0fe4
,这正好落在从
640K

1MB
的这段
“ISA


中。这正是我们所期望的。

但是,对于第二个语句来说,这里有一个问题,因为其
I/O
物理地址超过了系统
RAM
的最大物理地址。因此,虚拟地址
0xfc000000
就不需要与物理地址
0xfc000000
相对应。在这种情况下,为了在内核页表中包括对这个
I/O
物理地址进行映射的虚拟地址,必须对页表进行修改:这可以通过调用
ioremap()
函数来实现。
ioremap()

vmalloc()
函数类似,都调用
get_vm_area()
建立一个新的
vm_struct
描述符,其描述的虚拟地址区间为所请求
I/O
空间区的大小。然后,
ioremap()
函数适当地更新所有进程的对应页表项。

因此,第二个语句的正确形式应该为:



io_mem=ioremap(0xfb000000,0x200000);

t2=*((unsignedchar*)(io_mem+0x100000));



第一条语句建立一个
2MB
的虚拟地址区间,从
0xfb000000
开始;第二条语句读取地址
0xfc000000
的内存单元。驱动程序以后要取消这种映射,就必须使用
iounmap()
函数。



现在让我们考虑一下除
PC
之外的体系结构。在这种情况下,把
I/O
物理地址加上
0xc0000000
常量所得到的相应虚拟地址并不总是正确的。为了提高内核的可移植性,
Linux
特意包含了下面这些宏来访问
I/O
空间:

readb,readw,readl

分别从一个
I/O
空间单元读取
1

2
或者
4
个字节

writeb,writew,writel

分别向一个
I/O
空间单元写入
1

2
或者
4
个字节

memcpy_fromio,memcpy_toio

把一个数据块从一个
I/O
空间单元拷贝到动态内存中,另一个函数正好相反,把一个数据块从动态内存中拷贝到一个
I/O
空间单元

memset_io

用一个固定的值填充一个
I/O
空间区域

对于
0xfc000000I/O
单元的访问推荐使用这样的方法:

io_mem=ioremap(0xfb000000,0x200000);

t2=readb(io_mem+0x100000);

使用这些宏,就可以隐藏不同平台访问
I/O
空间所用方法的差异。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: