当前位置: 首页 > backend >正文

Linux驱动之DMA(三)

目录

    • 一、驱动内容
      • 1. 核心结构体解析
      • 2. 关键模块解析
      • 3. 驱动初始化流程
      • 4. 关键寄存器操作
      • 5. 典型工作流程
      • 6. 代码特点
      • 7. 重要函数列表
      • 8. 使用示例
    • 二、驱动中DMA的使用
      • 1. DMA通道初始化(`imx_uart_dma_init`)
      • 2. DMA发送流程(`imx_uart_dma_tx`)
      • 3. DMA发送完成回调(`imx_uart_dma_tx_callback`)
      • 4. DMA接收实现(`imx_uart_start_rx_dma`)
      • 5. DMA接收回调(`imx_uart_dma_rx_callback`)
      • 6. DMA引擎核心操作
      • 7. DMA寄存器配置
      • 8. DMA中断处理
      • 9. DMA性能优化
      • 10. DMA错误处理
      • 11. DMA与传统中断模式对比
      • 12. DMA传输生命周期
      • 13. DMA寄存器配置示例
      • 14. DMA缓冲区管理
      • 15. DMA性能调优建议
      • 16. DMA调试方法
      • 17. DMA异常处理
      • 18. DMA与电源管理
      • 19. DMA性能指标
      • 20. DMA调试寄存器
      • 21. DMA典型问题排查

  本文分析的驱动源码路径为:https://github.com/rockchip-linux/kernel/blob/develop-5.10/drivers/tty/serial/imx.c

一、驱动内容

1. 核心结构体解析

  1. imx_uart_data(硬件特性描述)
struct imx_uart_data {unsigned uts_reg;        // UART测试寄存器偏移enum imx_uart_type devtype; // 设备类型(i.MX1/21/53/6Q)
};
  1. imx_port(核心驱动结构)
struct imx_port {struct uart_port port;  // 标准UART端口结构struct timer_list timer; // 调制解调器状态轮询定时器struct dma_chan *dma_chan_rx, *dma_chan_tx; // DMA通道void *rx_buf;           // DMA接收缓冲区struct circ_buf rx_ring; // 接收环形缓冲区// 其他寄存器缓存、GPIO、时钟等成员...
};

2. 关键模块解析

DMA传输管理

  • DMA通道初始化:imx_uart_dma_init
  • DMA发送流程:imx_uart_dma_tx
  • DMA接收回调处理:imx_uart_dma_rx_callback

中断处理:

  • 主中断处理函数
static irqreturn_t imx_uart_int(int irq, void *dev_id)
{// 处理各种中断源(接收、发送、状态变化等)if (usr1 & USR1_RRDY) { // 接收就绪__imx_uart_rxint();}if (usr1 & USR1_TRDY || usr2 & USR2_TXDC) { // 发送完成imx_uart_transmit_buffer();}
}
  • DMA接收中断处理
static void imx_uart_dma_rx_callback(void *data)
{// 处理DMA接收完成事件dma_sync_sg_for_cpu(); // 同步数据tty_insert_flip_string(); // 将数据送入TTY层dma_sync_sg_for_device(); // 重新同步DMA
}

UART核心操作:

  • 发送缓冲区处理
static void imx_uart_transmit_buffer(struct imx_port *sport)
{// 处理XON/XOFF字符if (sport->port.x_char) {imx_uart_writel(sport, sport->port.x_char, URTX0);return;}// 使用DMA或PIO发送数据if (sport->dma_is_enabled) {imx_uart_dma_tx(sport);} else {// PIO模式发送while (!uart_circ_empty(xmit) && !TXFULL) {imx_uart_writel(sport, xmit->buf[xmit->tail], URTX0);}}
}
  • 接收错误处理
static void imx_uart_clear_rx_errors(struct imx_port *sport)
{// 处理帧错误、校验错误、溢出等错误if (usr2 & USR2_ORE) {sport->port.icount.overrun++;imx_uart_writel(sport, USR2_ORE, USR2);}
}

电源管理:

  • 挂起/恢复操作
static int imx_uart_suspend_noirq(struct device *dev)
{// 保存寄存器上下文imx_uart_save_context(sport);clk_disable(sport->clk_ipg); // 关闭时钟
}static int imx_uart_resume_noirq(struct device *dev)
{// 恢复寄存器状态imx_uart_restore_context(sport);
}

