在Sylixos 下串口被封装成一个字符设备;
1,我们可以看到内核库源码ty目录下,是内核封装好的字符设备,在开发时我们只要完成相关具体的io操作及回调函数的填充即可:
2,tty 设备的实现:
基本流程,(1)安装驱动操作函数; (2)实现SIO_CHAN 结构体中的回调函数供tty 设备函数进行回调使用,创建 tty 设备,创建收发缓冲区;
(3)需要我们实现 SIO_DRV_FUNCS 中ioctl()、txStartup、callbackInstall 、pollInput、pollOutput这些函数以及在驱动中实现中断函数的注册;
ttyDrv == API_TtyDrvInstall // 安装TTY设备驱动程序
LW_API
INT API_TtyDrvInstall (VOID)
{
if (_G_iTycoDrvNum > 0) {
return (ERROR_NONE);
}
_G_iTycoDrvNum = iosDrvInstall(_ttyOpen,
(FUNCPTR)LW_NULL,
_ttyOpen,
_ttyClose,
_TyRead,
_TyWrite,
_ttyIoctl);
DRIVER_LICENSE(_G_iTycoDrvNum, "GPL->Ver 2.0");
DRIVER_AUTHOR(_G_iTycoDrvNum, "Han.hui");
DRIVER_DESCRIPTION(_G_iTycoDrvNum, "tty driver.");
return ((_G_iTycoDrvNum > 0) ? (ERROR_NONE) : (PX_ERROR));
}
通过以上代码可知tty 设备使用的相关io 操作的接口;
psio0 = sioChanCreate(BSP_CFG_DBG_UART_CH); /* 创建串口通道,该部分需要由驱动开发着实现 */
ttyDevCreate("/dev/ttyS0", psio0, 30, 50); /* add tty device */
// 该函数创建一个 ttyS0 的设备,并且将 psio0 传递给内核相关操作函数使用
LW_API
INT API_TtyDevCreate (PCHAR pcName,
SIO_CHAN *psiochan,
size_t stRdBufSize,
size_t stWrtBufSize)
{
REGISTER INT iTemp;
REGISTER TYCO_DEV *ptycoDev;
if (LW_CPU_GET_CUR_NESTING()) {
_DebugHandle(__ERRORMESSAGE_LEVEL, "called from ISR.\r\n");
_ErrorHandle(ERROR_KERNEL_IN_ISR);
return (PX_ERROR);
}
if (_G_iTycoDrvNum <= 0) { //判断 driver 操作函数是否被注册
_DebugHandle(__ERRORMESSAGE_LEVEL, "tty Driver invalidate.\r\n");
_ErrorHandle(ERROR_IO_NO_DRIVER);
return (PX_ERROR);
}
if (psiochan == LW_NULL) {
_DebugHandle(__ERRORMESSAGE_LEVEL, "SIO channel invalidate.\r\n");
_ErrorHandle(ERROR_IOS_DEVICE_NOT_FOUND);
return (PX_ERROR);
}
if ((pcName == LW_NULL) || (*pcName == PX_EOS)) {
_DebugHandle(__ERRORMESSAGE_LEVEL, "device name invalidate.\r\n");
_ErrorHandle(EFAULT); /* Bad address */
return (PX_ERROR);
}
ptycoDev = (TYCO_DEV *)__SHEAP_ALLOC(sizeof(TYCO_DEV)); /* 分配内存 */
if (ptycoDev == LW_NULL) {
_DebugHandle(__ERRORMESSAGE_LEVEL, "system low memory.\r\n");
_ErrorHandle(ERROR_SYSTEM_LOW_MEMORY);
return (PX_ERROR);
}
iTemp = _TyDevInit(&ptycoDev->TYCODEV_tydevTyDev,
stRdBufSize,
stWrtBufSize,
(FUNCPTR)_ttyStartup); /* 初始化设备控制块 */
if (iTemp != ERROR_NONE) {
__SHEAP_FREE(ptycoDev);
_DebugHandle(__ERRORMESSAGE_LEVEL, "system low memory.\r\n");
_ErrorHandle(ERROR_SYSTEM_LOW_MEMORY);
return (PX_ERROR);
}
ptycoDev->TYCODEV_psiochan = psiochan;
sioCallbackInstall(psiochan, SIO_CALLBACK_GET_TX_CHAR,
(VX_SIO_CALLBACK)_TyITx, (PVOID)ptycoDev);
sioCallbackInstall(psiochan, SIO_CALLBACK_PUT_RCV_CHAR,
(VX_SIO_CALLBACK)_TyIRd, (PVOID)ptycoDev);
sioIoctl(psiochan, SIO_MODE_SET, (PVOID)SIO_MODE_INT);
iTemp = (INT)iosDevAddEx(&ptycoDev->TYCODEV_tydevTyDev.TYDEV_devhdrHdr,
pcName,
_G_iTycoDrvNum,
DT_CHR);
if (iTemp) {
_TyDevRemove(&ptycoDev->TYCODEV_tydevTyDev);
__SHEAP_FREE(ptycoDev);
_DebugHandle(__ERRORMESSAGE_LEVEL, "add device error.\r\n");
_ErrorHandle(ERROR_SYSTEM_LOW_MEMORY);
return (PX_ERROR);
} else {
ptycoDev->TYCODEV_tydevTyDev.TYDEV_timeCreate = lib_time(LW_NULL);
/* 记录创建时间 (UTC 时间) */
return (ERROR_NONE);
}
}
3, SIO_DRV_FUNCS 中的txStartup 调用时间:
可以通过阅读源码发现在API_TtyDevCreate的 _TyDevInit函数中将该函数作为回掉函数给ptyDev->TYDEV_pfuncTxStartup,每次启动发送时 ptyDev->TYDEV_pfuncTxStartup 调用到 _ttyStartup,
_ttyStartup调用到我们在驱动中实现的pDrvFuncs->txStartup(ptycoDev->TYCODEV_psiochan) ;
在txStartup函数中,我们需要从将缓冲区的数据发送到uart 的fifo 中,并使能发送中断,代码可以参考如下:
static INT SioStartup (SIO_CHAN *psiochanChan)
{
__PSIO_CHANNEL pChan = (__PSIO_CHANNEL)psiochanChan;
CHAR outChar;
if (pChan->pcbGetTxChar(pChan->pvGetTxArg, &outChar) != ERROR_NONE) {
return ERROR_NONE; /* 如果没有数据,直接返回 */
}
while(readl(&pChan->regs->txcount) >= JM7200_UART_FIFO_LEN);
/*
* 使能发送中断
*/
writel(readl(&pChan->regs->intr) | JM7200_UART_INT_TX_ENABLE, &pChan->regs->intr);
/*
* 发送数据
*/
writel(outChar, &pChan->regs->fifo);
return (ERROR_NONE);
}
由代码追踪 可以发现 pcbGetTxChar 是由 callbackInstall注册回调函数实现。
3,callbackInstall 就是驱动中用来注册发送 和接收回调函数的;
callbackInstall 在API_TtyDevCreate 被调用,可以看到:
psiochanUart->pcbGetTxChar = _TyITx;
psiochanUart->pcbPutRcvChar = _TyIRd;
sioCallbackInstall(psiochan, SIO_CALLBACK_GET_TX_CHAR,
(VX_SIO_CALLBACK)_TyITx, (PVOID)ptycoDev);
sioCallbackInstall(psiochan, SIO_CALLBACK_PUT_RCV_CHAR,
(VX_SIO_CALLBACK)_TyIRd, (PVOID)ptycoDev);
static INT xxxSioCbInstall (SIO_CHAN *psiochanChan,
INT iCallbackType,
VX_SIO_CALLBACK callbackRoute,
PVOID pvCallbackArg)
{
__PSIO_CHANNEL psiochanUart = (__PSIO_CHANNEL)psiochanChan;
if (!psiochanUart) { /* 参数检查 */
_ErrorHandle(EINVAL);
return (PX_ERROR);
}
switch (iCallbackType) {
case SIO_CALLBACK_GET_TX_CHAR: /* 发送回调函数 */
psiochanUart->pcbGetTxChar = callbackRoute;
psiochanUart->pvGetTxArg = pvCallbackArg;
return (ERROR_NONE);
case SIO_CALLBACK_PUT_RCV_CHAR: /* 接收回调函数 */
psiochanUart->pcbPutRcvChar = callbackRoute;
psiochanUart->pvPutRcvArg = pvCallbackArg;
return (ERROR_NONE);
default:
_ErrorHandle(ENOSYS);
return (PX_ERROR);
}
}
4,串口中断处理函数:
中断处理函数中一般要处理将 接收中断产生的数据提交到给串口接收缓冲区或者将 发送缓冲区的数据发送到硬件fifo 中;
这部分也可以使用信号量来处理,将发送和接收数据通过信号机制提交到tty缓冲区,这样可以避免中断上部分可以快速处理完中断;
static irqreturn_t xxxSioIsr (SIO_CHAN *psiochanChan, ULONG ulVector)
{
clear_irq //清中断
//处理发送中断产生的数据
outChar = readl(&pChan->regs->fifo) & 0xff;
pChan->pcbPutRcvChar(pChan->pvPutRcvArg, outChar);// 提交到内核代码中处理
// 处理发送中断产生的数据
pChan->pcbGetTxChar(pChan->pvGetTxArg, &outChar) // 从内核tty层取出数据写到fifo下
writel(outChar, &pChan->regs->fifo);
}