sylixos pci

13 篇文章 5 订阅

1.pci基地址获取

pci的基地址在x86平台是通过acpi获得的,acpi全称是(Advanced Configuration and Power Management Interface)高级配置和电源管理接口,在ACPI规范文档3.0中 https://download.csdn.net/download/smartzhen2003/981664可以看到如下图

 

 

可以看到如果想看MCFG结构需要查看PCI Firmware Specification Revison 3.0 版本文档。在PCI Firmware Specification Revison 3.0文档中查看MCFG 硬件描述符,定义如下:

前面是个固定的信息,在偏移44是 PCI基地址结构的物理地址,具体结构如下图Table4-3:

在sylixos中Table4-2和4-3 对应的结构体体如下

 

在x86 bsp中driver文件夹中的pic.c文件中。以上可以看出想知道PCI操作的基地址首先要知道MCFG地址。

SylixOS 获取MCFG是在acpi 初始化函数中。在x86 bsp中会调用x86AcpiInit函数

 

在acpiTableInit函数中会查找RSDP。

在ACPI中包含各种描述表管理处理器系统的部分硬件信息,而且包含与这些硬件相关的操作,并使用了RSDP指针(Root System Description Point)指向这些描述表。ACPI规范定义了以下描述表:

  • XSDT(Extended System Description Table). XSDT 包含了ACPI规范的版本号和一些与OEM相关的信息,并含有其他描述表的64位物理地址。如FADT和SSDT等。
  • RSDT(Root System Description Table)。 RSDT包含的信息与XSDT一致,只是在RSDT中存放的物理地址为32位的。在V1.0版本后的ACPI版本中,该描述符表被XSDT驱动。但是有些BIOS可能会为系统同时提供RSDT和XSDT,并且有操作系统选择使用那一个。
  • FADT(Fixed ACPI Description Table)包含了ACPI寄存器组使用的系统I/O端口地址,FACS(Firmware ACPI Control Structure)和DSDT(Different加特点 System Description Table)的基地址等信息。
  • FACS 包含了OS 与BIOS 进行数据交换使用的一些参数,包含处理器系统的硬件签名,一节Firmware在处理器系统被唤醒后使用的,用来通知操作系统Firmware工作已经告一段落的中断向量。
  • DSDT 包含了处理器系统使用的硬件资源以及对这些硬件资源的管理操作。SSDT可以对DSDT进行补充,在一个处理器中可以存在多个SSDT。

还包括MADT,SRAT,SLIT等描述表。

在RSDP中提供了两个物理地址分别指向RSDT和XSDT。如下图

所以在代码中首先要找到RSDP,RSDP描述表可以在ACPI规范中找到,如下:

Signature 是名称,查找RSDP时使用比对“RSD PTR”名称,相符就是RSDP的地址。

Checksum是校验和,在ACPI1.0中的只检验前20个字节。

OEMID是厂商提供的字符串

Revision 是版本号

RsdtAddress 是RSDT 32位物理地址,与上面讲到RSDP指向RSDT和XSDT相对应。

Length是整个表的长度。

Xsdt addresss shi XSDT 64 位物理地址长度。

Extended Checksum 是整个表的校验和。

 

 

SylixOS下RSDP结构体如下:

sylixo通过以下函数查找

acpiScan函数就是在固定的地址强制转换成RSDP结构体,判断Signature是否是“RSD PTR”

如果是就找到RSDP的地址,如果不是增加地址,不断的查找比对。

 

在查找到RSDP后就可以选择使用RSDT还是XSDT。如果通过RSDT或者XSDT获得其他表的地址。

原理和查找RSDP相似,在ACPI中 RSDT ,XSDT, FADT,FACS,MCFG等都有公用header,如下

以使用RSDT为例整个表的长度将去表header大小,剩下的大小除以32字节就是整个RSDT中包含表的数量,在SylixoS对应的代码如下:

第532行就是计算表的数量,然后下面是根据每个表Signature 名称比对是那个表然后将地址填充到系统变量保存。

 

SylixoS中获取MCFG基地址在一个单独的函数中,但是与上面原理一致,都是首先查找当前RSDP然后获得RSDT或者XSDT,计算出RSDT或者XSDT中标表的数量后比对Signature名称。源代码如下:

