SylixOS申请PCIe设备中断
本来这篇文章是想和读写配置空间放一起讲的,但是梳理了下发现PCIe的中断方面的知识还是比较重要的,因为现在一般的PCIe设备都需要配合中断来进行工作,而PCIe中断又分为INTx中断和MSI中断,如果使用或者设置不当的话,很可能造成设备无法正常使用,所以把中断单独拿出来用一个章节讲解。
1. PCIe中断简单回顾
我们首先来简单回顾下PCIe中断方面的知识,PCIe体系中的中断继承自PCI,分为INTx中断和MSI中断。INTx中断共有四种:INTA、INTB、INTC、INTD,大部分的设备都只使用了INTA作为中断引脚。为了平衡系统中所有设备中断请求负载,会将不同设备的INTx引脚相连到中断控制器的同一个IRQ引脚:
INTx中断是低电平有效,PCIe设备还可以使用MSI和MSI-X方式产生中断,MSI中断本质上就是PCIe设备向某个地址提交一个数据写请求,然后就会触发中断控制器处理这个中断并通知CPU来处理。配置空间中有一段寄存器就是给MSI中断使用的,共有4种格式,我们来看最基础的一种:
其中Message Control区域中有一个Bit位可以控制PCIe设备是否使能MSI中断机制,当使能MSI中断后,INTx中断机制自动失效。Message Address和Message Data具体设置为何值根据不同的平台而不同,比如对于zynq mpsoc平台,Message Address固定为0xFE440000,Message Data为MSI向量号:
2. 申请INTx中断向量号
根据上述信息我们来看看驱动中申请中断的处理:
static int pci_vector_get (int bus, int dev, int func, int msi_en, int line, int pin, void *vector) { return 0; }
前三个入参很好理解,msi_en表示申请的是INTx还是MSI中断,line只在x86平台上有用,这里不需要关心,pin表示如果申请的是INTx中断向量号,PCIe设备使用的是哪个INT引脚,因为不同设备的INTx引脚可能使用不同的中断号,驱动中需要根据不同平台上的中断路由寻找INTx实际对应的中断向量号:
static int pci_vector_get (int bus, int dev, int func, int msi_en, int line, int pin, void *vector) { if (!msi_en) { /* * 根据不同平台上的中断路由寻找INTx引脚实际对应的中断向量号 */ switch (pin) { case 1: break; case 2: break; case 3: break; case 4: break; default: return -1; } } return 0; }
中断号最终就通过最后出参vector返回给调用者。在一个实际的嵌入式系统中,INTx引脚对应的实际中断号可能只有一个,也就是INTA~INTD公用一个中断号,如mpsoc平台:
也可能INTA~INTD各对应一个中断号,比如飞腾2000/4平台:
3. 申请MSI中断向量号
当msi_en为true时,表示上层驱动想申请MSI中断向量号,这时最后一个出参就表示PCI_MSI_DESC 类型的数据结构指针,驱动中需要设置这个数据结构来返回正确的信息,我们来看下这个数据结构:
typedef struct { UINT32 uiAddressLo; /* low 32 bits of address */ UINT32 uiAddressHi; /* high 32 bits of address */ UINT32 uiData; /* 16 bits of msi message data */ } PCI_MSI_MSG; typedef struct { UINT32 PCIMSI_uiNum; ULONG PCIMSI_ulDevIrqVector; PCI_MSI_MSG PCIMSI_pmmMsg; UINT32 PCIMSI_uiMasked; UINT32 PCIMSI_uiMaskPos; PVOID PCIMSI_pvPriv; } PCI_MSI_DESC; typedef PCI_MSI_DESC *PCI_MSI_DESC_HANDLE;
这里介绍其中几个比较重要的成员。
- PCIMSI_uiNum:表示设备驱动想申请多少个向量号,根据标准,MSI支持32个中断向量号,MSI-X支持更多。一般控制器驱动会根据这个值来设置PCIe相关的寄存器来使能MSI向量号对应的中断。
- PCIMSI_ulDevIrqVector:MSI和MSI-X中的向量号只是从0开始的数值,其需要对应实际平台上中断控制器上的中断号,PCIMSI_ulDevIrqVector就是申请成功时,起始的MSI向量号对应的中断号,这个成员需要控制器来设置。
- PCIMSI_pmmMsg:根据我们之前回顾的中断知识可知,这是PCIe设备触发MSI中断的方法,根据不同的平台而不同,需要控制器驱动来进行设置。
在mpsoc平台上,MSI向量号支持0~63共64个,对应的实际中断号为两个,0~31MSI向量号的中断号为146,32~63MSI向量号的中断号为147:
假设在mpsoc平台上申请MSI向量号0,设置应该如下所示:
static int pci_vector_get (int bus, int dev, int func, int msi_en, int line, int pin, void *vector) { PCI_MSI_DESC *msi_desc; if (msi_en) { msi_desc = (PCI_MSI_DESC *)vector; msi_desc->PCIMSI_pmmMsg.uiAddressHi = 0; msi_desc->PCIMSI_pmmMsg.uiAddressLo = 0xFE440000; msi_desc->PCIMSI_pmmMsg.uiData = 0 & 0x1f; msi_desc->PCIMSI_ulDevIrqVector = 146; } return 0; }
INTx和MSI中断申请都处理完之后,用pci_vector_get 来初始化pci_driver_funcs 变量即可:
static PCI_DRV_FUNCS0 pci_driver_funcs = { .cfgRead = pci_cfg_read, .cfgWrite = pci_cfg_write, .irqGet = pci_vector_get, };
附源码
pci_host_driver_demo3.c源码:
#define __SYLIXOS_KERNEL #define __SYLIXOS_PCI_DRV #include <SylixOS.h> #include <module.h> #include <system/device/pci/pciBus.h> #include <system/device/pci/pciMsi.h> #include <system/device/pci/pciLib.h> static PCI_CTRL_CB pci_host; static int pci_cfg_read (int bus, int dev, int func, int offset, int len, void *data) { return 0; } static int pci_cfg_write (int bus, int dev, int func, int offset, int len, unsigned int data) { return 0; } static int pci_vector_get (int bus, int dev, int func, int msi_en, int line, int pin, void *vector) { PCI_MSI_DESC *msi_desc; if (msi_en) { msi_desc = (PCI_MSI_DESC *)vector; /* * 根据不同平台进行设置,这里以mpsoc平台为例 * msi_desc->PCIMSI_pmmMsg.uiAddressHi = 0; * msi_desc->PCIMSI_pmmMsg.uiAddressLo = 0xFE440000; * msi_desc->PCIMSI_pmmMsg.uiData = 0 & 0x1f; * msi_desc->PCIMSI_ulDevIrqVector = 146; */ } else { /* * 根据不同平台上的中断路由寻找INTx实际对应的中断向量号 */ switch (pin) { case 1: break; case 2: break; case 3: break; case 4: break; default: return -1; } } return 0; } static PCI_DRV_FUNCS0 pci_driver_funcs = { .cfgRead = pci_cfg_read, .cfgWrite = pci_cfg_write, .irqGet = pci_vector_get, }; int pci_host_probe (void) { API_PciCtrlCreate(&pci_host); return 0; } int module_init (void) { pci_host_probe(); return 0; } void module_exit (void) { }
2021年3月30日 07:52 1F
看懂了一点,找个机会对照代码在看一下