Skip to content

Commit 846e32e

Browse files
committed
finish chapter I/O and memory
1 parent 68d1545 commit 846e32e

File tree

1 file changed

+291
-1
lines changed

1 file changed

+291
-1
lines changed

为了工作/Linux/Linux 设备驱动开发详解.md

Lines changed: 291 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ categories:
44
- Linux 学习
55
abbrlink: 484892ff
66
date: 2024-10-24 15:00:00
7-
updated: 2024-12-09 17:40:00
7+
updated: 2024-12-10 20:15:00
88
---
99

1010
<meta name="referrer" content="no-referrer"/>
@@ -4984,3 +4984,293 @@ Cache 和 DMA 本身似乎是两个毫不相关的事物。Cache 被用作 CPU
49844984

49854985
**内存中用于与外设交互数据的区域称为 DMA 缓冲区。**在设备不支持 scatter/gather(分散/聚集,简称 SG)操作的情况下,DMA 缓冲区在物理上必须是连续的。
49864986

4987+
#### DMA 区域
4988+
4989+
对于 x86 架构的 ISA 设备而言,其 DMA 操作只能在 16 MB 以下的内存中进行,因此,在使用 kmalloc()、`__get_free_pages()` 及其类似函数申请 DMA 缓冲区时应使用 GFP_DMA 标志,这样能保证获得的内存位于 DMA 区域中,并具备 DMA 能力。
4990+
4991+
关于 ISA、PCI、PCIE 等总线协议的了解,参考:[https://blog.csdn.net/yinqiusheng/article/details/140387774](https://blog.csdn.net/yinqiusheng/article/details/140387774)
4992+
4993+
内核中定义了使用 GFP_DMA 标志的申请 DMA 缓冲区的快捷函数 `__get_dma_pages()`,定义如下:
4994+
4995+
```c
4996+
#define __get_dma_pages(gfp_mask, order) \
4997+
__get_free_pages((gfp_mask) | GFP_DMA, (order))
4998+
```
4999+
5000+
如果不想使用参数 order 申请 DMA 内存,可使用另一个函数 dma_mem_alloc(),定义如下:
5001+
5002+
```c
5003+
static unsigned long dma_mem_alloc(unsigned long size)
5004+
{
5005+
// get_order():order = log2(size)
5006+
return __get_dma_pages(GFP_KERNEL, get_order(size));
5007+
}
5008+
```
5009+
5010+
#### 虚拟地址、物理地址和总线地址
5011+
5012+
基于 DMA 的硬件使用的是总线地址而不是物理地址,总线地址是从设备角度上看到的内存地址,物理地址则是从 CPU MMU 控制器外围角度上看到的内存地址(从 CPU 核角度看到的是虚拟地址)。虽然在 PC 上,对于 ISA 和 PCI 而言,总线地址即为物理地址,但并不是每个平台都是如此。因为有时候接口总线通过桥接电路连接,桥接电路会将 I/O 地址映射为不同的物理地址。
5013+
5014+
内核提供了如下函数进行简单的虚拟地址/总线地址转换:
5015+
5016+
```c
5017+
unsigned long virt_to_bus(void *address)
5018+
{
5019+
return (unsigned long)address;
5020+
}
5021+
5022+
void *bus_to_virt(unsigned long address)
5023+
{
5024+
return (void *)address;
5025+
}
5026+
```
5027+
5028+
#### DMA 地址掩码
5029+
5030+
设备不一定能在所有的内存地址上执行 DMA 操作,在这种情况下应该通过下列函数执行 DMA 地址掩码:
5031+
5032+
```c
5033+
int dma_set_mask(struct device *dev, u64 mask);
5034+
```
5035+
5036+
这个函数的本质是修改 device 结构体中的 dma_mask 成员。在 device 结构体中,除了 dma_mask 以外,还有 coherent_dma_mask 成员。dma_mask 是设备 DMA 可寻址的范围,coherent_dma_mask 用作申请一致性 DMA 缓冲区。
5037+
5038+
```c
5039+
struct device {
5040+
5041+
...
5042+
5043+
u64 *dma_mask; /* dma mask (if dma'able device) */
5044+
u64 coherent_dma_mask;/* Like dma_mask, but for
5045+
alloc_coherent mappings as
5046+
not all hardware supports
5047+
64 bit addresses for consistent
5048+
allocations such descriptors. */
5049+
5050+
...
5051+
};
5052+
```
5053+
5054+
#### 一致性 DMA 缓冲区
5055+
5056+
**DMA 映射包括两部分工作:分配一片 DMA 缓冲区;为这片缓冲区产生设备可访问的地址。**同时 DMA 映射也必须考虑 Cache 一致性问题。内核中提供了如下函数以分配一个 DMA 一致性的内存区域:
5057+
5058+
```c
5059+
// 返回申请到的 DMA 缓冲区的虚拟地址。
5060+
// 通过参数 dma_handle 返回 DMA 缓冲区的总线地址。
5061+
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp)
5062+
{
5063+
return dma_alloc_attrs(dev, size, dma_handle, gfp,
5064+
(gfp & __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0);
5065+
}
5066+
```
5067+
5068+
dma_alloc_coherent() 申请一片 DMA 缓冲区,进行地址映射并保证该缓冲区的 Cache 一致性。对应的释放函数为 dma_free_coherent()。
5069+
5070+
```c
5071+
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle)
5072+
{
5073+
return dma_free_attrs(dev, size, cpu_addr, dma_handle, 0);
5074+
}
5075+
```
5076+
5077+
使用函数 dma_alloc_writecombine() 分配一个写合并(Writecombining)的 DMA 缓冲区,释放函数是 dma_free_coherent()。这两个接口在 Linux 5.15 以后已移除。
5078+
5079+
Linux 内核还提供了 PCI 设备申请 DMA 缓冲区的函数 pci_alloc_consistent(),释放函数是 pci_free_consistent()。
5080+
5081+
```c
5082+
void *pci_alloc_consistent(struct pci_dev *hwdev, size_t size, dma_addr_t *dma_handle)
5083+
{
5084+
return dma_alloc_coherent(&hwdev->dev, size, dma_handle, GFP_ATOMIC);
5085+
}
5086+
5087+
void pci_free_consistent(struct pci_dev *hwdev, size_t size, void *vaddr, dma_addr_t dma_handle)
5088+
{
5089+
dma_free_coherent(&hwdev->dev, size, vaddr, dma_handle);
5090+
}
5091+
```
5092+
5093+
> dma_alloc_xxx() 函数虽然以 `dma_alloc_` 开头,但是申请的区域不一定在 DMA 区域里。以 32 位 ARM 处理器为例,当 coherent_dma_mask 小于 0xffffffff 时,才会设置 GFP_DMA 标记,并从 DMA 区域申请内存。
5094+
5095+
#### 流式 DMA 映射
5096+
5097+
**并不是所有的 DMA 缓冲区都是驱动申请的,如果是驱动申请的,用一致性 DMA 缓冲区自然最方便,这直接考虑了 Cache 一致性问题。**但在许多情况下,缓冲区来自内核的较上层(如网卡驱动中的网络报文、块设备驱动中要写入设备的数据等),上层很可能用普通的 kmalloc()、`__get_free_pages()` 等方法申请,这时就要使用流式 DMA 映射。使用步骤一般如下:
5098+
5099+
1. 进行流式 DMA 映射。
5100+
2. 执行 DMA 操作。
5101+
3. 进行流式 DMA 去映射(去掉映射)。
5102+
5103+
**流式 DMA 映射操作本质上大多是进行 Cache 的使无效或清除操作,以解决 Cache 一致性问题。**
5104+
5105+
##### 单一缓冲区下的流式 DMA 映射
5106+
5107+
对于单个已经分配的缓冲区而言,使用 dma_map_single() 可实现流式 DMA 映射。
5108+
5109+
```c
5110+
#define dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, 0)
5111+
5112+
// 映射成功返回总线地址,失败返回 NULL。
5113+
// dir:DMA 的方向,包括 DMA_TO_DEVICE、DMA_FROM_DEVICE、DMA_BIDIRECTIONAL 和 DMA_NONE 等。
5114+
dma_addr_t dma_map_single_attrs(struct device *dev, void *ptr, size_t size, enum dma_data_direction dir, unsigned long attrs)
5115+
{
5116+
/* DMA must never operate on areas that might be remapped. */
5117+
if (dev_WARN_ONCE(dev, is_vmalloc_addr(ptr),
5118+
"rejecting DMA map of vmalloc memory\n"))
5119+
return DMA_MAPPING_ERROR;
5120+
debug_dma_map_single(dev, ptr, size);
5121+
return dma_map_page_attrs(dev, virt_to_page(ptr), offset_in_page(ptr),
5122+
size, dir, attrs);
5123+
}
5124+
```
5125+
5126+
dma_map_single() 对应的去映射函数是 dma_unmap_single()。
5127+
5128+
```c
5129+
#define dma_unmap_single(d, a, s, r) dma_unmap_single_attrs(d, a, s, r, 0)
5130+
5131+
void dma_unmap_single_attrs(struct device *dev, dma_addr_t addr,
5132+
size_t size, enum dma_data_direction dir, unsigned long attrs)
5133+
{
5134+
return dma_unmap_page_attrs(dev, addr, size, dir, attrs);
5135+
}
5136+
```
5137+
5138+
通常情况下,设备驱动不应访问未映射的流式 DMA 缓冲区。如果一定要这么做,可使用如下函数获得 DMA 缓冲区的拥有权。
5139+
5140+
```c
5141+
void dma_sync_single_for_cpu(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir);
5142+
```
5143+
5144+
在驱动访问完 DMA 缓冲区后,使用如下函数将其所有权返还给设备。
5145+
5146+
```c
5147+
void dma_sync_single_for_device(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir);
5148+
```
5149+
5150+
##### SG 映射
5151+
5152+
如果设备要求较大的 DMA 缓冲区,在其支持 SG 模式的情况下,申请多个相对较小不连续的 DMA 缓冲区通常是防止申请太大的连续物理空间的方法。使用函数 dma_map_sg() 申请,对应的释放函数是 dma_unmap_sg()。
5153+
5154+
```c
5155+
#define dma_map_sg(d, s, n, r) dma_map_sg_attrs(d, s, n, r, 0)
5156+
5157+
// 函数返回 DMA 缓冲区的数量,可能小于 nents。
5158+
// nents:散列表(scatterlist)入口的数量。
5159+
// 对于 scatterlist 的每个项目,dma_map_sg() 为设备产生恰当的总线地址,它会合并物理上临近的内存区域。
5160+
unsigned int dma_map_sg_attrs(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir, unsigned long attrs)
5161+
{
5162+
int ret;
5163+
5164+
ret = __dma_map_sg_attrs(dev, sg, nents, dir, attrs);
5165+
if (ret < 0)
5166+
return 0;
5167+
return ret;
5168+
}
5169+
EXPORT_SYMBOL(dma_map_sg_attrs);
5170+
5171+
#define dma_unmap_sg(d, s, n, r) dma_unmap_sg_attrs(d, s, n, r, 0)
5172+
5173+
void dma_unmap_sg_attrs(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir, unsigned long attrs)
5174+
{
5175+
const struct dma_map_ops *ops = get_dma_ops(dev);
5176+
5177+
BUG_ON(!valid_dma_direction(dir));
5178+
debug_dma_unmap_sg(dev, sg, nents, dir);
5179+
if (dma_map_direct(dev, ops) ||
5180+
arch_dma_unmap_sg_direct(dev, sg, nents))
5181+
dma_direct_unmap_sg(dev, sg, nents, dir, attrs);
5182+
else if (ops->unmap_sg)
5183+
ops->unmap_sg(dev, sg, nents, dir, attrs);
5184+
}
5185+
EXPORT_SYMBOL(dma_unmap_sg_attrs);
5186+
```
5187+
5188+
scatterlist 结构体定义如下,包含与 scatterlist 对应的页结构体指针、缓冲区在页中的偏移 offset、缓冲区长度 length 以及总线地址 dma_address。
5189+
5190+
```c
5191+
struct scatterlist {
5192+
unsigned long page_link;
5193+
unsigned int offset;
5194+
unsigned int length;
5195+
dma_addr_t dma_address;
5196+
#ifdef CONFIG_NEED_SG_DMA_LENGTH
5197+
unsigned int dma_length;
5198+
#endif
5199+
};
5200+
```
5201+
5202+
执行 dma_map_sg() 后,通过 sg_dma_address() 返回 scatterlist 对应缓冲区的总线地址,sg_dma_len() 返回 scatterlist 对应缓冲区的长度。
5203+
5204+
```c
5205+
#define sg_dma_address(sg) ((sg)->dma_address)
5206+
5207+
#define sg_dma_len(sg) ((sg)->dma_length)
5208+
```
5209+
5210+
同单一缓冲区的情况,如果设备驱动一定要访问未映射的 SG 缓冲区,应先调用函数 dma_sync_sg_for_cpu(),归还所有权的函数是 dma_sync_sg_for_device()。
5211+
5212+
```c
5213+
void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nelems, enum dma_data_direction dir);
5214+
5215+
void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, int nelems, enum dma_data_direction dir);
5216+
```
5217+
5218+
#### dmaengine 标准 API
5219+
5220+
推荐使用 dmaengine 的驱动架构来编写 DMA 控制器的驱动,外设的驱动使用标准的 dmaengine API 进行 DMA 的准备、发起和完成时的回调工作。
5221+
5222+
和中断一样,在使用 DMA 前,设备驱动程序需首先向 dmaengine 系统申请 DMA 通道,申请 DMA 通道的函数如下:
5223+
5224+
```c
5225+
/* Deprecated, please use dma_request_chan() directly */
5226+
struct dma_chan * __deprecated dma_request_slave_channel(struct device *dev, const char *name)
5227+
{
5228+
struct dma_chan *ch = dma_request_chan(dev, name);
5229+
5230+
return IS_ERR(ch) ? NULL : ch;
5231+
}
5232+
5233+
struct dma_chan *dma_request_chan(struct device *dev, const char *name);
5234+
```
5235+
5236+
对应的释放通道的函数是 dma_release_channel()。
5237+
5238+
```c
5239+
void dma_release_channel(struct dma_chan *chan);
5240+
```
5241+
5242+
下面是利用 dmaengine API 发起一次 DMA 操作的示例:
5243+
5244+
```c
5245+
static void xxx_dma_fini_callback(void *data)
5246+
{
5247+
struct completion *dma_complete = data;
5248+
5249+
complete(dma_complete);
5250+
}
5251+
5252+
issue_xxx_dma(...)
5253+
{
5254+
// 通过 dmaengine_prep_slave_single() 准备好一些 DMA 描述符。
5255+
rx_desc = dmaengine_prep_slave_single(xxx->rx_chan, xxx->dst_start, t->len, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
5256+
5257+
// 填充完成回调为 xxx_dma_fini_callback()。
5258+
rx_desc->callback = xxx_dma_fini_callback;
5259+
rx_desc->callback_param = &xxx->rx_done;
5260+
5261+
// 通过 dmaengine_submit() 把这个描述符插入队列。
5262+
dmaengine_submit(rx_desc);
5263+
5264+
// 通过 dma_async_issue_pending() 发起这次 DMA 动作。完成后 xxx_dma_fini_callback() 函数会被 dmaengine 驱动自动调用。
5265+
dma_async_issue_pending(xxx->rx_chan);
5266+
}
5267+
```
5268+
5269+
## 小结
5270+
5271+
外设可处于 CPU 的内存空间和 I/O 空间。除 x86 外,嵌入式处理器一般只存在内存空间。Linux 为 I/O 内存和 I/O 端口的访问提高了一套统一的方法,访问流程一般为**申请资源->映射->访问->去映射->释放资源**。
5272+
5273+
对于有 MMU 的处理器而言,Linux 的内部布局比较复杂,可直接映射的物理内存称为常规内存,超出部分为高端内存。kmalloc() 和 `__get_free_pages()` 申请的内存在物理上连续,vmalloc() 申请的内存在物理上不连续。
5274+
5275+
DMA 操作可能导致 Cache 不一致性的问题,故对于 DMA 缓冲,应使用 dma_alloc_coherent() 等方法申请。在 DMA 操作中涉及总线地址、物理地址和虚拟地址等概念,区分这 3 类地址非常重要。
5276+

0 commit comments

Comments
 (0)