/*********************************************************************************************************
** 函数名称: acpiGetMcfg
** 功能描述: 从 ACPI 表获得 MCFG 地址
** 输 入  : NONE
** 输 出  : MCFG 地址或 0
** 全局变量:
** 调用模块:
*********************************************************************************************************/
ULONG  acpiGetMcfg (VOID)
{
    ACPI_TABLE_RSDP   *pRsdp;
    ACPI_TABLE_HEADER *pHeader = LW_NULL;
    ACPI_TABLE_RSDT   *pRsdt;
    ACPI_TABLE_XSDT   *pXsdt;
    INT                iTableEntriesNr, i;
 
    pRsdp = acpiFindRsdp();
    if (pRsdp == LW_NULL) {
        return  (0);
    }
 
    /*
     * There are two possible table pointers
     */
 
    /*
     * If we have a 32 bit long address table
     */
    if (_G_pAcpiRsdt != LW_NULL) {
        pRsdt = AcpiOsMapMemory((ACPI_PHYSICAL_ADDRESS)_G_pAcpiRsdt, sizeof(ACPI_TABLE_RSDT));
        if (pRsdt == LW_NULL) {
            return  (0);
        }
 
        /*
         * Compute number of tables
         */
        iTableEntriesNr = (INT)((pRsdt->Header.Length - sizeof(ACPI_TABLE_HEADER)) / sizeof(UINT32));
 
        /*
         * We need to verify the length
         */
        if (iTableEntriesNr >= 0) {
            for (i = 0; i < iTableEntriesNr; i++) {
                pHeader = AcpiOsMapMemory((ACPI_PHYSICAL_ADDRESS)pRsdt->TableOffsetEntry[i],
                                          sizeof(ACPI_TABLE_HEADER));
                if (pHeader == LW_NULL) {
                    continue;
                }
 
                if (ACPI_NAME_COMPARE(pHeader->Signature, "MCFG")) {
                    AcpiOsUnmapMemory(pRsdt, sizeof(ACPI_TABLE_RSDT));
                    return  ((ULONG)pHeader);
                }
            }
 
            /*
             * Unmap header for both 32-bit and 64-bit if not found
             */
            AcpiOsUnmapMemory(pHeader, sizeof(ACPI_TABLE_HEADER));
        }
        AcpiOsUnmapMemory(pRsdt, sizeof(ACPI_TABLE_RSDT));
    }
 
    /*
     * If we have a 64 bit long address table
     */
    if (_G_pAcpiXsdt != LW_NULL) {
        pXsdt = AcpiOsMapMemory((ACPI_PHYSICAL_ADDRESS)_G_pAcpiXsdt, sizeof(ACPI_TABLE_XSDT));
        if (pXsdt == LW_NULL) {
            return  (0);
        }
 
        /*
         * Compute number of tables
         */
        iTableEntriesNr = (INT)((pXsdt->Header.Length - sizeof(ACPI_TABLE_HEADER)) / sizeof(UINT64));
        if (iTableEntriesNr >= 0) {
            for (i = 0; i < iTableEntriesNr; i++) {
                pHeader = AcpiOsMapMemory((ACPI_PHYSICAL_ADDRESS)pXsdt->TableOffsetEntry[i],
                                          sizeof(ACPI_TABLE_HEADER));
                if (pHeader == LW_NULL) {
                    continue;
                }
 
                if (ACPI_NAME_COMPARE(pHeader->Signature, "MCFG")) {
                    AcpiOsUnmapMemory(pXsdt, sizeof(ACPI_TABLE_XSDT));
                    return  ((ULONG)pHeader);
                }
            }
            AcpiOsUnmapMemory(pHeader, sizeof(ACPI_TABLE_HEADER));
        }
        AcpiOsUnmapMemory(pXsdt, sizeof(ACPI_TABLE_XSDT));
    }
 
    return  (0);
}

 

查找到的地址赋值给全局变量 _G_ulAcpiMcfgBaseAddress。

    /*
     * Init PCIE ECAM base addess
     */
    _G_ulAcpiMcfgBaseAddress = acpiGetMcfg();

到此获取基地址过程就结束了。

2.MSI驱动注册

VOID  pciDrv (VOID)
{
    static PCI_DRV_FUNCS12  x86PciDrvFuncs = {                          /*  PCI 驱动函数                */
        .ioInByte   = in8,
        .ioInWord   = in16,
        .ioInDword  = in32,
        .ioOutByte  = out8,
        .ioOutWord  = out16,
        .ioOutDword = out32,
        .irqGet     = __pciIrqGet,
        .mmCfgRead  = __pciMmCfgRead,
        .mmCfgWrite = __pciMmCfgWrite,
    };
 
    if (bspIntModeGet() != X86_INT_MODE_PIC) {
        LW_SPIN_INIT(&_G_slX86MsiVector);
        __pciMsiIrqPoolInit();
    }
 
    lib_bzero(&_G_x86PciDrv, sizeof(PCI_CTRL_CB));
 
    _G_x86PciDrv.PCI_iBusMax      = PCI_MAX_BUS;
    _G_x86PciDrv.PCI_pDrvFuncs12  = &x86PciDrvFuncs;
    _G_x86PciDrv.PCI_ucMechanism  = PCI_MECHANISM_1;
    _G_x86PciDrv.PCI_ulConfigAddr = PCI_CONFIG_ADDR;
    _G_x86PciDrv.PCI_ulConfigData = PCI_CONFIG_DATA;
 
    /*
     * Advancing PCI Platform Interface PCI Firmware Specification 3.0
     */
    if (_G_ulAcpiMcfgBaseAddress) {
        ACPI_MCFG        *pPcieMcfg = (ACPI_MCFG *)_G_ulAcpiMcfgBaseAddress;
        ACPI_MCFG_ENTRY  *pPcieMcfgEntry;
 
        pPcieMcfgEntry = (ACPI_MCFG_ENTRY *)((addr_t)pPcieMcfg + sizeof(ACPI_MCFG));
 
        _G_ulX86PciMcfgVirtAddr = (addr_t)API_VmmIoRemapNocache2(
                                            (phys_addr_t)pPcieMcfgEntry->BaseAddress,
                                            ((pPcieMcfgEntry->EndBus - pPcieMcfgEntry->StartBus) + 1) *
                                            PCI_MAX_SLOTS * PCI_MAX_FUNCTIONS * MCFG_CFG_SPACE_SIZE);
        if (!_G_ulX86PciMcfgVirtAddr) {
            _PrintFormat("PCI driver failed to ioremap!\r\n");
        }
    }
 
    API_PciCtrlCreate(&_G_x86PciDrv);
}
 
 

在PCI驱动函数中提供了in和out函数,正常的x86输入,输出函数。也提供了mcfg的读写函数,函数会再下面解释。

PCI_iBusMax 指定了最大总线数。PCI规定最大总线不能超过256,所以这里最大填写256。

PCI_ulConfigAddr和PCI_ulConfigData 是x86 pci提供地址和数据操作的地址CF8和CFC。具体可以下面文档介绍。大体意思就是有两种配置方式,传统的in,out只能访问前256字节,要想访问256-4096的使用CF8和CFC地址操作。

第35行是跳转到PCI条目,在第一章中MCFG描述符中提到的

 

获得end bus和 start bus ,最后将计算出地址映射成程序可以操作的虚拟地址。

 

在MCFG 条目中有个PCI Segment Group Number ,这个是PCIE的扩展。Segment Group Number 最大当时65535,bus总线由原来最大的256扩展到16777216,参考链接https://wiki.osdev.org/PCI_Express

以下是截图

由于普通的桌面电脑基本不会超过256,所以默认Segment Group Number 默认是0,SylixOS默认只映射第一个MCFG 条目。

 

在第11行注册了MCFG读函数,由于普通in和out只能访问配置空间的256个字节。MCFG读函数会根据总线号,端口号,设备号,裸机功能号等算出需要操作的地址。SylixOS读函数代码如下:

/*********************************************************************************************************
** 函数名称: __pciMmCfgAccess
** 功能描述: MCFG 访问
** 输 入  : iBus          总线
**           iSlot         槽
**           iFunc         功能
**           iOft          偏移量
**           iLen          长度
**           pvData        数据
**           bRead         是否为读
** 输 出  : ERROR or OK
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static INT  __pciMmCfgAccess (INT iBus, INT iSlot, INT iFunc, INT iOft, INT iLen, PVOID pvData, BOOL bRead)
{
    ACPI_MCFG       *pPcieMcfg = (ACPI_MCFG *)_G_ulAcpiMcfgBaseAddress;
    ACPI_MCFG_ENTRY *pPcieMcfgEntry;
    addr_t           ulCfgAddr;
    INT              i;
 
    if (!_G_ulX86PciMcfgVirtAddr) {
        return  (PX_ERROR);
    }
 
    for (i = 0; i < (pPcieMcfg->Length - sizeof(ACPI_MCFG)) / sizeof(ACPI_MCFG_ENTRY); i++) {
        pPcieMcfgEntry = (ACPI_MCFG_ENTRY *)((ULONG) pPcieMcfg + sizeof(ACPI_MCFG) + i * sizeof(ACPI_MCFG_ENTRY));
        if ((iBus >= pPcieMcfgEntry->StartBus) && (iBus <= pPcieMcfgEntry->EndBus)) {
            break;
        }
    }
 
    if (i == (pPcieMcfg->Length - sizeof(ACPI_MCFG)) / sizeof(ACPI_MCFG_ENTRY)) {
        return  (PX_ERROR);
    }
 
    ulCfgAddr = _G_ulX86PciMcfgVirtAddr +
                (((iBus << MCFG_BUS_SHIFT)  & MCFG_BUS_MASK)  |
                ((iSlot << MCFG_SLOT_SHIFT) & MCFG_SLOT_MASK) |
                ((iFunc << MCFG_FUNC_SHIFT) & MCFG_FUNC_MASK) |
                 (iOft & MCFG_OFFSET_MASK));
 
    switch (iLen) {
 
    case 4:
        if (bRead) {
            *(UINT32 *)pvData    = *(UINT32 *)ulCfgAddr;
        } else {
            *(UINT32 *)ulCfgAddr = *(UINT32 *)pvData;
        }
        break;
 
    case 2:
        if (bRead) {
            *(UINT16 *)pvData    = *(UINT16 *)ulCfgAddr;
        } else {
            *(UINT16 *)ulCfgAddr = *(UINT16 *)pvData;
        }
        break;
 
    case 1:
        if (bRead) {
            *(UINT8 *)pvData    = *(UINT8 *)ulCfgAddr;
        } else {
            *(UINT8 *)ulCfgAddr = *(UINT8 *)pvData;
        }
        break;
 
    default:
        return  (PX_ERROR);
    }
 
    return  (ERROR_NONE);
}

由于读函数总线可能是很大的数,这里是使用PCI段(Segment )概念,当前 段没有总线后会查后面的条目。在SylixOS中只映射了第一个条目的虚拟地址,在桌面电脑上一般不会超过256条总线。

组成访问地址组成如下图:

 

对应代码中的37行。总线号左移20位,端口号左移15位,裸机设备号左移12位。mcfg写函数与读函数原理相同。

完成读写函数后调用 API_PciCtrlCreate(&_G_x86PciDrv);注册驱动。

API_PciCtrlCreate(&_G_x86PciDrv);函数首先会对需要的信号量,锁等初始化。

LW_API 
PCI_CTRL_HANDLE  API_PciCtrlCreate (PCI_CTRL_HANDLE hCtrl)
{
    if (PCI_CTRL == LW_NULL) {
        PCI_CTRL = hCtrl;
 
        LW_SPIN_INIT(&PCI_CTRL->PCI_slLock);
 
        API_PciDevInit();
        API_PciDevListCreate();
 
        /*
         *  需要在设备列表创建完成之后再进行自动配置
         *  因为部分平台配置寄存器的读写需要依赖父节点的索引参数
         */
        API_PciAutoScan(hCtrl, (UINT32 *)&hCtrl->PCI_iBusMax);
 
