Condividi tramite


Ricezione di pacchetti di richiesta di I/O asincroni nel bus IEEE 1394

Il computer stesso è un nodo sul bus IEEE 1394 e quindi può ricevere richieste di I/O asincrone. I driver possono allocare intervalli di indirizzi nello spazio indirizzi IEEE 1394 del computer e ricevere richieste da nodi esterni inviando la richiesta di REQUEST_ALLOCATE_ADDRESS_RANGE al driver del bus.

Quando il driver alloca l'intervallo di indirizzi, può specificare quali tipi di transazioni un dispositivo può inviare agli indirizzi allocati, specificando uno o più ACCESS_FLAGS_TYPE_READ, ACCESS_FLAGS_TYPE_WRITE o ACCESS_FLAGS_TYPE_LOCK nel membro U.AllocateAddressRange.fulAccessType della richiesta IRB. Le richieste che non sono uno dei tipi specificati hanno esito negativo automaticamente.

Due driver diversi possono allocare lo stesso intervallo di indirizzi. Per impostazione predefinita, il driver del bus demultiplexa automaticamente le richieste e il driver visualizza solo le richieste sugli indirizzi allocati provenienti dal dispositivo del driver. I driver possono richiedere che ricevano tutti i pacchetti inviati agli indirizzi da tutti i nodi del bus, specificando il flag ACCESS_FLAGS_TYPE_BROADCAST in u.AllocateAddressRange.fulAccessType.

Indirizzi allocati

Il driver del bus supporta due strategie diverse per l'allocazione degli intervalli di indirizzi. Se il driver richiede un intervallo specifico di indirizzi, a partire da un indirizzo hardcoded, può specificare l'indirizzo hardcoded nell'u.AllocateAddressRange.Required1394Offset del membro IRB della richiesta e la lunghezza dell'intervallo di indirizzi in u.AllocateAddressRange.nLength. Il driver del bus consentirà a due driver diversi di allocare lo stesso indirizzo due volte. Se lo stesso driver tenta di allocare un intervallo di indirizzi che inizia con lo stesso indirizzo due volte, il driver del bus restituisce la richiesta con un codice di stato di STATUS_SUCCESS, ma la richiesta stessa viene ignorata.

In caso contrario, il driver può consentire al driver del bus di scegliere gli indirizzi allocati. Il driver del bus tiene traccia di tutti gli intervalli di indirizzi allocati dai driver e restituirà solo gli indirizzi non assegnati in precedenza.

Il driver del bus non alloca l'indirizzo contiguo. Gli indirizzi vengono segmentati in base all'archivio MDL fornito come archivio di backup. Ogni segmento nel MDL corrisponde a un segmento nell'intervallo di indirizzi. Un driver che richiede che gli indirizzi allocati siano contigui possono allocare memoria contigua dal pool non con pagine.

Se il driver deve garantire che ogni segmento sia inferiore a una dimensione specifica, è possibile specificare tale dimensione in u.AllocateAddressRange.MaxSegmentSize. I driver che non devono specificare una dimensione massima del segmento impostata su u.AllocateAddressRange.MaxSegmentSize su zero.

Il driver del bus restituisce gli intervalli di indirizzi nella posizione di memoria a cui fa riferimento il membro U.AllocateAddressRange.p1394AddressRange dell'IRB. Il driver del dispositivo deve allocare una matrice sufficientemente grande per contenere ogni struttura ADDRESS_RANGE, anche nello scenario di segmentazione del caso peggiore. Se il driver non specifica una dimensione del segmento o la dimensione massima del segmento è maggiore di PAGE_SIZE, il driver può determinare il caso peggiore usando la macro ADDRESS_AND_SIZE_TO_SPAN_PAGES nel buffer usato per l'archivio di backup. Se la dimensione massima del segmento è inferiore a PAGE_SIZE, il driver deve allocare una matrice di dimensioni u.AllocateAddressRange.nLength/u.AllocateAddressRange.MaxSegmentSize + 2.

Quando il driver del bus restituisce gli indirizzi allocati, registra il numero effettivo di intervalli di indirizzi allocati in u.AllocateAddressRange.hAddressRange.

Allocazione e backup dell'archivio

Il driver del bus riceve tutte le richieste di pacchetti asincrone per conto del driver. Al momento della richiesta del driver, può gestire in modo trasparente la richiesta oppure inviare la richiesta al driver. Impostando le opzioni quando alloca gli indirizzi, il driver può scegliere come gestisce ogni richiesta.

  1. Il driver fornisce l'archivio di backup per l'intervallo di indirizzi e il driver del bus gestisce in modo trasparente tutte le richieste di lettura, scrittura e blocco usando l'archivio di backup.

    Quando il driver alloca gli indirizzi, può fornire un MDL in u.AllocateAddressRange.Mdl per fungere da archivio di backup. Il driver del bus esegue il mapping dell'MDL all'intervallo di indirizzi allocati per il driver e gestisce tutte le richieste leggendo o scrivendo dall'MDL. Se il controller host lo supporta, la transazione viene gestita interamente dall'hardware DMA del controller host. Quando possibile, il driver del dispositivo deve consentire al driver del bus di scegliere l'intervallo di indirizzi allocati: il driver del bus sceglierà 1394 indirizzi che supportano LMA automatico per ogni transazione.

    Il driver deve specificare NOTIFY_FLAGS_NEVER per u.AllocateAddressRange.fulNotificationOptions.

    Ecco un esempio:

    pIRB->u.AllocateAddressRange.Mdl = an MDL previously allocated by the driver.
    pIRB->u.AllocateAddressRange.fulNotificationOptions = NOTIFY_FLAGS_NEVER
    

    Per questa opzione, il driver può anche allocare gli intervalli di indirizzi durante la generazione di IRQL. Il driver può inviare la richiesta direttamente al driver della porta, ignorando il consueto metodo IRP di comunicazione, chiamando la routine di mapping fisica del driver della porta. Il driver del dispositivo passa l'IRB alla routine di mapping fisico del driver della porta. Il driver della porta allocherà quindi l'intervallo di indirizzi in modo asincrono; al termine del driver di porta, chiama la routine di notifica del driver del dispositivo, passata in u.AllocateAddressRange.Callback e passa u.AllocateAddressRange.Context come parametro. La routine di notifica viene chiamata in DISPATCH_LEVEL.

    Il driver del dispositivo può ottenere un puntatore alla routine di mapping fisico del driver della porta inviando la richiesta di GET_LOCAL_HOST_INFO al driver del bus, con nLevel = GET_PHYS_ADDR_ROUTINE. Il driver del bus restituisce una struttura GET_LOCAL_HOST_INFO4, che contiene la routine di mapping fisico e un parametro di contesto che il driver del dispositivo passa insieme alla routine di mapping fisico IRB.

    Ecco un esempio di come il driver può configurare la richiesta per la routine di mapping fisico. La routine di mapping fisico non cambia, quindi il driver normalmente invia questa richiesta una sola volta.

    GET_LOCAL_HOST_INFO5 PhysMapInfo;
    pGetInfoIRB->FunctionNumber = GET_LOCAL_HOST_INFO;
    pGetInfoIRB->GET_PHYS_ADDR_ROUTINE;
    
    /* Driver submits the request. */
    

    Continuando l'esempio, ecco come il driver può usare la routine di mapping fisico per inviare la richiesta a un IRQL con privilegi elevati.

    VOID AllocationCompletionRoutine(PVOID Context);
    /* Driver fills out the members of the IRB, including these: */
    pIRB->u.AllocateAddressRange.Mdl = an MDL previously allocated by the driver.
    pIRB->u.AllocateAddressRange.fulNotificationOptions = NOTIFY_FLAGS_NEVER
    pIRB->Callback = AllocationCompletionRoutine;
    pIRB->Context = information specific to this allocation the driver wants passed to its callback.
    
    /* Driver submits the request to the physical mapping routine. */
    PhysMapInfo.PhysAddrMappingRoutine(PhysMapInfo.Context, pIRB);
    
    /*
    The bus driver does the allocation asynchronously. When it's done, it will signal the driver by executing AllocationCompletionRoutine(pIRB->u.AllocateAddressRange.Context);
    */
    
  2. Il driver fornisce l'archivio di backup per l'intervallo di indirizzi. Il driver del bus notifica al driver dopo il completamento di ogni transazione di I/O.

    Il driver può fornire un singolo MDL o un elenco collegato di MDLs, come archivio di backup. Se il driver fornisce un singolo MDL, il driver del bus pompa i dati in o fuori dall'MDL in risposta alla richiesta asincrona. Al termine della transazione, segnala il driver del dispositivo chiamando il callback di notifica fornito dal driver.

    Il driver del dispositivo fornisce la routine di notifica nel membro u.AllocateAddressRange.Callback dell'IRB. Il driver deve impostare almeno uno dei flag di NOTIFY_FLAGS_AFTER_XXX. Quando il driver del bus chiama la routine, passa una struttura NOTIFICATION_INFO, che specifica l'archivio di backup MDL (in Mdl), l'offset di byte all'interno dell'MDL in cui inizia la transazione (in ulOffset) e la lunghezza in byte dell'intervallo di indirizzi interessati (in nLength). La routine di notifica viene chiamata in DISPATCH_LEVEL. Tutte le informazioni di contesto per questa richiesta che il driver passa in u.AllocateAddressRange.Context viene passato dal driver del bus nel membro Contesto di NOTIFICATION_INFO.

    L'uso di un solo MDL comporta alcuni problemi di sincronizzazione: il dispositivo può scrivere nell'intervallo di indirizzi più veloce rispetto al driver in grado di leggerlo. Per evitare questo scontro, per gli indirizzi a cui il dispositivo ha accesso in scrittura solo il driver può fornire un elenco collegato di MDLs, in u.AllocateAddressRange.FifoSListHead e un blocco spin in u.AllocateAddressRange.FifoSpinLock. Quando il driver del bus riceve ogni pacchetto di richiesta asincrona, contiene il blocco di rotazione e visualizza il primo elemento nell'elenco per soddisfare la richiesta. Chiama quindi la routine di notifica del driver.

    Nella struttura NOTIFICATION_INFO il driver del bus fornisce l'MDL usato per gestire la transazione (in Mdl), l'offset di byte del primo indirizzo interessato (in ulOffset) e la lunghezza degli intervalli di indirizzi interessati (in nLength). Fornisce anche la ADDRESS_FIFO del MDL (in Fifo). Prima che il driver venga restituito dalla routine di notifica, deve usare Fifo per eseguire il push dell'elemento nell'elenco o specificare un altro MDL della stessa dimensione; in caso contrario, il driver del bus verrà esaurito dagli MDL da usare per gestire le richieste di scrittura dal dispositivo.

    Ecco un esempio esteso dell'uso di questo tipo di notifica. A livello globale, il driver crea un elenco interlock, collegato a singly e un blocco di rotazione. Il driver deve accedere all'elenco collegato e al blocco di rotazione in irQLs generati, quindi devono essere in memoria non di pagina. In genere, i driver li mantengono nelle estensioni del dispositivo.

    PSLIST_HEADER FifoSListHead;
    KSPIN_LOCK FifoSpinLock;
    
    ExInitializeSListHead(FifoSListHead);
    KeInitializeSpinLock(FifoSpinLock);
    

    Quando il driver invia la richiesta, può configurare i membri IRB pertinenti come indicato di seguito:

    VOID DriverNotificationRoutine(PNOTIFICATION_INFO NotificationInfo);
    
    pIRB->u.AllocateAddressRange.Mdl = NULL;
    pIRB->u.AllocateAddressRange.fulAccessType = ACCESS_FLAGS_READ;
    pIRB->u.AllocateAddressRange.fulNotificationOptions = NOTIFY_FLAGS_AFTER_WRITE;
    pIRB->u.AllocateAddressRange.FifoSListHead = FifoSListHead;
    pIRB->u.AllocateAddressRange.FifoSpinLock = FifoSpinLock;
    pIRB->u.AllocateAddressRange.Callback = DriverNotificationRoutine;
    pIRB->u.AllocateAddressRange.Context = context information specific to this request -- the bus driver will pass this as the Context member of the NOTIFICATION_INFO it passes to NotificationRoutine.
    

    Nel callback, il driver deve allocare un nuovo MDL e eseguirne il push nell'elenco oppure eseguire il push dell'oggetto MDL originale nell'elenco. Per quest'ultimo, il conducente del bus passa il ADDRESS_FIFO originale per l'MDL corrente. Ecco un esempio del driver che esegue il push dell'MDL corrente nell'elenco:

    ExInterlockedPushEntrySList(FifoSListHead, NotificationInfo->Fifo->FifoList, FifoSpinLock);
    

    Se il driver specifica un singolo MDL come archivio di backup nella richiesta di allocazione originale, il driver può restituire uno o più intervalli di indirizzi.

  3. Il driver del bus segnala al conducente ogni volta che una richiesta arriva e passa il pacchetto al conducente.

    Il driver fornisce un callback nel membro u.AllocateAddressRange.Callback di IRB. I flag NOTIFY_FLAGS_AFTER_XXX vengono ignorati e tutti i pacchetti vengono passati al driver per gestire.

    Il driver deve impostare i membri Mdl, FifoSListHead e FifoSpinLock di u.AllocateAddressRange su NULL. Ecco un esempio delle impostazioni per un driver che desidera ricevere una notifica quando riceve pacchetti di richieste asincrone di tutti e tre i tipi.

    VOID DriverNotificationRoutine( IN PNOTIFICATION_INFO NotificationInfo );
    
    pIRB->u.AllocateAddressRange.Callback = DriverNotificationRoutine;
    pIRB->u.AllocateAddressRange.Context = information specific to this address range.
    pIRB->u.AllocateAddressRange.Mdl = NULL;
    pIRB->u.AllocateAddressRange.FifoSListHead = NULL;
    pIRB->u.AllocateAddressRange.FifoSpinLock = NULL;
    

    Il driver del bus alloca un singolo intervallo contiguo di indirizzi.

    Il conducente del bus passa una struttura NOTIFICATION_INFO alla routine di callback del conducente. Il driver di dispositivo deve creare il proprio pacchetto di risposta (vedere la specifica IEEE 1394 per i dettagli) e deve allocare il proprio buffer per contenere il pacchetto di risposta creato. Il pacchetto di risposta deve essere dal pool non a pagina o da un buffer che è stato eseguito il probe e bloccato.

    All'interno di NOTIFICATION_INFO, il driver del bus fornisce un MDL non inizializzato nel membro ResponseMdl . Fornisce anche puntatori alle posizioni di memoria in cui si prevede che il driver del dispositivo immetti un puntatore al pacchetto di risposta (in ResponsePacket) e la lunghezza del pacchetto di risposta (in ResponseLength). Il driver può anche fornire un oggetto evento kernel. Il driver del bus segnala l'oggetto evento kernel una volta completata la transazione.

    Ecco un esempio di come il driver del dispositivo può compilare le informazioni necessarie nella routine di notifica.

    /* Suppose the driver creates its response packet in PVOID ResponsePacket, of length ResponseLength. It has created a kernel event ResponseEvent. */
    MmInitializeMdl(NotificationInfo->ResponseMdl, ResponsePacket, ResponseLength);
    MmBuildMdlFForNonPagedPool(Notification->ResponseMdl);
    *(NotificationInfo->ResponsePacket) = ResponsePacket.
    *(NotificationInfo->ResponseLength) = ResponseLength;
    *(NotificationInfo)->ResponseEvent = Event;
    

Routine di notifica del driver client per ricevere richieste di I/O asincrone

Il driver client deve eseguire le attività seguenti nella routine di notifica di ricezione asincrona del driver:

  • Verificare i membri della struttura NOTIFICATION_INFO passati al driver client.
  • Per le richieste di lettura/blocco riuscite (in cui si restituiscono dati tramite il pacchetto di risposta), il driver client deve:
    • Allocare memoria chiamando WdfMemoryCreate (ExAllocatePoolWithTag per i driver client basati su WDM) per i dati dei pacchetti di risposta.
    • Compilare il buffer con i dati da restituire.
    • Inizializzare il membro ResponseMdl e fare riferimento al buffer. È possibile chiamare MmInitializeMdl e MmBuildMdlForNonPagedPool.
    • Impostare *NotificationInfo-ResponsePacket> per puntare al buffer.
    • Impostare *NotificationInfo-ResponseLength> sulle dimensioni dei dati di risposta restituiti, ovvero 4 per una richiesta di lettura quadlet.
    • Allocare memoria chiamando WdfMemoryCreate (ExAllocatePoolWithTag per i driver client basati su WDM) per l'evento di risposta.
    • Impostare *NotificationInfo-ResponseEvent> per puntare al buffer dell'evento.
    • Pianificare un elemento di lavoro in attesa dell'evento e liberare i buffer di dati di risposta e eventi dopo che l'evento di risposta viene segnalato.
    • Impostare NotificationInfo-ResponseCode> su RCODE_RESPONSE_COMPLETE.

