Condividi tramite


Connessione di un driver di periferica KMDF a una porta seriale

Il driver KMDF per un dispositivo periferico in una porta seriale gestita da SerCx2 richiede alcune risorse hardware per il funzionamento del dispositivo. Incluse in queste risorse sono le informazioni necessarie al driver per aprire una connessione logica alla porta seriale. Altre risorse possono includere un interrupt e uno o più pin di input o output GPIO.

Questo driver implementa un set di funzioni di callback degli eventi plug and Play e risparmio energia. Per registrare queste funzioni con KMDF, la funzione di callback degli eventi EvtDriverDeviceAdd del driver chiama il metodo WdfDeviceInitSetPnpPowerEventCallbacks . Il framework chiama le funzioni di callback degli eventi di risparmio energia per notificare al driver le modifiche dello stato di alimentazione del dispositivo periferico. Incluse in queste funzioni è la funzione EvtDevicePrepareHardware , che esegue tutte le operazioni necessarie per rendere il dispositivo accessibile al driver.

Dopo che il dispositivo periferico connesso in modo seriale entra in uno stato di alimentazione del dispositivo D0 non inizializzato, il framework driver chiama la funzione EvtDevicePrepareHardware per indicare al driver periferico di preparare il dispositivo per l'uso. Durante questa chiamata, il driver riceve due elenchi di risorse hardware come parametri di input. Il parametro ResourcesRaw è un handle di oggetto WDFCMRESLIST per l'elenco di risorse non elaborate e il parametro ResourcesTranslated è un handle di oggetto WDFCMRESLIST per l'elenco di risorse tradotte. Le risorse tradotte includono l'ID connessione richiesto dal driver per stabilire una connessione logica al dispositivo periferico.

L'esempio di codice seguente mostra come la funzione EvtDevicePrepareHardware ottiene l'ID connessione dal parametro ResourcesTranslated .

BOOLEAN fConnectionIdFound = FALSE;
LARGE_INTEGER connectionId = 0;
ULONG resourceCount;
NTSTATUS status = STATUS_SUCCESS;

resourceCount = WdfCmResourceListGetCount(ResourcesTranslated);

// Loop through the resources and save the relevant ones.

for (ULONG ix = 0; ix < resourceCount; ix++)
{
    PCM_PARTIAL_RESOURCE_DESCRIPTOR pDescriptor;

    pDescriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, ix);

    if (pDescriptor == NULL)
    {
        status = E_POINTER;
        break;
    }

    // Determine the resource type.
    switch (pDescriptor->Type)
    {
    case CmResourceTypeConnection:
        {
            // Check against the expected connection types.

            UCHAR Class = pDescriptor->u.Connection.Class;
            UCHAR Type = pDescriptor->u.Connection.Type;

            if (Class == CM_RESOURCE_CONNECTION_CLASS_SERIAL)
            {
                if (Type == CM_RESOURCE_CONNECTION_TYPE_SERIAL_UART)
                {
                    if (fConnectionIdFound == FALSE)
                    {
                        // Save the connection ID.

                        connectionId.LowPart = pDescriptor->u.Connection.IdLowPart;
                        connectionId.HighPart = pDescriptor->u.Connection.IdHighPart;
                        fConnectionIdFound = TRUE;
                    }
                }
            }

            if (Class == CM_RESOURCE_CONNECTION_CLASS_GPIO)
            {
                // Check for GPIO pin resource.
                ...
            }
        }
        break;

    case CmResourceTypeInterrupt:
        {
            // Check for interrupt resource.
            ...
        }
        break;

    default:
        // Don't care about other resource descriptors.
        break;
    }
}

L'esempio di codice precedente copia l'ID connessione per un dispositivo periferico connesso in modo seriale in una variabile denominata connectionId.

Nell'esempio di codice seguente viene illustrato come incorporare questo ID connessione in un nome di percorso del dispositivo che può essere usato per aprire una connessione logica al dispositivo periferico. Questo nome del percorso del dispositivo identifica l'hub risorse come componente di sistema da cui ottenere i parametri necessari per accedere al dispositivo periferico.

// Use the connection ID to create the full device path name.
 
DECLARE_UNICODE_STRING_SIZE(szDeviceName, RESOURCE_HUB_PATH_SIZE);

status = RESOURCE_HUB_CREATE_PATH_FROM_ID(&szDeviceName,
                                          connectionId.LowPart,
                                          connectionId.HighPart);

if (!NT_SUCCESS(status))
{
     // Error handling
     ...
}

Nell'esempio di codice precedente, la macro DECLARE_UNICODE_STRING_SIZE crea la dichiarazione di una variabile UNICODE_STRING inizializzata denominata szDeviceName con un buffer sufficientemente grande da contenere un nome di percorso del dispositivo nel formato usato dall'hub risorse. Questa macro è definita nel file di intestazione Ntdef.h. La costante RESOURCE_HUB_PATH_SIZE specifica il numero di byte nel nome del percorso del dispositivo. La macro RESOURCE_HUB_CREATE_PATH_FROM_ID genera il nome del percorso del dispositivo dall'ID connessione. RESOURCE_HUB_PATH_SIZE e RESOURCE_HUB_CREATE_PATH_FROM_ID sono definiti nel file di intestazione Reshub.h.