#if LW_CFG_PROCFS_EN > 0
        __procFsPciInit();
#endif                                                                  /*  LW_CFG_PROCFS_EN > 0        */
 
        API_PciDevSetupAll();
        API_PciDrvInit();
 
        API_TShellKeywordAdd("pcictrl", __tshellPciCmdCtrl);
        API_TShellHelpAdd("pcictrl", "show control table\n"
                                     "eg. pcictrl\n");
    }
 
    return  (PCI_CTRL) ? (PCI_CTRL) : (LW_NULL);
}

第9行就是对应初始化相关的函数。第10行会遍历整个pci设备,并将设备加入到链表中。

LW_API
INT  API_PciDevListCreate (VOID)
{
    API_PciTraversal(__pciDevListCreate, LW_NULL, PCI_MAX_BUS - 1);
 
    return  (ERROR_NONE);
}
/**********

list创建函数如上,__pciDevListCreate函数是往链表中加入设备函数。

 
    hDevHandle = API_PciDevHandleGet(iBus, iDevice, iFunction);         /* 获取设备句柄                 */
    if (hDevHandle != LW_NULL) {
        return  (hDevHandle);
    }
 
    lib_bzero(&phPciHdr, sizeof(PCI_HDR));
    API_PciGetHeader(iBus, iDevice, iFunction, &phPciHdr);              /* 获取设备头信息               */
    iType = phPciHdr.PCIH_ucType & PCI_HEADER_TYPE_MASK;                /* 获取设备类型                 */
    if ((iType != PCI_HEADER_TYPE_NORMAL) &&
        (iType != PCI_HEADER_TYPE_BRIDGE) &&
        (iType != PCI_HEADER_TYPE_CARDBUS)) {                           /* 设备类型错误                 */
        return  (LW_NULL);
    }
 
    hDevHandle = (PCI_DEV_HANDLE)__SHEAP_ZALLOC(sizeof(PCI_DEV_CB));    /* 分配设备控制块               */
    if (hDevHandle == LW_NULL) {
        _ErrorHandle(ERROR_SYSTEM_LOW_MEMORY);
        return  (LW_NULL);
    }
    /*
     *  保存设备参数
     */
    hDevHandle->PCIDEV_iDevBus      = iBus;
    hDevHandle->PCIDEV_iDevDevice   = iDevice;
    hDevHandle->PCIDEV_iDevFunction = iFunction;
    lib_memcpy(&hDevHandle->PCIDEV_phDevHdr, &phPciHdr, sizeof(PCI_HDR));
 
    switch (iType) {
 
    case PCI_HEADER_TYPE_NORMAL:
        hDevHandle->PCIDEV_iType  = PCI_HEADER_TYPE_NORMAL;
        hDevHandle->PCIDEV_ucPin  = hDevHandle->PCIDEV_phDevHdr.PCIH_pcidHdr.PCID_ucIntPin;
        hDevHandle->PCIDEV_ucLine = hDevHandle->PCIDEV_phDevHdr.PCIH_pcidHdr.PCID_ucIntLine;
        break;
 
    case PCI_HEADER_TYPE_BRIDGE:
        hDevHandle->PCIDEV_iType  = PCI_HEADER_TYPE_BRIDGE;
        hDevHandle->PCIDEV_ucPin  = hDevHandle->PCIDEV_phDevHdr.PCIH_pcibHdr.PCIB_ucIntPin;
        hDevHandle->PCIDEV_ucLine = hDevHandle->PCIDEV_phDevHdr.PCIH_pcibHdr.PCIB_ucIntLine;
        break;
 
    case PCI_HEADER_TYPE_CARDBUS:
        hDevHandle->PCIDEV_iType  = PCI_HEADER_TYPE_CARDBUS;
        hDevHandle->PCIDEV_ucPin  = hDevHandle->PCIDEV_phDevHdr.PCIH_pcicbHdr.PCICB_ucIntPin;
        hDevHandle->PCIDEV_ucLine = hDevHandle->PCIDEV_phDevHdr.PCIH_pcicbHdr.PCICB_ucIntLine;
        break;
 
    default:
        __SHEAP_FREE(hDevHandle);
        return  (LW_NULL);
    }
 
    __PCI_DEV_LOCK();                                                   /*  锁定 PCI 驱动               */
    _List_Line_Add_Tail(&hDevHandle->PCIDEV_lineDevNode, &_GplinePciDevHeader);
    _GuiPciDevTotalNum += 1;

这个函数的主要功能是判断当前链表中是否含有相同的总线,端口,逻辑设备号的设备,如果有直接退出,如果没有获取pci设备空间的信息,根据类型赋值,然后将设备加入到链表中,然后计数增加1.

