目錄
- 一、驅動內容
- 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. 核心結構體解析
- imx_uart_data(硬件特性描述)
struct imx_uart_data {unsigned uts_reg; // UART測試寄存器偏移enum imx_uart_type devtype; // 設備類型(i.MX1/21/53/6Q)
};
- 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. 關鍵寄存器操作
- 發送寄存器(URTX0):
imx_uart_writel(sport, data, URTX0); // 發送單個字符
- 控制寄存器(UCR1-UCR4):
// 啟用DMA發送
ucr1 |= UCR1_TXDMAEN;
imx_uart_writel(sport, ucr1, UCR1);
- 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. 代碼特點
-
DMA優化:
- 使用循環DMA(cyclic DMA)實現高效接收
- 支持DMA和PIO兩種傳輸模式
- 自動切換DMA/POLL模式
-
硬件特性適配:
- 支持i.MX1/21/53/6Q等不同版本
- 處理DTE/RTS/CTS等硬件流控
- 支持RS485模式
-
錯誤處理:
- 處理溢出(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. 使用示例
- 發送數據:
// 用戶調用write()時,最終調用
imx_uart_start_tx() → imx_uart_dma_tx() → 啟動DMA傳輸
- 接收數據:
// 硬件接收到數據時觸發
DMA中斷 → imx_uart_dma_rx_callback() → 數據提交到TTY層
- 設置波特率:
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);
}
-
通道請求:
dma_request_slave_channel(dev, "rx")
:通過設備樹或平臺數據匹配DMA通道dma_request_slave_channel(dev, "tx")
:同上,請求發送通道
-
DMA配置:
- 方向:
DMA_DEV_TO_MEM
(接收)或DMA_MEM_TO_DEV
(發送) - 地址寬度:
DMA_SLAVE_BUSWIDTH_1_BYTE
保證字節對齊傳輸 - 突發傳輸:
src_maxburst
設置DMA突發傳輸大小,與FIFO觸發級別配合
- 方向:
-
緩沖區管理:
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);
}
-
環形緩沖區分段:
- 當發送緩沖區頭尾指針未回繞時,單段映射
- 當頭尾指針回繞時,使用兩個scatterlist實現環形緩沖區映射
-
DMA映射:
dma_map_sg()
:將虛擬地址轉換為DMA物理地址DMA_TO_DEVICE
:傳輸方向標志
-
描述符準備:
dmaengine_prep_slave_sg()
:準備scatter-gather DMA傳輸DMA_PREP_INTERRUPT
:要求傳輸完成時觸發中斷
-
傳輸啟動:
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);
}
-
傳輸完成處理:
- 更新發送緩沖區尾指針
xmit->tail
- 增加發送計數
icount.tx
- 更新發送緩沖區尾指針
-
自動重啟機制:
- 如果發送緩沖區仍有數據,自動重新啟動DMA傳輸
- 實現"偽循環"效果(非硬件循環,而是軟件層重提交)
-
同步與喚醒:
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;
}
-
循環DMA(Cyclic DMA):
dmaengine_prep_dma_cyclic()
:創建循環傳輸- 每當DMA傳輸到緩沖區末尾時自動回到開頭,適合流式接收
-
緩沖區管理:
rx_ring
:環形緩沖區跟蹤讀寫指針rx_periods
:將緩沖區分成16個周期(RX_DMA_PERIODS
)
-
硬件級優化:
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);}
}
-
緩沖區同步:
dma_sync_sg_for_cpu()
:CPU訪問前同步dma_sync_sg_for_device()
:DMA訪問前重新同步
-
數據量計算:
state.residue
:剩余未傳輸字節數head = total - residue
:計算當前寫入位置tail = head所在周期的起始位置
:確定讀取范圍
-
數據搬運:
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);
}
寄存器 | 位定義 | 功能 |
---|---|---|
UCR1 | UCR1_RXDMAEN | 接收DMA使能 |
UCR1_TXDMAEN | 發送DMA使能 | |
UCR1_ATDMAEN | 老化定時器DMA使能(接收) | |
UFCR | UFCR_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);}
}
-
發送中斷:
USR1_TRDY
:發送緩沖區可寫USR2_TXDC
:傳輸完成
-
接收中斷:
USR1_RRDY
:接收緩沖區準備就緒USR1_AGTIM
:老化定時器超時(接收中斷)
9. DMA性能優化
-
突發傳輸(Burst Size):
slave_config.src_maxburst = RXTL_DMA - 1; // 接收突發大小=FIFO觸發級-1 slave_config.dst_maxburst = TXTL_DMA; // 發送突發大小=FIFO觸發級
- 保證DMA傳輸與FIFO觸發級別匹配,避免過度中斷
-
循環接收優化:
#define RX_DMA_PERIODS 16 #define RX_BUF_SIZE (RX_DMA_PERIODS * PAGE_SIZE / 4)
- 將4KB緩沖區劃分為16個周期,每個周期256字節
- 通過
dmaengine_prep_dma_cyclic()
實現自動循環
-
零拷貝優化:
- 數據直接從DMA緩沖區搬運到TTY層
- 減少內存拷貝次數
10. DMA錯誤處理
-
DMA錯誤檢測:
status = dmaengine_tx_status(chan, sport->rx_cookie, &state); if (status == DMA_ERROR) {imx_uart_clear_rx_errors(sport);return; }
-
傳輸終止:
dmaengine_terminate_sync(sport->dma_chan_tx); // 終止發送 dmaengine_terminate_sync(sport->dma_chan_rx); // 終止接收
-
異常處理:
- 溢出處理:
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);
-
FIFO觸發級:
- 發送:
TXTL_DMA = 8
(8字節觸發) - 接收:
RXTL_DMA = 9
(9字節觸發)
- 發送:
-
老化定時器:
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;
特點:
- 硬件級環形:DMA硬件自動循環填充緩沖區
- 軟件級環形:
rx_ring
跟蹤讀取位置 - 零拷貝:數據直接從DMA緩沖區搬運到TTY層
15. DMA性能調優建議
-
調整FIFO觸發級:
imx_uart_setup_ufcr(sport, TXTL_DMA, RXTL_DMA);
- 發送:8字節觸發
- 接收:9字節觸發(老化定時器觸發)
-
調整DMA周期數:
#define RX_DMA_PERIODS 16 #define RX_BUF_SIZE (RX_DMA_PERIODS * PAGE_SIZE / 4)
- 16個周期,每個周期256字節(總4KB)
- 根據數據流量調整周期數
-
優化突發大小:
slave_config.src_maxburst = RXTL_DMA - 1; // 接收突發大小=FIFO觸發級-1 slave_config.dst_maxburst = TXTL_DMA; // 發送突發大小=FIFO觸發級
- 匹配FIFO觸發級別,避免DMA與FIFO沖突
16. DMA調試方法
-
查看DMA通道狀態:
cat /proc/interrupts | grep imx
-
日志輸出:
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); // 接收日志
-
手動觸發DMA測試:
echo "DMA test" > /dev/ttymxc0 // 測試發送DMA cat /dev/ttymxc0 // 測試接收DMA
-
錯誤統計:
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);}
}
-
溢出處理:
- 清除
USR2_ORE
標志 - 增加溢出計數器
- 清除
-
幀錯誤:
- 清除
USR1_FRAMERR
標志 - 增加幀錯誤計數器
- 清除
-
校驗錯誤:
- 清除
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);}
}
電源管理要點:
-
掛起時:
- 終止所有DMA傳輸
- 保存寄存器狀態
-
恢復時:
- 恢復寄存器狀態
- 重新啟動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);
-
USR1:
USR1_RRDY
:接收就緒USR1_TRDY
:發送就緒
-
USR2:
USR2_ORE
:溢出錯誤USR2_RDR
:接收數據就緒
-
DMA狀態:
dma_tx_nents
:當前傳輸段數dma_cookie
:DMA事務標識
21. DMA典型問題排查
-
DMA通道獲取失敗:
- 原因:設備樹未配置DMA
- 解決:檢查
dts
中dma-channels
屬性
-
DMA映射失敗:
- 原因:內存分配失敗或DMA地址越界
- 解決:檢查
rx_buf
分配和dma_map_sg()
返回值
-
DMA傳輸失敗:
- 原因:DMA配置錯誤或硬件故障
- 解決:檢查
dmaengine_slave_config()
返回值
-
DMA緩沖區溢出:
- 原因:處理速度不足
- 解決:增大
RX_BUF_SIZE
或調整FIFO觸發級
??通過以上深度解析,可以全面理解該UART驅動中DMA的實現機制。DMA模式顯著降低了CPU占用率,特別適合高速串口通信場景。實際開發中,需要根據具體應用場景調整DMA緩沖區大小、FIFO觸發級和突發傳輸大小等參數以獲得最佳性能。