SylixOS PCIe控制器驱动使用自动配置功能
1. 地址空间简单回顾
自动配置主要就是配置各个设备BAR空间的PCIe地址,所以我们简单回顾下PCIe地址和CPU物理地址的概念。
PCIe控制器和PCIe设备之间通信都是使用的PCIe地址,PCIe地址分为32位和64位,32位只能使用4GB地址空间,64位可以使用超过4GB地址空间。PCIe设备配置空间中BAR寄存器中记录的就是PCIe空间地址,这个地址是Bootloader或者操作系统在启动的过程中分配的。
BAR寄存器中记录的地址其实就是PCIe设备寄存器的基地址,设置好这个基地址后,CPU这时还不能直接去访问,因为CPU访问所使用的是物理地址,而PCIe体系中都是通过PCIe地址来访问的,所以还需要设置PCIe控制器中的地址翻译单元(ATU),将CPU的物理地址和PCIe设备使用的PCIe地址建立映射。大部分情况下,Bootloader或者操作系统都会为这两种地址建立一一映射的关系,所以从数值上来看,这两种地址的值是一样的。在不同的平台中,在CPU的物理地址域中专门有一段空间是留给PCIe控制器建立映射使用的,比如飞腾2000/4平台:
PCIe体系中,地址空间分为三种:配置空间用于访问设备的配置空间寄存器;IO和MEM空间用于访问设备的寄存器。其中IO空间只是为了兼容以前老的PCI设备而保留的,现在大部分PCIe设备都通过MEM方式访问寄存器。
2. Uboot下查看映射关系
一般情况下,PCIe控制器的电源、时钟、地址映射关系等初始化Bootloader都会做好,x86下是BIOS来做的,龙芯下是PMON来做的,飞腾下是Uboot下来做的,设备的枚举和BAR地址分配Bootloader也会做好。
大部分情况下,SylixOS为了简化控制器驱动工作是不再做上述这些工作的,但是在某些情况下可能需要SylixOS重新分配设备的BAR地址,这时就需要使能控制器驱动中的自动配置功能,使用自动配置之前,我们需要知道Bootloader对CPU地址和PCIe地址的映射关系,这个一般只能通过阅读Bootloader源码来了解,但是如果系统使用了Uboot来引导,并且使能了Uboot中PCI相关命令后,可以通过pci region 这个命令来查看UBoot中建立的映射关系:
其中MEM空间根据PCIe标准可以分为支持预取和不支持预取两种,图中的Bus就是指PCIe地址,Phys就是指CPU物理地址。我们可以看出mem和mem prefetch两段空间是一一映射的,IO空间的PCIe地址是从0开始的,其对应的CPU物理地址是从0x50000000开始的,总共15MB。
3. SylixOS使用自动配置
SylixOS下通过PCI_AUTO_CB 这个数据结构来记录自动配置的信息,通过初始化这个成员我们就可以使能自动配置功能:
PCI_AUTO_HANDLE pci_auto_cfg; pci_auto_cfg = &pci_host.PCI_tAutoConfig; pci_auto_cfg->PCIAUTO_iConfigEn = LW_TRUE; pci_auto_cfg->PCIAUTO_iHostBridegCfgEn = LW_TRUE; pci_auto_cfg->PCIAUTO_uiFirstBusNo = 0; pci_auto_cfg->PCIAUTO_uiLastBusNo = PCI_MAX_BUS - 1; pci_auto_cfg->PCIAUTO_uiCurrentBusNo = 0; pci_auto_cfg->PCIAUTO_ucCacheLineSize = PCI_AUTO_CACHE_LINE_SIZE; pci_auto_cfg->PCIAUTO_ucLatencyTimer = PCI_AUTO_LATENCY_TIMER; pci_auto_cfg->PCIAUTO_pfuncDevFixup = LW_NULL; pci_auto_cfg->PCIAUTO_pfuncDevIrqFixup = LW_NULL; pci_auto_cfg->PCIAUTO_pvPriv = LW_NULL;
- PCIAUTO_iConfigEn:是否使能自动配置功能。
- PCIAUTO_iHostBridegCfgEn:是否使能主桥自动配置功能。
- PCIAUTO_uiFirstBusNo:使用自动配置的起始总线号,一般为0。
- PCIAUTO_uiLastBusNo:使用自动配置的最后一个总线号,一般为255。
- PCIAUTO_uiCurrentBusNo:当前总线号,一般为0。
- PCIAUTO_ucCacheLineSize:PCIe高速缓冲大小,一般设置为PCI_AUTO_CACHE_LINE_SIZE。
- PCIAUTO_ucLatencyTimer:时间相关参数,一般设置为PCI_AUTO_LATENCY_TIMER。
- PCIAUTO_pfuncDevFixup:修正回调函数,一般为NULL。
- PCIAUTO_pfuncDevIrqFixup:中断修正回调函数,一般为NULL。
- PCIAUTO_pvPriv:私有数据,根据实际需要设置。
通过上面的初始化我们使能了自动配置的功能,除此之外,我们还需要告诉系统各个PCIe空间起始地址和大小,以让系统自动配置时为各个设备的BAR空间分配地址。在SylixOS下,这是通过API_PciAutoCtrlRegionSet 接口来实现的,这个接口的函数原型如下:
LW_API INT API_PciAutoCtrlRegionSet(PCI_CTRL_HANDLE hCtrl, UINT uiIndex, pci_bus_addr_t addrBusStart, pci_addr_t addrPhyStart, pci_size_t stSize, ULONG ulFlags);
- hCtrl:PCIe控制器数据结构。
- uiIndex:各段PCIe地址空间的索引号。
- addrBusStart:PCIe地址起始值。
- addrPhyStart:PCIe地址对应的CPU物理地址值,这个成员目前在SylixOS下没有使用。
- stSize:PCIe空间大小。
- ulFlags:PCIe空间属性,比如时IO还是MEM空间,支持不支持预取等。
注意:目前SylxiOS不支持处理MEM空间PCIe地址和CPU物理地址不一一映射的情况,PCIe设备驱动通过系统接口只能拿到BAR空间的PCIe地址,如果不是一一映射,访问时还需要手动将PCIe地址转换成物理地址以进行访问。
以飞腾2000/4平台来说,通过Uboot下查看映射关系,我们就可以在SylixOS下如下设置IO空间,PCI_AUTO_REGION_IO 宏标明设置的是IO空间:
#define PCI_IO_REGION_BASE (0x0) #define PCI_IO_SIZE (15 * LW_CFG_MB_SIZE) API_PciAutoCtrlRegionSet(&pci_host, PCI_AUTO_REGION_INDEX_0, PCI_IO_REGION_BASE, PCI_IO_REGION_BASE, PCI_IO_SIZE, PCI_AUTO_REGION_IO);
同样,不支持预取功能的MEM空间设置如下,PCI_AUTO_REGION_MEM宏标明设置的是非预取MEM空间:
#define PCI_MEM_NONPRE_BASE (0x0000000058000000) #define PCI_NONPRE_SIZE (640 * LW_CFG_MB_SIZE) API_PciAutoCtrlRegionSet(&pci_host, PCI_AUTO_REGION_INDEX_1, PCI_MEM_NONPRE_BASE, PCI_MEM_NONPRE_BASE, PCI_NONPRE_SIZE, PCI_AUTO_REGION_MEM);
如果需要设置预取MEM空间,则标志上还需加上PCI_AUTO_REGION_PREFETCH这个宏,如下所示:
#define PCI_MEM_PRE_BASE (0x0000000100000000) #define PCI_PRE_SIZE (64 * LW_CFG_GB_SIZE) API_PciAutoCtrlRegionSet(&pci_host, PCI_AUTO_REGION_INDEX_2, PCI_MEM_PRE_BASE, PCI_MEM_PRE_BASE, PCI_PRE_SIZE, PCI_AUTO_REGION_MEM | PCI_AUTO_REGION_PREFETCH);
最后将要自动配置的空间个数赋值给PCIAUTO_uiRegionCount这个成员:
pci_auto_cfg->PCIAUTO_uiRegionCount = 3;
另外,针对IO空间我们还需要额外做一些设置,因为我们知道IO空间现在是非一一映射的,我们需要在驱动中定义个全局变量__IO_BASE__,并将IO空间的起始物理地址经过MMU映射后的虚拟地址赋值给这个变量:
ioaddr_t __IO_BASE__ = (ioaddr_t)0x50000000ul;
一般在SylixOS启动过程中可以将IO空间的物理地址和虚拟地址一一映射,这样就可以直接将物理地址值赋值给__IO_BASE__变量了。
如果PCIe设备使用IO空间对寄存器访问,那么在设备驱动中,我们就必须使用in8、out8这类接口来访问IO空间:
#define in8(a) read8(__IO_BASE__ + a) #define in16(a) read16(__IO_BASE__ + a) #define in32(a) read32(__IO_BASE__ + a) #define in64(a) read64(__IO_BASE__ + a) #define out8(d, a) write8(d, __IO_BASE__ + a) #define out16(d, a) write16(d, __IO_BASE__ + a) #define out32(d, a) write32(d, __IO_BASE__ + a) #define out64(d, a) write64(d, __IO_BASE__ + a)
至此,我们就完成了自动配置的相关设置。
附源码
pci_host_driver_demo5.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> #define PCI_CFG_BASE (0x0000000040000000) #define PCI_IO_REGION_BASE (0x0) #define PCI_MEM_NONPRE_BASE (0x0000000058000000) #define PCI_MEM_PRE_BASE (0x0000000100000000) #define PCI_CFG_SIZE (256 * LW_CFG_MB_SIZE) #define PCI_IO_SIZE (15 * LW_CFG_MB_SIZE) #define PCI_NONPRE_SIZE (640 * LW_CFG_MB_SIZE) #define PCI_PRE_SIZE (64 * LW_CFG_GB_SIZE) ioaddr_t __IO_BASE__ = (ioaddr_t)0x50000000ul; static PCI_CTRL_CB pci_host; static int pci_cfg_read (int bus, int dev, int func, int offset, int line, void *data) { return 0; } static int pci_cfg_write (int bus, int dev, int func, int offset, int line, 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) { pci_host.PCI_iIndex = 0; pci_host.PCI_iBusMax = PCI_MAX_BUS; pci_host.PCI_pDrvFuncs0 = &pci_driver_funcs; pci_host.PCI_ucMechanism = PCI_MECHANISM_0; pci_host.PCI_pvPriv = LW_NULL; PCI_AUTO_HANDLE pci_auto_cfg; pci_auto_cfg = &pci_host.PCI_tAutoConfig; pci_auto_cfg->PCIAUTO_iConfigEn = LW_TRUE; pci_auto_cfg->PCIAUTO_iHostBridegCfgEn = LW_TRUE; pci_auto_cfg->PCIAUTO_uiFirstBusNo = 0; pci_auto_cfg->PCIAUTO_uiLastBusNo = PCI_MAX_BUS - 1; pci_auto_cfg->PCIAUTO_uiCurrentBusNo = 0; pci_auto_cfg->PCIAUTO_ucCacheLineSize = PCI_AUTO_CACHE_LINE_SIZE; pci_auto_cfg->PCIAUTO_ucLatencyTimer = PCI_AUTO_LATENCY_TIMER; pci_auto_cfg->PCIAUTO_pfuncDevFixup = LW_NULL; pci_auto_cfg->PCIAUTO_pfuncDevIrqFixup = LW_NULL; pci_auto_cfg->PCIAUTO_pvPriv = LW_NULL; API_PciAutoCtrlRegionSet(&pci_host, PCI_AUTO_REGION_INDEX_0, PCI_IO_REGION_BASE, PCI_IO_REGION_BASE, PCI_IO_SIZE, PCI_AUTO_REGION_IO); API_PciAutoCtrlRegionSet(&pci_host, PCI_AUTO_REGION_INDEX_1, PCI_MEM_NONPRE_BASE, PCI_MEM_NONPRE_BASE, PCI_NONPRE_SIZE, PCI_AUTO_REGION_MEM); API_PciAutoCtrlRegionSet(&pci_host, PCI_AUTO_REGION_INDEX_2, PCI_MEM_PRE_BASE, PCI_MEM_PRE_BASE, PCI_PRE_SIZE, PCI_AUTO_REGION_MEM | PCI_AUTO_REGION_PREFETCH); pci_auto_cfg->PCIAUTO_uiRegionCount = 3; API_PciCtrlCreate(&pci_host); return 0; } int module_init (void) { pci_host_probe(); return 0; } void module_exit (void) { }
2021年3月29日 19:36 1F
你明天能不能调休?