PCI 设备驱动模型
SylixOS 提供完整的 PCI 总线驱动,统一的 PCI 设备驱动模型。为了方便开发人员快速学习 PCI 设备驱动的开发,SylixOS 在“libsylixos/SylixOS/system/driver/pci/null”目录下实现了一个简单的 PCI NULL 设备驱动示例,该示例给出了通用的 PCI 设备驱动框架。本小节将以该示例驱动为模板介绍 SyilxOS 的 PCI 设备驱动模型。
SyilxOS 在安装 PCI 主控制驱动程序时,会初始化 PCI 设备管理,同时创建 PCI 设备列表。创建设备列表时,系统遍历 PCI 总线,为所有探测到的 PCI 设备创建设备控制块,并加入到全局设备列表中。完成 PCI 设备创建后,系统会配置 PCI 总线的所有设备,获取所有资源信息。完成上述设备相关的初始化后,系统还将进行 PCI 驱动注册初始化,完成相应资源的申请和全局变量的初始化。
遍历总线时,探测到的每个 PCI 设备都由一个控制块管理,即上文提到的 PCI 设备控制块,该控制块的详细内容如下:
typedef struct {
LW_LIST_LINE PCIDEV_lineDevNode; /* 设备管理节点 */
LW_OBJECT_HANDLE PCIDEV_hDevLock; /* 设备自身操作锁 */
UINT32 PCIDEV_uiDevVersion; /* 设备版本 */
UINT32 PCIDEV_uiUnitNumber; /* 设备编号 */
CHAR PCIDEV_cDevName[PCI_DEV_NAME_MAX];/* 设备名称 */
INT PCIDEV_iDevBus; /* 总线号 */
INT PCIDEV_iDevDevice; /* 设备号 */
INT PCIDEV_iDevFunction; /* 功能号 */
PCI_HDR PCIDEV_phDevHdr; /* 设备头 */
/*
* PCI_HEADER_TYPE_NORMAL PCI_HEADER_TYPE_BRIDGE
* PCI_HEADER_TYPE_CARDBUS
*/
INT PCIDEV_iType; /* 设备类型 */
UINT8 PCIDEV_ucPin; /* 中断引脚 */
UINT8 PCIDEV_ucLine; /* 中断线 */
UINT32 PCIDEV_uiIrq; /* 虚拟中断向量号 */
UINT8 PCIDEV_ucRomBaseReg;
UINT32 PCIDEV_uiResourceNum;
PCI_RESOURCE_CB PCIDEV_tResource[PCI_NUM_RESOURCES]; /* I/O and memory、ROMs */
INT PCIDEV_iDevIrqMsiEn; /* 是否使能 MSI */
ULONG PCIDEV_ulDevIrqVector; /* MSI 或 INTx 中断向量 */
UINT32 PCIDEV_uiDevIrqMsiNum; /* MSI 中断数量 */
PCI_MSI_DESC PCIDEV_pmdDevIrqMsiDesc; /* MSI 中断描述 */
CHAR PCIDEV_cDevIrqName[PCI_DEV_IRQ_NAME_MAX];/* 中断名称 */
PINT_SVR_ROUTINE PCIDEV_pfuncDevIrqHandle; /* 中断服务句柄 */
PVOID PCIDEV_pvDevIrqArg; /* 中断服务参数 */
PVOID PCIDEV_pvDevDriver; /* 驱动句柄 */
} PCI_DEV_CB;
typedef PCI_DEV_CB *PCI_DEV_HANDLE;
所有 PCI 设备控制块通过管理节点形成管理列表,并以总线号、设备号、功能号作为索引。其中 PCIDEV_iType 字段用来标示该设备类型,其可能的取值如下表所示:
设备类型 | 含义 |
---|---|
PCI_HEADER_TYPE_NORMAL | 普通 PCI 设备 |
PCI_HEADER_TYPE_CARDBUS | CardBus 桥设备 |
PCI 设备控制块,只管理 PCI_HEADER_TYPE_NORMAL 类型设备,本文也只介绍普通 PCI 设备,PCI 桥和 CardBus 桥片不做过多介绍。为了保存 PCI 设备的基本配置空间,每个控制块中包含了一个 PCI 标准设备头结构体,该结构体的具体实现如下:
typedef struct {
UINT8 PCIH_ucType; /* PCI 类型 */
/*
* PCI_HEADER_TYPE_NORMAL
* PCI_HEADER_TYPE_BRIDGE
* PCI_HEADER_TYPE_CARDBUS
*/
union {
PCI_DEV_HDR PCIHH_pcidHdr;
PCI_BRG_HDR PCIHH_pcibHdr;
PCI_CBUS_HDR PCIHH_pcicbHdr;
} hdr;
#define PCIH_pcidHdr hdr.PCIHH_pcidHdr
#define PCIH_pcibHdr hdr.PCIHH_pcibHdr
#define PCIH_pcicbHdr hdr.PCIHH_pcicbHdr
} PCI_HDR;
- PCIH_ucType:PCI 类型,即下表所示。
- hdr:设备头结构体。
设备类型 | 含义 |
---|---|
PCI_HEADER_TYPE_NORMAL | 普通 PCI 设备 |
PCI_HEADER_TYPE_CARDBUS | CardBus 桥设备 |
PCI 标准设备头结构体中的 hdr 结构体是一个联合结构体,将根据 PCI 设备的类型,采用不同的 PCI 设备头结构,分别对应着三种不同的基本配置空间,其具体结构与 PCI 基本配置空间基本相同,主要包含厂商 ID、设备 ID 等配置信息,这里不做详细讲解,读者可自行查阅相关代码。
PCI 设备控制块中还有一个十分重要的成员,即资源控制块。在 PCI 设备配置时,系统根据 PCI 设备头,获取 PCI 所有的资源信息,并保存在 PCI 设备资源控制块中。普通 PCI 设备的常见资源包括I/O、memory、IRQ 以及 ROM 资源。该资源结构的详细内容如下所示。
typedef struct {
pci_resource_size_t PCIRS_stStart;
pci_resource_size_t PCIRS_stEnd;
PCHAR PCIRS_pcName;
ULONG PCIRS_ulFlags;
ULONG PCIRS_ulDesc;
} PCI_RESOURCE_CB;
typedef PCI_RESOURCE_CB *PCI_RESOURCE_HANDLE;
- PCIRS_stStart:资源的起始地址,如果为 IRQ 资源,则等于 IRQ 号。
- PCIRS_stEnd:资源结束地址,如果为 IRQ 资源,则等于 IRQ 号。
- PCIRS_pcName: 资源名称。
- PCIRS_ulFlags:资源标志。
- PCIRS_ulDesc:资源描述符。
PCI 设备控制块中除了包含以上内容外,还包含了 PCI 设备的中断信息,包括 INTx 的中断引脚、中断线、虚拟中断向量号,MSI 中断的数量、描述等。关于 INTx、MSI 的具体含义,以及相关结构体在前一小节中已做出说明,此处不再重复。
上述所有内容都是安装 PCI 主控制驱动程序时所涉及到的内容,也就是说 SylixOS 在安装 PCI 设备驱动程序之前就已经管理好了所有的 PCI 设备。当加载 PCI 设备驱动时,系统会尝试绑定设备。SylixOS 中 PCI 设备驱动的注册函数如下:
#include <SylixOS.h>
INT API_PciDrvRegister (PCI_DRV_HANDLE hHandle);
函数 API_PciDrvRegister 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 hHandle 是驱动注册控制句柄。
函数 API_PciDrvRegister 向系统注册了一个 PCI 设备驱动,系统通过一个链表对其进行统一管理。其中传入的驱动控制句柄的详细信息如下:
typedef struct {
LW_LIST_LINE PCIDRV_lineDrvNode; /* 驱动节点管理 */
CHAR PCIDRV_cDrvName[PCI_DRV_NAME_MAX]; /* 驱动名称 */
PVOID PCIDRV_pvPriv; /* 私有数据 */
PCI_DEV_ID_HANDLE PCIDRV_hDrvIdTable; /* 设备支持列表 */
UINT32 PCIDRV_uiDrvIdTableSize; /* 设备支持列表大小 */
/*
* 驱动常用函数, PCIDRV_pfuncDevProbe 与 PCIDRV_pfuncDevRemove 不能为 LW_NULL, * 其它可选
*/
INT (*PCIDRV_pfuncDevProbe)(PCI_DEV_HANDLE hHandle,
const PCI_DEV_ID_HANDLE hIdEntry);
VOID (*PCIDRV_pfuncDevRemove)(PCI_DEV_HANDLE hHandle);
INT (*PCIDRV_pfuncDevSuspend)(PCI_DEV_HANDLE hHandle, PCI_PM_MESSAGE_HANDLE hPmMsg);
INT (*PCIDRV_pfuncDevSuspendLate)(PCI_DEV_HANDLE hHandle, PCI_PM_MESSAGE_HANDLE hPmMsg);
INT (*PCIDRV_pfuncDevResumeEarly)(PCI_DEV_HANDLE hHandle);
INT (*PCIDRV_pfuncDevResume)(PCI_DEV_HANDLE hHandle);
VOID (*PCIDRV_pfuncDevShutdown)(PCI_DEV_HANDLE hHandle);
PCI_ERROR_HANDLE PCIDRV_hDrvErrHandler; /* 错误处理句柄 */
INT PCIDRV_iDrvFlag; /* 驱动标志 */
UINT32 PCIDRV_uiDrvDevNum; /* 关联设备数 */
LW_LIST_LINE_HEADER PCIDRV_plineDrvDevHeader;
/* 设备管理链表头 */
} PCI_DRV_CB;
typedef PCI_DRV_CB *PCI_DRV_HANDLE;
驱动控制块中的 PCIDRV_hDrvIdTable 设备驱动列表中包含了该驱动程序所支持的所有 PCI 设备,驱动加载后系统会用其与所有存在的 PCI 设备进行匹配,匹配成功则进行绑定。驱动支持设备列表控制块的详细内容如下:
typedef struct {
UINT32 PCIDEVID_uiVendor; /* 厂商 ID */
UINT32 PCIDEVID_uiDevice; /* 设备 ID */
UINT32 PCIDEVID_uiSubVendor; /* 子厂商 ID */
UINT32 PCIDEVID_uiSubDevice; /* 子设备 ID */
UINT32 PCIDEVID_uiClass; /* 设备类 */
UINT32 PCIDEVID_uiClassMask; /* 设备子类 */
ULONG PCIDEVID_ulData; /* 设备私有数据 */
} PCI_DEV_ID_CB;
typedef PCI_DEV_ID_CB *PCI_DEV_ID_HANDLE;
设备匹配成功后系统会调用驱动控制块中的 PCIDRV_pfuncDevProbe 函数,因此该函数设备驱动必须实现。相应的,删除设备时,系统会调用驱动控制块中的 PCIDRV_pfuncDevRemove 函数,因此该函数设备驱动也需要完整的实现。
系统在调用 PCIDRV_pfuncDevProbe 函数时,会将匹配到的设备的设备控制块句柄以及设备列表控制块句柄作为参数传入。通常驱动程序需要先获取 PCI 设备的资源信息,如 memory 资源、IO 资源和 IRQ 资源等。SylixOS 中获取 PCI 设备资源信息的函数原型如下:
#include <SylixOS.h>
PCI_RESOURCE_HANDLE API_PciDevResourceGet (PCI_DEV_HANDLE hDevHandle,
UINT uiType,
UINT uiNum);
函数 API_PciDrvRegister 原型分析:
- 此函数成功返回资源句柄,失败返回 LW_NULL 。
- 参数 hDevHandle 是设备句柄。
- 参数 uiType 是资源类型。
- 参数 uiNum 是资源索引。
函数 API_PciDevResourceGet 从已知的设备中获取指定类型和索引的资源,该资源的控制块前文已做过介绍。其中的资源类型如下表所示。
资源类型 | 含义 |
---|---|
PCI_IORESOURCE_IO | IO 资源 |
PCI_IORESOURCE_MEM | Memory 资源 |
PCI_IORESOURCE_REG | Register 资源 |
PCI_IORESOURCE_IRQ | IRQ 资源 |
PCI_IORESOURCE_DMA | DMA 资源 |
PCI_IORESOURCE_BUS | Bus 资源 |
当同一类型资源存在多个时,需要通过索引区分,不同的资源分别索引。值得注意的是,通过上述函数获取到的 IO 和 memory 资源并不能直接使用,因为得到的都是 PCI 总线域中的地址。如果想要正确的使用,必须将 PCI 总线域的地址转换为存储器域的地址。在 SyilxOS 中,默认 PCI 总线域的地址与存储器域的物理地址相等,因此只提供了从物理 IO 空间映射内存到逻辑空间的函数,该函数原型如下:
#include <SylixOS.h>
PVOID API_PciDevIoRemap (PVOID pvPhysicalAddr, size_t stSize);
函数 API_PciDevIoRemap 原型分析:
- 此函数成功返回虚拟地址,失败返回 LW_NULL 。
- 参数 pvphysicalAddr 是物理内存地址。
- 参数 stSize 是需要映射的内存大小。
进行重新映射后,驱动程序便可以正常使用 PCI 设备的资源。PCIDRV_pfuncDevProbe 函数在获取了相关资源后,便可进行设备相关的操作。对于 PCI 设备的中断相关操作,如 INTx 和 MSI,前面小节已经进行过相关说明。对于 PCI 设备中断的绑定和使能,SylixOS 提供了专门的API接口如下:
#include <SylixOS.h>
INT API_PciDevInterConnect (PCI_DEV_HANDLE hHandle,
ULONG ulVector,
PINT_SVR_ROUTINE pfuncIsr,
PVOID pvArg,
CPCHAR pcName);
函数 API_PciDevInterConnect 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 hHandle 是设备句柄。
- 参数 ulVector 是中断向量。
- 参数 pfuncIsr 是中断服务函数。
- 参数 pvArg 是中断服务函数参数。
- 参数 pcName 是中断服务名称。
#include <SylixOS.h>
INT API_PciDevInterEnable (PCI_DEV_HANDLE hHandle,
ULONG ulVector,
PINT_SVR_ROUTINE pfuncIsr,
PVOID pvArg);
函数 API_PciDevInterEnable 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 hHandle 是设备句柄。
- 参数 ulVector 是中断向量。
- 参数 pfuncIsr 是中断服务函数。
- 参数 pvArg 是中断服务函数参数。
与此相对应的,SyilxOS 提供了对应的 PCI 设备解除中断连接函数和 PCI 设备禁能中断函数,函数原型如下:
#include <SylixOS.h>
INT API_PciDevInterDisonnect (PCI_DEV_HANDLE hHandle,
ULONG ulVector,
PINT_SVR_ROUTINE pfuncIsr,
PVOID pvArg);
函数 API_PciDevInterDisonnect 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 hHandle 是设备句柄。
- 参数 ulVector 是中断向量。
- 参数 pfuncIsr 是中断服务函数。
- 参数 pvArg 是中断服务函数参数。
#include <SylixOS.h>
INT API_PciDevInterDisable (PCI_DEV_HANDLE hHandle,
ULONG ulVector,
PINT_SVR_ROUTINE pfuncIsr,
PVOID pvArg);
函数 API_PciDevInterDisable 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 hHandle 是设备句柄。
- 参数 ulVector 是中断向量。
- 参数 pfuncIsr 是中断服务函数。
- 参数 pvArg 是中断服务函数参数。
当指定 PCI 驱动删除一个设备时,程序会调用驱动控制块中 PCIDRV_pfuncDevRemove 函数,驱动程序需要在该函数中进行删除前的设备操作,以及各种资源的释放。
以上主要是驱动注册时的相关内容,SylixOS 还提供对应的驱动卸载函数,其函数原型如下:
#include <SylixOS.h>
INT API_PciDrvDelete (PCI_DRV_HANDLE hDrvHandle)
函数 API_PciDrvDelete 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 hDrvHandle 是驱动控制块句柄。
注意:
当 PCI 驱动属于活跃状态或还存在关联设备时,将无法卸载,该函数返回错误。