Conexión de un controlador periférico KMDF a un puerto serie
El controlador KMDF para un dispositivo periférico en un puerto serie administrado por SerCx2 requiere determinados recursos de hardware para operar el dispositivo. Incluido en estos recursos es la información que el controlador necesita para abrir una conexión lógica al puerto serie. Los recursos adicionales pueden incluir una interrupción y uno o varios patillas de entrada o salida de GPIO.
Este controlador implementa un conjunto de Plug and Play y funciones de devolución de llamada de eventos de administración de energía. Para registrar estas funciones con KMDF, la función de devolución de llamada de eventos EvtDriverDeviceAdd del controlador llama al método WdfDeviceInitSetPnpPowerEventCallbacks . El marco llama a las funciones de devolución de llamada de eventos de administración de energía para notificar al controlador los cambios en el estado de alimentación del dispositivo periférico. Incluida en estas funciones es la función EvtDevicePrepareHardware , que realiza las operaciones necesarias para que el dispositivo sea accesible para el controlador.
Una vez que el dispositivo periférico conectado en serie entra en un estado de alimentación del dispositivo D0 sin inicializar, el marco de trabajo del controlador llama a la función EvtDevicePrepareHardware para indicar al controlador periférico que prepare el dispositivo para su uso. Durante esta llamada, el controlador recibe dos listas de recursos de hardware como parámetros de entrada. El parámetro ResourcesRaw es un identificador de objeto WDFCMRESLIST para la lista de recursos sin procesar y el parámetro ResourcesTranslated es un identificador de objeto WDFCMRESLIST para la lista de recursos traducidos. Los recursos traducidos incluyen el identificador de conexión que el controlador requiere para establecer una conexión lógica con el dispositivo periférico.
En el ejemplo de código siguiente se muestra cómo la función EvtDevicePrepareHardware obtiene el identificador de conexión del parámetro 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;
}
}
En el ejemplo de código anterior se copia el identificador de conexión de un dispositivo periférico conectado en serie en una variable denominada connectionId
.
En el ejemplo de código siguiente se muestra cómo incorporar este identificador de conexión en un nombre de ruta de acceso de dispositivo que se puede usar para abrir una conexión lógica al dispositivo periférico. Este nombre de ruta de acceso del dispositivo identifica el centro de recursos como el componente del sistema desde el que se obtienen los parámetros necesarios para acceder al dispositivo periférico.
// 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
...
}
En el ejemplo de código anterior, la macro DECLARE_UNICODE_STRING_SIZE crea la declaración de una variable de UNICODE_STRING inicializada denominada szDeviceName
que tiene un búfer lo suficientemente grande como para contener un nombre de ruta de acceso de dispositivo en el formato usado por el centro de recursos. Esta macro se define en el archivo de encabezado Ntdef.h. La constante RESOURCE_HUB_PATH_SIZE especifica el número de bytes en el nombre de la ruta de acceso del dispositivo. La macro RESOURCE_HUB_CREATE_PATH_FROM_ID genera el nombre de la ruta de acceso del dispositivo a partir del identificador de conexión. RESOURCE_HUB_PATH_SIZE y RESOURCE_HUB_CREATE_PATH_FROM_ID se definen en el archivo de encabezado Reshub.h.
En el ejemplo de código siguiente se usa el nombre de la ruta de acceso del dispositivo para abrir un identificador de archivo (denominado SerialIoTarget
) en el dispositivo periférico conectado en serie.
// 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
...
}
En el ejemplo de código anterior, la función WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME inicializa la estructura de WDF_IO_TARGET_OPEN_PARAMS para que el controlador pueda abrir una conexión lógica al dispositivo periférico conectado en serie especificando el nombre del dispositivo. La SerialIoTarget
variable es un identificador WDFIOTARGET para un objeto de destino de E/S de marco. Este identificador se obtuvo de una llamada anterior al método WdfIoTargetCreate , que no se muestra en el ejemplo. Si la llamada al método WdfIoTargetOpen se realiza correctamente, el controlador puede usar el SerialIoTarget
identificador para enviar solicitudes de E/S al dispositivo periférico.
En la función de devolución de llamada de eventos EvtDriverDeviceAdd , el controlador periférico puede llamar al método WdfRequestCreate para asignar un objeto de solicitud de marco para que lo use el controlador. Más adelante, cuando el objeto ya no es necesario, el controlador llama al método WdfObjectDelete para eliminar el objeto. El controlador puede reutilizar el objeto de solicitud de marco obtenido de la llamada WdfRequestCreate varias veces para enviar solicitudes de E/S al dispositivo periférico. Para enviar de forma sincrónica una solicitud de lectura, escritura o IOCTL, el controlador llama al método WdfIoTargetSendReadSynchronously, WdfIoTargetSendWriteSynchronously o WdfIoTargetSendIoctlSynchronously .
En el ejemplo de código siguiente, el controlador llama a WdfIoTargetSendWriteSynchronousmente para enviar de forma sincrónica una solicitud de IRP_MJ_WRITE al dispositivo periférico. Al principio de este ejemplo, la pBuffer
variable apunta a un búfer no paginado que contiene los datos que se van a escribir en el dispositivo periférico y la dataSize
variable especifica el tamaño, en bytes, de estos datos.
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
...
}
El ejemplo de código anterior hace lo siguiente:
- La llamada de función WDF_MEMORY_DESCRIPTOR_INIT_BUFFER inicializa la
memoryDescriptor
variable, que es una estructura WDF_MEMORY_DESCRIPTOR que describe el búfer de entrada. Anteriormente, el controlador llamó a una rutina como ExAllocatePoolWithTag para asignar el búfer del grupo no paginado y copió los datos de escritura en este búfer. - La llamada de función WDF_REQUEST_SEND_OPTIONS_INIT inicializa la
requestOptions
variable , que es una estructura de WDF_REQUEST_SEND_OPTIONS que contiene la configuración opcional de la solicitud de escritura. En este ejemplo, la estructura configura la solicitud para agotar el tiempo de espera si no se completa después de dos segundos. - La llamada al método WdfIoTargetSendWriteSynchronously envía la solicitud de escritura al dispositivo periférico. El método devuelve de forma sincrónica, una vez completada la operación de escritura o agota el tiempo de espera. Si es necesario, otro subproceso de controlador puede llamar a WdfRequestCancelSentRequest para cancelar la solicitud.
En la llamada WdfIoTargetSendWriteSynchronously , el controlador proporciona una variable denominada SerialRequest
, que es un identificador de un objeto de solicitud de marco que el controlador creó anteriormente. Después de llamar a WdfIoTargetSendWriteSynchronously , el controlador normalmente debe llamar al método WdfRequestReuse para preparar el objeto de solicitud de marco que se usará de nuevo.