msix中断分析

此文档是基于linux-3.6.10内核代码对msix中断相关进行分析。

PCIe设备可以使用msix报文向处理器提交中断,下面首先看下PCIe设备中的MSXI Capability结构。

msix中断分析_第1张图片

此结构在PCIe设备配置空间偏移0x68的位置处。

字段

含义

Capability ID

Capability结构的ID

Next Cap Ptr

下一个Capability结构的位置

Message Control

当前PCIe设置使用msix中断请求的状态和控制信息

MSI-X Table Offset

存放MSI-X Tablebar空间中的偏移

MSI-X Table BAR Indicator

表示设备使用BAR0~5寄存器中的哪个空间存放MSI-X table

内核中读写PCIe配置空间的函数有:

pci_read_config_dword/pci_read_config_word/pci_read_config_byte

pci_write_config_dword/pci_write_config_word/pci_write_config_byte

 

初始化流程

◆下面分析pci_enable_msix函数的实现。

int pci_enable_msix(struct pci_dev *devstruct msix_entry *entriesint nvec 

pci_enable_msix函数向PCI子系统请求分配nvecmsix中断,第二个参数entries指向msix_entry结构体数组,元素个数不能少于nvec

struct msix_entry  

  u32 vector/* kernel uses to write allocated vector */  

  u16 entry;  /* driver uses to specify entry, OS writes */ 

 };

该结构体的vector字段在后面将会看到被填充为中断向量号。

pci_enable_msix函数开始会检查设备是否支持MSIX,以及支持所需的中断数量。通过检查后重点就是调用msix_capability_init函数配置MSXI Capability结构。

 

◆下面分析msix_capability_init函数的实现。

pos = pci_find_capability(devPCI_CAP_ID_MSIX);  

pci_read_config_word(devpos + PCI_MSIX_FLAGS&control);  

 

/* Ensure MSI-X is disabled while it is set up */   

control &= ~PCI_MSIX_FLAGS_ENABLE 

pci_write_config_word(devpos + PCI_MSIX_FLAGScontrol);  

 

/* Request & Map MSI-X table region */   

base = msix_map_region(devposmulti_msix_capable(control)); 

