Verbinden eines UMDF-Peripherietreibers mit einem seriellen Port
Der UMDF-Treiber für ein Peripheriegerät an einem von SerCx2 verwalteten seriellen Port erfordert bestimmte Hardwareressourcen, um das Gerät zu betreiben. In diesen Ressourcen sind die Informationen enthalten, die der Treiber benötigt, um eine logische Verbindung mit dem seriellen Port zu öffnen. Zusätzliche Ressourcen können einen Interrupt und einen oder mehrere GPIO-Eingabe- oder Ausgabepins enthalten.
Dieser Treiber implementiert eine IPnpCallbackHardware2-Schnittstelle und registriert diese Schnittstelle beim Windows-Treiberframework während des Aufrufs der IDriverEntry::OnDeviceAdd-Methode des Treibers. Das Framework ruft die Methoden in der IPnpCallbackHardware2-Schnittstelle auf, um den Treiber über Änderungen im Energiezustand des Geräts zu benachrichtigen.
Nachdem das serielle Peripheriegerät in einen nicht initialisierten D0-Gerätestromzustand wechselt, ruft das Treiberframework die IPnpCallbackHardware2::OnPrepareHardware-Methode des Treibers auf, um den Treiber anweisen, dieses Gerät für die Verwendung vorzubereiten. Während dieses Aufrufs empfängt der Treiber zwei Listen von Hardwareressourcen als Eingabeparameter. Der Parameter pWdfResourcesRaw verweist auf die Liste der Rohressourcen, und der Parameter pWdfResourcesTranslated verweist auf die Liste der übersetzten Ressourcen. Beide Parameter sind Zeiger auf IWDFCmResourceList-Objekte . Die übersetzten Ressourcen enthalten die Verbindungs-ID, die der Peripherietreiber benötigt, um die logische Verbindung mit dem seriellen Peripheriegerät herzustellen.
Damit ein UMDF-Peripherietreiber Verbindungs-IDs in seiner Ressourcenliste empfangen kann, muss die INF-Datei, die den Treiber installiert, die folgende Anweisung im WDF-spezifischen Abschnitt DDInstall enthalten:
UmdfDirectHardwareAccess = AllowDirectHardwareAccess Weitere Informationen zu dieser Direktive finden Sie unter Angeben von WDF-Direktiven in INF-Dateien. Ein Beispiel für eine INX-Datei (zum Erstellen der entsprechenden INF-Datei), die diese Direktive verwendet, finden Sie im SpbAccelerometer in den WDK-Treiberbeispielen.
Das folgende Codebeispiel zeigt, wie die OnPrepareHardware-Methode des Treibers die Verbindungs-ID aus dem Parameter pWdfResourcesTranslated abruft .
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;
}
}
Im vorherigen Codebeispiel wird die Verbindungs-ID für das serielle Peripheriegerät in eine Variable namens connectionId
kopiert. Im folgenden Codebeispiel wird gezeigt, wie die Verbindungs-ID in einen Gerätepfadnamen integriert wird, mit dem der serielle Controller identifiziert werden kann, mit dem das Peripheriegerät verbunden ist.
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
...
}
Im vorherigen Codebeispiel wird der Gerätepfadname für den seriellen Controller in das szTargetPath
Array geschrieben. Im folgenden Codebeispiel wird dieser Pfadname verwendet, um ein Dateihandle für den seriellen Controller zu öffnen.
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
...
}
Im vorherigen Codebeispiel ist der pRemoteTarget
Parameter ein Zeiger auf ein IWDFRemoteTarget-Objekt . Wenn der Aufruf der IWDFRemoteTarget::OpenFileByName-Methode erfolgreich ist, kann der Treiber für das serielle Peripheriegerät das IWDFRemoteTarget-Objekt verwenden, um E/A-Anforderungen an den seriellen Controller zu senden.
Um eine Lese- oder Schreibanforderung an das Peripheriegerät zu senden, ruft der Treiber zunächst die IWDFRemoteTarget::FormatRequestForRead - oder IWDFRemoteTarget::FormatRequestForWrite-Methode dieses Objekts auf, um die Anforderung zu formatieren. (Die IWDFRemoteTarget-Schnittstelle erbt diese beiden Methoden von der IWDFIoTarget-Schnittstelle .)
Um eine E/A-Steuerungsanforderung an den seriellen Controller zu senden, ruft der Treiber zunächst die IWDFRemoteTarget::FormatRequestForIoctl-Methode auf, um die Anforderung zu formatieren. (Die IWDFRemoteTarget-Schnittstelle erbt diese Methode von der IWDFIoTarget-Schnittstelle .) Als Nächstes ruft der Treiber die IWDFIoRequest::Send-Methode auf, um die E/A-Steuerungsanforderung an das serielle Peripheriegerät zu senden.
Im folgenden Codebeispiel sendet der Peripherietreiber eine E/A-Steuerungsanforderung an den seriellen Controller.
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);
}
Im vorherigen Codebeispiel wird Folgendes ausgeführt:
Die
pWdfDevice
Variable ist ein Zeiger auf die IWDFDevice-Schnittstelle des Frameworkgeräteobjekts, das das serielle Peripheriegerät darstellt. Die IWDFDevice::CreateRequest-Methode erstellt eine E/A-Anforderung und kapselt diese Anforderung in der IWDFIoRequest-Schnittstelle instance, auf die derpWdfIoRequest
Parameter verweist. Die E/A-Anforderung wird später gelöscht (siehe Schritt 6). Diese Implementierung ist etwas ineffizient, da sie ein Anforderungsobjekt für jede gesendete E/A-Anforderung erstellt und anschließend löscht. Ein effizienterer Ansatz besteht darin, dasselbe Anforderungsobjekt für eine Reihe von E/A-Anforderungen wiederzuverwenden. Weitere Informationen finden Sie unter Wiederverwenden von Framework-Anforderungsobjekten.Die
pWdfDriver
Variable ist ein Zeiger auf die IWDFDriver-Schnittstelle des Frameworktreiberobjekts, das den Peripherietreiber darstellt. DiepInBuffer
Variablen undinBufferSize
geben die Adresse und größe des Eingabepuffers für die E/A-Steuerungsanforderung an. Die IWDFDriver::CreatePreallocatedWdfMemory-Methode erstellt ein Frameworkspeicherobjekt für den Eingabepuffer und bezeichnet das IWDFIoRequest-Objekt , auf das vonpWdfIoRequest
als übergeordnetes Objekt des Speicherobjekts verwiesen wird.Die
pWdfRemoteTarget
Variable ist der Remotezielzeiger, der aus dem OpenFileByName-Aufruf in einem früheren Codebeispiel abgerufen wurde. Die IWDFRemoteTarget::FormatRequestForIoctl-Methode formatiert die Anforderung für einen E/A-Steuerelementvorgang. DieioctlCode
Variable ist auf einen der E/A-Steuerungscodes festgelegt, die in der Tabelle in der Seriellen E/A-Anforderungsschnittstelle aufgeführt sind.Die
fSynchronous
Variable ist TRUE , wenn die E/A-Steuerungsanforderung synchron gesendet werden soll, und false , wenn sie asynchron gesendet werden soll. DiepCallback
Variable ist ein Zeiger auf eine zuvor erstellte IRequestCallbackRequestCompletion-Schnittstelle . Wenn die Anforderung asynchron gesendet werden soll, registriert der Aufruf der IWDFIoRequest::SetCompletionCallback-Methode diese Schnittstelle. Später wird die IRequestCallbackRequestCompletion::OnCompletion-Methode aufgerufen, um den Treiber zu benachrichtigen, wenn die Anforderung asynchron abgeschlossen wird.Die Send-Methode sendet die formatierte Schreibanforderung an das serielle Peripheriegerät. Die
Flags
Variable gibt an, ob die Schreibanforderung synchron oder asynchron gesendet werden soll.Wenn die Anforderung synchron gesendet wird, löscht die IWDFIoRequest::D eleteWdfObject-Methode sowohl das E/A-Anforderungsobjekt, auf das von
pWdfIoRequest
verweist, als auch das untergeordnete Objekt, auf das vonpInputMemory
verweist. Die IWDFIoRequest-Schnittstelle erbt diese Methode von der IWDFObject-Schnittstelle . Wenn die Anforderung asynchron gesendet wird, sollte der Aufruf der DeleteWdfObject-Methode später in der OnCompletion-Methode des Treibers erfolgen.