接上文,本文章继续记录中泰联创的数据采集卡驱动翻新过程。
读写FPGA寄存器函数移植
将PCI8KPLX_IOCTL_BAR_RW改为PCI8KPLX_IOCTL_BAR_READ作为读FPGA寄存器命令,将PCI8KPLX_IOCTL_BAR_BULK_RW改为PCI8KPLX_IOCTL_BAR_BULK_WRITE作为写FPGA寄存器命令。
老驱动使用一个PCI8KPLX_IOCTL_BAR_RW命令作为内核通讯接口,没有利用到系统自身设计的便利,所以新驱动将读命令和写命令拆分成两个IOCTL操作。
之前的PCI8KPLX_IOCTL_OPEN_IRQ和PCI8KPLX_IOCTL_CLOSE_IRQ请求,都不需要返回给应用层数据,因此在PLxEvtIoDeviceControl函数中直接使用WdfRequestComplete函数设置请求完成即可;但是PCI8KPLX_IOCTL_BAR_READ需要返回给应用层寄存器读取结果,因此必须要设定返回字节数,可以通过WdfRequestCompleteWithInformation完成请求来设置返回字节数,但是这样就必须考虑其它不返回数据的函数,会增加编程复杂度,因此使用WdfRequestSetInformation函数在需要返回数据的函数里面设置返回字节数,不需要返回数据的函数,不调用这个函数,系统会默认返回0字节。
内核中添加代码如下:
VOID
PLxEvtIoDeviceControl(_In_ WDFQUEUE Queue,_In_ WDFREQUEST Request,_In_ size_t OutputBufferLength,_In_ size_t InputBufferLength,_In_ ULONG IoControlCode){...case PCI8KPLX_IOCTL_BAR_READ:status = PCI8KPLX_IOCTL_BAR_READ_Handler(Request, devExt);break;case PCI8KPLX_IOCTL_BAR_WRITE:status = PCI8KPLX_IOCTL_BAR_WRITE_Handler(Request, devExt);break;...}
/*** 功能:读回FPGA寄存器内容* 输入:ULONG[0],操作位宽;0对应32位,1对应16位,2对应8位* ULONG[1],地址;0~255* 输出:ULONG[0],寄存器内容*/
NTSTATUS
PCI8KPLX_IOCTL_BAR_READ_Handler(_In_ WDFREQUEST Request,_In_ PDEVICE_EXTENSION DevExt
)
{NTSTATUS status = STATUS_SUCCESS;PULONG pBuff = NULL; // 指向输入缓冲区PULONG pOutBuff = NULL; // 指向输出缓冲区size_t bufferSize = 0;// 1. 获取输入缓冲区 status = WdfRequestRetrieveInputBuffer(Request, PCI8KPLX_IOCTL_BAR_READ_IN_SIZE, (PVOID*)&pBuff, &bufferSize);if (!NT_SUCCESS(status)) {return status;}ULONG type = pBuff[0];ULONG addr = pBuff[1];ULONG ret = 0;switch (type){case PORT_RW_32BIT:ret = READ_PORT_ULONG((PULONG)(DevExt->addrLocal + addr));break;case PORT_RW_16BIT:ret = READ_PORT_USHORT((PUSHORT)(DevExt->addrLocal + addr));break;case PORT_RW_8BIT:ret = READ_PORT_UCHAR((PUCHAR)(DevExt->addrLocal + addr));break;default:return STATUS_INVALID_PARAMETER;}//获取输出缓冲区status = WdfRequestRetrieveOutputBuffer(Request,PCI8KPLX_IOCTL_BAR_READ_OUT_SIZE, // 要求至少能存 EVENT_COUNT 个 ULONG(PVOID*)&pOutBuff,&bufferSize);if (!NT_SUCCESS(status)) {return status;}pOutBuff[0] = ret;WdfRequestSetInformation(Request, (ULONG_PTR)PCI8KPLX_IOCTL_BAR_READ_OUT_SIZE);TraceEvents(TRACE_LEVEL_INFORMATION, DBG_DPC,"%s:status %x", __FUNCDNAME__, status);return status;
}
/*** 功能:设置FPGA寄存器* 输入:ULONG[0],操作位宽;0对应32位,1对应16位,2对应8位* ULONG[1],地址;0~255* ULONG[2],数据* 输出:无*/
NTSTATUS
PCI8KPLX_IOCTL_BAR_WRITE_Handler(_In_ WDFREQUEST Request,_In_ PDEVICE_EXTENSION DevExt
)
{NTSTATUS status = STATUS_SUCCESS;PULONG pBuff = NULL; // 指向输入缓冲区size_t bufferSize = 0;// 1. 获取输入缓冲区 status = WdfRequestRetrieveInputBuffer(Request, PCI8KPLX_IOCTL_BAR_WRITE_IN_SIZE, (PVOID*)&pBuff, &bufferSize);if (!NT_SUCCESS(status)) {return status;}ULONG type = pBuff[0];ULONG addr = pBuff[1];ULONG data = pBuff[2];switch (type){case PORT_RW_32BIT:WRITE_PORT_ULONG((PULONG)(DevExt->addrLocal + addr), data);break;case PORT_RW_16BIT:WRITE_PORT_USHORT((PUSHORT)(DevExt->addrLocal + addr), (USHORT)data);break;case PORT_RW_8BIT:WRITE_PORT_UCHAR((PUCHAR)(DevExt->addrLocal + addr), (UCHAR)data);break;default:return STATUS_INVALID_PARAMETER;}TraceEvents(TRACE_LEVEL_INFORMATION, DBG_DPC,"%s:status %x", __FUNCDNAME__, status);return status;
}
应用层代码移植,打算尝试直接用 Qoder 修改,看能否成功,下达下面的指令:“zt9054/sys/dll目录下的程序原来是调用sys目录下的驱动,现在改成调用zt9054/sys/sys目录下的驱动,现在要把TK3000_WriteD调用PCI8KPLX_IOCTL_BAR_WRITE,TK3000_ReadD调用PCI8KPLX_IOCTL_BAR_READ实现,请修改zt9054/sys/dll目录下这两个函数的相关代码”
代码加上了,但是所有中文都变成了乱码。接下来为了编码问题又和Qoder斗争了半天,最后总算是搞定,和自己写时间也差不多,但是如果没有编码问题还是会节约不少时间的。
以下是Qoder修改的部分代码
long CZTCARD::WriteD(ULONG cardNO, ULONG nOffset, ULONG dataDoubleWord)
{
//函数名称:
//函数功能:以IO方式,对板卡寄存器进行32位写
//入口参数:
// baseAddr:板卡基地址
// nOffset:偏移地址,在硬件说明书上可以查到
// dataDoubleWord:要写入寄存的值
//返回值: 0 表成功
// -1 表失败,应该进一步调用 ZT511PF_GetLastErr 判断出错原因CHECK_CARDNO(cardNO); //检查板卡号CHECK_ERRNO; //若用户参数错,不继续执行ULONG inBuffer[3];inBuffer[0] = PORT_RW_32BIT; // New protocol: specify 32-bit writeinBuffer[1] = nOffset;inBuffer[2] = dataDoubleWord;// ULONG outBuffer[1];ULONG nOutput;if (!DeviceIoControl(g_ztpciHandle[cardNO-g_baseNO],PCI8KPLX_IOCTL_BAR_WRITE, // Use new control codeinBuffer,sizeof(inBuffer),NULL,0,&nOutput,NULL)){g_errorLevel = ERR_EXCHANGE_DATA; //与底层驱动之间交换数据出错return -1;}return ZT_SUCCESS;
}
修改启动和停止AD采集对应代码
主要是需要将内核寄存器操作部分移植到应用层,让Qoder仿照我之前写好的中断初始化相关代码,结果他直接把我一个文件大部分代码都删除了,幸亏有git,还得随时盯着才能不出错。
经过多次沟通调整,Qoder 生成的代码仍频繁出错,最后还是我自己修改代码,Qoder反倒是浪费了时间。
最后把通道号缓存变量转移到了dll中:
// AD通道相关全局变量
ULONG g_adEndChCache[MAX_CARD_COUNT + 1];
void SetAdEndChCache(ULONG cardNo, ULONG ulCh) {if (cardNo > MAX_CARD_COUNT)return;g_adEndChCache[cardNo] = ulCh;
}ULONG GetAdEndChCache(ULONG cardNo) {if (cardNo > MAX_CARD_COUNT)return 0;return g_adEndChCache[cardNo];
}
两个核心函数的修改内容如下:
long CROMAD::EnableAD(ULONG cardNO, unsigned long ulCh, unsigned long ulADFreqFlag)
{CHECK_CARDNO(cardNO); //检查板卡号CHECK_ERRNO; //若用户参数错,不继续执行// 设置本地缓存的通道号SetAdEndChCache(cardNO, ulCh);//设置控制字//设置频率// 从本地缓存读取控制字ULONG ulCtrlWord = GetCtrlWord(cardNO); // 使用tk2000.CPP中定义的函数 ulCtrlWord = SET_ULONG_BITS( ulCtrlWord, TK3000_ADFREQ_MASK, TK3000_ADFREQ, ulADFreqFlag );//将设备数据线设成输入状态(D0~D15)ulCtrlWord = SET_ULONG_BITS( ulCtrlWord, TK3000_DIR_MASK, TK3000_DIR, TK3000_D_IN );//将控制字写到本地缓存中SetCtrlWord(cardNO, ulCtrlWord);//实际写AD通道和控制寄存器,AD采集开始WRITED(cardNO, OFF_AD_END_CH, GetAdEndChCache(cardNO)); WRITED(cardNO, OFF_CTRLWORD, (USHORT)(GetCtrlWord(cardNO)));return (g_errorLevel != ZT_SUCCESS) ? -1 : ZT_SUCCESS; //调过别的函数,要重新判断是否出错
}long CROMAD::DisableAD(ULONG cardNO)
{CHECK_CARDNO(cardNO); //检查板卡号CHECK_ERRNO; //若用户参数错,不继续执行//停止AD采集,将频率设成0即可// 从本地缓存读取控制字ULONG ulCtrlWord = GetCtrlWord(cardNO); ulCtrlWord = SET_ULONG_BITS( ulCtrlWord, TK3000_ADFREQ_MASK, TK3000_ADFREQ, ADFREQ_0 );SetCtrlWord(cardNO, ulCtrlWord);//更新控制字中的通道状态ulCtrlWord = GetCtrlWord(cardNO);ulCtrlWord = SET_ULONG_BITS( ulCtrlWord, TK3000_ADFREQ_MASK, TK3000_ADFREQ, 0 );SetCtrlWord(cardNO, ulCtrlWord);WRITED(cardNO, OFF_CTRLWORD, (USHORT)(GetCtrlWord(cardNO)));return (g_errorLevel != ZT_SUCCESS) ? -1 : ZT_SUCCESS; //调过别的函数,要重新判断是否出错
}