📄 ch15s04.html
字号:
</pre><p>返回这个缓冲的长度.</p><p>再次, 记住要传送的缓冲的地址和长度可能和传递给 dma_map_sg 的不同.</p><p>一旦传送完成, 一个 发散/汇聚 映射被使用 dma_unmap_sg 去映射:</p><pre class="programlisting">void dma_unmap_sg(struct device *dev, struct scatterlist *list, int nents, enum dma_data_direction direction);</pre><p>注意 nents 必须是你起初传递给 dma_map_sg 的入口项的数目, 并且不是这个函数返回给你的 DMA 缓冲的数目.</p><p>发散/汇聚映射是流 DMA 映射, 并且同样的存取规则如同单一映射一样适用. 如果你必须存取一个被映射的发散/汇聚列表, 你必须首先同步它:</p><pre class="programlisting">void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction);void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction); </pre></div><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="PCIdoubleaddresscyclemapping.sect3"></a>15.4.4.8. PCI 双地址周期映射</h4></div></div></div><p>正常地, DMA 支持层使用 32-位 总线地址, 可能受限于一个特定设备的 DMA 掩码. PCI 总线, 但是, 也支持一个 64-位地址模式, 双地址周期(DAC). 通常的 DMA 层不支持这个模式, 因为几个理由, 第一个是它是一个 PCI-特定 的特性. 还有, 许多 DAC 的实现满是错误, 并且, 因为 DAC 慢于一个常规的, 32-位 DMA, 可能有一个性能开销. 即便如此, 有的应用程序使用 DAC 是正确的事情; 如果你有一个设备可能使用非常大的位于高内存的缓冲, 你可能要考虑实现 DAC 支持. 这个支持只对 PCI 总线适用, 因此 PCI-特定的函数必须被使用.</p><p>为使用 DAC, 你的驱动必须包含 <linux/pci.h>. 你必须设置一个单独的 DMA 掩码:</p><pre class="programlisting">int pci_dac_set_dma_mask(struct pci_dev *pdev, u64 mask);</pre><p>你可使用 DAC 寻址只在这个调用返回 0 时. 一个特殊的类型 (dma64_addr_t) 被用作 DAC 映射. 为建立一个这些映射, 调用 pci_dac_page_to_dma:</p><pre class="programlisting">dma64_addr_t pci_dac_page_to_dma(struct pci_dev *pdev, struct page *page, unsigned long offset, int direction);</pre><p>DAC 映射, 你将注意到, 可能被完成只从 struct page 指针(它们应当位于高内存, 毕竟, 否则使用它们没有意义了); 它们必须一次一页地被创建. direction 参数是在通用 DMA 层中使用的 enum dma_data_direction 的 PCI 对等体; 它应当是 PCI_DMA_TODEVICE, PCI_DMA_FROMDEVICE, 或者 PCI_DMA_BIRDIRECTIONAL.</p><p>DAC 映射不要求外部资源, 因此在使用后没有必要明确释放它们. 但是, 有必要象对待其他流映射一样对待 DAC 映射, 并且遵守关于缓冲所有权的规则. 有一套函数来同步 DMA 缓冲, 和通常的变体相似:</p><pre class="programlisting">void pci_dac_dma_sync_single_for_cpu(struct pci_dev *pdev, dma64_addr_t dma_addr, size_t len, int direction); void pci_dac_dma_sync_single_for_device(struct pci_dev *pdev, dma64_addr_t dma_addr, size_t len, int direction);</pre></div><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="AsimplePCIDMAexample.sect3"></a>15.4.4.9. 一个简单的 PCI DMA 例子</h4></div></div></div><p>作为一个 DMA 映射如何被使用的例子, 我们展示了一个简单的给一个 PCI 设备的 DMA 编码的例子. 在 PCI 总线上的数据的 DMA 操作的形式非常依赖被驱动的设备. 因此, 这个例子不适用于任何真实的设备; 相反, 它是一个称为 dad ( DMA Acquisiton Device) 的假想驱动的一部分. 一个给这个设备的驱动可能定义一个传送函数象这样:</p><pre class="programlisting">int dad_transfer(struct dad_dev *dev, int write, void *buffer, size_t count){ dma_addr_t bus_addr; /* Map the buffer for DMA */ dev->dma_dir = (write ? DMA_TO_DEVICE : DMA_FROM_DEVICE); dev->dma_size = count; bus_addr = dma_map_single(&dev->pci_dev->dev, buffer, count, dev->dma_dir); dev->dma_addr = bus_addr; /* Set up the device */ writeb(dev->registers.command, DAD_CMD_DISABLEDMA); writeb(dev->registers.command, write ? DAD_CMD_WR : DAD_CMD_RD); writel(dev->registers.addr, cpu_to_le32(bus_addr)); writel(dev->registers.len, cpu_to_le32(count)); /* Start the operation */ writeb(dev->registers.command, DAD_CMD_ENABLEDMA); return 0;} </pre><p>这个函数映射要被传送的缓冲并且启动设备操作. 这个工作的另一半必须在中断服务过程中完成, 这个看来如此:</p><pre class="programlisting">void dad_interrupt(int irq, void *dev_id, struct pt_regs *regs){ struct dad_dev *dev = (struct dad_dev *) dev_id; /* Make sure it's really our device interrupting */ /* Unmap the DMA buffer */ dma_unmap_single(dev->pci_dev->dev, dev->dma_addr, dev->dma_size, dev->dma_dir); /* Only now is it safe to access the buffer, copy to user, etc. */ ...}</pre><p>显然, 这个例子缺乏大量的细节, 包括可能需要的任何步骤来阻止启动多个同时的 DMA 操作.</p></div></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="DMAforISADevices.sect2"></a>15.4.5. ISA 设备的 DMA</h3></div></div></div><p>ISA 总线允许 2 类 DMA 传送: 本地 DMA 和 ISA 总线主 DMA. 本地 DMA 使用在主板上的标准 DMA-控制器电路来驱动 ISA 总线上的信号线. ISA 总线主 DMA, 另一方面, 完全由外设处理, 至少从驱动的观点看. 一个 ISA 总线主的例子是 1542 SCSI 控制器, 在内核源码中是在 drivers/scsi/aha1542.c.</p><p>至于本地 DMA, 有 3 个实体包含在 ISA 总线上的 DMA 数据传送.</p><div class="variablelist"><dl><dt><span class="term"><span>The 8237 DMA controller (DMAC) </span></span></dt><dd><p>控制器持有关于 DMA 传送的信息, 诸如方向, 内存地址, 以及传送的大小. 它还包含一个计数器来跟踪进行中的传送的状态. 当这个控制器收到一个 DMA 请求信号, 它获得总线的控制权并且驱动信号线以便设备可读或些它的数据.</p></dd><dt><span class="term"><span>The peripheral device </span></span></dt><dd><p>这个设备必须激活 DMA 请求线当它准备传送数据时. 实际的传送由 DMAC 管理; 硬件设备顺序读或写数据到总线当控制器探测设备时. 设备常常触发中断当传送结束时.</p></dd><dt><span class="term"><span>The device driver </span></span></dt><dd><p>这个驱动什么不做; 它提供给 DMA 控制器方向, 总线地址,和传送的大小. 它还和它的外设通讯来准备传送数据和响应中断当 DMA 结束时.</p></dd></dl></div><p>开始的在 PC 上使用的 DMA 控制器管理 4 个"通道", 每个有一套 DMA 寄存器. 4 个设备可同时存储它们的 DMA 信息在控制器中. 更新的 PC 包含相同的 2 个 DMAC 设备<sup>[<a name="id500013" href="#ftn.id500013">51</a>]</sup>: 第 2 个控制器(主)被连接到系统的处理器, 并且第 1 个(从)被连接到第 2 个控制器的通道 0.</p><p>最初的 PC 只有一个控制器; 第 2 个是在基于 286 的平台上增加的. 但是, 第 2 个控制器如同主控制器一样被连接, 因为它处理 16-位的传送; 第 1 个只传送 8 位每次并且它为向后兼容而存在.</p><p>通道的编号从 0 到 7: 通道 4 对 ISA 外设不可用, 因为它在内部用来层叠从控制器到主控制器. 因此, 可用的通道是 0 到 3 在从控制器上( 8-位 通道) 和 5 到 7 到主控制器上( 16-位通道). 任何 DMA 传送的大小, 当被存储于控制器中, 是一个代表总线周期的数目的 16-位数. 最大的传送大小是, 因此, 64KB 对于从控制器(因为它传送 8 位在一个周期)和 128KB 对于主控制器( 它进行 16-位 传送).</p><p>因为 DMA 控制器是一个系统范围的资源, 内核帮助处理这个. 它使用一个 DMA 注册来提供一个请求并释放机制给 DMA 通道, 和一套函数来在 DMA 控制器中配置通道信息.</p><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="RegisteringDMAusage.sect3"></a>15.4.5.1. 注册 DMA 使用</h4></div></div></div><p>你应当熟悉内核注册 -- 我们已经见到它们在 I/O 端口和中断线. DMA 通道注册和其他的类似. 在 <asm/dma.h> 中已经包含, 下面的函数可用来获得和释放一个 DMA 通道的拥有权:</p><pre class="programlisting">int request_dma(unsigned int channel, const char *name); void free_dma(unsigned int channel);</pre><p>通道参数是一个在 0 到 7 之间的数, 更精确些, 一个小于 MAX_DMA_CHANNELS 的正值. 在 PC 上, MAX_DMA_CHANNELS 定义为 8 来匹配硬件. name 参数是一个字符串来标识设备. 特定的 name 出现在文件 /proc/dma, 它可被用户程序读.</p><p>从 request_dma 的返回值是 0 对于成功, 是 -EINVAL 或者 -EBUSY 如果有错误. 前者意思是请求的通道超范围, 后者意思是另一个设备持有这个通道.</p><p>我们推荐你象对待 I/O 端口和中断线一样小心对待 DMA 通道; 在打开时请求通道好于从模块初始化函数里请求它. 延后请求允许在驱动之间的一些共享; 例如, 你的声卡和模拟 I/O 接口可以共享 DMA 通道只要它们不同时使用.</p><p>我们还建议你请求 DMA 通道在你已请求中断线之后并且你在中断前释放它. 这是惯用的顺序来请求这 2 个资源; 遵循这个惯例避免了死锁的可能. 注意每个使用 DMA 的设备需要一个 IRQ 线; 否则, 它不能指示数据传送的完成.</p><p>在一个典型的情况, open 代码看来如下, 引用了我们的假想的 dad 模块. dad 设备使用了一个快速中断处理, 不带共享 IRQ 线支持.</p><pre class="programlisting">int dad_open (struct inode *inode, struct file *filp){ struct dad_device *my_device; /* ... */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -