📄 ch09s04.html
字号:
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>9.4. 使用 I/O 内存-Linux设备驱动第三版(中文版)-开发频道-华星在线</title>
<meta name="description" content="驱动开发-开发频道-华星在线" />
<meta name="keywords" content="Linux设备驱动,中文版,第三版,ldd,linux device driver,驱动开发,电子版,程序设计,软件开发,开发频道" />
<meta name="author" content="华星在线 www.21cstar.com QQ:610061171" />
<meta name="verify-v1" content="5asbXwkS/Vv5OdJbK3Ix0X8osxBUX9hutPyUxoubhes=" />
<link rel="stylesheet" href="docbook.css" type="text/css">
<meta name="generator" content="DocBook XSL Stylesheets V1.69.0">
<link rel="start" href="index.html" title="Linux 设备驱动 Edition 3">
<link rel="up" href="ch09.html" title="第 9 章 与硬件通讯">
<link rel="prev" href="ch09s03.html" title="9.3. 一个 I/O 端口例子">
<link rel="next" href="ch09s05.html" title="9.5. 快速参考">
</head>
<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
<div class="navheader">
<table width="100%" summary="Navigation header">
<tr><th colspan="3" align="center">9.4. 使用 I/O 内存</th></tr>
<tr>
<td width="20%" align="left">
<a accesskey="p" href="ch09s03.html">上一页</a> </td>
<th width="60%" align="center">第 9 章 与硬件通讯</th>
<td width="20%" align="right"> <a accesskey="n" href="ch09s05.html">下一页</a>
</td>
</tr>
</table>
<hr>
</div>
<div class="sect1" lang="zh-cn">
<div class="titlepage"><div><div><h2 class="title" style="clear: both">
<a name="UsingIOMemory.sect"></a>9.4. 使用 I/O 内存</h2></div></div></div>
<p>尽管 I/O 端口在 x86 世界中流行, 用来和设备通讯的主要机制是通过内存映射的寄存器和设备内存. 2 者都称为 I/O 内存, 因为寄存器和内存之间的区别对软件是透明的.</p>
<p>I/O 内存是简单的一个象 RAM 的区域, 它被处理器用来跨过总线存取设备. 这个内存可用作几个目的, 例如持有视频数据或者以太网报文, 同时实现设备寄存器就象 I/O 端口一样的行为(即, 它们有读和写它们相关联的边际效果).</p>
<p>存取 I/O 内存的方式依赖计算机体系, 总线, 和使用的设备, 尽管外设到处都一样. 本章的讨论主要触及 ISA 和 PCI 内存, 而也试图传递通用的信息. 尽管存取 PCI 内存在这里介绍, 一个 PCI 的通透介绍安排在第 12 章.</p>
<p>依赖计算机平台和使用的总线, I/O 内存可以或者不可以通过页表来存取. 当通过页表存取, 内核必须首先安排从你的驱动可见的物理地址, 并且这常常意味着你必须调用 ioremap 在做任何 I/O 之前. 如果不需要页表, I/O 内存位置看来很象 I/O 端口, 并且你只可以使用正确的包装函数读和写它们.</p>
<p>不管是否需要 ioremap 来存取 I/O 内存, 不鼓励直接使用 I/O 内存的指针. 尽管(如同在 "I/O 端口和 I/O 内存" 一节中介绍的 )I/O 内存如同在硬件级别的正常 RAM 一样寻址, 在"I/O 寄存器和传统内存"一节中概述的额外的小心建议避免正常的指针. 用来存取 I/O 内存的包装函数在所有平台上是安全的并且在任何时候直接的指针解引用能够进行操作时, 会被优化掉.</p>
<p>因此, 尽管在 x86 上解引用一个指针能工作(在现在), 不使用正确的宏定义阻碍了驱动的移植性和可读性.</p>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="IOMemoryAllocationandMapping.sect"></a>9.4.1. I/O 内存分配和映射</h3></div></div></div>
<p>I/O 内存区必须在使用前分配. 分配内存区的接口是( 在 <linux/ioport.h> 定义):</p>
<pre class="programlisting">
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
</pre>
<p>这个函数分配一个 len 字节的内存区, 从 start 开始. 如果一切顺利, 一个 非NULL 指针返回; 否则返回值是 NULL. 所有的 I/O 内存分配来 /proc/iomem 中列出.</p>
<p>内存区在不再需要时应当释放:</p>
<pre class="programlisting">
void release_mem_region(unsigned long start, unsigned long len);
</pre>
<p>还有一个旧的检查 I/O 内存区可用性的函数:</p>
<pre class="programlisting">
int check_mem_region(unsigned long start, unsigned long len);
</pre>
<p>但是, 对于 check_region, 这个函数是不安全和应当避免的.</p>
<p>在存取内存之前, 分配 I/O 内嵌不是唯一的要求的步骤. 你必须也保证这个 I/O 内存已经对内核是可存取的. 使用 I/O 内存不只是解引用一个指针的事情; 在许多系统, I/O 内存根本不是可以这种方式直接存取的. 因此必须首先设置一个映射. 这是 ioremap 函数的功能, 在第 1 章的 "vmalloc 及其友"一节中介绍的. 这个函数设计来特别的安排虚拟地址给 I/O 内存区.</p>
<p>一旦装备了 ioremap (和iounmap), 一个设备驱动可以存取任何 I/O 内存地址, 不管是否它是直接映射到虚拟地址空间. 记住, 但是, 从 ioremap 返回的地址不应当直接解引用; 相反, 应当使用内核提供的存取函数. 在我们进入这些函数之前, 我们最好回顾一下 ioremap 原型和介绍几个我们在前一章略过的细节.</p>
<p>这些函数根据下列定义调用:</p>
<pre class="programlisting">
#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);
</pre>
<p>首先, 你注意新函数 ioremap_nacache. 我们在第 8 章没有涉及它, 因为它的意思是明确地硬件相关的. 引用自一个内核头文件:"It’s useful if some control registers are in such an area, and write combining or read caching is not desirable.". 实际上, 函数实现在大部分计算机平台上与 ioremap 一致: 在所有 I/O 内存已经通过非缓冲地址可见的地方, 没有理由使用一个分开的, 非缓冲 ioremap 版本.</p>
</div>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="AccessingIOMemory.sect"></a>9.4.2. 存取 I/O 内存</h3></div></div></div>
<p>在一些平台上, 你可能逃过作为一个指针使用 ioremap 的返回值的惩罚. 这样的使用不是可移植的, 并且, 更加地, 内核开发者已经努力来消除任何这样的使用. 使用 I/O 内存的正确方式是通过一系列为此而提供的函数(通过 <asm/io.h> 定义的).</p>
<p>从 I/O 内存读, 使用下列之一:</p>
<pre class="programlisting">
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
</pre>
<p>这里, addr 应当是从 ioremap 获得的地址(也许与一个整型偏移); 返回值是从给定 I/O 内存读取的.</p>
<p>有类似的一系列函数来写 I/O 内存:</p>
<pre class="programlisting">
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
</pre>
<p>如果你必须读和写一系列值到一个给定的 I/O 内存地址, 你可以使用这些函数的重复版本:</p>
<pre class="programlisting">
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);
</pre>
<p>这些函数读或写 count 值从给定的 buf 到 给定的 addr. 注意 count 表达为在被写入的数据大小; ioread32_rep 读取 count 32-位值从 buf 开始.</p>
<p>上面描述的函数进行所有的 I/O 到给定的 addr. 如果, 相反, 你需要操作一块 I/O 地址, 你可使用下列之一:</p>
<pre class="programlisting">
void memset_io(void *addr, u8 value, unsigned int count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
</pre>
<p>这些函数行为如同它们的 C 库类似物.</p>
<p>如果你通览内核源码, 你可看到许多调用旧的一套函数, 当使用 I/O 内存时. 这些函数仍然可以工作, 但是它们在新代码中的使用不鼓励. 除了别的外, 它们较少安全因为它们不进行同样的类型检查. 但是, 我们在这里描述它们:</p>
<pre class="programlisting">
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
</pre>
<p>这些宏定义用来从 I/O 内存获取 8-位, 16-位, 和 32-位 数据值.</p>
<pre class="programlisting">
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
</pre>
<p>如同前面的函数, 这些函数(宏)用来写 8-位, 16-位, 和 32-位数据项.</p>
<p>一些 64-位平台也提供 readq 和 writeq, 为 PCI 总线上的 4-字(8-字节)内存操作. 这个 4-字 的命名是一个从所有的真实处理器有 16-位 字的时候的历史遗留. 实际上, 用作 32-位 值的 L 命名也已变得不正确, 但是命名任何东西可能使事情更混淆.</p>
</div>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -