前言

CANN(Compute Architecture for Neural Networks)作为面向 AI 加速场景的异构计算架构,其性能与稳定性高度依赖于底层驱动对硬件资源的精细调度。在 CANN 开源生态中,driver 仓库https://atomgit.com/cann/driver)承担了从操作系统内核到专用计算硬件之间的桥梁角色,负责管理中断、DMA 传输、计算单元调度等核心功能。

1. 整体架构与控制流概览

driver 仓库采用分层设计,确保硬件无关性与可维护性:

HAL Layer

ioctl / mmap

用户态应用 / Runtime

SDK-driver Layer

HAL Layer

Kernel Driver Module

Hardware Registers

hdc: Host-Device Communication

trs: Task Resource Schedule

queue: Message Queue

platform: 中断/内存资源管理

uda: Unified Device Access

  • 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
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