msix中斷分析

此文檔是基於linux-3.6.10內核代碼對msix中斷相關進行分析。

PCIe設備可以使用msix報文向處理器提交中斷,下面首先看下PCIe設備中的MSXI Capability結構。

此結構在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結構圖

 

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中斷號了。

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 

到這裏初始化流程基本上都完了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章