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 *dev, struct msix_entry *entries, int 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(dev, PCI_CAP_ID_MSIX);  

pci_read_config_word(dev, pos + PCI_MSIX_FLAGS, &control);  

 

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

control &= ~PCI_MSIX_FLAGS_ENABLE;  

pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control);  

 

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

base = msix_map_region(dev, pos, multi_msix_capable(control)); 

if (!base)  

return -ENOMEM;  

ret = msix_setup_entries(dev, pos, base, entries, nvec);  

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(dev, nvec, PCI_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_list, list) {  

   irq = create_irq_nr(irq_want, node);  

   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(dev, irq, nvec);  

    if (index < 0) {  

     ret = index;  

     goto  error;  

    }  

   } else {  

    ret = msi_setup_remapped_irq(dev, irq, index,  

            sub_handle);  

    if (ret < 0)  

     goto  error;  

   }  

 no_ir:  

   ret = setup_msi_irq(dev, msidesc, irq);  

   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 (i = index; i < index + count; i++)  

  if  (table->base[i].present)  

   break;  

 /* empty index found */  

 if (i == index + count)  

  break;  

 index = (index + count) % INTR_REMAP_TABLE_ENTRIES;  

 if (index == start_index) {  

  raw_spin_unlock_irqrestore(&irq_2_ir_lock, flags);  

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

  return -1;  

 }  

} while (1);  

for (i = index; i < index + count; i++)  

 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(dev, irq, &msg, -1);  

  

  irq_set_msi_desc(irq, msidesc);  

  write_msi_msg(irq, &msg);  

  

  if (irq_remapped(irq_get_chip_data(irq))) {  

   irq_set_status_flags(irq, IRQ_MOVE_PCNTXT);  

   irq_remap_modify_chip_defaults(chip);  

  }  

  

  irq_set_chip_and_handler_name(irq, chip, handle_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_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);

在前面分析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, &flags, 0);  

 

desc->handle_irq = handle;  

desc->name = name;  

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

  • 2
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值