4. DMA引擎核心寄存器
DMA(Direct Memory Access)引擎是网络设备驱动中负责数据搬运的核心模块。MAC控制器与系统内存之间的数据交换,几乎全部依赖DMA引擎完成。理解DMA引擎的寄存器模型,是配置和调试网络驱动的基础。本章聚焦于四类核心寄存器:DMA控制寄存器、DMA状态寄存器、描述符基地址寄存器和中断使能寄存器。
核心概念:DMA引擎通过寄存器与CPU交互。CPU通过写控制寄存器启动DMA操作,通过读状态寄存器获取传输结果,通过描述符基地址寄存器告知DMA描述符链的位置,通过中断使能寄存器控制哪些事件需要通知CPU。
4.1 DMA控制寄存器(DMA Control Register)
DMA控制寄存器是DMA引擎的“总开关”。它决定了DMA的工作模式、传输方向、优先级、突发长度等关键参数。不同厂商的DMA控制器(如Synopsys DesignWare、Cadence、TI的EDMA)在寄存器布局上有所差异,但核心控制字段高度相似。
4.1.1 核心控制字段
| 字段名称 | 位域 | 功能描述 |
|---|---|---|
| Start/Stop | Bit 0 | 启动(1)或停止(0)DMA引擎。停止时,DMA完成当前描述符后暂停。 |
| Transfer Direction | Bit 1 | 0:内存到外设(TX);1:外设到内存(RX)。 |
| Burst Length | Bits 3:2 | 设置单次突发传输的数据长度(如8/16/32字节)。影响总线带宽利用率。 |
| Priority | Bits 5:4 | DMA通道优先级(00=低,11=高)。多通道DMA中用于仲裁。 |
| Descriptor Fetch Mode | Bit 6 | 0:单次获取描述符;1:预取下一个描述符(提升性能)。 |
| Endianness | Bit 7 | 0:小端模式;1:大端模式。需与CPU和MAC控制器对齐。 |
驱动配置要点:在Linux内核中,DMA控制寄存器通常通过
writel()或iowrite32()写入。配置前必须确保DMA处于停止状态,否则写入可能被忽略或导致未定义行为。
/* 示例:配置DMA控制寄存器(基于Synopsys DW DMA) */
void dma_ctrl_config(struct dma_device *dma, int direction)
{
u32 ctrl = readl(dma->base + DMA_CTRL_REG);
/* 先停止DMA */
ctrl &= ~DMA_CTRL_START;
writel(ctrl, dma->base + DMA_CTRL_REG);
/* 配置方向 */
if (direction == DMA_DEV_TO_MEM) /* RX */
ctrl |= DMA_CTRL_DIR_RX;
else /* TX */
ctrl &= ~DMA_CTRL_DIR_RX;
/* 设置突发长度为32字节 */
ctrl &= ~DMA_CTRL_BURST_MASK;
ctrl |= (0x2 << DMA_CTRL_BURST_SHIFT);
/* 启用描述符预取 */
ctrl |= DMA_CTRL_DESC_PREFETCH;
/* 写入并启动 */
writel(ctrl, dma->base + DMA_CTRL_REG);
ctrl |= DMA_CTRL_START;
writel(ctrl, dma->base + DMA_CTRL_REG);
}
4.2 DMA状态寄存器(DMA Status Register)
DMA状态寄存器是驱动获取DMA引擎当前工作状态的“仪表盘”。通过读取该寄存器,驱动可以判断DMA是否空闲、是否发生错误、当前传输进度等信息。状态寄存器通常是只读的,但某些位在读取后会自动清零(write-1-to-clear)。
4.2.1 关键状态位
| 状态位 | 含义 | 读取后行为 |
|---|---|---|
| Busy | DMA正在处理描述符 | 保持状态,直到DMA空闲 |
| Transfer Complete | 当前描述符传输完成 | 自动清零(或写1清除) |
| Error | 发生总线错误或描述符错误 | 写1清除 |
| Descriptor Unavailable | 描述符链耗尽,DMA暂停 | 写1清除 |
| FIFO Overflow/Underflow | FIFO溢出或欠载 | 写1清除 |
常见陷阱:许多驱动工程师在中断服务程序中直接读取状态寄存器来判断错误类型,但忽略了“写1清除”特性。如果不清除错误位,后续中断可能无法正确触发。正确的做法是:读取状态寄存器后,立即将读取的值(或需要清除的位)写回。
/* 示例:DMA中断服务程序中的状态处理 */
irqreturn_t dma_isr(int irq, void *dev_id)
{
struct dma_device *dma = dev_id;
u32 status;
status = readl(dma->base + DMA_STATUS_REG);
if (status & DMA_STATUS_ERROR) {
dev_err(dma->dev, "DMA error occurred!\n");
/* 清除错误位 */
writel(DMA_STATUS_ERROR, dma->base + DMA_STATUS_REG);
}
if (status & DMA_STATUS_COMPLETE) {
/* 处理完成描述符 */
dma_process_completed(dma);
writel(DMA_STATUS_COMPLETE, dma->base + DMA_STATUS_REG);
}
if (status & DMA_STATUS_DESC_UNAVAIL) {
dev_warn(dma->dev, "Descriptor chain exhausted\n");
writel(DMA_STATUS_DESC_UNAVAIL, dma->base + DMA_STATUS_REG);
}
return IRQ_HANDLED;
}
4.3 描述符基地址寄存器(Descriptor Base Address Register)
描述符基地址寄存器是DMA引擎与内存描述符链之间的“桥梁”。DMA引擎通过该寄存器获取第一个描述符的物理地址,然后自动遍历描述符链完成数据传输。该寄存器通常需要配置为物理地址(非虚拟地址),且要求对齐到特定边界(如32字节或64字节)。
4.3.1 配置要点
- 物理地址对齐:描述符基地址必须对齐到描述符大小的整数倍。例如,如果描述符大小为16字节,则基地址必须16字节对齐。
- 一致性内存:描述符内存应分配自一致性DMA内存(如
dma_alloc_coherent()),避免缓存一致性问题。 - 链式结构:描述符基地址寄存器指向链头,每个描述符内部包含指向下一个描述符的指针(Next Descriptor Pointer)。
- 更新时机:只能在DMA停止或空闲时更新基地址寄存器。运行时更新可能导致DMA获取到错误的描述符。
/* 示例:配置描述符基地址寄存器 */
void dma_set_desc_base(struct dma_device *dma, dma_addr_t base_phys)
{
/* 确保DMA处于停止状态 */
u32 ctrl = readl(dma->base + DMA_CTRL_REG);
if (ctrl & DMA_CTRL_START) {
dev_err(dma->dev, "Cannot set desc base while DMA is running!\n");
return;
}
/* 写入物理地址(假设寄存器为32位) */
writel(lower_32_bits(base_phys), dma->base + DMA_DESC_BASE_LOW);
writel(upper_32_bits(base_phys), dma->base + DMA_DESC_BASE_HIGH);
dev_info(dma->dev, "Descriptor base set to 0x%llx\n",
(unsigned long long)base_phys);
}
4.4 中断使能寄存器(Interrupt Enable Register)
中断使能寄存器决定了哪些DMA事件可以触发中断信号给CPU。合理配置中断使能寄存器,可以避免CPU被频繁中断,同时确保关键事件(如错误、传输完成)能够及时得到处理。
4.4.1 常见中断使能位
| 中断源 | 使能位 | 建议策略 |
|---|---|---|
| 传输完成中断 | IE_TC | 通常使能,用于通知CPU处理已完成的数据包 |
| 错误中断 | IE_ERR | 必须使能,用于及时捕获总线错误、描述符错误等 |
| 描述符不可用中断 | IE_DU | 建议使能,用于检测描述符链耗尽的情况 |
| FIFO溢出/欠载中断 | IE_FIFO | 调试阶段使能,生产环境可关闭以减少中断频率 |
| 早期接收中断 | IE_ERX | 可选,用于低延迟场景(数据未完全到达时提前通知) |
性能优化技巧:对于高吞吐网络设备,可以关闭“传输完成中断”,改用“描述符完成计数”或“定时器轮询”方式。这样可以减少中断频率,提升CPU利用率。但错误中断必须始终使能。
/* 示例:配置中断使能寄存器 */
void dma_irq_enable(struct dma_device *dma, bool enable_error_only)
{
u32 ie = 0;
if (enable_error_only) {
/* 仅使能错误中断,用于高吞吐模式 */
ie |= DMA_IE_ERROR;
} else {
/* 标准模式:使能完成中断和错误中断 */
ie |= DMA_IE_TC | DMA_IE_ERROR | DMA_IE_DESC_UNAVAIL;
}
writel(ie, dma->base + DMA_IE_REG);
}
/* 在中断服务程序中,通过读取中断状态寄存器确认中断源 */
u32 dma_irq_status(struct dma_device *dma)
{
u32 status = readl(dma->base + DMA_STATUS_REG);
u32 ie = readl(dma->base + DMA_IE_REG);
/* 只有使能的中断源才会被反映到中断状态中 */
return status & ie;
}
4.5 寄存器配置综合流程
在实际驱动开发中,DMA引擎的初始化通常遵循以下步骤。这些步骤的顺序至关重要,错误的顺序可能导致DMA无法正常工作。
- 复位DMA引擎:通过复位寄存器或控制寄存器中的复位位,将DMA引擎恢复到已知状态。
- 分配描述符内存:使用
dma_alloc_coherent()分配一致性内存,并初始化描述符链。 - 配置描述符基地址寄存器:将描述符链头的物理地址写入基地址寄存器。
- 配置DMA控制寄存器:设置方向、突发长度、优先级等参数,但此时不启动DMA。
- 配置中断使能寄存器:使能需要的中断源,注册中断处理函数。
- 启动DMA:设置控制寄存器的Start位,DMA开始从描述符链中获取描述符并执行传输。
调试建议:在初始化完成后,通过读取DMA状态寄存器确认DMA是否进入Busy状态。如果状态寄存器显示错误