PCIe MSI 中断相关的启动流程

文章详细阐述了Linux系统下PCIe控制器和设备的初始化过程,包括内存和中断设置。在控制器侧,主要涉及配置空间设置、设备枚举和pci_dev结构体的构建。设备侧则涵盖内存映射和MSI/MSI-X中断的申请与激活。文章还深入解析了MSI中断的分配和硬件中断号的获取,特别是涉及到GIC和ITS的中断处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux PCIe 中断相关启动流程

1. PCIe控制器初始化过程

0. 准备配置空间,做outbound配置
1. 枚举 pcie 设备
    2.1. 为设备准备物理内存
        读写BAR0,获取需求内存大小.
        写 BAR0 为 PCIe地址,做outbound设置(映射物理地址到pcie地址)
    2.2. 为设备准备legacy硬件中断号,但是不会准备MSI/MSI-X硬件中断号
        
2. 构造 pci_dev 结构体
    3.1 用 legacy irq 对应的 软件中断号 填充 pci_dev 结构体变量的 irq 成员
    3.2 填充 pci_dev 结构体变量的 mem 相关成员
    3.3 调用 pci_device_register  注册 pci_dev 结构体变量 到 pci总线 设备链表

2. PCIe设备初始化过程

1. 内存设置
    获取到设备内存并映射物理内存到虚拟地址

2. 中断设置,这些都是在 msi_domain_alloc_irqs 中完成的 , 该函数的消费者有pci_msi_setup_msi_irqs
    2.1. 申请软件中断号
        从bitmap 中获取 软件中断号,然后alloc irq_desc , 并填充 handler 进 irq_action 链表 
    
    2.2. 根据rid((bus << 8) | (dev << 4) | function) 获取硬件中断号
        // 01:00.0应该是 8192+256 开始的
        // 关注 : its_create_device its_lpi_alloc_chunks dev->event_map.lpi_base = lpi_base;
        从 设备配置空间 cap 0x11 中读取到他需要M(Table Size+1)个 msi-x 中断
        解析 pcie 节点的 msi-map,做mask和offset,用rid作为输入,申请硬件中断号.
        在这里会返回硬件中断号的起始number(x)和数量(n)
        假设 x = 8192 , n = 3
        那么会有是三个中断号 , 8192 , 8193 , 8194
        对于ITS来讲,对应的 event id 为 0  1  2 // 这里与ITS设置的表强相关
    2.3. 地址值对设置 // 激活中断1
        做硬件配置,对于ITS,设置它的几个表.
        构造M个 <addr,val> 对
        将 M个 <addr,val> 对 写入 内存.内存基址由 cap msi-x 的 Table offset/Table BIR 指定
        将<addr,val>中的地址做inbound设置,做 Bus Master Enable// 这样 PCIe设备才可能发出中断
        // 对应关系 : 地址对中的 val  等于 hwirq - dev->event_map.lpi_base
        // 在没有pre_ITS补丁的话 , 地址对中的 addr 总是 GITS_TRANSLATER 的地址
        // 每个设备的 地址对中的 addr 都是一样的,但是每个设备有一个deviceid,是唯一的
        // its 根据 deviceid 和 eventid(地址对中的val) 来 转化为 intid 和 distributor
3.MSI-X Enable 及 开启层层开关,都是在request_irq中 完成的 //激活中断2
    索引 irq_domain_ops 中的 activate

驱动详细解读

控制器侧驱动

在配置空间侧只做了一个事情:配置了 BAR0 为 0x68100004

设备侧驱动

设备驱动调用如下函数获取硬件和软件中断号.因为是pci设备,所以不能从设备树中获取硬件中断号.
pci_enable_msix_range// range 指的是 其中的参数 minvec 到 maxvec
	申请软件中断号,申请好了,会填充entry 参数的 vector 成员 为软件中断号

驱动可能会调用如下函数来获取MSI/MSI-X软件中断号,最终都会调用到 __pci_enable_msix_range
	pci_alloc_irq_vectors
	pci_alloc_irq_vectors_affinity
	pci_enable_msix_range
	pci_enable_msi // 已经遗弃了
__pci_enable_msix_range->__pci_enable_msix->msix_capability_init->
pci_msi_setup_msi_irqs
	msi_domain_alloc_irqs
		ops->domain_alloc_irqs/__msi_domain_alloc_irqs 
		// 初始化pci-msi msi domain的时候,
		// 用 msi_domain_ops 变量 来作为 "pci-msi msi domain" 的 结构体(struct msi_domain_ops) 
			
			//1. 计算 gicv3-its irq-domain 中的 硬件中断号的参考值 B , 将B 放入 (struct its_device *)(((msi_alloc_info_t *)arg)->scratchpad[0].ptr)->event_map.lpi_base 变量,备用 // 被 irq_domain_alloc_irqs_hierarchy 用
			msi_domain_prepare_irqs
			
				ops->msi_prepare/its_pci_msi_prepare // irq-gic-v3-its-pci-msi.c
					// 以rid 作为 device id 传递 给 its msi-domain , rid 为 F
					info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain, pdev); // get rid // compare with msi-map
					
					msi_info->ops->msi_prepare/its_msi_prepare // irq-gic-v3-its.c
						dev_id = info->scratchpad[0].ul // 消费 F
						its_dev = its_create_device(its, dev_id, nvec, true); // prepare for hwirq
							its_lpi_alloc(nvecs, &lpi_base, &nr_lpis);
					 		dev->event_map.lpi_base = lpi_base; // prepare for hwirq core
				 			dev->event_map.nr_lpis = nr_lpis;
				 			its_send_mapd(dev, 1); // create table ITT
		
/*
irq_domain_alloc_irqs_hierarchy 的 步骤
	1.调用 ops->alloc,参数中有virq
		1.1 获取该域的中断号 hwirq
		1.2 为father 准备中断号,并调用 irq_domain_alloc_irqs_hierarchy call father
		1.3 将 hwirq 和 virq 设置入 irq_data	
*/
			//2. 填充硬件中断号, 申请软件中断号,desc
			for_each_msi_entry(desc, dev)
				
				// 2.1 计算 gicv3-its-pci irq-domain 中的 硬件中断号A,将A放入 arg->hwirq 备用
				ops->set_desc(&arg, desc);/pci_msi_domain_set_desc
					//将 gicv3-its-pci irq-domain 中的 硬件中断号 放入 arg 中的 hwirq ,备用
					// 至此, gicv3-its irq-domain 中的 硬件中断号的参考值 和 gicv3-its-pci irq-domain 的硬件中断号都放入了arg变量
				
				__irq_domain_alloc_irqs
					virq = irq_domain_alloc_descs // 申请软件中断号
					// 注意 : 虽然过程中有多个domain的硬件中断号,但是软件中断号只有一个(一处)
					// 是因为 只需要最底层的domain 来申请软件中断号即可
					// 高层的 硬件中断号 只负责使用
					// 以下的 virq 的值 都是同一个值
					// 三个层次中,共有多个少 irq_descs 结构体
					
					// irq_domain_alloc_irqs_hierarchy 1
					irq_domain_alloc_irqs_hierarchy
IRQ_domain:GICV3-ITS-PCI:domain->ops->alloc/msi_domain_alloc 			 // irq-domain : gicv3-its-pci
							irq_hw_number_t hwirq = ops->get_hwirq(info, arg); // 获取域中的硬件中断号 : arg->hwirq // 消费了A
							
							// irq_domain_alloc_irqs_hierarchy 2
							irq_domain_alloc_irqs_parent/irq_domain_alloc_irqs_hierarchy // call father
IRQ_domain:GICV3-ITS 			domain->ops->alloc/its_irq_domain_alloc  // irq-domain : gicv3-its 
									its_alloc_device_irq(its_dev, nr_irqs, &hwirq)	// 获取域中的硬件中断号 // 消费了B
										idx = bitmap_find_free_region(...);
										*hwirq = dev->event_map.lpi_base + idx;
									its_irq_gic_domain_alloc(domain, virq + i, hwirq + i); // call father
										fwspec.param[1] = hwirq; // parepare hwirq for father // 准备 IRQ_domain:GICV3 的 硬件中号 C
										
										// irq_domain_alloc_irqs_hierarchy 3
										irq_domain_alloc_irqs_parent/irq_domain_alloc_irqs_hierarchy
