📄 ch09s04.html
字号:
<p>一些硬件有一个有趣的特性: 一些版本使用 I/O 端口, 而其他的使用 I/O 内存. 输出给处理器的寄存器在任一种情况中相同, 但是存取方法是不同的. 作为一个使驱动处理这类硬件的生活容易些的方式, 并且作为一个使 I/O 端口和内存存取的区别最小化的方法, 2.6 内核提供了一个函数, 称为 ioport_map:</p><pre class="programlisting">void *ioport_map(unsigned long port, unsigned int count); </pre><p>这个函数重映射 count I/O 端口和使它们出现为 I/O 内存. 从这点以后, 驱动可以在返回的地址上使用 ioread8 和其友并且根本忘记它在使用 I/O 端口.</p><p>这个映射应当在它不再被使用时恢复:</p><pre class="programlisting">void ioport_unmap(void *addr); </pre><p>这些函数使 I/O 端口看来象内存. 但是, 注意 I/O 端口必须仍然使用 request_region 在它们以这种方式被重映射前分配.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="ReusingshortforIOMemory.sect"></a>9.4.4. 重用 short 为 I/O 内存</h3></div></div></div><p>short 例子模块, 在存取 I/O 端口前介绍的, 也能用来存取 I/O 内存. 为此, 你必须告诉它使用 I/O 内存在加载时; 还有, 你需要改变基地址来使它指向你的 I/O 区.</p><p>例如, 这是我们如何使用 short 来点亮调试 LED, 在一个 MIPS 开发板上:</p><pre class="screen">mips.root# ./short_load use_mem=1 base=0xb7ffffc0mips.root# echo -n 7 > /dev/short0</pre><p>使用 short 给 I/O 内存是与它用在 I/O 端口上同样的.</p><p>下列片段显示了 short 在写入一个内存位置时用的循环:</p><pre class="programlisting">while (count--) { iowrite8(*ptr++, address); wmb(); } </pre><p>注意, 这里使用一个写内存屏障. 因为在很多体系上 iowrites8 可能转变为一个直接赋值, 需要内存屏障来保证以希望的顺序来发生.</p><p>short 使用 inb 和 outb 来显示它如何完成. 对于读者它可能是一个直接的练习, 但是, 改变 short 来使用 ioport_map 重映射 I/O 端口, 并且相当地简化剩下的代码.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="ISAMemoryBelow1MB.sect"></a>9.4.5. 在 1 MB 之下的 ISA 内存</h3></div></div></div><p>一个最著名的 I/O 内存区是在个人计算机上的 ISA 范围. 这是在 640 KB(0xA0000)和 1 MB(0x100000)之间的内存范围. 因此, 它正好出现于常规内存 RAM 中间. 这个位置可能看起来有点奇怪; 它是一个在 1980 年代早期所作的决定的产物, 当时 640 KB 内存看来多于任何人可能用到的大小.</p><p>这个内存方法属于非直接映射的内存类别. <sup>[<a name="id457602" href="#ftn.id457602">36</a>]</sup>你可以读/写几个字节在这个内存范围, 如同前面解释的使用 short 模块, 就是, 通过在加载时设置 use_mem.</p><p>尽管 ISA I/O 内存只在 x86-类 计算机中存在, 我们认为值得用几句话和一个例子驱动.</p><p>我们不会谈论 PCI 在本章, 因为它是最干净的一类 I/O 内存: 一旦你知道内存地址, 你可简单地重映射和存取它. PCI I/O 内存的"问题"是它不能为本章提供一个能工作的例子, 因为我们不能事先知道你的 PCI 内存映射到的物理地址, 或者是否它是安全的来存取任一这些范围. 我们选择来描述 ISA 内存范围, 因为它不但少干净并且更适合运行例子代码.</p><p>为演示存取 ISA 内存, 我们还使用另一个 silly 小模块( 例子源码的一部分). 实际上, 这个称为 silly, 作为 Simple Tool for Unloading and Printing ISA Data 的缩写, 或者如此的东东.</p><p>模块补充了 short 的功能, 通过存取整个 384-KB 内存空间和通过显示所有的不同 I/O 功能. 它特有 4 个设备节点来进行同样的任务, 使用不同的数据传输函数. silly 设备作为一个 I/O 内存上的窗口, 以类似 /dev/mem 的方式. 你可以读和写数据, 并且lseek 到一个任意 I/O 内存地址.</p><p>因为 silly 提供了对 ISA 内存的存取, 它必须开始于从映射物理 ISA 地址到内核虚拟地址. 在 Linux 内核的早期, 一个人可以简单地安排一个指针给一个感兴趣的 ISA 地址, 接着直接对它解引用. 在现代世界, 但是, 我们必须首先使用虚拟内存系统和重映射内存范围. 这个映射使用 ioremap 完成, 如同前面为 short 解释的:</p><pre class="programlisting">#define ISA_BASE 0xA0000#define ISA_MAX 0x100000 /* for general memory access *//* this line appears in silly_init */io_base = ioremap(ISA_BASE, ISA_MAX - ISA_BASE); </pre><p>ioremap 返回一个指针值, 它能被用来使用 ioread8 和其他函数, 在"存取 I/O 内存"一节中解释.</p><p>让我们回顾我们的例子模块来看看这些函数如何被使用. /dev/sillyb, 特有次编号 0, 存取 I/O 内存使用 ioread8 和 iowrite8. 下列代码显示了读的实现, 它使地址范围 0xA0000-0xFFFF 作为一个虚拟文件在范围 0-0x5FFF. 读函数构造为一个 switch 语句在不同存取模式上; 这是 sillyb 例子:</p><pre class="programlisting">case M_8: while (count) { *ptr = ioread8(add); add++; count--; ptr++; } break;</pre><p>实际上, 这不是完全正确. 内存范围是很小和很频繁的使用, 以至于内核在启动时建立页表来存取这些地址. 但是, 这个用来存取它们的虚拟地址不是同一个物理地址, 并且因此无论如何需要 ioremap.</p><p>下 2 个设备是 /dev/sillyw (次编号 1) 和 /dev/silly1 (次编号 2). 它们表现象 /dev/sillyb, 除了它们使用 16-位 和 32-位 函数. 这是 sillyl 的写实现, 又一次部分 switch:</p><pre class="programlisting">case M_32: while (count >= 4) { iowrite8(*(u32 *)ptr, add); add += 4; count -= 4; ptr += 4; } break;</pre><p>最后的设备是 /dev/sillycp (次编号 3), 它使用 memcpy_*io 函数来进行同样的任务. 这是它的读实现的核心:</p><pre class="programlisting">case M_memcpy: memcpy_fromio(ptr, add, count); break;</pre><p>因为 ioremap 用来提供对 ISA 内存区的存取, silly 必须调用 iounmap 当模块卸载时:</p><pre class="programlisting">iounmap(io_base); </pre></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="isareadbandFriends.sect"></a>9.4.6. isa_readb 和其友</h3></div></div></div><p>看一下内核源码会展现另一套函数, 有如 isa_readb 的名子. 实际上, 每个刚才描述的函数都有一个 isa_ 对等体. 这些函数提供对 ISA 内存的存取不需要一个单独的 ioremap 步骤. 但是, 来自内核开发者的话, 是这些函数打算用来作为暂时的驱动移植辅助, 并且它可能将来消失. 因此, 你应当避免使用它们.</p></div><div class="footnotes"><br><hr width="100" align="left"><div class="footnote"></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch09s03.html">上一页</a> </td><td width="20%" align="center"><a accesskey="u" href="ch09.html">上一级</a></td><td width="40%" align="right"> <a accesskey="n" href="ch09s05.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">9.3. 一个 I/O 端口例子 </td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top"> 9.5. 快速参考</td></tr></table></div></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -