增加: 11_INTx_MSI_MSIX三种中断机制分析

This commit is contained in:
weidongshan
2022-01-17 17:55:47 +08:00
parent 2fb53a8d37
commit 3adb509b6d
30 changed files with 845 additions and 186 deletions

View File

@@ -48,7 +48,7 @@ PCI设备就像普通的设备一样通过物理引脚发出中断信号
| Interrupt Pin取值 | 含义 |
| ----------------- | ----------------- |
| 0 | 不属于中断 |
| 0 | 不需要中断引脚 |
| 1 | 通过INTA#发出中断 |
| 2 | 通过INTB#发出中断 |
| 3 | 通过INTC#发出中断 |
@@ -124,7 +124,7 @@ PCIe控制器内部接收到INTx的TLP包后就会向GIC发出中断最终
在初始PCI设备时可以告诉它一个地址(主控芯片的地址)、一个数据:
* PCI设备想发出中断时这个数据写入整个地址就可以触发中断
* PCI设备想发出中断时这个地址写入这个数据就可以触发中断
* 软件读到这个数据,就知道是哪个设备发出中断了
@@ -140,274 +140,269 @@ PCIe控制器内部接收到INTx的TLP包后就会向GIC发出中断最终
上图中的"pci_addr/value"保存在哪里保存在设备的配置空间的capability里。
#### 3.1 capability
capability的意思是"能力"PCI/PCIe设备可以提供各种能力所以在配置空间里有寄存器来描述这些capability
* 个capability,配置空间里有对应的多个寄存器
* 可以有多个capability
* 配置空间里有第1个capability的位置Capabilities Pointer
* 它指向第1个capability的多个寄存器,这些寄存器也是在配置空间里
* 第1个capability的寄存器里也会指示第2个capability在哪里
![image-20220114163327615](pic/10_PCI_PCIe/103_capability.png)
Capability示例图如下
* 配置空间0x34位置存放的是第1个capability的位置假设是 A4H
* 在配置空间0xA4位置找到第1个capabilitycapability的寄存器有如下约定
* 第1个字节表示ID每类capability都有自己的ID
* 第2个字节表示下一个capability的位置如果等于0表示这是最后一个capability
* 其他寄存器由capability决定所占据的寄存器数量由capability决定
* 第1个capability里面它表示下一个capability在5CH
* 在配置空间0x5C位置找到第2个capability
* 第1个字节表示ID第2个字节表示下一个capability的位置(图里是E0H)
* 其他字节由capability本身决定
* 在配置空间0xE0位置找到第3个capability
* 第1个字节表示ID
* 第2个字节表示下一个capability的位置这里是00H表示没有更多的capability了
* 其他字节由capability本身决定
![image-20220117103544744](pic/10_PCI_PCIe/105_capabilities.png)
#### 3.2 MSI capability
### 5. 代码分析
一个PCI设备是否支持MSI需要读取配置空间的capability来判断: 有MSI capability的话就支持MSI机制。
#### 5.1 使用中断线
在配置空间中MSI capability用来保存pci_addr、data。表示PCI设备往这个pci_addr写入data就可以触发中断
设备树
有如下问题要确认
```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>;
* pci_addr是32位、还是64位
* 能触发几个中断?通过地址来分辨,还是通过数据来分辨?
* 这些中断能否屏蔽?
* 能否读出中断状态?
* 这个些问题都由capability里面的"Message Control"决定。
pcie0_intc: interrupt-controller {
interrupt-controller;
#address-cells = <0>;
#interrupt-cells = <1>;
};
};
```
MSI capability格式如下
![image-20220117115002363](pic/10_PCI_PCIe/106_msi_capability.png)
#### 3.3 格式解析
MSI Capability格式的含义如下
* Capability ID对于MSI capability它的ID为05H
```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);
* Next Pointer下一个capability的位置00H表示这是最后一个capability
of_irq_parse_pci
rc = pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &pin);
* Message Control
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;
}
| 位 | 域 | 描述 |
| ---- | -------------------------- | ------------------------------------------------------------ |
| 8 | Per-vector masking capable | 是否支持屏蔽单个中断(vector)<br />1: 支持<br />0: 不支持<br />这是只读位。 |
| 7 | 64 bit address capable | 是否支持64位地址<br />1: 支持<br />0: 不支持<br />这是只读位。 |
| 6:4 | Multiple Message Enable | 系统软件可以支持多少个MSI中断<br />PCI设备可以表明自己想发出多少个中断<br />但是到底能发出几个中断?<br />由系统软件决定,它会写这些位,表示能支持多少个中断:<br />000: 系统分配了1个中断<br />001: 系统分配了2个中断<br />010: 系统分配了4个中断<br />011: 系统分配了8个中断<br />100: 系统分配了16个中断<br />101: 系统分配了32个中断<br />110: 保留值<br />111: 保留值<br />这些位是可读可写的。 |
| 3:1 | Multiple Message Capable | PCI设备可以表明自己想发出多少个中断<br />000: 设备申请1个中断<br />001: 设备申请2个中断<br />010: 设备申请4个中断<br />011: 设备申请8个中断<br />100: 设备申请16个中断<br />101: 设备申请32个中断<br />110: 保留值<br />111: 保留值<br />这些位是只读的。 |
| 0 | MSI Enable | 使能MSI<br />1: 使能<br />0: 禁止 |
rockchip_pcie_legacy_int_handler
reg = rockchip_pcie_read(rockchip, PCIE_CLIENT_INT_STATUS);
reg = (reg & PCIE_CLIENT_INTR_MASK) >> PCIE_CLIENT_INTR_SHIFT;
* Message Address/Message Uper Address地址
hwirq = ffs(reg) - 1;
virq = irq_find_mapping(rockchip->irq_domain, hwirq);
generic_handle_irq(virq);
```
* 32位地址保存在Message Address中
* 64位地址低32位地址保存在Message Address中高32位地址保存在Message Uper Address中
* 这些地址是系统软件初始化PCI设备时分配的系统软件把分配的地址写入这些寄存器
* 这些地址属于PCI地址空间
* Message Data数据
* 这个寄存器只有15位PCI设备发出中断时数据是32位的其中高16位数据为0
* 这个寄存器的数值是系统软件初始化设备时写入的
* 当PCI设备想发出中断是会发起一次写传输
* 往Message Address寄存器表示的地址写入Message Data寄存器的数据
* 如果可以发出多个中断的话,发出的数据中低位可以改变
* 比如"Multiple Message Enable"被设置为"010"表示可以发出4个中断
* 那么PCI设备发出的数据中bit1,0可以修改
* Mask Bits/Pending Bits: 屏蔽位/挂起位这是可选的功能PCI设备不一定实现它
* Mask Bits每一位用来屏蔽一个中断被系统软件设置为1表示禁止对应的中断
* Pending Bits每一位用来表示一个中断的状态这是软件只读位它的值为1表示对应中断发生了待处理
#### 5.2 MSI
在具体PCIe设备里设置中断比如
### 4. MSI-X中断机制
`nvme_setup_io_queues in pci.c (drivers\nvme\host) : vecs = pci_enable_msi_range(pdev, 1, min(nr_io_queues, 32));`
MSI机制有几个缺点
```c
pci_enable_msi_range
msi_capability_init
```
* 每个设备的中断数最大是32太少了
* 需要系统软件分配连续的中断号很可能失败也就是说设备想发出N个中断但是系统软件分配给它的中断少于N个
* 通过MSI发出中断时地址是固定的
于是引入了MSI-X机制Enhanced MSI interrupt support它解决了MSI的缺点
* 可以支持多大2048个中断
* 系统软件可以单独设置每个中断,不需要分配连续的中断号
* 每个中断可以单独设置PCI设备使用的"地址/数据"可以单独设置
#### 5.3 MSI-X
假设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);
* "地址/数据"
* 不放在配置空间,空间不够
* 放在PCI设备的内存空间哪个内存空间哪个BAR内存空间哪个位置(偏移地址)
* 系统软件可以读写这些内存空间
* 中断的控制信息
* 使能/禁止?
* 地址是32位还是64位
* 这些控制信息也是保存在PCI设备的内存空间
* 中断的状态信息(挂起?)
* 这些信息也是保存在PCI设备的内存空间
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
```
#### 4.1 MSI-X capability
一个PCI设备是否支持MSI-X需要读取配置空间的capability来判断: 有MSI-X capability的话就支持MSI-X机制。
MSI-X capability格式如下
#### 5.4 ITS的初始化
![image-20220117142556453](pic/10_PCI_PCIe/107_msi-x_capability.png)
文件:`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);
#### 4.2 MSI-X capability格式解析
```
格式解析如下:
对于同一个设备树节点:
* Capability ID对于MSI-X capability它的ID为11H
```shell
its: interrupt-controller@fee20000 {
compatible = "arm,gic-v3-its";
msi-controller;
reg = <0x0 0xfee20000 0x0 0x20000>;
};
```
* Next Pointer下一个capability的位置00H表示这是最后一个capability
它有3个IRQ Domain
* Message Control
* drivers\irqchip\irq-gic-v3-its.c
![image-20220112151329295](pic/10_PCI_PCIe/92_its_domain_bus_nexus.png)
| 位 | 名 | 描述 |
| ---- | ------------- | ------------------------------------------------------------ |
| 15 | MSI-X Enable | 是否使能MSI-X<br />1: 使能<br />0: 禁止<br />注意: MSI-X和MSI不能同时使能。 |
| 14 | Function Mask | 相当于MSI-X中断总开关<br />1: 所有中断禁止<br />0: 有各个中断的Mask位决定是否使能对应中断 |
| 13 | 保留 | |
| 10:0 | Table Size | 系统软件读取这些位算出MSI-X Table的大小也就是支持多少个中断<br />读出值为"N-1"表示支持N个中断 |
* drivers\irqchip\irq-gic-v3-its-pci-msi.c
* Table Offset/Table BIR BIR意思为"BAR Indicator register"表示使用哪个BAR。
```c
its_pci_msi_init
pci_msi_create_irq_domain
info->flags |= MSI_FLAG_ACTIVATE_EARLY;
domain = msi_create_irq_domain(fwnode, info, parent);
```
| 位 | 域 | 描述 |
| ---- | ------------ | ------------------------------------------------------------ |
| 31:3 | Table Offset | MSI-X Table保存在PCI设备的内存空间里<br />在哪个内存空间?有下面的"Table BIR"表示。<br />在这个内存空间的哪个位置?由当前这几位表示。 |
| 2:0 | Table BIR | 使用哪个内存空间来保存MSI-X Table<br />也就是系统软件使用哪个BAR来访问MSI-X Table<br />取值为0~5表示BAR0~BAR5 |
* PBA Offset/PBA BIRPBA意思为"Pending Bit Array"用来表示MSI-X中断的挂起状态。
| 位 | 域 | 描述 |
| ---- | ---------- | ------------------------------------------------------------ |
| 31:3 | PBA Offset | PBA保存在PCI设备的内存空间里<br />在哪个内存空间?有下面的"PBA BIR"表示。<br />在这个内存空间的哪个位置?由当前这几位表示。 |
| 2:0 | PBA BIR | 使用哪个内存空间来保存PBA<br />也就是系统软件使用哪个BAR来访问PBA<br />取值为0~5表示BAR0~BAR5 |
![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)
#### 4.3 MSI-X Table
PCI设备可以往某个地址写入某个数据从而触发MSI-X中断。
#### 5.5 PCIe设备发出中断时写哪个地址
这些"地址/数据"信息,是由系统软件分配的,系统软件要把"地址/数据"发给PCI设备
![image-20220112160643994](pic/10_PCI_PCIe/95_its_top_domain.png)
PCI设备在哪里保存这些信息
```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);
```
* 在PCI设备的内存空间
* 在哪个内存空间由MSI-X capability的"Table BIR"决定
* 在这个内存空间的哪个位置由MSI-X capability的"Table Offset"决定
MSI-X Table格式如何如下图所示
![image-20220117144509485](pic/10_PCI_PCIe/108_msi-x_table.png)
调用过程:`kernel/irq/msi.c`
上图中Msg Data、Msg Addr Msg Upper Addr含义与MSI机制相同PCI设备要发出MSI-X中断时往这个地址写入这个数据。如果使用32位地址的话写哪个地址由Msg Addr寄存器决定如果使用64位地址的话写哪个地址由Msg Addr和Msg Upper Addr这两个寄存器决定。
```c
msi_domain_activate
irq_chip_write_msi_msg
```
Vector Control寄存器中只有Bit0有作用表示"Mask Bit"。系统软件写入1表示禁止对应中断写入0表示使能对应中断。
##### 5.5.1 分配中断
#### 4.4 PBA
```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);
PBA意思为"Pending Bit Array"用来表示MSI-X中断的挂起状态。它的格式如下
__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);
这些"挂起"信息是由PCI设备设置系统软件只能读取这些信息。
// 对于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设备在哪里保存这些信息
* 在PCI设备的内存空间
* 在哪个内存空间由MSI-X capability的"PBA BIR"决定
* 在这个内存空间的哪个位置由MSI-X capability的"PBA Offset"决定
// 为PCI设备确定hwirq
PBA格式如下每一位对应一个中断值为1表示中断发生了、等待处理。
its_domain_ops.alloc
its_irq_domain_alloc
its_alloc_device_irq
*hwirq = dev->event_map.lpi_base + idx;
![image-20220117145232528](pic/10_PCI_PCIe/109_pba.png)
```
### 5. MSI/MSI-X操作流程
#### 5.1 扫描设备
扫描设备读取capability确定是否含有MSI capability、是否含有MSI-X capability。
#### 5.2 配置设备
一个设备可能都支持INTx、MSI、MSI-X这3中方式只能选择一种。
##### 5.2.1 MSI配置
系统软件读取MSI capability确定设备想申请多少个中断。
系统软件确定能分配多少个中断给这个设备,并把"地址/数据"写入MSI capability。
如果MSI capability支持中断使能的话还需要系统软件设置MSI capability来使能中断。
注意如果支持多个MSI中断PCI设备发出中断时写的是同一个地址但是数据的低位可以发生变化。
比如支持4个MSI中断时通过数据的bit1、bit0来表示是哪个中断。
##### 5.2.2 MSI-X配置
MSI-X机制中中断相关的更多信息保存在设备的内存空间。所以要使用MSI-X中断要先配置设备、分配内存空间。
然后系统软件读取MSI-X capability确定设备需要多少个中断。
系统软件确定能分配多少个中断给这个设备,并把多个"地址/数据"写入MSI-X Table。
注意PCI设备要发出MSI-X中断时它会往"地址"写入"数据",这些"地址/数据"一旦配置后是不会变化的。MSI机制中数据可以变化MSI-X机制中数据不可以变化。
使能中断设置总开关、MSI-X Table中某个中断的开关。
注意MSI-X Table中每一项都可以保存一个"地址/数据"Table中"地址/数据"可以相同也就是说PCI设备发出的中断可以是同一个。
#### 5.3 设备发出中断
PCI设备发出MSI中断、MSI-X中断时都是发起"数据写"传输,就是往指定地址写入指定数据。
PCI控制器接收到数据后就会触发CPU中断。
#### 5.4 中断函数
系统软件执行中断处理函数。
#### 5.6 IRQ Domain创建流程
* `drivers\irqchip\irq-gic-v3-its.c`
*

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -446,6 +446,11 @@ git clone https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
```shell
10_RK3399_PCIe_Host驱动分析_设备枚举
```
* 2022.01.17 发布"PCI和PCIe子系统"
```shell
11_INTx_MSI_MSIX三种中断机制分析
```
## 6. 联系方式

View File

@@ -0,0 +1,408 @@
## 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
* 配置空间里有第1个capability的位置Capabilities Pointer
* 它指向第1个capability的多个寄存器这些寄存器也是在配置空间里
* 第1个capability的寄存器里也会指示第2个capability在哪里
![image-20220114163327615](pic/10_PCI_PCIe/103_capability.png)
Capability示例图如下
* 配置空间0x34位置存放的是第1个capability的位置假设是 A4H
* 在配置空间0xA4位置找到第1个capabilitycapability的寄存器有如下约定
* 第1个字节表示ID每类capability都有自己的ID
* 第2个字节表示下一个capability的位置如果等于0表示这是最后一个capability
* 其他寄存器由capability决定所占据的寄存器数量由capability决定
* 第1个capability里面它表示下一个capability在5CH
* 在配置空间0x5C位置找到第2个capability
* 第1个字节表示ID第2个字节表示下一个capability的位置(图里是E0H)
* 其他字节由capability本身决定
* 在配置空间0xE0位置找到第3个capability
* 第1个字节表示ID
* 第2个字节表示下一个capability的位置这里是00H表示没有更多的capability了
* 其他字节由capability本身决定
![image-20220117103544744](pic/10_PCI_PCIe/105_capabilities.png)
#### 3.2 MSI capability
一个PCI设备是否支持MSI需要读取配置空间的capability来判断: 有MSI capability的话就支持MSI机制。
在配置空间中MSI capability用来保存pci_addr、data。表示PCI设备往这个pci_addr写入data就可以触发中断。
有如下问题要确认:
* pci_addr是32位、还是64位
* 能触发几个中断?通过地址来分辨,还是通过数据来分辨?
* 这些中断能否屏蔽?
* 能否读出中断状态?
* 这个些问题都由capability里面的"Message Control"决定。
MSI capability格式如下
![image-20220117115002363](pic/10_PCI_PCIe/106_msi_capability.png)
#### 3.3 格式解析
MSI Capability格式的含义如下
* Capability ID对于MSI capability它的ID为05H
* Next Pointer下一个capability的位置00H表示这是最后一个capability
* Message Control
| 位 | 域 | 描述 |
| ---- | -------------------------- | ------------------------------------------------------------ |
| 8 | Per-vector masking capable | 是否支持屏蔽单个中断(vector)<br />1: 支持<br />0: 不支持<br />这是只读位。 |
| 7 | 64 bit address capable | 是否支持64位地址<br />1: 支持<br />0: 不支持<br />这是只读位。 |
| 6:4 | Multiple Message Enable | 系统软件可以支持多少个MSI中断<br />PCI设备可以表明自己想发出多少个中断<br />但是到底能发出几个中断?<br />由系统软件决定,它会写这些位,表示能支持多少个中断:<br />000: 系统分配了1个中断<br />001: 系统分配了2个中断<br />010: 系统分配了4个中断<br />011: 系统分配了8个中断<br />100: 系统分配了16个中断<br />101: 系统分配了32个中断<br />110: 保留值<br />111: 保留值<br />这些位是可读可写的。 |
| 3:1 | Multiple Message Capable | PCI设备可以表明自己想发出多少个中断<br />000: 设备申请1个中断<br />001: 设备申请2个中断<br />010: 设备申请4个中断<br />011: 设备申请8个中断<br />100: 设备申请16个中断<br />101: 设备申请32个中断<br />110: 保留值<br />111: 保留值<br />这些位是只读的。 |
| 0 | MSI Enable | 使能MSI<br />1: 使能<br />0: 禁止 |
* Message Address/Message Uper Address地址
* 32位地址保存在Message Address中
* 64位地址低32位地址保存在Message Address中高32位地址保存在Message Uper Address中
* 这些地址是系统软件初始化PCI设备时分配的系统软件把分配的地址写入这些寄存器
* 这些地址属于PCI地址空间
* Message Data数据
* 这个寄存器只有15位PCI设备发出中断时数据是32位的其中高16位数据为0
* 这个寄存器的数值是系统软件初始化设备时写入的
* 当PCI设备想发出中断是会发起一次写传输
* 往Message Address寄存器表示的地址写入Message Data寄存器的数据
* 如果可以发出多个中断的话,发出的数据中低位可以改变
* 比如"Multiple Message Enable"被设置为"010"表示可以发出4个中断
* 那么PCI设备发出的数据中bit1,0可以修改
* Mask Bits/Pending Bits: 屏蔽位/挂起位这是可选的功能PCI设备不一定实现它
* Mask Bits每一位用来屏蔽一个中断被系统软件设置为1表示禁止对应的中断
* Pending Bits每一位用来表示一个中断的状态这是软件只读位它的值为1表示对应中断发生了待处理
### 4. MSI-X中断机制
MSI机制有几个缺点
* 每个设备的中断数最大是32太少了
* 需要系统软件分配连续的中断号很可能失败也就是说设备想发出N个中断但是系统软件分配给它的中断少于N个
* 通过MSI发出中断时地址是固定的
于是引入了MSI-X机制Enhanced MSI interrupt support它解决了MSI的缺点
* 可以支持多大2048个中断
* 系统软件可以单独设置每个中断,不需要分配连续的中断号
* 每个中断可以单独设置PCI设备使用的"地址/数据"可以单独设置
假设MSI-X可以支持很多中断每个中断的"地址/数据"都不一样。提问:在哪里描述这些信息?
* "地址/数据"
* 不放在配置空间,空间不够
* 放在PCI设备的内存空间哪个内存空间哪个BAR内存空间哪个位置(偏移地址)
* 系统软件可以读写这些内存空间
* 中断的控制信息
* 使能/禁止?
* 地址是32位还是64位
* 这些控制信息也是保存在PCI设备的内存空间
* 中断的状态信息(挂起?)
* 这些信息也是保存在PCI设备的内存空间
#### 4.1 MSI-X capability
一个PCI设备是否支持MSI-X需要读取配置空间的capability来判断: 有MSI-X capability的话就支持MSI-X机制。
MSI-X capability格式如下
![image-20220117142556453](pic/10_PCI_PCIe/107_msi-x_capability.png)
#### 4.2 MSI-X capability格式解析
格式解析如下:
* Capability ID对于MSI-X capability它的ID为11H
* Next Pointer下一个capability的位置00H表示这是最后一个capability
* Message Control
| 位 | 名 | 描述 |
| ---- | ------------- | ------------------------------------------------------------ |
| 15 | MSI-X Enable | 是否使能MSI-X<br />1: 使能<br />0: 禁止<br />注意: MSI-X和MSI不能同时使能。 |
| 14 | Function Mask | 相当于MSI-X中断总开关<br />1: 所有中断禁止<br />0: 有各个中断的Mask位决定是否使能对应中断 |
| 13 | 保留 | |
| 10:0 | Table Size | 系统软件读取这些位算出MSI-X Table的大小也就是支持多少个中断<br />读出值为"N-1"表示支持N个中断 |
* Table Offset/Table BIR BIR意思为"BAR Indicator register"表示使用哪个BAR。
| 位 | 域 | 描述 |
| ---- | ------------ | ------------------------------------------------------------ |
| 31:3 | Table Offset | MSI-X Table保存在PCI设备的内存空间里<br />在哪个内存空间?有下面的"Table BIR"表示。<br />在这个内存空间的哪个位置?由当前这几位表示。 |
| 2:0 | Table BIR | 使用哪个内存空间来保存MSI-X Table<br />也就是系统软件使用哪个BAR来访问MSI-X Table<br />取值为0~5表示BAR0~BAR5 |
* PBA Offset/PBA BIRPBA意思为"Pending Bit Array"用来表示MSI-X中断的挂起状态。
| 位 | 域 | 描述 |
| ---- | ---------- | ------------------------------------------------------------ |
| 31:3 | PBA Offset | PBA保存在PCI设备的内存空间里<br />在哪个内存空间?有下面的"PBA BIR"表示。<br />在这个内存空间的哪个位置?由当前这几位表示。 |
| 2:0 | PBA BIR | 使用哪个内存空间来保存PBA<br />也就是系统软件使用哪个BAR来访问PBA<br />取值为0~5表示BAR0~BAR5 |
#### 4.3 MSI-X Table
PCI设备可以往某个地址写入某个数据从而触发MSI-X中断。
这些"地址/数据"信息,是由系统软件分配的,系统软件要把"地址/数据"发给PCI设备。
PCI设备在哪里保存这些信息
* 在PCI设备的内存空间
* 在哪个内存空间由MSI-X capability的"Table BIR"决定
* 在这个内存空间的哪个位置由MSI-X capability的"Table Offset"决定
MSI-X Table格式如何如下图所示
![image-20220117144509485](pic/10_PCI_PCIe/108_msi-x_table.png)
上图中Msg Data、Msg Addr Msg Upper Addr含义与MSI机制相同PCI设备要发出MSI-X中断时往这个地址写入这个数据。如果使用32位地址的话写哪个地址由Msg Addr寄存器决定如果使用64位地址的话写哪个地址由Msg Addr和Msg Upper Addr这两个寄存器决定。
Vector Control寄存器中只有Bit0有作用表示"Mask Bit"。系统软件写入1表示禁止对应中断写入0表示使能对应中断。
#### 4.4 PBA
PBA意思为"Pending Bit Array"用来表示MSI-X中断的挂起状态。它的格式如下
这些"挂起"信息是由PCI设备设置系统软件只能读取这些信息。
PCI设备在哪里保存这些信息
* 在PCI设备的内存空间
* 在哪个内存空间由MSI-X capability的"PBA BIR"决定
* 在这个内存空间的哪个位置由MSI-X capability的"PBA Offset"决定
PBA格式如下每一位对应一个中断值为1表示中断发生了、等待处理。
![image-20220117145232528](pic/10_PCI_PCIe/109_pba.png)
### 5. MSI/MSI-X操作流程
#### 5.1 扫描设备
扫描设备读取capability确定是否含有MSI capability、是否含有MSI-X capability。
#### 5.2 配置设备
一个设备可能都支持INTx、MSI、MSI-X这3中方式只能选择一种。
##### 5.2.1 MSI配置
系统软件读取MSI capability确定设备想申请多少个中断。
系统软件确定能分配多少个中断给这个设备,并把"地址/数据"写入MSI capability。
如果MSI capability支持中断使能的话还需要系统软件设置MSI capability来使能中断。
注意如果支持多个MSI中断PCI设备发出中断时写的是同一个地址但是数据的低位可以发生变化。
比如支持4个MSI中断时通过数据的bit1、bit0来表示是哪个中断。
##### 5.2.2 MSI-X配置
MSI-X机制中中断相关的更多信息保存在设备的内存空间。所以要使用MSI-X中断要先配置设备、分配内存空间。
然后系统软件读取MSI-X capability确定设备需要多少个中断。
系统软件确定能分配多少个中断给这个设备,并把多个"地址/数据"写入MSI-X Table。
注意PCI设备要发出MSI-X中断时它会往"地址"写入"数据",这些"地址/数据"一旦配置后是不会变化的。MSI机制中数据可以变化MSI-X机制中数据不可以变化。
使能中断设置总开关、MSI-X Table中某个中断的开关。
注意MSI-X Table中每一项都可以保存一个"地址/数据"Table中"地址/数据"可以相同也就是说PCI设备发出的中断可以是同一个。
#### 5.3 设备发出中断
PCI设备发出MSI中断、MSI-X中断时都是发起"数据写"传输,就是往指定地址写入指定数据。
PCI控制器接收到数据后就会触发CPU中断。
#### 5.4 中断函数
系统软件执行中断处理函数。

View File

@@ -0,0 +1,251 @@
## MSI和MSIX原理分析
参考资料:
* 《PCI_SPEV_V3_0.pdf》6.8节
* [PCIe中MSI和MSI-X中断机制](https://blog.csdn.net/pieces_thinking/article/details/119431791)
* [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. capability
### 5. 代码分析
#### 5.1 使用中断线
```c
pci_scan_single_device
pci_device_add
pcibios_add_device
dev->irq = of_irq_parse_and_map_pci(dev, 0, 0);
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;
-------------------
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`
*

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: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 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