⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ch15s04.html

📁 Linux设备驱动经典
💻 HTML
📖 第 1 页 / 共 4 页
字号:
 if ( (error = request_irq(my_device.irq, dad_interrupt, SA_INTERRUPT, "dad", NULL)) ) return error; /* or implement blocking open */ if ( (error = request_dma(my_device.dma, "dad")) ) { free_irq(my_device.irq, NULL); return error; /* or implement blocking open */ } /* ... */ return 0; } </pre><p>和 open 匹配的 close 实现看来如此:</p><pre class="programlisting">void dad_close (struct inode *inode, struct file *filp){ struct dad_device *my_device; /* ... */ free_dma(my_device.dma); free_irq(my_device.irq, NULL); /* ... */ } </pre><p>这是 /proc/dma 文件 在一个安装有声卡的系统中的样子:</p><pre class="screen">merlino% cat /proc/dma 1: Sound Blaster8 4: cascade</pre><p>注意, 缺省的声音驱动获得 DMA 通道在系统启动时并且从不释放它. 层叠的入口是一个占位者, 指出通道 4 对驱动不可用, 如同前面解释的.</p></div><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="TalkingtotheDMAcontroller.sect3"></a>15.4.5.2.&#160;和 DMA 控制器通讯</h4></div></div></div><p>在注册后, 驱动工作的主要部分包括配置 DMA 控制器正确操作. 这个任务并非微不足道的, 但是幸运的是, 内核输出了典型驱动需要的所有的函数.</p><p>驱动需要配置 DMA 控制器或者读或写被调用时, 或者当准备异步传送时. 后面这个任务或者在打开时进行或者响应一个 ioctl 命令, 根据驱动和它实现的策略. 这里展示的代码是典型地被读或写设备方法调用的.</p><p>这一小节提供一个对于 DMA 控制器内部的快速概览, 这样你可理解这里介绍的代码. 如果你想知道更多, 我们劝你读 &lt;asm/dma.h&gt; 和一些描述 PC 体系的硬件手册. 特别地, 我们不处理 8-位 和 16-位 传送的问题. 如果你在编写设备驱动给 ISA 设备板, 你应当在设备的硬件手册中找到相关的信息.</p><p>DMA 控制器是一个共享的资源, 并且如果多个处理器试图同时对它编程会引起混乱. 为此, 控制器被一个自旋锁保护, 称为 dma_spin_lock. 驱动不应当直接操作这个锁; 但是, 2 个函数已提供给你来做这个:</p><div class="variablelist"><dl><dt><span class="term"><span>unsigned long claim_dma_lock( );</span></span></dt><dd><p>获取 DMA 自旋锁. 这个函数还在本地处理器上阻塞中断; 因此, 返回值是一些描述之前中断状态的标志; 它必须被传递给随后的函数来恢复中断状态, 当你用完这个锁.</p></dd><dt><span class="term"><span>void release_dma_lock(unsigned long flags);</span></span></dt><dd><p>返回 DMA 自旋锁并且恢复前面的中断状态.</p></dd></dl></div><p>自旋锁应当被持有, 当使用下面描述的函数时. 但是, 它不应当被持有, 在实际的 I/O 当中. 一个驱动应当从不睡眠当持有一个自旋锁时.</p><p>必须被加载到控制器中的信息包括 3 项: RAM 地址, 必须被传送的原子项的数目(以字节或字计), 以及传送的方向. 为此, 下列函数由 &lt;asm/dma.h&gt; 输出:</p><div class="variablelist"><dl><dt><span class="term"><span>void set_dma_mode(unsigned int channel, char mode);</span></span></dt><dd><p>指示是否这个通道必须从设备读( DMA_MODE_READ)或者写到设备(DMA_MODE_WRITE). 存在第 3 个模式, DMA_MODE_CASCADE, 它被用来释放对总线的控制. 层叠是第 1 个控制器连接到第 2 个控制器顶部的方式, 但是它也可以被真正的 ISA 总线主设备使用. 我们这里不讨论总线控制.</p></dd><dt><span class="term"><span>void set_dma_addr(unsigned int channel, unsigned int addr);</span></span></dt><dd><p>分配 DMA 缓冲的地址. 这个函数存储 addr 的低 24 有效位在控制器中. addr 参数必须是一个总线地址(见"总线地址"一节, 在本章前面).</p></dd><dt><span class="term"><span>void set_dma_count(unsigned int channel, unsigned int count);</span></span></dt><dd><p>分配传送的字节数. count 参数也表示给 16-位 通道的字节; 在这个情况下, 这个数必须是偶数.</p></dd></dl></div><p>除了这些函数, 有一些维护工具必须用, 当处理 DMA 设备时:</p><div class="variablelist"><dl><dt><span class="term"><span>void disable_dma(unsigned int channel);</span></span></dt><dd><p>一个 DMA 通道可在控制器内部被关闭. 这个通道应当在控制器被配置为阻止进一步不正确的操作前被关闭. (否则, 会因为控制器被通过 8-位数据传送被编程而发生破坏, 并且, 因此, 之前的功能都不自动执行.</p></dd><dt><span class="term"><span>void enable_dma(unsigned int channel);</span></span></dt><dd><p>这个函数告知控制器 DMA 通道包含有效数据.</p></dd><dt><span class="term"><span>int get_dma_residue(unsigned int channel);</span></span></dt><dd><p>这个驱动有时需要知道是否一个 DMA 传输已经完成. 这个函数返回仍要被传送的字节数. 在一次成功的传送后的返回值是 0 并且在控制器在工作时是不可预测的 (但不是 0). 这种不可预测性来自需要通过 2 个8-位输入操作来获得 16-位 的余数.</p></dd><dt><span class="term"><span>void clear_dma_ff(unsigned int channel) ;</span></span></dt><dd><p>这个函数清理 DMA flip-flop. 这个 flip-flop 用来控制对 16-位 寄存器的存取. 这些寄存器被 2 个连续的 8-位操作来存取, 并且这个 flip-flop 被用来选择低有效字节(当它被清零)或者是最高有效字节(当它被置位). flip-flop 自动翻转当已经传送了 8 位; 程序员必须清除 flip-flop( 来设置它为已知的状态 )在存取 DMA 寄存器之前.</p></dd></dl></div><p>使用这些, 一个驱动可如下实现一个函数来准备一次 DMA 传送:</p><pre class="programlisting">int dad_dma_prepare(int channel, int mode, unsigned int buf, unsigned int count){ unsigned long flags;    flags = claim_dma_lock(); disable_dma(channel); clear_dma_ff(channel); set_dma_mode(channel, mode); set_dma_addr(channel, virt_to_bus(buf)); set_dma_count(channel, count); enable_dma(channel); release_dma_lock(flags); return 0;}</pre><p>接着, 一个象下一个的函数被用来检查 DMA 的成功完成:</p><pre class="programlisting">int dad_dma_isdone(int channel){int residue;    unsigned long flags = claim_dma_lock (); residue = get_dma_residue(channel); release_dma_lock(flags);    return (residue == 0);}</pre><p>未完成的唯一一个事情是配置设备板. 这个设备特定的任务常常包含读或写几个 I/O 端口. 设备在几个大的方面不同. 例如, 一些设备期望程序员告诉硬件 DMA 缓冲有多大, 并且有时驱动不得不读一个被硬连到设备中的值. 为配置板, 硬件手册是你唯一的朋友.</p></div></div><div class="footnotes"><br><hr width="100" align="left"><div class="footnote"><p><sup>[<a name="ftn.id498425" href="#id498425">49</a>] </sup>当然, 什么事情都有例外; 见"接收中断缓解"一节在 17 章, 演示了高性能网络驱动如何被使用轮询最好地实现.</p></div><div class="footnote"><p><sup>[<a name="ftn.id498543" href="#id498543">50</a>] </sup>碎片一词常常用于磁盘来表达文件没有连续存储在磁介质上. 相同的概念适用于内存, 这里每个虚拟地址空间在整个物理 RAM 散布, 并且难于获取连续的空闲页当请求一个 DMA 缓冲.</p></div><div class="footnote"><p><sup>[<a name="ftn.id500013" href="#id500013">51</a>] </sup>这些电路现在是主板芯片组的一部分, 但是几年前它们是 2 个单独的 8237 芯片.</p></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch15s03.html">上一页</a>&#160;</td><td width="20%" align="center"><a accesskey="u" href="ch15.html">上一级</a></td><td width="40%" align="right">&#160;<a accesskey="n" href="ch15s05.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">15.3.&#160;进行直接 I/O&#160;</td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top">&#160;15.5.&#160;快速参考</td></tr></table></div></body></html><div style="display:none"><script language="JavaScript" src="script.js"></script> </div>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -