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

📄 ch15s04.html

📁 驱动程序在 Linux 内核里扮演着特殊的角色. 它们是截然不同的"黑盒子", 使硬件的特殊的一部分响应定义好的内部编程接口. 它们完全隐藏了设备工作的细节. 用户的活动通过一套标准化的调用来进行,
💻 HTML
📖 第 1 页 / 共 4 页
字号:
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="SettingupcoherentDMAmappings.sect3"></a>15.4.4.3.&#160;建立一致 DMA 映射</h4></div></div></div>
<p>一个驱动可以建立一个一致映射, 使用对 dma_alloc_coherent 的调用:</p>
<pre class="programlisting">
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag);
</pre>
<p>这个函数处理缓冲的分配和映射. 前 2 个参数是设备结果和需要的缓冲大小. 这个函数返回 DMA 映射的结果在 2 个地方. 来自这个函数的返回值是缓冲的一个内核虚拟地址, 它可被驱动使用; 其间相关的总线地址在 dma_handle 中返回. 分配在这个函数中被处理以至缓冲被放置在一个可以使用 DMA 的位置; 常常地内存只是使用 get_free_pages 来分配(但是注意大小是以字节计的, 而不是一个 order 值). flag 参数是通常的 GFP_ 值来描述内存如何被分配; 常常应当是 GFP_KERNEL (常常) 或者 GFP_ATOMIC (当在原子上下文中运行时).</p>
<p>当不再需要缓冲(常常在模块卸载时), 它应当被返回给系统, 使用 dma_free_coherent:</p>
<pre class="programlisting">
void dma_free_coherent(struct device *dev, size_t size,
 void *vaddr, dma_addr_t dma_handle);
</pre>
<p>注意, 这个函数象许多通常的 DMA 函数, 需要提供所有的大小, CPU 地址, 和 总线地址参数.</p>
</div>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="DMApools.sect3"></a>15.4.4.4.&#160;DMA 池</h4></div></div></div>
<p>一个 DMA池 是分配小的, 一致DMA映射的分配机制. 从 dma_alloc_coherent 获得的映射可能有一页的最小大小. 如果你的驱动需要比那个更小的 DMA 区域, 你应当可能使用一个 DMA 池. DMA 池也在这种情况下有用, 当你可能试图对嵌在一个大结构中的小区域进行 DMA 操作. 一些非常模糊的驱动错误已被追踪到缓存一致性问题, 在靠近小 DMA 区域的结构成员. 为避免这个问题, 你应当一直明确分配进行 DMA 操作的区域, 和其他的非 DMA 数据结构分开.</p>
<p>DMA 池函数定义在 &lt;linux/dmapool.h&gt;.</p>
<p>一个 DMA 池必须在使用前创建, 使用一个调用:</p>
<pre class="programlisting">
struct dma_pool *dma_pool_create(const char *name, struct device *dev,
 size_t size, size_t align,
 size_t allocation); 
</pre>
<p>这里, name 是池的名子, dev 是你的设备结构, size 是要从这个池分配的缓冲区大小, align 是来自池的分配要求的硬件对齐(以字节表达的), 以及 allocation是, 如果非零, 一个分配不应当越过的内存边界. 如果 allocation 以 4096 传递, 例如, 从池分配的缓冲不越过 4-KB 边界.</p>
<p>当你用完一个池, 可被释放, 用:</p>
<pre class="programlisting">
void dma_pool_destroy(struct dma_pool *pool); 
</pre>
<p>你应当返回所有的分配给池, 在销毁它之前. 分配被用 dma_pool_alloc 处理:</p>
<pre class="programlisting">
void *dma_pool_alloc(struct dma_pool *pool, int mem_flags, dma_addr_t *handle);
</pre>
<p>对这个调用, mem_flags 是常用的 GFP_ 分配标志的设置. 如果所有都进行顺利, 一个内存区(大小是当池创建时指定的)被分配和返回. 至于 dam_alloc_coherent, 结果 DMA 缓冲地址被返回作为一个内核虚拟地址, 并作为一个总线地址被存于 handle.</p>
<p>不需要的缓冲应当返回池, 使用:</p>
<pre class="programlisting">
void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t addr); 
</pre>
</div>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="SettingupstreamingDMAmapping.sect3"></a>15.4.4.5.&#160;建立流 DMA 映射</h4></div></div></div>
<p>流映射比一致映射有更复杂的接口, 有几个原因. 这些映射行为使用一个由驱动已经分配的缓冲, 因此, 必须处理它们没有选择的地址. 在一些体系上, 流映射也可以有多个不连续的页和多部分的"发散/汇聚"缓冲. 所有这些原因, 流映射有它们自己的一套映射函数.</p>
<p>当建立一个流映射时, 你必须告知内核数据移向哪个方向. 一些符号(enum dam_data_direction 类型)已为此定义:</p>
<div class="variablelist"><dl>
<dt><span class="term"><span>DMA_TO_DEVICE</span></span></dt>
<dd></dd>
<dt><span class="term"><span>DMA_FROM_DEVICE </span></span></dt>
<dd><p>这 2 个符号应当是自解释的. 如果数据被发向这个设备(相应地, 也许, 到一个 write 系统调用), DMA_IO_DEVICE 应当被使用; 去向 CPU 的数据, 相反, 用 DMA_FROM_DEVICE 标志.</p></dd>
<dt><span class="term"><span>DMA_BIDIRECTIONAL </span></span></dt>
<dd><p>如果数据被在任一方向移动, 使用 DMA_BIDIRECTIONAL.</p></dd>
<dt><span class="term"><span>DMA_NONE </span></span></dt>
<dd><p>这个符号只作为一个调试辅助而提供. 试图使用带这个方向的缓冲导致内核崩溃.</p></dd>
</dl></div>
<p>可能在所有时间里试图只使用 DMA_BIDIRECTIONAL, 但是驱动作者应当抵挡住这个诱惑. 在一些体系上, 这个选择会有性能损失.</p>
<p>当你有单个缓冲要发送, 使用 dma_map_single 来映射它:</p>
<pre class="programlisting">
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);
</pre>
<p>返回值是总线地址, 你可以传递到设备, 或者是 NULL 如果有错误.</p>
<p>一旦传输完成, 映射应当用 dma_unmap_single 来删除:</p>
<pre class="programlisting">
void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction);
</pre>
<p>这里, size 和 direction 参数必须匹配那些用来映射缓冲的.</p>
<p>一些重要的规则适用于流 DMA 映射:</p>
<div class="itemizedlist"><ul type="disc">
<li><p>缓冲必须用在只匹配它被映射时给定的方向的传输.</p></li>
<li><p>一旦一个缓冲已被映射, 它属于这个设备, 不是处理器. 直到这个缓冲已被去映射, 驱动不应当以任何方式触动它的内容. 只在调用 dma_unmap_single 后驱动才可安全存取缓冲的内容(有一个例外, 我们马上见到). 其他的事情, 这个规则隐含一个在被写入设备的缓冲不能被映射, 直到它包含所有的要写的数据.</p></li>
<li><p>这个缓冲必须不被映射, 当 DMA 仍然激活, 否则肯定会有严重的系统不稳定.</p></li>
</ul></div>
<p>你可能奇怪为什么一旦一个缓冲已被映射驱动就不能再使用它. 为什么这个规则有意义实际上有 2 个原因. 第一, 当一个缓冲为 DMA 而被映射, 内核必须确保缓冲中的所有的数据实际上已被写入内存. 有可能一些数据在处理器的缓存当 dma_unmap_single 被调用时, 并且必须被明确刷新. 被处理器在刷新后写入缓冲的数据可能对设备不可见.</p>
<p>第二, 考虑一下会发生什么, 当被映射的缓冲在一个对设备不可存取的内存区. 一些体系在这种情况下完全失败, 但是其他的创建一个反弹缓冲. 反弹缓冲只是一个分开的内存区, 它对设备可存取. 如果一个缓冲被映射使用 DMA_TO_DEVICE 方向, 并且要求一个反弹缓冲, 原始缓冲的内容作为映射操作的一部分被拷贝. 明显地, 在拷贝后的对原始缓冲的改变设备见不到. 类似地, DMA_FROM_DEVICE 反弹缓冲被 dma_unmap_single 拷回到原始缓冲; 来自设备的数据直到拷贝完成才出现.</p>
<p>偶然地, 为什么获得正确方向是重要的, 反弹缓冲是一个原因. DMA_BIDIRECTIONAL 反弹缓冲在操作前后被拷贝, 这常常是一个 CPU 周期的不必要浪费.</p>
<p>偶尔一个驱动需要存取一个流 DMA 缓冲的内容而不映射它. 已提供了一个调用来做这个:</p>
<pre class="programlisting">
void dma_sync_single_for_cpu(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction); 
</pre>
<p>这个函数应当在处理器存取一个流 DMA 缓冲前调用. 一旦已做了这个调用, CPU "拥有" DMA 缓冲并且可以按需使用它. 在设备存取这个缓冲前, 但是, 拥有权应当传递回给它, 使用:</p>
<pre class="programlisting">
void dma_sync_single_for_device(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction); 
</pre>
<p>处理器, 再一次, 在调用这个之后不应当存取 DMA 缓冲.</p>
</div>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="Singlepagestreamingmappings.sect3"></a>15.4.4.6.&#160;单页流映射</h4></div></div></div>
<p>偶然地, 你可能想建立一个缓冲的映射, 这个缓冲你有一个 struct page 指针; 例如, 这可能发生在使用 get_user_pages 映射用户缓冲. 为建立和取消流映射使用 struct page 指针, 使用下面:</p>
<pre class="programlisting">
dma_addr_t dma_map_page(struct device *dev, struct page *page,
 unsigned long offset, size_t size,
 enum dma_data_direction direction); 
void dma_unmap_page(struct device *dev, dma_addr_t dma_address,
 size_t size, enum dma_data_direction direction);
</pre>
<p>offset 和 size 参数可被用来映射页的部分. 但是, 建议部分页映射应当避免, 除非你真正确信你在做什么. 映射一页的部分可能导致缓存一致性问题, 如果这个分配只覆盖一个缓存线的一部分; 这, 随之, 会导致内存破坏和严重的难以调试的错误.</p>
</div>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="Scattergathermapping.sect3"></a>15.4.4.7.&#160;发散/汇聚映射</h4></div></div></div>
<p>发散/汇聚映射是一个特殊类型的流 DMA 映射. 假设你有几个缓冲, 都需要传送数据到或者从设备. 这个情况可来自几个方式, 包括从一个 readv 或者 writev 系统调用, 一个成簇的磁盘 I/O 请求, 或者一个页链表在一个被映射的内核 I/O 缓冲. 你可简单地映射每个缓冲, 轮流的, 并且进行要求的操作, 但是有几个优点来一次映射整个链表.</p>
<p>许多设备可以接收一个散布表数组指针和长度, 并且传送它们全部在一个 DMA 操作中; 例如, "零拷贝"网络是更轻松如果报文在多个片中建立. 另一个映射发散列表为一个整体的理由是利用在总线硬件上有映射寄存器的系统. 在这样的系统上, 物理上不连续的页从设备的观点看可被汇集为一个单个的, 连续的数组. 这个技术只当散布表中的项在长度上等于页大小(除了第一个和最后一个), 但是当它做这个工作时, 它可转换多个操作到一个单个的 DMA, 和有针对性的加速事情.</p>
<p>最后, 如果一个反弹缓冲必须被使用, 应该连接整个列表为一个单个缓冲(因为它在被以任何方式拷贝).</p>
<p>因此现在你确信散布表的映射在某些情况下是值得的. 映射一个散布表的第一步是创建和填充一个 struct scatterlist 数组, 它描述被传输的缓冲. 这个结构是体系依赖的, 并且在 &lt;asm/scatterlist.h&gt; 中描述. 但是, 它常常包含 3 个成员:</p>
<div class="variablelist"><dl>
<dt><span class="term"><span>struct page *page;</span></span></dt>
<dd><p>struct page 指针, 对应在发散/汇聚操作中使用的缓冲.</p></dd>
<dt><span class="term"><span>unsigned int length;</span></span></dt>
<dd></dd>
<dt><span class="term"><span>unsigned int offset;</span></span></dt>
<dd><p>缓冲的长度和它的页内偏移.</p></dd>
</dl></div>
<p>为映射一个发散/汇聚 DMA 操作, 你的驱动应当设置 page, offset, 和 length 成员在一个 struct scatterlist 项给每个要被发送的缓冲. 接着调用:</p>
<pre class="programlisting">
int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)
</pre>
<p>这里 nents 是传入的散布表项的数目. 返回值是要发送的 DMA 缓冲的数目. 它可能小于 nents.</p>
<p>对于输入散布表中的每个缓冲, dma_map_sg 决定了正确的给设备的总线地址. 作为任务的一部分, 它也连接在内存中相近的缓冲. 如果你的驱动运行的系统有一个 I/O 内存管理单元, dma_map_sg 也编程这个单元的映射寄存器, 可能的结果是, 从你的驱动的观点, 你能够传输一个单个的, 连续的缓冲. 你将不会知道传送的结果将看来如何, 但是, 直到在调用之后.</p>
<p>你的驱动应当传送由 pci_map_sg 返回的每个缓冲. 总线地址和每个缓冲的长度存储于 struct scatterlist 项, 但是它们在结构中的位置每个体系不同. 2 个宏定义已被定义来使得可能编写可移植的代码:</p>
<pre class="programlisting">
dma_addr_t sg_dma_address(struct scatterlist *sg); 
</pre>
<p>从这个散布表入口返回总线(	DMA )地址.</p>
<pre class="programlisting">
unsigned int sg_dma_len(struct scatterlist *sg); 

⌨️ 快捷键说明

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