This commit is contained in:
weidongshan
2022-01-14 17:04:09 +08:00
parent e180458683
commit 18d2b8648b
13 changed files with 421 additions and 0 deletions

View File

@@ -0,0 +1,413 @@
## INTx_MSI_MSIX三种中断机制分析
参考资料:
* 《PCI_SPEV_V3_0.pdf》6.8节
* [PCIe中MSI和MSI-X中断机制](https://blog.csdn.net/pieces_thinking/article/details/119431791)
* [PCIe学习笔记之MSI/MSI-x中断及代码分析](https://blog.csdn.net/yhb1047818384/article/details/106676560/)
* [msix中断分析](https://blog.csdn.net/weijitao/article/details/46566789)
* [MSI中断与Linux实现](https://www.cnblogs.com/gunl/archive/2011/06/09/2076892.html)
* [ARM GICv3中断控制器](https://blog.csdn.net/yhb1047818384/article/details/86708769)
开发板资料:
* https://wiki.t-firefly.com/zh_CN/ROC-RK3399-PC-PLUS/
本课程分析的文件:
* `linux-4.4_rk3399\drivers\pci\host\pcie-rockchip.c`
### 1. PCI设备的INTx中断机制
传统PCI设备的引脚中有4条线INTA#、INTB#、INTC#、INTD#"#"表示低电平有效,如下图所示:
![image-20220114144708980](pic/10_PCI_PCIe/96_pci_intx.png)
PCI设备就像普通的设备一样通过物理引脚发出中断信号
![image-20220114150723805](pic/10_PCI_PCIe/97_pci_intx_example.png)
在PCI设备的配置空间它声明通过INTA#、INTB#、INTC#还是INTD#发出中断
![image-20220114152410023](pic/10_PCI_PCIe/98_pci_config_reg_for_int.png)
配置空间有2个寄存器Interrupt Pin、Interrupt Line作用如下
* Interrupt Pin用来表示本设备通过哪条引脚发出中断信号取值如下
| Interrupt Pin取值 | 含义 |
| ----------------- | ----------------- |
| 0 | 不属于中断 |
| 1 | 通过INTA#发出中断 |
| 2 | 通过INTB#发出中断 |
| 3 | 通过INTC#发出中断 |
| 4 | 通过INTD#发出中断 |
| 5~0xff | 保留 |
* Interrupt Line给软件使用的PCI设备本身不使用该寄存器。软件可以写入中断相关的信息比如在Linux系统中可以把分配的virq(虚拟中断号)写入此寄存器。软件完全可以自己记录中断信息,没必要依赖这个寄存器。
INTx中断是电平触发处理过程如下
* PCI设备发出中断让INTx引脚变低
* 软件处理中断清除中断写PCI设备的某个寄存器导致PCI设备取消中断
* PCI设备取消中断让INTx引脚变高
### 2. PCIe设备的INTx中断机制
PCIe设备的配置空间也同样有这2个寄存器Interrupt Pin、Interrupt Line它们的作用跟PCI设备完全一样。
PCI总线上有INTA#~INTD#这些真实存在的引脚但是PCIE总线上并没有这些引脚PCIe设备怎么兼容INTx中断机制
PCIe设备通过"INTx模拟"(PCI Compatible INTx Emulation)来实现传统的INTx中断当设备需要发出中断时它会发出特殊的TLP包
![image-20220114155205690](pic/10_PCI_PCIe/99_tlp_for_int.png)
TLP头部中Message Code被用来区分发出的是哪类TLP包为例"INTx模拟"有两类TLP包
* Assert_INTx
![image-20220114155744259](pic/10_PCI_PCIe/100_Assert_INTx.png)
* Deassert_INTx
![image-20220114160030443](pic/10_PCI_PCIe/101_Deassert_INTx.png)
跟传统PCI设备类似这个"INTx模拟"的处理过程也是一样的:
* PCIe设备发出中断发出Assert_INTx的TLP包
* 软件处理中断清除中断写PCIe设备的某个寄存器导致PCIe设备取消中断
* PCIe设备取消中断发出Deassert_INTx的TLP包
硬件框图如下:
![image-20220114160521777](pic/10_PCI_PCIe/102_pcie_intx_example.png)
对于软件开发人员来说,他感觉不到变化:
* PCI设备通过真实的引脚传输中断
* PCIe设备通过TLP实现虚拟的引脚传输中断
PCIe控制器内部接收到INTx的TLP包后就会向GIC发出中断最终导致CPU接收到中断信号。
对应的中断程序执行时会读取PCIe控制器的寄存器分辨发生的是INTA#~INTD#这4个中断的哪一个
### 3. MSI中断机制
在PCI系统中使用真实的引脚发出中断已经带来了不方便
* 电路板上需要布线
* 只有4条引脚多个PCI设备共享这些引脚中断处理效率低。
在PCI系统中就已经引入了新的中断机制MSIMessage Signaled Interrupts。
在初始PCI设备时可以告诉它一个地址(主控芯片的地址)、一个数据:
* PCI设备想发出中断时把这个数据写入整个地址就可以触发中断
* 软件读到这个数据,就知道是哪个设备发出中断了
流程及硬件框图如下:
* 写哪个地址可以触发中断可能是PCI/PCIe控制器里面的某个地址也可能是GIC的某个地址
* 初始化PCI/PCIe设备时把该地址(cpu_addr)转换为pci_addr告知PCI/PCIe设备(写入它的配置空间)
* PCI/PCIe设备要发出中断时会发起一个"写内存传输"往pci_addr写入数据value
* 这导致cpu_addr被写入数据value触发中断
![image-20220114165335847](pic/10_PCI_PCIe/104_msi_msix.png)
上图中的"pci_addr/value"保存在哪里保存在设备的配置空间的capability里。
#### 3.1 capability
capability的意思是"能力"PCI/PCIe设备可以提供各种能力所以在配置空间里有寄存器来描述这些capability
* 一个capability配置空间里有对应的多个寄存器
* 可以有多个capability
![image-20220114163327615](pic/10_PCI_PCIe/103_capability.png)
### 5. 代码分析
#### 5.1 使用中断线
设备树:
```shell
pcie0: pcie@f8000000 {
interrupt-map-mask = <0 0 0 7>;
interrupt-map = <0 0 0 1 &pcie0_intc 0>,
<0 0 0 2 &pcie0_intc 1>,
<0 0 0 3 &pcie0_intc 2>,
<0 0 0 4 &pcie0_intc 3>;
pcie0_intc: interrupt-controller {
interrupt-controller;
#address-cells = <0>;
#interrupt-cells = <1>;
};
};
```
```c
pci_scan_single_device
pci_device_add
pcibios_add_device
dev->irq = of_irq_parse_and_map_pci(dev, 0, 0);
ret = of_irq_parse_pci(dev, &oirq);
return irq_create_of_mapping(&oirq);
of_irq_parse_pci
rc = pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &pin);
out_irq->np = ppnode;
out_irq->args_count = 1;
out_irq->args[0] = pin;
laddr[0] = cpu_to_be32((pdev->bus->number << 16) | (pdev->devfn << 8));
laddr[1] = laddr[2] = cpu_to_be32(0);
rc = of_irq_parse_raw(laddr, out_irq);
initial_match_array[0] = laddr[0] = b/d/f;
initial_match_array[1] = laddr[1] = 0;
initial_match_array[2] = laddr[2] = 0;
initial_match_array[3] = pin;
// map后
out_irq->args[0] = pin;
out_irq->args_count = 1;
out_irq->np = newpar; // 指向pcie0_intc
-------------------
irq = platform_get_irq_byname(pdev, "legacy");
if (irq < 0) {
dev_err(dev, "missing legacy IRQ resource\n");
return -EINVAL;
}
rockchip_pcie_legacy_int_handler
reg = rockchip_pcie_read(rockchip, PCIE_CLIENT_INT_STATUS);
reg = (reg & PCIE_CLIENT_INTR_MASK) >> PCIE_CLIENT_INTR_SHIFT;
hwirq = ffs(reg) - 1;
virq = irq_find_mapping(rockchip->irq_domain, hwirq);
generic_handle_irq(virq);
```
#### 5.2 MSI
在具体PCIe设备里设置中断比如
`nvme_setup_io_queues in pci.c (drivers\nvme\host) : vecs = pci_enable_msi_range(pdev, 1, min(nr_io_queues, 32));`
```c
pci_enable_msi_range
msi_capability_init
```
#### 5.3 MSI-X
```c
nvme_setup_io_queues in pci.c (drivers\nvme\host) : vecs = pci_enable_msix_range(pdev, dev->entry, 1, nr_io_queues);
nvme_setup_io_queues
pci_enable_msix_range
pci_enable_msix
msix_capability_init
msix_setup_entries
base = msix_map_region(dev, msix_table_size(control));
phys_addr = pci_resource_start(dev, bir) + table_offset;
entry->mask_base = base;
---------------------------
pci_device_add
pci_set_msi_domain
pci_dev_msi_domain
pci_msi_get_device_domain
of_msi_map_get_device_domain
__of_msi_map_rid
```
#### 5.4 ITS的初始化
文件:`drivers\irqchip\irq-gic-v3.c`
```c
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
gic_of_init
gic_init_bases
its_init(node, &gic_data.rdists, gic_data.domain);
its_probe(np, parent_domain);
err = of_address_to_resource(node, 0, &res);
its_base = ioremap(res.start, resource_size(&res));
inner_domain = irq_domain_add_tree(node, &its_domain_ops, its);
```
对于同一个设备树节点:
```shell
its: interrupt-controller@fee20000 {
compatible = "arm,gic-v3-its";
msi-controller;
reg = <0x0 0xfee20000 0x0 0x20000>;
};
```
它有3个IRQ Domain
* drivers\irqchip\irq-gic-v3-its.c
![image-20220112151329295](pic/10_PCI_PCIe/92_its_domain_bus_nexus.png)
* drivers\irqchip\irq-gic-v3-its-pci-msi.c
```c
its_pci_msi_init
pci_msi_create_irq_domain
info->flags |= MSI_FLAG_ACTIVATE_EARLY;
domain = msi_create_irq_domain(fwnode, info, parent);
```
![image-20220112151509232](pic/10_PCI_PCIe/93_its_domain_pci_msi.png)
* drivers\irqchip\irq-gic-v3-its-platform-msi.c
![image-20220112151613719](pic/10_PCI_PCIe/94_its_domain_platform_msi.png)
#### 5.5 PCIe设备发出中断时写哪个地址
![image-20220112160643994](pic/10_PCI_PCIe/95_its_top_domain.png)
```c
msi_domain_activate
irq_chip_write_msi_msg
its_irq_compose_msi_msg
msg->address_lo = addr & ((1UL << 32) - 1);
msg->address_hi = addr >> 32;
msg->data = its_get_event_id(d);
```
调用过程:`kernel/irq/msi.c`
```c
msi_domain_activate
irq_chip_write_msi_msg
```
##### 5.5.1 分配中断
```c
msix_capability_init/msi_capability_init
pci_msi_setup_msi_irqs
pci_msi_domain_alloc_irqs
msi_domain_alloc_irqs
__irq_domain_alloc_irqs
irq_domain_alloc_irqs_recursive
ret = domain->ops->alloc(domain, irq_base, nr_irqs, arg);
its_irq_domain_alloc
err = its_alloc_device_irq(its_dev, &hwirq);
*hwirq = dev->event_map.lpi_base + idx;
irq_domain_set_hwirq_and_chip
irq_data->hwirq = hwirq;
irq_data->chip = chip ? chip : &no_irq_chip;
irq_domain_activate_irq(irq_data);
domain->ops->activate(domain, irq_data);
msi_domain_activate
irq_chip_compose_msi_msg(irq_data, &msg)
// 构造msg里面含有MSI或msi-x的addr/val
its_irq_compose_msi_msg
msg->address_lo = addr & ((1UL << 32) - 1);
msg->address_hi = addr >> 32;
// its_get_event_id:
// d->hwirq - its_dev->event_map.lpi_base;
msg->data = its_get_event_id(d);
// 设置msi-x的entry地址
irq_chip_write_msi_msg(irq_data, &msg);
data->chip->irq_write_msi_msg(data, msg);
pci_msi_domain_write_msg
__pci_write_msi_msg(desc, msg);
__pci_write_msi_msg(desc, msg);
// 对于MSI-X
writel(msg->address_lo, base + PCI_MSIX_ENTRY_LOWER_ADDR);
writel(msg->address_hi, base + PCI_MSIX_ENTRY_UPPER_ADDR);
writel(msg->data, base + PCI_MSIX_ENTRY_DATA);
// 对于MSI
pci_write_config_word(dev, pos + PCI_MSI_FLAGS, msgctl);
pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_LO,
msg->address_lo);
// 为PCI设备确定hwirq
its_domain_ops.alloc
its_irq_domain_alloc
its_alloc_device_irq
*hwirq = dev->event_map.lpi_base + idx;
```
#### 5.6 IRQ Domain创建流程
* `drivers\irqchip\irq-gic-v3-its.c`
*

View File

@@ -241,3 +241,11 @@ its_irq_domain_alloc
```
#### 5.6 IRQ Domain创建流程
* `drivers\irqchip\irq-gic-v3-its.c`
*

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB