中断与 DMA 最佳实践
1. 中断处理函数要尽量短
硬中断处理期间,同优先级中断被屏蔽,延迟过长影响系统实时性:
c
/* ✅ 正确:硬中断只做最少的事 */
static irqreturn_t my_isr(int irq, void *dev_id)
{
struct my_priv *priv = dev_id;
/* 1. 读取并清除中断状态(必须在硬中断中做,否则中断会再次触发) */
u32 status = readl(priv->base + IRQ_STATUS_REG);
writel(status, priv->base + IRQ_STATUS_REG);
/* 2. 保存必要数据 */
priv->pending_status = status;
/* 3. 调度下半部 */
tasklet_schedule(&priv->tasklet);
return IRQ_HANDLED;
}
/* ❌ 错误:在硬中断中做耗时操作 */
static irqreturn_t bad_isr(int irq, void *dev_id)
{
msleep(10); /* 绝对不行! */
mutex_lock(&lock); /* 可能睡眠,绝对不行! */
i2c_transfer(...); /* 可能睡眠,绝对不行! */
return IRQ_HANDLED;
}2. 共享中断必须检查来源
c
static irqreturn_t my_shared_isr(int irq, void *dev_id)
{
struct my_priv *priv = dev_id;
u32 status = readl(priv->base + IRQ_STATUS);
/* 必须检查是否是本设备触发的中断 */
if (!(status & MY_IRQ_BIT))
return IRQ_NONE; /* 不是我的,让其他处理函数处理 */
/* 处理中断 */
return IRQ_HANDLED;
}3. 使用 devm_request_irq 自动释放
c
/* ✅ devm 版本,设备解绑时自动 free_irq */
ret = devm_request_irq(&pdev->dev, irq, my_isr,
IRQF_SHARED, "my-device", priv);4. DMA 缓冲区对齐
c
/* DMA 缓冲区需要缓存行对齐,避免 false sharing */
#define DMA_BUF_SIZE PAGE_SIZE
/* 使用 dma_alloc_coherent 保证对齐 */
priv->dma_buf = dma_alloc_coherent(dev, DMA_BUF_SIZE,
&priv->dma_addr, GFP_KERNEL);
/* 或使用 ARCH_DMA_MINALIGN 对齐的 kmalloc */
priv->buf = kmalloc(size, GFP_KERNEL | GFP_DMA);5. DMA 方向与缓存一致性
c
/* 写入设备前:CPU 写 → 刷新缓存 → DMA 读 */
dma_sync_single_for_device(dev, dma_addr, size, DMA_TO_DEVICE);
/* 设备写完后:DMA 写 → 无效化缓存 → CPU 读 */
dma_sync_single_for_cpu(dev, dma_addr, size, DMA_FROM_DEVICE);6. 避免在中断上下文分配内存
c
/* ❌ 中断上下文不能用 GFP_KERNEL */
static irqreturn_t bad_isr(int irq, void *dev_id)
{
void *buf = kmalloc(size, GFP_KERNEL); /* 可能睡眠! */
}
/* ✅ 预先分配,或使用 GFP_ATOMIC */
static irqreturn_t good_isr(int irq, void *dev_id)
{
void *buf = kmalloc(size, GFP_ATOMIC); /* 不睡眠,可能失败 */
if (!buf)
return IRQ_HANDLED; /* 内存不足时跳过 */
}7. 中断风暴防护
c
/* 检测并处理中断风暴 */
static irqreturn_t my_isr(int irq, void *dev_id)
{
struct my_priv *priv = dev_id;
if (++priv->irq_count > MAX_IRQ_PER_SEC) {
dev_err(priv->dev, "IRQ storm detected, disabling IRQ\n");
disable_irq_nosync(irq); /* 不可在中断中用 disable_irq(会死锁) */
schedule_work(&priv->irq_storm_work);
return IRQ_HANDLED;
}
return IRQ_HANDLED;
}8. workqueue 选择
c
/* 系统 workqueue(适合大多数场景) */
schedule_work(&priv->work);
/* 高优先级 workqueue(对延迟敏感) */
queue_work(system_highpri_wq, &priv->work);
/* 专用 workqueue(避免被其他驱动阻塞) */
priv->wq = alloc_ordered_workqueue("my-driver", WQ_MEM_RECLAIM);
queue_work(priv->wq, &priv->work);
destroy_workqueue(priv->wq); /* 卸载时销毁 */