Skip to content

中断与 DMA:原理与架构

中断子系统架构

硬件设备
    │  电信号

中断控制器(GIC / APIC / RISC-V PLIC)
    │  IRQ 线

CPU 异常向量表

    ▼ 汇编入口(irq_entry)
通用中断处理框架(generic IRQ)

    ├── 硬中断处理(hardirq)
    │       └── 驱动的 irq_handler_t

    └── 软中断(softirq)/ tasklet / workqueue
            └── 延迟处理

注册中断

c
#include <linux/interrupt.h>

/* 中断处理函数 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_priv *priv = dev_id;
    u32 status;

    /* 读取并清除中断状态 */
    status = readl(priv->base + IRQ_STATUS);
    writel(status, priv->base + IRQ_STATUS);

    if (!(status & MY_IRQ_MASK))
        return IRQ_NONE;   /* 不是我的中断(共享中断时) */

    /* 快速处理:只做最必要的操作 */
    priv->irq_data = readl(priv->base + DATA_REG);

    /* 触发下半部处理 */
    tasklet_schedule(&priv->tasklet);

    return IRQ_HANDLED;
}

/* 注册中断 */
ret = request_irq(irq,
                  my_irq_handler,
                  IRQF_SHARED,    /* 共享中断线 */
                  "my-device",
                  priv);          /* dev_id,共享中断时用于区分 */

/* 注销中断 */
free_irq(irq, priv);

中断标志

标志说明
IRQF_SHARED共享中断线(多设备共用)
IRQF_TRIGGER_RISING上升沿触发
IRQF_TRIGGER_FALLING下降沿触发
IRQF_TRIGGER_HIGH高电平触发
IRQF_TRIGGER_LOW低电平触发
IRQF_ONESHOT线程化中断:处理完成前保持屏蔽

线程化中断(Threaded IRQ)

将中断处理移到内核线程,允许睡眠:

c
ret = request_threaded_irq(
    irq,
    my_hardirq_handler,    /* 上半部:快速,不可睡眠 */
    my_thread_handler,     /* 下半部:在内核线程中运行,可睡眠 */
    IRQF_ONESHOT,
    "my-device",
    priv
);

/* 上半部:仅判断是否是本设备中断 */
static irqreturn_t my_hardirq_handler(int irq, void *dev_id)
{
    if (!is_my_interrupt())
        return IRQ_NONE;
    return IRQ_WAKE_THREAD;   /* 唤醒线程处理 */
}

/* 下半部:可以睡眠,可以调用 i2c_transfer 等 */
static irqreturn_t my_thread_handler(int irq, void *dev_id)
{
    struct my_priv *priv = dev_id;
    /* 可以睡眠的操作 */
    i2c_smbus_read_byte_data(priv->client, REG_DATA);
    return IRQ_HANDLED;
}

下半部机制对比

机制上下文可睡眠适用场景
硬中断中断上下文最紧急操作(清中断、读状态)
softirq软中断上下文网络收发、块设备
tasklet软中断上下文简单延迟处理
workqueue进程上下文需要睡眠的延迟处理
threaded IRQ进程上下文现代驱动推荐方式

tasklet

c
static void my_tasklet_fn(struct tasklet_struct *t)
{
    struct my_priv *priv = from_tasklet(priv, t, tasklet);
    /* 处理数据,不可睡眠 */
}

/* 初始化 */
tasklet_setup(&priv->tasklet, my_tasklet_fn);

/* 在中断中调度 */
tasklet_schedule(&priv->tasklet);

/* 销毁 */
tasklet_kill(&priv->tasklet);

workqueue

c
/* 使用系统 workqueue */
INIT_WORK(&priv->work, my_work_fn);

/* 在中断中调度 */
schedule_work(&priv->work);

/* 延迟调度 */
schedule_delayed_work(&priv->dwork, msecs_to_jiffies(100));

/* 工作函数(进程上下文,可睡眠) */
static void my_work_fn(struct work_struct *work)
{
    struct my_priv *priv = container_of(work, struct my_priv, work);
    /* 可以睡眠 */
    msleep(10);
    mutex_lock(&priv->lock);
    /* ... */
    mutex_unlock(&priv->lock);
}

DMA 子系统

DMA Engine API

c
#include <linux/dmaengine.h>

/* 申请 DMA 通道 */
struct dma_chan *chan = dma_request_chan(dev, "tx");
if (IS_ERR(chan))
    return PTR_ERR(chan);

/* 准备 DMA 描述符(内存到内存) */
struct dma_async_tx_descriptor *desc;
desc = dmaengine_prep_dma_memcpy(chan,
    dst_dma_addr,   /* 目标物理地址 */
    src_dma_addr,   /* 源物理地址 */
    len,
    DMA_PREP_INTERRUPT | DMA_CTRL_ACK
);

/* 设置完成回调 */
desc->callback = my_dma_callback;
desc->callback_param = priv;

/* 提交并启动 */
dma_cookie_t cookie = dmaengine_submit(desc);
dma_async_issue_pending(chan);

/* 等待完成(同步方式) */
dma_sync_wait(chan, cookie);

/* 释放通道 */
dma_release_channel(chan);

Scatter-Gather DMA

c
/* 准备 sg 列表 */
struct scatterlist sg[4];
sg_init_table(sg, 4);
sg_set_buf(&sg[0], buf0, len0);
sg_set_buf(&sg[1], buf1, len1);

/* 映射 sg 列表 */
int nents = dma_map_sg(dev, sg, 4, DMA_TO_DEVICE);

/* 准备 sg 描述符 */
desc = dmaengine_prep_slave_sg(chan, sg, nents,
                                DMA_MEM_TO_DEV,
                                DMA_PREP_INTERRUPT);

/* 提交 */
dmaengine_submit(desc);
dma_async_issue_pending(chan);

/* 完成后解映射 */
dma_unmap_sg(dev, sg, 4, DMA_TO_DEVICE);

中断亲和性与 CPU 隔离

bash
# 查看中断分配
cat /proc/interrupts

# 设置中断亲和性(绑定到 CPU 2)
echo 4 > /proc/irq/42/smp_affinity   # 位掩码,4 = CPU 2

# 查看中断触发类型
cat /sys/kernel/irq/42/trigger

褚成志的笔记