Partager via


Connexion d’un pilote de périphérique UMDF à un port série

Le pilote UMDF pour un périphérique sur un port série géré par SerCx2 nécessite certaines ressources matérielles pour faire fonctionner l’appareil. Ces ressources incluent les informations dont le pilote a besoin pour ouvrir une connexion logique au port série. Des ressources supplémentaires peuvent inclure une interruption et une ou plusieurs broches d’entrée ou de sortie GPIO.

Ce pilote implémente une interface IPnpCallbackHardware2 et inscrit cette interface auprès de l’infrastructure du pilote Windows lors de l’appel à la méthode IDriverEntry::OnDeviceAdd du pilote. L’infrastructure appelle les méthodes dans l’interface IPnpCallbackHardware2 pour informer le pilote des modifications apportées à l’état d’alimentation de l’appareil.

Une fois que le périphérique connecté en série passe à l’état d’alimentation du périphérique D0 non initialisé, l’infrastructure du pilote appelle la méthode IPnpCallbackHardware2::OnPrepareHardware du pilote pour indiquer au pilote de préparer ce périphérique pour l’utilisation. Pendant cet appel, le pilote reçoit deux listes de ressources matérielles en tant que paramètres d’entrée. Le paramètre pWdfResourcesRaw pointe vers la liste des ressources brutes, et le paramètre pWdfResourcesTranslated pointe vers la liste des ressources traduites. Les deux paramètres sont des pointeurs vers des objets IWDFCmResourceList . Les ressources traduites incluent l’ID de connexion dont le pilote de périphérique a besoin pour établir la connexion logique au périphérique connecté en série.

Pour permettre à un pilote de périphérique UMDF de recevoir des ID de connexion dans sa liste de ressources, le fichier INF qui installe le pilote doit inclure la directive suivante dans sa section DDInstall spécifique à WDF :

UmdfDirectHardwareAccess = AllowDirectHardwareAccess Pour plus d’informations sur cette directive, consultez Spécification de directives WDF dans les fichiers INF. Pour obtenir un exemple de fichier INX (utilisé pour générer le fichier INF correspondant) qui utilise cette directive, consultez le SpbAccelerometer dans les exemples de pilotes WDK.

L’exemple de code suivant montre comment la méthode OnPrepareHardware du pilote obtient l’ID de connexion à partir du paramètre pWdfResourcesTranslated .

BOOLEAN fConnectIdFound = FALSE;
BOOLEAN fDuplicateFound = FALSE;
LARGE_INTEGER connectionId = 0;
ULONG resourceCount;

resourceCount = pWdfResourcesTranslated->GetCount();

// Loop through the resources and save the relevant ones.
for (ULONG ix = 0; ix < resourceCount; ix++)
{
    PCM_PARTIAL_RESOURCE_DESCRIPTOR pDescriptor;

    pDescriptor = pWdfResourcesTranslated->GetDescriptor(ix);

    if (pDescriptor == NULL)
    {
        hr = 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 (fConnIdFound == FALSE)
                        {
                            // Save the serial controller's connection ID.
                            connectionId.LowPart = pDescriptor->u.Connection.IdLowPart;
                            connectionId.HighPart = pDescriptor->u.Connection.IdHighPart;
                            fConnectIdFound = TRUE;
                        }
                        else
                        {
                            fDuplicateFound = TRUE;
                        }
                    }
                }

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

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

        default:
            // Ignore all other resource descriptors.
            break;
    }
}

L’exemple de code précédent copie l’ID de connexion du périphérique connecté en série dans une variable nommée connectionId. L’exemple de code suivant montre comment incorporer l’ID de connexion dans un nom de chemin d’accès d’appareil qui peut être utilisé pour identifier le contrôleur série auquel le périphérique est connecté.

WCHAR szTargetPath[100];
HRESULT hres;

// Create the device path using the well-known resource hub
// path name and the connection ID.
//
hres = StringCbPrintfW(&szTargetPath[0],
                       sizeof(DevicePath),
                       L"\\\\.\\RESOURCE_HUB\\%0*I64x",
                       (size_t)(sizeof(LARGE_INTEGER) * 2),
                       connectionId.QuadPart);
if (FAILED(hres))
{
     // Error handling
     ...
}

L’exemple de code précédent écrit le nom du chemin d’accès de l’appareil pour le contrôleur série dans le szTargetPath tableau. L’exemple de code suivant utilise ce nom de chemin d’accès pour ouvrir un handle de fichier au contrôleur série.

UMDF_IO_TARGET_OPEN_PARAMS openParams;

openParams.dwShareMode = 0;
openParams.dwCreationDisposition = OPEN_EXISTING;
openParams.dwFlagsAndAttributes = FILE_FLAG_OVERLAPPED;
hres = pRemoteTarget->OpenFileByName(&szTargetPath[0],
                                     (GENERIC_READ | GENERIC_WRITE),
                                     &openParams);
if (FAILED(hres))
{
    // Error handling
    ...
}

Dans l’exemple de code précédent, le pRemoteTarget paramètre est un pointeur vers un objet IWDFRemoteTarget . Si l’appel à la méthode IWDFRemoteTarget::OpenFileByName réussit, le pilote du périphérique connecté en série peut utiliser l’objet IWDFRemoteTarget pour envoyer des demandes d’E/S au contrôleur série.

