常常有朋友问我关于pci驱动程序的问题,但是在和他们的交谈中
我发现有一些问题非常重复,也没有什么特别的地方(真有什么特别
的地方,我也搞不定:-)),对于一些问题,其实卡的结构非常
普通,只要熟悉pci驱动程序的一些基本概念就可以解决的。
下面我把pci驱动程序的一些基本概念整理整理:首先讲一讲pci的配置空间。
这个概念本来是非常简单的,但是我这个有灌水的爱好,为了让文章能够
更成体系,加上这一段。
pci的配置空间的结构不在这里重复,任何一本pci的书里都有。
对于pci的配置空间操作,我用过的操作系统中,linux下是最简单的,
linux提供了许多函数,细节可看linux device driver。
我在这里简单的给出一个操作x86结构下pci的方法就结束这一段。
我在写第一个pci driver的时候是在dos下,最早我操作pci的方法就是用
int 1a,需要bios支持,现在的主板是没有不支持得了。后来我发现了另外
一种方法,更为简单,int 1a的方法就记不得了,如果想知道,可以去查
int 1a中断,要96年以后的书(int 1a是时钟,但是扩展为pci操作)。
由于不同操作系统提供的函数不同,使得代码可移植性变差,其实这些实现
几乎都是基于BIOS的int 1a中断调用。由于在不同系统中调用1a中断的实现
也很不相同,而且一般都需要写驱动程序。下面是我写的一个通用的操作PCI
配置空间的程序如下,该方法和int 1a内部机制相同,都是基于PCI规范实现
的,只用到了端口操作。代码如下:
#include "windows.h"
DWORD DWORD_In(WORD io_Port)
{ DWORD val;
_asm {
mov dx,io_Port
in eax,dx
mov val,eax
}
return val;
}
DWORD DWORD_Out(WORD io_Port,DWORD val)
{
_asm {
mov dx,io_Port
mov eax,val
out dx,eax
}
return 0 ;
}
int main()
{
DWORD io_CF8; // port 0xcf8
DWORD io_CFC; // port 0xcfc
int i;
io_CF8=0x80000000; //because the first bit is enable/disable
for(;;) //so must be 1,so from 0x800000000
{
DWORD_Out(0xcf8 ,io_CF8);
io_CFC=DWORD_In(0xcfc );
if (io_CFC!=0xffffffff) //if =0xffffffff,then is a invalid
{ //bus number and device number
printf(" PCI device has found,the pci config address=%lx " ,io_CF8);
printf("its Bus Numer is %lx ",(io_CF8&0x00ff0000)/0x10000 );
printf("its Device Number is %lx ",(io_CF8&0x0000f800)/0x800 );
printf("its Functin Number is %lx ",(io_CF8&0x700)/0x100 );
printf("this device's deviceID and vendorID=%lx " ,io_CFC);
for (i=0 ;i<=15;i++ )
{
DWORD_Out(0xcf8,io_CF8+4*i); //read DWORD
switch (i)
{
case 0 :
printf("Device Number and Vendor Number =%lx" );
break ;
case 1 :
printf("Status and Command =" );
break ;
case 2 :
printf("Class Code and Revision ID=" );
break ;
case 3 :
printf("Bist and Header Type and Latency Timer and Cacne Line
Size =");
break ;
case 4: //PCI Configration has 6 base address
case 5: //register
case 6 :
case 7 :
case 8 :
case 9 :
printf("Base Address Register=" );
break ;
case 10 :
case 11 :
case 13 :
case 14 :
printf("Reserved =" );
break ;
case 12 :
printf("Expansion ROM Base Address=" );
break ;
case 15: //attention:the interrupt IRQ= this result&0xff
printf("Max_Lat Min_Gnt Interrupt Pin Interrupt line=" );
break ;
}
printf("%lx ",DWORD_In(0xcfc ));
}
}
io_CF8+=0x800 ;
if (io_CF8>=0x80FFFF00 )
break ;
}
return 0 ;
}
driver for 9x/nt的时候都要先用它来scan一遍pci空间,看看硬件有没有问题。
程序中用到了双字读写,这个似乎c库中不提供,不过没有关系,简单的写一个小汇编就解决了。
这个程序可以在9x和dos下用,因为nt对于所有的io操作都是禁止的,所以在nt下,需要写一个
小sys,提供io操作。9x对于部分io操作也是禁止的,不过这里用到的cf8,cfc没有禁止。
资料来源:我当时手头上的一本PCI2.1规范,我没有遇到过PCI66M,64bit的卡(好像是PCI2.2),
因此不知道会不会有问题。现在这本书有了中译本,到处有售。
还有一点要说明的是,9x/nt都是提供包装的更好,更安全的系统函数来读写pci配置空间,但是
如果不考虑什么兼容性,这些代码完全可以用在9x/nt的驱动程序里面。linux就没有必要,因为,
linux下提供的函数不像nt的那样晦涩,非常易懂。
下面就是和用户态交互的地方了
一般来说,在nt下,较为通用的方法是用readfile和writefile进行常规通讯,当然deviceiocontrol也不是
不可以,问题是既然提供了readfile/writefile(9x下没有),如果能够解决,就不要用deviceiocontrol。
我一般也用deviceioctl,但是一般用在设置板卡工作状态,或者提供一个读写板卡寄存器得deviceioctl,
在app层上封装。
首先当然是在driverentry加上一条 DriverObject->MajorFunction[IRP_MJ_READ] = xxxRead;
这个似乎不难以理解,但是,我建议一般来说,加上DriverObject->DriverStartIo = xxxStartIo;
关于startio,我想说几句,如果你觉得自己够牛,可以不通过nt manager管理irp,那么不要也可以,
特别的,如果在全双工的情况下,startio是串行的,只能保证半双工.
对于read来说,一般都是首先解析参数,如果不对,立即返回error,如果对的话,再看能够立即
返回,一般来说是不能立即返回的,能够立即返回的操作我一般都放在deviceioctl。不能立即返回
的话先设置cancelroutine,IoSetCancelRoutine(Irp,GT48001ACancel);
然后IoMarkIrpPending(Irp);最后
Irp,
NULL,
GT48001ACancel
);
return STATUS_PENDING;
这里需要说明一点,iostartpacket是用来让系统启动startio的,startio我以前的理解
是用来做一些板卡准备工作的,有些卡较简单,不需要什么准备工作,唯一的事情就是
等待中断,在这种情况下,startio可以设成空函数,也可以在这里处理cancel。
cancel非常重要,绝对不要不写,特别对于数据采集卡来说更是这样。因为app层不知道
什么时候会有数据,app一般都是发几个read irp下去,然后等待event,如果没有cancelroutine,
那么结果就是app关闭了,但是irp却没有清除,下一次运行app的时候,头几个数据会丢失。
这个问题我决定放在app的处理那里仔细讲,这里先放下。
Gt48001aReadWrite(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
PDEVICE_EXTENSION deviceExtension = DeviceObject -> DeviceExtension;
ULONG transferPages;
ULONG transferByteCount = irpStack -> Parameters.Read.Length;
// Ensure that the IRP information is correct. If it is not,
// complete the IRP with the proper error status.
if ( 0 == transferByteCount) {
DebugPrint(( 2 , " Zero transfer length input to read/write " ));
Irp -> IoStatus.Status = STATUS_INVALID_PARAMETER;
Irp -> IoStatus.Information = 0 ;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INVALID_PARAMETER;
}
// read 's offset must be 0,if not ,return invalid parameter
if (RtlLargeIntegerNotEqualToZero(irpStack -> Parameters.Read.ByteOffset))
{
DebugPrint(( 2 , " read byteoffset !=0 " ));
Irp -> IoStatus.Status = STATUS_INVALID_PARAMETER;
Irp -> IoStatus.Information = 0 ;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INVALID_PARAMETER;
}
IoSetCancelRoutine(Irp,GT48001ACancel);
// Mark IRP as pending. --for queueing
IoMarkIrpPending(Irp);
// Start the I/O request. --Call StartIO routine or queueing the irq when busy
IoStartPacket(DeviceObject,
Irp,
NULL,
GT48001ACancel
);
return STATUS_PENDING;
} // ReadWrite
在讲startio之前,我认为我们有必要先对一个irp的历程搞搞清楚。
一般来说,一个irp是这样产生的,首先是app执行readfile(hHandle,,,,);
这样就要调用ntreadfile(这个是ntdll.dll),向那个设备发出一个read irp,
io manager执行Gt48001aReadWrite,然后解析参数,判断缓冲区大小等等,
如果能够立即read,当时就返回了,如果不行,先mark pending,然后调用iostartpacket,
这样io manager就会看当前是否有irp在队列里,如果有,把这个irp排队,如果没有,
调用startio。这个队列是系统管理的,所以叫做系统队列。你可以自己用连表管理。
startio里面做一些寄存器的处理,然后就返回了。这个时候,deviceObject->CurrentIrp
就是这个irp。这个时候,这个irp就一直pending。如果来了中断,首先我们判断这个时候
deviceObject->CurrentIrp是否有,如果没有irp,中断来了也不用处理,因为我们
对此不感兴趣,如果有irp在,那么判断,然后启动一个dpc,在dpc里面处理这个irp,
处理结束后,返回到app,并且设置hevent,通知app,一个irp完成了。这个irp处理结束
后,调用IoStartNextPacket(DeviceObject,FALSE);这个很重要,如果没有的话,会有
两个后果,1,再发readfile,发irp的时候,在Gt48001aReadWrite里面调用iostartpacket,
系统会认为当前有irp,所以会排队这个irp,其实这个时候已经没有irp了,2,isr里面
判断deviceObject->CurrentIrp的时候,这个值不为null,这就欺骗了isr,他以为有一个
irp呢,其实是上一个已经处理过的,而且更为糟糕的是,其实这个irp已经不存在了,
当然在dpc里面使用这个irp的时候,会dump 系统。这一段是我个人痛苦的经历,我是
经历了死机无数次,经过无数次debug才发现的。osr和art baker的书里面都没有详细讲
这个地方,而且ddk里面也说得很简单,不仔细debug是无法发现系统背后的动作的。
搞清楚前面的地方之后,基本上下面就没有什么问题了,我还有一个地方要讲一讲,
就是cancel,这个是非常重要的,我知道很多驱动程序作者都忽略这个问题,会出
问题的!!!startio里面必须包括对于cancel的处理:
我先把我的cancelroutine说一下:
GT48001ACancel(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
#ifdef DBG
DbgPrint( " Irp Cancel " );
#endif
if (Irp == DeviceObject -> CurrentIrp)
{
IoReleaseCancelSpinLock(Irp -> CancelIrql);
IoStartNextPacket(DeviceObject,TRUE);
}
else
{
KeRemoveEntryDeviceQueue( & DeviceObject -> DeviceQueue,
& Irp -> Tail.Overlay.DeviceQueueEntry);
IoReleaseCancelSpinLock(Irp -> CancelIrql);
}
Irp -> IoStatus.Status = STATUS_CANCELLED;
Irp -> IoStatus.Information = 0 ;
IoCompleteRequest( Irp,IO_NO_INCREMENT);
return ;
};
然后是stario
Gt48001aStartIo(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
// Because we only need wait for interrupt,so do nothing
// in here. This function is use to let system query our
// irp.
PDEVICE_EXTENSION devExt;
PIO_STACK_LOCATION ioStack;
KIRQL cancelIrpl;
NTSTATUS code;
IoAcquireCancelSpinLock( & cancelIrpl);
if (Irp -> Cancel)
{
IoReleaseCancelSpinLock(cancelIrpl);
return ;
}
// we need not set cancelroutin to NULL
// of course,many sample and ddk document set the CancelRoutine to NULL in here,
// but our adapter has some strange,it must wait the interrupt
// and we donot know when the interrupt will happen,so if in here
// we set CancelRoutine to NULL,we canot cancel it's IRP.
// Not set CancelRoutine to NULL ,we must do it in DPC routine.
对于cancel的一些说明都在上面的注释说明了,我的英语很差,弟兄们将就着读吧: - )
return ;
} // Gt48001aStartIo
cancel也一样,虽然很多sample和书里都有描述,但是未必适合你的卡,你一定要
真正搞清楚一个irp什么时候可以被cancel,什么时候不必被cancel,整个irp的历程,
才能真正写出稳定合适的代码。
ok,下面该讲isr和dpc了,好累啊,写得我的手都酸了,还好,苦难的写作就要结束了:-(。
isr/dpc
这个是真正处理中断,完成任务的代码。
由于pci共享中断,所以这个地方我要解释一下。
pci设备的一个好处就是共享中断,想想isa的年代,为了安装一块卡简直就是一场
噩梦,我个人还喜欢那种需要跳线设置io和中断号的isa卡,可能现在的弟兄
门是没有见过了,对于那种所谓的jumpless isa卡,号称无跳线,我个人感觉
也不是一个东西,还要用设置盘设置,至于isa pnp,简直就是plug and prey,
就看运气了,我曾经在9x和nt下经历过若干次挫折,在一直奉我为高手的mm面前,
居然一块普通的卡都装不上,简直是丢尽了面子:-(。不过我发现2000似乎这个
问题解决较好,许多我在nt/9x下无法装上的卡,在2000下一下就ok了。
前面说了些废话,不过还请弟兄们原谅,因为前不久我还挫折了一次。
由于pci共享中断,一个中断来了之后,系统如何动作呢?在驱动程序
注册中断服务程序的时候,系统把这些函数指针放在一个链表里面,(如果
不是链表,也不要找我,因为我也没有看到过2000的source,不过如果是
我来实现的话,我一定是用链表的)中断来的时候,系统跳转到一个链表的
函数指针,这个isr有可能是你写的那个,也可能不是,这个运行的isr就要
判断,这个中断是不是他管理的那块卡发出的,如果不是,那么return false,
系统发现return false了,oh,不是这个isr,那么调用下一个,如果isr发现
这个中断是他管理的那块卡发出的,那么return true,系统发现return true,
ok,就不再调用下面的isr了。因此,如果你的驱动程序乱写,就会影响和你
的卡共享中断的设备,使得他们有可能响应不到中断,如果那些设备运气不好,
在你的isr之后被调用的话。前面说,isr必须负责判断这个中断是否是自己
管理的卡发出的,如何判断呢?不同的pci卡是不一样得,但是pci规定,卡上必须有
这么一个寄存器,这个寄存器初始化为0,或者你的初始化代码把它清0,如果
是卡发出了中断,卡会去写这个寄存器,如果这个寄存器不为0,那么当然就是
你的卡来了中断。这个寄存器还有别的意义,因为产生中断的原因有很多,比如
说数据来了,这个时候寄存器为00001,如果是error1,那么寄存器为000010,
驱动程序通过这个寄存器还可以知道下面该干什么。当然,在isr返回之前,千万
不要忘记了把这个寄存器清0,否者这个中断会不停的发生,这个时候,系统没有
死,不过和死也差不多,因为几乎所有的cpu时间都处理isr去了。
ok,下面让我们来看看代码:
Gt48001aISR(
IN PKINTERRUPT Interrupt,
IN PVOID ServiceContext
)
{
PDEVICE_OBJECT deviceObject = (PDEVICE_OBJECT)ServiceContext;
PDEVICE_EXTENSION deviceExtension = deviceObject -> DeviceExtension;
USHORT IntStatus;
USHORT intStat0;
USHORT intStat1;
// DebugPrint((3, "Gt48001aISR "));
if ( ! deviceExtension -> DeviceConfigured) {
尽管这应该不会发生,但是偏执一点没有什么不好
DebugPrint(( 3 , " Device not configured but ISR called " ));
// for shared interrupts, what should I do?
// I just return false,and system will call the next isr
return FALSE;
}
// Check if the adapter is interrupting. If not, indicate this fact.
IntStatus = READ_USHORT(IntCause_44); // 读卡寄存器,看看是否是我们的卡来了中断
if ( ! (IntStatus & DEFAULT_INT_MASK)) {
// This adapter was not interrupting.
// maybe something error happen :-(. 如果我屏蔽了这位中断,居然还有...
return FALSE;
}
// Clear the interrupt.
// Check if there is a current IRP. If not, then this interrupt cannot
// do anything. This driver design requires an I/O to be pending in order
// to queue the DPC. If there is no I/O current, then there is no need
// to have a DPC queued. This driver also assumes one I/O per interrupt.
//
// Before returning TRUE, the interrupt must have been cleared
// on the device or the system will hang trying to service this level
// sensitive interrupt.
if ( ! deviceObject -> CurrentIrp) {
#ifdef DBG // I add it in 8/20,but maybe no use :-),Because no dgbprint will in free version
DbgPrint( " interrupt cause =%x " ,IntStatus);
DebugPrint(( 3 , " Hardware generated interrupt with no IRP pending " ));
#endif
没有irp请求,就是我们的卡来了中断,也不用理睬
// Clear the interrupt on the device before returning TRUE.
WRITE_USHORT(IntCause_44, 0x0 );
return TRUE;
}
下面是和卡有关的东西
deviceExtension -> InterruptCause = IntStatus;
WRITE_USHORT(IntCause_44, 0x0 );
if (IntStatus & 0x2804 )
{
if (IntStatus & 0x2004 ) // a packet received
{
if (IntStatus & 0x4 )
{
// ReceivePointer is a pointer which point to the site in cpu buffer
// if(deviceExtension->ReceivePointer)
deviceExtension -> ReceivePointer ++ ;
ASSERT(deviceExtension -> ReceivePointer < 17 );
deviceExtension -> ReceivePointer = deviceExtension -> ReceivePointer % 16 ;
}
else
{
// buf wrap,but no packet arrive? why?
CWRITE_ULONG(CPUBufferBaseAddress_34, 0x3e00000 + 0x8000000 );
deviceExtension -> ReceivePointer = 15 ; // at the end of buf
return TRUE;
}
if (IntStatus & 0x2000 )
{
// Set CPU Buffer Base Address. 63M+128M
CWRITE_ULONG(CPUBufferBaseAddress_34, 0x3e00000 + 0x8000000 );
deviceExtension -> ReceivePointer = 15 ; // at the end of buf
}
}
// Request the DPC to complete the transfer.
请求一个dpc,放进dpc队列
IoRequestDpc(deviceObject,
deviceObject -> CurrentIrp,
NULL
);
}
// Indicate that this adapter was interrupting.
// Because when this interrupt happen,in it's IRQ,will disable the same IRQ happen
// so we need not disable adapter's interrupt
// WRITE_USHORT(IntMask_48, DEFAULT_INT_MASK);
return TRUE;
} // Gt48001aISR
这段代码简单明了,下面是dpc的代码:
VOID
Gt48001aDpc(
IN PKDPC Dpc,
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
PDEVICE_EXTENSION deviceExtension = DeviceObject -> DeviceExtension;
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
USHORT IntStatus;
ULONG END_OF_PACKET;
PUCHAR pPacket;
BOOLEAN PacketValidBit;
ULONG PacketLen,i;
USHORT PacketChannelNumber;
ULONG Offset;
ULONG BytesToRead;
KIRQL cancelIrql;
#ifdef DBG
DebugPrint(( 3 , " Gt48001aDpc " ));
#endif
// we must first see cause register
IntStatus = deviceExtension -> InterruptCause ;
// first check irpStack
实际上,在dpc里面调用了iostartnextpacket之后就不会发生这样的事情,但是偏执狂才能生存
if ( ! irpStack) return ;
if (irpStack -> MajorFunction == IRP_MJ_READ)
{
// is it irp request been deleted after startio?
我们需要判断这个irp是否被cancel掉了
IoAcquireCancelSpinLock( & cancelIrql);
if (Irp -> Cancel)
{
IoReleaseCancelSpinLock(cancelIrql);
// what should I do? I just return ,and
// pray this will never happen.
return ;
}
// To here , I neednot cancel this IRP
到了这里,眼看着这个irp就要被处理了,而且,dpc的中断优先级比app中断优先级高2,绝对不会发生
被app发出一个cancelio cancel irp的事情
IoSetCancelRoutine(Irp,NULL);
禁止这个irp被cancel
IoReleaseCancelSpinLock(cancelIrql);
下面是和卡有关的代码,读被卡dma发到内存中的数据
if (IntStatus & 0x4 )
{
// let's see the first DWORD:END_OF_PACKET
pPacket = (PUCHAR)(deviceExtension -> CpuBufferBaseAddress + deviceExtension -> ReceivePointer * 2048 );
END_OF_PACKET = * pPacket + ( * (pPacket + 1 )) * 0x100 + ( * (pPacket + 2 )) * 0x10000 + ( * (pPacket + 3 )) * 0x1000000 ;
// let 's see the packet len
PacketLen = (END_OF_PACKET & 0xffe ) / 2 ;
#ifdef DBG
PacketValidBit = (UCHAR)(END_OF_PACKET & 0x1 );
PacketChannelNumber = (UCHAR)((END_OF_PACKET & 0x7000 ) / 0x1000 );
DbgPrint( " PacketLen=%d,PacketValidBit=%d,PacketChannelNumber=%d " ,PacketLen,PacketValidBit,PacketChannelNumber);
#endif
BytesToRead = irpStack -> Parameters.Read.Length;
Irp -> IoStatus.Status = STATUS_SUCCESS;
就这么简单,我发现似乎用memcpy也行,不过移植性肯定是不好了
RtlCopyMemory(Irp -> AssociatedIrp.SystemBuffer,pPacket,(PacketLen > BytesToRead ? BytesToRead:PacketLen) + 32 );
Irp -> IoStatus.Information = (PacketLen > BytesToRead ? BytesToRead:PacketLen) + 32 ; // end_of_packet的格式
// Because our app thread maybe use realtime priority ,so we need not add it's priority
/*
#ifdef DBG
// dump the packet
for(i=0;i<PacketLen;i++)
{
DbgPrint("%02x ",*((PUCHAR)(pPacket+i+32)));
if(((i+1)%30)==0)
DbgPrint(" ");
}
DbgPrint(" ");
#endif
*/
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
// linkchange
if (IntStatus & 0x800 )
{
Irp -> IoStatus.Status = STATUS_SUCCESS;
* ((USHORT * )Irp -> AssociatedIrp.SystemBuffer) = IntStatus;
Irp -> IoStatus.Information = sizeof (IntStatus);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
}
// although startio do nothing,
// It's very important,if havenot this line,the Irp Will not clear
这句话很重要,我前面已经说了他的意义
IoStartNextPacket(DeviceObject,FALSE);
} // Gt48001aDpc
ok,一个pci驱动程序基本上都走了一遍了,下面我考虑讲一讲app上的处理。
这个驱动程序非常简单,但是非常具有代表性,80%的驱动程序都可以如下
处理,当然特定设备如网卡,声卡什么的不能这样写,要符合一定的规范。
app上的处理
常常有人问我说驱动程序如何和app通讯,他们着迷的是先用devicioctl传一个
event进去,然后driver处理这个event,让app等待这个event,当然,这样做原理上
是可以,但是,怎么说呢,没有这个必要,因为这样做,按照我的说法是脱开
裤子放屁-----多此一举。而且还容易出错,我认为产生这个想法,是没有真正
理解nt的缘故(我又要开始做nt inside 的广告了:-),还有jeffery的advanced windows,
这书有4个版本,我都有,且都看过了,尽管jeffery的书是win32的,但是熟悉
win32有助于理解nt,我们学习的时候要揣摩,设计nt的大师们为什么要这样设计,
nt是一群天才设计的优秀os,不承认这一点是非常不严肃的)。
nt是怎么做的呢?readfile/writefile/deviceioctl都有一个参数,overlapped,
这个参数是一个结构,里面就有hevent,因此,当发送一个irp下去的时候,这个
hevent也发到io manager去了,当调用IoCompleteRequest(Irp, IO_NO_INCREMENT);
的时候,这个hevent已经置成有信号了,你只要察看这个hevent就行了,nt替你管理
了一切!
下面我提供一段代码,是win32和驱动程序通讯的,我在97年的时候写过之后在许多
地方都反复的用过它。
这段代码的思想,我记得是97年的时候,我在学习win32的时候在别人的代码里看到
的,当时我非常感谢这个人,因为那个时候我对于许多win32概念还是似懂非懂,仔细
看过之后我感觉进了一大步。
这段代码事我用来测试我前面讲过的那个驱动程序的。
//
#include " stdafx.h "
#include " windows.h "
typedef struct _PACKET
{
OVERLAPPED OverLapped;
BYTE Buffer[ 1514 + 256 ];
DWORD Length;
} PACKET, * LPPACKET;
bool g_bFirstCall = true ;
BOOLEAN
RecvPacket(
BYTE * pbuf,
PULONG BytesReceived,
HANDLE hGt
)
{
BOOLEAN Result;
static PACKET Packet[ 32 ];
static HANDLE hEvent[ 32 ];
DWORD dwByteReceive;
HANDLE hEventTemp;
int i,j,k;
if (g_bFirstCall) // if first call ,let's call 32 times readfile first
{
for (i = 0 ;i < 32 ;i ++ )
{
Packet[i].OverLapped.Offset = 0 ;
Packet[i].OverLapped.OffsetHigh = 0 ;
Packet[i].OverLapped.hEvent = CreateEvent(
0 ,
TRUE,
FALSE,
NULL
); // manual reset,initial=false
hEvent[i] = Packet[i].OverLapped.hEvent;
Packet[i].Length = 1514 + 256 ; // if someone shit send a packet>>1514,what's happen?
Result = ReadFile(
hGt,
Packet[i].Buffer,
Packet[i].Length,
& dwByteReceive,
& Packet[i].OverLapped
);
}
g_bFirstCall = false ;
}
i = WaitForMultipleObjects( // which read return?
32 ,
hEvent,
false , // wait untill one hevent signal
INFINITE // wait ever
);
if (i == WAIT_FAILED) return false ;
for (j = 0 ;j < 32 ;j ++ )
if (Packet[j].OverLapped.hEvent == hEvent[i]) break ; // which read return?
k = j;
dwByteReceive = 0 ;
Result = GetOverlappedResult(
hGt,
& Packet[k].OverLapped,
& dwByteReceive,
false
);
if ( ! Result)
{
printf( " !!! " );
return false ;
}
memcpy(( void * )pbuf,( void * )Packet[k].Buffer,dwByteReceive);
* BytesReceived = dwByteReceive;
CloseHandle(Packet[k].OverLapped.hEvent);
for (j = i;j < 32 ;i ++ )
hEvent[i] = hEvent[ ++ j];
hEventTemp = CreateEvent( 0 , TRUE, 0 , NULL);
if ( ! hEventTemp) {
printf( " Can not create event! " );
return false ;
}
Packet[k].OverLapped.hEvent = hEventTemp;
memset(Packet[k].Buffer, 0 , 1514 );
Packet[k].Length = 1514 ;
hEvent[ 31 ] = hEventTemp;
// k返回了,就再读K一次
Result = ReadFile(
hGt,
Packet[k].Buffer,
Packet[k].Length,
& dwByteReceive,
& Packet[k].OverLapped
);
return Result;
}
int main( int argc, char * argv[])
{
HANDLE hGT;
unsigned char buf[ 2000 ];
char show[ 10000 ];
unsigned long cb;
char msg[ 200 ];
ULONG END_OF_PACKET;
BOOLEAN PacketValidBit;
ULONG PacketLen;
USHORT PacketChannelNumber;
int iCount = 0 ;
hGT = CreateFile( " //./GT48001A0 " ,
GENERIC_WRITE | GENERIC_READ,
0 ,
NULL,
CREATE_ALWAYS,
FILE_FLAG_OVERLAPPED,
0
);
if (hGT == INVALID_HANDLE_VALUE) {
MessageBox(NULL, " OK " ,NULL,MB_OK);
return 0 ;
}
for ( int j = 0 ; true ;j ++ ) // ctrl+c就退出循环
{
if ( ! RecvPacket(buf, & cb,hGT)) // 核心就是这个函数
{
if (cb)
{
下面不过是打印出数据而已
END_OF_PACKET = * buf + ( * (buf + 1 )) * 0x100 + ( * (buf + 2 )) * 0x10000 + ( * (buf + 3 )) * 0x1000000 ;
// let 's see the packet len
PacketLen = (END_OF_PACKET & 0xffe ) / 2 ;
PacketValidBit = (UCHAR)(END_OF_PACKET & 0x1 );
PacketChannelNumber = (UCHAR)((END_OF_PACKET & 0x7000 ) / 0x1000 );
printf( " PacketLen=%d,PacketValidBit=%d,PacketChannelNumber=%d " ,PacketLen,PacketValidBit,PacketChannelNumber);
for (unsigned long i = 0 ;i < PacketLen;i ++ )
{
sprintf(show + 3 * i, " |%02x " ,buf[i + 32 ]);
}
sprintf(msg, " read %d -32 bytes " ,cb);
printf( " %s,count = %d " ,msg,iCount);
printf( " %s " ,show);
iCount ++ ;
}
}
}
CloseHandle(hGT);
return true ;
}
creatfile之后,我就开始进行32次readfile,因为是异步读,
立即返回了,然后我就waitmultiobject,看是否有数据返回,
如果有,再读一次,始终保持有32个irp在驱动程序队列。
这样,就不会丢失数据。
这段代码是app和驱动程序通讯的核心,如果真想搞清楚,
请仔细读。
最后,我还要说一点的是,nt的所有操作都是异步的,readfile
如果overlapped参数为null,在win32上是同步,但是在ntdll.dll
同步得,就是说,核心驱动程序还是异步,不过在ntdll.dll调用
getoverlappedresult阻塞而已。ntdll.dll直到数据真正返回了之后
才返回到win32。
本来后面还应该有2000下的变化的,但是
由于1变化不多,主要是pnp方面的,2我实在
没有体力了。
后面我也许还要写点关于安装,inf文件的东西,
因为在2000下用nt驱动程序,有些东西需要注意,
这些都在inf文件里面体现。
现在我实在是累了,以后再说吧。