nt/2000pci设备驱动程序详解

来源:互联网 发布:网络交换机辐射大不大 编辑:程序博客网 时间:2024/05/16 15:16

 常常有朋友问我关于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 "stdio.h" 
#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

}     
这个程序是我97年的时候写的,后来就没有改过,在各种场合下用过很多次,我一般在写pci
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);最后
IoStartPacket(DeviceObject, 
                  Irp, 
                  NULL, 
                  GT48001ACancel 
                  ); 
    
return STATUS_PENDING; 

这里需要说明一点,iostartpacket是用来让系统启动startio的,startio我以前的理解
是用来做一些板卡准备工作的,有些卡较简单,不需要什么准备工作,唯一的事情就是
等待中断,在这种情况下,startio可以设成空函数,也可以在这里处理cancel。
cancel非常重要,绝对不要不写,特别对于数据采集卡来说更是这样。因为app层不知道
什么时候会有数据,app一般都是发几个read irp下去,然后等待event,如果没有cancelroutine,
那么结果就是app关闭了,但是irp却没有清除,下一次运行app的时候,头几个数据会丢失。
这个问题我决定放在app的处理那里仔细讲,这里先放下。

NTSTATUS 
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说一下:

 

VOID 
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

 

VOID 
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,下面让我们来看看代码:

 

BOOLEAN 
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概念还是似懂非懂,仔细
看过之后我感觉进了一大步。

这段代码事我用来测试我前面讲过的那个驱动程序的。

 

// testgt48001a.cpp : Defines the entry point for the console application. 
// 

#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文件里面体现。
现在我实在是累了,以后再说吧。