Partager via


Ressources matérielles pour Kernel-Mode pilotes périphériques SPB

Les exemples de code de cette rubrique montrent comment le pilote KMDF ( Kernel-Mode Driver Framework ) pour un périphérique sur un bus de périphérique simple (SPB) obtient les ressources matérielles dont il a besoin pour faire fonctionner l’appareil. Ces ressources incluent les informations que le pilote utilise pour établir une connexion logique à l’appareil. Des ressources supplémentaires peuvent inclure une interruption et une ou plusieurs broches d’entrée ou de sortie GPIO. (Une broche GPIO est une broche sur un périphérique de contrôleur d’E/S à usage général configuré en tant qu’entrée ou sortie ; pour plus d’informations, consultez Pilotes d’E/S à usage général (GPIO). Contrairement à un appareil mappé en mémoire, un périphérique connecté à SPB n’a pas besoin d’un bloc d’adresses mémoire système pour mapper ses registres.

Ce pilote implémente un ensemble de fonctions de rappel d’événements Plug-and-Play et de gestion de l’alimentation. Pour inscrire ces fonctions auprès de KMDF, la fonction de rappel d’événement EvtDriverDeviceAdd du pilote appelle la méthode WdfDeviceInitSetPnpPowerEventCallbacks . L’infrastructure appelle les fonctions de rappel d’événements de gestion de l’alimentation pour informer le pilote des modifications apportées à l’état d’alimentation du périphérique. La fonction EvtDevicePrepareHardware est incluse dans ces fonctions, qui effectue toutes les opérations nécessaires pour rendre l’appareil accessible au pilote.

Lorsque l’alimentation est restaurée sur le périphérique, l’infrastructure du pilote appelle la fonction EvtDevicePrepareHardware pour informer le pilote de périphérique SPB que ce périphérique doit être prêt à être utilisé. Pendant cet appel, le pilote reçoit deux listes de ressources matérielles en tant que paramètres d’entrée. Le paramètre ResourcesRaw est un handle d’objet WDFCMRESLIST pour la liste des ressources brutes, et le paramètre ResourcesTranslated est un handle d’objet WDFCMRESLIST pour la liste des ressources traduites. Les ressources traduites incluent l’ID de connexion dont le pilote a besoin pour établir une connexion logique au périphérique. Pour plus d’informations, consultez ID de connexion pour SPB-Connected périphériques.

L’exemple de code suivant montre comment la fonction EvtDevicePrepareHardware obtient l’ID de connexion à partir du paramètre 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_I2C)
                {
                    if (fConnectionIdFound == FALSE)
                    {
                        // Save the SPB 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’exemple de code précédent copie l’ID de connexion d’un périphérique connecté par SPB dans une variable nommée connectionId.

L’exemple de code suivant montre comment incorporer cet ID de connexion dans un nom de chemin d’accès d’appareil qui peut être utilisé pour ouvrir une connexion logique au périphérique. Ce nom de chemin d’accès d’appareil identifie le hub de ressources comme composant système à partir duquel obtenir les paramètres requis pour accéder au périphérique.

// 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
     ...
}

Dans l’exemple de code précédent, la macro DECLARE_UNICODE_STRING_SIZE crée la déclaration d’une variable UNICODE_STRING initialisée nommée szDeviceName qui a une mémoire tampon suffisamment grande pour contenir un nom de chemin d’accès d’appareil au format utilisé par le hub de ressources. Cette macro est définie dans le fichier d’en-tête Ntdef.h. La constante RESOURCE_HUB_PATH_SIZE spécifie le nombre d’octets dans le nom du chemin d’accès de l’appareil. La macro RESOURCE_HUB_CREATE_PATH_FROM_ID génère le nom du chemin d’accès de l’appareil à partir de l’ID de connexion. RESOURCE_HUB_PATH_SIZE et RESOURCE_HUB_CREATE_PATH_FROM_ID sont définis dans le fichier d’en-tête Reshub.h.

L’exemple de code suivant utilise le nom du chemin d’accès de l’appareil pour ouvrir un handle de fichier (nommé SpbIoTarget) sur le périphérique connecté À SPB.

// Open the SPB peripheral device 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(SpbIoTarget, &openParams);

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

Dans l’exemple de code précédent, la fonction WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME initialise la structure WDF_IO_TARGET_OPEN_PARAMS afin que le pilote puisse ouvrir une connexion logique au périphérique en spécifiant le nom de l’appareil. La SpbIoTarget variable est un handle WDFIOTARGET pour un objet cible d’E/S de framework. Ce handle a été obtenu à partir d’un appel précédent à la méthode WdfIoTargetCreate , qui n’est pas illustré dans l’exemple. Si l’appel à la méthode WdfIoTargetOpen réussit, le pilote peut utiliser le SpbIoTarget handle pour envoyer des demandes d’E/S au périphérique.

Dans la fonction de rappel d’événement EvtDriverDeviceAdd , le pilote de périphérique SPB peut appeler la méthode WdfRequestCreate pour allouer un objet de demande d’infrastructure à utiliser par le pilote. Plus tard, lorsque l’objet n’est plus nécessaire, le pilote appelle la méthode WdfObjectDelete pour supprimer l’objet. Le pilote peut réutiliser l’objet de demande d’infrastructure obtenu à partir de l’appel WdfRequestCreate plusieurs fois pour envoyer des demandes d’E/S au périphérique. Pour une requête de lecture, d’écriture ou IOCTL, le pilote appelle la méthode WdfIoTargetSendReadSynchronously, WdfIoTargetSendWriteSynchronously ou WdfIoTargetSendIoctlSynchronously pour envoyer la demande.

Dans l’exemple de code suivant, le pilote appelle WdfIoTargetSendWriteSynchronously pour envoyer de manière synchrone une requête IRP_MJ_WRITE au périphérique connecté À SPB. Au début de cet exemple, la pBuffer variable pointe vers une mémoire tampon non pagée qui contient les données qui doivent être écrites sur le périphérique, et la dataSize variable spécifie la taille, en octets, de ces données.

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(SpbIoTarget,
                                           SpbRequest,
                                           &memoryDescriptor,
                                           NULL,
                                           &requestOptions,
                                           &bytesWritten);
if (!NT_SUCCESS(status))
{
    // Error handling
    ...
}

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

  1. L’appel de fonction WDF_MEMORY_DESCRIPTOR_INIT_BUFFER initialise la memoryDescriptor variable, qui est une structure WDF_MEMORY_DESCRIPTOR qui décrit la mémoire tampon d’entrée. Auparavant, le pilote appelait une routine telle que ExAllocatePoolWithTag pour allouer la mémoire tampon à partir d’un pool non paginé et copieait les données d’écriture dans cette mémoire tampon.
  2. L’appel de fonction WDF_REQUEST_SEND_OPTIONS_INIT initialise la requestOptions variable, qui est une structure WDF_REQUEST_SEND_OPTIONS qui contient les paramètres facultatifs de la demande d’écriture. Dans cet exemple, la structure configure la requête pour qu’elle expire si elle ne se termine pas au bout de deux secondes.
  3. L’appel à la méthode WdfIoTargetSendWriteSynchronously envoie la demande d’écriture au périphérique connecté À SPB. La méthode retourne de façon synchrone, une fois l’opération d’écriture terminée ou expirée. Si nécessaire, un autre thread de pilote peut appeler WdfRequestCancelSentRequest pour annuler la demande.

Dans l’appel WdfIoTargetSendWriteSynchronously , le pilote fournit une variable nommée SpbRequest, qui est un handle à un objet de demande d’infrastructure que le pilote a créé précédemment. Après l’appel WdfIoTargetSendWriteSynchronously , le pilote doit généralement appeler la méthode WdfRequestReuse pour préparer l’objet de demande d’infrastructure à réutiliser.