PCI 串口

您所在的位置:网站首页 16c550芯片的结构 PCI 串口

PCI 串口

2024-07-12 14:05| 来源: 网络整理| 查看: 265

# 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 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 函数未做过多实现。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3