Ricezione asincrona nel caso di pre-notifica

Il driver del bus legacy 1394 non riesce a completare le transazioni di ricezione asincrone usando il meccanismo di pre-notifica.

Per il nuovo driver del bus 1394, il comportamento previsto della routine di callback di notifica del driver client nel caso di pre-notifica è il seguente:

  • Se i membri Mdl e Fifo della struttura di NOTIFICATION_INFO sono NULL, viene eseguita la pre-notifica.
  • I membri ResponseMdl, ResponsePacket, ResponseLength e ResponseEvent della struttura di NOTIFICATION_INFO non devono essere NULL.
  • Il membro fulNotificationOptions della struttura NOTIFICATION_INFO deve indicare l'azione (lettura, scrittura, blocco) che ha attivato la notifica. È possibile impostare un solo flag (NOTIFY_FLAGS_AFTER_READ , NOTIFY_FLAGS_AFTER_WRITE o NOTIFY_FLAGS_AFTER_LOCK) ogni volta che viene richiamata la routine di notifica.
  • È possibile identificare il tipo di richiesta controllando il membro RequestPacket-AP_tCode> della struttura ASYNC_PACKET. Il membro indica il TCODE che specifica il tipo di richiesta, ad esempio blocco o quadlet read/write, tipo di richiesta di blocco. La struttura ASYNC_PACKET è dichiarata nel 1394.h.
  • I membri ResponsePacket e ResponseEvent di NOTIFICATION_INFO contengono puntatori ai puntatori. È pertanto necessario fare riferimento ai puntatori al pacchetto di risposta e all'evento di risposta in modo appropriato.
  • Il membro ResponseLength di NOTIFICATION_INFO è un puntatore a una variabile ULONG. Pertanto, è necessario dereferenziare il membro in modo appropriato quando si imposta la lunghezza dei dati di risposta per le richieste, ad esempio per leggere & richieste di blocco.
  • Il driver client 1394 è responsabile dell'allocazione della memoria per il pacchetto di risposta e l'evento di risposta (dal pool non a pagina) e rilasciando tale memoria dopo che è stata recapitata la risposta. È consigliabile che un elemento di lavoro venga accodato e l'elemento di lavoro deve attendere l'evento di risposta. Questo evento viene segnalato dal conducente del bus 1394 dopo la consegna della risposta. Il driver client può quindi rilasciare la memoria all'interno dell'elemento di lavoro.
  • Il membro ResponseCode nella struttura NOTIFICATION_INFO deve essere impostato su uno dei valori RCODE definiti in 1394.h. Se ResponseCode è impostato su qualsiasi valore diverso da RCODE_RESPONSE_COMPLETE, il driver del bus 1394 invia un pacchetto di risposta di errore. Nel caso di una richiesta di lettura o blocco, la richiesta non restituisce dati.
  • Per le richieste di lettura e blocco, il membro ResponsePacket della struttura NOTIFICATION_INFO deve puntare ai dati da restituire nel pacchetto di risposta asincrona.