控制台支持(可选):

#if IS_ENABLED(CONFIG_SERIAL_IMX_CONSOLE)
static void imx_uart_console_write(struct console *co, const char *s, unsigned int count)
{// 控制台输出实现while (imx_uart_readl(sport, UTS) & UTS_TXFULL) {}imx_uart_writel(sport, ch, URTX0);
}
#endif

3. 驱动初始化流程

static int imx_uart_probe(struct platform_device *pdev)
{// 1. 获取平台资源base = devm_ioremap_resource(...); // 寄存器映射sport->clk_ipg = devm_clk_get(...); // 获取时钟// 2. 初始化UART端口sport->port.ops = &imx_uart_pops; // 操作函数集sport->port.uartclk = clk_get_rate(sport->clk_per); // 设置时钟频率// 3. 注册DMA通道if (imx_uart_dma_init(sport) == 0) {imx_uart_enable_dma(sport); // 启用DMA}// 4. 注册UART端口return uart_add_one_port(&imx_uart_uart_driver, &sport->port);
}

4. 关键寄存器操作

  1. 发送寄存器(URTX0)
imx_uart_writel(sport, data, URTX0); // 发送单个字符
  1. 控制寄存器(UCR1-UCR4)
// 启用DMA发送
ucr1 |= UCR1_TXDMAEN;
imx_uart_writel(sport, ucr1, UCR1);
  1. FIFO控制寄存器(UFCR)
imx_uart_writel(sport, TXTL_DMA << UFCR_TXTL_SHF, UFCR); // 设置FIFO触发级别

5. 典型工作流程

数据发送流程:

用户写入数据 → imx_uart_start_tx() → └─ DMA模式: imx_uart_dma_tx() → dmaengine_prep_slave_sg() → 启动DMA传输 → └─ PIO模式: imx_uart_writel() → 硬件发送 → 中断处理 → imx_uart_txint()

数据接收流程:

硬件接收 → DMA中断 → imx_uart_dma_rx_callback() → └─ tty_insert_flip_string() → 提交到TTY层 → 用户读取

电源管理:

suspend → imx_uart_suspend_noirq() → └─ 保存寄存器状态 → 禁用时钟 → 进入睡眠
resume → imx_uart_resume_noirq() → └─ 恢复寄存器 → 启用时钟 → 恢复运行

6. 代码特点

  1. DMA优化

    • 使用循环DMA(cyclic DMA)实现高效接收
    • 支持DMA和PIO两种传输模式
    • 自动切换DMA/POLL模式
  2. 硬件特性适配

    • 支持i.MX1/21/53/6Q等不同版本
    • 处理DTE/RTS/CTS等硬件流控
    • 支持RS485模式
  3. 错误处理

    • 处理溢出(OVERRUN)、帧错误(FRAMERR)、校验错误(PARITYERR)等
    • 自动恢复机制(如imx_uart_flush_buffer())

7. 重要函数列表

函数名功能说明调用时机
imx_uart_dma_tx()启动DMA发送有数据需要发送时
imx_uart_dma_rx_callback()DMA接收完成回调接收DMA完成时
imx_uart_transmit_buffer()发送数据处理发送缓冲区有数据时
imx_uart_set_termios()设置串口参数(波特率等)termios配置变更时
imx_uart_rs485_config()RS485模式配置RS485模式切换时
imx_uart_probe()驱动探测函数平台设备匹配时

8. 使用示例

  1. 发送数据
// 用户调用write()时,最终调用
imx_uart_start_tx()imx_uart_dma_tx() → 启动DMA传输
  1. 接收数据
// 硬件接收到数据时触发
DMA中断 → imx_uart_dma_rx_callback() → 数据提交到TTY层
  1. 设置波特率
stty /dev/ttymxc0 115200
→ 内核调用imx_uart_set_termios() → 重新配置UBIR/UBMR寄存器

二、驱动中DMA的使用

