DMA工作原理及Linux下DMA实际使用
一个DMA controller可以"同时"进行的DMA传输的个数是有限的,这称作DMA channels。这里的channel,只是一个逻辑概念,因为鉴于总线访问的冲突,以及内存一致性的考量,从物理的角度看,不大可能会同时进行两个(及以上)的DMA传输。
因而DMA channel不太可能是物理上独立的通道;很多时候,DMA channels是DMA controller为了方便,抽象出来的概念,让consumer以为独占了一个channel,实际上所有channel的DMA传输请求都会在DMA controller中进行仲裁,进而串行传输;
因此,软件也可以基于controller提供的channel(我们称为"物理"channel),自行抽象更多的“逻辑”channel,软件会管理这些逻辑channel上的传输请求。实际上很多平台都这样做了,在DMA Engine framework中,不会区分这两种channel(本质上没区别)。
apdma: dma-controller@11000580 {
compatible = "mediatek,mt6577-uart-dma";
reg = <0 0x11000680 0 0x80>,
<0 0x11000700 0 0x80>,
<0 0x11000780 0 0x80>,
<0 0x11000800 0 0x80>;
interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_LOW>,
<GIC_SPI 54 IRQ_TYPE_LEVEL_LOW>,
<GIC_SPI 55 IRQ_TYPE_LEVEL_LOW>,
<GIC_SPI 60 IRQ_TYPE_LEVEL_LOW>;
clocks = <&infracfg_ao CLK_IFR_AP_DMA>;
clock-names = "apdma";
#dma-cells = <1>;
};
uart0: serial@11020000 {
compatible = "mediatek,mt6761-uart",
"mediatek,mt6577-uart";
reg = <0 0x11002000 0 0x1000>;
interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_LOW>;
clocks = <&clk26m>, <&infracfg_ao CLK_IFR_UART0>;
clock-names = "baud", "bus";
dmas = <&apdma 0
&apdma 1>;
dma-names = "tx", "rx";
};
uart1: serial@11030000 {
compatible = "mediatek,mt6761-uart",
"mediatek,mt6577-uart";
reg = <0 0x11003000 0 0x1000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_LOW>;
clocks = <&clk26m>, <&infracfg_ao CLK_IFR_UART1>;
clock-names = "baud", "bus";
dmas = <&apdma 2
&apdma 3>;
dma-names = "tx", "rx";
};
- kernel-4.9/drivers/dma/8250_mtk_dma.c
static int mtk_dma_probe(struct platform_device *pdev) { // ...省略 for (i = 0; i < MTK_SDMA_CHANNELS; i++) { res = platform_get_resource(pdev, IORESOURCE_MEM, i); if (res == NULL) return -ENODEV; mtkd->mem_base[i] = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(mtkd->mem_base[i])) return PTR_ERR(mtkd->mem_base[i]); } /* request irq */ for (i = 0; i < MTK_SDMA_CHANNELS; i++) { mtkd->dma_irq[i] = platform_get_irq(pdev, i); if ((int)mtkd->dma_irq[i] < 0) { pr_err("Cannot claim IRQ%d\n", i); return mtkd->dma_irq[i]; } } // ...省略 }
- kernel-4.9/drivers/tty/serial/8250/8250_mtk.c
static int mtk8250_probe_of(struct platform_device *pdev, struct uart_port *p, struct mtk8250_data *data) { // ...省略 data->dma = NULL; #ifdef CONFIG_SERIAL_8250_DMA dmacnt = of_property_count_strings(pdev->dev.of_node, "dma-names"); if (dmacnt == 2) { data->dma = devm_kzalloc(&pdev->dev, sizeof(*(data->dma)), GFP_KERNEL); data->dma->fn = mtk8250_dma_filter; data->dma->rx_size = MTK_UART_RX_SIZE; data->dma->rxconf.src_maxburst = MTK_UART_RX_TRIGGER; data->dma->txconf.dst_maxburst = MTK_UART_TX_TRIGGER; } #endif return 0; } #endif static int mtk8250_probe(struct platform_device *pdev) { // ...省略 #ifndef CONFIG_FPGA_EARLY_PORTING if (pdev->dev.of_node) { err = mtk8250_probe_of(pdev, &uart.port, data); if (err) return err; } else return -ENODEV; #endif spin_lock_init(&uart.port.lock); uart.port.mapbase = regs->start; uart.port.irq = irq->start; uart.port.pm = mtk8250_do_pm; uart.port.type = PORT_16550; uart.port.flags = UPF_BOOT_AUTOCONF | UPF_FIXED_PORT; uart.port.dev = &pdev->dev; uart.port.iotype = UPIO_MEM32; uart.port.regshift = 2; uart.port.private_data = data; uart.port.shutdown = mtk8250_shutdown; uart.port.startup = mtk8250_startup; // DMA设备树配置的Channel获取 uart.port.set_termios = mtk8250_set_termios; // 串口开启的时候,会调用到这里面的DMA使能 uart.port.uartclk = clk_get_rate(data->uart_clk); #ifdef CONFIG_FPGA_EARLY_PORTING uart.port.uartclk = MTK_UART_FPGA_CLK; #endif #ifdef CONFIG_SERIAL_8250_DMA if (data->dma) uart.dma = data->dma; #endif // ...省略 }