CANN driver 对硬件中断、DMA 与计算单元的底层控制逻辑
CANN driver 仓库通过精心设计的 HAL 层,实现了对硬件中断、DMA 引擎与计算单元的精细化控制。从中断注册与快速响应,到高效 DMA 描述符管理,再到基于 SQ/CQ 模型的任务调度与多租户隔离,每一层都体现了对高性能与高可靠性的追求。随着容器共享、版本兼容性等新特性的加入,driver 正逐步成为支撑大规模 AI 基础设施的坚实底座。🔗相关链接CANN 组织主页:https://
前言
CANN(Compute Architecture for Neural Networks)作为面向 AI 加速场景的异构计算架构,其性能与稳定性高度依赖于底层驱动对硬件资源的精细调度。在 CANN 开源生态中,driver 仓库(https://atomgit.com/cann/driver)承担了从操作系统内核到专用计算硬件之间的桥梁角色,负责管理中断、DMA 传输、计算单元调度等核心功能。
1. 整体架构与控制流概览
driver 仓库采用分层设计,确保硬件无关性与可维护性:
- UDA(Unified Device Access)提供统一设备访问接口;
- Platform 模块管理中断号、预留内存、PCIe BAR 映射;
- TRS 负责任务队列构建与提交;
- HDC 处理主机与设备间的数据通道;
- Queue 管理命令队列与事件通知。
整个控制流程遵循“注册 → 配置 → 提交 → 中断响应 → 完成回调”的范式。
2. 硬件中断管理:从注册到处理
中断是硬件通知 CPU 任务完成或异常发生的核心机制。driver 通过 platform 模块实现中断的全生命周期管理。
2.1 中断资源注册与映射
在设备初始化阶段,driver 解析 ACPI 或设备树,获取中断号并注册处理函数:
// src/ascend_hal/platform/interrupt.c
int ascend_register_interrupts(struct npu_device *dev) {
int i, ret;
for (i = 0; i < dev->irq_count; i++) {
// 注册共享中断处理函数
ret = request_irq(dev->irqs[i], ascend_irq_handler,
IRQF_SHARED, "npu_core", dev);
if (ret) {
pr_err("Failed to request IRQ %d\n", dev->irqs[i]);
goto err_free_irqs;
}
}
return 0;
err_free_irqs:
while (--i >= 0)
free_irq(dev->irqs[i], dev);
return ret;
}
每个 NPU 核心通常对应一个独立中断线,支持 MSI-X 以降低延迟。
2.2 中断处理函数逻辑
当中断触发时,ascend_irq_handler 被调用:
// ascend_irq_handler (简化版)
irqreturn_t ascend_irq_handler(int irq, void *data) {
struct npu_device *dev = data;
u32 status = readl(dev->reg_base + REG_INTERRUPT_STATUS);
if (status & BIT(INTERRUPT_TASK_DONE)) {
// 任务完成中断
task_completion_handler(dev);
writel(BIT(INTERRUPT_TASK_DONE), dev->reg_base + REG_INTERRUPT_CLEAR);
}
if (status & BIT(INTERRUPT_ERROR)) {
// 错误中断(如 ECC、非法指令)
error_handler(dev);
writel(BIT(INTERRUPT_ERROR), dev->reg_base + REG_INTERRUPT_CLEAR);
}
return IRQ_HANDLED;
}
✅ 关键点:中断处理函数必须快速执行,因此仅做状态记录与唤醒工作队列,详细分析由下半部(workqueue)完成。
2.3 用户态事件通知
为避免频繁陷入内核,driver 使用 eventfd 或 UIO 机制将中断事件传递至用户态:
// SDK-driver 层通过 ioctl 获取事件
int sdk_wait_for_completion(int fd, u32 task_id) {
struct cann_event evt;
// 阻塞等待内核写入事件
read(fd, &evt, sizeof(evt));
if (evt.task_id == task_id && evt.type == TASK_COMPLETE)
return 0;
return -EIO;
}
该机制被 runtime 仓库广泛使用,实现高效的异步任务调度。
3. DMA 控制:高效数据搬运的核心
AI 计算的瓶颈常在于数据搬运。driver 通过 SVM(Shared Virtual Memory)与 HDC(Host-Device Communication)模块实现零拷贝或高效 DMA 传输。
3.1 DMA 引擎初始化
每个 NPU 设备包含多个 DMA 引擎(如 H2D、D2H、Peer-to-Peer)。driver 在 hdc 模块中初始化通道:
// src/ascend_hal/hdc/hdc_init.c
int hdc_dma_engine_init(struct hdc_context *ctx) {
// 映射 DMA 控制寄存器
ctx->dma_reg = ioremap(ctx->dma_bar_addr, DMA_REG_SIZE);
// 配置 descriptor ring buffer
ctx->desc_ring = dma_alloc_coherent(ctx->dev, RING_SIZE,
&ctx->desc_dma_handle, GFP_KERNEL);
if (!ctx->desc_ring)
return -ENOMEM;
// 写入硬件寄存器
writel(lower_32_bits(ctx->desc_dma_handle), ctx->dma_reg + DMA_DESC_LO);
writel(upper_32_bits(ctx->desc_dma_handle), ctx->dma_reg + DMA_DESC_HI);
writel(RING_ENTRIES, ctx->dma_reg + DMA_RING_SIZE);
return 0;
}
3.2 提交 DMA 传输请求
当上层请求数据传输时,driver 构建描述符并触发引擎:
// hdc_submit_dma (关键逻辑)
int hdc_submit_dma(struct hdc_context *ctx, dma_addr_t src, dma_addr_t dst, size_t len) {
struct dma_descriptor *desc = &ctx->desc_ring[ctx->tail];
desc->src_addr = src;
desc->dst_addr = dst;
desc->length = len;
desc->flags = DMA_FLAG_H2D | DMA_FLAG_INTERRUPT_ON_COMPLETION;
// 内存屏障确保描述符写入完成
wmb();
// 更新尾指针,通知硬件取 descriptors
ctx->tail = (ctx->tail + 1) % RING_ENTRIES;
writel(ctx->tail, ctx->dma_reg + DMA_TAIL_PTR);
return 0;
}
💡 优化点:使用 ring buffer 避免每次传输都写寄存器,提升吞吐;支持 scatter-gather 以处理非连续内存。
3.3 与 SVM 的协同
对于支持 SVM 的平台,driver 可直接使用虚拟地址,无需 IOMMU 映射:
// 在 ascend_hal/svm/svm.c 中
int svm_map_user_buffer(struct vm_area_struct *vma, u64 user_vaddr, size_t size) {
// 标记页表项为 device-coherent
set_pages_uc(vma->pages, size >> PAGE_SHIFT); // 非缓存,确保一致性
return 0;
}
这使得 PyTorch/TensorFlow 的张量可直接被硬件访问,极大降低延迟。
4. 计算单元调度:任务提交与资源隔离
AI Core 是执行矩阵运算的核心单元。driver 通过 TRS(Task Resource Schedule)与 TS Agent 实现任务调度。
4.1 任务描述符构建
每个计算任务被封装为 task_desc,包含 kernel 参数、输入输出地址、依赖关系等:
// src/ascend_hal/trs/task_builder.c
struct task_desc *build_conv_task(struct conv_params *p) {
struct task_desc *td = kzalloc(sizeof(*td), GFP_KERNEL);
td->op_type = OP_CONV2D;
td->input_addr = p->input_phys;
td->output_addr = p->output_phys;
td->kernel_size = p->ksize;
td->stride = p->stride;
// ... 其他参数
return td;
}
4.2 任务提交至硬件队列
TRS 模块将任务描述符写入硬件命令队列(Command Queue):
// trs_submit_task
int trs_submit_task(struct trs_context *ctx, struct task_desc *td) {
u64 hw_desc = virt_to_phys(td);
// 写入 SQ(Submission Queue)
writeq(hw_desc, ctx->sq_base + ctx->sq_tail * 8);
ctx->sq_tail = (ctx->sq_tail + 1) % SQ_DEPTH;
// Doorbell 寄存器通知硬件取任务
writel(ctx->sq_tail, ctx->doorbell_reg);
return 0;
}
⚠️ 注意:SQ/CQ(Completion Queue)模型借鉴了 NVMe 协议思想,确保高并发与低延迟。
4.3 多租户与资源隔离
在容器或多用户场景下(见 PR !35 支持的 device-share 特性),driver 通过 VMNG(Virtual Machine Manager)模块实现资源隔离:
// src/sdk_driver/vmng/vmng.c
int vmng_create_partition(u32 device_id, u32 core_mask, u64 mem_quota) {
// 配置硬件 MMU,限制该 partition 只能访问指定 cores 和 memory range
write_partition_config(device_id, core_mask, mem_quota);
return 0;
}
每个容器获得独立的计算核心子集与内存配额,互不干扰。
5. 同步机制与错误恢复
为确保系统鲁棒性,driver 实现了完善的同步与恢复机制。
5.1 任务超时检测
若任务长时间未完成,watchdog 线程介入:
// trs_watchdog_thread
void trs_watchdog(struct work_struct *work) {
struct trs_context *ctx = container_of(work, ...);
if (ktime_ms_delta(ktime_get(), ctx->last_submit_time) > TASK_TIMEOUT_MS) {
pr_alert("Task timeout! Triggering reset.\n");
ascend_trigger_chip_reset(ctx->device);
}
}
5.2 芯片复位与恢复
复位流程由 examples/dcmi/dcmi/2_chip_reset/ 中的代码验证(修复于 Issue #24):
// main.c (外部复位示例)
int external_chip_reset(int chip_id) {
// 1. 禁用中断
disable_irq(chip_id);
// 2. 拉低 RESET GPIO
gpio_set_value(RESET_PIN, 0);
msleep(100);
gpio_set_value(RESET_PIN, 1);
// 3. 重新初始化 HAL 上下文
reinit_hal_context(chip_id);
// 4. 重新注册中断
ascend_register_interrupts(&devices[chip_id]);
return 0;
}
该流程确保在硬件死锁时可自动恢复,保障服务可用性。
结语
CANN driver 仓库通过精心设计的 HAL 层,实现了对硬件中断、DMA 引擎与计算单元的精细化控制。从中断注册与快速响应,到高效 DMA 描述符管理,再到基于 SQ/CQ 模型的任务调度与多租户隔离,每一层都体现了对高性能与高可靠性的追求。随着容器共享、版本兼容性等新特性的加入,driver 正逐步成为支撑大规模 AI 基础设施的坚实底座。
🔗 相关链接:
- CANN 组织主页:https://atomgit.com/cann
- driver 仓库地址:https://atomgit.com/cann/driver
更多推荐


所有评论(0)