往期内容
本文章相关专栏往期内容,PCI/PCIe子系统专栏:
- 嵌入式系统的内存访问和总线通信机制解析、PCI/PCIe引入
- 深入解析非桥PCI设备的访问和配置方法
- PCI桥设备的访问方法、软件角度讲解PCIe设备的硬件结构
- 深入解析PCIe设备事务层与配置过程
- PCIe的三种路由方式
- PCI驱动与AXI总线框架解析(RK3399)
- 深入解析PCIe地址空间与寄存器机制:从地址映射到TLP生成的完整流程
- PCIe_Host驱动分析_地址映射
- PCIe_Host驱动分析_设备枚举
- PCI/PCIe设备INTx中断机制和MSI中断机制
- MSI-X中断机制、MSI/MSI-X操作流程详解
- RK3399 PCIe 中断处理与映射分析(INTx中断机制源码分析)
- GICv2与GICv3中断架构对比与LPI中断机制分析
Uart子系统专栏:
- 专栏地址:Uart子系统
- Linux内核早期打印机制与RS485通信技术
– 末片,有专栏内容观看顺序interrupt子系统专栏:
- 专栏地址:interrupt子系统
- Linux 链式与层级中断控制器讲解:原理与驱动开发
– 末片,有专栏内容观看顺序pinctrl和gpio子系统专栏:
专栏地址:pinctrl和gpio子系统
编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用
– 末片,有专栏内容观看顺序
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有专栏内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-优快云博客
– 末篇,有专栏内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-优快云博客
– 末篇,有专栏内容观看顺序
目录
1.参考资料
开发板资料:
- https://wiki.t-firefly.com/zh_CN/ROC-RK3399-PC-PLUS/
参考内核文件:
Documentation\PCI\MSI-HOWTO.txt
📎MSI-HOWTO.txtdrivers\pci\host\pcie-rockchip.c
📎pcie-rockchip.cdrivers\nvme\host\pci.c
📎pci.cdrivers\irqchip\irq-gic-v3.c
📎irq-gic-v3.cdrivers\irqchip\irq-gic-v3-its.c
📎irq-gic-v3-its.cdrivers\irqchip\irq-gic-v3-its-pci-msi.c
📎irq-gic-v3-its-pci-msi.c
2.MSI/MSI-X中断源码分析
2.1 怎么使用MSI/MSI-X中断
参考内核文档:Documentation\PCI\MSI-HOWTO.txt
、drivers\nvme\host\pci.c
主要函数是这2个:
int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries,
int minvec, int maxvec);
int pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec);
**pci_enable_msix_range**
:用于启用MSI-X中断。
- 参数
entries
:指向一个msix_entry
数组,每个数组项用来保存一个中断向量的相关信息,具体包括中断号。 - 参数
minvec
和maxvec
:分别表示希望启用的最少和最多中断向量数。函数返回实际分配的中断向量数。如果返回的数值小于0,表示分配失败。
**pci_enable_msi_range**
:用于启用MSI中断。
- 参数
minvec
和maxvec
:表示最少和最多请求的MSI中断向量数量,类似于pci_enable_msix_range
。
// 分配 msix_entry 数组,每一数组项用来保存一个中断的信息
dev->entry = kzalloc_node(num_possible_cpus() * sizeof(*dev->entry),
GFP_KERNEL, node); //---------(1)
// 先尝试使用MSI-X
vecs = pci_enable_msix_range(pdev, dev->entry, 1, nr_io_queues); //---------(2)
if (vecs < 0) {
// 再尝试使用MSI
vecs = pci_enable_msi_range(pdev, 1, min(nr_io_queues, 32)); //---------(3)
if (vecs < 0) {
vecs = 1;
} else {
for (i = 0; i < vecs; i++)//---------(4)
dev->entry[i].vector = i + pdev->irq;
}
}
// request_irq: 中断号都保存在dev->entry[i].vector里
for (i = 0; i < vecs; i++)
request_irq(dev->entry[i].vector, ...);//---------(5)
(1)这里为MSI-X中断分配 msix_entry
数组,数组的每个元素对应一个中断向量。num_possible_cpus()
返回系统中可能的CPU数量,分配数组大小时通常会根据系统负载来决定分配多少个中断向量,以提高并发性。
(2)尝试启用MSI-X中断。1
表示至少分配1个中断向量,nr_io_queues
表示最多分配的中断向量数量(一般对应I/O队列数)。函数返回实际分配的中断向量数。如果返回负值表示启用失败,进入 else
分支尝试启用MSI。
(3)如果MSI-X启用失败,则尝试启用MSI。minvec = 1
,表示至少启用1个中断向量,maxvec
的值为 nr_io_queues
和 32
之间的最小值。返回值 vecs
表示实际分配的中断向量数,仍然有可能小于 nr_io_queues
。
(4)如果成功启用了MSI,则 pdev->irq
中存储的是第一个分配的MSI中断号。在这种情况下,dev->entry[i].vector
被依次设置为 pdev->irq + i
,对应不同的中断向量。
(5)这里为每个分配的中断向量调用 request_irq
,将中断号和相应的中断处理函数绑定。dev->entry[i].vector
保存了每个中断向量的中断号。
注意,在pci_enable_msix_range
或者pci_enable_msi_range
函数中:
- minvec从1开始
- 对于pci_enable_msix_range,中断号保存在entries[i].vector里
- 对于pci_enable_msi_range,第1个中断号保存在pdev->irq里
2.2 IRQ Domain的创建
2.2.1 GIC
gic: interrupt-controller@fee00000 {
compatible = "arm,gic-v3";
#interrupt-cells = <4>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
interrupt-controller;
reg = <0x0 0xfee00000 0 0x10000>, /* GICD */
<0x0 0xfef00000 0 0xc0000>, /* GICR */
<0x0 0xfff00000 0 0x10000>, /* GICC */
<0x0 0xfff10000 0 0x10000>, /* GICH */
<0x0 0xfff20000 0 0x10000>; /* GICV */
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH 0>;
its: interrupt-controller@fee20000 {
compatible = "arm,gic-v3-its";
msi-controller;
reg = <0x0 0xfee20000 0x0 0x20000>;
};
drivers\irqchip\irq-gic-v3.c:
static int __init gic_init_bases(void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
{
//.....
gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
&gic_data);
//....
}
static const struct irq_domain_ops gic_irq_domain_ops = {
.alloc = gic_irq_domain_alloc,
.free = gic_irq_domain_free,
.match = gic_irq_domain_match,
};
static struct irq_chip gic_chip = {
.name = "GICv3",
.irq_mask = gic_mask_irq,
.irq_unmask = gic_unmask_irq,
.irq_eoi = gic_eoi_irq,
.irq_set_type = gic_set_type,
.irq_set_affinity = gic_set_affinity,
.irq_get_irqchip_state = gic_irq_get_irqchip_state,
.irq_set_irqchip_state = gic_irq_set_irqchip_state,
.flags = IRQCHIP_SET_TYPE_MASKED,
};
其中关键即使irq_domain_create_tree,至于gic_irq_domain_ops和gic_chip,是赋值到struct irq_domain *domain中的,具体可以看下面:
\Linux-4.9.88\include\linux\irqdomain.h
static inline struct irq_domain *irq_domain_create_tree(struct fwnode_handle *fwnode,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(fwnode, 0, ~0, 0, ops, host_data); //看下面
}
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
irq_hw_number_t hwirq_max, int direct_max,
const struct irq_domain_ops *ops,
void *host_data)
{
struct device_node *of_node = to_of_node(fwnode);
struct irq_domain *domain;
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
GFP_KERNEL, of_node_to_nid(of_node));
if (WARN_ON(!domain))
return NULL;
of_node_get(of_node);
/* Fill structure */
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
domain->ops = ops; //gic_irq_domain_ops
domain->host_data = host_data; //就是前面传的gic_data,而data中又有gic_chip
domain->fwnode = fwnode;
domain->hwirq_max = hwirq_max;
domain->revmap_size = size;
domain->revmap_direct_max_irq = direct_max;
irq_domain_check_hierarchy(domain);
mutex_lock(&irq_domain_mutex);
list_add(&domain->link, &irq_domain_list);
mutex_unlock(&irq_domain_mutex);
pr_debug("Added domain %s\n", domain->name);
return domain;
}
这就创建了GIC的domain,但并没有立即建立具体的硬件中断号(hwirq)到虚拟中断号(virq)的映射,下面提到的也是一样
2.2.2 ITS
its: interrupt-controller@fee20000 {
compatible = "arm,gic-v3-its";
msi-controller;
reg = <0x0 0xfee20000 0x0 0x20000>;
};
drivers\irqchip\irq-gic-v3-its.c:
static const struct irq_domain_ops its_domain_ops = {
.alloc = its_irq_domain_alloc,
.free = its_irq_domain_free,
.activate = its_irq_domain_activate,
.deactivate = its_irq_domain_deactivate,
};
static struct irq_chip its_irq_chip = {
.name = "ITS",
.irq_mask = its_mask_irq,
.irq_unmask = its_unmask_irq,
.irq_eoi = irq_chip_eoi_parent,
.irq_set_affinity = its_set_affinity,
.irq_compose_msi_msg = its_irq_compose_msi_msg,
};
static int its_init_domain(struct fwnode_handle *handle, struct its_node *its)
{
struct irq_domain *inner_domain;
struct msi_domain_info *info;
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
inner_domain = irq_domain_create_tree(handle, &its_domain_ops, its); //-----(1)
if (!inner_domain) {
kfree(info);
return -ENOMEM;
}
inner_domain->parent = its_parent;
inner_domain->bus_token = DOMAIN_BUS_NEXUS;
info->ops = &its_msi_domain_ops;
info->data = its;
inner_domain->host_data = info;
return 0;
}
(1)irq_domain_create_tree创建its的域,内部原理和GIC差不多
2.2.3 PCI 控制器
its: interrupt-controller@fee20000 {
compatible = "arm,gic-v3-its";
msi-controller;
reg = <0x0 0xfee20000 0x0 0x20000>;
};
static struct msi_domain_info its_pci_msi_domain_info = {
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX),
.ops = &its_pci_msi_ops,
.chip = &its_msi_irq_chip,
};
static struct irq_chip its_msi_irq_chip = {
.name = "ITS-MSI",
.irq_unmask = its_unmask_msi_irq,
.irq_mask = its_mask_msi_irq,
.irq_eoi = irq_chip_eoi_parent,
.irq_write_msi_msg = pci_msi_domain_write_msg,
};
static int __init its_pci_msi_init_one(struct fwnode_handle *handle,
const char *name)
{
struct irq_domain *parent;
parent = irq_find_matching_fwnode(handle, DOMAIN_BUS_NEXUS);
if (!parent || !msi_get_domain_info(parent)) {
pr_err("%s: Unable to locate ITS domain\n", name);
return -ENXIO;
}
if (!pci_msi_create_irq_domain(handle, &its_pci_msi_domain_info,
parent)) // 看这里
{
pr_err("%s: Unable to create PCI domain\n", name);
return -ENOMEM;
}
return 0;
}
pci_msi_create_irq_domain:
struct irq_domain *pci_msi_create_irq_domain(struct fwnode_handle *fwnode,
struct msi_domain_info *info,
struct irq_domain *parent)
{
struct irq_domain *domain;
if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS)
pci_msi_domain_update_dom_ops(info);
if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS)
pci_msi_domain_update_chip_ops(info);
info->flags |= MSI_FLAG_ACTIVATE_EARLY;
domain = msi_create_irq_domain(fwnode, info, parent);
if (!domain)
return NULL;
domain->bus_token = DOMAIN_BUS_PCI_MSI;
return domain;
}
其中的msi_create_irq_domain就是去创建域( 并将其父域设置为 ITS 域(parent
)),其实和ITS差不多,PCIe控制器可以看作只是在ITS下面增加了一个处理层
2.3.4 映射
msi-map = <0x0 &its 0x0 0x1000>;
msi-map
是用来将 PCIe 设备映射到特定的 MSI 控制器(在这里是 GIC ITS)的配置,主要包括 Request ID 和中断控制器的信息。
msi-map = <rid-base &msi-controller msi-base length>;
-
rid-base(Request ID Base):
rid-base
是第一个 PCIe 设备的 Request ID。- Request ID 通常由三部分组成:
bus number + device number + function number
,它是标识 PCIe 设备的唯一 ID。 rid-base = 0x0
表示起始设备的 Request ID 是 0。
-
msi-controller(MSI 控制器):
- 这里指定了这个 PCIe 设备关联的中断控制器。在例子中,
&its
表示这个设备映射到 GIC ITS(Interrupt Translation Service),ITS 是 GICv3 中的一个子系统,用于处理 MSI 类中断。 - 每个 PCIe 设备都会产生 MSI 中断,并且这些中断需要通过 ITS 进行翻译并路由到合适的 CPU。
- 这里指定了这个 PCIe 设备关联的中断控制器。在例子中,
-
msi-base(MSI 基地址):
msi-base
是 PCIe 设备第一个中断在 MSI 控制器中的映射起始位置。- 在这里,
msi-base = 0x0
表示从 MSI 控制器(ITS)的第一个中断开始映射。
-
length(映射长度):
length
指定了可以映射的中断个数。length = 0x1000
表示最多可以为这个 PCIe 控制器的设备分配 4096 个 MSI 中断。
msi-map = <0x0 &its 0x0 0x1000>;
是指从 Request ID 为 0 的设备开始,把它们映射到 ITS 中断控制器,从 MSI 中断的 0 开始映射,并且可以分配最多 4096 个 MSI 中断。这个配置将 PCIe 设备的 MSI 中断与 ITS 控制器连接起来,ITS 负责接收这些 MSI 中断并将它们路由到相应的 CPU 进行处理。
PCIe 设备的中断系统就可以通过 ITS 进行高效管理,并使用 MSI/MSI-X 来处理中断请求。
2.3 分配中断
既然创建好了域,就得分配中断了,同时向PCIe设备中配置空间的Capability写入触发中断的addr和data(event)
nvme_probe > nvme_probe_work > nvme_setup_io_queues
static int nvme_setup_io_queues(struct nvme_dev *dev)
{
//....
/*
* If we enable msix early due to not intx, disable it again before
* setting up the full range we need.
*/
pci_free_irq_vectors(pdev);
nr_io_queues = pci_alloc_irq_vectors(pdev, 1, nr_io_queues,
PCI_IRQ_ALL_TYPES | PCI_IRQ_AFFINITY); //继续往下看
if (nr_io_queues <= 0)
return -EIO;
dev->max_qid = nr_io_queues;
//....
}
\Linux-4.9.88\Linux-4.9.88\drivers\pci\msi.c:
int pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs,
unsigned int max_vecs, unsigned int flags)
{
int vecs = -ENOSPC;
if (flags & PCI_IRQ_MSIX) {
vecs = __pci_enable_msix_range(dev, NULL, min_vecs, max_vecs,
flags); //-------(1)
if (vecs > 0)
return vecs;
}
if (flags & PCI_IRQ_MSI) {
vecs = __pci_enable_msi_range(dev, min_vecs, max_vecs, flags);//-------(2)
if (vecs > 0)
return vecs;
}
/* use legacy irq if allowed */
if ((flags & PCI_IRQ_LEGACY) && min_vecs == 1) {
pci_intx(dev, 1);//-----(3)
return 1;
}
return vecs;
}
(1)如果 flags
指定允许使用 MSI-X 中断,首先尝试启用 MSI-X 中断。使用 __pci_enable_msix_range
函数来为设备分配 MSI-X 中断向量。如果成功(返回值大于 0),则返回分配的向量数。__pci_enable_msix_range
是底层函数,负责在给定的范围内(min_vecs
到 max_vecs
)为设备分配 MSI-X 向量。
(2)如果 MSI-X 分配失败,且 flags
允许使用 MSI 中断,则尝试启用 MSI 中断。使用 __pci_enable_msi_range
函数为设备分配 MSI 向量。如果成功,返回分配的向量数。__pci_enable_msi_range
用于在 min_vecs
到 max_vecs
之间分配 MSI 向量。
(3)如果 MSI 和 MSI-X 都不可用,并且 flags
允许使用传统的 PCI 中断(PCI_IRQ_LEGACY
),且设备只需要一个中断向量(min_vecs == 1
),则启用传统的 PCI INTx 中断。pci_intx(dev, 1)
会启用设备的传统中断机制。
就挑一种进行进一步分析,以(1)__pci_enable_msix_range为例子:
\Linux-4.9.88\drivers\pci\msi.c
static int __pci_enable_msix_range(struct pci_dev *dev,
struct msix_entry *entries, int minvec, int maxvec,
unsigned int flags)
{
bool affinity = flags & PCI_IRQ_AFFINITY;
int rc, nvec = maxvec;
if (maxvec < minvec)
return -ERANGE;
for (;;) {
if (affinity) {
nvec = irq_calc_affinity_vectors(dev->irq_affinity,
nvec);
if (nvec < minvec)
return -ENOSPC;
}
//如果设置了亲和性,调用 irq_calc_affinity_vectors 来计算适合的中断向量数
//此函数irq_calc_affinity_vectors可能会返回比 maxvec 更小的值,基于设备的 IRQ 亲和性
rc = __pci_enable_msix(dev, entries, nvec, affinity);//-----(1)
if (rc == 0)
return nvec;
if (rc < 0)
return rc;
if (rc < minvec)
return -ENOSPC;
nvec = rc;
}
}
(1) 调用 __pci_enable_msix
函数尝试为设备分配 nvec
个 MSI-X 中断向量。 如果 __pci_enable_msix
返回负值,则直接返回该错误码。如果返回值小于 minvec
,表示分配的中断向量数量不足。
继续进入往下看:
\Linux-4.9.88\drivers\pci\msi.c:
static int __pci_enable_msix(struct pci_dev *dev, struct msix_entry *entries,
int nvec, bool affinity)
{
int nr_entries;
int i, j;
if (!pci_msi_supported(dev, nvec))
return -EINVAL;
nr_entries = pci_msix_vec_count(dev);
if (nr_entries < 0)
return nr_entries;
if (nvec > nr_entries)
return nr_entries;
if (entries) {
/* Check for any invalid entries */
for (i = 0; i < nvec; i++) {
if (entries[i].entry >= nr_entries)
return -EINVAL; /* invalid entry */
for (j = i + 1; j < nvec; j++) {
if (entries[i].entry == entries[j].entry)
return -EINVAL; /* duplicate entry */
}
}
}
WARN_ON(!!dev->msix_enabled);
/* Check whether driver already requested for MSI irq */
if (dev->msi_enabled) {
dev_info(&dev->dev, "can't enable MSI-X (MSI IRQ already assigned)\n");
return -EINVAL;
}
return msix_capability_init(dev, entries, nvec, affinity);
}
大概就是先进行多项检查(如支持性检查、范围检查、重复检查等),确保请求的中断向量是有效的,并且设备没有已经分配的 MSI 中断。如果所有检查通过,最终调用 msix_capability_init
来进行 MSI-X 的具体启用过程。msix_capability_init 继续往下看:
msix_capability_init > pci_msi_setup_msi_irqs > pci_msi_domain_alloc_irqs
> msi_domain_alloc_irqs :
int msi_domain_alloc_irqs(struct irq_domain *domain, struct device *dev,
int nvec)
{
struct msi_domain_info *info = domain->host_data;
struct msi_domain_ops *ops = info->ops;
msi_alloc_info_t arg;
struct msi_desc *desc;
int i, ret, virq;
ret = msi_domain_prepare_irqs(domain, dev, nvec, &arg); //----(1)
if (ret)
return ret;
for_each_msi_entry(desc, dev) {
ops->set_desc(&arg, desc);
virq = __irq_domain_alloc_irqs(domain, -1, desc->nvec_used,
dev_to_node(dev), &arg, false,
desc->affinity); //----(2)
if (virq < 0) {
ret = -ENOSPC;
if (ops->handle_error)
ret = ops->handle_error(domain, desc, ret);
if (ops->msi_finish)
ops->msi_finish(&arg, ret);
return ret;
}
for (i = 0; i < desc->nvec_used; i++)
irq_set_msi_desc_off(virq, i, desc);
}
if (ops->msi_finish)
ops->msi_finish(&arg, 0);
for_each_msi_entry(desc, dev) {
virq = desc->irq;
if (desc->nvec_used == 1)
dev_dbg(dev, "irq %d for MSI\n", virq);
else
dev_dbg(dev, "irq [%d-%d] for MSI\n",
virq, virq + desc->nvec_used - 1);
/*
* This flag is set by the PCI layer as we need to activate
* the MSI entries before the PCI layer enables MSI in the
* card. Otherwise the card latches a random msi message.
*/
if (info->flags & MSI_FLAG_ACTIVATE_EARLY) {
struct irq_data *irq_data;
irq_data = irq_domain_get_irq_data(domain, desc->irq);
irq_domain_activate_irq(irq_data);//----(3)
}
}
return 0;
}
(1)msi_domain_prepare_irqs:
//是 MSI 中断分配的高层接口,负责调用不同的操作进行中断的准备和分配。
int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *arg)
{
struct msi_domain_info *info = domain->host_data;
struct msi_domain_ops *ops = info->ops;
int ret;
ret = ops->msi_check(domain, info, dev);
if (ret == 0)
ret = ops->msi_prepare(domain, dev, nvec, arg);// 上一层irq-gic-v3-its.c的its_pci_msi_prepare
/*
static struct msi_domain_ops its_pci_msi_ops = {
.msi_prepare = its_pci_msi_prepare,
};//irq-gic-v3-its.c
*/
return ret;
}
-----------------------------------------------------------------------------------------------------------
//its_pci_msi_prepare具体如下
//负责处理 PCI 设备的 MSI 中断准备,涉及设备别名和请求 ID 的管理。
static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *info)
{
struct pci_dev *pdev;
struct its_pci_alias dev_alias;
struct msi_domain_info *msi_info;
if (!dev_is_pci(dev))
return -EINVAL;
msi_info = msi_get_domain_info(domain->parent);
pdev = to_pci_dev(dev);
dev_alias.pdev = pdev;
dev_alias.count = nvec;
pci_for_each_dma_alias(pdev, its_get_pci_alias, &dev_alias);
/* ITS specific DeviceID, as the core ITS ignores dev. */
info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain, pdev);
return msi_info->ops->msi_prepare(domain->parent,
dev, dev_alias.count, info); // 上一层irq-gic-v3-its.c的its_msi_prepare
/*
static struct msi_domain_ops its_msi_domain_ops = {
.msi_prepare = its_msi_prepare,
};// 上一层irq-gic-v3-its.c
*/
}
-----------------------------------------------------------------------------------------------------------
//its_msi_prepare如下:
//是具体的中断准备操作,负责查找或创建 ITS 设备并更新分配信息。
static int its_msi_prepare(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *info)
{
struct its_node *its;
struct its_device *its_dev;
struct msi_domain_info *msi_info;
u32 dev_id;
/*
* We ignore "dev" entierely, and rely on the dev_id that has
* been passed via the scratchpad. This limits this domain's
* usefulness to upper layers that definitely know that they
* are built on top of the ITS.
*/
dev_id = info->scratchpad[0].ul;// rid
msi_info = msi_get_domain_info(domain);
its = msi_info->data;
its_dev = its_find_device(its, dev_id);
if (its_dev) {
/*
* We already have seen this ID, probably through
* another alias (PCI bridge of some sort). No need to
* create the device.
*/
pr_debug("Reusing ITT for devID %x\n", dev_id);
goto out;
}
its_dev = its_create_device(its, dev_id, nvec);
// 从ITS全局的位图里找到空闲位 chunk
// 一个chunk表示32个中断
// its的hwirq = (chunk << 5) + 8192
// 这也是GIC的hwirq
/*
lpi_map = its_lpi_alloc_chunks(nvecs, &lpi_base, &nr_lpis);
// 等于(chunk << 5) + 8192
dev->event_map.lpi_base = lpi_base;
*/
if (!its_dev)
return -ENOMEM;
pr_debug("ITT %d entries, %d bits\n", nvec, ilog2(nvec));
out:
info->scratchpad[0].ptr = its_dev;
return 0;
}
(2)分配中断的入口函数,负责管理 IRQ 的分配过程
__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);//调用 its_alloc_device_irq 函数为 ITS 设备分配 IRQ
*hwirq = dev->event_map.lpi_base + idx;
//分配的硬件 IRQ 是通过设备的 event_map.lpi_base 和当前的索引 idx 计算得到的。
//LPI(Low-Power Interrupt)相关的硬件 IRQ 是通过加法运算进行映射。
irq_domain_set_hwirq_and_chip
irq_data->hwirq = hwirq;//设置硬件 IRQ:
irq_data->chip = chip ? chip : &no_irq_chip;//设置中断芯片
- 入口分配:
__irq_domain_alloc_irqs
作为中断分配的入口,递归地调用中断域的分配操作。 - ITS 设备分配: 在 ITS 中断域中,调用
its_irq_domain_alloc
来为设备分配具体的 IRQ。 - 硬件 IRQ 计算: 通过
dev->event_map.lpi_base
和索引来计算具体的硬件 IRQ。 - 设置数据结构: 最后,通过
irq_domain_set_hwirq_and_chip
将分配的信息写入 IRQ 数据结构,以供后续使用。
(3) 使用 ITS(Interrupt Translation Service)中处理MSI-X 的消息构建和发送流程,也就是往设备的table写入:
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
addr = its->phys_base + GITS_TRANSLATER;
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);
- 激活 IRQ: 使用
irq_domain_activate_irq
激活 IRQ 数据结构,并调用自定义的激活逻辑。 - 构建 MSI 消息: 通过
irq_chip_compose_msi_msg
和its_irq_compose_msi_msg
构建具体的 MSI 消息,设置其地址和数据。 - 写入消息: 将构建好的 MSI 消息写入到 IRQ 数据结构中,并通过中断芯片接口发送到目标设备。
- 发送到 PCI 设备: 最后,通过调用
pci_msi_domain_write_msg
将消息写入到实际的 PCI 设备寄存器中,以触发中断。
3.如何编写PCIe设备驱动程序
参考内核文件:
- `Documentation\PCI\MSI-HOWTO.txt```📎MSI-HOWTO.txt
drivers\nvme\host\pci.c
📎pci.c
3.1 PCI总线设备驱动模型
3.2 获得PCIe设备的资源
PCIe控制器扫描出PCIe设备后,会为这个设备分配资源、并记录在对应的pci_dev里:
- struct resource resource[DEVICE_COUNT_RESOURCE]:含有mem/io资源
- irq:含有INTx中断号
3.2.1 获得内存/IO空间
参考代码:kernel\drivers\scsi\3w-9xxx.c
判断资源类型,参考代码:
3.2.2 获得中断号
- 获得INTx中断号
直接使用pci_dev->irq。
- 获得MSI-X/MSI中断号
参考代码:drivers\nvme\host\pci.c
3.3 使能设备
参考代码:drivers\nvme\host\pci.c