天天看點

USB轉序列槽驅動代碼分析

driverobject->driverextension->adddevice = usb2com_pnpadddevice;  

(1) iocreatedevice系統api的原理為:

ntkernelapi  

ntstatus  

iocreatedevice(  

    in pdriver_object driverobject,  

    in ulong deviceextensionsize,  

    in punicode_string devicename optional,  

    in device_type devicetype,  

    in ulong devicecharacteristics,  

    in boolean reserved,  

    out pdevice_object *deviceobject  

    );  

在之前真實的usb驅動中我們是這樣建立的:

ntstatus = iocreatedevice(  

                    driverobject,                   // our driver object  

                    sizeof(device_extension),       // extension size for us  

                    null,                           // name for this device  

                    file_device_unknown,  

                    file_autogenerated_device_name, // device characteristics  

                    false,                          // not exclusive  

                    &deviceobject);                 // our device object  

就是第三個參數為null, 第四個參數為file_device_unknown,意味着我們驅動想附加的裝置是空的,且未知。

由于我們是建立虛拟序列槽驅動,是以調用iocreatedevice建立時在指定序列槽裝置的名字,且指定裝置類型。

ntstatus = iocreatedevice(driverobject, sizeof(device_extension),  

                           &deviceobjname, file_device_serial_port,  

                           file_device_secure_open, true, deviceobject);  

(2)為自定義的擴充裝置中的裝置名字段指定裝置名

// deviceextension->devicename為unicode_string類型  

rtlzeromemory(&deviceextension->devicename, sizeof(unicode_string));  

deviceextension->devicename.maximumlength = deviceobjname.length + sizeof(wchar);  

// buffer重新配置設定  

deviceextension->devicename.buffer = usb2com_exallocatepool(nonpagedpool, deviceobjname.length + sizeof(wchar));  

rtlzeromemory(deviceextension->devicename.buffer,  

        deviceobjname.length+sizeof(wchar));  

rtlappendunicodestringtostring(&deviceextension->devicename, &deviceobjname);  

(3)初始化事件、序列槽控件對象、關鍵代碼段、自旋鎖、讀寫隊列連結清單。

 // this event is triggered when there is no pending io of any kind and device is removed  

keinitializeevent(&deviceextension->removeevent, notificationevent, false);  

// this event is triggered when self-requested power irps complete  

keinitializeevent(&deviceextension->selfrequestedpowerirpevent, notificationevent, false);  

// this event is triggered when there is no pending io  (pending io count == 1 )  

keinitializeevent(&deviceextension->nopendingioevent, notificationevent, false);  

// spinlock used to protect inc/dec iocount logic  

keinitializespinlock (&deviceextension->iocountspinlock);  

    deviceextension->baudrate = 19200;  

/* set line control */  

deviceextension->seriallinecontrol.stopbits = stop_bit_1;  

deviceextension->seriallinecontrol.parity = no_parity;  

deviceextension->seriallinecontrol.wordlength = 8;  

deviceextension->specialchars.xonchar = serial_def_xon;  

deviceextension->specialchars.xoffchar = serial_def_xoff;  

deviceextension->handflow.controlhandshake = serial_dtr_control;  

deviceextension->handflow.flowreplace      = serial_rts_control;  

deviceextension->handflow.xofflimit    = 300;  

deviceextension->handflow.xonlimit     = 100;  

initializecircularbuffer(&deviceextension->inputbuffer, 512);  

initializecircularbuffer(&deviceextension->outputbuffer, 512);  

keinitializespinlock(&deviceextension->inputbufferlock);  

keinitializespinlock(&deviceextension->outputbufferlock);  

initializelisthead(&deviceextension->readqueue);  

keinitializespinlock(&deviceextension->readqueuespinlock);  

initializelisthead(&deviceextension->writequeue);  

keinitializespinlock(&deviceextension->writequeuespinlock);  

initializelisthead(&deviceextension->purgequeue);  

