从 linux 内核中的 irq 处理程序开始 DMA 事务

Starting DMA transaction from irq handler in linux kernel

我正在为 beaglebone black like 开发板开发驱动程序。驱动程序与 FPGA 通信。当 FPGA 有数据要读取时,它会触发 irq。所以我必须快速启动 DMA 事务来读取数据。是否可以从原子上下文开始事务?

现在我向高优先级工作队列发送一个任务,它开始 DMA 事务,但有时,当我以 30mb/s 的速度工作时,我在调用 queue_work 之间有很大的延迟(大约 200-500 微秒) irq 处理程序并从工作队列开始作业。

那么我可以直接从 irq 启动 DMA 还是有更快的方法从 irq 处理程序启动 DMA 事务?

我使用 linux 内核 4.9。

更新:

void init(){
    g_fpga_dma_queue = alloc_workqueue("fpga_dma_queue", WQ_UNBOUND |Q_HIGHPRI, 1);
}

static irq_handler_t irqReadyRead(unsigned int irq, void* dev_id, struct pt_regs* regs)
{
    if(g_fpga_dma_queue) {
        queue_work(g_fpga_dma_queue, &fpga_dma_work);
    }

    return (irq_handler_t)IRQ_HANDLED;
}

static void dma_callback_read(void *param)
{
    struct dma_chan *chan = g_dma_chan_read;

    switch (dma_async_is_tx_complete(chan, cookie_read, NULL, NULL)) {
        case DMA_COMPLETE:
            irqraised1_read = 1;
            g_good_dma++;
            break;

        case DMA_ERROR:
            irqraised1_read = -1;
            g_bad_dma++;
            break;

        default:
            irqraised1_read = -1;
            g_bad_dma++;
            break;
    }

    
    complete(&dma_comp_read);
}

static int read_dma(int count)
{
    struct dma_device *dev;
    struct dma_async_tx_descriptor *tx;
    unsigned long flags;
    int result = 0;

    dev = g_dma_chan_read->device;
    flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;

    tx = dev->device_prep_dma_memcpy(g_dma_chan_read, dmaphysbuf_read, (unsigned long) FPGA_READ_BUFFER_ADDR,
                                     (size_t) count, flags);

    if (!tx) {
        DBG_LOG("device_prep_dma_memcpy failed\n");

        return -ENODEV;
    }

    irqraised1_read = 0u;
    dma_comp_read.done = 0;

    /* set the callback and submit the transaction */
    tx->callback = dma_callback_read;
    tx->callback_param = NULL;
    cookie_read = dmaengine_submit(tx);
    dma_async_issue_pending(g_dma_chan_read);

    wait_for_completion(&dma_comp_read);

    /* Check the status of the completed transfer */

    if (irqraised1_read < 0) {
        DBG_LOG("edma copy: Event Miss Occured!!!\n");
        dmaengine_terminate_all(g_dma_chan_read);

        result = -EAGAIN;
    }
}
static void fpga_dma_work_handler(struct work_struct *w){
    size_t count = readSize();
    read_dma();
}

有时(当我们插入或移除 USB 闪存时,当任何驱动程序在终端中写入消息时)我在 irq 处理程序和 DMA 实际启动之间有很大的延迟(~1 毫秒)

我找到了延误的主要原因。当任何驱动程序在控制台中打印某些内容时(当我插入 usb、删除网络电缆、从驱动程序调用 printk 等)时,我的驱动程序出现了巨大的延迟,通常我在工作队列中推送任务和启动 dma 事务之间的最大延迟大约为 100 -200 微秒(准备 DMA 事务约 10-20 微秒),目前还可以。

而且我发现了当我直接从 irq 处理程序启动某些 dma 事务时以错误结束的原因,我每大约 500 微秒得到一次中断,如果在 irq 处理程序之后和 dma 事务实际开始之前发生延迟,我开始新的事务在前一个结束之前(dma 事务应该非常快地完成大约 10 微秒,我不检查前一个事务是否完成并且使用单线程工作队列不可能在前一个任务完成之前启动下一个任务所以我忘记检查事务是否完成时我更改我的代码以从 irq 处理程序开始事务)

现在我继续使用高优先级队列,禁用内核输出到控制台。如果我需要更小的延迟来启动 dma 事务,我可能会尝试使用 tasklet insted workqueue(我认为从 irq 启动 dma 不好,因为它在我的板上的 irq 处理程序中需要大约 10-20 微秒,但也许有时它没问题)