Pour envoyer une demande de lecture ou d’écriture au périphérique, le pilote appelle d’abord la méthode IWDFRemoteTarget::FormatRequestForRead ou IWDFRemoteTarget::FormatRequestForWrite de cet objet pour mettre en forme la demande. ( L’interface IWDFRemoteTarget hérite de ces deux méthodes de l’interface IWDFIoTarget .)

Pour envoyer une demande de contrôle d’E/S au contrôleur série, le pilote appelle d’abord la méthode IWDFRemoteTarget::FormatRequestForIoctl pour mettre en forme la demande. ( L’interface IWDFRemoteTarget hérite de cette méthode de l’interface IWDFIoTarget .) Ensuite, le pilote appelle la méthode IWDFIoRequest::Send pour envoyer la demande de contrôle d’E/S au périphérique connecté en série.

Dans l’exemple de code suivant, le pilote de périphérique envoie une demande de contrôle d’E/S au contrôleur série.

HRESULT hres;
IWDFMemory *pInputMemory = NULL;

// Create a new I/O request.
if (SUCCEEDED(hres))
{
    hres = pWdfDevice->CreateRequest(NULL, 
                                     pWdfDevice, 
                                     &pWdfIoRequest);
    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

// Allocate memory for the input buffer.
if (SUCCEEDED(hres))
{
    hres = pWdfDriver->CreatePreallocatedWdfMemory(pInBuffer, 
                                                   inBufferSize, 
                                                   NULL,
                                                   pWdfIoRequest,
                                                   &pInputMemory);
    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

// Format the request to be an I/O control request.
if (SUCCEEDED(hres))
{
    hres = pRemoteTarget->FormatRequestForIoctl(pWdfIoRequest,
                                                ioctlCode,
                                                NULL,
                                                pInputMemory, 
                                                NULL, 
                                                NULL,
                                                NULL);
    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

// Send the request to the serial controller.
if (SUCCEEDED(hres))
{
    ULONG Flags = fSynchronous ? WDF_REQUEST_SEND_OPTION_SYNCHRONOUS : 0;

    if (!fSynchronous)
    {
        pWdfIoRequest->SetCompletionCallback(pCallback, NULL); 
    }

    hres = pWdfIoRequest->Send(pRemoteTarget, Flags, 0);

    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

if (fSynchronous || FAILED(hres))
{
    pWdfIoRequest->DeleteWdfObject();
    SAFE_RELEASE(pWdfIoRequest);
}

L’exemple de code précédent effectue les opérations suivantes :

  1. La pWdfDevice variable est un pointeur vers l’interface IWDFDevice de l’objet d’appareil framework qui représente le périphérique connecté en série. La méthode IWDFDevice::CreateRequest crée une requête d’E/S et encapsule cette requête dans l’interface IWDFIoRequest instance vers laquelle pointe le pWdfIoRequest paramètre. La demande d’E/S est supprimée ultérieurement (voir l’étape 6). Cette implémentation est quelque peu inefficace, car elle crée puis supprime un objet de requête pour chaque demande d’E/S envoyée. Une approche plus efficace consiste à réutiliser le même objet de requête pour une série de demandes d’E/S. Pour plus d’informations, consultez Réutilisation d’objets de demande d’infrastructure.

  2. La pWdfDriver variable est un pointeur vers l’interface IWDFDriver de l’objet pilote d’infrastructure qui représente le pilote de périphérique. Les pInBuffer variables et inBufferSize spécifient l’adresse et la taille de la mémoire tampon d’entrée pour la demande de contrôle d’E/S. La méthode IWDFDriver::CreatePreallocatedWdfMemory crée un objet de mémoire framework pour la mémoire tampon d’entrée et désigne l’objet IWDFIoRequest pointé vers comme pWdfIoRequest objet parent de l’objet memory.

  3. La pWdfRemoteTarget variable est le pointeur cible distant obtenu à partir de l’appel OpenFileByName dans un exemple de code précédent. La méthode IWDFRemoteTarget::FormatRequestForIoctl met en forme la demande pour une opération de contrôle d’E/S. La ioctlCode variable est définie sur l’un des codes de contrôle d’E/S répertoriés dans le tableau de l’interface de demande d’E/S série.

  4. La fSynchronous variable a la valeur TRUE si la demande de contrôle d’E/S doit être envoyée de manière synchrone, et a la valeur FALSE si elle doit être envoyée de manière asynchrone. La pCallback variable est un pointeur vers une interface IRequestCallbackRequestCompletion créée précédemment. Si la demande doit être envoyée de manière asynchrone, l’appel à la méthode IWDFIoRequest::SetCompletionCallback inscrit cette interface. Plus tard, la méthode IRequestCallbackRequestCompletion::OnCompletion est appelée pour avertir le pilote lorsque la requête se termine de manière asynchrone.

  5. La méthode Send envoie la demande d’écriture mise en forme au périphérique connecté en série. La Flags variable indique si la demande d’écriture doit être envoyée de manière synchrone ou asynchrone.

  6. Si la requête est envoyée de manière synchrone, la méthode IWDFIoRequest::D eleteWdfObject supprime à la fois l’objet de requête d’E/S pointé vers pWdfIoRequest et l’objet enfant pointé vers pInputMemory. L’interface IWDFIoRequest hérite de cette méthode de l’interface IWDFObject. Si la requête est envoyée de manière asynchrone, l’appel à la méthode DeleteWdfObject doit se produire ultérieurement, dans la méthode OnCompletion du pilote.