Gli esempi di codice seguenti illustrano l'implementazione dell'elemento di lavoro e la routine di notifica del driver client.

VOID
kmdf1394_NotifyRoutineWorkItem (
                                 IN WDFWORKITEM  WorkItem)
{
    NTSTATUS                 ntStatus;
    PNOTIFY_WORKITEM_CONTEXT notifyContext = GetNotifyWorkitemContext(WorkItem);

    Enter();

    ASSERT(notifyContext);

    ntStatus = KeWaitForSingleObject(
                                     &notifyContext->responseEvent,
                                     Executive,
                                     KernelMode,
                                     FALSE,
                                     NULL);
    if (!NT_SUCCESS(ntStatus))
    {
        DoTraceLevelMessage(TRACE_LEVEL_ERROR,
                            TRACE_FLAG_ASYNC,
                            "Wait on notify response event failed: %!STATUS!\n",
                            ntStatus);
    }

    WdfObjectDelete (WorkItem);

    Exit();
}

VOID
kmdf1394_NotificationCallback (
    IN PNOTIFICATION_INFO   NotifyInfo)
{
    PASYNC_PACKET           asyncPacket = (PASYNC_PACKET)NotifyInfo->RequestPacket;
    PASYNC_ADDRESS_DATA     asyncAddressData = NotifyInfo->Context;
    PULONG                  responseQuadlet;
    WDF_WORKITEM_CONFIG     workItemConfig;
    WDFWORKITEM             workItem;
    WDF_OBJECT_ATTRIBUTES   attributes;
    PNOTIFY_WORKITEM_CONTEXT notifyContext;
    NTSTATUS                ntStatus;

    Enter();

    DoTraceLevelMessage(TRACE_LEVEL_INFORMATION,
                        TRACE_FLAG_ASYNC,
                        "NotifyInfo Mdl %p ulOffset %08x nLength %08x fulNotificationOptions %08x Context %p RCode %x Pkt %p\n",
                        NotifyInfo->Mdl,
                        NotifyInfo->ulOffset,
                        NotifyInfo->nLength,
                        NotifyInfo->fulNotificationOptions,
                        NotifyInfo->Context,
                        NotifyInfo->ResponseCode,
                        asyncPacket);

    if (NotifyInfo->Mdl)
    {
        // Post-Notification
        switch (NotifyInfo->fulNotificationOptions)
        {
            case NOTIFY_FLAGS_AFTER_LOCK:
                NotifyInfo->ResponseCode = RCODE_TYPE_ERROR;
                break;

            case NOTIFY_FLAGS_AFTER_WRITE:
                NotifyInfo->ResponseCode = RCODE_TYPE_ERROR;
                break;

            case NOTIFY_FLAGS_AFTER_READ:
                // Don't touch ResponseCode if no error
                // NotifyInfo->ResponseCode = RCODE_RESPONSE_COMPLETE;
                break;

            default:
                NotifyInfo->ResponseCode = RCODE_TYPE_ERROR;
                break;
        }
    }
    else
    {
        // Pre-Notification
        if (asyncPacket)
        {
            if ( !NotifyInfo->ResponseMdl ||
                    !NotifyInfo->ResponsePacket ||
                    !NotifyInfo->ResponseLength ||
                    !NotifyInfo->ResponseEvent )
            {
                DoTraceLevelMessage(TRACE_LEVEL_ERROR,
                                    TRACE_FLAG_ASYNC,
                                    "Pre-Notification failure: missing Response field(s)!\n");
                DoTraceLevelMessage(TRACE_LEVEL_ERROR,
                                    TRACE_FLAG_ASYNC,
                                    "ResponseMdl %p\tResponsePacket %p\tResponseLength %p\tResponseEvent %p\n",
                                    NotifyInfo->ResponseMdl, NotifyInfo->ResponsePacket,
                                    NotifyInfo->ResponseLength, NotifyInfo->ResponseEvent);
                if (NotifyInfo->ResponsePacket != NULL)
                {
                    DoTraceLevelMessage(TRACE_LEVEL_ERROR,
                                        TRACE_FLAG_ASYNC,
                                        "\t*ResponsePacket %p\n",
                                        *NotifyInfo->ResponsePacket);
                }
                NotifyInfo->ResponseCode = RCODE_TYPE_ERROR;
            }
            else if ( NULL == asyncAddressData )
            {
                DoTraceLevelMessage(TRACE_LEVEL_ERROR,
                                    TRACE_FLAG_ASYNC,
                                    "Pre-Notification failure: missing Notify Context!\n");
                NotifyInfo->ResponseCode = RCODE_TYPE_ERROR;
            }
            else
            {
                DoTraceLevelMessage(TRACE_LEVEL_INFORMATION,
                                    TRACE_FLAG_ASYNC,
                                    "AddrData %p DevExt %p Buffer %p Len %x hAddrRange %!HANDLE! Mdl %p\n",
                                    asyncAddressData,
                                    asyncAddressData->DeviceExtension,
                                    asyncAddressData->Buffer,
                                    asyncAddressData->nLength,
                                    asyncAddressData->hAddressRange,
                                    asyncAddressData->pMdl);

                switch (asyncPacket->AP_tCode)
                {
                    case TCODE_LOCK_REQUEST:
                        NotifyInfo->ResponseCode = RCODE_TYPE_ERROR;
                        break;

                    case TCODE_WRITE_REQUEST_QUADLET:
                    case TCODE_WRITE_REQUEST_BLOCK:
                        NotifyInfo->ResponseCode = RCODE_TYPE_ERROR;
                        break;

                    case TCODE_READ_REQUEST_BLOCK:
                        NotifyInfo->ResponseCode = RCODE_DATA_ERROR;
                        break;

                    case TCODE_READ_REQUEST_QUADLET:
                        // only implementing Quadlet Read for now

                        // Create a WdfWorkItem, with notifyResponse as its context,
                        // to handle waiting for the Response Event & cleaning up all the response stuff

                        WDF_WORKITEM_CONFIG_INIT (&workItemConfig, kmdf1394_NotifyRoutineWorkItem);

                        WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, NOTIFY_WORKITEM_CONTEXT);

                        attributes.ParentObject = WdfObjectContextGetObject(asyncAddressData->DeviceExtension);

                        ntStatus = WdfWorkItemCreate( &workItemConfig,
                            &attributes,
                            &workItem);

                        if (!NT_SUCCESS (ntStatus))
                        {
                            DoTraceLevelMessage(TRACE_LEVEL_ERROR,
                                                TRACE_FLAG_ASYNC,
                                                "Failed to create workitem %x\n",
                                                ntStatus);

                            NotifyInfo->ResponseCode = RCODE_DATA_ERROR;
                            break;
                        }

                        notifyContext = GetNotifyWorkitemContext(workItem);

                        notifyContext->asyncAddressData = asyncAddressData;

                        // NotifyInfo->ResponsePacket
                        // memory location that the driver fills in with
                        // a pointer to the beginning of its response packet

                        // parent this memory object to the workitem so both can be cleaned up together
                        WDF_OBJECT_ATTRIBUTES_INIT (&attributes);
                        attributes.ParentObject = workItem;

                        ntStatus = WdfMemoryCreate(
                                                   &attributes,
                                                   NonPagedPool,
                                                   POOLTAG_KMDF_VDEV,
                                                   sizeof(ULONG),
                                                   &notifyContext->responseMemory,
                                                   &responseQuadlet);

                        if (!NT_SUCCESS(ntStatus) || !responseQuadlet)
                        {
                            DoTraceLevelMessage(TRACE_LEVEL_ERROR,
                                                TRACE_FLAG_ASYNC,
                                                "Failed to allocate Response Data Memory: %!STATUS!\n",
                                                ntStatus);

                            WdfObjectDelete (workItem);

                            NotifyInfo->ResponseCode = RCODE_DATA_ERROR;
                            break;
                        }

                        RtlFillMemory(responseQuadlet, sizeof(ULONG), 0x8F);    // dummy data for testing

                        // NotifyInfo->ResponseMdl
                        // initialize MDL for a nonpageable buffer,
                        // and fill the buffer with the response packet
                        MmInitializeMdl(NotifyInfo->ResponseMdl,
                                        responseQuadlet,
                                        sizeof(ULONG));
                        MmBuildMdlForNonPagedPool(NotifyInfo->ResponseMdl);

                        // do what it looks like the New (KMDF) stack expects,
                        // which is that NotifyInfo->ResponsePacket points to the
                        // data following the Async Packet header
                        *NotifyInfo->ResponsePacket = (PVOID)&responseQuadlet;

                        // NotifyInfo->ResponseEvent
                        // memory location that the driver fills in with
                        // the kernel event the bus driver should use to signal
                        // that it has completed sending the response packet
                        KeInitializeEvent(&notifyContext->responseEvent, NotificationEvent, FALSE);

                        *NotifyInfo->ResponseEvent = &notifyContext->responseEvent;

                        // NotifyInfo->ResponseLength
                        // memory location that the driver fills in with
                        // the length of its response packet
                        *NotifyInfo->ResponseLength = sizeof(ULONG);

                        // NotifyInfo->ResponseCode
                        // specifies the result of the driver's response to the request
                        NotifyInfo->ResponseCode = RCODE_RESPONSE_COMPLETE;

                        // Enqueue the work item to clean up after notification completion
                        WdfWorkItemEnqueue (workItem);

                        DoTraceLevelMessage(TRACE_LEVEL_INFORMATION,
                                            TRACE_FLAG_ASYNC,
                                            "Pre-Notification: Read Quadlet: Notify Response Handle %!HANDLE! Data %08x Event %p\n",
                                            notifyContext->responseMemory,
                                            *responseQuadlet,
                                            &notifyContext->responseEvent);

                        break;

                    default:
                        NotifyInfo->ResponseCode = RCODE_TYPE_ERROR;
                        break;
                } // switch (asyncPacket->AP_tCode)
            } // else [pre-notification params OK]
        } // if (asyncPacket)
        else
        {
            DoTraceLevelMessage(TRACE_LEVEL_ERROR,
                                TRACE_FLAG_ASYNC,
                                "Pre-Notification failure: no RequestPacket!\n");
            NotifyInfo->ResponseCode = RCODE_DATA_ERROR;
        }
    }

    ExitS( NotifyInfo->ResponseCode | RCODE_STATUS_MASK );
    return;
} // kmdf1394_NotificationCallback