keinitializespinlock(&deviceextension->purgequeuespinlock);  

 設定do_power_pagable的目的是在suspend期間不接收一個irp_mn_stop_device,

在resume時不接收一個irp_mn_start_device消息。

// we support direct io for read/write  

       //  

       deviceobject->flags |= do_direct_io;  

       //set this flag causes the driver to not receive a irp_mn_stop_device  

       //during suspend and also not get an irp_mn_start_device during resume.  

       //this is neccesary because during the start device call,  

       // the getdescriptors() call  will be failed by the usb stack.  

       deviceobject->flags |= do_power_pagable;  

USB轉序列槽驅動代碼分析

deviceextension->topofstackdeviceobject =  

            ioattachdevicetodevicestack(deviceobject, physicaldeviceobject);  

status = ioregisterdeviceinterface(pdevext->physicaldeviceobject, (lpguid)&guid_class_comport,  

                                      null, &pdevext->deviceclasssymbolicname);  

 (1)建立irp來産生一個發往fdo的内部查詢請求;

irp = ioallocateirp(lowerdeviceobject->stacksize, false);  

(2)設定irp要發往的裝置棧location(是更低層的裝置,在這裡就是它附加下的usb)的資訊,

eg: majorfunction、minorfunction、parameters.devicecapabilities.capabilities

nextstack = iogetnextirpstacklocation(irp);  

nextstack->majorfunction= irp_mj_pnp;  

nextstack->minorfunction= irp_mn_query_capabilities;  

在以上代碼中的iogetnextirpstacklocation是一個宏,它的定義如下:

#define iogetnextirpstacklocation( irp ) (\  

    (irp)->tail.overlay.currentstacklocation - 1 )  

從以上宏可以看出:irp結構體中存有目前的棧location currentstackloctation,而我們的irp要發往的棧location的獲得方法就是原有棧的位址 - 1。

 (3)設定irp的完成例程;(在完成全程中就是把事件激活,這樣kewaitforsingleobject就能走下來)

(4)把irp發送下去,并等待完成;

ntstatus = iocalldriver(lowerdeviceobject,  

                           irp);  

   usb2com_kdprint( dbglvl_medium,(" usb2com_querycapabilities() ntstatus from iocalldriver to pci = 0x%x\n", ntstatus));  

   if (ntstatus == status_pending) {  

      // wait for irp to complete  

      kewaitforsingleobject(  

           &event,  

           suspended,  

           kernelmode,  

           false,  

           null);  

當完成全程傳回時, nextstack->parameters.devicecapabilities.capabilities = devicecapabilities;指針就存着我們的性能資訊。

直接調用系統api:

usbd_getusbdiversion(&versioninformation);  

driverobject->majorfunction[irp_mj_pnp] = usb2com_processpnpirp;  

在usb2com_processpnpirp裡case了以下幾個消息:

irp_mn_start_device 、irp_mn_query_stop_device、irp_mn_cancel_stop_device、irp_mn_stop_device、

irp_mn_query_remove_device、irp_mn_cancel_remove_device、irp_mn_surprise_removal、irp_mn_remove_device

irp_mn_start_device、

irp_mn_stop_device 、irp_mn_eject和irp_mn_surprise_removal

是以一比較,覺得usb2com考慮得更全面。

(1)irp_mn_start_device

(2)irp_mn_query_stop_device

(3)irp_mn_cancel_stop_device

(4)irp_mn_stop_device、

(5)irp_mn_query_remove_device、

(6)irp_mn_cancel_remove_device、

(7)irp_mn_surprise_removal、

(8)irp_mn_remove_device

 driverobject->majorfunction[irp_mj_create] = usb2com_create;

如果不能接收一個新的io請求,那麼直接傳回。

if ( !usb2com_canacceptiorequests( deviceobject ) ) {  

      ntstatus = status_delete_pending;  

usb2com_kdprint( dbglvl_default,("aborting usb2com_create\n"));  

      goto done;  

  }  

在以下的條件不能接收一個新的io(判斷的标志是我們自己标記的):

1) 裝置已經被移除了,

