首页 理论教育Linux操作系统启用MSI中断机制的方法

Linux操作系统启用MSI中断机制的方法

【摘要】:如果pci_enable_msi_block函数通过了这些检查,将调用pci_msi_check_device函数,检查Linux系统是否能够使能PCI设备的MSI中断机制。这段代码置PCI设备的MSI Capability结构的Enable位为0,msi_capability_init函数需要对MSI Capability结构进行读写操作,因此需要暂时禁止当前设备使用MSI中断机制。在entry参数中存放该PCI设备使用的MSI中断机制的详细信息。源代码15-11 Linux x86使用的arch_setup_msi_irqs函数这段代码首先判断type是否为PCI_CAP_ID_MSI,而且nvec参数是否大于1,如果满足这两个条件,该函数将直接返回1。

如果PCI/PCIe设备需要使用MSI中断机制,将调用pci_enable_msi函数,在Linux2.6.31内核中,pci_enable_msi函数使用pci_enable_msi_block(pdev,1)实现。pci_enable_msi_block函数在./drivers/pci/msi.c文件中,如源代码15-8所示。pci_enable_msi_block函数具有两个入口参数,其中dev参数存放PCIe设备的pci_dev结构,而nvec参数为申请的irq号个数。

该函数返回值为0时,表示成功返回,此时该函数将更新pci_dev→irq参数,此时在Linux设备驱动程序中,可以使用的irq号在pci_dev→irq~pci_dev→irq+nvec-1之间;当函数返回值为负数时,表示出现错误;而为正数时,表示pci_enable_msi_block函数没有成功返回,返回值为该PCIe设备MSI Cabalibities结构的Multiple Message Capable字段。

源代码15-8 pci_enable_msi_block函数

978-7-111-29822-9-Part03-83.jpg

978-7-111-29822-9-Part03-84.jpg

这段代码首先检查PCI设备是否支持MSI中断机制,如果不支持将直接退出该函数。否则检查nvec参数和Multiple Message Capable字段的大小,如果nvec的值较大时,该函数直接使用Multiple Message Capable字段返回。

如果pci_enable_msi_block函数通过了这些检查,将调用pci_msi_check_device函数,检查Linux系统是否能够使能PCI设备的MSI中断机制。这个检查包含两方面内容,一方面是纯软件层面的,包括检查全局变量pci_msi_enable、pci_dev→no_msi参数等;一方面是硬件层面的检测,包括当前PCI设备的上游PCI桥是否支持MSI报文的转发,PCI设备是否具有Capabilities链表,是否具有MSI Capability结构。完成这些检查后,pci_enable_msi将进一步调用msi_capability_init函数,完成与MSI中断相关的设置,msi_capability_init函数的实现如源代码15-9~10所示。

源代码15-9 msi_capability_init函数片段1

978-7-111-29822-9-Part03-85.jpg

978-7-111-29822-9-Part03-86.jpg

msi_capability_init函数具有两个入口参数,在Linux 2.6.30内核中,该函数具有一个入口参数,仅能获得一个irq号。msi_capability_init函数参考了msix_capability_init函数的实现机制。这段代码置PCI设备的MSI Capability结构的Enable位为0,msi_capability_init函数需要对MSI Capability结构进行读写操作,因此需要暂时禁止当前设备使用MSI中断机制。

这段程序随后读取MSI Capabilty结构的Message Control字段,并暂时保存在control变量中,在control变量中存放PCIe设备使用的MSI Capability结构的格式,如图10-1所示,MSI Capability结构可以使用4种格式。

最后这段程序调用alloc_msi_entry函数分配一个msi_desc结构的entry参数,并将其初始化后,加入到pci_dev→msi_list链表中。在entry参数中存放该PCI设备使用的MSI中断机制的详细信息。

源代码15-10 msi_capability_init函数片段2

978-7-111-29822-9-Part03-87.jpg

这段代码继续调用arch_setup_msi_irqs函数设置MSI Capability结构的其他字段,并设置entry结构的irq参数,arch_setup_msi_irqs函数的实现与体系结构相关,下文将分别介绍x86和PowerPC处理器的实现方式。

然后这段代码调用pci_intx_for_msi函数,关闭PCI设备配置空间Command寄存器的In-terrupt Disable位,因为该PCI设备将使用MSI中断机制,而不是传统的INTx中断机制;并调用msi_set_enable函数使能MSI Capability结构的Enable位;最后对pci_dev→msi_enabled位置1,并将pci_dev→irq参数赋值。(www.chuimin.cn)

1.Linux x86

Linux x86使用的arch_setup_msi_irqs函数在./arch/x86/kernel/apic/io_apic.c文件中,其实现如源代码15-1所示,本节并不关心intr_remapping_enabled参数为1的情况,该参数与IOMMU机制的IRQ Remapping相关。

源代码15-11 Linux x86使用的arch_setup_msi_irqs函数

978-7-111-29822-9-Part03-88.jpg

这段代码首先判断type是否为PCI_CAP_ID_MSI,而且nvec参数是否大于1,如果满足这两个条件,该函数将直接返回1。通过这段代码可以发现,虽然在Linux 2.6.31内核中定义了一个新的pci_enable_msi_block函数,但是PCIe设备依然只能使用一个中断向量号。

这段程序随后调用create_irq_nr函数,分配PCI设备使用的irq号,并将其保存到irq变量中,之后调用setup_msi_irq函数初始化当前PCI设备的MSI Capability结构。setup_msi_irq函数是一个重要函数,其实现如源代码15-12所示。

源代码15-12 setup_msi_irq函数

978-7-111-29822-9-Part03-89.jpg

这段代码首先调用msi_compose_msg函数,初始化msg结构的address_hi、address_lo和data参数,与MSI Capability结构的Message Upper Address、Message Address和Message Data字段对应。

对于x86处理器系统,Message Address字段的格式见图10-1,其中Destination ID字段与CPU的ACPI ID相关,Linux x86使用cpu_mask_to_apicid_and函数获得该字段的值;而Message Data字段的格式如图10-1所示,其Vector字段由assign_irq_vector函数设置,Trig-ger Mode字段为0x00表示使用边沿触发方式,而Delivery Mode字段为“Fixed Mode”或者“Lowest Priority”。值得注意的是,在Message Data字段中存放的是中断向量号(vector),是一个硬件的概念,而设备驱动程序中使用的irq号是一个软件关系,两者之间存在对应关系,但是并不等同。

set_irq_msi函数设置PCIe设备使用的irq_desc结构;而write_msi_msg函数将msg结构中的参数写入到PCIe设备的对应寄存器中;而set_irq_chip_and_handler_name函数设置MSI中断使用的中断处理程序。本节对这些函数不做进一步介绍。

2.PowerPC

Linux PowerPC使用的arch_setup_msi_irqs函数在./arch/powerpc/kernel/msi.c文件中,对于Freescale的PowerPC处理器,该函数等效与fsl_setup_msi_irqs。fsl_setup_msi_irqs函数的实现,如源代码15-13所示。

源代码15-13 fsl_setup_msi_irqs函数

978-7-111-29822-9-Part03-90.jpg

该函数的实现机制与x86处理器类似,值得提醒读者注意的是在fsl_compose_msi_msg函数中msg→address_hi等于fsl_msi→msi_addr_lo,其值为MSIIR寄存器在PCI总线域的物理地址。在该函数中使用的address_hi、address_lo和data参数的详细描述见第10.2节。本节对此不一一叙述。目前在PowerPC处理器系统中,PCIe设备也只能使用一个中断向量号。