本页定位

本页介绍PCIe设备驱动开发——运行在RC侧系统中,驱动连接在PCIe总线上的EP设备(如NVMe SSD、网卡、GPU)。如需开发 RC控制器驱动EP控制器驱动,请访问对应页面。

用户应用程序 设备控制、数据处理
内核驱动 ← 你在这里 设备枚举、资源管理、中断处理
PCI子系统 总线枚举、资源分配、热插拔
固件 (BIOS/UEFI) 平台初始化、ACPI配置

Linux PCI子系统

Linux内核提供了完善的PCI/PCIe子系统框架,驱动开发者可以通过标准API访问设备资源。

PCI Core 总线核心、设备模型
PCI Host Bridge Driver RC驱动(详见
Device Driver ← 你在这里 终端设备驱动
Hardware Root Complex / Endpoint

关键数据结构

/*
 * pci_dev - 表示一个PCI设备
 * 包含设备的所有信息:配置空间、资源、驱动等
 */
struct pci_dev {
    struct list_head bus_list;      /* 总线链表 */
    struct pci_bus *bus;            /* 所属总线 */
    struct pci_bus *subordinate;    /* 桥接设备的下游总线 */
    
    u16 vendor;                     /* 厂商ID */
    u16 device;                     /* 设备ID */
    u16 subsystem_vendor;           /* 子系统厂商ID */
    u16 subsystem_device;           /* 子系统设备ID */
    u8 revision;                    /* 修订版本 */
    u8 hdr_type;                    /* 头部类型 */
    
    struct resource resource[DEVICE_COUNT_RESOURCE]; /* BAR资源 */
    
    u8 irq;                         /* 中断号 */
    struct msix_entry *msix_entries; /* MSI-X条目 */
    
    struct pci_driver *driver;      /* 绑定的驱动 */
    void *driver_data;              /* 驱动私有数据 */
};

/*
 * pci_driver - PCI驱动结构
 * 定义驱动标识、探测/移除回调等
 */
struct pci_driver {
    struct list_head node;
    const char *name;
    const struct pci_device_id *id_table;
    
    int (*probe)(struct pci_dev *dev, const struct pci_device_id *id);
    void (*remove)(struct pci_dev *dev);
    int (*suspend)(struct pci_dev *dev, pm_message_t state);
    int (*resume)(struct pci_dev *dev);
    void (*shutdown)(struct pci_dev *dev);
    
    struct device_driver driver;
    const struct pci_error_handlers *err_handler;
};

驱动开发基础

设备ID表与模块注册

/*
 * 定义设备ID表 - 驱动匹配规则
 */
static const struct pci_device_id my_pci_ids[] = {
    /* vendor, device, subvendor, subdevice, class, class_mask, driver_data */
    { PCI_DEVICE(0x1234, 0x5678) },  /* 匹配厂商1234, 设备5678 */
    { PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_NVME, 0xffffff) }, /* 匹配NVMe类 */
    { 0, }
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);

/*
 * 驱动结构定义
 */
static struct pci_driver my_pci_driver = {
    .name = "my_pci_driver",
    .id_table = my_pci_ids,
    .probe = my_probe,
    .remove = my_remove,
};

/*
 * 模块注册
 */
