PCI 串口
PCI 串口主要是用来扩展 PC 的串口数量和种类。常见的 x86 机器主板上可能只有一个或者两个 RS232 的串口接口,在某些场景下不能满足对串口的需求,于是就有了 PCI 串口卡。通常 PCI 串口卡会支持拓展 2 至 8 个串口,也会支持 RS232、RS485、RS422 这几种串口格式。PCI 串口卡在工业和军工领域应用广泛。
SylixOS 在“libsylixos/SylixOS/driver/sio”目录下提供了两种不同厂商的 PCI 串口卡驱动,此两种驱动的设备都使用了 16C550 兼容的串口芯片。常见的 PCI 串口卡都是使用 16C550 这一系列的串口芯片。本文以 NETMOS 的 PCI 串口卡为例,说明 SyliOS 下 PCI 设备驱动的一般开发流程。
如前文所述,PCI 驱动加载后通过驱动支持设备列表进行绑定,因此驱动程序需要正确的提供本驱动所支持的所有设备 ID 信息。NETMOS PCI 串口卡驱动中关于支持设备列表的具体实现如下所示。
static const PCI_DEV_ID_CB pciSioNetmosIdTbl[] = {
{
PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9901,
0xa000, 0x1000, 0, 0,
netmos_9912
},
{
PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9912,
0xa000, 0x1000, 0, 0,
netmos_9912
},
{
PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9922,
0xa000, 0x1000, 0, 0,
netmos_9912
},
{
PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9904,
0xa000, 0x1000, 0, 0,
netmos_9912
},
{
PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9900,
0xa000, 0x1000, 0, 0,
netmos_9912
},
{
} /* terminate list */
};
在确定支持列表后,PCI 设备驱动程序需要实现自己的驱动控制块,并向系统注册。驱动控制块除了要包含支持设备列表外,还要实现该设备的一系列操作函数。其中 PCIDRV_pfuncDevProbe 函数和 PCIDRV_pfuncDevRemove 函数必须实现,其他函数可根据具体设备的需求具体实现。NETMOS PCI 串口驱动相关初始化代码如下:
INT pciSioNetmosInit (VOID)
{
INT iRet;
PCI_DRV_CB tPciDrv;
PCI_DRV_HANDLE hPciDrv = &tPciDrv;
lib_bzero(hPciDrv, sizeof(PCI_DRV_CB));
iRet = pciSioNetmosIdTblGet(&hPciDrv->PCIDRV_hDrvIdTable, &hPciDrv->PCIDRV_uiDrvIdTableSize);
if (iRet != ERROR_NONE) {
return (PX_ERROR);
}
lib_strlcpy(&hPciDrv->PCIDRV_cDrvName[0], "pci_netmos", PCI_DRV_NAME_MAX);
hPciDrv->PCIDRV_pvPriv = LW_NULL; /* 设备驱动的私有数据 */
hPciDrv->PCIDRV_hDrvErrHandler = LW_NULL; /* 驱动错误处理 */
hPciDrv->PCIDRV_pfuncDevProbe = pciSioNetmosProbe;
hPciDrv->PCIDRV_pfuncDevRemove = pciSioNetmosRemove;
iRet = API_PciDrvRegister(hPciDrv);
if (iRet != ERROR_NONE) {
return (PX_ERROR);
}
return (ERROR_NONE);
}
PCIDRV_pfuncDevProbe 函数是驱动探测到设备后进行绑定时所调用,不同 PCI 设备的具体实现逻辑最终都要由此函数展开,是 PCI 设备驱动的核心内容。本例中的 NETMOS PCI 串口卡,在通过设备资源获取函数获取到 memory 资源和 IRQ 资源后,经过简单的 memory 资源映射,就可以和普通 16C550 芯片一样操作。本文主要讲解 PCI 设备驱动的开发方法,16C550 驱动的具体实现不做过多的说明。关于串口的相关内容和 SylixOS 中串口驱动模型可参考第十一章。NETMOS PCI 串口卡驱动的具体实现如下:
static INT pciSioNetmosProbe (PCI_DEV_HANDLE hPciDevHandle, const PCI_DEV_ID_HANDLE hIdEntry)
{
INT i, iChanNum, iTtyNum;
PCI_SIO_NETMOS *pcisio;
PCI_SIO_NETMOS_CFG *pcisiocfg;
SIO16C550_CHAN *psiochan;
SIO_CHAN *psio;
ULONG ulVector;
CHAR cDevName[64];
PCI_RESOURCE_HANDLE hResource;
addr_t ulBaseAddr; /* 起始地址 */
PVOID pvBaseAddr; /* 起始地址 */
size_t stBaseSize; /* 资源大小 */
if ((!hPciDevHandle) || (!hIdEntry)) {
_ErrorHandle(EINVAL);
return (PX_ERROR);
}
if (hIdEntry->PCIDEVID_ulData > ARRAY_SIZE(pciSioNetmosCard)) {
_ErrorHandle(EINVAL);
return (PX_ERROR);
}
hResource = API_PciDevResourceGet(hPciDevHandle, PCI_IORESOURCE_MEM, 0);
ulBaseAddr = (ULONG)(PCI_RESOURCE_START(hResource));
/* 获取 MEM 的起始地址 */
stBaseSize = (size_t)(PCI_RESOURCE_SIZE(hResource));
/* 获取 MEM 的大小 */
pvBaseAddr = API_PciDevIoRemap((PVOID)ulBaseAddr, stBaseSize);
if (!pvBaseAddr) {
return (PX_ERROR);
}
pcisio = &pciSioNetmosCard[hIdEntry->PCIDEVID_ulData];
iChanNum = pcisio->NETMOS_uiPorts; /* 获得设备通道数 */
hResource = API_PciDevResourceGet(hPciDevHandle, PCI_IORESOURCE_IRQ, 0);
ulVector = (ULONG)PCI_RESOURCE_START(hResource);
API_PciDevMasterEnable(hPciDevHandle, LW_TRUE);
write32(0, (addr_t)pvBaseAddr + 0x3fc);
/*
* 创建串口通道
*/
for (i = 0; i < iChanNum; ++i) {
psiochan = (SIO16C550_CHAN *)__SHEAP_ZALLOC(sizeof(SIO16C550_CHAN));
if (!psiochan) {
_ErrorHandle(ENOMEM);
return (PX_ERROR);
}
pcisiocfg = (PCI_SIO_NETMOS_CFG*)__SHEAP_ZALLOC(sizeof(PCI_SIO_NETMOS_CFG));
if (!pcisiocfg) {
__SHEAP_FREE(psiochan);
_ErrorHandle(ENOMEM);
return (PX_ERROR);
}
pcisiocfg->CFG_idx = hPciDevHandle->PCIDEV_iDevFunction;
pcisiocfg->CFG_ulVector = ulVector;
pcisiocfg->CFG_ulBase = (addr_t)pvBaseAddr;
pcisiocfg->CFG_ulBaud = pcisio->NETMOS_uiBaud;
pcisiocfg->CFG_ulXtal = pcisio->NETMOS_uiBaud * 16;
pcisiocfg->CFG_pciHandle = hPciDevHandle;
psio = pciSioNetmosChan(hPciDevHandle->PCIDEV_iDevFunction, psiochan, pcisiocfg);
for (iTtyNum = 0; iTtyNum < 512; iTtyNum++) {
snprintf(cDevName, sizeof(cDevName),
PCI_SIO_NETMOS_TTY_PERFIX "%d", iTtyNum);
if (!API_IosDevMatchFull(cDevName)) {
break;
}
}
ttyDevCreate(cDevName, psio,
PCI_SIO_NETMOS_TTY_RBUF_SZ,
PCI_SIO_NETMOS_TTY_SBUF_SZ); /* add tty device */
}
return (ERROR_NONE);
}
NETMOS PCI 串口卡有时需要作为主设备,因此需使能其 Master 模式。SyliOS 中设置使能主模式的函数原型如下:
#include <SylixOS.h>
INT API_PciDevMasterEnable (PCI_DEV_HANDLE hDevHandle, BOOL bEnable);
函数 API_PciDevMasterEnable 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 hHandle 是设备控制句柄。
- 参数 iEnable 是使能与禁能标志,0 为禁能,1 为使能。
NETMOS PCI 串口驱动获取到存储器资源和中断自后后,将进行串口相关的驱动设置。在创建 SIO 通道时,16C550 兼容的串口驱动需要封装统一的读写寄存器接口。NETMOS PCI 串口驱动的写寄存器接口具体实现如下:
static VOID pciSioNetmosSetReg (SIO16C550_CHAN *psiochan,
INT iReg,
UINT8 ucValue)
{
REGISTER PCI_SIO_NETMOS_CFG *pcisiocfg = (PCI_SIO_NETMOS_CFG *)
psiochan->priv;
write8(ucValue, pcisiocfg->CFG_ulBase + 0x280 + ((addr_t)iReg * 4));
}
NETMOS PCI 串口驱动的读寄存器接口具体实现如下:
static UINT8 pciSioNetmosGetReg (SIO16C550_CHAN *psiochan, INT iReg)
{
REGISTER PCI_SIO_NETMOS_CFG *pcisiocfg = (PCI_SIO_NETMOS_CFG *)
psiochan->priv;
return (read8(pcisiocfg->CFG_ulBase + 0x280 + ((addr_t)iReg * 4)));
}
完成以上的接口封装后,NETMOS PCI 串口驱动便可调用 16C550 兼容串口驱动的初始化函数进行设备初始化和中断绑定、使能操作,其具体实现如下:
static SIO_CHAN *pciSioNetmosChan (UINT uiChannel,
SIO16C550_CHAN *psiochan,
PCI_SIO_NETMOS_CFG *pcisiocfg)
{
CHAR cIrqName[64];
psiochan->pdeferq = API_InterDeferGet(0);
/*
* Receiver FIFO Trigger Level and Tirgger Bytes table
* level 16 Bytes FIFO Trigger 32 Bytes FIFO Trigger 64 Bytes FIFO Trigger
* 0 1 8 1
* 1 4 16 16
* 2 8 24 32
* 3 14 28 56
*/
psiochan->fifo_len = 8;
psiochan->rx_trigger_level = 1;
psiochan->baud = pcisiocfg->CFG_ulBaud;
psiochan->xtal = pcisiocfg->CFG_ulXtal;
psiochan->setreg = pciSioNetmosSetReg;
psiochan->getreg = pciSioNetmosGetReg;
psiochan->priv = pcisiocfg;
API_PciDevInterDisable(pcisiocfg->CFG_pciHandle, pcisiocfg->CFG_ulVector,
(PINT_SVR_ROUTINE)pciSioNetmosIsr,
(PVOID)psiochan);
sio16c550Init(psiochan);
snprintf(cIrqName, sizeof(cIrqName), "pci_netmos_%d", uiChannel);
API_PciDevInterConnect(pcisiocfg->CFG_pciHandle, pcisiocfg->CFG_ulVector,
(PINT_SVR_ROUTINE)pciSioNetmosIsr,
(PVOID)psiochan, cIrqName);
API_PciDevInterEnable pcisiocfg->CFG_pciHandle, pcisiocfg->CFG_ulVector,
(PINT_SVR_ROUTINE)pciSioNetmosIsr,
(PVOID)psiochan);
return ((SIO_CHAN *)psiochan);
}
NETMOS PCI 串口驱动的中断处理函数在清除中断后,仍然调用 16C550 兼容串口驱动的通用中断处理函数,其具体实现如下:
static irqreturn_t pciSioNetmosIsr (PVOID pvArg, ULONG ulVector)
{
REGISTER SIO16C550_CHAN *psiochan = (SIO16C550_CHAN *)pvArg;
UINT8 ucIIR;
ucIIR = pciSioNetmosGetReg(psiochan, IIR);
if (ucIIR & 0x01) {
return (LW_IRQ_NONE);
}
sio16c550Isr(psiochan);
return (LW_IRQ_HANDLED);
}
以上便是 NETMOS PCI 串口驱动的核心内容,而控制块的 PCIDRV_pfuncDevRemove 函数未做过多实现。