if (!base 

return -ENOMEM 

ret = msix_setup_entries(devposbaseentriesnvec);  

if (ret 

return ret 

1、 首先调用pci_find_capability函数在pci配置空间中查找MSXI Capability的偏移位置pos,如图1所示就是0x68

2、 读取图1中的message control,并将bit15位清零。在此函数的后面会重新设置bit15,即使能中断。

3、 调用msix_map_region函数建立MSIX Table的地址映射。此函数首先从0x6c偏移处读取值table_offset。如图1所示,此值的bit[0:2]说明了MSIX Table所在的BARbit[3:31]则是MSIX Table在此BAR空间内的偏移。然后使用ioremap_nocache映射此MSIX Table地址。

4、 调用msix_setup_entries函数申请nvecmsix中断对应的msi_desc结构内存,并初始化,添加到devmsi_list链表中。

 

ret = arch_setup_msi_irqs(devnvecPCI_CAP_ID_MSIX); 

if (ret 

goto  error 

arch_setup_msi_irqs函数在x86价格下对应的函数为x86_setup_msi_irqs,此函数最终调用的为native_setup_msi_irqs函数。

 

◆下面分析native_setup_msi_irqs函数的实现。

  list_for_each_entry(msidesc&dev->msi_listlist) {  

   irq = create_irq_nr(irq_wantnode);  

   if (irq == 0 

    return -1 

   irq_want = irq + 1 

   if (!irq_remapping_enabled 

    goto  no_ir 

  

   if (!sub_handle) {  

    /*  

     * allocate the consecutive block of IRTE's  

    * for 'nvec'  

    */  

    index = msi_alloc_remapped_irq(devirqnvec);  

    if (index < 0) {  

     ret index 

     goto  error 

     

   else  

    ret = msi_setup_remapped_irq(devirqindex 

            sub_handle);  

    if (ret < 0 

     goto  error 

    

 no_ir:  

   ret = setup_msi_irq(devmsidescirq);  

   if (ret < 0 

    goto  error 

   sub_handle++ 

   

此函数的主体是循环遍历devmsi_list链表,对前面加入的nvecmsi中断的msi_desc结构。对应于此设备的每个msix中断:

1、 首先调用create_irq_nr函数为此msix中断分配一个中断号irq

2、 如果是此设备的第一个msix中断,调用msi_alloc_remapped_irq分配此设备的nvecmsix中断映射表项。后面的msix中断则调用msi_setup_remapped_irq函数来建立相应的中断映射。

3、 调用setup_msi_irq函数会首先填充MSIX Table中的low_addrhi_addrdata项;为设备的每一个msix中断指定一个vectore向量号;并将MSIX Table中的内容写到前面映射的地址。

msi_alloc_remapped_irq、msi_setup_remapped_irq和setup_msi_irq三个函数内容比较多,下面逐个分析。

■msi_alloc_remapped_irq函数分析

msi_alloc_remapped_irq函数在x86价格下对应是intel_msi_alloc_irq函数。此函数中和底层硬件相关的部分没看怎么明白,也不是重点。重点在alloc_irte函数中。

alloc_irte函数代码片段:

do  

 for (indexindex counti++ 

  if  (table->base[i].present 

   break 

 /* empty index found */  

 if (== index count 

  break 

 index (index count% INTR_REMAP_TABLE_ENTRIES 

 if (index == start_index) {  

  raw_spin_unlock_irqrestore(&irq_2_ir_lockflags);  

  printk(KERN_ERR "can't allocate an IRTE\n");  

  return -1 

  

while (1);  

for (indexindex counti++ 

 table->base[i].present = 1 

irq_iommu->iommu = iommu 

irq_iommu->irte_index =  index 

irq_iommu->sub_handle = 0 

irq_iommu->irte_mask = mask 

此函数中会遍历irte表,找到nvec个连续未使用的位置,并返回位置Index

初始化irq_iommu的irte_index和sub_handle字段。这两个字段在后面计算vector的时候会用到。

 

■msi_setup_remapped_irq函数分析

msi_setup_remapped_irq函数在x86架构下对应的是intel_msi_setup_irq函数。此函数重点在set_irte_irq函数中。

set_irte_irq函数代码片段:

struct irq_2_iommu *irq_iommu = irq_2_iommu(irq);  

 

 irq_iommu->iommu = iommu 

 irq_iommu->irte_index = index 

 irq_iommu->sub_handle = subhandle 

 irq_iommu->irte_mask = 0  

此函数与alloc_irte的基本功能相同,区别在于:alloc_irte函数计算了index值,此值在set_irte_irq中继续赋值给irte_index字段,但是sub_handle会累加。比如一个设备申请了四个msix中断,这四个msix中断对应的irq_iommu结构的irte_index都相同,sub_handle则是按照msix中断对应的irq的大小从03赋值。

 

■setup_msi_irq函数分析

setup_msi_irq函数的代码片段:

  ret = msi_compose_msg(devirq&msg-1);  

  

  irq_set_msi_desc(irqmsidesc);  

  write_msi_msg(irq&msg);  

  

  if (irq_remapped(irq_get_chip_data(irq)))  

   irq_set_status_flags(irqIRQ_MOVE_PCNTXT);  

   irq_remap_modify_chip_defaults(chip);  

   

  

  irq_set_chip_and_handler_name(irqchiphandle_edge_irq"edge");  

1、 首先看看msi_compose_msg函数。

① 调用assign_irq_vector-à__assign_irq_vector函数,此函数中首先计算vector

for_each_cpu_and(new_cpu, tmp_mask, cpu_online_mask)

per_cpu(vector_irq, new_cpu)[vector] = irq;

cfg->vector = vector;

msix中断对应的irq存入per cpu的数组vector_irq的位置vector的地方。并将vector值赋给cfg->vector,在prepare_irte函数中会将cfg->vector赋值给irte->vector

② 填充msg结构的address_lo、address_hi和data字段。X86架构下对应的intel_compose_msi_msg函数完成此功能。

intel_compose_msi_msg函数首先调用map_irq_to_irte_handle来获取irq对应的irq_iommu结构的sub_handle和irte_index字段。

*sub_handle = irq_iommu->sub_handle;

index = irq_iommu->irte_index;

接着调用prepare_irte函数初始化IRTE结构的低64bitset_msi_sid函数初始化IRTE结构的高64bit

irte->present = 1;

irte->dst_mode = apic->irq_dest_mode;

irte->trigger_mode = 0;

irte->dlvry_mode = apic->irq_delivery_mode;

irte->vector = vector;

irte->dest_id = IRTE_DEST(dest);

irte->redir_hint = 1;

下图是IRTE结构图

msix中断分析_第2张图片

 

dest_id字段决定了哪个cpu响应处理此中断,vector字段决定了此中断在IDT中的位置这里的vector是由cfg->vector传过来的,在前面__assign_irq_vector函数中我们有看到vector相关的设置。

初始化完IRTE结构之后,就调用modify_irte函数将其赋值给对应的irq中断。

index = irq_iommu->irte_index + irq_iommu->sub_handle;

irte = &iommu->ir_table->base[index];

 

msg->address_hi = MSI_ADDR_BASE_HI;

msg->data = sub_handle;

msg->address_lo = MSI_ADDR_BASE_LO | MSI_ADDR_IR_EXT_INT |

MSI_ADDR_IR_SHV |

MSI_ADDR_IR_INDEX1(ir_index) |

MSI_ADDR_IR_INDEX2(ir_index);

irq_iommu->irte_index字段对应msg->address_lo的bit[19:5]irq_iommu->sub_handle字段对应msg->data值。通过这两个值计算此中断在ir_table中的索引,进而得到IRTE结构,然后irte结构的vector字段就可以得到irq中断号了。

msix中断分析_第3张图片

Msg就是MSIX Table中的一个entry,每个entry44字节的成员组成。Data字段赋值为sub_handle,每个设备的多个中断对应的sub_handle值从0开始,连续递增1address_hi字段值赋值为0address_lo字段的值组成如下图所示:

 

bit[31:20]赋值为MSI_ADDR_BASE_LO宏,即是0xfee00000一个固定的值。PCI设备通过向特定地址空间(FSB Interrupt存储器空间)发起一个写操作来发起中断,x86架构下此地址空间是0xfee00000开始的地址空间,其实就是local APIC寄存器映射的地址空间

bit[19:12]是处理中断的目标cpuID号。FSB Interrupt Message总线任务向不同的cpu提交中断请求,目标cpu将接收这个中断请求。

 

2、 调用write_msi_msg函数-à__write_msi_msg函数将msg信息写到此PCI设备的相应BAR空间位置。

base entry->mask_base +  

entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE 

writel(msg->address_lobase + PCI_MSIX_ENTRY_LOWER_ADDR);  

writel(msg->address_hibase + PCI_MSIX_ENTRY_UPPER_ADDR);  

writel(msg->database + PCI_MSIX_ENTRY_DATA);

在前面分析msix_map_region函数时候知道entry->mask_base就是此PCI设备的MSIX TableBAR空间内的偏移位置,PCI_MSIX_ENTRY_SIZE宏为16,对应44字节的成员。entry_nr对应此设备的nvecMSIX中断的从0开始的序列号。

 

3、调用irq_set_chip_and_handler_name函数设置中断流控回调函数handle_edge_irq。Msix中断都是使用边沿触发的。

struct irq_desc *desc = irq_get_desc_buslock(irq&flags0);  

 

desc->handle_irq = handle 

desc->name = name 

到这里初始化流程基本上都完了。

你可能感兴趣的:(Linux内核)