在第8行是根据 API_PciGetHeader(iBus, iDevice, iFunction, &phPciHdr); /* 获取设备头信息 */获取头部信息的。

 
    API_PciConfigInByte(iBus, iSlot, iFunc, PCI_HEADER_TYPE, &p_pcihdr->PCIH_ucType);
    p_pcihdr->PCIH_ucType &= PCI_HEADER_TYPE_MASK;
 
    if (p_pcihdr->PCIH_ucType == PCI_HEADER_TYPE_NORMAL) {              /* PCI iSlot                   */
        API_PciConfigInWord( iBus, iSlot, iFunc, PCI_VENDOR_ID,       &PCI_D.PCID_usVendorId);
        API_PciConfigInWord( iBus, iSlot, iFunc, PCI_DEVICE_ID,       &PCI_D.PCID_usDeviceId);
        API_PciConfigInWord( iBus, iSlot, iFunc, PCI_COMMAND,         &PCI_D.PCID_usCommand);

此函数主要是调用Byte和Word读函数,这两个函数都是对之前mcfg读函数和in函数的封装,只是读回多少位的区别,函数的第四个参数是pci设备(包含设备和桥)配置空间的偏移值 ,例如PCI_VENDOR_ID宏定义是0. 对应的就是配置空间第0个字, 设备和桥关系如下图:

PCI桥作为一个特殊的PCI设备,具有独立的配置空间。但是PCI桥配置空间的定义与PCI Agent设备有所不同。PCI桥的配置空间可以管理其下PCI总线子树的PCI设备,并可以优化这些PCI设备通过PCI桥的数据访问。PCI桥的配置空间在系统软件遍历PCI总线树时配置,系统软件不需要专门的驱动程序设置PCI桥的使用方法,这也是PCI桥被称为透明桥的主要原因。

 

在某些处理器系统中,还有一类PCI桥,叫做非透明桥。非透明桥不是PCI总线定义的标准桥片,但是在使用PCI总线挂接另外一个处理器系统时非常有用,非透明桥片的主要作用是连接两个不同的PCI总线域,进而连接两个处理器系统,本章将在第2.5节中详细介绍PCI非透明桥。

 

使用PCI桥可以扩展出新的PCI总线,在这条PCI总线上还可以继续挂接多个PCI设备。PCI桥跨接在两个PCI总线之间,其中距离HOST主桥较近的PCI总线被称为该桥片上游总线(Primary Bus),距离HOST主桥较远的PCI总线被称为该桥片的下游总线(Secondary Bus)。如图2‑8所示,PCI桥1的上游总线为PCI总线x0,而PCI桥1的下游总线为PCI总线x1。这两条总线间的数据通信需要通过PCI桥1。

 

通过PCI桥连接的PCI总线属于同一个PCI总线域,在图2‑8中,PCI桥1、2和3连接的PCI总线都属于PCI总线x域。在这些PCI总线域上的设备可以通过PCI桥直接进行数据交换而不需要进行地址转换;而分属不同PCI总线域的设备间的通信需要进行地址转换,如与PCI非透明桥两端连接的设备之间的通信。

 

如图2‑8所示,每一个PCI总线的下方都可以挂接一个到多个PCI桥,每一个PCI桥都可以推出一条新的PCI总线。在同一条PCI总线上的设备之间的数据交换不会影响其他PCI总线。如PCI设备21与PCI设备22之间的数据通信仅占用PCI总线x2的带宽,而不会影响PCI总线x0、x1与x3,这也是引入PCI桥的另一个重要原因。

 

由图2‑8我们还可以发现PCI总线可以通过PCI桥组成一个胖树结构,其中每一个桥片都是父节点,而PCI Agent设备只能是子节点。当PCI桥出现故障时,其下的设备不能将数据传递给上游总线,但是并不影响PCI桥下游设备间的通信。当PCI桥1出现故障时,PCI设备11、PCI设备21和PCI设备22将不能与PCI设备01和存储器进行通信,但是PCI设备21和PCI设备22之间的通信可以正常进行。

 

使用PCI桥可以扩展一条新的PCI总线,但是不能扩展新的PCI总线域。如果当前系统使用32位的PCI总线地址,那么这个系统的PCI总线域的地址空间为4GB大小,在这个总线域上的所有设备将共享这个4GB大小的空间。如在PCI总线x域上的PCI桥1、PCI设备01、PCI设备11、PCI桥2、PCI设备21和PCI设备22等都将共享一个4GB大小的空间。再次强调这个4GB空间是PCI总线x域的“PCI总线地址空间”,和存储器域地址空间和PCI总线y域没有直接联系。

配置空间结构如下:

在PCI Agent设备配置空间中包含的寄存器如下所示。

(1) Device ID和Vendor ID寄存器

 

这两个寄存器的值由PCISIG分配,只读。其中Vendor ID代表PCI设备的生产厂商,而Device ID代表这个厂商所生产的具体设备。如Intel公司的基于82571EB芯片的系列网卡,其Vendor ID为0x8086[1],而Device ID为0x105E[2]。

 

(2) Revision ID和Class Code寄存器

 

这两个寄存器只读。其中Revision ID寄存器记载PCI设备的版本号。该寄存器可以被认为是Device ID寄存器的扩展。

 

(3) Header Type寄存器

 

该寄存器只读,由8位组成。

 

第7位为1表示当前PCI设备是多功能设备,为0表示为单功能设备。

第6~0位表示当前配置空间的类型,为0表示该设备使用PCI Agent设备的配置空间,普通PCI设备都使用这种配置头;为1表示使用PCI桥的配置空间,PCI桥使用这种配置头;为2表示使用Cardbus桥片的配置空间,Card Bus桥片使用这种配置头,本篇对这类配置头不感兴趣。

系统软件需要使用该寄存器区分不同类型的PCI配置空间,该寄存器的初始化必须与PCI设备的实际情况对应,而且必须为一个合法值。

 

(4) Cache Line Size寄存器

 

该寄存器记录HOST处理器使用的Cache行长度。在PCI总线中和Cache相关的总线事务,如存储器写并无效和Cache多行读等总线事务需要使用这个寄存器。值得注意的是,该寄存器由系统软件设置,但是在PCI设备的运行过程中,只有其硬件逻辑才会使用该寄存器,比如PCI设备的硬件逻辑需要得知处理器系统Cache行的大小,才能进行存储器写并无效总线事务,单行读和多行读总线事务。

 

如果PCI设备不支持与Cache相关的总线事务,系统软件可以不设置该寄存器,此时该寄存器为初始值0x00。对于PCIe设备,该寄存器的值无意义,因为PCIe设备在进行数据传送时,在其报文中含有一次数据传送的大小,PCIe总线控制器可以使用这个“大小”,判断数据区域与Cache行的对应关系。

 

(5) Subsystem ID和Subsystem Vendor ID寄存器

 

这两个寄存器和Device ID和Vendor ID类似,也是记录PCI设备的生产厂商和设备名称。但是这两个寄存器和Device ID与Vendor ID寄存器略有不同。下文以一个实例说明Subsystem ID和Subsystem Vendor ID的用途。

 

Xilinx公司在FGPA中集成了一个PCIe总线接口的IP核,即LogiCORE。用户可以使用LogiCORE设计各种各样基于PCIe总线的设备,但是这些设备的Device ID都是0x10EE,而Vendor ID为0x0007[3]。

 

(6) Expansion ROM base address寄存器

 

有些PCI设备在处理器还没有运行操作系统之前,就需要完成基本的初始化设置,比如显卡、键盘和硬盘等设备。为了实现这个“预先执行”功能,PCI设备需要提供一段ROM程序,而处理器在初始化过程中将运行这段ROM程序,初始化这些PCI设备。Expansion ROM base address记载这段ROM程序的基地址。

 

(7) Capabilities Pointer寄存器

 

在PCI设备中,该寄存器是可选的,但是在PCI-X和PCIe设备中必须支持这个寄存器,Capabilities Pointer寄存器存放Capabilities寄存器组的基地址,PCI设备使用Capabilities寄存器组存放一些与PCI设备相关的扩展配置信息。该组寄存器的详细说明见第4.3节。

 

(8) Interrupt Line寄存器

 

这个寄存器是系统软件对PCI设备进行配置时写入的,该寄存器记录当前PCI设备使用的中断向量号,设备驱动程序可以通过这个寄存器,判断当前PCI设备使用处理器系统中的哪个中断向量号,并将驱动程序的中断服务例程注册到操作系统中[4]。

 

该寄存器由系统软件初始化,其保存的值与8259A中断控制器相关,该寄存器的值也是由PCI设备与8259A中断控制器的连接关系决定的。如果在一个处理器系统中,没有使用8259A中断控制器管理PCI设备的中断,则该寄存器中的数据并没有意义。

 

在多数PowerPC处理器系统中,并不使用8259A中断控制器管理PCI设备的中断请求,因此该寄存器没有意义。即使在x86处理器系统中,如果使用I/O APIC中断控制器,该寄存器保存的内容仍然无效。目前在绝大多数处理器系统中,并没有使用该寄存器存放PCI设备使用的中断向量号。

 

(9) Interrupt Pin寄存器

 

这个寄存器保存PCI设备使用的中断引脚,PCI总线提供了四个中断引脚INTA#、INTB#、INTC#和INTD#。Interrupt Pin寄存器为1时表示使用INTA#引脚向中断控制器提交中断请求,为2表示使用INTB#,为3表示使用INTC#,为4表示使用INTD#。

 

如果PCI设备只有一个子设备时,该设备只能使用INTA#;如果有多个子设备时,可以使用INTB~D#信号。如果PCI设备不使用这些中断引脚,向处理器提交中断请求时,该寄存器的值必须为0。值得注意的是,虽然在PCIe设备中并不含有INTA~D#信号,但是依然可以使用该寄存器,因为PCIe设备可以使用INTx中断消息,模拟PCI设备的INTA~D#信号,详见第6.3.4节。

 

(10) Base Address Register 0~5寄存器

 

该组寄存器简称为BAR寄存器,BAR寄存器保存PCI设备使用的地址空间的基地址,该基地址保存的是该设备在PCI总线域中的地址。其中每一个设备最多可以有6个基址空间,但多数设备不会使用这么多组地址空间。

 

在PCI设备复位之后,该寄存器将存放PCI设备需要使用的基址空间大小,这段空间是I/O空间还是存储器空间[5],如果是存储器空间该空间是否可预取,有关PCI总线预读机制的详细说明见第3.4.5节。

 

系统软件对PCI总线进行配置时,首先获得BAR寄存器中的初始化信息,之后根据处理器系统的配置,将合理的基地址写入相应的BAR寄存器中。系统软件还可以使用该寄存器,获得PCI设备使用的BAR空间的长度,其方法是向BAR寄存器写入0xFFFF-FFFF,之后再读取该寄存器。

 

处理器访问PCI设备的BAR空间时,需要使用BAR寄存器提供的基地址。值得注意的是,处理器使用存储器域的地址,而BAR寄存器存放PCI总线域的地址。因此处理器系统并不能直接使用“BAR寄存器+偏移”的方式访问PCI设备的寄存器空间,而需要将PCI总线域的地址转换为存储器域的地址。

 

如果x86处理器系统使能了IOMMU后,这两个地址也并不一定相等,因此处理器系统直接使用这个PCI总线域的物理地址,并不能确保访问PCI设备的BAR空间的正确性。除此之外在sylioss系统中,ioremap函数的输入参数为存储器域的物理地址,而不能使用PCI总线域的物理地址。

 

因此在编写SylixOS系统的设备驱动程序时,需要使用bar的物理地址,然后再经过ioremap函数将物理地址转换为“存储器域”的虚拟地址。

两个图对应IO和MMIO

(11) Command寄存器

 

该寄存器为PCI设备的命令寄存器,该寄存器在初始化时,其值为0,此时这个PCI设备除了能够接收配置请求总线事务之外,不能接收任何存储器或者I/O请求。系统软件需要合理设置该寄存器之后,才能访问该设备的存储器或者I/O空间。在Linux系统中,设备驱动程序调用pci_enable_device函数,使能该寄存器的I/O和Memory Space位之后,才能访问该设备的存储器或者I/O地址空间。

(12) Status寄存器

 

该寄存器的绝大多数位都是只读位,保存PCI设备的状态。

 

 

桥的配置空间如下:

与PCI Agent设备不同,在PCI桥中只含有两组BAR寄存器,Base Address Register 0~1寄存器。这两组寄存器与PCI Agent设备配置空间的对应寄存器的含义一致。但是在PCI桥中,这两个寄存器是可选的。如果在PCI桥中不存在私有寄存器,那么可以不使用这组寄存器设置BAR空间。

在大多数PCI桥中都不存在私有寄存器,操作系统也不需要为PCI桥提供专门的驱动程序,这也是这类桥被称为透明桥的原因。如果在PCI桥中不存在私有空间时,PCI桥将这两个BAR寄存器初始化为0。在PCI桥的配置空间中使用两个BAR寄存器的目的是这两个32位的寄存器可以组成一个64位地址空间。

 

在PCI桥的配置空间中,有许多寄存器是PCI桥所特有的。PCI桥除了作为PCI设备之外,还需要管理其下连接的PCI总线子树使用的各类资源,即Secondary Bus所连接PCI总线子树使用的资源。这些资源包括存储器、I/O地址空间和总线号。

 

在PCI桥中,与Secondary bus相关的寄存器包括两大类。一类寄存器管理Secondary Bus之下PCI子树的总线号,如Secondary和Subordinate Bus Number寄存器;另一类寄存器管理下游PCI总线的I/O和存储器地址空间,如I/O和Memory Limit、I/O和Memory Base寄存器。在PCI桥中还使用Primary Bus寄存器保存上游的PCI总线号。

 

其中存储器地址空间还分为可预读空间和不可预读空间,Prefetchable Memory Limit和Prefetchable Memory Base寄存器管理可预读空间,而Memory Limit、Memory Base管理不可预读空间。在PCI体系结构中,除了了ROM地址空间之外,PCI设备使用的地址空间大多都是不可预读的。

 

(1) Subordinate Bus Number、Secondary Bus Number和Primary Bus Number寄存器

 

PCI桥可以管理其下的PCI总线子树。其中Subordinate Bus Number寄存器存放当前PCI子树中,编号最大的PCI总线号。而Secondary Bus Number寄存器存放当前PCI桥Secondary Bus使用的总线号,这个PCI总线号也是该PCI桥管理的PCI子树中编号最小的PCI总线号。因此一个PCI桥能够管理的PCI总线号在Secondary Bus Number~Subordinate Bus Number之间。这两个寄存器的值由系统软件遍历PCI总线树时设置。

 

Primary Bus Number寄存器存放该PCI桥上游的PCI总线号,该寄存器可读写。Primary Bus Number、Subordinate Bus Number和Secondary Bus Number寄存器在初始化时必须为0,系统软件将根据这几个寄存器是否为0,判断PCI桥是否被配置过。

 

不同的操作系统使用不同的Bootloader引导,有的Bootloader可能会对PCI总线树进行遍历,此时操作系统可以不再重新遍历PCI总线树。在x86处理器系统中,BIOS会遍历处理器系统中的所有PCI总线树,操作系统可以直接使用BIOS的结果,也可以重新遍历PCI总线树。而PowerPC处理器系统中的Bootloader,如U-Boot并没有完全遍历PCI总线树,此时操作系统必须重新遍历PCI总线树。

 

(2) Secondary Status寄存器

 

该寄存器的含义与PCI Agent配置空间的Status寄存器的含义相近,PCI桥的Secondary Status寄存器记录Secondary Bus的状态,而不是PCI桥作为PCI设备时使用的状态。在PCI桥配置空间中还存在一个Status寄存器,该寄存器保存PCI桥作为PCI设备时的状态。

 

(3) Secondary Latency Timer寄存器

 

该寄存器的含义与PCI Agent配置空间的Latency Timer寄存器的含义相近,PCI桥的Secondary Latency Timer寄存器管理Secondary Bus的超时机制,即PCI桥发向下游的总线事务;在PCI桥配置空间中还存在一个Latency Timer寄存器,该寄存器管理PCI桥发向上游的总线事务。

 

(4) I/O Limit和I/O Base寄存器

 

在PCI桥管理的PCI子树中包含许多PCI设备,而这些PCI设备可能会使用I/O地址空间。PCI桥使用这两个寄存器,存放PCI子树中所有设备使用的I/O地址空间集合的基地址和大小。

 

(5) Memory Limit和Memory Base寄存器

 

在PCI桥管理的PCI子树中有许多PCI设备,这些PCI设备可能会使用存储器地址空间。这两个寄存器存放所有这些PCI设备使用的,存储器地址空间集合的基地址和大小,PCI桥规定这个空间的大小至少为1MB。

 

(6) Prefetchable Memory Limit和Prefetchable Memory Base寄存器

 

在PCI桥管理的PCI子树中有许多PCI设备,如果这些PCI设备支持预读,则需要从PCI桥的可预读空间中获取地址空间。PCI桥的这两个寄存器存放这些PCI设备使用的,可预取存储器空间的基地址和大小。

 

如果PCI桥不支持预读,则其下支持预读的PCI设备需要从Memory Base寄存器为基地址的存储器空间中获取地址空间。如果PCI桥支持预读,其下的PCI设备需要根据情况,决定使用可预读空间,还是不可预读空间。PCI总线建议PCI设备支持预读,但是支持预读的PCI设备并不多见。

 

(7) I/O Base Upper 16 Bits and I/O Limit Upper 16寄存器

 

如果PCI桥仅支持16位的I/O端口,这组寄存器只读,且其值为0。如果PCI桥支持32位I/O端口,这组寄存器可以提供I/O端口的高16位地址。

 

(8) Bridge Control Register。

 

该寄存器用来管理PCI桥的Secondary Bus,其主要位的描述如下。

 

Secondary Bus Reset位,第6位,可读写。当该位为1时,将使用下游总线提供的RST#信号复位与PCI桥的下游总线连接的PCI设备。通常情况下与PCI桥下游总线连接的PCI设备,其复位信号需要与PCI桥提供的RST#信号连接,而不能与HOST主桥提供的RST#信号连接。

Primary Discard Timer位,第8位,可读写。PCI桥支持Delayed传送方式,当PCI桥的Primary总线上的主设备使用Delayed方式进行数据传递时,PCI桥使用Retry周期结束Primary总线的Non-Posted数据请求,并将这个Non-Posted数据请求转换为Delayed数据请求,之后主设备需要择时重试相同的Non-Posted数据请求。当该位为1时,表示在Primary Bus上的主设备需要在210个时钟周期之内重试这个数据请求,为0时,表示主设备需要在215个时钟周期之内重试这个数据请求,否则PCI桥将丢弃Delayed数据请求。

Secondary Discard Timer位,第9位,可读写。当该位为1时,表示在Secondary Bus上的主设备需要在210个时钟周期之内重试这个数据请求,为0时,表示主设备需要在215个时钟周期之内重试这个数据请求,如果主设备在规定的时间内没有进行重试时,PCI桥将丢弃Delayed数据请求。

 

因为配置空间稍有不同,所以读取整个配置空间信息时要根据是桥还是设备对类型判断采取不同读取方式和偏移值。x86平台上通过总线,端口,逻辑设备号,偏移组成32位地址,硬件就能转换成对应的地址去读写数据。

 

API_PciTraversal 是根据总线,端口,逻辑设备号三个组成嵌套的for循环去挨个遍历设备,发现设备厂商不为0后调用上面的添加函数往设备链表中添加数据。

for (iBus = 0; iBus <= iMaxBusNum; iBus++) {
        for (iSlot = 0; iSlot < PCI_MAX_SLOTS; iSlot++) {
            for (iFunc = 0; iFunc < PCI_MAX_FUNCTIONS; iFunc++) {
 
                API_PciConfigInWord(iBus, iSlot, iFunc, PCI_VENDOR_ID, &usVendorTemp);
 
                if (PCI_VENDOR_ID_IS_INVALIDATE(usVendorTemp)) {        /*  没有设备存在                */
                    if (iFunc == 0) {
                        break;                                          /*  next slot                   */
                    }
                    continue;                                           /*  next function               */
                }
 
                if (pfuncCall(iBus, iSlot, iFunc, pvArg) != ERROR_NONE) {
                    goto    __out;
                }
 
                if (iFunc == 0) {
                    API_PciConfigInByte(iBus, iSlot, iFunc, PCI_HEADER_TYPE, &ucHeader);
 
                    if ((ucHeader & PCI_HEADER_MULTI_FUNC) != PCI_HEADER_MULTI_FUNC) {
                        break;
                    }

到此设备枚举已经完成。

 

参考《PCI+EXPRESS体系结构导读》

参考网址:https://wiki.osdev.org/PCI_Express

参考《ACPIspec30b.doc》

参考《pcifw_r3.0.pdf》

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值