1. DMA通道初始化(imx_uart_dma_init

static int imx_uart_dma_init(struct imx_port *sport)
{struct dma_slave_config slave_config = {};struct device *dev = sport->port.dev;int ret;// 1. 请求DMA通道(接收方向)sport->dma_chan_rx = dma_request_slave_channel(dev, "rx");if (!sport->dma_chan_rx) {dev_dbg(dev, "cannot get the DMA channel.\n");ret = -EINVAL;goto err;}// 2. 配置DMA参数(接收方向)slave_config.direction = DMA_DEV_TO_MEM; // 设备到内存slave_config.src_addr = sport->port.mapbase + URXD0; // UART接收寄存器物理地址slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; // 每次传输1字节slave_config.src_maxburst = RXTL_DMA - 1; // 突发传输大小(比FIFO触发级少1)ret = dmaengine_slave_config(sport->dma_chan_rx, &slave_config); // 应用配置// 3. 分配接收缓冲区sport->rx_buf = kzalloc(RX_BUF_SIZE, GFP_KERNEL); // 16页大小if (!sport->rx_buf) {ret = -ENOMEM;goto err;}sport->rx_ring.buf = sport->rx_buf;// 4. 请求并配置发送DMA通道sport->dma_chan_tx = dma_request_slave_channel(dev, "tx");slave_config.direction = DMA_MEM_TO_DEV; // 内存到设备slave_config.dst_addr = sport->port.mapbase + URTX0; // UART发送寄存器物理地址slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;slave_config.dst_maxburst = TXTL_DMA; // 发送FIFO触发级别dmaengine_slave_config(sport->dma_chan_tx, &slave_config);
}
  1. 通道请求

    • dma_request_slave_channel(dev, "rx"):通过设备树或平台数据匹配DMA通道
    • dma_request_slave_channel(dev, "tx"):同上,请求发送通道
  2. DMA配置

    • 方向DMA_DEV_TO_MEM(接收)或 DMA_MEM_TO_DEV(发送)
    • 地址宽度DMA_SLAVE_BUSWIDTH_1_BYTE保证字节对齐传输
    • 突发传输src_maxburst设置DMA突发传输大小,与FIFO触发级别配合
  3. 缓冲区管理

    • rx_buf = kzalloc(RX_BUF_SIZE, GFP_KERNEL):分配连续内存作为DMA缓冲区
    • RX_BUF_SIZE = RX_DMA_PERIODS * PAGE_SIZE / 4:16个周期,每个周期1页/4=4KB

2. DMA发送流程(imx_uart_dma_tx

static void imx_uart_dma_tx(struct imx_port *sport)
{struct circ_buf *xmit = &sport->port.state->xmit;struct scatterlist *sgl = sport->tx_sgl;struct dma_async_tx_descriptor *desc;struct dma_chan	*chan = sport->dma_chan_tx;// 1. 检查是否已有DMA传输if (sport->dma_is_txing)return;// 2. 准备DMA映射sport->tx_bytes = uart_circ_chars_pending(xmit);if (xmit->tail < xmit->head || xmit->head == 0) {// 单个scatterlistsg_init_one(sgl, xmit->buf + xmit->tail, sport->tx_bytes);} else {// 环形缓冲区分两段映射sg_init_table(sgl, 2);sg_set_buf(sgl, xmit->buf + xmit->tail, UART_XMIT_SIZE - xmit->tail);sg_set_buf(sgl + 1, xmit->buf, xmit->head);}// 3. 执行DMA映射ret = dma_map_sg(dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE);// 4. 准备DMA描述符desc = dmaengine_prep_slave_sg(chan, sgl, ret, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);// 5. 设置回调desc->callback = imx_uart_dma_tx_callback;desc->callback_param = sport;// 6. 提交并启动DMAdmaengine_submit(desc);dma_async_issue_pending(chan);
}
  1. 环形缓冲区分段

    • 当发送缓冲区头尾指针未回绕时,单段映射
    • 当头尾指针回绕时,使用两个scatterlist实现环形缓冲区映射
  2. DMA映射

    • dma_map_sg():将虚拟地址转换为DMA物理地址
    • DMA_TO_DEVICE:传输方向标志
  3. 描述符准备

    • dmaengine_prep_slave_sg():准备scatter-gather DMA传输
    • DMA_PREP_INTERRUPT:要求传输完成时触发中断
  4. 传输启动

    • dmaengine_submit():将描述符加入传输队列
    • dma_async_issue_pending():激活DMA传输

3. DMA发送完成回调(imx_uart_dma_tx_callback

static void imx_uart_dma_tx_callback(void *data)
{struct imx_port *sport = data;struct scatterlist *sgl = &sport->tx_sgl[0];struct circ_buf *xmit = &sport->port.state->xmit;// 1. 解除DMA映射dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE);// 2. 更新发送状态xmit->tail = (xmit->tail + sport->tx_bytes) & (UART_XMIT_SIZE - 1);sport->port.icount.tx += sport->tx_bytes;// 3. 重置DMA状态sport->dma_is_txing = 0;// 4. 唤醒等待的写入进程if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)uart_write_wakeup(&sport->port);// 5. 如果还有数据,重新启动DMAif (!uart_circ_empty(xmit) && !uart_tx_stopped(&sport->port))imx_uart_dma_tx(sport);
}
  1. 传输完成处理

    • 更新发送缓冲区尾指针xmit->tail
    • 增加发送计数icount.tx
  2. 自动重启机制

    • 如果发送缓冲区仍有数据,自动重新启动DMA传输
    • 实现"伪循环"效果(非硬件循环,而是软件层重提交)
  3. 同步与唤醒

    • uart_write_wakeup():唤醒等待的写入进程
    • 保证数据流的连续性

4. DMA接收实现(imx_uart_start_rx_dma

static int imx_uart_start_rx_dma(struct imx_port *sport)
{struct scatterlist *sgl = &sport->rx_sgl;struct dma_chan	*chan = sport->dma_chan_rx;struct device *dev = sport->port.dev;struct dma_async_tx_descriptor *desc;int ret;// 1. 初始化scatterlistsg_init_one(sgl, sport->rx_buf, RX_BUF_SIZE);// 2. 执行DMA映射ret = dma_map_sg(dev, sgl, 1, DMA_FROM_DEVICE);if (ret == 0) {dev_err(dev, "DMA mapping error for RX.\n");return -EINVAL;}// 3. 准备循环DMA描述符desc = dmaengine_prep_dma_cyclic(chan, sg_dma_address(sgl), sg_dma_len(sgl),sg_dma_len(sgl) / sport->rx_periods, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);// 4. 设置回调desc->callback = imx_uart_dma_rx_callback;desc->callback_param = sport;// 5. 提交并启动DMAsport->dma_is_rxing = 1;sport->rx_cookie = dmaengine_submit(desc);dma_async_issue_pending(chan);return 0;
}
  1. 循环DMA(Cyclic DMA)

    • dmaengine_prep_dma_cyclic():创建循环传输
    • 每当DMA传输到缓冲区末尾时自动回到开头,适合流式接收
  2. 缓冲区管理

    • rx_ring:环形缓冲区跟踪读写指针
    • rx_periods:将缓冲区分成16个周期(RX_DMA_PERIODS
  3. 硬件级优化

    • src_maxburst = RXTL_DMA - 1:突发传输大小比FIFO触发级少1
    • 利用aging timer机制:当字符在FIFO中停留时间过长时触发DMA传输

5. DMA接收回调(imx_uart_dma_rx_callback

static void imx_uart_dma_rx_callback(void *data)
{struct imx_port *sport = data;struct scatterlist *sgl = &sport->rx_sgl;struct tty_port *port = &sport->port.state->port;struct dma_tx_state state;enum dma_status status;unsigned int w_bytes = 0, r_bytes, bd_size;// 1. 获取DMA状态status = dmaengine_tx_status(chan, sport->rx_cookie, &state);// 2. 计算数据位置rx_ring->head = sg_dma_len(sgl) - state.residue; // 当前写入位置bd_size = sg_dma_len(sgl) / sport->rx_periods; // 每个周期大小rx_ring->tail = ((rx_ring->head-1) / bd_size) * bd_size; // 当前读取位置// 3. 数据搬运if (rx_ring->head > rx_ring->tail) {r_bytes = rx_ring->head - rx_ring->tail;dma_sync_sg_for_cpu(); // 同步CPU访问w_bytes = tty_insert_flip_string(); // 搬运到TTY层dma_sync_sg_for_device(); // 重新同步DMA访问}// 4. 提交TTY缓冲区if (w_bytes) {tty_flip_buffer_push(port);dev_dbg(sport->port.dev, "Received %d bytes via DMA\n", w_bytes);}
}
  1. 缓冲区同步

    • dma_sync_sg_for_cpu():CPU访问前同步
    • dma_sync_sg_for_device():DMA访问前重新同步
  2. 数据量计算

    • state.residue:剩余未传输字节数
    • head = total - residue:计算当前写入位置
    • tail = head所在周期的起始位置:确定读取范围
  3. 数据搬运

    • tty_insert_flip_string():将DMA缓冲区数据插入TTY翻转缓冲区
    • tty_flip_buffer_push():提交缓冲区供上层读取

6. DMA引擎核心操作

操作类型函数调用功能说明
通道请求dma_request_slave_channel()获取DMA通道
通道配置dmaengine_slave_config()设置传输方向、地址、突发大小等
单次传输准备dmaengine_prep_slave_sg() 准备scatter-gather DMA传输
循环传输准备dmaengine_prep_dma_cyclic()准备循环DMA传输(用于接收)
映射/解映射dma_map_sg() / dma_unmap_sg()虚拟地址到物理地址映射
同步CPU访问dma_sync_sg_for_cpu()保证CPU访问前数据一致性
同步设备访问dma_sync_sg_for_device()保证DMA访问前数据一致性
提交传输dmaengine_submit()将描述符加入传输队列
启动传输dma_async_issue_pending()激活DMA传输
终止传输dmaengine_terminate_all()终止所有待处理的DMA传输

7. DMA寄存器配置

static void imx_uart_enable_dma(struct imx_port *sport)
{u32 ucr1 = imx_uart_readl(sport, UCR1);ucr1 |= UCR1_RXDMAEN | UCR1_TXDMAEN | UCR1_ATDMAEN; // 启用DMA功能imx_uart_writel(sport, ucr1, UCR1);
}
寄存器位定义功能
UCR1UCR1_RXDMAEN接收DMA使能
UCR1_TXDMAEN发送DMA使能
UCR1_ATDMAEN老化定时器DMA使能(接收)
UFCRUFCR_TXTL发送FIFO触发级别
UFCR_RXTL接收FIFO触发级别

8. DMA中断处理

static irqreturn_t imx_uart_int(int irq, void *dev_id)
{// 1. 读取中断状态usr1 = imx_uart_readl(sport, USR1);usr2 = imx_uart_readl(sport, USR2);// 2. 处理发送完成中断if (usr1 & USR1_TRDY || usr2 & USR2_TXDC) {imx_uart_transmit_buffer(sport);}// 3. 处理接收中断if (usr1 & (USR1_RRDY | USR1_AGTIM)) {__imx_uart_rxint(irq, dev_id);}
}
  1. 发送中断

    • USR1_TRDY:发送缓冲区可写
    • USR2_TXDC:传输完成
  2. 接收中断

    • USR1_RRDY:接收缓冲区准备就绪
    • USR1_AGTIM:老化定时器超时(接收中断)

9. DMA性能优化

  1. 突发传输(Burst Size)

    slave_config.src_maxburst = RXTL_DMA - 1; // 接收突发大小=FIFO触发级-1
    slave_config.dst_maxburst = TXTL_DMA; // 发送突发大小=FIFO触发级
    
    • 保证DMA传输与FIFO触发级别匹配,避免过度中断
  2. 循环接收优化

    #define RX_DMA_PERIODS 16
    #define RX_BUF_SIZE (RX_DMA_PERIODS * PAGE_SIZE / 4)
    
    • 将4KB缓冲区划分为16个周期,每个周期256字节
    • 通过dmaengine_prep_dma_cyclic()实现自动循环
  3. 零拷贝优化

    • 数据直接从DMA缓冲区搬运到TTY层
    • 减少内存拷贝次数

10. DMA错误处理

  1. DMA错误检测

    status = dmaengine_tx_status(chan, sport->rx_cookie, &state);
    if (status == DMA_ERROR) {imx_uart_clear_rx_errors(sport);return;
    }
    
  2. 传输终止

    dmaengine_terminate_sync(sport->dma_chan_tx); // 终止发送
    dmaengine_terminate_sync(sport->dma_chan_rx); // 终止接收
    
  3. 异常处理

    • 溢出处理USR2_ORE标志
    • 帧错误处理USR1_FRAMERR标志
    • 校验错误处理USR1_PARITYERR标志

11. DMA与传统中断模式对比

特性DMA模式中断模式
CPU占用率极低较高
传输效率高(突发传输)低(逐字节中断)
适用场景大数据量传输小数据量或无DMA支持平台
中断频率低(每DMA周期触发)高(每字节触发)
缓冲区管理环形DMA缓冲区内核环形缓冲区
数据搬运次数1次/周期1次/字节

12. DMA传输生命周期

1. 初始化:→ 请求DMA通道→ 配置DMA参数→ 分配缓冲区2. 启动传输:→ 准备描述符→ 提交并启动DMA3. 传输中:→ 硬件DMA搬运数据→ 传输完成触发中断→ 调用回调函数4. 完成处理:→ 同步CPU访问→ 数据搬运到TTY层→ 重新启动DMA(如需要)5. 终止:→ 终止DMA传输→ 释放DMA缓冲区→ 释放DMA通道

13. DMA寄存器配置示例

/* FIFO控制寄存器配置 */
imx_uart_writel(sport, TXTL_DMA << UFCR_TXTL_SHF | RXTL_DMA, UFCR);/* 启用DMA功能 */
ucr1 |= UCR1_RXDMAEN | UCR1_TXDMAEN | UCR1_ATDMAEN;
imx_uart_writel(sport, ucr1, UCR1);
  1. FIFO触发级

    • 发送:TXTL_DMA = 8(8字节触发)
    • 接收:RXTL_DMA = 9(9字节触发)
  2. 老化定时器

    • UCR1_ATDMAEN:启用老化定时器DMA模式
    • 当字符在FIFO中停留时间过长时触发DMA传输

14. DMA缓冲区管理

struct imx_port {// ...void *rx_buf;            // DMA接收缓冲区struct circ_buf rx_ring;  // 环形缓冲区管理// ...
};

环形缓冲区管理:

// 计算当前接收位置
rx_ring.head = sg_dma_len(sgl) - state.residue;
bd_size = sg_dma_len(sgl) / sport->rx_periods;
rx_ring.tail = ((rx_ring.head-1) / bd_size) * bd_size;// 数据量计算
r_bytes = rx_ring.head - rx_ring.tail;

特点:

  1. 硬件级环形:DMA硬件自动循环填充缓冲区
  2. 软件级环形rx_ring跟踪读取位置
  3. 零拷贝:数据直接从DMA缓冲区搬运到TTY层

15. DMA性能调优建议

  1. 调整FIFO触发级

    imx_uart_setup_ufcr(sport, TXTL_DMA, RXTL_DMA);
    
    • 发送:8字节触发
    • 接收:9字节触发(老化定时器触发)
  2. 调整DMA周期数

    #define RX_DMA_PERIODS 16
    #define RX_BUF_SIZE (RX_DMA_PERIODS * PAGE_SIZE / 4)
    
    • 16个周期,每个周期256字节(总4KB)
    • 根据数据流量调整周期数
  3. 优化突发大小

    slave_config.src_maxburst = RXTL_DMA - 1; // 接收突发大小=FIFO触发级-1
    slave_config.dst_maxburst = TXTL_DMA;       // 发送突发大小=FIFO触发级
    
    • 匹配FIFO触发级别,避免DMA与FIFO冲突

16. DMA调试方法

  1. 查看DMA通道状态

    cat /proc/interrupts | grep imx
    
  2. 日志输出

    dev_dbg(sport->port.dev, "TX: %d bytes by DMA\n", w_bytes); // 发送日志
    dev_dbg(sport->port.dev, "RX: %d bytes received\n", w_bytes); // 接收日志
    
  3. 手动触发DMA测试

    echo "DMA test" > /dev/ttymxc0  // 测试发送DMA
    cat /dev/ttymxc0              // 测试接收DMA
    
  4. 错误统计

    sport->port.icount.rx += w_bytes; // 记录接收字节数
    sport->port.icount.overrun++     // 记录溢出错误
    

17. DMA异常处理

static void imx_uart_clear_rx_errors(struct imx_port *sport)
{// 处理接收错误(帧错误、校验错误、溢出等)if (usr2 & USR2_ORE) {sport->port.icount.overrun++;imx_uart_writel(sport, USR2_ORE, USR2);}
}
  1. 溢出处理

    • 清除USR2_ORE标志
    • 增加溢出计数器
  2. 帧错误

    • 清除USR1_FRAMERR标志
    • 增加帧错误计数器
  3. 校验错误

    • 清除USR1_PARITYERR标志
    • 增加校验错误计数器

18. DMA与电源管理

static int imx_uart_suspend(struct device *dev)
{if (sport->dma_is_enabled) {dmaengine_terminate_sync(sport->dma_chan_tx);dmaengine_terminate_sync(sport->dma_chan_rx);}
}static int imx_uart_resume(struct device *dev)
{if (sport->dma_is_enabled) {imx_uart_enable_dma(sport);imx_uart_start_rx_dma(sport);}
}

电源管理要点:

  1. 挂起时

    • 终止所有DMA传输
    • 保存寄存器状态
  2. 恢复时

    • 恢复寄存器状态
    • 重新启动DMA接收

19. DMA性能指标

指标DMA模式中断模式优化空间
CPU占用率<1%5-10%可进一步优化突发大小
传输延迟可调整FIFO触发级
最大传输速率115200+115200可调整DMA周期数
数据完整性一般可增强错误处理

20. DMA调试寄存器

// 读取DMA相关状态寄存器
usr1 = imx_uart_readl(sport, USR1);
usr2 = imx_uart_readl(sport, USR2);// 打印DMA状态
dev_dbg(sport->port.dev, "DMA state: head=%u, tail=%u, residue=%u\n",rx_ring.head, rx_ring.tail, state.residue);
  1. USR1

    • USR1_RRDY:接收就绪
    • USR1_TRDY:发送就绪
  2. USR2

    • USR2_ORE:溢出错误
    • USR2_RDR:接收数据就绪
  3. DMA状态

    • dma_tx_nents:当前传输段数
    • dma_cookie:DMA事务标识

21. DMA典型问题排查

  1. DMA通道获取失败

    • 原因:设备树未配置DMA
    • 解决:检查dtsdma-channels属性
  2. DMA映射失败

    • 原因:内存分配失败或DMA地址越界
    • 解决:检查rx_buf分配和dma_map_sg()返回值
  3. DMA传输失败

    • 原因:DMA配置错误或硬件故障
    • 解决:检查dmaengine_slave_config()返回值
  4. DMA缓冲区溢出

    • 原因:处理速度不足
    • 解决:增大RX_BUF_SIZE或调整FIFO触发级

  通过以上深度解析,可以全面理解该UART驱动中DMA的实现机制。DMA模式显著降低了CPU占用率,特别适合高速串口通信场景。实际开发中,需要根据具体应用场景调整DMA缓冲区大小、FIFO触发级和突发传输大小等参数以获得最佳性能。

http://www.xdnf.cn/news/18631.html

相关文章:

  • ubuntu中网卡的 IP 及网关配置设置为永久生效
  • Maxwell学习笔记
  • 8月精选!Windows 11 25H2 【版本号:26200.5733】
  • 从技术精英到“芯”途末路:一位工程师的沉沦与救赎
  • IC验证 APB 项目(二)——框架结构(总)
  • 项目编译 --- 基于cmake ninja编译 rtos项目
  • COSMIC智能化编写工具:革命性提升软件文档生成效率
  • 20.13 ChatGLM3 QLoRA微调实战:3步实现高效低资源训练
  • Shell Case 条件语句详解
  • 数据挖掘 4.8 评估泛化能力
  • k8s原理及操作
  • Go语言环境安装
  • Spring面试题及详细答案 125道(16-25) -- 核心概念与基础2
  • Jwt令牌设置介绍
  • c++基础知识入门
  • Https之(三)TLS双向认证
  • 打响“A+H”双重上市突围战,云天励飞实力如何?
  • 云原生俱乐部-RH294知识点归纳(3)
  • [滑动窗口]1493. 删掉一个元素以后全为 1 的最长子数组
  • 今天学习计算机网格技术的TCP,UDP以及OSPF
  • 【AI智能体】Dify 搭建业务单据差异核对助手实战详解
  • 【Spring Cloud 微服务】3.智能路由器——深入理解与配置负载均衡
  • 【数据结构】从基础到实战:全面解析归并排序与计数排序
  • 在 Docker 容器中查看 Python 版本
  • SpringBoot的学生学习笔记共享系统设计与实现
  • SO_REUSEADDR
  • 计算机视觉与自然语言处理技术体系概述
  • Python内置函数全解析:30个核心函数语法、案例与最佳实践指南
  • Shell脚本-expect
  • Linux 软件编程(十)网络编程:网络协议,UDP 与 TCP 知识点