2) 從來沒有被啟動過,,

3) 已經停止了,

4) 有一個移除的請求還沒處理,

5) 有一個停止的請求還沒處理。

//flag set when processing irp_mn_remove_device  

    if ( !deviceextension->deviceremoved &&  

         // device must be started( enabled )  

         deviceextension->devicestarted &&  

         // flag set when driver has answered success to irp_mn_query_remove_device  

         !deviceextension->removedevicerequested &&  

         // flag set when driver has answered success to irp_mn_query_stop_device  

         !deviceextension->stopdevicerequested ){  

            fcan = true;  

    }  

startreadinturb(  

        deviceobject,  

        &interface->pipes[0]  

        );  

(1)如果判斷現在不能接收io請求,那麼就記錄在extension中的irp置為完成狀态,然後傳回;

(2)配置設定irp、urb空間,并存到extension->readinturbs數組中,再把pipe句柄、buffer、傳送标志填入到urb結構體成員;

irp = ioallocateirp(stacksize, false);  

        if(irp == null)   

        {  

                return status_insufficient_resources;  

        }  

        urb = usb2com_exallocatepool(nonpagedpool,   

                        sizeof(struct _urb_bulk_or_interrupt_transfer));  

        if(urb == null)  

            iofreeirp(irp);  

        return status_insufficient_resources;  

        deviceextension->readinturbs[i].irp = irp;  

        deviceextension->readinturbs[i].urb = urb;  

        deviceextension->readinturbs[i].deviceobject = deviceobject;  

        deviceextension->readinturbs[i].pipeinfo = pipeinfo;  

        initinturb(urb,  

                pipeinfo->pipehandle,  

                deviceextension->readinturbs[i].transferbuffer,  

                sizeof(deviceextension->readinturbs[i].transferbuffer),  

                true);  

initinturb為自己封裝的函數,很簡單,就是把資料填入到urb結構體中:

void  

initinturb(  

    in purb urb,  

    in usbd_pipe_handle  pipehandle,  

    in puchar transferbuffer,  

    in ulong length,  

    in boolean read  

    )  

{  

    ushort siz = sizeof(struct _urb_bulk_or_interrupt_transfer);  

    if (urb) {  

        rtlzeromemory(urb, siz);  

        urb->urbbulkorinterrupttransfer.hdr.length = (ushort) siz;  

        urb->urbbulkorinterrupttransfer.hdr.function =  

                    urb_function_bulk_or_interrupt_transfer;  

        urb->urbbulkorinterrupttransfer.pipehandle = pipehandle;  

        urb->urbbulkorinterrupttransfer.transferflags =  

            read ? usbd_transfer_direction_in : 0;  

        // short packet is not treated as an error.  

        urb->urbbulkorinterrupttransfer.transferflags |=   

            usbd_short_transfer_ok;              

        //  

        // not using linked urb's  

        urb->urbbulkorinterrupttransfer.urblink = null;  

        urb->urbbulkorinterrupttransfer.transferbuffermdl = null;  

        urb->urbbulkorinterrupttransfer.transferbuffer = transferbuffer;  

        urb->urbbulkorinterrupttransfer.transferbufferlength = length;  

}  

(3)初始化irp的棧location及它的完成例程readinturbcomplete,并把目前readinturbs的記憶體作為完成例程的context;

(4)完成例程readinturbcomplete的處理;

a、判斷傳回執行後傳回的irp、urb的狀态,如果為未連接配接或取消那麼就釋放irp及urb記憶體,并完成把extension中的irp置為完成狀态,然後直接傳回;否則往下執行;

b、把irp之前綁定的buffer資料拷到inputbuffer中(現在的buffer就是我們的結果資料);

keacquirespinlock(&deviceextension->inputbufferlock, &oldirql);  

            pushcircularbufferentry(  

                &deviceextension->inputbuffer,  

                &pinturbs->transferbuffer[1],  

                pinturbs->transferbuffer[0]);  

            kereleasespinlock(&deviceextension->inputbufferlock, oldirql);  

pushcircularbufferentry為自己封裝的函數:

把data記憶體中的len個資料拷到pbuffer中

pushcircularbufferentry(  

    in pcircular_buffer pbuffer,  

    in puchar data,  

    in ulong len)  

    ulong nextposition;  

    dbgprint("serial: pushcircularbufferentry(data %p, len %d)\n", data, len);  

    assert(pbuffer);  

    assert(pbuffer->length);  

    if ((data == null) || (len == 0))  

        return status_invalid_parameter;  

    do{  

        nextposition = (pbuffer->writeposition + 1) % pbuffer->length;  

        if (nextposition == pbuffer->readposition)  

            return status_buffer_too_small;  

        pbuffer->buffer[pbuffer->writeposition] = *data++;  

        pbuffer->datalen++;  

        pbuffer->writeposition = nextposition;  

    }while(--len);  

    return status_success;  

c、如果extension中有等待的irp,且等待的事件中有serial_ev_rxchar,那麼通過serialcompletecurrentwait完成等待序列槽的等待irp。

c、1   把目前irp的取消完成例程置為null,根據包是否已經被取消标志pirp->cancel 及之前的irp取消例程是否被執行過了,那麼調用serialcancelcurrentwait來取消。

注意serialcancelcurrentwait中很重要的是要通過調用ioreleasecancelspinlock來釋放系統的删除自旋鎖。

serialcancelcurrentwait( pdevice_object deviceobject, pirp pirp )  

    pio_stack_location irpstack;  

    pdevice_extension deviceextension;  

    dbgprint("serialcancelcurrentwait enter irp = %p\n",pirp);  

    assert(pirp);  

    irpstack = iogetcurrentirpstacklocation(pirp);  

    deviceextension = irpstack->deviceobject->deviceextension;  

    deviceextension->currentwaitirp = null;  

    /* 

    *all cancel routines must follow these guidelines: 

    * 1. call ioreleasecancelspinlock to release the system's cancel spin lock 

    * 2. ... 

    */  

    ioreleasecancelspinlock(pirp->cancelirql);  

    pirp->iostatus.status = status_cancelled;  

    pirp->iostatus.information = 0;  

    iocompleterequest(pirp, io_no_increment);  

    dbgprint("serialcancelcurrentwait exit\n");  

c、2    如果沒有被取消,那麼把目前我們的事件傳回給應用層,并完成irp。

deviceextension->currentwaitirp = null;  

deviceextension->historymask &= ~events;  

ioreleasecancelspinlock(oldirql);  

pirp->iostatus.information = sizeof(ulong);  

pirp->iostatus.status = ntstatus;  

*((ulong *)pirp->associatedirp.systembuffer) = events;  

iocompleterequest (pirp,io_no_increment);  

d、完成目前的讀irp;

d、1   如果有目前讀的irp,那麼把第(4)b、裡得到的結果資料彈出到目前的讀irp中(通過目前讀的irp中的mdladdress位址通路到記憶體)。

if(deviceextension->currentreadirp)  

    ulong       havelen;  

    boolean     returnwhatspresent = false;  

    if(deviceextension->serialtimeouts.readintervaltimeout &&  

        ( deviceextension->serialtimeouts.readtotaltimeoutmultiplier == 0) &&  

        ( deviceextension->serialtimeouts.readtotaltimeoutconstant == 0)  

    {  

        returnwhatspresent = true;  

    iobuffer = mmgetsystemaddressformdlsafe(deviceextension->currentreadirp->mdladdress,normalpagepriority );  

    iolength = mmgetmdlbytecount(deviceextension->currentreadirp->mdladdress);  

    keacquirespinlock(&deviceextension->inputbufferlock, &oldirql);  

    havelen = circularbufferdatalen(&deviceextension->inputbuffer);  

    if( (iolength <= havelen) || (returnwhatspresent && (havelen > 0)))  

        iolength = (iolength < havelen) ? iolength : havelen;  

        dbgprint("complete currentreadirp iolength = %d\n",iolength);  

        ntstatus = popcircularbufferentry(&deviceextension->inputbuffer,iobuffer,iolength);  

        kereleasespinlock(&deviceextension->inputbufferlock, oldirql);  

        deviceextension->currentreadirp->iostatus.information = iolength;  

        deviceextension->currentreadirp->iostatus.status = ntstatus;   

        iocompleterequest(deviceextension->currentreadirp,io_no_increment);  

        deviceextension->currentreadirp = dequeuereadirp(deviceextension);  

    else  

以上代碼中mmgetsystemaddressformdlsafe

// 函數說明:  

//     此函數傳回mdl映射的位址,如果此mdl還沒有被映射,那麼它将會被映射  

// 參數:  

//     memorydescriptorlist - 指向mdl的指針  

//     priority - 指向一個标志,該标記表明它是如何的重要,以至于這個請求在低的可用pte條件下也成功了  

// 傳回值:  

//     傳回映射頁的基位址,這個基位址和mdl的虛拟位址有同樣的偏移位址.  

//     與mmgetsystemaddressformdl不同,在失敗時它會傳回null,而不是bugchecking the system.  

// 版本說明:  

//     此宏在wdm 1.0中不同用,在wdm1.0的驅動中實作此功能是通過提供同步并set/reset mdl_mapping_can_fail bit.  

#define mmgetsystemaddressformdlsafe(mdl, priority)                    \  

     (((mdl)->mdlflags & (mdl_mapped_to_system_va |                    \  

                        mdl_source_is_nonpaged_pool)) ?                \  

                             ((mdl)->mappedsystemva) :                 \  

                             (mmmaplockedpagesspecifycache((mdl),      \  

                                                           kernelmode, \  

                                                           mmcached,   \  

                                                           null,       \  

                                                           false,      \  

                                                           (priority))))  

第一個參數是deviceextension->currentreadirp->mdladdress,我們從wdm.h可以看到它的定義:

// 定義一個指向這個i/o請求的記憶體描述符(mdl)的指針,  

// 此域僅在i/o是“direct i/o"時被用  

pmdl mdladdress;  

d、2   判斷是否能接收一個新的io請求

如果不能接收一個新的io請求或者pipe[0]關閉了,那麼直接傳回。

d、3   循環發送irp,并置完成例程為readinturbcomplete。(這樣,就又回到了(4)),何時結束呢?

結束條件:直至裝置不能接收一個新的io請求或未連接配接、取消。

是以處理目前讀irp是一直進行的。

preparewriteinturb(  

    in pdevice_object deviceobject,  

    in pusbd_pipe_information pipeinfo  

a、配置設定寫的irb 與 urb空間。

b、儲存pipe[1]位址為deviceextension->writeinturb.pipeinfo存在extension中。

應用程式的名字從目前irp中的fileobject得到。

ourpipeinfo = usb2com_pipewithname( deviceobject, &fileobject->filename );  

for (i=0; i<interface->numberofpipes; i++) {  

        pipeinfo =  &interface->pipes[i]; // pusbd_pipe_information  pipeinfo;  

        if ( ourpipeinfo == &deviceextension->pipeinfo[i] ) {  

            //  

            // found a match  

            usb2com_kdprint( dbglvl_default,("open pipe %d\n", i));  

            fileobject->fscontext = pipeinfo;  

            ourpipeinfo->fpipeopened = true; // set flag for opened  

            ntstatus = status_success;  

            deviceextension->openpipecount++;  

            // try to power up device if its not in d0  

            actstat = usb2com_selfsuspendoractivate( deviceobject, false );  

            break;  

USB轉序列槽驅動代碼分析

繼續閱讀