IRQ_domain:GICV3 					 			domain->ops->alloc/gic_irq_domain_alloc  // irq-domain : gicv3
													gic_irq_domain_translate	// 获取域中的硬件中断号 // 消费了 C
													// 没有 call father . 这里没有这一步,是因为GICV3 是 顶层 irq domain
													gic_irq_domain_map/irq_domain_set_info(d, irq, hw, chip, d->host_data, handle_fasteoi_irq
														irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);  // set irq_data
														__irq_set_handler(virq, handler, 0, handler_name);
														irq_set_handler_data(virq, handler_data);


IRQ_domain:GICV3-ITS				irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, &its_irq_chip, its_dev); // // set irq_data
										irq_data->hwirq = hwirq; // 填充硬件中断号
										
IRQ_domain:GICV3-ITS-PCI:ops->msi_init(domain, info, virq + i, hwirq + i, arg);/msi_domain_ops_init // set irq_data
							irq_domain_set_hwirq_and_chip(domain, virq, hwirq, info->chip, info->chip_data);
							__irq_set_handler(virq, info->handler, 0, info->handler_name);
							irq_set_handler_data(virq, info->handler_data);
							

			//3. 将 一个个中断 对应的  <addr,value> 填充并 写入 内存
			for_each_msi_vector(desc, i, dev) 
				irq_domain_activate_irq
					__irq_domain_activate_irq
						domain->ops->activate/msi_domain_activate
							irq_chip_compose_msi_msg
								pos->chip->irq_compose_msi_msg /its_irq_compose_msi_msg // irq-gic-v3-its.c
							irq_chip_write_msi_msg(irq_data, msg)
								data->chip->irq_write_msi_msg /pci_msi_domain_write_msg // irq-gic-v3-its-pci-msi.c

