PCI 串口

更新时间:
2024-01-09
下载文档

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

文档内容是否对您有所帮助?
有帮助
没帮助