module_pci_driver(my_pci_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("PCIe Device Driver Example");

Probe函数实现

static int my_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
    int ret;
    void __iomem *mmio_base;
    struct my_device *dev;
    
    /* 1. 启用PCI设备 */
    ret = pci_enable_device(pdev);
    if (ret) {
        dev_err(&pdev->dev, "无法启用设备\n");
        return ret;
    }
    
    /* 2. 请求MMIO区域 (BAR0) */
    ret = pci_request_region(pdev, 0, "my_driver");
    if (ret) {
        dev_err(&pdev->dev, "无法请求BAR0区域\n");
        goto err_disable;
    }
    
    /* 3. 映射MMIO到内核虚拟地址空间 */
    mmio_base = pci_iomap(pdev, 0, 0);
    if (!mmio_base) {
        dev_err(&pdev->dev, "无法映射MMIO\n");
        ret = -ENOMEM;
        goto err_region;
    }
    
    /* 4. 设置DMA掩码 */
    ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
    if (ret) {
        ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
        if (ret) {
            dev_err(&pdev->dev, "不支持DMA\n");
            goto err_iomap;
        }
    }
    
    /* 5. 启用总线主控 (DMA) */
    pci_set_master(pdev);
    
    /* 6. 分配设备私有结构 */
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev) {
        ret = -ENOMEM;
        goto err_iomap;
    }
    
    dev->pdev = pdev;
    dev->mmio_base = mmio_base;
    pci_set_drvdata(pdev, dev);
    
    /* 7. 初始化设备硬件 */
    my_hw_init(dev);
    
    /* 8. 分配中断向量 (优先使用MSI/MSI-X) */
    ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
    if (ret < 0) {
        dev_err(&pdev->dev, "无法分配中断向量: %d\n", ret);
        goto err_free;
    }
    
    /* 9. 注册中断 */
    ret = request_irq(pci_irq_vector(pdev, 0), my_interrupt, IRQF_SHARED,
                      "my_device", dev);
    if (ret) {
        dev_err(&pdev->dev, "无法注册中断\n");
        goto err_irq_vectors;
    }
    
    dev_info(&pdev->dev, "设备初始化成功\n");
    return 0;

err_irq_vectors:
    pci_free_irq_vectors(pdev);
err_free:
    kfree(dev);
err_iomap:
    pci_iounmap(pdev, mmio_base);
err_region:
    pci_release_region(pdev, 0);
err_disable:
    pci_disable_device(pdev);
    return ret;
}

Remove函数实现

static void my_remove(struct pci_dev *pdev)
{
    struct my_device *dev = pci_get_drvdata(pdev);
    
    if (!dev)
        return;
    
    /* 禁用中断 */
    free_irq(pci_irq_vector(pdev, 0), dev);
    
    /* 释放中断向量 */
    pci_free_irq_vectors(pdev);
    
    /* 停止设备 */
    my_hw_stop(dev);
    
    /* 清理资源 */
    pci_iounmap(pdev, dev->mmio_base);
    pci_release_region(pdev, 0);
    pci_disable_device(pdev);
    
    kfree(dev);
    pci_set_drvdata(pdev, NULL);
}

配置空间访问

Linux内核提供了多种访问配置空间的方法。

标准配置空间访问 (0x00-0xFF)

/* 读取配置空间 */
u8 val8;
u16 val16;
u32 val32;

pci_read_config_word(pdev, PCI_VENDOR_ID, &val16);
pci_read_config_word(pdev, PCI_DEVICE_ID, &val16);
pci_read_config_dword(pdev, PCI_CLASS_REVISION, &val32);

/* 写入配置空间 */
pci_write_config_byte(pdev, PCI_INTERRUPT_LINE, 10);
pci_write_config_word(pdev, PCI_COMMAND, PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
/* 注意:BAR 地址由 PCI core 在枚举阶段统一分配(write-all-1s 测大小 + 资源分配),
 * 设备驱动不应自行写 PCI_BASE_ADDRESS_*,这里仅演示写接口本身 */

扩展配置空间访问 (0x100-0xFFF)

/*
 * PCIe扩展配置空间 (4KB)
 * 需要使用ECAM或PCIe增强访问机制
 */

/* 读取扩展配置寄存器 */
int pcie_capability_read_word(struct pci_dev *dev, int pos, u16 *val);
int pcie_capability_read_dword(struct pci_dev *dev, int pos, u32 *val);

/* 示例:读取链路状态 */
u16 link_status;
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &link_status);
dev_info(&pdev->dev, "链路速度: %d, 宽度: x%d\n",
         link_status & PCI_EXP_LNKSTA_CLS,
         (link_status & PCI_EXP_LNKSTA_NLW) >> 4);

DMA 编程

PCIe设备通常使用DMA(直接内存访问)进行高速数据传输,避免CPU参与数据搬运。

流式DMA (Streaming DMA)

用于一次性数据传输,需要显式同步。

/*
 * 流式DMA映射 - 用于单向传输
 */

/* 映射单个缓冲区 */
dma_addr_t dma_handle;
void *virt_addr = kmalloc(size, GFP_KERNEL);
dma_handle = dma_map_single(&pdev->dev, virt_addr, size, DMA_TO_DEVICE);

/* 检查映射结果 */
if (dma_mapping_error(&pdev->dev, dma_handle)) {
    dev_err(&pdev->dev, "DMA映射失败\n");
    kfree(virt_addr);
    return -ENOMEM;
}

/* 告知设备DMA地址和大小 */
writel(dma_handle, dev->mmio_base + DMA_ADDR_REG);
writel(size, dev->mmio_base + DMA_SIZE_REG);
writel(START_TRANSFER, dev->mmio_base + DMA_CTRL_REG);

/* 等待传输完成... */

/* 传输完成后取消映射 */
dma_unmap_single(&pdev->dev, dma_handle, size, DMA_TO_DEVICE);
kfree(virt_addr);

一致性DMA (Coherent DMA)

用于设备频繁访问的共享内存,自动维护缓存一致性。

/*
 * 一致性DMA映射 - 用于设备持续访问
 */

void *virt_addr;
dma_addr_t dma_handle;

/* 分配一致性DMA缓冲区 */
virt_addr = dma_alloc_coherent(&pdev->dev, size, &dma_handle, GFP_KERNEL);
if (!virt_addr) {
    dev_err(&pdev->dev, "DMA分配失败\n");
    return -ENOMEM;
}

/* CPU写入数据 */
memset(virt_addr, 0xaa, size);

/* 确保数据对设备可见 */
wmb();

/* 告知设备DMA地址 */
writel(dma_handle, dev->mmio_base + DMA_ADDR_REG);
writel(size, dev->mmio_base + DMA_SIZE_REG);

/* 设备可以直接访问此缓冲区 */
/* 不需要手动同步 */

/* 清理时释放 */
dma_free_coherent(&pdev->dev, size, virt_addr, dma_handle);

分散/聚集DMA (Scatter/Gather)

用于处理不连续内存区域的传输。

/*
 * Scatter/Gather DMA - 处理不连续内存
 */

struct scatterlist sg[16];
int nents;

/* 初始化scatterlist */
sg_init_table(sg, 16);

/* 添加页面到scatterlist */
nents = 0;
for (i = 0; i < nr_pages; i++) {
    struct page *page = alloc_page(GFP_KERNEL);
    sg_set_page(&sg[nents], page, PAGE_SIZE, 0);
    nents++;
}

/* 映射scatterlist */
nents = dma_map_sg(&pdev->dev, sg, nents, DMA_TO_DEVICE);
if (!nents) {
    dev_err(&pdev->dev, "scatterlist映射失败\n");
    return -ENOMEM;
}

/* 将sg列表地址告知设备 */
/* 设备驱动通常会将sg列表转换为设备特定的描述符链表 */

/* 传输完成后取消映射 */
dma_unmap_sg(&pdev->dev, sg, nents, DMA_TO_DEVICE);

中断处理

PCIe支持多种中断机制:传统INTx、MSI和MSI-X。

类型 向量数 特点 适用场景
INTx 1 共享中断,性能较低 传统设备
MSI 1-32 向量独立,中等性能 一般设备
MSI-X 可达2048 向量独立,高性能 高速设备(NVMe, NIC)

MSI-X 实现

/*
 * MSI-X中断实现
 */

#define NUM_MSIX_VECTORS 16

struct msix_entry msix_entries[NUM_MSIX_VECTORS];

static irqreturn_t my_msix_handler(int irq, void *data)
{
    struct my_queue *q = data;
    u32 status;
    
    /* 读取中断状态 */
    status = readl(q->mmio + QUEUE_INT_STATUS);
    writel(status, q->mmio + QUEUE_INT_STATUS); /* 清除中断 */
    
    /* 处理完成队列 */
    my_process_completions(q);
    
    return IRQ_HANDLED;
}