<think>好的,用户现在想了解pci_alloc_irq_vector函数的用法以及它和MSI-X中断的关系。用户之前已经提问过关于PCIe使用MSIX中断的问题,并且我在之前的回答中详细介绍了配置流程、关键步骤和常见问题。现在需要结合用户提供的引用和新问题来组织答案。 首先,我需要回忆pci_alloc_irq_vector函数的作用。这个函数在Linux内核中用于分配中断向量,支持MSIMSI-X模式。用户提到MSI-X,所以重点要放在这个函数如何用于MSI-X的配置。 接下来,用户提供的引用中,引用[1]提到MSI-X通过写入特定内存地址来触发中断,而引用[2]是关于pci_request_regions的代码,这个函数在之前的配置流程中出现过,用于请求PCI资源。需要确认这些引用如何关联到当前的函数用法。 用户的问题集中在pci_alloc_irq_vector的用法,以及与MSI-X的关系。我需要详细解释这个函数的参数、返回值、使用场景,并给出代码示例。同时,要说明在MSI-X配置中这个函数的作用,比如如何替代旧的pci_enable_msix_range等函数。 可能需要注意的点包括:函数的参数min_vecs和max_vecs的意义,flags中PCI_IRQ_MSIX标志的使用,错误处理,以及如何与中断处理函数结合。此外,用户可能想知道如何动态调整请求的中断数量,这在之前的回答中也有涉及,需要联系起来。 还要考虑用户可能的实际需求,比如在多队列设备中的应用,如何设置中断亲和性,以及调试方法。需要确保回答结构清晰,分点说明,并给出实际的代码示例,如初始化流程中断处理等。 最后,相关问题部分需要扩展用户可能关心的问题,比如错误处理、与旧接口的区别、多设备情况下的使用等。确保覆盖用户可能的后续疑问,帮助他们深入理解。</think>#### 一、`pci_alloc_irq_vectors` 核心作用 该函数是Linux内核中**统一的中断向量分配接口**,支持MSI/MSI-X/传统中断模式,与MSI-X的关系体现在: 1. 直接替代旧接口`pci_enable_msix_range()`,提供更简洁的API[^1] 2. 自动处理中断模式选择(优先尝试MSI-X > MSI > Legacy) 3. 支持动态调整中断向量数量 #### 二、函数原型与参数解析 ```c int pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs, unsigned int max_vecs, unsigned int flags); ``` - `min_vecs`:**最小必须分配的中断数量** - `max_vecs`:**最大可接受的中断数量** - `flags`:中断模式标志 - `PCI_IRQ_MSIX`:强制使用MSI-X模式 - `PCI_IRQ_MSI`:强制使用MSI模式 - `PCI_IRQ_LEGACY`:允许传统中断回退 #### 三、MSI-X场景典型使用模式 ##### 1. 基本分配流程 ```c struct pci_dev *pdev; int nvec = 8; // 请求8个MSI-X向量 int ret; // 优先尝试MSI-X模式 ret = pci_alloc_irq_vectors(pdev, 1, nvec, PCI_IRQ_MSIX); if (ret < 0) { // 回退到MSI模式 ret = pci_alloc_irq_vectors(pdev, 1, nvec, PCI_IRQ_MSI); } if (ret < 0) { // 最终回退到传统IRQ ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_LEGACY); } ``` - **返回值**表示实际分配的中断数量[^1] ##### 2. 多队列设备配置 ```c // 请求与CPU核心数相同的中断向量 int nvec = num_online_cpus(); ret = pci_alloc_irq_vectors(pdev, nvec, nvec, PCI_IRQ_MSIX); if (ret == nvec) { for (int i = 0; i < nvec; i++) { irq_set_affinity_hint(pci_irq_vector(pdev, i), cpumask_of(i)); } } ``` #### 四、中断向量获取与使用 ##### 1. 获取向量号 ```c // 获取第i个中断向量号 int irq = pci_irq_vector(pdev, i); ``` - **i的范围**:0 ≤ i < `pci_alloc_irq_vectors`返回值 ##### 2. 中断处理注册 ```c for (int i = 0; i < ret; i++) { int irq = pci_irq_vector(pdev, i); if (request_irq(irq, handler, 0, dev_name(dev), dev) < 0) { // 错误处理 } } ``` #### 五、关键实现机制 1. **MSI-X表配置**: - 自动设置`msg_address`和`msg_data`字段 - 通过`PCI_IRQ_MSIX`标志强制使用独立地址配置[^1] 2. **中断路由**: ```text +----------------+ +---------------+ +-----------+ | PCIe设备 | --> | MSI-X表项 | --> | CPU APIC | | (生成MSI-X消息)| | (msg_address) | | (中断投递)| +----------------+ +---------------+ +-----------+ ``` #### 六、调试与验证方法 1. **查看分配结果**: ```c printk(KERN_INFO "分配了%d个MSI-X中断\n", pdev->irq_vectors_allocated); ``` 2. **性能监控**: ```bash # 查看中断分布 $ watch -n 1 "cat /proc/interrupts | grep 'MSI-X'" ``` 3. **动态调试**: ```bash # 启用PCI子系统调试 $ echo 'file drivers/pci/*.c +p' > /sys/kernel/debug/dynamic_debug/control ``` #### 七、资源释放 ```c void pci_free_irq_vectors(struct pci_dev *dev); ``` - **必须对称调用**:与`pci_alloc_irq_vectors`成对出现 - 自动处理MSI-X表项掩码位的清除 #### 八、典型错误码处理 | 错误码 | 原因 | 解决方案 | |----------------|-------------------------|--------------------------| | `-ENOSPC` | 系统资源不足 | 减少请求的向量数量 | | `-EINVAL` | 参数非法 | 检查flags和向量范围 | | `-ENOTSUPP` | 设备不支持MSI-X | 回退到MSI或Legacy模式 | --- ### 相关问题 1. **如何实现MSI-X中断的动态重映射?** - 需要调用`pci_free_irq_vectors`后重新分配 - 更新MSI-X表项地址时需确保原子性操作[^1] 2. **`pci_alloc_irq_vectors`与旧接口的主要区别?** - 统一了MSI/MSI-X的分配流程 - 提供更精确的向量数量控制 - 自动处理中断模式回退逻辑[^1] 3. **多设备场景下如何避免中断冲突?** - 使用`irqbalance`服务动态调整 - 手动设置CPU亲和性 - 通过`PCI_IRQ_AFFINITY`标志优化分配 4. **如何验证MSI-X中断的实际触发频率?** - 通过`/proc/interrupts`统计 - 使用`perf record -e irq:irq_handler_entry`跟踪 - 设备特定的性能计数器 --- [^1]: MSI-X机制通过设备内存中的配置表实现中断向量与物理地址的映射,`pci_alloc_irq_vectors`封装了底层硬件配置细节,使驱动开发者能更专注于业务逻辑实现。 [^2]: PCI资源配置函数`pci_request_regions`需在中断分配前调用,确保设备内存空间已正确映射。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值