Nell'esempio di codice seguente viene usato il nome del percorso del dispositivo per aprire un handle di file (denominato SerialIoTarget) al dispositivo periferico connesso in modo seriale.

// Open the peripheral device on the serial port as a remote I/O target.
 
WDF_IO_TARGET_OPEN_PARAMS openParams;
WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(&openParams,
                                            &szDeviceName,
                                            (GENERIC_READ | GENERIC_WRITE));

openParams.ShareAccess = 0;
openParams.CreateDisposition = FILE_OPEN;
openParams.FileAttributes = FILE_ATTRIBUTE_NORMAL;

status = WdfIoTargetOpen(SerialIoTarget, &openParams);

if (!NT_SUCCESS(status))
{
    // Error handling
    ...
}

Nell'esempio di codice precedente, la funzione WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME inizializza la struttura WDF_IO_TARGET_OPEN_PARAMS in modo che il driver possa aprire una connessione logica al dispositivo periferico connesso in modo seriale specificando il nome del dispositivo. La SerialIoTarget variabile è un handle WDFIOTARGET per un oggetto di destinazione I/O del framework. Questo handle è stato ottenuto da una chiamata precedente al metodo WdfIoTargetCreate , che non è illustrato nell'esempio. Se la chiamata al metodo WdfIoTargetOpen ha esito positivo, il driver può usare l'handle SerialIoTarget per inviare richieste di I/O al dispositivo periferico.

Nella funzione di callback dell'evento EvtDriverDeviceAdd , il driver periferico può chiamare il metodo WdfRequestCreate per allocare un oggetto richiesta framework da usare dal driver. Successivamente, quando l'oggetto non è più necessario, il driver chiama il metodo WdfObjectDelete per eliminare l'oggetto. Il driver può riutilizzare l'oggetto richiesta framework ottenuto dalla chiamata WdfRequestCreate più volte per inviare richieste di I/O al dispositivo periferico. Per inviare in modo sincrono una richiesta di lettura, scrittura o IOCTL, il driver chiama il metodo WdfIoTargetSendReadSynchronously, WdfIoTargetSendWriteSynchronously o WdfIoTargetSendIoctlSynchronously .

Nell'esempio di codice seguente il driver chiama WdfIoTargetSendWriteSynchronously per inviare in modo sincrono una richiesta di IRP_MJ_WRITE al dispositivo periferico. All'inizio di questo esempio, la pBuffer variabile punta a un buffer non di paging contenente i dati da scrivere nel dispositivo periferico e la dataSize variabile specifica le dimensioni, in byte, di questi dati.

ULONG_PTR bytesWritten;
NTSTATUS status;

// Describe the input buffer.

WDF_MEMORY_DESCRIPTOR memoryDescriptor;
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDescriptor, pBuffer, dataSize);

// Configure the write request to time out after 2 seconds.

WDF_REQUEST_SEND_OPTIONS requestOptions;
WDF_REQUEST_SEND_OPTIONS_INIT(&requestOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT);
requestOptions.Timeout = WDF_REL_TIMEOUT_IN_SEC(2);

// Send the write request synchronously.

status = WdfIoTargetSendWriteSynchronously(SerialIoTarget,
                                           SerialRequest,
                                           &memoryDescriptor,
                                           NULL,
                                           &requestOptions,
                                           &bytesWritten);
if (!NT_SUCCESS(status))
{
    // Error handling
    ...
}

L'esempio di codice precedente esegue le operazioni seguenti:

  1. La chiamata di funzione WDF_MEMORY_DESCRIPTOR_INIT_BUFFER inizializza la memoryDescriptor variabile, ovvero una struttura WDF_MEMORY_DESCRIPTOR che descrive il buffer di input. In precedenza, il driver chiamò una routine come ExAllocatePoolWithTag per allocare il buffer dal pool non di paging e copiare i dati di scrittura in questo buffer.
  2. La chiamata di funzione WDF_REQUEST_SEND_OPTIONS_INIT inizializza la requestOptions variabile, ovvero una struttura WDF_REQUEST_SEND_OPTIONS che contiene le impostazioni facoltative per la richiesta di scrittura. In questo esempio la struttura configura la richiesta per il timeout se non viene completata dopo due secondi.
  3. La chiamata al metodo WdfIoTargetSendWriteSynchronously invia la richiesta di scrittura al dispositivo periferico. Il metodo restituisce in modo sincrono, dopo il completamento o il timeout dell'operazione di scrittura. Se necessario, un altro thread del driver può chiamare WdfRequestCancelSentRequest per annullare la richiesta.

Nella chiamata WdfIoTargetSendWriteSynchronously il driver fornisce una variabile denominata SerialRequest, che è un handle per un oggetto richiesta framework creato in precedenza dal driver. Dopo la chiamata WdfIoTargetSendWriteSynchronously , il driver deve in genere chiamare il metodo WdfRequestReuse per preparare l'oggetto richiesta framework da usare di nuovo.