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

 常常有朋友问我关于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文件里面体现。
现在我实在是累了,以后再说吧。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值