static int setup_msix(struct pci_dev *pdev, struct my_device *dev)
{
    int ret, i;
    
    /* 启用MSI-X */
    ret = pci_alloc_irq_vectors(pdev, NUM_MSIX_VECTORS, NUM_MSIX_VECTORS,
                                PCI_IRQ_MSIX);
    if (ret < 0) {
        dev_err(&pdev->dev, "无法分配MSI-X向量: %d\n", ret);
        return ret;
    }
    
    /* 为每个向量注册中断处理函数 */
    for (i = 0; i < NUM_MSIX_VECTORS; i++) {
        int vector = pci_irq_vector(pdev, i);
        
        ret = request_irq(vector, my_msix_handler, 0,
                          "my_device", &dev->queues[i]);
        if (ret) {
            dev_err(&pdev->dev, "无法请求IRQ %d: %d\n", vector, ret);
            goto err_free_irqs;
        }
        
        dev->queues[i].vector = vector;
    }
    
    return 0;

err_free_irqs:
    while (--i >= 0) {
        free_irq(dev->queues[i].vector, &dev->queues[i]);
    }
    pci_free_irq_vectors(pdev);
    return ret;
}

MSI/MSI-X 硬件层:中断到底是怎么"发"出来的

上面的 API 只是冰山一角。理解 MSI 的本质对调试中断丢失/亲和性问题至关重要:MSI 中断就是一条普通的 Memory Write TLP——设备向 Message Address 写 Message Data,Root Complex 识别该地址区间后把它转成中断而非内存写入。

MSI Capability 结构(Cap ID 0x05)

偏移寄存器说明
+0x02Message Controlbit0 = MSI Enable;bits[3:1] Multiple Message Capable(设备请求的向量数,2^n);bits[6:4] Multiple Message Enable(内核实际使能数);bit7 = 64-bit Address Capable;bit8 = Per-Vector Masking Capable
+0x04Message Address(低 32 位)中断写 TLP 的目标地址
+0x08Message Upper Address仅 64-bit 能力置位时存在
+0x08 / +0x0CMessage Data写 TLP 的数据(16 位),区别向量;32 位设备在 +0x08,64 位在 +0x0C
+0x0C / +0x10Mask Bits / Pending Bits仅 Per-Vector Masking 能力时存在(MSI 的按向量屏蔽能力很少用)

MSI 的所有向量共享同一对 Address/Data,只能靠 Data 值区分——这就是它最多 32 向量、且各向量中断内容同源的原因。

MSI-X Capability 结构(Cap ID 0x11)与向量表

MSI-X 能力结构本身只有三个寄存器,真正的信息在位于 BAR 内的 MSI-X Table

偏移寄存器说明
+0x02Message Controlbits[10:0] Table Size(N−1,实际向量数 = 值 + 1,上限 2048);bit14 = MASKALL;bit15 = MSI-X Enable
+0x04Table Offset / BIRbits[2:0] = BIR(表所在的 BAR 编号 0–5),bits[31:3] = 表在该 BAR 内的字节偏移
+0x08PBA Offset / BIR同上格式,指向 Pending Bit Array(每向量 1 位"已挂起")

MSI-X Table 每个表项 16 字节(PCI_MSIX_ENTRY_SIZE):

表项内偏移字段说明
0x0 / 0x4Message Address(64 位)每向量独立的目标地址
0x8Message Data每向量独立的写数据
0xCVector Controlbit0 = Mask Bit(屏蔽该向量);bits[31:16] = Steering Tag(Gen6 新增)

每向量独立的 Address/Data 意味着不同的向量可以指向不同的 CPU(在 x86 上指向不同 Local APIC 的中断重定向区,如 0xFEExxxxx)——这是 MSI-X 能做中断亲和性的硬件基础,而 MSI 做不到。

内核侧流水线:irq_domain / msi_domain

pci_alloc_irq_vectors() 执行时,内核沿一条清晰的域链路把"Linux IRQ 号"翻译成"设备里的 MSI 表项":

/* MSI 分配路径(简化) */
pci_alloc_irq_vectors(dev, min, max, PCI_IRQ_MSIX)
  └─ pci_setup_msi_context() → msi_domain_alloc_irqs()
       └─ msi_domain_alloc_irqs_all()           /* irq_domain 分层分配 Linux IRQ */
            └─ __msi_domain_alloc_irqs()
                 ├─ irq_domain_alloc_irqs_parent()  /* 向上递归到中断控制器域 */
                 └─ .compose_msi_msg()             /* 由中断控制器填 Address/Data:
                                                      x86 = Local APIC 重定向区 + vector,
                                                      GICv3 = ITS 队列地址 + DeviceID/EventID */
  └─ msi_domain_activate()
       └─ .msi_write_msg() → pci_write_msi_msg()
            └─ __pci_write_msi_msg()           /* 把 Address/Data 写进设备的
                                                  MSI-X Table 表项(或 MSI Cap 寄存器) */

理解这条链路后,很多"玄学"就有了解释:request_irq() 注册的 handler 挂在 Linux IRQ 号上,与硬件无关;真正决定中断落在哪个 CPU 的,是 compose_msi_msg() 写进 MSI-X Table 的 Addressirq_chip 的 ack/eoi 对 MSI 通常近乎空操作(边沿语义、无中断控制器 pending 位);改亲和性时内核只是重写对应表项的 Address/Data。

进阶 API 与常见坑

  • pci_alloc_irq_vectors() 的 flags 可以组合(PCI_IRQ_MSIX | PCI_IRQ_MSI | PCI_IRQ_LEGACY),返回实际分配数——失败时按最小向量数重试是标准写法;
  • pci_request_irq() / pcim_request_irq()(devres 版)把 pci_irq_vector() + request_irq() 合成一步;
  • irq_set_affinity_hint(irq, mask) 提示用户态将线程绑定到对应核(NVMe/NIC 的多队列模型标配);
  • 使能 MSI-X 后,Command 寄存器的 Interrupt Disable 位(bit 10)会置位,INTx 从此不可用——确认设备无 INTx 遗留路径;
  • MSI-X Table 所在的 BAR 区间不能映射为可缓存,否则处理器投机合并写会破坏表项语义;
  • 调试中断丢失:先确认 /proc/interrupts 有无该向量、再查 MSI-X Enable 与 MASKALL 位(错误码/寄存器速查可直接解码 Message Control)。

用户空间交互

字符设备接口

/*
 * 字符设备 - 提供标准文件操作
 */

static int my_open(struct inode *inode, struct file *filp)
{
    struct my_device *dev = container_of(inode->i_cdev, 
                                         struct my_device, cdev);
    filp->private_data = dev;
    return 0;
}

static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct my_device *dev = filp->private_data;
    
    switch (cmd) {
    case MY_IOCTL_GET_INFO:
        /* 返回设备信息 */
        return copy_to_user((void __user *)arg, &dev->info,
                           sizeof(dev->info));
    
    case MY_IOCTL_SUBMIT_CMD:
        /* 提交命令到设备 */
        return my_submit_command(dev, arg);
    
    default:
        return -ENOTTY;
    }
}

static const struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .release = my_release,
    .read = my_read,
    .write = my_write,
    .mmap = my_mmap,
    .unlocked_ioctl = my_ioctl,
};

mmap实现

/*
 * mmap - 将设备内存映射到用户空间
 */
static int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
    struct my_device *dev = filp->private_data;
    unsigned long vsize = vma->vm_end - vma->vm_start;
    phys_addr_t paddr = dev->mmio_phys;
    unsigned long psize;
    
    psize = pci_resource_len(dev->pdev, 0);
    
    if (vsize > psize)
        return -EINVAL;
    
    /* 禁用缓存(MMIO区域) */
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
    
    /* 映射物理地址到用户虚拟地址 */
    if (io_remap_pfn_range(vma, vma->vm_start, 
                           paddr >> PAGE_SHIFT, vsize,
                           vma->vm_page_prot))
        return -EAGAIN;
    
    return 0;
}

/*
 * 映射DMA缓冲区到用户空间
 */
static int my_dma_mmap(struct file *filp, struct vm_area_struct *vma)
{
    struct my_device *dev = filp->private_data;
    
    return dma_mmap_coherent(&dev->pdev->dev, vma,
                             dev->dma_virt, dev->dma_handle,
                             vma->vm_end - vma->vm_start);
}

Sysfs接口

/*
 * Sysfs属性 - 提供设备参数访问
 */

static ssize_t speed_show(struct device *dev,
                          struct device_attribute *attr, char *buf)
{
    struct pci_dev *pdev = to_pci_dev(dev);
    u16 link_status;
    
    pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &link_status);
    
    return sprintf(buf, "Gen%d x%d\n",
                   link_status & PCI_EXP_LNKSTA_CLS,
                   (link_status & PCI_EXP_LNKSTA_NLW) >> 4);
}

static ssize_t speed_store(struct device *dev,
                           struct device_attribute *attr,
                           const char *buf, size_t count)
{
    /* 可以实现配置修改 */
    return count;
}

static DEVICE_ATTR_RW(speed);

static struct attribute *my_attrs[] = {
    &dev_attr_speed.attr,
    NULL,
};

static const struct attribute_group my_attr_group = {
    .attrs = my_attrs,
};

/* 在probe中创建 */
sysfs_create_group(&pdev->dev.kobj, &my_attr_group);

调试方法

内核命令

# 列出所有PCI设备
lspci -vvv

# 查看特定设备
lspci -s 01:00.0 -vvv

# 查看设备资源
cat /sys/bus/pci/devices/0000:01:00.0/resource

# 查看配置空间
hexdump -C /sys/bus/pci/devices/0000:01:00.0/config

# 启用设备
echo 1 > /sys/bus/pci/devices/0000:01:00.0/enable

# 绑定/解绑驱动
echo '0000:01:00.0' > /sys/bus/pci/drivers/my_driver/bind
echo '0000:01:00.0' > /sys/bus/pci/drivers/my_driver/unbind

# 查看内核日志
dmesg | tail -50

# 动态调试
echo 'module my_driver +p' > /sys/kernel/debug/dynamic_debug/control

内核调试选项

# 内核配置选项
CONFIG_PCI_DEBUG=y        # PCI调试信息
CONFIG_DYNAMIC_DEBUG=y    # 动态调试

# 启用所有PCI调试
echo 1 > /proc/sys/kernel/printk

# 使用ftrace跟踪
echo function > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
echo pci_* > /sys/kernel/debug/tracing/set_ftrace_filter
cat /sys/kernel/debug/tracing/trace

最佳实践

资源管理

  • 使用devm_*函数族进行资源管理,简化清理代码
  • 在probe失败时正确清理已分配的资源
  • 使用引用计数管理设备生命周期

错误处理

  • 检查所有可能失败的函数返回值
  • 使用goto进行错误路径的集中处理
  • 实现AER错误恢复回调

性能优化

  • 合理使用一致性DMA和流式DMA
  • 利用MSI-X多向量实现中断亲和性
  • 考虑使用DPDK或RDMA等高性能框架

电源管理

  • 实现runtime PM回调
  • 正确处理suspend/resume
  • 使用ASPM减少功耗

GPU/AI加速卡驱动示例

除了常见的NVMe SSD和网卡,GPU和AI加速卡是PCIe生态中的重要设备类型。它们的驱动开发有其特殊性。

典型GPU/AI加速卡PCIe配置

设备类型 厂商 典型型号 PCIe配置 驱动特点
数据中心GPU NVIDIA A100/H100/H200 Gen4/Gen5 x16 nvidia驱动,CUDA支持,MIG
消费级GPU NVIDIA RTX 4090/4080 Gen4 x16 nvidia驱动,图形和计算
数据中心GPU AMD MI300X/MI250X Gen4 x16 amdgpu驱动,ROCm支持
AI训练卡 Google TPU v4/v5 Gen4 x16 专用驱动,TPU软件栈
AI推理卡 Intel Gaudi2/Gaudi3 Gen4 x16 habanalabs驱动
AI加速器 寒武纪 MLU290/MLU370 Gen4 x16 cambricon驱动
AI加速器 华为 Ascend 910B Gen4 x16 hinic驱动,CANN软件栈

GPU驱动开发特点

大容量BAR空间

GPU通常映射大量显存到BAR空间

  • A100: 64GB HBM2e
  • H100: 80GB HBM3
  • 需要64-bit BAR支持

高带宽DMA

GPU需要持续高速数据传输

  • GPUDirect RDMA
  • NVLink互连
  • P2P DMA传输

多功能设备

GPU可能包含多个PCI功能

  • 3D控制器 + 音频
  • USB控制器
  • Serial BUS

电源管理

GPU功耗巨大,需要精细管理

  • 运行时PM
  • 动态调频调压
  • 温度监控与限速

GPUDirect技术

NVIDIA GPUDirect是一组技术,允许GPU与网络设备、存储设备直接通信,绕过CPU:

技术 功能 应用场景
GPUDirect RDMA 网卡直接访问GPU显存 分布式训练、RDMA网络
GPUDirect P2P GPU之间直接传输数据 多GPU训练、集群计算
GPUDirect NVMe NVMe SSD直接访问GPU显存 大规模数据加载
GPUDirect SCI 跨节点GPU内存访问 大规模分布式系统

GPU驱动核心功能

/*
 * GPU驱动典型功能模块
 */

/* 1. 设备初始化 */
static int gpu_init(struct pci_dev *pdev, struct gpu_device *gpu)
{
    /* 启用设备 */
    pci_set_master(pdev);
    
    /* 请求MMIO区域 */
    gpu->mmio = pci_iomap(pdev, 0, 0);  /* BAR0: 寄存器 */
    gpu->fb = pci_iomap(pdev, 2, 0);    /* BAR2: 显存映射 */
    
    /* 初始化命令队列/工作队列 */
    gpu_init_cmd_queue(gpu);
    
    /* 初始化内存管理 */
    gpu_init_memory_manager(gpu);
    
    return 0;
}

/* 2. 中断处理 - 上下文切换、命令完成 */
static irqreturn_t gpu_irq_handler(int irq, void *data)
{
    struct gpu_device *gpu = data;
    u32 intr_status;
    
    intr_status = readl(gpu->mmio + GPU_INTR_STATUS);
    
    if (intr_status & INTR_CMD_COMPLETE)
        handle_cmd_completion(gpu);
    
    if (intr_status & INTR_CTX_SWITCH)
        handle_context_switch(gpu);
    
    if (intr_status & INTR_ERROR)
        handle_gpu_error(gpu);
    
    writel(intr_status, gpu->mmio + GPU_INTR_STATUS);  /* 清除中断 */
    return IRQ_HANDLED;
}

/* 3. 内存管理 - 显存分配 */
struct gpu_buffer *gpu_alloc_buffer(struct gpu_device *gpu, size_t size)
{
    struct gpu_buffer *buf;
    
    /* 分配GPU内存 */
    buf->gpu_addr = gpu_mm_alloc(gpu->mm, size);
    
    /* 可选:映射到用户空间 */
    buf->cpu_addr = dma_alloc_coherent(&gpu->pdev->dev, size,
                                       &buf->dma_addr, GFP_KERNEL);
    
    return buf;
}

/* 4. 命令提交 */
static int gpu_submit_command(struct gpu_device *gpu, 
                               struct gpu_command *cmd)
{
    /* 将命令写入命令缓冲区 */
    gpu_write_cmd_buffer(gpu, cmd);
    
    /* 通知GPU处理命令 */
    writel(CMD_SUBMIT, gpu->mmio + GPU_CMD_DOORBELL);
    
    return 0;
}

AI加速卡的特殊考虑

  • 多芯片互连:如AMD MI300使用Infinity Fabric,NVLink连接GPU
  • HBM内存:高带宽内存(High Bandwidth Memory)与传统GDDR不同
  • 定制计算单元:TPU的矩阵乘法单元、NPU的推理加速器
  • 软件栈:CUDA、ROCm、CANN等完整软件生态
  • 集群互连:多卡、多节点的高效通信

查看GPU PCIe配置

# 查看GPU设备
lspci -nn | grep -i nvidia

# 查看GPU详细信息
lspci -vvv -s 01:00.0

# 查看GPU资源
cat /sys/bus/pci/devices/0000:01:00.0/resource

# 查看GPU内存映射区域
cat /proc/iomem | grep -i nvidia

# 查看GPU中断
cat /proc/interrupts | grep -i nvidia

# 使用nvidia-smi查看GPU状态
nvidia-smi -q

# 查看GPUDirect支持
nvidia-smi -q | grep -i peer

推荐学习资源

规范文档

内核文档

  • Linux PCI子系统文档
  • Documentation/PCI/pci.rst(原 pci.txt,已转 rst)
  • Documentation/PCI/endpoint/(EP 子系统)

推荐书籍

  • PCI Express System Architecture (MindShare)
  • PCI Express Technology (Mike Jackson)
  • Linux Device Drivers (LDD3,基于 2.6 内核,API 已有变化,仅作概念参考)

调试工具

  • lspci - PCI设备查看
  • setpci - 配置空间访问
  • /sys/bus/pci/ - sysfs接口