diff options
| author | Dave Wilson <[email protected]> | 2015-03-17 19:50:07 -0700 |
|---|---|---|
| committer | Dave Wilson <[email protected]> | 2015-03-17 19:50:07 -0700 |
| commit | 97cf5197cf5b882b2c689d8dc2b555f2edf8f418 (patch) | |
| tree | 46f3701832d70b420eb0fc0eb93261f9da45db3f /bluetooth | |
| parent | ef1905bf1e8825bb31120dfb27e0daf3154d859a (diff) | |
Initial publish
Diffstat (limited to 'bluetooth')
| -rw-r--r-- | bluetooth/serialhcibus/Fdo.c | 2165 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/Io.h | 251 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/ReadMe.md | 59 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/WDK/Debugdef.h | 49 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/WDK/SerialBusWdk.inx | 88 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/WDK/SerialBusWdk.vcxproj | 210 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/WDK/SerialBusWdk.vcxproj.Filters | 51 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/WDK/device.c | 257 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/WDK/device.h | 56 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/WDK/driver.rc | 14 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/driver.c | 378 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/driver.h | 530 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/io.c | 1592 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/pdo.c | 972 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/public.h | 47 | ||||
| -rw-r--r-- | bluetooth/serialhcibus/serialhcibus.sln | 28 |
16 files changed, 6747 insertions, 0 deletions
diff --git a/bluetooth/serialhcibus/Fdo.c b/bluetooth/serialhcibus/Fdo.c new file mode 100644 index 00000000..c7fcc4ff --- /dev/null +++ b/bluetooth/serialhcibus/Fdo.c @@ -0,0 +1,2165 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + Fdo.c + +Abstract: + + This module contains routines to handle the function driver + aspect of the bus driver. + +Environment: + + kernel mode only + +--*/ + +#include "driver.h" +#include <reshub.h> +#include "fdo.tmh" + +#define BTHX_VALID_WRITE_PACKET_TYPE(type) (type == HciPacketCommand || type == HciPacketAclData) +#define BTHX_VALID_READ_PACKET_TYPE(type) (type == HciPacketEvent || type == HciPacketAclData) + + +#ifdef ALLOC_PRAGMA +#pragma alloc_text (PAGE, FdoCreateOneChildDevice) +#pragma alloc_text (PAGE, FdoRemoveOneChildDevice) +#pragma alloc_text (PAGE, FdoCreateAllChildren) +#pragma alloc_text (PAGE, FdoFindConnectResources) +#pragma alloc_text (PAGE, FdoDevPrepareHardware) +#pragma alloc_text (PAGE, FdoDevReleaseHardware) +#pragma alloc_text (PAGE, FdoDevSelfManagedIoInit) +#pragma alloc_text (PAGE, FdoDevSelfManagedIoCleanup) +#pragma alloc_text (PAGE, FdoDevD0Exit) +#pragma alloc_text (PAGE, HlpInitializeFdoExtension) +#pragma alloc_text (PAGE, FdoWriteToDeviceSync) +#endif + +// +// Child device node, PDO(s), could be enumerated statically if number of PDOs are known +// at driver start, or dynamic enuermation mechanism is used. Both methods are presented +// in this code, but only one can be chosen using the define macro (see sources file). +// +#ifdef DYNAMIC_ENUM + +typedef struct _ENABLE_PDO_CONTEXT { + WDFDEVICE Fdo; +} ENABLE_PDO_CONTEXT, *PENABLE_PDO_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(ENABLE_PDO_CONTEXT, GetEnablePdoWorkItemContext) + +// Timeout used to delay dev node enuemeration +ULONG g_WaitToEnablePDO = 20000; // MSec + +VOID +DeviceEnablePDOWorker( + _In_ WDFWORKITEM _WorkItem + ) +/*++ +Routine Description: + + A work item function to dynamically enuermate a PDO. + +Arguments: + + _pWorkItem - work item that contains a context to help carrying out its task + +Return Value: +--*/ +{ + PENABLE_PDO_CONTEXT Context; + LARGE_INTEGER RemoteWakeTimeout; + + NTSTATUS Status = STATUS_SUCCESS; + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP, ("+DeviceEnablePDOWorker")); + Context = GetEnablePdoWorkItemContext(_WorkItem); + + RemoteWakeTimeout.QuadPart = WDF_REL_TIMEOUT_IN_MS(g_WaitToEnablePDO); + KeDelayExecutionThread(KernelMode, FALSE, &RemoteWakeTimeout); + + DoTrace(LEVEL_INFO, TFLAG_PNP, ("+Complete the wait")); + + Status = FdoCreateOneChildDeviceDynamic(Context->Fdo, + BT_PDO_HARDWARE_IDS, + sizeof(BT_PDO_HARDWARE_IDS)/sizeof(WCHAR), + BLUETOOTH_FUNC_IDS ); + + DoTrace(LEVEL_INFO, TFLAG_POWER, ("-DeviceEnablePDOWorker %!STATUS!", Status)); + +} + +NTSTATUS +FdoEvtDeviceListCreatePdo( + WDFCHILDLIST DeviceList, + PWDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER IdentificationDescription, + PWDFDEVICE_INIT ChildInit + ) +/*++ + +Routine Description: + + Called by the framework in response to Query-Device relation when + a new PDO for a child device needs to be created. + +Arguments: + + DeviceList - Handle to the default WDFCHILDLIST created by the framework as part + of FDO. + + IdentificationDescription - Decription of the new child device. + + ChildInit - It's a opaque structure used in collecting device settings + and passed in as a parameter to CreateDevice. + +Return Value: + + NT Status code. + +--*/ +{ + PPDO_IDENTIFICATION_DESCRIPTION pDesc; + + PAGED_CODE(); + + pDesc = CONTAINING_RECORD(IdentificationDescription, + PDO_IDENTIFICATION_DESCRIPTION, + Header); + + return PdoCreateDynamic(WdfChildListGetDevice(DeviceList), + ChildInit, + pDesc->HardwareIds, + pDesc->SerialNo); +} + + +NTSTATUS +FdoCreateOneChildDeviceDynamic( + _In_ WDFDEVICE _Device, + _In_ PWCHAR _HardwareIds, + _In_ size_t _CchHardwareIds, + _In_ ULONG _SerialNo + ) + +/*++ + +Routine Description: + + The trigger event has been signalled that a new device on the bus has arrived. + + We therefore create a description structure in stack, fill in information about + the child device and call WdfChildListAddOrUpdateChildDescriptionAsPresent + to add the device. + +--*/ + +{ + PDO_IDENTIFICATION_DESCRIPTION Description; + NTSTATUS Status; + + PAGED_CODE (); + + // + // Initialize the description with the information about the newly + // plugged in device. + // + WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER_INIT(&Description.Header, + sizeof(Description)); + + Description.SerialNo = _SerialNo; + Description.CchHardwareIds = _CchHardwareIds; + Description.HardwareIds = _HardwareIds; + + // + // Call the framework to add this child to the childlist. This call + // will internaly call our DescriptionCompare callback to check + // whether this device is a new device or existing device. If + // it's a new device, the framework will call DescriptionDuplicate to create + // a copy of this description in nonpaged pool. + // The actual creation of the child device will happen when the framework + // receives QUERY_DEVICE_RELATION request from the PNP manager in + // response to InvalidateDeviceRelations call made as part of adding + // a new child. + // + Status = WdfChildListAddOrUpdateChildDescriptionAsPresent(WdfFdoGetDefaultChildList(_Device), + &Description.Header, + NULL); // AddressDescription + + if (Status == STATUS_OBJECT_NAME_EXISTS) { + // + // The description is already present in the list, the serial number is + // not unique, return error. + // + Status = STATUS_INVALID_PARAMETER; + } + + return Status; +} + +#endif // ifdef DYNAMIC_ENUM + + +NTSTATUS +FdoCreateOneChildDevice( + _In_ WDFDEVICE _Device, + _In_ PWSTR _HardwareIds, + _In_ ULONG _SerialNo + ) +/*++ + +Routine Description: + + Create a new PDO, initialize it, add it to the list of PDOs for this + FDO bus. + +Arguments: + + _Device - WDF device object + + _HardwareIDs - hardware Id for a device + + _SerialNo - Unique ID for a child DO + +Returns: + + Status + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + BOOLEAN IsUnique = TRUE; + WDFDEVICE ChildDevice; + PPDO_EXTENSION PdoExtension; + PFDO_EXTENSION FdoExtension; + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP, ("+ FdoCreateOneChildDevice() HWID: %S", _HardwareIds)); + + // + // First make sure that we don't already have another device with the + // same serial number. + // Framework creates a collection of all the child devices we have + // created so far. So acquire the handle to the collection and lock + // it before walking the item. + // + FdoExtension = FdoGetExtension(_Device); + ChildDevice = NULL; + + // + // We need an additional lock to synchronize addition because + // WdfFdoLockStaticChildListForIteration locks against anyone immediately + // updating the static child list (the changes are put on a queue until the + // list has been unlocked). This type of lock does not enforce our concept + // of unique IDs on the bus (ie SerialNo). + // + // Without our additional lock, 2 threads could execute this function, both + // find that the requested SerialNo is not in the list and attempt to add + // it. If that were to occur, 2 PDOs would have the same unique SerialNo, + // which is incorrect. + // + // We must use a passive level lock because you can only call WdfDeviceCreate + // at PASSIVE_LEVEL. + // + WdfWaitLockAcquire(FdoExtension->ChildLock, NULL); + WdfFdoLockStaticChildListForIteration(_Device); + + while ((ChildDevice = WdfFdoRetrieveNextStaticChild(_Device, + ChildDevice, + WdfRetrieveAddedChildren)) != NULL) { + // + // WdfFdoRetrieveNextStaticChild returns reported and to be reported + // children (ie children who have been added but not yet reported to PNP). + // + // A surprise removed child will not be returned in this list. + // + PdoExtension = PdoGetExtension(ChildDevice); + + // + // It's okay to plug in another device with the same serial number + // as long as the previous one is in a surprise-removed state. The + // previous one would be in that state after the device has been + // physically removed, if somebody has an handle open to it. + // + if (_SerialNo == PdoExtension->SerialNo) { + IsUnique = FALSE; + Status = STATUS_INVALID_PARAMETER; + break; + } + } + + if (IsUnique) { + // + // Create a new child device. It is OK to create and add a child while + // the list locked for enumeration. The enumeration lock applies only + // to enumeration, not addition or removal. + // + Status = PdoCreate(_Device, _HardwareIds, _SerialNo); + } + + WdfFdoUnlockStaticChildListFromIteration(_Device); + WdfWaitLockRelease(FdoExtension->ChildLock); + + DoTrace(LEVEL_INFO, TFLAG_PNP, ("- FdoCreateOneChildDevice() %!STATUS!", Status)); + + return Status; +} + +NTSTATUS +FdoRemoveOneChildDevice( + WDFDEVICE _Device, + ULONG _SerialNo + ) +/*++ + +Routine Description: + + The application has told us a device has departed from the bus. + + We therefore need to flag the PDO as no longer present + and then tell Plug and Play about it. + +Arguments: + + _Device - WDF device object + + _SerialNo - Unique ID for a child DO + +Returns: + + Status + +--*/ + +{ + PPDO_EXTENSION PdoExtension; + BOOLEAN Found = FALSE; + BOOLEAN PlugOutAll; + WDFDEVICE ChildDevice; + NTSTATUS Status = STATUS_INVALID_PARAMETER; + + PAGED_CODE(); + + PlugOutAll = (0 == _SerialNo) ? TRUE : FALSE; + + ChildDevice = NULL; + + WdfFdoLockStaticChildListForIteration(_Device); + + while ((ChildDevice = WdfFdoRetrieveNextStaticChild(_Device, + ChildDevice, + WdfRetrieveAddedChildren)) != NULL) { + if (PlugOutAll) { + + Status = WdfPdoMarkMissing(ChildDevice); + if(!NT_SUCCESS(Status)) { + DoTrace(LEVEL_INFO, TFLAG_PNP, ("WdfPdoMarkMissing failed 0x%x\n", Status)); + break; + } + + Found = TRUE; + } + else { + PdoExtension = PdoGetExtension(ChildDevice); + + if (_SerialNo == PdoExtension->SerialNo) { + + Status = WdfPdoMarkMissing(ChildDevice); + if(!NT_SUCCESS(Status)) { + DoTrace(LEVEL_INFO, TFLAG_PNP, ("WdfPdoMarkMissing failed 0x%x\n", Status)); + break; + } + + Found = TRUE; + break; + } + } + } + + WdfFdoUnlockStaticChildListFromIteration(_Device); + + if (Found) { + Status = STATUS_SUCCESS; + } + + return Status; +} + + +NTSTATUS +FdoCreateAllChildren( + _In_ WDFDEVICE _Device + ) +/*++ +Routine Description: + + The routine enables you to statically enumerate child device functions + during start. + +Arguments: + + _Device - WDF device object + +Returns: + + Status + +--*/ +{ + NTSTATUS Status; + PFDO_EXTENSION FdoExtension; + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP, (" + FdoCreateAllChildren")); + + // + // Bus driver enumerates all child devnode in this function. + // Vendor Specific: retrieve all statically saved devnode info + // HWID, COMPATID, etc. + // + + // + // This sample code only enuemrate the Bluetooth function as the only + // child device. + // + Status = FdoCreateOneChildDevice(_Device, + BT_PDO_HARDWARE_IDS, + BLUETOOTH_FUNC_IDS); + + FdoExtension = FdoGetExtension(_Device); + if (NT_SUCCESS(Status)) { + FdoExtension->IsRadioEnabled = TRUE; + } + + return Status; +} + + + +NTSTATUS +HlpInitializeFdoExtension( + WDFDEVICE _Device + ) +/*++ +Routine Description: + + This helper function initialize the device context. + +Arguments: + + _Device - WDF Device object + +Return Value: + + Status + +--*/ +{ + PFDO_EXTENSION FdoExtension; + WDF_OBJECT_ATTRIBUTES Attributes; + NTSTATUS Status; + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+HlpInitializeFdoExtension")); + + FdoExtension = FdoGetExtension(_Device); + FdoExtension->WdfDevice = _Device; + + // + // Set Bluetooth (PDO) capabilities + // MaxAclTransferInSize - is used by the host to notify the Bluetooth controller + // in HCI_Host_Buffer_Size command to set the maximum size of the data portion + // of an HCI ACL packet that will be sent from the controller to the host. + // BthMini will only send down an HCI read request with this data buffer size. + // + FdoExtension->BthXCaps.MaxAclTransferInSize = MAX_HCI_ACLDATA_SIZE; + FdoExtension->BthXCaps.ScoSupport = ScoSupportHCIBypass; // Only option + FdoExtension->BthXCaps.MaxScoChannels = 1; // Limit to 1 HCIBypass channel + FdoExtension->BthXCaps.IsDeviceIdleCapable = TRUE; // Disable Idle to S0 and wake + FdoExtension->BthXCaps.IsDeviceWakeCapable = FALSE; // Wake from Sx + + // + // Preallocate Request + // + WDF_OBJECT_ATTRIBUTES_INIT(&Attributes); + Attributes.ParentObject = _Device; + + Status = WdfRequestCreate(&Attributes, FdoExtension->IoTargetSerial, &FdoExtension->RequestIoctlSync); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" WdfRequestCreate failed %!STATUS!", Status)); + goto Exit; + } + + FdoExtension->HardwareErrorDetected = FALSE; + + Status = WdfRequestCreate(&Attributes, FdoExtension->IoTargetSerial, &FdoExtension->RequestWaitOnError); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" WdfRequestCreate failed %!STATUS!", Status)); + goto Exit; + } + + Status = WdfMemoryCreatePreallocated(&Attributes, + &FdoExtension->SerErrorMask, + sizeof(FdoExtension->SerErrorMask), + &FdoExtension->WaitMaskMemory); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" WdfMemoryCreatePreallocated failed %!STATUS!", Status)); + goto Exit; + } + + KeInitializeSpinLock(&FdoExtension->QueueAccessLock); + +Exit: + + return Status; + +} + + +VOID +FdoEvtDeviceDisarmWake( + _In_ WDFDEVICE _Device + ) +/*++ +Routine Description: + + + This function is invoked by the framework after the bus driver determines + that an event has awakened the device, and after the bus driver subsequently + completes the wait/wake IRP. + + This function perform any hardware operations that are needed to disable + the device's ability to trigger a wake signal after the power has been lowered. + +Arguments: + + _Device - WDF Device object + +Return Value: + + VOID + +--*/ +{ + UNREFERENCED_PARAMETER(_Device); + DoTrace(LEVEL_INFO, TFLAG_PNP,(" FdoEvtDeviceDisarmWake")); +} + + +NTSTATUS +FdoEvtDeviceArmWake( + _In_ WDFDEVICE _Device + ) +/*++ +Routine Description: + + This function is invoked while the device is still in the D0 device power state, + before the bus driver lowers the device's power state but after the framework + has sent a wait/wake IRP on behalf of the driver. + +Arguments: + + _Device - WDF Device object + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + UNREFERENCED_PARAMETER(_Device); + + DoTrace(LEVEL_INFO, TFLAG_PNP,(" FdoEvtDeviceArmWake")); + + return Status; +} + +NTSTATUS +FdoFindConnectResources( + _In_ WDFDEVICE _Device, + _In_ WDFCMRESLIST _ResourcesRaw, + _In_ WDFCMRESLIST _ResourcesTranslated + ) +/*++ + +Routine Description: + + This routine enuermates and finds specific connection resources and cache them. + +Arguments: + + _Device - Supplies a handle to a framework device object. + + _ResourcesRaw - Supplies a handle to a collection of framework resource + objects. This collection identifies the raw (bus-relative) hardware + resources that have been assigned to the device. + + _ResourcesTranslated - Supplies a handle to a collection of framework + resource objects. This collection identifies the translated + (system-physical) hardware resources that have been assigned to the + device. The resources appear from the CPU's point of view. + +Return Value: + + NT Status code. + +--*/ + +{ + PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor; + PFDO_EXTENSION FdoExtension; + ULONG Index; + ULONG ResourceCount = 0; + NTSTATUS Status; + BOOLEAN UartConnectionIdIsFound = FALSE; + + UNREFERENCED_PARAMETER(_ResourcesRaw); + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+FdoFindConnectResources")); + + FdoExtension = FdoGetExtension(_Device); + + Status = STATUS_SUCCESS; + + // + // Walk through the resource list and find and cache expected resources. + // + + ResourceCount = WdfCmResourceListGetCount(_ResourcesTranslated); + + for (Index = 0; Index < ResourceCount; Index++) + { + Descriptor = WdfCmResourceListGetDescriptor(_ResourcesTranslated, Index); + + switch(Descriptor->Type) + { + case CmResourceTypeConnection: + + // + // Cache connetion ID that this BT Peripheral device is connected to + // - UART (must exist) + // - GPIO (optional) + // + + if ((Descriptor->u.Connection.Class == CM_RESOURCE_CONNECTION_CLASS_SERIAL) && + (Descriptor->u.Connection.Type == CM_RESOURCE_CONNECTION_TYPE_SERIAL_UART)) + { + NT_ASSERT(UartConnectionIdIsFound == FALSE && L"More than one set of UART connection"); + + UartConnectionIdIsFound = TRUE; + + FdoExtension->UARTConnectionId.LowPart = Descriptor->u.Connection.IdLowPart; + FdoExtension->UARTConnectionId.HighPart = Descriptor->u.Connection.IdHighPart; + + DoTrace(LEVEL_INFO, TFLAG_PNP,(" UART ConnectionID (0x%x, 0x%x)", + FdoExtension->UARTConnectionId.HighPart, FdoExtension->UARTConnectionId.LowPart)); + } + else if ((Descriptor->u.Connection.Class == CM_RESOURCE_CONNECTION_CLASS_SERIAL) && + (Descriptor->u.Connection.Type == CM_RESOURCE_CONNECTION_TYPE_SERIAL_I2C)) + { + + FdoExtension->I2CConnectionId.LowPart = Descriptor->u.Connection.IdLowPart; + FdoExtension->I2CConnectionId.HighPart = Descriptor->u.Connection.IdHighPart; + + DoTrace(LEVEL_INFO, TFLAG_PNP,(" I2C ConnectionID (0x%x, 0x%x)", + FdoExtension->I2CConnectionId.HighPart, FdoExtension->I2CConnectionId.LowPart)); + } + else if ((Descriptor->u.Connection.Class == CM_RESOURCE_CONNECTION_CLASS_GPIO) && + (Descriptor->u.Connection.Type == CM_RESOURCE_CONNECTION_TYPE_GPIO_IO)) + { + + FdoExtension->GPIOConnectionId.LowPart = Descriptor->u.Connection.IdLowPart; + FdoExtension->GPIOConnectionId.HighPart = Descriptor->u.Connection.IdHighPart; + + DoTrace(LEVEL_INFO, TFLAG_PNP,(" GPIO ConnectionID (0x%x, 0x%x)", + FdoExtension->GPIOConnectionId.HighPart, FdoExtension->GPIOConnectionId.LowPart)); + } + break; + + case CmResourceTypeInterrupt: + + // + // NT Interrupt to support HOST_WAKE for remote wake (TBD) + // + + default: + DoTrace(LEVEL_INFO, TFLAG_PNP,(" Resource type %d not used.", Descriptor->Type)); + break; + } + + } + + + // + // Expect to find UART controller + // + if (!UartConnectionIdIsFound) + { + Status = STATUS_NOT_FOUND; + } + + DoTrace(LEVEL_INFO, TFLAG_PNP,("-FdoFindConnectResources ResourceCount %d, %!STATUS!", ResourceCount, Status)); + + return Status; +} + + + +NTSTATUS +FdoOpenDevice( + _In_ WDFDEVICE _Device, + _Out_ WDFIOTARGET *_pIoTarget + ) +/*++ +Routine Description: + + This function search for a serial port and create a remote IO Target object, + which will be used to send control and data. + +Arguments: + + _Device - WDF Device object + + _pIoTarget - IO Target object to be created in this function + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + WDFIOTARGET IoTargetSerial; + PFDO_EXTENSION FdoExtension = NULL; + WCHAR TargetDeviceNameBuffer[100]; + PWSTR SymbolicLinkList = NULL; + UNICODE_STRING TargetDeviceName; + + WDF_IO_TARGET_OPEN_PARAMS OpenParams; + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+FdoOpenDevice")); + + + Status = WdfIoTargetCreate(_Device, + WDF_NO_OBJECT_ATTRIBUTES, + &IoTargetSerial); + + if (!NT_SUCCESS(Status)) + { + goto Exit; + } + + FdoExtension = FdoGetExtension(_Device); + + // + // On SoC platform, a valid connection ID to a UART is set; if not, the legacy way + // of enumerating serial device interface is used. + // + + if (ValidConnectionID(FdoExtension->UARTConnectionId)) + { + RtlInitEmptyUnicodeString(&TargetDeviceName, + TargetDeviceNameBuffer, + sizeof(TargetDeviceNameBuffer)); + + Status = RESOURCE_HUB_CREATE_PATH_FROM_ID(&TargetDeviceName, + FdoExtension->UARTConnectionId.LowPart, + FdoExtension->UARTConnectionId.HighPart); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_INFO, TFLAG_PNP,(" Failed to construct the open path %!STATUS!", Status)); + goto Exit; + } + } + else + { + // Query the system for device with SERIAL interface + Status = IoGetDeviceInterfaces(&GUID_DEVINTERFACE_COMPORT, + NULL, + 0, + &SymbolicLinkList // List of symbolic names; separate by NULL, EOL with NULL+NULL. + ); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_INFO, TFLAG_PNP,("IoGetDeviceInterfaces(): %!STATUS!", Status)); + goto Exit; + } + + // Check for empty list + if (*SymbolicLinkList == L'\0') + { + Status = STATUS_DEVICE_DOES_NOT_EXIST; + goto Exit; + } + + // A list of devices is returned, we use only the first one. + // ACPI component will enuermate us and this step is not necessary. + RtlInitUnicodeString(&TargetDeviceName, SymbolicLinkList); + } + + DoTrace(LEVEL_INFO, TFLAG_PNP, (" Symbolic Name '%S'", TargetDeviceName.Buffer)); + + // + // Open the "remote" IO Target (device) using its symbolic link. + // + WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(&OpenParams, + &TargetDeviceName, + STANDARD_RIGHTS_ALL); + OpenParams.ShareAccess = 0; // Explicite: Exclusive access + + // + // Open this serial device (Io Target) in order to send IOCTL_SERIAL_* control to it. + // + Status = WdfIoTargetOpen(IoTargetSerial, + &OpenParams); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_INFO, TFLAG_PNP, ( " WdfIoTargetOpen failed %!STATUS!", Status)); + WdfObjectDelete(IoTargetSerial); + goto Exit; + } + + *_pIoTarget = IoTargetSerial; + +Exit: + + if (SymbolicLinkList) + { + ExFreePool(SymbolicLinkList); + SymbolicLinkList = NULL; + } + + return Status; +} + + +NTSTATUS +FdoSetIdleSettings( + _In_ WDFDEVICE _Device, + _In_ IDLE_CAP_STATE _IdleCapState + ) +/*++ +Routine Description: + + This function defines how device idle (Dx) is support while system is in + (S0) for the Serial Hci device (not its child node, which is supported + in the PDO). + + If its Enuemrator is "ROOT" (in the case of using a Bluetooth dev board), + its Idle support is IdleCannotWakeFromS0. Its power capabilities are + limited to D0 and D3; it is basically on or off, and there is no Idle + while in S0. + + Vendor: If its Enumerator is ACPI, then it might be possible to support + idle while in S0. This is vendor specific. + +Arguments: + + _Device - WDF Device object + + IDLE_CAP_STATE - The idle capability state to enter + +Return Value: + + NTSTATUS + +--*/ +{ + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS IdleSettings; + NTSTATUS Status = STATUS_SUCCESS; + BOOLEAN AssignS0IdleSettings = TRUE; + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+FdoSetIdleSettings")); + + switch (_IdleCapState) + { + case IdleCapActiveOnly: + + // + // By default ACPI supports D0 active, and idle to D3 without remote wake. + // While in D3, only host (e.g. IO request) can wake the device to D0. + // + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&IdleSettings, + IdleCannotWakeFromS0); + + // Low Dx state to enter after IdleTimeout has expired and Idle is enabled. + IdleSettings.DxState = PowerDeviceD3; + IdleSettings.IdleTimeout = IdleTimeoutDefaultValue; // Use default (~5 seconds) + IdleSettings.IdleTimeoutType = DriverManagedIdleTimeout; // Driver is in control (typically for out of SoC). + + // Idle to DxState is not initially disable, and do not allow user control to enable it (as this is active only). + IdleSettings.UserControlOfIdleSettings = IdleDoNotAllowUserControl; + IdleSettings.Enabled = WdfFalse; + + // Do not wake from D3 to D0 due to system wake (Sx to S0); ie only host app can wake. + IdleSettings.PowerUpIdleDeviceOnSystemWake = WdfFalse; + break; + + case IdleCapCanWake: + + // + // If it has a child PDO and there is a controller (GPIO) being configured to support wake, + // this state can be supported. + // + // Vendor: in order to support idle in S0 for this ACPI enumerated device, specify that the device + // can wake in S0. For example, if it can wake from D2 in S0, this should be set in its device section: + // + // Name(_S0W, 0x2) + // + // Additionally, the wake interrupt, e.g. HOST_WAKE, will need to be known by ACPI (instead of exposing + // it directly to this driver as system resource); so that, ACPI will do the arming and wake on this + // driver's behalf with Dx state transition. + // + + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&IdleSettings, + IdleCanWakeFromS0); + + // Low Dx state to enter after IdleTimeout has expired and Idle is enabled. + IdleSettings.DxState = PowerDeviceD2; + IdleSettings.IdleTimeout = 0; // May want to enter D2 immediately and invoke arm wake callback. + IdleSettings.IdleTimeoutType = DriverManagedIdleTimeout; // Driver is in control (typically for out of SoC). + + // Idle to DxState is initially enable, but allow user control as well (e.g to turn off idle support). + IdleSettings.UserControlOfIdleSettings = IdleAllowUserControl; + IdleSettings.Enabled = WdfTrue; + + // + // Note: wiil invoke EvtDeviceArmWakeFromS0 callback before entering DxState; + // Driver can arm for HOST_WAKE interrrupt in the callback. + // + break; + + case IdleCapCanTurnOff: + + // + // If there is no child PDO (e.g. in Radio off mode), in effect the BT radio can be turned off + // to enter D3 state. All unused controllers (e.g. GPIO) can be turned off, also + // the Bluetooth function block. While in D3 state, only host can wake the device. + // + // Here is one approach to prevent the FDO from entering DxState while its PDO is in Dx and there is no pending IO: + // + // The PDO can hold a reference on its parent to prevent the parent from going into DxState. This is done in + // PrepareHardware with WdfDeviceStopIdle() and releasing that reference + // in the PDO's ReleaseHardware with WdfDeviceResumeIdle(). This applies to the case when the PDO is disabled. + // In the resource rebalancing case, the FDO may enter D3 shortly and then resume to D0. + // + + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&IdleSettings, + IdleCannotWakeFromS0); + + // Low Dx state to enter after IdleTimeout has expired and Idle is enabled. + IdleSettings.DxState = PowerDeviceD3; + IdleSettings.IdleTimeout = IdleTimeoutDefaultValue; + IdleSettings.IdleTimeoutType = DriverManagedIdleTimeout; // Driver is in control (typically for out of SoC). + + // Idle to DxState is initially enabled, but allow user control as well (e.g. do not turn off). + IdleSettings.UserControlOfIdleSettings = IdleAllowUserControl; + IdleSettings.Enabled = WdfTrue; + + // Do not wake from D3 to D0 due to system wake (Sx to S0); ie only host app can wake. + IdleSettings.PowerUpIdleDeviceOnSystemWake = WdfFalse; + break; + + default: + AssignS0IdleSettings = FALSE; + break; + } + + if (AssignS0IdleSettings) + { + Status = WdfDeviceAssignS0IdleSettings(_Device, + &IdleSettings); + } + + DoTrace(LEVEL_INFO, TFLAG_PNP,("-FdoSetIdleSettings %!STATUS!", Status)); + return Status; +} + + +NTSTATUS +FdoDevPrepareHardware( + _In_ WDFDEVICE _Device, + _In_ WDFCMRESLIST _ResourcesRaw, + _In_ WDFCMRESLIST _ResourcesTranslated + ) +/*++ +Routine Description: + + This PnP CB function allocate hardware related resource allocation and + perform device initialization. + +Arguments: + + _Device - WDF Device object + + _ResourcesRaw - (Not referenced) + + _ResourcesTranslated - (Not referenced) + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status; + PFDO_EXTENSION FdoExtension; + + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+FdoDevPrepareHardware")); + + // + // Acquire connection ID of connected controllers (UART and GPIO) + // + Status = FdoFindConnectResources(_Device, + _ResourcesRaw, + _ResourcesTranslated); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" Failed to find connection ID of target UART controller %!STATUS!", Status)); + + // Log(Informational): no UART Connection ID resource + + // Can still use the legacy approach to find it based on its serial interface GUID. + } + + + FdoExtension = FdoGetExtension(_Device); + + // + // Open Bluetooth UART device as a remote IO Target + // + Status = FdoOpenDevice(_Device, &FdoExtension->IoTargetSerial); + + if (!NT_SUCCESS(Status) || FdoExtension->IoTargetSerial == NULL) + { + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" FdoOpenDevice failed %!STATUS!", Status)); + + // Log(Error): Failed to open UART controller + goto Exit; + } + + // + // Initialize content of this device extension + // + Status = HlpInitializeFdoExtension(_Device); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" HlpInitializeFdoExtension failed %!STATUS!", Status)); + goto Exit; + } + + // + // Set device's idle configuration if it is capable + // + Status = FdoSetIdleSettings(_Device, + IdleCapCanTurnOff); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" FdoSetIdleSettings failed %!STATUS!", Status)); + // goto Exit; + } + + // Enable serial bus device + if (ValidConnectionID(FdoExtension->GPIOConnectionId)) { + Status = DeviceEnable(_Device, TRUE); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP,("DeviceEnable failed %!STATUS!", Status)); + goto Exit; + } + } + + // Power On serial bus device + if (ValidConnectionID(FdoExtension->I2CConnectionId)) { + Status = DevicePowerOn(_Device); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP,("DevicePowerOn failed %!STATUS!", Status)); + goto Exit; + } + } + + // + // Configure local UART controller + // + FdoExtension->DeviceInitialized = DeviceInitialize(FdoExtension, + FdoExtension->IoTargetSerial, + FdoExtension->RequestIoctlSync, + TRUE); + if (!IsDeviceInitialized(FdoExtension)) + { + // Can have issue if this UART device cannot be initalized + Status = STATUS_DEVICE_NOT_READY; + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" DeviceInitialize failed %!STATUS!", Status)); + + // Log(Error): Failed to intialize/configure the device + goto Exit; + } + +#ifdef DYNAMIC_ENUM + // + // This code segment is for testing: spawn a work item to do dynamic enuermation + // of a Bluetooth dev node (PDO); the actual implementation could be to query + // the peripheral device for what function blocks that it can support, or + // to listen for a published interface of its dependent controller driver + // to start the enuermation after driver has started. + // + { + WDF_OBJECT_ATTRIBUTES ObjAttributes; + WDF_WORKITEM_CONFIG WorkitemConfig; + WDFWORKITEM WorkItem; + PENABLE_PDO_CONTEXT Context; + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP, ("+CreateWorkItem to enable PDO")); + + WDF_OBJECT_ATTRIBUTES_INIT(&ObjAttributes); + + WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE(&ObjAttributes, + ENABLE_PDO_CONTEXT); + ObjAttributes.ParentObject = _Device; + + WDF_WORKITEM_CONFIG_INIT(&WorkitemConfig, DeviceEnablePDOWorker); + Status = WdfWorkItemCreate(&WorkitemConfig, &ObjAttributes, &WorkItem); + + if (NT_SUCCESS(Status)) + { + // Get and initialize the context + Context = GetEnablePdoWorkItemContext(WorkItem); + Context->Fdo = _Device; + + // Initialize work item context + WdfWorkItemEnqueue(WorkItem); + } + } +#else + // + // Perform static PDO enumertion by reading child device info saved in the registry. + // But the info needs to be populated first by acquired supported device for supported + // child devices. + // + Status = FdoCreateAllChildren(_Device); +#endif + +Exit: + + DoTrace(LEVEL_INFO, TFLAG_PNP, ("-FdoDevPrepareHardware %!STATUS!", Status)); + + return Status; +} + + +NTSTATUS +FdoDevReleaseHardware( + _In_ WDFDEVICE _Device, + _In_ WDFCMRESLIST _ResourcesTranslated + ) +/*++ +Routine Description: + + This PnP CB function free resource allocated in FdoDevPrepareHardware. + +Arguments: + + _Device - WDF Device object + + _ResourcesTranslated - (Not referenced) + +Return Value: + + NTSTATUS + +--*/ +{ + PFDO_EXTENSION FdoExtension; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(_ResourcesTranslated); + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+PnpReleaseHardware")); + + FdoExtension = FdoGetExtension(_Device); + + if (FdoExtension->IoTargetSerial) + { + WdfObjectDelete(FdoExtension->IoTargetSerial); + FdoExtension->IoTargetSerial = NULL; + } + + return STATUS_SUCCESS; +} + + + +NTSTATUS +FdoDevSelfManagedIoInit( + _In_ WDFDEVICE _Device +) +/*++ +Routine Description: + + This PnP CB function is invoked once and will perform IO related resource allocation + and start the read pump. + +Arguments: + + _Device - WDF Device object + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status; + PFDO_EXTENSION FdoExtension; + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+FdoDevSelfManagedIoInit")); + + // + // Preallocate resources needed to perform read opeations + // + Status = ReadResourcesAllocate(_Device); + + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" ReadResourcesAllocate failed %!STATUS!", Status)); + goto Exit; + } + + // Issue pending IO request to prefetch HCI event and data + FdoExtension = FdoGetExtension(_Device); + FdoExtension->ReadContext.RequestState = REQUEST_COMPLETE; + + // Start the read pump + FdoExtension->ReadPumpRunning = TRUE; + Status = ReadH4Packet(&FdoExtension->ReadContext, + FdoExtension->ReadRequest, + FdoExtension->ReadMemory, + FdoExtension->ReadBuffer, + INITIAL_H4_READ_SIZE); + + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" ReadH4Packet failed %!STATUS!", Status)); + goto Exit; + } + +Exit: + + return Status; +} + + +VOID +FdoDevSelfManagedIoCleanup( + _In_ WDFDEVICE _Device + ) +/*++ +Routine Description: + + This PnP CB function is invoked once and will be used here to free resource + that was alocated in its corresponding SelfMagedInit fucntion. + +Arguments: + + _Device - WDF Device object + +Return Value: + + none + +--*/ +{ + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+FdoDevSelfManagedIoCleanup")); + + // + // Cancel and free resources + // + ReadResourcesFree(_Device); + + return; +} + + +NTSTATUS +FdoDevD0Entry( + _In_ WDFDEVICE _Device, + _In_ WDF_POWER_DEVICE_STATE _PreviousState + ) +/*++ +Routine Description: + + This PnP CB function is invoked after device has enter D0 (working) state. Most + of initilization of hardware is already performed in PrepareHardware CB but will + be performed again if the device was resume from non-D0 state. + +Arguments: + + _Device - WDF Device object + + PreviousState - Next power state it is entering from D0 + +Return Value: + + NTSTATUS + +--*/ +{ + PFDO_EXTENSION FdoExtension = FdoGetExtension(_Device); + NTSTATUS Status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(_PreviousState); + + DoTrace(LEVEL_INFO, TFLAG_UART, ("+FdoDevD0Entry")); + + // Reset error count upon resume to D0 + FdoExtension->OutOfSyncErrorCount = 0; + + // Initialize serial port settings if re-enter D0 + if (!IsDeviceInitialized(FdoExtension)) { + + // Enable serial bus device + if (ValidConnectionID(FdoExtension->GPIOConnectionId)) { + Status = DeviceEnable(_Device, TRUE); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP,("DeviceEnable failed %!STATUS!", Status)); + goto Done; + } + } + + // Power On serial bus device + if (ValidConnectionID(FdoExtension->I2CConnectionId)) { + Status = DevicePowerOn(_Device); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP,("DevicePowerOn failed %!STATUS!", Status)); + goto Done; + } + } + + // + // The local UART may need to be re-initialized to match the remote UART if its context + // was lost, but the assumption here is that the UART controller driver does save and + // restore its context. + // +#ifdef REQUIRE_REINITIALIZE + + // Reinitialize serial bus device + FdoExtension->DeviceInitialized = DeviceInitialize(FdoExtension, + FdoExtension->IoTargetSerial, + FdoExtension->RequestIoctlSync, + FALSE); + if (!IsDeviceInitialized(FdoExtension)) { + Status = STATUS_DEVICE_NOT_READY; + DoTrace(LEVEL_ERROR, TFLAG_PNP, ("DeviceInitialize failed!")); + goto Done; + } + +#else + // Set to TRUE in order to restart the read pump + FdoExtension->DeviceInitialized = TRUE; +#endif + + // Restart the IOTarget to receiving request + Status = WdfIoTargetStart(FdoExtension->IoTargetSerial); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP, ("WdfIoTargetStart failed %!STATUS!", Status)); + goto Done; + } + + // Restart read pump + DoTrace(LEVEL_INFO, TFLAG_IO, (" Restarting read pump")); + Status = ReadH4Packet(&FdoExtension->ReadContext, + FdoExtension->ReadRequest, + FdoExtension->ReadMemory, + FdoExtension->ReadBuffer, + INITIAL_H4_READ_SIZE); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_IO, ("ReadH4Packet [0] failed %!STATUS!", Status)); + goto Done; + } + } + +Done: + + DoTrace(LEVEL_INFO, TFLAG_UART, ("-FdoDevD0Entry %!STATUS!", Status)); + + return Status; +} + + +NTSTATUS +FdoDevD0Exit( + _In_ WDFDEVICE _Device, + _In_ WDF_POWER_DEVICE_STATE _TargetState + ) +/*++ +Routine Description: + + This PnP CB function is invoked when device has exited D0 (working) state. + It stops the queue and can be restarted later, and mark the device uninitialize + and will be initialized again when resumes to D0. + +Arguments: + + _Device - WDF Device object + + _TargetState - Next power state it is entering from D0 + +Return Value: + + NTSTATUS + +--*/ +{ + PFDO_EXTENSION FdoExtension = FdoGetExtension(_Device); + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(_TargetState); + + DoTrace(LEVEL_INFO, TFLAG_UART, ("+FdoDevD0Exit D0-> D%d", _TargetState-WdfPowerDeviceD0)); + + // Cancel IO requests that are already in the IO queue, + // wait for their completion before this function is returned. + // Can restart this queue at later time. + WdfIoTargetStop(FdoExtension->IoTargetSerial, WdfIoTargetCancelSentIo); + + // Delete GPIO IoTarget to disable the device and this will + // require device to be re-initialized when it re-enters D0. + if (FdoExtension->IoTargetGPIO) + { + WdfObjectDelete(FdoExtension->IoTargetGPIO); + FdoExtension->IoTargetGPIO = NULL; + } + FdoExtension->DeviceInitialized = FALSE; + + // + // Note: Do not delete the UART's IoTarget. + // + + DoTrace(LEVEL_INFO, TFLAG_UART, ("-FdoDevD0Exit")); + + return STATUS_SUCCESS; +} + + +NTSTATUS +HCIContextValidate( + ULONG _Index, + PBTHX_HCI_READ_WRITE_CONTEXT _HCIContext + ) +/*++ +Routine Description: + + This function validate the incoming data context and print out (WPP) trace. + +Arguments: + + _Index - count number of HCI command/event/data that has been completed (0 based). + _HCIContext - Context to be valdiated + +Return Value: + + NTSTATUS - STATUS_SUCCESS or STATUS_INVALID_PARAMETER + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONG Index; + + DoTrace(LEVEL_INFO, TFLAG_HCI,("+HCIContextValidate")); + + switch ((BTHX_HCI_PACKET_TYPE) _HCIContext->Type) + { + case HciPacketCommand: + { + PHCI_COMMAND_PACKET HciCommand = (PHCI_COMMAND_PACKET) _HCIContext->Data; + DoTrace(LEVEL_INFO, TFLAG_HCI, (" -> HCI Command [%d] OpCode: 0x%x, nParams: %d ---->", + _Index, + HciCommand->Opcode, + HciCommand->ParamsCount)); + + for (Index = 0; Index < MinToPrint((ULONG) HciCommand->ParamsCount, MAX_COMMAND_PARAMS_TO_DISPLAY); Index++) + { + DoTrace(LEVEL_INFO, TFLAG_HCI, (" [%d] 0x%.2x", + Index, HciCommand->Params[Index])); + } + + if (!WithinRange(MIN_HCI_CMD_SIZE, _HCIContext->DataLen, MAX_HCI_CMD_SIZE)) + { + Status = STATUS_INVALID_PARAMETER; + DoTrace(LEVEL_ERROR, TFLAG_HCI,(" HciPacketCommand %!STATUS!", Status)); + break; + } + } + break; + + case HciPacketEvent: + { + PHCI_EVENT_PACKET HciEvent = (PHCI_EVENT_PACKET) _HCIContext->Data; + DoTrace(LEVEL_INFO, TFLAG_HCI, (" <- HCI Event [%d] EventCode: 0x%x (%S), nParams: %d", + _Index, + HciEvent->EventCode, + HciEvent->EventCode == CommandComplete ? L"Complete" : + HciEvent->EventCode == CommandStatus ? L"Status(Async)!!" : L"??", + HciEvent->ParamsCount)); + + // Note if CommandStatus is returned, there will be another event to complete this command. + + for (Index = 0; Index < MinToPrint((ULONG) HciEvent->ParamsCount, MAX_EVENT_PARAMS_TO_DISPLAY); Index++) + { + DoTrace(LEVEL_VERBOSE, TFLAG_HCI, (" [%d] 0x%.2x", + Index, HciEvent->Params[Index])); + } + + if (!WithinRange(MIN_HCI_EVENT_SIZE, _HCIContext->DataLen, MAX_HCI_EVENT_SIZE)) + { + Status = STATUS_INVALID_PARAMETER; + DoTrace(LEVEL_ERROR, TFLAG_HCI,(" HciPacketEvent %!STATUS!", Status)); + break; + } + } + break; + + case HciPacketAclData: + { + PHCI_ACLDATA_PACKET AclData = (PHCI_ACLDATA_PACKET) _HCIContext->Data; + DoTrace(LEVEL_INFO, TFLAG_HCI, (" HCI Data [%d] (Handle:0x%x, PB:%x, BC:%x, Length:%d)", + _Index, + AclData->ConnectionHandle, + AclData->PBFlag, + AclData->BCFlag, + AclData->DataLength)); + + for (Index = 0; Index < (ULONG) (AclData->DataLength > 8 ? 8 : AclData->DataLength); Index++) + { + DoTrace(LEVEL_VERBOSE, TFLAG_HCI, (" [%d] 0x%.2x", + Index, AclData->Data[Index])); + } + + if (!WithinRange(1, AclData->DataLength, MAX_HCI_ACLDATA_SIZE)) + { + Status = STATUS_INVALID_PARAMETER; + DoTrace(LEVEL_ERROR, TFLAG_HCI,(" HciPacketAclData data (%d) exceeds its max %d, %!STATUS!", + AclData->DataLength, MAX_HCI_ACLDATA_SIZE, Status)); + break; + } + } + break; + + default: + DoTrace(LEVEL_ERROR, TFLAG_HCI, (" Packet type %d unexpected!", _HCIContext->Type)); + Status = STATUS_INVALID_PARAMETER; + break; + } + + NT_ASSERT(NT_SUCCESS(Status) && L"Invlaid data is detected!"); + + DoTrace(LEVEL_INFO, TFLAG_HCI,("-HCIContextValidate %!STATUS!", Status)); + + return Status; +} + + + + +NTSTATUS +FdoWriteDeviceIO( + _In_ WDFREQUEST _RequestFromBthport, + _In_ WDFDEVICE _Device, + _In_ PFDO_EXTENSION _FdoExtension, + _In_ PBTHX_HCI_READ_WRITE_CONTEXT _HCIContext + ) +/*++ + +Routine Description: + + This function send an HCI packet to target device. + +Arguments: + + _RequestFromBthport - Request from upper layer that initiate this transfer + _Device - WDF Device Object + _FdoExtension - Device's context + _HCIContext - Context used to process this HCI + +Return Value: + + NTSTATUS + +--*/ +{ + WDF_OBJECT_ATTRIBUTES ObjAttributes; + NTSTATUS Status; + WDFREQUEST RequestToUART; + PUART_WRITE_CONTEXT TransferContext = NULL; + ULONG DataLength; + PVOID Data = NULL; + + DoTrace(LEVEL_INFO, TFLAG_DATA,("+FdoWriteDeviceIO")); + + + if (!IsDeviceInitialized(_FdoExtension)) + { + Status = STATUS_DEVICE_NOT_READY; + DoTrace(LEVEL_ERROR, TFLAG_IO, (" FdoWriteDeviceIO: cannot attach IO %!STATUS!", Status)); + goto Done; + } + + // + // Add a context to this existing WDFREQUEST for cancellation purpose + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&ObjAttributes, + UART_WRITE_CONTEXT); + + Status = WdfObjectAllocateContext(_RequestFromBthport, + &ObjAttributes, + &TransferContext); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfObjectAllocateContext failed %!STATUS!", Status)); + goto Done; + } + + Status = HLP_AllocateResourceForWrite( + _Device, + _FdoExtension->IoTargetSerial, + &RequestToUART); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO,(" HLP_WriteDeviceIO %!STATUS!", Status)); + goto Done; + } + + WDF_OBJECT_ATTRIBUTES_INIT(&ObjAttributes); + ObjAttributes.ParentObject = _Device; + + // Reuse the data buffer coming from upper layer; UART's HCI packet starts with + // packet type, and then follows by the actual HCI packet. + Data = (PVOID) &_HCIContext->Type; + DataLength = (ULONG) sizeof(_HCIContext->Type) + _HCIContext->DataLen; + + _Analysis_assume_(DataLength > 0); + Status = WdfMemoryCreatePreallocated(&ObjAttributes, + Data, + DataLength, + &TransferContext->Memory); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfMemoryCreatePreallocated failed %!STATUS!", Status)); + goto Done; + } + + Status = WdfIoTargetFormatRequestForWrite(_FdoExtension->IoTargetSerial, + RequestToUART, + TransferContext->Memory, + NULL, + NULL); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfIoTargetFormatRequestForRead failed %!STATUS!", Status)); + goto Done; + } + + // Setup transfer context + TransferContext->FdoExtension = _FdoExtension; + TransferContext->HCIContext = _HCIContext; + TransferContext->RequestFromBthport = _RequestFromBthport; + TransferContext->RequestCompletePath = REQUEST_PATH_NONE; + TransferContext->RequestToUART = RequestToUART; + TransferContext->HCIPacket = Data; + TransferContext->HCIPacketLen = DataLength; + + // + // Both Requests are typically accessed by the completion routine, and in rare case also + // by the cancellation callback. Take a reference on them to ensure they stays valid in both cases. + // + WdfObjectReference(RequestToUART); + WdfObjectReference(_RequestFromBthport); + + + // Mark cancellable of the Request in our possession from upper layer + // Cannot mark the request that we will forward to lower driver cancellable. + // Only if the Request from upper layer is cancelled, we will then cancel the + // Request that is sent to lower driver. + WdfRequestMarkCancelable(_RequestFromBthport, CB_RequestFromBthportCancel); + + WdfRequestSetCompletionRoutine(RequestToUART, CR_WriteDeviceIO, TransferContext); + + // This request will be delivered to its IoTarget asynchronously (the default option). It should return + // STATUS_PENDING unless there is an error in its delivery to its IoTarget. After it has been delivered + // successfully, its completion function will be called for any outcome - success, failure, or cancellation. + if (!WdfRequestSend(RequestToUART, _FdoExtension->IoTargetSerial, WDF_NO_SEND_OPTIONS)) + { + NTSTATUS StatusTemp; + + // Get failure status, and this request will be completed by its caller of this function with this status. + Status = WdfRequestGetStatus(RequestToUART); + + // Unmark cancellable before it is completed. + StatusTemp = WdfRequestUnmarkCancelable(_RequestFromBthport); + + // Balance the reference count for both Requests due to failure. + WdfObjectDereference(RequestToUART); + WdfObjectDereference(_RequestFromBthport); + + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfRequestSend failed %!STATUS! and UnmarkCancelable %!STATUS!", Status, StatusTemp)); + goto Done; + } + else + { + // Request has been delivered to UART driver asychronously. It will be completed in its completion function + // after IoTarget (UART driver) completes its delivery to the BT controller. + } + +Done: + + if (!NT_SUCCESS(Status)) + { + HLP_FreeResourceForWrite(TransferContext); + } + + DoTrace(LEVEL_INFO, TFLAG_IO, ("-FdoWriteDeviceIO %!STATUS!", Status)); + + return Status; +} + + +NTSTATUS +FdoWriteToDeviceSync( + _In_ WDFIOTARGET _IoTargetSerial, + _In_ WDFREQUEST _RequestWriteSync, + _In_ ULONG _IoControlCode, + _In_opt_ ULONG _InBufferSize, + _In_opt_ PVOID _InBuffer, + _Out_ PULONG_PTR _BytesWritten +) +/*++ +Routine Description: + + This helper function send a synchronous write or Ioctl Request to device with + timeout (to prevent hang). + +Arguments: + + _IoTargetSerial - Serial port IO Target where to issue this request to + _RequestWriteSync - caller allocated WDF Request + _IoControlCode - IOCTL control code; if 0, it is a Write request. + _InBufferSize - Input buffer size + _InBuffer - (optional) Input buffer + _BytesWritten - Bytes written to device; this is driver dependent; a write + could be successfully (and fully) written with 0 BytesWritten. + +Return Value: + + NTSTATUS - STATUS_SUCCESS or Status from issuing this request + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + WDF_REQUEST_REUSE_PARAMS RequestReuseParams; + WDF_REQUEST_SEND_OPTIONS Options; + WDF_MEMORY_DESCRIPTOR MemoryDescriptor; + ULONG_PTR BytesWritten = 0; + BOOLEAN HasInputParam = FALSE; + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_IO,("+FdoWriteToDeviceSync")); + + + WDF_REQUEST_REUSE_PARAMS_INIT(&RequestReuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_SUCCESS); + Status = WdfRequestReuse(_RequestWriteSync, &RequestReuseParams); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfRequestReuse failed %!STATUS!", Status)); + goto Done; + } + + if (_InBuffer && _InBufferSize) { + HasInputParam = TRUE; + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&MemoryDescriptor, + _InBuffer, + _InBufferSize); + } + + WDF_REQUEST_SEND_OPTIONS_INIT(&Options, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS); + WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&Options, WDF_REL_TIMEOUT_IN_SEC(MAX_WRITE_TIMEOUT_IN_SEC)); + + if (_IoControlCode) + { + Status = WdfIoTargetSendIoctlSynchronously(_IoTargetSerial, + NULL, + _IoControlCode, + HasInputParam ? &MemoryDescriptor : NULL, // InputBuffer + NULL, // OutputBuffer + &Options, // RequestOptions + &BytesWritten // BytesReturned + ); + } + else + { + Status = WdfIoTargetSendWriteSynchronously(_IoTargetSerial, + NULL, + HasInputParam ? &MemoryDescriptor : NULL, // InputBuffer + NULL, // DeviceOffset + &Options, // RequestOptions + &BytesWritten // BytesReturned + ); + } + + DoTrace(LEVEL_INFO, TFLAG_IO,("-FdoWriteToDeviceSync: %d BytesWritten %!STATUS!", (ULONG) BytesWritten, Status)); + + if (NT_SUCCESS(Status)) + { + *_BytesWritten = BytesWritten; + } + +Done: + return Status; +} + + +VOID +FdoIoQuDeviceControl( + _In_ WDFQUEUE _Queue, + _In_ WDFREQUEST _Request, + _In_ size_t _OutputBufferLength, + _In_ size_t _InputBufferLength, + _In_ ULONG _IoControlCode + ) +/*++ + +Routine Description: + + This routine is the dispatch routine for device control requests. + +Arguments: + + _Queue - Handle to the framework queue object that is associated + with the I/O request. + _Request - Handle to a framework request object. + + _OutputBufferLength - length of the request's output buffer, + if an output buffer is available. + _InputBufferLength - length of the request's input buffer, + if an input buffer is available. + + _IoControlCode - the driver-defined or system-defined I/O control code + (IOCTL) that is associated with the request. + +Return Value: + + VOID + +--*/ +{ + WDFMEMORY ReqInMemory = NULL, ReqOutMemory = NULL; + PVOID InBuffer = NULL, OutBuffer = NULL; + size_t InBufferSize = 0, OutBufferSize = 0; + PFDO_EXTENSION FdoExtension; + NTSTATUS Status = STATUS_SUCCESS; + WDFDEVICE Device; + BOOLEAN CompleteRequest = FALSE; + ULONG ControlCode = (_IoControlCode & 0x00003ffc) >> 2; + KIRQL Irql; + BTHX_HCI_PACKET_TYPE PacketType; + PBTHX_HCI_READ_WRITE_CONTEXT HCIContext; + + + DoTrace(LEVEL_INFO, TFLAG_IOCTL,("+IoDeviceControl - InBufLen:%d, OutBufLen:%d", + (ULONG) _InputBufferLength, (ULONG) _OutputBufferLength)); + + Device = WdfIoQueueGetDevice(_Queue); + + FdoExtension = FdoGetExtension(Device); + + if (_InputBufferLength) + { + Status = WdfRequestRetrieveInputMemory(_Request, &ReqInMemory); + if (NT_SUCCESS(Status)) + { + InBuffer = WdfMemoryGetBuffer(ReqInMemory, &InBufferSize); + } + } + + if (_OutputBufferLength) + { + Status = WdfRequestRetrieveOutputMemory(_Request, &ReqOutMemory); + if (NT_SUCCESS(Status)) + { + OutBuffer = WdfMemoryGetBuffer(ReqOutMemory, &OutBufferSize); + } + } + + switch (_IoControlCode) + { + case IOCTL_BTHX_WRITE_HCI: + DoTrace(LEVEL_INFO, TFLAG_IOCTL,(" IOCTL_BTHX_WRITE_HCI ---------->")); + // Validate input and output parameters + if (!InBuffer || InBufferSize < sizeof(BTHX_HCI_READ_WRITE_CONTEXT) || + !OutBuffer || OutBufferSize != sizeof(BTHX_HCI_PACKET_TYPE)) + { + Status = STATUS_INVALID_PARAMETER; + DoTrace(LEVEL_ERROR, TFLAG_IOCTL,(" IOCTL_BTHX_WRITE_HCI %!STATUS!", Status)); + break; + } + + HCIContext = (PBTHX_HCI_READ_WRITE_CONTEXT) InBuffer; + + PacketType = (BTHX_HCI_PACKET_TYPE) HCIContext->Type; + + if (!BTHX_VALID_WRITE_PACKET_TYPE(PacketType)) + { + Status = STATUS_INVALID_PARAMETER; + DoTrace(LEVEL_ERROR, TFLAG_IOCTL,(" Mismach Write HCI packet type and IOCTL %!STATUS!", Status)); + break; + } + + if (PacketType == HciPacketCommand) + { + InterlockedIncrement(&FdoExtension->CntCommandReq); + } + else + { + InterlockedIncrement(&FdoExtension->CntWriteDataReq); + } + + Status = FdoWriteDeviceIO(_Request, + Device, + FdoExtension, + HCIContext); + break; + + case IOCTL_BTHX_READ_HCI: + DoTrace(LEVEL_INFO, TFLAG_IOCTL,(" IOCTL_BTHX_READ_HCI <----------")); + // Validate input and output parameters + if (!InBuffer || InBufferSize != sizeof(BTHX_HCI_PACKET_TYPE) || + !OutBuffer || OutBufferSize < sizeof(BTHX_HCI_READ_WRITE_CONTEXT)) + { + Status = STATUS_INVALID_PARAMETER; + DoTrace(LEVEL_ERROR, TFLAG_IOCTL,(" IOCTL_BTHX_READ_HCI %!STATUS!", Status)); + break; + } + + PacketType = *((BTHX_HCI_PACKET_TYPE *) InBuffer); + + if (!BTHX_VALID_READ_PACKET_TYPE(PacketType)) + { + Status = STATUS_INVALID_PARAMETER; + DoTrace(LEVEL_ERROR, TFLAG_IOCTL,(" IOCTL_BTHX_READ_HCI %!STATUS!", Status)); + break; + } + + if (PacketType == HciPacketEvent) + { + KeAcquireSpinLock(&FdoExtension->QueueAccessLock, &Irql); + // Queue the new request to preserve sequential order + Status = WdfRequestForwardToIoQueue(_Request, FdoExtension->ReadEventQueue); + if (NT_SUCCESS(Status)) + { + InterlockedIncrement(&FdoExtension->EventQueueCount); + InterlockedIncrement(&FdoExtension->CntEventReq); + } + KeReleaseSpinLock(&FdoExtension->QueueAccessLock, Irql); + + if (NT_SUCCESS(Status)) + { + Status = ReadRequestComplete(FdoExtension, + HciPacketEvent, + 0, NULL, + FdoExtension->ReadEventQueue, + &FdoExtension->EventQueueCount, + &FdoExtension->ReadEventList, + &FdoExtension->EventListCount); + } + + } + else if (PacketType == HciPacketAclData) + { + KeAcquireSpinLock(&FdoExtension->QueueAccessLock, &Irql); + // Queue the new request to preserve sequential order + Status = WdfRequestForwardToIoQueue(_Request, FdoExtension->ReadDataQueue); + if (NT_SUCCESS(Status)) + { + InterlockedIncrement(&FdoExtension->DataQueueCount); + InterlockedIncrement(&FdoExtension->CntReadDataReq); + } + KeReleaseSpinLock(&FdoExtension->QueueAccessLock, Irql); + + if (NT_SUCCESS(Status)) + { + Status = ReadRequestComplete(FdoExtension, + HciPacketAclData, + 0, NULL, + FdoExtension->ReadDataQueue, + &FdoExtension->DataQueueCount, + &FdoExtension->ReadDataList, + &FdoExtension->DataListCount); + } + } + else + { + Status = STATUS_INVALID_PARAMETER; + DoTrace(LEVEL_ERROR, TFLAG_IOCTL,(" IOCTL_BTHX_READ_HCI %!STATUS!", Status)); + break; + } + break; + + case IOCTL_BTHX_GET_VERSION: + CompleteRequest = TRUE; + DoTrace(LEVEL_INFO, TFLAG_IOCTL,("IOCTL_BTHX_GET_VERSION")); + + if (OutBuffer && OutBufferSize >= sizeof(BTHX_VERSION)) + { + RtlCopyMemory(OutBuffer, &Microsoft_BTHX_DDI_Version, sizeof(BTHX_VERSION)); + WdfRequestCompleteWithInformation(_Request, Status, sizeof(BTHX_VERSION)); + return; + } + else + { + Status = STATUS_INVALID_PARAMETER; + } + break; + + case IOCTL_BTHX_SET_VERSION: + CompleteRequest = TRUE; + DoTrace(LEVEL_INFO, TFLAG_IOCTL,("IOCTL_BTHX_SET_VERSION")); + + if (InBuffer && InBufferSize >= sizeof(BTHX_VERSION)) + { + BTHX_VERSION SupportedVersion = *((BTHX_VERSION *)InBuffer); + + DoTrace(LEVEL_INFO, TFLAG_IOCTL,("IOCTL_BTHX_SET_VERSION 0x%x", SupportedVersion.Version)); + + WdfRequestComplete(_Request, Status); + return; + } + else + { + Status = STATUS_INVALID_PARAMETER; + } + break; + + case IOCTL_BTHX_QUERY_CAPABILITIES: + CompleteRequest = TRUE; + DoTrace(LEVEL_INFO, TFLAG_IOCTL,("IOCTL_BTHX_QUERY_CAPABILITIES")); + + if (OutBuffer && OutBufferSize >= sizeof(BTHX_CAPABILITIES)) + { + BTHX_CAPABILITIES *pCaps = (BTHX_CAPABILITIES *) OutBuffer; + + RtlCopyMemory(pCaps, &FdoExtension->BthXCaps, sizeof(BTHX_CAPABILITIES)); + WdfRequestCompleteWithInformation(_Request, Status, sizeof(BTHX_CAPABILITIES)); + return; + } + else + { + Status = STATUS_INVALID_PARAMETER; + } + break; + + // + // This IOCTL is used to support radio on/off feature by doing the following + // 1. Power up/down the Bluetooth radio function, and + // 2. Add/remove a PDO for Bluetooth devnode; + // + case IOCTL_BUSENUM_SET_RADIO_ONOFF_VENDOR_SPECFIC: + CompleteRequest = TRUE; + DoTrace(LEVEL_INFO, TFLAG_IOCTL,("IOCTL_BUSENUM_SET_RADIO_ONOFF_VENDOR_SPECFIC")); + if (InBuffer && InBufferSize >= sizeof(BOOLEAN)) { + BOOLEAN IsRadioEnabled = *((BOOLEAN *) InBuffer); + + if (IsRadioEnabled) { + if (!FdoExtension->IsRadioEnabled) { + + // + // 1. Power up the Bluetooth function of this device; + // + Status = DevicePowerOn(Device); + + if (NT_SUCCESS(Status)) { + + // + // 2. Create a PDO for the Bluetooth devnode; + // + Status = FdoCreateOneChildDevice(Device, + BT_PDO_HARDWARE_IDS, + BLUETOOTH_FUNC_IDS); + if (NT_SUCCESS(Status)) { + FdoExtension->IsRadioEnabled = TRUE; + } + } + DoTrace(LEVEL_INFO, TFLAG_IOCTL,(" EnableRadio %!STATUS!", Status)); + } + else { + Status = STATUS_SUCCESS; + DoTrace(LEVEL_WARNING, TFLAG_IOCTL,(" Already enabled!")); + } + } + else { + if (FdoExtension->IsRadioEnabled) { + + // + // 1. Remove the PDO for the Bluetooth devnode; + // + Status = FdoRemoveOneChildDevice(Device, + BLUETOOTH_FUNC_IDS); + if (NT_SUCCESS(Status)) { + FdoExtension->IsRadioEnabled = FALSE; + + // + // 2. Power down the Bluetooth function (at least the antenna) of this device; + // + Status = DevicePowerOff(Device); + } + + DoTrace(LEVEL_INFO, TFLAG_IOCTL,(" DisableRadio %!STATUS!", Status)); + } + else { + Status = STATUS_SUCCESS; + DoTrace(LEVEL_WARNING, TFLAG_IOCTL,(" Already disabled!")); + } + } + } + else { + Status = STATUS_INVALID_PARAMETER; + } + break; + + default: + DoTrace(LEVEL_INFO, TFLAG_IOCTL,(" IOCTL_(0x%x, Func %d)", _IoControlCode, ControlCode)); + Status = STATUS_NOT_SUPPORTED; + break; + } + + if (!NT_SUCCESS(Status) || CompleteRequest) + { + WdfRequestComplete(_Request, Status); + } + + return; +} diff --git a/bluetooth/serialhcibus/Io.h b/bluetooth/serialhcibus/Io.h new file mode 100644 index 00000000..76abb82a --- /dev/null +++ b/bluetooth/serialhcibus/Io.h @@ -0,0 +1,251 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + io.h + +Abstract: + + Common header definitions and structs for read and write (IO) operation + +Author: + +Environment: + + Kernel mode only + + +Revision History: + +--*/ + +#ifndef __IO_H__ +#define __IO_H__ + +// +// 255 bytes of data + 3 bytes for HCI cmd hdr (2-byte opcode + 1-byte Parameter). +// +#define MIN_HCI_CMD_SIZE (3) +#define MAX_HCI_CMD_SIZE (258) + +// +// 255 bytes of data + 2 byte hdr (1-byte event code + 1-byte parameter). +// +#define MIN_HCI_EVENT_SIZE (2) +#define HCI_EVENT_HEADER_SIZE (2) +#define MAX_HCI_EVENT_SIZE (257) + +// +// Can be variable but usually 1021-byte (largest 3-DH5 ACL packet size) +// +#define HCI_ACL_HEADER_SIZE (4) +#define HCI_MAX_ACL_PAYLOAD_SIZE (1021) +#define MIN_HCI_ACLDATA_SIZE HCI_ACL_HEADER_SIZE +#define MAX_HCI_ACLDATA_SIZE (HCI_ACL_HEADER_SIZE + HCI_MAX_ACL_PAYLOAD_SIZE) + +#define INITIAL_H4_READ_SIZE (1+HCI_EVENT_HEADER_SIZE) +#define MAX_H4_HCI_PACKET_SIZE (1+HCI_ACL_HEADER_SIZE + HCI_MAX_ACL_PAYLOAD_SIZE) // include packet type + +#define BUFFER_AND_SIZE_ADJUSTED(Buffer, Size, SegmentCount, Increment) {Buffer += Increment; Size -= Increment; SegmentCount += Increment;} + +#include <PSHPACK1.H> + +// +// Standard HCI packet structs for Command, Event and ACL Data +// +typedef struct _HCI_COMMAND_PACKET { + UINT16 Opcode; + UCHAR ParamsCount; // 0..255 + UCHAR Params[1]; +} HCI_COMMAND_PACKET, *PHCI_COMMAND_PACKET; +#define HCI_COMMAND_HEADER_LEN FIELD_OFFSET(HCI_COMMAND_PACKET, Params) + +typedef struct _HCI_EVENT_PACKET { + UCHAR EventCode; + UCHAR ParamsCount; // 0..255 + UCHAR Params[1]; +} HCI_EVENT_PACKET, *PHCI_EVENT_PACKET; +#define HCI_EVENT_HEADER_LEN FIELD_OFFSET(HCI_EVENT_PACKET, Params) + +typedef struct _HCI_ACLDATA_PACKET { + UINT16 ConnectionHandle : 12; + UINT16 PBFlag : 2; + UINT16 BCFlag : 2; + UINT16 DataLength; // 0..65535 + UCHAR Data[1]; +} HCI_ACLDATA_PACKET, *PHCI_ACLDATA_PACKET; +#define HCI_ACLDATA_HEADER_LEN FIELD_OFFSET(HCI_ACLDATA_PACKET, Data) + +// +// UART packet that has a leading packet type over standard HCI packet +// + +typedef struct _H4_PACKET { + UCHAR Type; + union { + HCI_COMMAND_PACKET Command; + HCI_EVENT_PACKET Event; + HCI_ACLDATA_PACKET AclData; + UCHAR Raw[MAX_HCI_ACLDATA_SIZE]; + } Packet; +} H4_PACKET, *PH4_PACKET; + +typedef struct _UART_COMMAND_PACKET { + UCHAR Type; + HCI_COMMAND_PACKET Packet; +} UART_COMMAND_PACKET, *PUART_COMMAND_PACKET; + +typedef struct _UART_EVENT_PACKET { + UCHAR Type; + HCI_EVENT_PACKET Packet; +} UART_EVENT_PACKET, *PUART_EVENT_PACKET; + +typedef struct _UART_ACLDATA_PACKET { + UCHAR Type; + HCI_ACLDATA_PACKET Packet; +} UART_ACLDATA_PACKET, *PUART_ACLDATA_PACKET; + +#include <POPPACK.H> + + +typedef struct _FDO_EXTENSION *PFDO_EXTENSION; + + +#define REQUEST_PATH_NONE 0x00000000 +#define REQUEST_PATH_CANCELLATION 0x00000001 +#define REQUEST_PATH_COMPLETION 0x00000002 + + +// +// Context used for data transfer to device (write) +// +typedef struct _UART_WRITE_CONTEXT { + + // + // Back pointer to the FDO's extension + // + PFDO_EXTENSION FdoExtension; + + // + // Request from BthPort upper driver + // + WDFREQUEST RequestFromBthport; + + // + // Flag(Bit) to determine ownership for completing RequestFromBthport + // + LONG RequestCompletePath; + + // + // Request to perform this transfer to UART device + // + WDFREQUEST RequestToUART; + + // + // Memory object for data + // + WDFMEMORY Memory; + + // + // The caller's transfer context. + // + PBTHX_HCI_READ_WRITE_CONTEXT HCIContext; + + // + // Pointer to the data buffer from client's incoming data; not a copy. + // + PVOID HCIPacket; + + // + // Packet length, including packet type. + // + ULONG HCIPacketLen; + +} UART_WRITE_CONTEXT, *PUART_WRITE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(UART_WRITE_CONTEXT, GetWriteRequestContext) + + +// +// State machine used for reading incoming data streaming to form HCI event or data packet +// +typedef enum { + GET_PKT_TYPE = 1, // For UART/H4, UCHAR of packet type (Event or Data) + GET_PKT_HEADER = 2, // Get length to determine remaining payload + GET_PKT_PAYLOAD = 3 // Data payload +} UART_READ_STATE; + + +// +// A list to store prefetched (read) HCI packets utill they are retrieved. +// +typedef struct _HCI_PACKET_ENTRY { + LIST_ENTRY DataEntry; + ULONG PacketLen; + _Field_size_bytes_(PacketLen) UCHAR Packet[1]; +} HCI_PACKET_ENTRY, *PHCI_PACKET_ENTRY; + +// +// Use to track request completion path +// +typedef enum _READ_REQUEST_STATE { + REQUEST_SENT = 1, // Request is being sent + REQUEST_PENDING = 2, // Request is pending first - asynchronous completion + REQUEST_COMPLETE = 3 // Request has completedly first - synchronous completion +} READ_REQUEST_STATE; + + +// +// Context used for reading UART operation to form HCI data or event packet +// +typedef struct _UART_READ_CONTEXT { + + // + // Status of this request + // + NTSTATUS Status; + + // + // Back pointer to the device extension + // + PFDO_EXTENSION FdoExtension; + + // + // State machine for the read Request + // + READ_REQUEST_STATE RequestState; + + // + // State machine of repeat read (read pump) to complete an HCI packet + // + UART_READ_STATE ReadSegmentState; + + // + // Bytes read for each Segment (Type, Header, and Paylaod) of a partial H4 packet below + // + ULONG BytesReadNextSegment; + + // + // Bytes to read in order to have a full packet (only meaningful in GET_PKT_PAYLOAD state. + // + ULONG BytesToRead4FullPacket; + + // + // A union of H4 packet + // + H4_PACKET H4Packet; + +} UART_READ_CONTEXT, *PUART_READ_CONTEXT; + +#define MAX_HARDWARE_ERROR_COUNT 0 // Do not allow any error this time + + +// Timeout value for synchronous read and write requests +#define MAX_WRITE_TIMEOUT_IN_SEC 1 // unit = second +#define MAX_READ_TIMEOUT_IN_SEC 1 + + +#endif + diff --git a/bluetooth/serialhcibus/ReadMe.md b/bluetooth/serialhcibus/ReadMe.md new file mode 100644 index 00000000..871bb9e1 --- /dev/null +++ b/bluetooth/serialhcibus/ReadMe.md @@ -0,0 +1,59 @@ +Bluetooth Serial HCI Bus Driver +=============================== + +The purpose of this sample is to demonstrate how to implement a basic bus driver to support the new [Bluetooth Extensibility transport DDIs](http://msdn.microsoft.com/en-us/library/windows/hardware/ff536585) over the UART transport. Such a serial bus driver can support a multi-radio device over the UART transport and utilize a common Bluetooth HCI packet for communication. The lower edge of this driver interfaces with a UART controller following the Bluetooth SIG’s UART (H4) transport protocol. + +## Universal Compliant +This sample builds a Windows Universal driver. It uses only APIs and DDIs that are included in Windows Core. + +**Note**: This sample driver is generic i.e. it is not designed for a specific device and allows for a vendor to adopt and enhance it for supporting Bluetooth + +This sample driver, as is, may not properly function for a device until all vendor-specific device requirements (e.g. device initialization) have been incorporated + +It is recommended to use WDK whose version matches the target Windows build version or newer for the development of the serial bus driver. + +**FILE MANIFEST** + +**WDK header file** + +BthXDDI.h – this has the constants, struct, and IOCTL definitions for the Bluetooth extensibility transport. This header file is included in WDK. + +**Common code section** + +driver.c – driver initialization + +driver.h – common header file for driver.c and includes other header files + +Fdo.c – functions for function device object (FDO) and BTHX DDI processing + +io.c – functions that perform IO read pump via UART controller + +Io.h – header for io.c + +pdo.c – PDO (Bluetooth function) enumeration and IOCTL processing + +public.h – header to share with application to support Radio On/Off (“Airplane mode”) + +Note: The goal is to keep the common code section the same, so the vendor will only need to update those code sections in the device specific directory. + +**Device-specific code section** + +Debugdef.h – WPP trace GUID; user should use a new GUID (unique per driver) + +device.c – device specific functions to implement: + +--DeviceInitialize() – to perform UART and Bluetooth device initialization; + +--DeviceEnable() – (optional) to bring serial bus device out of disable/reset state. + +--DevicePowerOn() – (optional) to power on the device. + +--DeviceEnableWakeControl() – (optional) to arm for device wake signal + +--DeviceDisableWakeControl() – (optional) to disarm for device wake signal + +device.h – header file for device.c + +driver.rc – driver version and name + +SerialBusWdk.inx – device specific INF file to install this driver. The vendor will need to add the hardware ID to match the “\_HID” for the Serial Bus Device (Bluetooth) in the DSDT.asl file. For example, in SerialBusWDK.inx, the hardware ID is “ACPI\\\<Vendor\>\_BTH0” where “\<vendor\>” could be a 4 digit vendor name diff --git a/bluetooth/serialhcibus/WDK/Debugdef.h b/bluetooth/serialhcibus/WDK/Debugdef.h new file mode 100644 index 00000000..96958c58 --- /dev/null +++ b/bluetooth/serialhcibus/WDK/Debugdef.h @@ -0,0 +1,49 @@ +#ifndef __DEBUGDEF_H__ +#define __DEBUGDEF_H__ + +// +// SerialBusWdk's control Guid +// {a2bf23a4-bc00-4903-81f2-6fbd82ad2d13} +// +#define WPP_CONTROL_GUIDS \ + WPP_DEFINE_CONTROL_GUID(serialbuswdk,(a2bf23a4,bc00,4903,81f2,6fbd82ad2d13), \ + WPP_DEFINE_BIT(TFLAG_PNP) \ + WPP_DEFINE_BIT(TFLAG_POWER) \ + WPP_DEFINE_BIT(TFLAG_UART) \ + WPP_DEFINE_BIT(TFLAG_IOCTL) \ + WPP_DEFINE_BIT(TFLAG_IO) \ + WPP_DEFINE_BIT(TFLAG_DATA) \ + WPP_DEFINE_BIT(TFLAG_HCI)) + +// +// Define shorter versions of the ETW trace levels +// +#define LEVEL_CRITICAL TRACE_LEVEL_CRITICAL +#define LEVEL_ERROR TRACE_LEVEL_ERROR +#define LEVEL_WARNING TRACE_LEVEL_WARNING +#define LEVEL_INFO TRACE_LEVEL_INFORMATION +#define LEVEL_VERBOSE TRACE_LEVEL_VERBOSE + +#define WPP_LEVEL_FLAG_ENABLED(lvl, component) \ + (WPP_LEVEL_ENABLED(component) && WPP_CONTROL(WPP_BIT_ ## component).Level >=lvl) + +#define WPP_LEVEL_FLAG_LOGGER(lvl, component) \ + WPP_LEVEL_LOGGER(component) + +// +// IFR enable macros +// +#define WPP_RECORDER_LEVEL_FLAG_ARGS(lvl, component) \ + WPP_CONTROL(WPP_BIT_ ## component).AutoLogContext, 0, WPP_BIT_ ## component +#define WPP_RECORDER_LEVEL_FLAG_FILTER(lvl, component) \ + (lvl < TRACE_LEVEL_VERBOSE || WPP_CONTROL(WPP_BIT_ ## component).AutoLogVerboseEnabled) + +// +// Use for WPP trace +// +#define WithinRange(min, value, max) (min <= value && value <= max) +#define MinToPrint(val1, val2) (val1 < val2 ? val1 : val2) +#define MAX_EVENT_PARAMS_TO_DISPLAY 8 // maximun number of event parameter to be printed (WPP) +#define MAX_COMMAND_PARAMS_TO_DISPLAY 8 // maximun number of vommand parameter to be printed (WPP) + +#endif // __DEBUGDEF_H__ diff --git a/bluetooth/serialhcibus/WDK/SerialBusWdk.inx b/bluetooth/serialhcibus/WDK/SerialBusWdk.inx new file mode 100644 index 00000000..b6e81096 --- /dev/null +++ b/bluetooth/serialhcibus/WDK/SerialBusWdk.inx @@ -0,0 +1,88 @@ +;/*++ +; +;Copyright (c) 2010 Microsoft Corporation All rights Reserved +; +;Module Name: +; +; SerialBusWdk.INF +; +;Abstract: +; INF file for installing a serial bus driver over UART transport +; +;Installation Notes: +; For testing purpose, use Devcon.exe to install +; +; "devcon install SerialBusWdk.inf SerialBusWdk_RootEnum" +; +;--*/ + +[Version] +Signature="$WINDOWS NT$" +Class=System +ClassGuid={4D36E97D-E325-11CE-BFC1-08002BE10318} +Provider=%MSFTSAMPLE% +DriverVer=06/21/2006,6.2.7923.0 +CatalogFile=SerialBusWdk.cat + +[DestinationDirs] +DefaultDestDir = 12 + + +[SourceDisksNames] +1 = %DiskId1%,,,"" + +[SourceDisksFiles] +SerialBusWdk.sys = 1,, + + +[ControlFlags] +ExcludeFromSelect=* + +[Manufacturer] +%StdMfg%=VendorModels,NT$ARCH$ + +;***************************************** +; Serial bus driver install Section +;***************************************** + +[VendorModels.NT$ARCH$] +%Bus.DeviceDesc%=Bus_Vendor, SerialBusWdk_RootEnum + +[Bus_Vendor.NT] +CopyFiles=Bus.CopyFiles + +[Bus.CopyFiles] +SerialBusWdk.sys + +[Bus_Vendor.NT.HW] +AddReg=Bus_Vendor.NT.AddReg + +[Bus_Vendor.NT.AddReg] +; Reference: http://msdn.microsoft.com/en-us/library/ff546320.aspx +HKR,,DeviceCharacteristics,0x10001,0x0100 ; Use same security checks on relative opens +; Reference: http://msdn.microsoft.com/en-us/library/windows/hardware/ff563667(v=vs.85).aspx +HKR,,Security,,"D:P(A;;GA;;;BA)(A;;GA;;;SY)(A;;GA;;;LS)" ; DACL Protected to allow generic-all access to built-in Admin group, system, and Local Service. + +;-------------- Service installation +[Bus_Vendor.NT.Services] +AddService = SerialBusWdk,%SPSVCINST_ASSOCSERVICE%, Bus_Service_Inst + +; -------------- statbus driver install sections +[Bus_Service_Inst] +DisplayName = %Bus.SVCDESC% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\SerialBusWdk.sys + +[Strings] +;Non-Localizable + +SPSVCINST_ASSOCSERVICE= 0x00000002 + +;Localizable +MSFTSAMPLE = "Microsoft Sample" +StdMfg = "(Standard system devices)" +DiskId1 = "Serial Bus Driver over UART Installation Disk #1" +Bus.DeviceDesc = "Serial Bus Driver over UART Bus Enumerator" +Bus.SVCDESC = "Serial Bus Driver over UART Bus Enumerator"
\ No newline at end of file diff --git a/bluetooth/serialhcibus/WDK/SerialBusWdk.vcxproj b/bluetooth/serialhcibus/WDK/SerialBusWdk.vcxproj new file mode 100644 index 00000000..770cb2e7 --- /dev/null +++ b/bluetooth/serialhcibus/WDK/SerialBusWdk.vcxproj @@ -0,0 +1,210 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{B126E3A5-91B5-41B5-9B40-719D474AA513}</ProjectGuid> + <RootNamespace>$(MSBuildProjectName)</RootNamespace> + <KMDF_VERSION_MAJOR>1</KMDF_VERSION_MAJOR> + <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration> + <Platform Condition="'$(Platform)' == ''">Win32</Platform> + <SampleGuid>{0929A3E4-68D7-482B-93C5-539571CF51D7}</SampleGuid> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>False</UseDebugLibraries> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + <DriverType>KMDF</DriverType> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + </PropertyGroup> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>True</UseDebugLibraries> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + <DriverType>KMDF</DriverType> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + </PropertyGroup> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>False</UseDebugLibraries> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + <DriverType>KMDF</DriverType> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + </PropertyGroup> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>True</UseDebugLibraries> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + <DriverType>KMDF</DriverType> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <PropertyGroup> + <OutDir>$(IntDir)</OutDir> + </PropertyGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" /> + </ImportGroup> + <ItemGroup Label="WrappedTaskItems"> + <ClCompile Include="..\fdo.c; device.c; ..\io.c; ..\pdo.c; ..\driver.c"> + <WppEnabled>true</WppEnabled> + <WppKernelMode>true</WppKernelMode> + <WppScanConfigurationData>debugdef.h</WppScanConfigurationData> + <WppTraceFunction>DoTrace(LEVEL,FLAG,(MSG,...))</WppTraceFunction> + </ClCompile> + <Inf Include=".\SerialBusWdk.inx"> + <Architecture>$(InfArch)</Architecture> + <SpecifyArchitecture>true</SpecifyArchitecture> + <CopyOutput>.\$(IntDir)\SerialBusWdk.inf</CopyOutput> + </Inf> + <OtherWpp Include="driver.rc"> + <WppEnabled>true</WppEnabled> + <WppKernelMode>true</WppKernelMode> + <WppScanConfigurationData>debugdef.h</WppScanConfigurationData> + <WppTraceFunction>DoTrace(LEVEL,FLAG,(MSG,...))</WppTraceFunction> + </OtherWpp> + </ItemGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <TargetName>SerialBusWdk</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <TargetName>SerialBusWdk</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <TargetName>SerialBusWdk</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <TargetName>SerialBusWdk</TargetName> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ResourceCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + </ResourceCompile> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + <TreatWarningAsError>true</TreatWarningAsError> + <WarningLevel>Level4</WarningLevel> + <ExceptionHandling> + </ExceptionHandling> + </ClCompile> + <Midl> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + </Midl> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ResourceCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + </ResourceCompile> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + <TreatWarningAsError>true</TreatWarningAsError> + <WarningLevel>Level4</WarningLevel> + <ExceptionHandling> + </ExceptionHandling> + </ClCompile> + <Midl> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + </Midl> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ResourceCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + </ResourceCompile> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + <TreatWarningAsError>true</TreatWarningAsError> + <WarningLevel>Level4</WarningLevel> + <ExceptionHandling> + </ExceptionHandling> + </ClCompile> + <Midl> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + </Midl> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ResourceCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + </ResourceCompile> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + <TreatWarningAsError>true</TreatWarningAsError> + <WarningLevel>Level4</WarningLevel> + <ExceptionHandling> + </ExceptionHandling> + </ClCompile> + <Midl> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES</PreprocessorDefinitions> + </Midl> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ResourceCompile Include="driver.rc" /> + </ItemGroup> + <ItemGroup> + <Inf Exclude="@(Inf)" Include="*.inf" /> + <FilesToPackage Include="$(TargetPath)" Condition="'$(ConfigurationType)'=='Driver' or '$(ConfigurationType)'=='DynamicLibrary'" /> + <FilesToPackage Include="@(Inf->'%(CopyOutput)')" Condition="'@(Inf)'!=''" /> + </ItemGroup> + <ItemGroup> + <None Exclude="@(None)" Include="*.txt;*.htm;*.html" /> + <None Exclude="@(None)" Include="*.ico;*.cur;*.bmp;*.dlg;*.rct;*.gif;*.jpg;*.jpeg;*.wav;*.jpe;*.tiff;*.tif;*.png;*.rc2" /> + <None Exclude="@(None)" Include="*.def;*.bat;*.hpj;*.asmx" /> + </ItemGroup> + <ItemGroup> + <ClInclude Exclude="@(ClInclude)" Include="*.h;*.hpp;*.hxx;*.hm;*.inl;*.xsd" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> +</Project>
\ No newline at end of file diff --git a/bluetooth/serialhcibus/WDK/SerialBusWdk.vcxproj.Filters b/bluetooth/serialhcibus/WDK/SerialBusWdk.vcxproj.Filters new file mode 100644 index 00000000..83b788e4 --- /dev/null +++ b/bluetooth/serialhcibus/WDK/SerialBusWdk.vcxproj.Filters @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;*</Extensions> + <UniqueIdentifier>{4614A6EB-0DDA-4E7D-B6B7-1AC9BF5F6AAA}</UniqueIdentifier> + </Filter> + <Filter Include="Header Files"> + <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions> + <UniqueIdentifier>{A95E3765-07D5-45E1-B82E-8D9EF61CD4BC}</UniqueIdentifier> + </Filter> + <Filter Include="Resource Files"> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms;man;xml</Extensions> + <UniqueIdentifier>{4E3D8A20-8B42-428D-AAEB-A5BA3C5C33C5}</UniqueIdentifier> + </Filter> + <Filter Include="Driver Files"> + <Extensions>inf;inv;inx;mof;mc;</Extensions> + <UniqueIdentifier>{C6A2211E-78CA-4EFF-85FD-A921F4F8EB29}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\driver.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\fdo.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\io.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\pdo.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="device.c"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <FilesToPackage Include=".\Debug\\SerialBusWdk.inf"> + <Filter>Driver Files</Filter> + </FilesToPackage> + <Inf Include=".\SerialBusWdk.inx"> + <Filter>Driver Files</Filter> + </Inf> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="driver.rc"> + <Filter>Resource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/bluetooth/serialhcibus/WDK/device.c b/bluetooth/serialhcibus/WDK/device.c new file mode 100644 index 00000000..11859e9f --- /dev/null +++ b/bluetooth/serialhcibus/WDK/device.c @@ -0,0 +1,257 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + Device.c + +Abstract: + + This file handles device specific operations. + +Environment: + + Kernel mode only + +--*/ + +#include "driver.h" +#include "Device.tmh" + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, DeviceQueryDeviceParameters) +#endif + +#define STR_BAUDRATE L"BaudRateIndex" + +VOID +DeviceQueryDeviceParameters( + _In_ WDFDRIVER _Driver +) +/*++ +Routine Description: + + Query driver's registry location for device specific parameter, such as baudrate. + + HLM\system\CCS\Services\serialhcibus\Parameters\ + + KeyName/Type/value + +Arguments: + + _Driver - WDF Driver object + +Return Value: + + None + +--*/ +{ + WDFKEY Key; + NTSTATUS Status; + UNICODE_STRING ValueName; + ULONG Value = 0; + + PAGED_CODE(); + + Status = WdfDriverOpenParametersRegistryKey(_Driver, + GENERIC_READ, + WDF_NO_OBJECT_ATTRIBUTES, + &Key + ); + if (NT_SUCCESS(Status)) { + + RtlInitUnicodeString(&ValueName, STR_BAUDRATE); + Status = WdfRegistryQueryULong(Key, &ValueName, &Value); + + if (NT_SUCCESS(Status)) { + // Vendor: can cache and use this values. + } + + WdfRegistryClose(Key); + } + +} + + +NTSTATUS +DeviceEnableWakeControl( + _In_ WDFDEVICE _Device, + _In_ SYSTEM_POWER_STATE _PowerState + ) +/*++ + +Routine Description: + + Vendor: This is a device specific function, and it arms the wake mechanism + for this driver to receive the wake signal. This could be using an + HOST_WAKE GPIO interrupt, or inband CTS/RTS mechanism. + +Arguments: + + _Device - WDF Device object + _PowerState - Context used for reading data from target UART device + +Return Value: + + NTSTATUS + +--*/ +{ + UNREFERENCED_PARAMETER(_Device); + UNREFERENCED_PARAMETER(_PowerState); + + return STATUS_SUCCESS; +} + +VOID +DeviceDisableWakeControl( + WDFDEVICE _Device + ) +/*++ + +Routine Description: + + Vendor: This is a device specific function, and it disarms the wake mechanism + for this driver to receive the wake signal. + +Arguments: + + _Device - WDF Device object + +Return Value: + + VOID + +--*/ +{ + UNREFERENCED_PARAMETER(_Device); + + return; +} + +BOOLEAN +DeviceInitialize( + _In_ PFDO_EXTENSION _FdoExtension, + _In_ WDFIOTARGET _IoTargetSerial, + _In_ WDFREQUEST _RequestSync, + _In_ BOOLEAN _IsUartReset + ) +/*++ +Routine Description: + + This function perform device specific operations to intialize in order + to bring the device to operational state. + +Arguments: + + _FdoExtension - Function device object extension + + _IoTargetSerial - IO Target to issue request to serial port + + _RequestSync - A reuseable WDF Request to issue serial control + + -IsUartReset - Is UART Reset is required + +Return Value: + + TRUE if initialization is completed and successful; FALSE otherwise. + +--*/ +{ + UNREFERENCED_PARAMETER(_FdoExtension); + UNREFERENCED_PARAMETER(_IoTargetSerial); + UNREFERENCED_PARAMETER(_RequestSync); + UNREFERENCED_PARAMETER(_IsUartReset); + + // + // Vendor specifc operation; + // + + return TRUE; +} + +NTSTATUS +DeviceEnable( + _In_ WDFDEVICE _Device, + _In_ BOOLEAN _IsEnabled + ) + +/*++ + +Routine Description: + + This function enable/wake serial bus device. + +Arguments: + + _Device - Supplies a handle to the framework device object. + + _IsEnabled - Boolean to enable or disable the BT device. + + +Return Value: + + NTSTATUS code. + +--*/ + +{ + UNREFERENCED_PARAMETER(_Device); + UNREFERENCED_PARAMETER(_IsEnabled); + + return STATUS_SUCCESS; +} + + +NTSTATUS +DevicePowerOn( + _In_ WDFDEVICE _Device +) +/*++ + +Routine Description: + + This routine powers on the serial bus device + +Arguments: + + _Device - Supplies a handle to the framework device object. + +Return Value: + + NT status code. + +--*/ +{ + UNREFERENCED_PARAMETER(_Device); + + return STATUS_SUCCESS; +} + +NTSTATUS +DevicePowerOff( + _In_ WDFDEVICE _Device +) +/*++ + +Routine Description: + + This routine powers off the serial bus device + +Arguments: + + _Device - Supplies a handle to the framework device object. + +Return Value: + + NT status code. + +--*/ +{ + UNREFERENCED_PARAMETER(_Device); + + return STATUS_SUCCESS; +} + diff --git a/bluetooth/serialhcibus/WDK/device.h b/bluetooth/serialhcibus/WDK/device.h new file mode 100644 index 00000000..1827493d --- /dev/null +++ b/bluetooth/serialhcibus/WDK/device.h @@ -0,0 +1,56 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + device.h + +Abstract: + + Header definitions and structs that are device specific + +Author: + +Environment: + + Kernel mode only + + +Revision History: + +--*/ + +#ifndef __DEVICE_H__ +#define __DEVICE_H__ + +#pragma warning(disable:4214) // bit field types other than int + + +#define BT_PDO_HARDWARE_IDS L"SerialBusWdk\\UART_H4" +#define BT_PDO_COMPATIBLE_IDS L"MS_BTHX_BTHMINI" +#define BT_PDO_DEVICE_LOCATION L"Serial HCI Bus - Bluetooth Function" + + +// +// 255 bytes of data + 3 bytes for HCI cmd hdr (2-byte opcode + 1-byte Parameter). +// +#define MIN_HCI_CMD_SIZE (3) +#define MAX_HCI_CMD_SIZE (258) + +// +// 255 bytes of data + 2 byte hdr (1-byte event code + 1-byte parameter). +// +#define MIN_HCI_EVENT_SIZE (2) +#define MAX_HCI_EVENT_SIZE (257) + +// +// Can be variable but usually 1021-byte (largest 3-DH5 ACL packet size) +// +#define HCI_ACL_HEADER_SIZE (4) +#define HCI_MAX_ACL_PAYLOAD_SIZE (1021) +#define MIN_HCI_ACLDATA_SIZE HCI_ACL_HEADER_SIZE +#define MAX_HCI_ACLDATA_SIZE (HCI_ACL_HEADER_SIZE + HCI_MAX_ACL_PAYLOAD_SIZE) + +#endif + diff --git a/bluetooth/serialhcibus/WDK/driver.rc b/bluetooth/serialhcibus/WDK/driver.rc new file mode 100644 index 00000000..1d3dd7a9 --- /dev/null +++ b/bluetooth/serialhcibus/WDK/driver.rc @@ -0,0 +1,14 @@ +#include <windows.h> + +#include <ntverp.h> + +#define VER_FILETYPE VFT_DLL +#define VER_FILESUBTYPE VFT2_UNKNOWN +#define VER_FILEDESCRIPTION_STR "Serial bus driver over UART using a common HCI packet" +#define VER_INTERNALNAME_STR "SerialBusWdk.sys" +#define VER_ORIGINALFILENAME_STR "SerialBusWdk.sys" + +#include "common.ver" + +//MofResourceName MOFDATA driver.bmf + diff --git a/bluetooth/serialhcibus/driver.c b/bluetooth/serialhcibus/driver.c new file mode 100644 index 00000000..8bb5b6aa --- /dev/null +++ b/bluetooth/serialhcibus/driver.c @@ -0,0 +1,378 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + driver.C + +Abstract: + + This module contains routines to handle the function driver + aspect of the bus driver. + +Environment: + + kernel mode only + +--*/ + +#include "driver.h" +#include "driver.tmh" + +#ifdef ALLOC_PRAGMA +#pragma alloc_text (PAGE, DriverCleanup) +#pragma alloc_text (PAGE, DriverSetDeviceCallbackEvents) +#pragma alloc_text (PAGE, DriverDeviceAdd) +#pragma alloc_text (INIT, DriverEntry) +#endif + +VOID +DriverCleanup( + _In_ WDFOBJECT _Object + ) +/*++ + +Routine Description: + + This callback function performs operations that must take place before the + driver is unloaded. Free all the resources allocated in DriverEntry. + +Arguments: + + _Object - handle to a WDF Driver object. + +Return Value: + + None. + +--*/ +{ + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+DriverCleanup")); + + WPP_CLEANUP( WdfDriverWdmGetDriverObject( _Object )); +} + + +VOID +DriverSetDeviceCallbackEvents( + _In_ PWDFDEVICE_INIT _DeviceInit + ) +// Initialize device callback events +{ + WDF_POWER_POLICY_EVENT_CALLBACKS PowerPolicyCallbacks; + WDF_PNPPOWER_EVENT_CALLBACKS PnpPowerCallbacks; + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+DriverSetDeviceCallbackEvents")); + + // + // Set event callbacks + // 1. Pnp & Power events + // 2. Power Policy events + // + + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&PnpPowerCallbacks); + + // + // Register PnP callback + // + PnpPowerCallbacks.EvtDevicePrepareHardware = FdoDevPrepareHardware; + PnpPowerCallbacks.EvtDeviceReleaseHardware = FdoDevReleaseHardware; + + // + // Register Power callback + // + PnpPowerCallbacks.EvtDeviceD0Entry = FdoDevD0Entry; + PnpPowerCallbacks.EvtDeviceD0Exit = FdoDevD0Exit; + PnpPowerCallbacks.EvtDeviceSelfManagedIoInit = FdoDevSelfManagedIoInit; + PnpPowerCallbacks.EvtDeviceSelfManagedIoCleanup = FdoDevSelfManagedIoCleanup; + + WdfDeviceInitSetPnpPowerEventCallbacks(_DeviceInit, + &PnpPowerCallbacks); + + // + // This driver can manage arm and disarm wake signal to support + // idle while S0/Sx. + // + WDF_POWER_POLICY_EVENT_CALLBACKS_INIT(&PowerPolicyCallbacks); + + // + // Register power policy callback. This is device specific. The ArmWake + // callback function can enable/disable external event that triggers a + // wake signal. + // These functions are invoked only if Idle capability is also set; that is, + // IdleSettings.IdleCaps == IdleCanWakeFromS0 + // + PowerPolicyCallbacks.EvtDeviceArmWakeFromS0 = FdoEvtDeviceArmWake; + PowerPolicyCallbacks.EvtDeviceDisarmWakeFromS0 = FdoEvtDeviceDisarmWake; + + WdfDeviceInitSetPowerPolicyEventCallbacks(_DeviceInit, + &PowerPolicyCallbacks); +} + + +NTSTATUS +DriverDeviceAdd( + IN WDFDRIVER _Driver, + IN PWDFDEVICE_INIT _DeviceInit + ) +/*++ +Routine Description: + + DriverDeviceAdd is called by the framework in response to AddDevice + call from the PnP manager. We create and initialize a device object to + represent a new instance of toaster bus. + +Arguments: + + _Driver - Handle to a framework driver object created in DriverEntry + + _DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure. + +Return Value: + + NTSTATUS + +--*/ +{ + WDF_IO_QUEUE_CONFIG QueueConfig; + WDF_OBJECT_ATTRIBUTES Attributes; + NTSTATUS Status; + WDFDEVICE Device; + PFDO_EXTENSION FdoExtension; + WDFQUEUE Queue; + PNP_BUS_INFORMATION BusInfo; + WDF_DEVICE_STATE DeviceState; +#ifdef DYNAMIC_ENUM + WDF_CHILD_LIST_CONFIG Config; +#endif + + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_PNP, ("+DriverDeviceAdd: 0x%p", _Driver)); + + // + // Get device specific parameters, such as baudrate + // + DeviceQueryDeviceParameters(_Driver); + + // + // Set PnP, Power and Power Policy event callback + // + DriverSetDeviceCallbackEvents(_DeviceInit); + + // + // Initialize all the properties specific to the device. + // Framework has default values for the one that are not + // set explicitly here. So please read the doc and make sure + // you are okay with the defaults. + // + WdfDeviceInitSetDeviceType(_DeviceInit, FILE_DEVICE_BUS_EXTENDER); + +#ifdef DYNAMIC_ENUM + + // + // WDF_ DEVICE_LIST_CONFIG describes how the framework should handle + // dynamic child enumeration on behalf of the driver writer. + // Since we are a bus driver, we need to specify identification description + // for our child devices. This description will serve as the identity of our + // child device. Since the description is opaque to the framework, we + // have to provide bunch of callbacks to compare, copy, or free + // any other resources associated with the description. + // + WDF_CHILD_LIST_CONFIG_INIT(&Config, + sizeof(PDO_IDENTIFICATION_DESCRIPTION), + FdoEvtDeviceListCreatePdo // callback to create a child device. + ); + + // Do not register function pointers and use default option unless customization is + // required. Consult MSDN or other WDK documentation for their usage. + + // + // Tell the framework to use the built-in childlist to track the state + // of the device based on the configuration we just created. + // + WdfFdoInitSetDefaultChildListConfig(_DeviceInit, + &Config, + WDF_NO_OBJECT_ATTRIBUTES); +#endif + + // + // Initialize Attributes structure to specify size and accessor function + // for storing device context. + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attributes, FDO_EXTENSION); + + // + // Create a framework device object to represent FDO of this bus driver. In response + // to this call, framework creates a WDM deviceobject. + // Can no longer access the WDFDEVICE_INIT structure after this call. + // + Status = WdfDeviceCreate(&_DeviceInit, + &Attributes, + &Device); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" WdfDeveiceCreate failed %!STATUS!", Status)); + return Status; + } + + + // + // Allow serial bus driver to be disabled + // + WDF_DEVICE_STATE_INIT(&DeviceState); + DeviceState.NotDisableable = WdfFalse; + WdfDeviceSetDeviceState(Device, &DeviceState); + + + // + // Get the device context. + // + FdoExtension = FdoGetExtension(Device); + + WDF_OBJECT_ATTRIBUTES_INIT(&Attributes); + Attributes.ParentObject = Device; + + // + // Purpose of this lock is documented in FdoCreateOneChildDevice routine. + // + Status = WdfWaitLockCreate(&Attributes, &FdoExtension->ChildLock); + if (!NT_SUCCESS(Status)) { + return Status; + } + + // + // Create a power-managed IO Queue + // + // Configure a default queue so that requests that are not + // configure-forwarded using WdfDeviceConfigureRequestDispatching to go to + // other queues get dispatched here. + // + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&QueueConfig, + WdfIoQueueDispatchParallel); + QueueConfig.PowerManaged = WdfTrue; + // Queue's callback event + QueueConfig.EvtIoDeviceControl = FdoIoQuDeviceControl; + + // + // By default, Static Driver Verifier (SDV) displays a warning if it + // doesn't find the EvtIoStop callback on a power-managed queue. + // The 'assume' below causes SDV to suppress this warning. + // + // No need to handle EvtIoStop/Resume: + // + // Condition: When there is a device state change from D0 to Dx, it is processed as a + // device stop event, and the caller (BthMini) will cancel all pending IOs. + // + // 1. Write command/data Requests are marked cancellable by serial bus driver, and + // the cancellation routine will handle the cancellation. + // + // 2. Read event/data Requests have their separate queues (with manual dispatch); + // when a request is in the queue, WDF owns the requests and can cancel the request + // in response to a cancel reqeust (IoCancelIrp) from the caller (BthMini). + // + + __analysis_assume(QueueConfig.EvtIoStop != 0); + Status = WdfIoQueueCreate(Device, + &QueueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &Queue); + __analysis_assume(QueueConfig.EvtIoStop == 0); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" WdfIoQueueCreate failed %!STATUS!", Status)); + return Status; + } + + // + // Create device interface for this device. The interface will be + // enabled by the framework when we return from StartDevice successfully. + // Use this interface to support Bluetooth Radio on/off scenario + // + Status = WdfDeviceCreateDeviceInterface(Device, + &GUID_DEVINTERFACE_BLUETOOTH_RADIO_ONOFF_VENDOR_SPECIFIC, + NULL /* No Reference String */ ); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" WdfDeviceCreateDeviceInterface failed %!STATUS!", Status)); + return Status; + } + + // + // This value is used in responding to the IRP_MN_QUERY_BUS_INFORMATION + // for the child devices. This is an optional information provided to + // uniquely idenitfy the bus the device is connected. + // + BusInfo.BusTypeGuid = GUID_SERENUM_BUS_ENUMERATOR; + BusInfo.LegacyBusType = PNPBus; + BusInfo.BusNumber = 0; + + WdfDeviceSetBusInformationForChildren(Device, &BusInfo); + + // + // Note: Do static PDO enumeration in FdoDevPrepareHardware PnP callback + // + + DoTrace(LEVEL_INFO, TFLAG_PNP, ("-DriverDeviceAdd: exit %!STATUS!", Status)); + + return Status; +} + + +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT _DriverObject, + _In_ PUNICODE_STRING _RegistryPath + ) +/*++ +Routine Description: + + Initialize the call backs structure of Driver Framework. + +Arguments: + + _DriverObject - pointer to the driver object + + _RegistryPath - pointer to a unicode string representing the path, + to driver-specific key in the registry. + +Return Value: + + NT Status Code + +--*/ +{ + WDF_DRIVER_CONFIG Config; + NTSTATUS Status; + WDF_OBJECT_ATTRIBUTES Attributes; + + + WDF_DRIVER_CONFIG_INIT(&Config, DriverDeviceAdd); + Config.DriverPoolTag = POOLTAG_UARTHCIBUSSAMPLE; + + WDF_OBJECT_ATTRIBUTES_INIT(&Attributes); + Attributes.EvtCleanupCallback = DriverCleanup; + + // + // Create a framework driver object to represent our driver. + // + Status = WdfDriverCreate(_DriverObject, + _RegistryPath, + &Attributes, + &Config, + WDF_NO_HANDLE); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_PNP, ("WdfDriverCreate failed %!STATUS!", Status)); + return Status; + } + + WPP_INIT_TRACING(_DriverObject, _RegistryPath); + + return Status; + +} + diff --git a/bluetooth/serialhcibus/driver.h b/bluetooth/serialhcibus/driver.h new file mode 100644 index 00000000..d82f1f55 --- /dev/null +++ b/bluetooth/serialhcibus/driver.h @@ -0,0 +1,530 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + driver.h + +Abstract: + + This module contains the common private declarations for + for the Serial HCI bus driver. + +Environment: + + kernel mode only + +--*/ + +#ifndef DRIVER_H +#define DRIVER_H + +#include <ntddk.h> +#include <wdf.h> + +#define NTSTRSAFE_LIB +#include <ntstrsafe.h> + +#define INITGUID +#include <guiddef.h> +#include <ntddser.h> // Constants and types for access Serial device + +#include <BthXDDI.h> // BT Extensible Transport DDI + +#include "device.h" // Device specific +#include "io.h" // Read pump +#include "debugdef.h" // WPP trace +#include "public.h" // Share between driver and application + +#ifdef DEFINE_GUID + +// +// Container ID for internally connected device +// +DEFINE_GUID(GUID_CONTAINERID_INTERNALLY_CONNECTED_DEVICE, + 0x00000000, 0x0000, 0x0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + +//{00000000-0000-0000-ffff-ffffffffffff} + +#endif // #ifdef DEFINE_GUID + +// +// Define HCI event code +// +#ifndef CommandComplete +#define CommandComplete 0x0e +#endif +#ifndef CommandStatus +#define CommandStatus 0x0f +#endif + +#define POOLTAG_UARTHCIBUSSAMPLE 'SBHS' // 'S'erial 'H'ci 'B'us 'S'ample +#undef ExAllocatePool +#define ExAllocatePool(type, size) \ + ExAllocatePoolWithTag(type, size, POOLTAG_UARTHCIBUSSAMPLE) + +// +// An ID used to uniquely identify Bluetooth function from other function +// of this multifunction device. +// +#define BLUETOOTH_FUNC_IDS 0x1001 + + + +// +// Device's idle state capability +// +typedef enum _IDLE_CAP_STATE { + IdleCapActiveOnly = 1, // Support active only (cannot idle) + IdleCapCanWake = 2, // Can enter D2 (idle) and remote wake to save power while in idle state. + IdleCapCanTurnOff = 3 // Can enter D3 (off) and not remote wake to save max power while device is off. +} IDLE_CAP_STATE; + + +#ifdef DYNAMIC_ENUM +// +// The goal of the identification and address description abstractions is that enough +// information is stored for a discovered device so that when it appears on the bus, +// the framework (with the help of the driver writer) can determine if it is a new or +// existing device. The identification and address descriptions are opaque structures +// to the framework, they are private to the driver writer. The only thing the framework +// knows about these descriptions is what their size is. +// The identification contains the bus specific information required to recognize +// an instance of a device on its the bus. The identification information usually +// contains device IDs along with any serial or slot numbers. +// For some buses (like USB and PCI), the identification of the device is sufficient to +// address the device on the bus; in these instances there is no need for a separate +// address description. Once reported, the identification description remains static +// for the lifetime of the device. For example, the identification description that the +// PCI bus driver would use for a child would contain the vendor ID, device ID, +// subsystem ID, revision, and class for the device. This sample uses only identification +// description. +// On other busses (like 1394 and auto LUN SCSI), the device is assigned a dynamic +// address by the hardware (which may reassigned and updated periodically); in these +// instances the driver will use the address description to encapsulate this dynamic piece +// of data. For example in a 1394 driver, the address description would contain the +// device's current generation count while the identification description would contain +// vendor name, model name, unit spec ID, and unit software version. +// +typedef struct _PDO_IDENTIFICATION_DESCRIPTION +{ + WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER Header; // should contain this header + + // + // Unique serail number of the device on the bus + // + ULONG SerialNo; + + size_t CchHardwareIds; + + _Field_size_bytes_(CchHardwareIds) PWCHAR HardwareIds; + +} PDO_IDENTIFICATION_DESCRIPTION, *PPDO_IDENTIFICATION_DESCRIPTION; +#endif // #ifdef DYNAMIC_ENUM + + +typedef struct _UART_READ_CONTEXT *PUART_READ_CONTEXT; + +// +// Bus driver's FDO (Function Device Object) extension structure used to maintain device +// properties and state. +// + + +typedef struct _FDO_EXTENSION +{ + WDFWAITLOCK ChildLock; + + // + // Radio On/Off state + // + BOOLEAN IsRadioEnabled; + + // + // WDF Device handle + // + WDFDEVICE WdfDevice; + + // + // Serial port IO Target where we send IOCTL/READ/WRITE reuquest to + // + WDFIOTARGET IoTargetSerial; + + // + // (optional) GPIO IO Target to enable serial bus device + // + WDFIOTARGET IoTargetGPIO; + + // + // Bluetooth child dev node (PDO) capabilities + // + BTHX_CAPABILITIES BthXCaps; + + // + // Indicator if UART is properly initialize; may require re-inialization + // when tranistion from exiting D0 to resume D0. + // + BOOLEAN DeviceInitialized; + + // + // Cached UART controller connection IDs + // + LARGE_INTEGER UARTConnectionId; + + // + // Cached I2C controller connection IDs + // + LARGE_INTEGER I2CConnectionId; + + // + // Cached GPIO controller connection IDs + // + LARGE_INTEGER GPIOConnectionId; + + // + // Preallocate WDF Requests for synchronous operation like serial port settings + // + WDFREQUEST RequestIoctlSync; + + // + // Preallocate WDF Requests to wait on serial error event + // + WDFREQUEST RequestWaitOnError; + + // + // Data return from serial event wait mask IOCTL + // + ULONG SerErrorMask; + + // + // WDM memory use for Wait Mask event + // + WDFMEMORY WaitMaskMemory; + + // + // Set if a hardware error (e.g. data overrun in UART FIFO) is detected + // + BOOLEAN HardwareErrorDetected; + + // + // Indication the state of the read pump (TRUE = active) + // + BOOLEAN ReadPumpRunning; + + // + // Track number of out-of-sync error that has been detected + // + ULONG OutOfSyncErrorCount; + + // + // Locks for synchronization for list and queue + // + KSPIN_LOCK QueueAccessLock; + + // + // Track next packet read (one and only one) + // + UART_READ_CONTEXT ReadContext; + + // + // Preallocated local WDF requested and memory object that is reused to + // implement read pump + // + WDFREQUEST ReadRequest; + WDFMEMORY ReadMemory; + UCHAR ReadBuffer[MAX_H4_HCI_PACKET_SIZE]; + +#if DBG + // + // Track last completed HCI packet + // + UCHAR LastPacket[MAX_H4_HCI_PACKET_SIZE]; + ULONG LastPacketLength; +#endif + // + // WDF Queue for HCI event Request and total number of such request recevied + // + WDFQUEUE ReadEventQueue; + LONG EventQueueCount; + + // + // List to store (prefetched) incoming HCI events and number of entries + // + LIST_ENTRY ReadEventList; + LONG EventListCount; + + + // + // WDF Queue for HCI read data Request and total number of such request recevied + // + WDFQUEUE ReadDataQueue; + LONG DataQueueCount; + + // + // List to store (prefetched) incoming HCI data and number of entries + // + LIST_ENTRY ReadDataList; + LONG DataListCount; + + + // + // Counts used to track HCI requests received and completed for various packet types + // + LONG CntCommandReq; // Track total number of HCI command Requests + LONG CntCommandCompleted; // Number of HCI Command completed + + LONG CntEventReq; // Track total number of HCI Event Requests + LONG CntEventCompleted; // Number of HCI Command completed + + LONG CntWriteDataReq; // Track total number of HCI Write Data requests + LONG CntWriteDataCompleted; // Number of HCI (write) Data completed + + LONG CntReadDataReq; // Track total number of HCI Read Data Requests + LONG CntReadDataCompleted; // Number of HCI (Read) Data completed +} FDO_EXTENSION, *PFDO_EXTENSION; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(FDO_EXTENSION, FdoGetExtension) + + +// +// Can send IO only if the device (UART) is in the initialized state. +// +#define IsDeviceInitialized(FdoExtension) (FdoExtension->DeviceInitialized) + + +#define ValidConnectionID(ConnectionId) (ConnectionId.QuadPart != 0) + + +// +// Bus driver's child PDO (Physical Device Object) extension structure used to maintain this +// PDO's device properties and state. +// + +typedef struct _PDO_EXTENSION +{ + // + // Back pointer to FDO_EXTENSION + // + PFDO_EXTENSION FdoExtension; + + // + // Unique serial number of the device on the bus + // + ULONG SerialNo; + +} PDO_EXTENSION, *PPDO_EXTENSION; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PDO_EXTENSION, PdoGetExtension) + + +// +// Prototypes of functions +// + + +// +// Driver.c +// + +EVT_WDF_OBJECT_CONTEXT_CLEANUP DriverCleanup; + +VOID +DriverSetDeviceCallbackEvents( + _In_ PWDFDEVICE_INIT _DeviceInit + ); + +EVT_WDF_DRIVER_DEVICE_ADD DriverDeviceAdd; + +DRIVER_INITIALIZE DriverEntry; + + +// +// FDO.c +// + +NTSTATUS +HlpInitializeFdoExtension(WDFDEVICE _Device); + +NTSTATUS +FdoWriteDeviceIO(_In_ WDFREQUEST _RequestFromBthport, + _In_ WDFDEVICE _Device, + _In_ PFDO_EXTENSION _FdoExtension, + _In_ PBTHX_HCI_READ_WRITE_CONTEXT _HCIContext); + +NTSTATUS +FdoWriteToDeviceSync(_In_ WDFIOTARGET _IoTargetSerial, + _In_ WDFREQUEST _RequestWriteSync, + _In_ ULONG _IoControlCode, + _In_opt_ ULONG _InBufferSize, + _In_opt_ PVOID _InBuffer, + _Out_ PULONG_PTR _BytesWritten); + +NTSTATUS +DeviceConfigWaitOnError(_In_ WDFIOTARGET _IoTargetSerial, + _In_ WDFREQUEST _RequestWaitOnError, + _In_ WDFMEMORY _WaitMaskMemory, + _In_ PULONG _ErrorResult, + _In_ PFDO_EXTENSION _FdoExtension); + + +NTSTATUS +HCIContextValidate(ULONG Index, + PBTHX_HCI_READ_WRITE_CONTEXT _HCIContext); + + +// Power policy events +EVT_WDF_DEVICE_ARM_WAKE_FROM_S0 FdoEvtDeviceArmWake; +EVT_WDF_DEVICE_DISARM_WAKE_FROM_S0 FdoEvtDeviceDisarmWake; + +EVT_WDF_DEVICE_ARM_WAKE_FROM_SX FdoEvtDeviceArmWake; +EVT_WDF_DEVICE_DISARM_WAKE_FROM_SX FdoEvtDeviceDisarmWake; + +// PnP events +EVT_WDF_DEVICE_PREPARE_HARDWARE FdoDevPrepareHardware; +EVT_WDF_DEVICE_RELEASE_HARDWARE FdoDevReleaseHardware; + +// Power events +EVT_WDF_DEVICE_D0_ENTRY FdoDevD0Entry; +EVT_WDF_DEVICE_D0_EXIT FdoDevD0Exit; + +EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT FdoDevSelfManagedIoInit; +EVT_WDF_DEVICE_SELF_MANAGED_IO_CLEANUP FdoDevSelfManagedIoCleanup; + +// Queue +EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL FdoIoQuDeviceControl; + +// PDO creation + +#ifdef DYNAMIC_ENUM +EVT_WDF_CHILD_LIST_CREATE_DEVICE FdoEvtDeviceListCreatePdo; + +NTSTATUS +PdoCreateDynamic(_In_ WDFDEVICE Device, + _In_ PWDFDEVICE_INIT DeviceInit, + _In_ PWCHAR HardwareIds, + _In_ ULONG SerialNo); + +NTSTATUS +FdoCreateOneChildDeviceDynamic(_In_ WDFDEVICE _Device, + _In_ PWCHAR _HardwareIds, + _In_ size_t _CchHardwareIds, + _In_ ULONG _SerialNo); +#endif + +EVT_WDF_DEVICE_DISABLE_WAKE_AT_BUS PdoDevDisableWakeAtBus; +EVT_WDF_DEVICE_ENABLE_WAKE_AT_BUS PdoDevEnableWakeAtBus; + +NTSTATUS +FdoCreateOneChildDevice(_In_ WDFDEVICE _Device, + _In_ PWCHAR _HardwareIds, + _In_ ULONG _SerialNo); + +NTSTATUS +FdoCreateAllChildren(_In_ WDFDEVICE _Device); + +NTSTATUS +FdoRemoveOneChildDevice(WDFDEVICE _Device, + ULONG _SerialNo); + +NTSTATUS +FdoFindConnectResources(_In_ WDFDEVICE _Device, + _In_ WDFCMRESLIST _ResourcesRaw, + _In_ WDFCMRESLIST _ResourcesTranslated); + + +// +// Pdo.c +// + +EVT_WDF_DEVICE_PREPARE_HARDWARE PdoDevPrepareHardware; +EVT_WDF_DEVICE_RELEASE_HARDWARE PdoDevReleaseHardware; + +EVT_WDF_DEVICE_D0_ENTRY PdoDevD0Entry; +EVT_WDF_DEVICE_D0_EXIT PdoDevD0Exit; + +EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL PdoIoQuDeviceControl; + +NTSTATUS +PdoCreate(_In_ WDFDEVICE _Device, + _In_ PWSTR _HardwareIds, + _In_ ULONG _SerialNo); + +VOID +PdoDevDisableWakeAtBus(_In_ WDFDEVICE _Device); + +NTSTATUS +PdoDevEnableWakeAtBus(_In_ WDFDEVICE _Device, + _In_ SYSTEM_POWER_STATE _PowerState); + +// +// Define in io.c +// +NTSTATUS ReadResourcesAllocate(_In_ WDFDEVICE _Device); +VOID ReadResourcesFree(_In_ WDFDEVICE _Device); + +NTSTATUS +HLP_AllocateResourceForWrite(_In_ WDFDEVICE _Device, + _In_ WDFIOTARGET _IoTargetSerial, + _Out_ WDFREQUEST *_pRequest); + +VOID +HLP_FreeResourceForWrite(PUART_WRITE_CONTEXT _TransferContext); + +EVT_WDF_REQUEST_CANCEL CB_RequestFromBthportCancel; + +EVT_WDF_REQUEST_COMPLETION_ROUTINE CR_WriteDeviceIO; + +NTSTATUS +ReadRequestComplete(_In_ PFDO_EXTENSION _FdoExtension, + _In_ UCHAR _Type, + _In_ ULONG _PacketLength, + _In_reads_bytes_opt_(_PacketLength) PUCHAR _Packet, + _Inout_ WDFQUEUE _Queue, + _Inout_ PLONG _QueueCount, + _Inout_ PLIST_ENTRY _ListHead, + _Inout_ PLONG _ListCount); + +EVT_WDF_REQUEST_COMPLETION_ROUTINE ReadH4PacketCompletionRoutine; + +NTSTATUS +ReadH4Packet(_In_ PUART_READ_CONTEXT _ReadContext, + _In_ WDFREQUEST _WdfRequest, + _In_ WDFMEMORY _WdfMemory, + _Pre_notnull_ _Pre_writable_byte_size_(_BufferLen) PVOID _Buffer, + _In_ ULONG _BufferLen); + + +// +// Device.c +// + +VOID +DeviceQueryDeviceParameters(_In_ WDFDRIVER _Driver); + +BOOLEAN +DeviceInitialize(_In_ PFDO_EXTENSION _FdoExtension, + _In_ WDFIOTARGET _IoTargetSerial, + _In_ WDFREQUEST _RequestSync, + _In_ BOOLEAN _ResetUart); + +NTSTATUS +DeviceEnableWakeControl(_In_ WDFDEVICE _Device, + _In_ SYSTEM_POWER_STATE _PowerState); +void +DeviceDisableWakeControl(WDFDEVICE _Device); + +NTSTATUS +DeviceEnable(_In_ WDFDEVICE _Device, + _In_ BOOLEAN _Enabled); + +NTSTATUS +DevicePowerOn(_In_ WDFDEVICE _Device); + +NTSTATUS +DevicePowerOff(_In_ WDFDEVICE _Device); + +#endif + + + diff --git a/bluetooth/serialhcibus/io.c b/bluetooth/serialhcibus/io.c new file mode 100644 index 00000000..0b4d64ac --- /dev/null +++ b/bluetooth/serialhcibus/io.c @@ -0,0 +1,1592 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + IO.c + +Abstract: + + This module contains routines that perform read/write IO operations. + +Environment: + + Kernel mode only + +Revision History: + +--*/ + +#include "driver.h" +#include "IO.tmh" + +#pragma warning(disable:4127) // conditional expression is constant + +#ifdef ALLOC_PRAGMA +#endif + +VOID +CB_RequestFromBthportCancel( + _In_ WDFREQUEST _RequestFromUpper + ) +/*++ + +Routine Description: + + Request from upper layer that driver owns is being canceled. Its associated + Request to lower (UART) driver will be canceled and then this Request will + be completed with STATUS_CANCELLED. + + There are different paths for the Request from upper layer: + + 1. Completion routine is invoked without cancellation (typical path) + 2. Cancellation routine is invoked while lower Request is pending. The lower + request could be completed either + a. Synchronously - completion routine is invoked before + WdfRequestCancelSentRequest() is returned in the cancellation routine; or + b. Asynchronously - completion routine is invoked at later time after + WdfRequestCancelSentRequest has returned. + 3. Race conditions when both the cancelation and completion routine have independently started + a. Cancellation routine is ahead and the request is completed with cancellation status. + b. Completion routine is ahead and the request is completed with the status from the lower request. + +Arguments: + + _RequestFromUpper - WDF Request to be cancelled + +Return Value: + + none + +--*/ +{ + PUART_WRITE_CONTEXT TransferContext; + WDFREQUEST RequestToUART; + WDFMEMORY Memory; + BOOLEAN CancelSuccess; + LONG CompletePath = REQUEST_PATH_NONE; + + + DoTrace(LEVEL_WARNING, TFLAG_IO, ("+CB_RequestFromBthportCancel: Request(%p) from upper driver", _RequestFromUpper)); + + TransferContext = GetWriteRequestContext(_RequestFromUpper); + NT_ASSERT(TransferContext && L"TransferContext is not valid!"); + + // Cancel the write Request that was previously submitted to its I/O target + RequestToUART = TransferContext->RequestToUART; + Memory = TransferContext->Memory; + + + // + // The below operation can return one of the following values. + // REQUEST_PATH_NONE + // This value was returned due to one of the following conditions + // 1. The completion routine was not yet run. + // 2. The completion routine was run and it relinquished the control of completing the request from bthport to the cancel routine. + // + // No matter what causes this value to be returned, this function is now responsible for completing the request from bthport. + // + // REQUEST_PATH_COMPLETION + // The completion routine was already called. + // The completion routine has not yet had a chance to relinquish control of completing the request from bthport. + // + // This function does not have the control to complete the request from bthport. + // + CompletePath = InterlockedOr(&TransferContext->RequestCompletePath, REQUEST_PATH_CANCELLATION); + + if (REQUEST_PATH_NONE == CompletePath) { + + DoTrace(LEVEL_WARNING, TFLAG_IO, (" >CancelSentRequest(%p) to IO Target", RequestToUART)); + CancelSuccess = WdfRequestCancelSentRequest(RequestToUART); + DoTrace(LEVEL_WARNING, TFLAG_IO, (" <CancelSentRequest: %S", CancelSuccess ? L"Cancelled" : L"Failed")); + + // Done sending the cancel. It can be dereferenced. + WdfObjectDereference(RequestToUART); + + // No need to access this memory object in the cancellation code path in the completion function. + WdfObjectDelete(Memory); + + // Cannot access this request, including WdfRequestUnmarkCancelable(), after it has been completed. + WdfRequestComplete(_RequestFromUpper, STATUS_CANCELLED); + } + +} + + +NTSTATUS +HLP_AllocateResourceForWrite( + _In_ WDFDEVICE _Device, + _In_ WDFIOTARGET _IoTargetSerial, + _Out_ WDFREQUEST *_PRequest + ) +/*++ + +Routine Description: + + This helper function allocate resource to perform a write request + +Arguments: + + _Device - WDF Device object + + _IoTargetSerial - WDF IO Target + + _PRequest - WDF Request to allocate in this function + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status; + WDF_OBJECT_ATTRIBUTES ObjAttributes; + + DoTrace(LEVEL_INFO, TFLAG_IO,("+HLP_AllocateResourceForWrite")); + + // Create a WDF Request that will allocate a context(UART_WRITE_CONTEXT) + WDF_OBJECT_ATTRIBUTES_INIT(&ObjAttributes); + ObjAttributes.ParentObject = _Device; + + Status = WdfRequestCreate(&ObjAttributes, + _IoTargetSerial, + _PRequest); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfRequestCreate() failed %!STATUS!", Status)); + goto Done; + } + +Done: + + return Status; +} + + +VOID +HLP_FreeResourceForWrite( + PUART_WRITE_CONTEXT _TransferContext + ) +/*++ + +Routine Description: + + This helper function free resource allocated to perform a write request + +Arguments: + + _TransferContext - Transfer context used to perform write operation + +Return Value: + + none + +--*/ +{ + DoTrace(LEVEL_INFO, TFLAG_IO,("+HLP_FreeResourceForWrite")); + + if (_TransferContext) + { + if (_TransferContext->Memory) + { + WdfObjectDelete(_TransferContext->Memory); + _TransferContext->Memory = NULL; + } + + if (_TransferContext->RequestToUART) + { + WdfObjectDelete(_TransferContext->RequestToUART); + _TransferContext->RequestToUART = NULL; + + } + } +} + + +VOID +CR_WriteDeviceIO( + _In_ WDFREQUEST _Request, + _In_ WDFIOTARGET _Target, + _In_ PWDF_REQUEST_COMPLETION_PARAMS _Params, + _In_ WDFCONTEXT _Context + ) +/*++ + +Routine Description: + + This is the completion function for sending HCI packet to the lower layer. + This function can also complete the request from the upper layer; see the + description in the cancellation function for detail on the handling of possible + race conditions. + + A RequestCompletionPath flag in the write Context is used with atomic Interlocked function + to ensure deterministic operation in both the cancellation and this completion functions. + + If the cancellation function has been called, the WdfRequestUnmarkCancelable in the completion function will return STATUS_CANCELLED. + This return code is used to determine to handle the processing either as a typical completion, or as a cancellation and be in sync + with the cancellation function. + + Here are what are performed in either situations: + + 1. Typical completion (completion function only) + - WdfRequestUnmarkCancelable() returns not STATUS_CANCELLED + Exercise its typical completion code path + - Retrieve data transfer information for success case + - Dereference(RequestUART) - will not be accessed by cancellation function + - Complete(RequestFromUpper) & Delete(its Memory Object) + + - Delete(RequestUART) + - Dereference(RequestFromUpper) + + 2. Cancellation (both functions) + A: Cancellation Function + WdfRequestCancelSentRequest(RequestToUART) to cancel RequestToUART + - Dereference(RequestToUART) after cancel is sent + - Complete(RequestFromUpper) & Delete(its Memory Object) + + B: Completion function + WdfRequestUnmarkCancelable() returns STATUS_CANCELLED + Exercise its cancellation code path + - Delete(RequestToUART) + - Dereference(RequestFromUpper) + + Note: Code path A & B have no synchronization object to ensure their order of execution, but reference is taken on the Requests to ensure + that they stay valid until last access. + + RequestToUART - take a reference to protect against being used by the cancellation function; it is de-referenced by the + - completion function - in its typical completion code path, or + - cancellation function - after finishing accessing it (to sent cancel) + + RequestFromBthport - take a reference to protect against being completed by the cancellation function and then its context + is later accessed by the completion function; this can happen if the completion function is completed + asynchronously after WdfRequestCancelSentRequest() is returned; it is de-referenced by the + - completion function - right before it exits. + +Arguments: + + _Request - WDF Request allocated by this driver + _Target - WDF IO Target + _Params - Completion parameters + _Context - Context used to process this request + +Return Value: + + none + +--*/ +{ + NTSTATUS Status; + PUART_WRITE_CONTEXT TransferContext; + PFDO_EXTENSION FdoExtension; + WDFREQUEST RequestFromBthport; + ULONG BytesDataWritten = 0; + LONG CompletePath = REQUEST_PATH_NONE; + + UNREFERENCED_PARAMETER(_Target); + + Status = _Params->IoStatus.Status; + TransferContext = (PUART_WRITE_CONTEXT) _Context; + + DoTrace(LEVEL_INFO, TFLAG_DATA,("+CR_WriteDeviceIO: %!STATUS!, Request %p, Context %p", + Status, _Request, _Context)); + + NT_ASSERT( (Status == STATUS_SUCCESS || Status == STATUS_CANCELLED) && L"WriteHCI request failed!"); + + // + // Request to be completed to upper layer. + // + RequestFromBthport = TransferContext->RequestFromBthport; + + // + // The below operation can return one of the following values. + // REQUEST_PATH_NONE + // This value was returned either because + // 1. This is the normal operation for this function and the request from bthport has to be completed. + // 2. The request from bthport has already been cancelled, but the cancellation routine has not yet been called (race condition). + // + // No matter what causes this value to be returned, it is safe to call WdfRequestUnmarkCancelable on the request from bthport + // + // REQUEST_PATH_CANCELLATION + // The cancellation routine was already called. + // + // This function does not have the control to complete the request from bthport. + // + CompletePath = InterlockedOr(&TransferContext->RequestCompletePath, REQUEST_PATH_COMPLETION); + + // Mark RequestFromBthPort not cancellable as it is about to be completed. + if (REQUEST_PATH_NONE != CompletePath) + { + DoTrace(LEVEL_ERROR, TFLAG_IO,(" Request %p is in the process of being cancelled", RequestFromBthport)); + } + else + { + // + // Call WdfRequestUnmarkCancelable() to check whether this request has already been cancelled. + // + if (STATUS_CANCELLED == WdfRequestUnmarkCancelable(RequestFromBthport)) { + // + // The request from bthport has already been cancelled. + // Try to relinquish control of completing the request from bthport to the cancellation routine. It is possible that the cancellation routine + // has already been executed. In this case, this routine will have to complete the request from bthport. + // + // The below operation can return one of the following values. + // REQUEST_PATH_CANCELLATION | REQUEST_PATH_COMPLETION + // The cancellation routine was called. The cancellation will not complete the request, so this function will have to complete it. + // + // REQUEST_PATH_COMPLETION + // The cancellation routine has not yet been called. + // The InterlockedCompareExchange successfully masked the REQUEST_PATH_COMPLETE bit and so the completin routine + // will complete this request. + // + CompletePath = InterlockedCompareExchange(&TransferContext->RequestCompletePath, + REQUEST_PATH_NONE, + REQUEST_PATH_COMPLETION); + + // + // Since the cancellation was already called and it will not complete the request, reset the value of complete to + // REQUEST_PATH_NONE so that the request from bthport will be completed. + // + if (CompletePath & REQUEST_PATH_CANCELLATION) { + CompletePath = REQUEST_PATH_NONE; + } + } + + if (REQUEST_PATH_NONE == CompletePath) { + + // Dereference this request as cancellation function is not invoked to access it. + WdfObjectDereference(_Request); + + // + // Return data transfer information to caller for success Status + // + if (NT_SUCCESS(Status)) + { + WDFMEMORY ReqOutMemory = NULL; + ULONG BytesWritten; + PULONG OutBuffer = NULL; + size_t OutBufferSize = 0; + + BytesWritten = (ULONG) _Params->Parameters.Write.Length; + + DoTrace(LEVEL_INFO, TFLAG_DATA,(" Packet: Type %d, DataLen %d, BytesWritten %d", + TransferContext->HCIContext->Type, + TransferContext->HCIContext->DataLen, + BytesWritten)); + + NT_ASSERT(BytesWritten == TransferContext->HCIPacketLen && "Unexpected incomplete HCI Write!"); + + if (BytesWritten != TransferContext->HCIPacketLen) + { + // return a generic failure for an incomplete transfer + Status = STATUS_UNSUCCESSFUL; + goto Done; + } + + // + // return data bytes written in the OutputParameter + // + Status = WdfRequestRetrieveOutputMemory(RequestFromBthport, &ReqOutMemory); + if (NT_SUCCESS(Status)) + { + OutBuffer = (PULONG) WdfMemoryGetBuffer(ReqOutMemory, &OutBufferSize); + if (OutBufferSize >= sizeof(ULONG)) + { + // Set OutputParameter value and its size + *OutBuffer = TransferContext->HCIContext->DataLen; + BytesDataWritten = sizeof(ULONG); + } + } + } + else + { + // Return the status as is. + } + } + } + +Done: + + if (REQUEST_PATH_NONE == CompletePath) + { + // Increment the completion count based on packet type. + FdoExtension = TransferContext->FdoExtension; + + if (TransferContext->HCIContext->Type == (UCHAR) HciPacketCommand) + { + InterlockedIncrement(&FdoExtension->CntCommandCompleted); + } + else if (TransferContext->HCIContext->Type == (UCHAR) HciPacketAclData) + { + InterlockedIncrement(&FdoExtension->CntWriteDataCompleted); + } + + DoTrace(LEVEL_INFO, TFLAG_IO,(" WriteDeviceIO: Request %p complete with %!STATUS! and %d BytesDataWritten", + RequestFromBthport, Status, BytesDataWritten)); + + // Delete this memory object that is no longer needed. + WdfObjectDelete(TransferContext->Memory); + + // Cannot access this Request and its context after it is completed. + WdfRequestCompleteWithInformation(RequestFromBthport, Status, BytesDataWritten); + + } + + // Delete this request in its completion function. + WdfObjectDelete(_Request); + + // Done accessing it in this function. This request is either completed in this function for the typical completion situation or in the cancellation function. + WdfObjectDereference(RequestFromBthport); + + DoTrace(LEVEL_INFO, TFLAG_IO,("-CR_WriteDeviceIO")); +} + + + +VOID +ReadSegmentStateSet( + PUART_READ_CONTEXT _ReadContext, + UART_READ_STATE _NewState + ) +/*++ + +Routine Description: + + This helper centralize the setting of read state. It can be used to detect + possible incorrect state transition. + +Arguments: + + _ReadContext - read context which has existing state + _NewState - new read state + +Return Value: + + none + +--*/ +{ + UART_READ_STATE OldState = _ReadContext->ReadSegmentState; + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+<<<< -- %s to %s state -- >>>>", + OldState == GET_PKT_TYPE ? "Type" : + OldState == GET_PKT_HEADER ? "Header" : + OldState == GET_PKT_PAYLOAD ? "Payload" : "Unknown", + _NewState == GET_PKT_TYPE ? "Type" : + _NewState == GET_PKT_HEADER ? "Header" : + _NewState == GET_PKT_PAYLOAD ? "Payload" : "Unknown" )); + + // Validate the state transition + switch (_NewState) + { + case GET_PKT_TYPE: + // Intialize the context for a new packet + _ReadContext->BytesReadNextSegment = 0; + _ReadContext->H4Packet.Type = 0; + _ReadContext->BytesToRead4FullPacket = 0; + RtlZeroMemory(_ReadContext->H4Packet.Packet.Raw, HCI_ACLDATA_HEADER_LEN); + break; + case GET_PKT_HEADER: + case GET_PKT_PAYLOAD: + // Reset segment count + _ReadContext->BytesReadNextSegment = 0; + break; + } + + _ReadContext->ReadSegmentState = _NewState; +} + + // Full packet: match to a Request and complete it. +NTSTATUS +ReadH4PacketComplete( + PFDO_EXTENSION _FdoExtension, + UCHAR _Type, + _In_reads_bytes_(_BufferLength) PUCHAR _Buffer, + ULONG _BufferLength + ) +{ + NTSTATUS Status = STATUS_SUCCESS; + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+ReadH4PacketComplete %S Packet Length %d", + _Type == (UCHAR) HciPacketEvent ? L"Event" : L"AclData", _BufferLength )); + +#if DBG + // Tracking last completed packet + RtlCopyMemory(_FdoExtension->LastPacket, _Buffer, _BufferLength); + _FdoExtension->LastPacketLength = _BufferLength; +#endif + + if (_Type == (UCHAR) HciPacketEvent) + { + ReadRequestComplete(_FdoExtension, + HciPacketEvent, + _BufferLength, + _Buffer, + _FdoExtension->ReadEventQueue, + &_FdoExtension->EventQueueCount, + &_FdoExtension->ReadEventList, + &_FdoExtension->EventListCount); + } + else + { + ReadRequestComplete(_FdoExtension, + HciPacketAclData, + _BufferLength, + _Buffer, + _FdoExtension->ReadDataQueue, + &_FdoExtension->DataQueueCount, + &_FdoExtension->ReadDataList, + &_FdoExtension->DataListCount); + } + + DoTrace(LEVEL_INFO, TFLAG_IO, ("-ReadH4PacketComplete %!STATUS!", Status)); + + return Status; +} + + +NTSTATUS +ReadH4PacketReassemble( + _Inout_ PUART_READ_CONTEXT _ReadContext, + _In_ ULONG _BytesRead, + _In_reads_bytes_(_BytesRead) PUCHAR _Buffer + ) +/*++ + +Routine Description: + + A function enforce a state machine to process reading data to form a + complete HCI packet. + +Arguments: + + _ReadContext - read context + _BytesRead - bytes of data read and is in the output buffer + _OutBuffer - Buffer that contain the data + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONG BytesRemained = _BytesRead; + PUCHAR Buffer = _Buffer; + PFDO_EXTENSION FdoExtension = _ReadContext->FdoExtension; + PH4_PACKET H4Packet; + ULONG PacketLen; + ULONG BytesToRead; + + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+ReadH4PacketReassemble: %d _BytesRead, ReadSegmentState %d", + _BytesRead, _ReadContext->ReadSegmentState)); + + // + // By design, it will take two reads to complete an H4 packets. + // + // First Read (5 bytes = 1 + 4 = Type + Larger of (ACLDataHeader:4, EvetnHeader:2)) + // + // - Event + // Complete (1 + 2 ), this is an Event packet without any param. + // Complete (1 + 2 + 1), event with 1 param + // * These two outcome requires interval timeout to complete the read (ask for 5). + // Complete (1 + 2 + 2), event with 2 params + // * if completed with one read, do the First read again. + // + // Partial (1 + 2 + 2 + ParamCount-2), this will complete in next read + // BytesToRead = ParamCount - 2 + // + // - ACL Data + // Partial (1 + 4 + DataLength), this packet will be complete in next read + // ByteToRead = DataLength + // Second read + // - Event/AclData + // Complete (5 + BytesToRead) + // + + while (NT_SUCCESS(Status) && BytesRemained > 0) { + + // Process read buffer based on its read state + switch (_ReadContext->ReadSegmentState) { + case GET_PKT_TYPE: + H4Packet = (PH4_PACKET) Buffer; + BUFFER_AND_SIZE_ADJUSTED(Buffer, BytesRemained, _ReadContext->BytesReadNextSegment, 1); + + if (H4Packet->Type == (UCHAR) HciPacketEvent) { + DoTrace(LEVEL_INFO, TFLAG_IO, (" [Event] ---------- ")); + _ReadContext->BytesToRead4FullPacket = HCI_EVENT_HEADER_SIZE; + } + else if (H4Packet->Type == (UCHAR) HciPacketAclData) { + DoTrace(LEVEL_INFO, TFLAG_IO, (" [AclData] ---------- ")); + _ReadContext->BytesToRead4FullPacket = HCI_ACL_HEADER_SIZE; + } + else { + // + // Abort the read operation here but can consider to traverse the data + // until a valid packet type is found. + // + Status = STATUS_INVALID_PARAMETER; // discard and read again + DoTrace(LEVEL_ERROR, TFLAG_IO, (" Unexpected PacketType %d", H4Packet->Type)); + NT_ASSERT(FALSE && L"Detected unknown packet type"); + goto OutOfSync; + } + + // Proceed to read packet header + _ReadContext->H4Packet.Type = H4Packet->Type; // Valid packet type is cached. + ReadSegmentStateSet(_ReadContext, GET_PKT_HEADER); + break; + + case GET_PKT_HEADER: + if (_ReadContext->H4Packet.Type == (UCHAR) HciPacketEvent) { + if (_ReadContext->BytesReadNextSegment == 0 && BytesRemained) { + _ReadContext->H4Packet.Packet.Event.EventCode = *Buffer; + DoTrace(LEVEL_INFO, TFLAG_IO, (" [Event] Code 0x%x", _ReadContext->H4Packet.Packet.Event.EventCode)); + BUFFER_AND_SIZE_ADJUSTED(Buffer, BytesRemained, _ReadContext->BytesReadNextSegment, 1); + _ReadContext->BytesToRead4FullPacket = 1; // Read the ParamsCount if needed + } + + if (_ReadContext->BytesReadNextSegment == 1 && BytesRemained) { + _ReadContext->H4Packet.Packet.Event.ParamsCount = *Buffer; + DoTrace(LEVEL_INFO, TFLAG_IO, (" [Event] ParamsCount 0x%x", _ReadContext->H4Packet.Packet.Event.ParamsCount)); + BUFFER_AND_SIZE_ADJUSTED(Buffer, BytesRemained, _ReadContext->BytesReadNextSegment, 1); + + if (_ReadContext->H4Packet.Packet.Event.ParamsCount == 0) { + // Full packet: match to a Request and complete it. + PacketLen = HCI_EVENT_HEADER_LEN + _ReadContext->H4Packet.Packet.Event.ParamsCount; + DoTrace(LEVEL_INFO, TFLAG_DATA, (" [Event completed] PacketLen %d", PacketLen)); + Status = ReadH4PacketComplete(FdoExtension, + _ReadContext->H4Packet.Type, + (PUCHAR) &_ReadContext->H4Packet.Packet.Event, + PacketLen); + // Read next packet + ReadSegmentStateSet(_ReadContext, GET_PKT_TYPE); + } + // Read the remainder of a full (Event) packet + else { + if (BytesRemained < _ReadContext->H4Packet.Packet.Event.ParamsCount) { + _ReadContext->BytesToRead4FullPacket = + _ReadContext->H4Packet.Packet.Event.ParamsCount - BytesRemained; + } + + // Process to read packet payload + ReadSegmentStateSet(_ReadContext, GET_PKT_PAYLOAD); + } + } + } + else { + + if (_ReadContext->BytesReadNextSegment == 0 && BytesRemained) { + _ReadContext->H4Packet.Packet.Raw[_ReadContext->BytesReadNextSegment] = *Buffer; + DoTrace(LEVEL_INFO, TFLAG_IO, (" [AclData] Header[0] 0x%x", _ReadContext->H4Packet.Packet.Raw[_ReadContext->BytesReadNextSegment])); + BUFFER_AND_SIZE_ADJUSTED(Buffer, BytesRemained, _ReadContext->BytesReadNextSegment, 1); + _ReadContext->BytesToRead4FullPacket = 3; // Read the remaining Dta header if needed + } + + if (_ReadContext->BytesReadNextSegment == 1 && BytesRemained) { + _ReadContext->H4Packet.Packet.Raw[_ReadContext->BytesReadNextSegment] = *Buffer; + DoTrace(LEVEL_INFO, TFLAG_IO, (" [AclData] Header[1] 0x%x", _ReadContext->H4Packet.Packet.Raw[_ReadContext->BytesReadNextSegment])); + BUFFER_AND_SIZE_ADJUSTED(Buffer, BytesRemained, _ReadContext->BytesReadNextSegment, 1); + _ReadContext->BytesToRead4FullPacket = 2; // Read the remaining Dta header if needed + } + + if (_ReadContext->BytesReadNextSegment == 2 && BytesRemained) { + _ReadContext->H4Packet.Packet.Raw[_ReadContext->BytesReadNextSegment] = *Buffer; + DoTrace(LEVEL_INFO, TFLAG_IO, (" [AclData] Header[2] 0x%x", _ReadContext->H4Packet.Packet.Raw[_ReadContext->BytesReadNextSegment])); + BUFFER_AND_SIZE_ADJUSTED(Buffer, BytesRemained, _ReadContext->BytesReadNextSegment, 1); + _ReadContext->BytesToRead4FullPacket = 1; // Read the remaining Dta header if needed + } + + if (_ReadContext->BytesReadNextSegment == 3 && BytesRemained) { + _ReadContext->H4Packet.Packet.Raw[_ReadContext->BytesReadNextSegment] = *Buffer; + DoTrace(LEVEL_INFO, TFLAG_IO, (" [AclData] Header[3] 0x%x", _ReadContext->H4Packet.Packet.Raw[_ReadContext->BytesReadNextSegment])); + BUFFER_AND_SIZE_ADJUSTED(Buffer, BytesRemained, _ReadContext->BytesReadNextSegment, 1); + + // Read the reamainder of a full (Data) packet + if (BytesRemained < _ReadContext->H4Packet.Packet.AclData.DataLength) { + _ReadContext->BytesToRead4FullPacket = + _ReadContext->H4Packet.Packet.AclData.DataLength - BytesRemained; + } + + // Process to read packet payload + ReadSegmentStateSet(_ReadContext, GET_PKT_PAYLOAD); + } + } + break; + + case GET_PKT_PAYLOAD: + if (_ReadContext->H4Packet.Type == (UCHAR) HciPacketEvent) { + + BytesToRead = _ReadContext->H4Packet.Packet.Event.ParamsCount - _ReadContext->BytesReadNextSegment; + + if (BytesRemained >= BytesToRead) { + // Full packet + RtlCopyMemory(&_ReadContext->H4Packet.Packet.Event.Params[_ReadContext->BytesReadNextSegment], + Buffer, + BytesToRead); + DoTrace(LEVEL_INFO, TFLAG_IO, (" [Event] Payload[%d + %d] = FULL", + _ReadContext->BytesReadNextSegment, + BytesToRead)); + BUFFER_AND_SIZE_ADJUSTED(Buffer, BytesRemained, _ReadContext->BytesReadNextSegment, BytesToRead); + + // Full packet: match to a Request and complete it. + PacketLen = HCI_EVENT_HEADER_LEN + _ReadContext->H4Packet.Packet.Event.ParamsCount; + Status = ReadH4PacketComplete(FdoExtension, + _ReadContext->H4Packet.Type, + (PUCHAR) &_ReadContext->H4Packet.Packet.Event, + PacketLen); + // Read next packet + ReadSegmentStateSet(_ReadContext, GET_PKT_TYPE); + } + else { + // Partial packet + RtlCopyMemory(&_ReadContext->H4Packet.Packet.Event.Params[_ReadContext->BytesReadNextSegment], + Buffer, + BytesRemained); + DoTrace(LEVEL_INFO, TFLAG_IO, (" [Event] Payload[%d + %d] = Partial; %d to read", + _ReadContext->BytesReadNextSegment, + BytesRemained, + BytesToRead - BytesRemained)); + _ReadContext->BytesReadNextSegment += BytesRemained; + BUFFER_AND_SIZE_ADJUSTED(Buffer, BytesRemained, _ReadContext->BytesReadNextSegment, BytesRemained); + + // Remaining event params to read + _ReadContext->BytesToRead4FullPacket = + _ReadContext->H4Packet.Packet.Event.ParamsCount - _ReadContext->BytesReadNextSegment; + } + } + else { + + if (_ReadContext->H4Packet.Packet.AclData.DataLength > HCI_MAX_ACL_PAYLOAD_SIZE) { + Status = STATUS_INVALID_PARAMETER; // discard and read again + DoTrace(LEVEL_ERROR, TFLAG_IO, (" Unexpected ACL DataLength %d > Presetted maximum size %d", + _ReadContext->H4Packet.Packet.AclData.DataLength, + HCI_MAX_ACL_PAYLOAD_SIZE)); + NT_ASSERT(FALSE && L"Max ACL DataLength exceeded the presetted Max"); + goto OutOfSync; + } + + BytesToRead = _ReadContext->H4Packet.Packet.AclData.DataLength - _ReadContext->BytesReadNextSegment; + + if (BytesRemained >= BytesToRead) { + // Process full packet + RtlCopyMemory(&_ReadContext->H4Packet.Packet.AclData.Data[_ReadContext->BytesReadNextSegment], + Buffer, + BytesToRead); + DoTrace(LEVEL_INFO, TFLAG_IO, (" [AclData] Payload[%d + %d] = FULL", + _ReadContext->BytesReadNextSegment, + BytesToRead)); + BUFFER_AND_SIZE_ADJUSTED(Buffer, BytesRemained, _ReadContext->BytesReadNextSegment, BytesToRead); + + // Full packet: try match to a Request in queue (if any) and complete it. + PacketLen = HCI_ACLDATA_HEADER_LEN + _ReadContext->H4Packet.Packet.AclData.DataLength; + Status = ReadH4PacketComplete(FdoExtension, + _ReadContext->H4Packet.Type, + (PUCHAR) &_ReadContext->H4Packet.Packet.AclData, + PacketLen); + // Next packet + ReadSegmentStateSet(_ReadContext, GET_PKT_TYPE); + } + else { + // Process partial packet + RtlCopyMemory(&_ReadContext->H4Packet.Packet.AclData.Data[_ReadContext->BytesReadNextSegment], + Buffer, + BytesRemained); + DoTrace(LEVEL_INFO, TFLAG_IO, (" [AclData] Payload[%d + %d] = Partial; %d to read", + _ReadContext->BytesReadNextSegment, + BytesRemained, + BytesToRead - BytesRemained)); + _ReadContext->BytesReadNextSegment += BytesRemained; + BUFFER_AND_SIZE_ADJUSTED(Buffer, BytesRemained, _ReadContext->BytesReadNextSegment, BytesRemained); + + // Remaining data to read + _ReadContext->BytesToRead4FullPacket = + _ReadContext->H4Packet.Packet.AclData.DataLength - _ReadContext->BytesReadNextSegment; + } + } + break; + + default: + DoTrace(LEVEL_ERROR, TFLAG_IO, (" Unknown ReadSegmentState")); + break; + } + } + + return Status; + +OutOfSync: + + DoTrace(LEVEL_ERROR, TFLAG_IO, (" Out-of-sync error detected in ProcessReadBuffer() %!STATUS!", Status)); + + return Status; +} + + +VOID +ReadH4PacketCompletionRoutine( + _In_ WDFREQUEST _Request, + _In_ WDFIOTARGET _Target, + _In_ PWDF_REQUEST_COMPLETION_PARAMS _Params, + _In_ WDFCONTEXT _Context + ) +/*++ + +Routine Description: + + This is CR function for reading data from device. It process the data read and + send down another request unless there is an error or the request is being + canceled. + +Arguments: + + _Request - a caller allocated WDF Request + _Target - WDF IO Target + _Params - Completion parameters + _Context - Context of this request + +Return Value: + + none + +--*/ +{ + NTSTATUS Status; + PUART_READ_CONTEXT ReadContext; + PFDO_EXTENSION FdoExtension; + ULONG BytesRead; + WDFMEMORY ReadMemory; + PUCHAR OutBuffer; + size_t OutBufferSize; + READ_REQUEST_STATE PreviousState; + + + UNREFERENCED_PARAMETER(_Request); + UNREFERENCED_PARAMETER(_Target); + + // Operation result + Status = _Params->IoStatus.Status; + BytesRead = (ULONG) _Params->Parameters.Read.Length; + + ReadContext = (PUART_READ_CONTEXT) _Context; + ReadContext->Status = Status; + + // Set to REQUEST_COMPLETE if skip REQUEST_PENDING state. + PreviousState = InterlockedCompareExchange((PLONG)&ReadContext->RequestState, + REQUEST_COMPLETE, + REQUEST_SENT); + + DoTrace(LEVEL_WARNING, TFLAG_DATA, ("+ReadH4PacketCompletionRoutine %!STATUS! %d BytesRead %S)", + Status, BytesRead, PreviousState == REQUEST_PENDING ? L"Async" : L"*Sync*")); + + FdoExtension = (PFDO_EXTENSION) ReadContext->FdoExtension; + + // + // The return status can either be + // - successful (buffer completely filled), + // - timeout (buffer not completed filled prior to interval timeout expired + // - cancellation + // - failure + // + if (NT_SUCCESS(Status) || Status == STATUS_IO_TIMEOUT || Status == STATUS_TIMEOUT) { + // Continue to process + } + else { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" ReadH4PacketCompletionRoutine failed %!STATUS!", Status)); + if (Status == STATUS_CANCELLED) { + // + // Under regualr operational state, IO Target will only cancel a request + // when it is ready to abort (e.g. device removal). + // + } + + goto Exit; + } + + ReadMemory = _Params->Parameters.Read.Buffer; + OutBuffer = (PUCHAR) WdfMemoryGetBuffer(ReadMemory, &OutBufferSize); + NT_ASSERT(OutBufferSize >= BytesRead); + DoTrace(LEVEL_INFO, TFLAG_IO, (" ReadH4PacketCompletionRoutine %d BytesRead pBuffer %p", BytesRead, OutBuffer)); + + // + // Process a read buffer if there is data + // + if (OutBuffer && BytesRead) + { + // + // Process the incoming data to form partial or full H4 packet + // + Status = ReadH4PacketReassemble(ReadContext, + BytesRead, + OutBuffer); + + // If data stream error, ignore the packet and start over. + if (!NT_SUCCESS(Status)) + { + FdoExtension->OutOfSyncErrorCount++; + DoTrace(LEVEL_ERROR, TFLAG_IO, (" ====> [%d] 0x%x <=====", + FdoExtension->OutOfSyncErrorCount, + *OutBuffer)); + NT_ASSERT(NT_SUCCESS(Status) && L"Encountered an out-of-sync condition!"); + + // Prepare to read next data packet, starting with packet type. + ReadSegmentStateSet(ReadContext, GET_PKT_TYPE); + + // Log(Error): log statistic of the read pump until this error + + // + // If there is a (knonw) hardware error or if we have exceeded maximun hardware count, + // the link is no longer reliable. Need to report to the upper layer via a read request. + // + if (FdoExtension->HardwareErrorDetected && FdoExtension->OutOfSyncErrorCount > MAX_HARDWARE_ERROR_COUNT) + { + // + // Complete an event or read data request with STATUS_DEVICE_DATA_ERROR error to trigger + // BthMini/BthPort to handle the situation. IT can perform HCI_RESET to restore the + // data channel. + // +#ifdef REPORT_HARDWARE_ERROR + WDFREQUEST Request; + + DoTrace(LEVEL_ERROR, TFLAG_IO, (" ++++ Report a hardware error; OutOfSyncCount %d", FdoExtension->OutOfSyncErrorCount)); + + KeAcquireSpinLock(&FdoExtension->QueueAccessLock, &Irql); + // Complete a read (event or data) request with a specific error to indicate hardware error. + Status = WdfIoQueueRetrieveNextRequest(FdoExtension->ReadEventQueue, &Request); + + // if there is no event request, find a read data request. + if (Status == STATUS_NO_MORE_ENTRIES) + { + Status = WdfIoQueueRetrieveNextRequest(FdoExtension->ReadDataQueue, &Request); + } + KeReleaseSpinLock(&FdoExtension->QueueAccessLock, Irql); + + + if (NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" Complete a request with STATUS_DEVICE_DATA_ERROR")); + WdfRequestComplete(Request, STATUS_DEVICE_DATA_ERROR); + } +#endif // REPORT_HARDWARE_ERROR + Status = STATUS_DEVICE_DATA_ERROR; + + // abort and stop read pump + goto Exit; + + } + else + { + + DoTrace(LEVEL_ERROR, TFLAG_IO, (" Detect out-of-sync error but read ahead...")); + + // Reset hardware error. + FdoExtension->HardwareErrorDetected = FALSE; + + // try next + goto ReadNext; + } + } + } + else + { + NT_ASSERT(Status == STATUS_TIMEOUT); + } + +ReadNext: + + if (PreviousState == REQUEST_PENDING) + { + ULONG BytesToRead; + + // + // Determine what is the size of the buffer to send down. + // + BytesToRead = (ReadContext->ReadSegmentState == GET_PKT_TYPE ? INITIAL_H4_READ_SIZE : + ReadContext->BytesToRead4FullPacket ? ReadContext->BytesToRead4FullPacket : + sizeof(FdoExtension->ReadBuffer)); + + DoTrace(LEVEL_INFO, TFLAG_IO, (" ReadH4Packet(Read Buffer Size %d bytes)", BytesToRead)); + + // Issue next read here since this request was complete asychronously + // i.e. pending first and then this completion routein is invoked. + ReadH4Packet(ReadContext, + FdoExtension->ReadRequest, + FdoExtension->ReadMemory, + FdoExtension->ReadBuffer, + BytesToRead); + } + else + { + // Fall through and leave this fucntion if this request was completed synchronously; + // i.e. this function is invoked first and then return to the RequestSent function. + } + + DoTrace(LEVEL_INFO, TFLAG_IO, ("-CR_ReadReadIO (fall though)")); + + return; + +Exit: + + if (!NT_SUCCESS(Status)) + { + NT_ASSERT(Status == STATUS_CANCELLED); + FdoExtension->ReadPumpRunning = FALSE; + DoTrace(LEVEL_WARNING, TFLAG_IO, (" Pump has stopped!")); + } + + DoTrace(LEVEL_INFO, TFLAG_IO, ("-CR_ReadReadIO (error)")); +} + + +NTSTATUS +ReadH4Packet( + _In_ PUART_READ_CONTEXT _ReadContext, + _In_ WDFREQUEST _WdfRequest, + _In_ WDFMEMORY _WdfMemory, + _Pre_notnull_ _Pre_writable_byte_size_ (_BufferLen) PVOID _Buffer, + _In_ ULONG _BufferLen + ) +/*++ + +Routine Description: + + Initiate the reading of an HCI packet (event or data) by sending down a read request. + +Arguments: + + _ReadContext - Context used for reading data from target UART device + +Return Value: + + NTSTATUS + +--*/ +{ + PFDO_EXTENSION FdoExtension; + WDF_REQUEST_REUSE_PARAMS RequestReuseParams; + NTSTATUS Status; + + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+ReadH4Packet")); + + FdoExtension = _ReadContext->FdoExtension; + + if (0 == _BufferLen) { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" ReadH4Packet: _BufferLen cannot be 0")); + Status = STATUS_INVALID_PARAMETER; + goto Done; + } + + while (TRUE) { + + DoTrace(LEVEL_INFO, TFLAG_IO, (" ReadH4Packet - <start>")); + NT_ASSERT(_ReadContext->RequestState != REQUEST_SENT); + + if (!IsDeviceInitialized(FdoExtension)) { + Status = STATUS_DEVICE_NOT_READY; + DoTrace(LEVEL_ERROR, TFLAG_IO, (" ReadH4Packet: cannot attach IO %!STATUS!", Status)); + goto Done; + } + + // + // Issue a read event request + // + WDF_REQUEST_REUSE_PARAMS_INIT(&RequestReuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_SUCCESS); + Status = WdfRequestReuse(_WdfRequest, &RequestReuseParams); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfRequestReuse failed %!STATUS!", Status)); + goto Done; + } + + Status = WdfMemoryAssignBuffer(_WdfMemory, _Buffer, _BufferLen); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfMemoryAssignBuffer failed %!STATUS!", Status)); + goto Done; + } + + Status = WdfIoTargetFormatRequestForRead(FdoExtension->IoTargetSerial, + _WdfRequest, + _WdfMemory, + NULL, NULL); + + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfIoTargetFormatRequestForRead failed %!STATUS!", Status)); + goto Done; + } + + // Note: This request is sent to UART driver so it cannot be marked cancellable. + // But it can be canceled by issuing WdfRequestCancelSentRequest(). + + WdfRequestSetCompletionRoutine(_WdfRequest, + ReadH4PacketCompletionRoutine, + _ReadContext); + + InterlockedExchange((PLONG)&_ReadContext->RequestState, REQUEST_SENT); + + if (FALSE == WdfRequestSend(_WdfRequest, + FdoExtension->IoTargetSerial, + WDF_NO_SEND_OPTIONS)) + { + Status = WdfRequestGetStatus(_WdfRequest); + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfRequestSend failed %!STATUS!", Status)); + + // Not much we can do if cannot send this request; data pump will be stopped! + goto Done; + } + else + { + READ_REQUEST_STATE PreviousState; + + // Set to REQUEST_PENDING if it is in the REQUEST_SENT state. + PreviousState = InterlockedCompareExchange((PLONG) &_ReadContext->RequestState, + REQUEST_PENDING, + REQUEST_SENT); + + DoTrace(LEVEL_WARNING, TFLAG_IO, (" WdfRequestSend ReqState: %d -> %d", + PreviousState, _ReadContext->RequestState)); + + if (PreviousState == REQUEST_SENT) + { + // Request is still pending, and will be completed asychronously in the + // completion routine where it can issue next read. + Status = STATUS_PENDING; + break; + } + else + { + Status = FdoExtension->ReadContext.Status; + if (NT_SUCCESS(Status)) + { + // Previous request has been complete synchronously in the + // completion routine; do next read in this function. + } + else + { + // No tolerance for error + break; + } + } + } + } + +Done: + + if (!NT_SUCCESS(Status)) + { + NT_ASSERT(Status == STATUS_CANCELLED); + FdoExtension->ReadPumpRunning = FALSE; + } + + DoTrace(LEVEL_INFO, TFLAG_IO, ("-ReadH4Packet %!STATUS!", Status)); + + return Status; +} + +__inline +PHCI_PACKET_ENTRY +HLP_CreatePacketEntry( + _In_ ULONG _PacketLength, + _In_reads_bytes_(_PacketLength) PUCHAR _Packet + ) +{ + PHCI_PACKET_ENTRY PacketEntry = NULL; + + PacketEntry = (PHCI_PACKET_ENTRY)ExAllocatePool(NonPagedPoolNx, sizeof(HCI_PACKET_ENTRY) + _PacketLength); + if (PacketEntry != NULL) { + InitializeListHead(&PacketEntry->DataEntry); + RtlCopyMemory(PacketEntry->Packet, _Packet, _PacketLength); + PacketEntry->PacketLen = _PacketLength; + } + + return PacketEntry; +} + +NTSTATUS +ReadRequestComplete( + _In_ PFDO_EXTENSION _FdoExtension, + _In_ UCHAR _PacketType, + _In_ ULONG _PacketLength, + _In_reads_bytes_opt_(_PacketLength) PUCHAR _Packet, + _Inout_ WDFQUEUE _Queue, + _Inout_ PLONG _QueueCount, + _Inout_ PLIST_ENTRY _ListHead, + _Inout_ PLONG _ListCount + ) +/*++ +Routine Description: + + This helper function processes both complete HCI Data packet from the device to find + a pending Request, or find a completed HCI packet in a list to complete a Request. + +Arguments: + + _FdoExtension - Device context + _PacketType - HCI packet type (either Event or Data for incoming data) + _ListHead - List where to retrieve completed HCI packet + _Request - Request that is used to complete a read if a corresponding HCI packet is available. + +Return Value: + + NTSTATUS - STATUS_SUCCESS Or STATUS_INSUFFICIENT_RESOURCE + +--*/ +{ + KIRQL Irql; + WDFREQUEST Request = NULL; + NTSTATUS Status = STATUS_SUCCESS; + PHCI_PACKET_ENTRY PacketEntry = NULL; + WDFMEMORY ReqOutMemory; + size_t BufferSize = 0, BytesToReturn; + PBTHX_HCI_READ_WRITE_CONTEXT HCIContext; + BOOLEAN CompleteRequest = FALSE; + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+ReadRequestComplete")); + + // + // (ReqQueue, PktList) + // C0. ( empty, empty) -> Add packet to list + // C1. ( empty, !empty) -> Add packet to list + // C2. (!empty, empty) -> DequeueAndCompletRequest(Packet) + // C3. (!empty, !empty) -> Error! Cannot both empty at this function entry. + // + + KeAcquireSpinLock(&_FdoExtension->QueueAccessLock, &Irql); + + if (_Packet) { + + Status = WdfIoQueueRetrieveNextRequest(_Queue, &Request); + if (Status == STATUS_SUCCESS) { + // Case 2: Typical code path + InterlockedDecrement(_QueueCount); + DoTrace(LEVEL_INFO, TFLAG_IO, (" (C2) Complete a request %p, _Packet %p, _PacketLength %d", + Request, _Packet, _PacketLength)); + + CompleteRequest = TRUE; + + // Case 3: An error condition if List is not empty + NT_ASSERT(IsListEmpty(_ListHead)); + } + else { + // Case 0: + PacketEntry = HLP_CreatePacketEntry(_PacketLength, _Packet); + if (PacketEntry == NULL) { + // Error condition + Status = STATUS_INSUFFICIENT_RESOURCES; + DoTrace(LEVEL_ERROR, TFLAG_IO, (" (C0/Error) Could not allocate HCI_PACKET_ENTRY %!STATUS!", Status)); + // This packet will be dropped; but nothing we can do as system resource is depleted! + } + else { + // Cache this packet to Packet List + InsertTailList(_ListHead, &PacketEntry->DataEntry); + InterlockedIncrement(_ListCount); + DoTrace(LEVEL_INFO, TFLAG_IO, (" (C0) Queuing packet with list count %d", *_ListCount)); + } + } + } + else { + if (!IsListEmpty(_ListHead)) { + Status = WdfIoQueueRetrieveNextRequest(_Queue, &Request); + if (Status == STATUS_SUCCESS) { + // Case 2: Has Packet in the list while a new request arrives + InterlockedDecrement(_QueueCount); + + PacketEntry = (PHCI_PACKET_ENTRY) RemoveHeadList(_ListHead); + _Packet = PacketEntry->Packet; + _PacketLength = PacketEntry->PacketLen; + InterlockedDecrement(_ListCount); + + DoTrace(LEVEL_INFO, TFLAG_IO, (" (C2) Complete a request %p, _Packet %p, _PacketLength %d", + Request, _Packet, _PacketLength)); + + CompleteRequest = TRUE; + } + else { + NT_ASSERT(FALSE && L"Failed to retrieve a request just queued!"); + } + } + else { + // Case 1: Request is pre-pening and queued. + Status = STATUS_PENDING; + DoTrace(LEVEL_INFO, TFLAG_IO, (" (C1) Read request is queued")); + } + } + + KeReleaseSpinLock(&_FdoExtension->QueueAccessLock, Irql); + + if (!CompleteRequest) { + goto Done; + } + + // Complete this request + Status = WdfRequestRetrieveOutputMemory(Request, &ReqOutMemory); + if (Status != STATUS_SUCCESS) { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" Could not retrieve output buffer")); + WdfRequestCompleteWithInformation(Request, Status, (ULONG_PTR)0); + goto Done; + } + + HCIContext = WdfMemoryGetBuffer(ReqOutMemory, &BufferSize); + BytesToReturn = FIELD_OFFSET(BTHX_HCI_READ_WRITE_CONTEXT, Data) + _PacketLength; + + // This should not happen because BthMini should have sent down largest buffer according to device's capability. + NT_ASSERT(BytesToReturn <= BufferSize); + + // Transfer data to Request's output buffer + HCIContext->Type = _PacketType; + HCIContext->DataLen = _PacketLength; + if (BytesToReturn <= BufferSize) { + RtlCopyMemory(&HCIContext->Data, _Packet, _PacketLength); + } + else { + Status = STATUS_BUFFER_TOO_SMALL; + BytesToReturn = 0; + } + + // Validate and print out (WPP) HCI packet info + HCIContextValidate(HCIContext->Type == (UCHAR) HciPacketEvent ? + _FdoExtension->CntEventCompleted : _FdoExtension->CntReadDataCompleted, + HCIContext); + + // + // Release memory allocated for a completed packet entry; it was not removed from the packet list. + // + if (PacketEntry) { + ExFreePool(PacketEntry); + } + + if (HCIContext->Type == (UCHAR) HciPacketEvent) { + InterlockedIncrement(&_FdoExtension->CntEventCompleted); + DoTrace(LEVEL_INFO, TFLAG_DATA, (" [%d] HciPacketEvent completing %!STATUS!, %d BytesToReturn", + _FdoExtension->CntEventCompleted, Status, (ULONG) BytesToReturn)); + } + else if (HCIContext->Type == (UCHAR) HciPacketAclData) { + InterlockedIncrement(&_FdoExtension->CntReadDataCompleted); + DoTrace(LEVEL_INFO, TFLAG_DATA, (" [%d] HciPacketAclData completing %!STATUS!, %d BytesToReturn", + _FdoExtension->CntReadDataCompleted, Status, (ULONG) BytesToReturn)); + } + + DoTrace(LEVEL_INFO, TFLAG_IO, (" Completing Request(%p) %!STATUS!, %d BytesToReturn", + Request, Status, (ULONG) BytesToReturn)); + + // + // return only the actual data read, not including BTHX_HCI_READ_WRITE_CONTEXT + // + WdfRequestCompleteWithInformation(Request, Status, BytesToReturn); + +Done: + + DoTrace(LEVEL_INFO, TFLAG_IO, ("-ReadRequestComplete: %!STATUS!", Status)); + + return Status; +} + + +VOID +ReadResourcesFree( + _In_ WDFDEVICE _Device +) +/*++ +Routine Description: + + This helper function free resource allocated in its corresponding allocation + function. + +Arguments: + + _Device - WDF Device object + +Return + + VOID + +--*/ +{ + PFDO_EXTENSION FdoExtension; + KIRQL Irql; + + DoTrace(LEVEL_INFO, TFLAG_IO,("+ReadResourcesFree")); + + FdoExtension = FdoGetExtension(_Device); + + // + // Note: The Request(s) in WDFQUEUE (Event and ReadData) WDFQUEUEs + // are managed by WDF, which will dequeue and cancel them for us. + // WdfIoQueueRetrieveNextRequest() returns STATUS_WDF_PAUSED since this + // function is invoked after entered D0. + // + + // + // Free resources allocated earlier + // + + while(!IsListEmpty(&FdoExtension->ReadEventList)) + { + PHCI_PACKET_ENTRY PacketEntry; + + KeAcquireSpinLock(&FdoExtension->QueueAccessLock, &Irql); + PacketEntry = (PHCI_PACKET_ENTRY)RemoveHeadList(&FdoExtension->ReadEventList); + InterlockedDecrement(&FdoExtension->EventListCount); + KeReleaseSpinLock(&FdoExtension->QueueAccessLock, Irql); + + if (PacketEntry) + { + ExFreePool(PacketEntry); + PacketEntry = NULL; + } + } + NT_ASSERT(FdoExtension->EventListCount == 0); + + while(!IsListEmpty(&FdoExtension->ReadDataList)) + { + PHCI_PACKET_ENTRY PacketEntry; + + KeAcquireSpinLock(&FdoExtension->QueueAccessLock, &Irql); + PacketEntry = (PHCI_PACKET_ENTRY)RemoveHeadList(&FdoExtension->ReadDataList); + InterlockedDecrement(&FdoExtension->DataListCount); + KeReleaseSpinLock(&FdoExtension->QueueAccessLock, Irql); + + if (PacketEntry) + { + ExFreePool(PacketEntry); + PacketEntry = NULL; + } + } + NT_ASSERT(FdoExtension->DataListCount == 0); + + if (FdoExtension->ReadRequest) + { + WdfObjectDelete(FdoExtension->ReadRequest); + FdoExtension->ReadRequest = NULL; + } +} + + +NTSTATUS +ReadResourcesAllocate( + _In_ WDFDEVICE _Device +) +/*++ +Routine Description: + + This helper function allocates resource (queues and lists) for managing read IOs + Request from upper layer or for data pump with the device. + +Arguments: + + _Device - WDF Device object + +Return Value: + + NTSTATUS - STATUS_SUCCESS Or STATUS_INSUFFICIENT_RESOURCE + +--*/ +{ + NTSTATUS Status; + PFDO_EXTENSION FdoExtension; + WDF_IO_QUEUE_CONFIG QueueConfig; + WDF_OBJECT_ATTRIBUTES ObjAttributes; + + DoTrace(LEVEL_INFO, TFLAG_IO,("+ReadResourcesAllocate")); + + FdoExtension = FdoGetExtension(_Device); + + + // HCI_EVENT + // Create WDF Queue for pending Read Event Request(s), and + // Initialize a List for pre-fetched Event + WDF_IO_QUEUE_CONFIG_INIT(&QueueConfig, + WdfIoQueueDispatchManual); + + Status = WdfIoQueueCreate(_Device, + &QueueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &FdoExtension->ReadEventQueue); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfIoQueueCreate(Event) %!STATUS!", Status)); + goto Done; + } + + InitializeListHead(&FdoExtension->ReadEventList); + + FdoExtension->EventListCount = 0; + FdoExtension->EventQueueCount = 0; + + + // HCI_DATA + // Create WDF Queue for pending Read Data Request(s), and + // Initialize a List for pre-fetched Data + Status = WdfIoQueueCreate(_Device, + &QueueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &FdoExtension->ReadDataQueue); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfIoQueueCreate(Data) %!STATUS!", Status)); + goto Done; + } + + InitializeListHead(&FdoExtension->ReadDataList); + + FdoExtension->DataListCount = 0; + FdoExtension->DataQueueCount = 0; + + + // Track request from top and HCI packets from device + FdoExtension->CntCommandReq = 0; + FdoExtension->CntCommandCompleted = 0; + + FdoExtension->CntEventReq = 0; + FdoExtension->CntEventCompleted = 0; + + FdoExtension->CntWriteDataReq = 0; + FdoExtension->CntWriteDataCompleted = 0; + + FdoExtension->CntReadDataReq = 0; + FdoExtension->CntReadDataCompleted = 0; + + + // Create a WDF Request + WDF_OBJECT_ATTRIBUTES_INIT(&ObjAttributes); + ObjAttributes.ParentObject = _Device; + + Status = WdfRequestCreate(&ObjAttributes, + FdoExtension->IoTargetSerial, + &FdoExtension->ReadRequest); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfRequestCreate(ReadRequest) failed %!STATUS!", Status)); + goto Done; + } + + // Initialize the ReadContext and its initial ReadSegmentState + RtlZeroMemory(&FdoExtension->ReadContext, sizeof(UART_READ_CONTEXT)); + FdoExtension->ReadContext.FdoExtension = FdoExtension; + ReadSegmentStateSet(&FdoExtension->ReadContext, GET_PKT_TYPE); + + Status = WdfMemoryCreatePreallocated(&ObjAttributes, + &FdoExtension->ReadBuffer, + sizeof(FdoExtension->ReadBuffer), + &FdoExtension->ReadMemory); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfMemoryCreatePreallocated(ReadMemory) failed %!STATUS!", Status)); + goto Done; + } + +Done: + + DoTrace(LEVEL_INFO, TFLAG_IO,("-ReadResourcesAllocate %!STATUS!", Status)); + if (!NT_SUCCESS(Status)) + { + ReadResourcesFree(_Device); + } + + return Status; +} + + diff --git a/bluetooth/serialhcibus/pdo.c b/bluetooth/serialhcibus/pdo.c new file mode 100644 index 00000000..74ee6350 --- /dev/null +++ b/bluetooth/serialhcibus/pdo.c @@ -0,0 +1,972 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + Pdo.c + +Abstract: + + This module create a PDO and handles plug & play calls for the child device (PDO). + +Environment: + + kernel mode only + +--*/ + +#include "driver.h" +#include "pdo.tmh" + + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, PdoCreate) +#pragma alloc_text(PAGE, PdoDevD0Exit) +#pragma alloc_text(PAGE, PdoDevD0Entry) +#pragma alloc_text(PAGE, PdoDevPrepareHardware) +#pragma alloc_text(PAGE, PdoDevReleaseHardware) +#endif + +#define MAX_ID_LEN 80 + + + +#ifdef DYNAMIC_ENUM + + +NTSTATUS +PdoCreateDynamic( + _In_ WDFDEVICE _Device, + _In_ PWDFDEVICE_INIT _DeviceInit, + _In_ PWCHAR _HardwareIds, + _In_ ULONG _SerialNo + ) +/*++ + +Routine Description: + + This routine creates and initialize a PDO. + +Arguments: + +Return Value: + + NT Status code. + +--*/ +{ + NTSTATUS Status; + PPDO_EXTENSION PdoExtension = NULL; + WDFDEVICE ChildDevice = NULL; + WDF_OBJECT_ATTRIBUTES pdoAttributes; + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_DEVICE_POWER_CAPABILITIES powerCaps; + DECLARE_CONST_UNICODE_STRING(compatId, BT_PDO_COMPATIBLE_IDS); + DECLARE_CONST_UNICODE_STRING(deviceLocation, L"Serial HCI Bus - Bluetooth Function"); + DECLARE_UNICODE_STRING_SIZE(buffer, MAX_ID_LEN); + DECLARE_UNICODE_STRING_SIZE(deviceId, MAX_ID_LEN); + + WDF_IO_QUEUE_CONFIG QueueConfig; + WDFQUEUE Queue; + + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(_Device); + + KdPrint(("Entered PdoCreateDynamic\n")); + + // + // Set DeviceType + // + WdfDeviceInitSetDeviceType(_DeviceInit, FILE_DEVICE_BUS_EXTENDER); + + // + // Provide DeviceID, HardwareIDs, CompatibleIDs and InstanceId + // + RtlInitUnicodeString(&deviceId, _HardwareIds); + + Status = WdfPdoInitAssignDeviceID(_DeviceInit, &deviceId); + if (!NT_SUCCESS(Status)) { + return Status; + } + + // + // NOTE: same string is used to initialize hardware id too + // + Status = WdfPdoInitAddHardwareID(_DeviceInit, &deviceId); + if (!NT_SUCCESS(Status)) { + return Status; + } + + Status = WdfPdoInitAddCompatibleID(_DeviceInit, &compatId ); + if (!NT_SUCCESS(Status)) { + return Status; + } + + Status = RtlUnicodeStringPrintf(&buffer, L"%02d", _SerialNo); + if (!NT_SUCCESS(Status)) { + return Status; + } + + Status = WdfPdoInitAssignInstanceID(_DeviceInit, &buffer); + if (!NT_SUCCESS(Status)) { + return Status; + } + + // + // Provide a description about the device. This text is usually read from + // the device. In the case of USB device, this text comes from the string + // descriptor. This text is displayed momentarily by the PnP manager while + // it's looking for a matching INF. If it finds one, it uses the Device + // Description from the INF file or the friendly name created by + // coinstallers to display in the device manager. FriendlyName takes + // precedence over the DeviceDesc from the INF file. + // + Status = RtlUnicodeStringPrintf( &buffer, + L"SerialHciBus_%02d", + _SerialNo ); + if (!NT_SUCCESS(Status)) { + return Status; + } + + // + // You can call WdfPdoInitAddDeviceText multiple times, adding device + // text for multiple locales. When the system displays the text, it + // chooses the text that matches the current locale, if available. + // Otherwise it will use the string for the default locale. + // The driver can specify the driver's default locale by calling + // WdfPdoInitSetDefaultLocale. + // + Status = WdfPdoInitAddDeviceText(_DeviceInit, + &buffer, + &deviceLocation, + 0x409 ); + if (!NT_SUCCESS(Status)) { + return Status; + } + + WdfPdoInitSetDefaultLocale(_DeviceInit, 0x409); + + // + // Initialize the attributes to specify the size of PDO device extension. + // All the state information private to the PDO will be tracked here. + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&pdoAttributes, PDO_EXTENSION); + + // + // Allow to forward requests to its FDO of this bus driver by using + // WdfRequestForwardToParentDeviceIoQueue()in the DeviceIoControl callback. + // + WdfPdoInitAllowForwardingRequestToParent(_DeviceInit); + + // + // Create a framework device object to represent PDO of this bus driver. In response + // to this call, framework creates a WDM deviceobject. + // + Status = WdfDeviceCreate(&_DeviceInit, + &pdoAttributes, + &ChildDevice); + + if (!NT_SUCCESS(Status)) { + return Status; + } + + + // + // Note: Once the device is created successfully, framework frees the + // _DeviceInit memory and sets the _DeviceInit to NULL. So don't + // call any WdfDeviceInit functions after that. + // + + // + // Initalize the PDO extension + // + PdoExtension = PdoGetExtension(ChildDevice); + + RtlZeroMemory(PdoExtension, sizeof(PDO_EXTENSION)); + + PdoExtension->FdoExtension = FdoGetExtension(_Device); + + PdoExtension->SerialNo = _SerialNo; + + + + // + // Set some properties for the child device. + // + WDF_DEVICE_PNP_CAPABILITIES_INIT(&PnpCaps); // Zeros this structure (note: WdfFalse is 0) + + // + // Bus driver sets this value to WdfFalse for this embedded device, which cannot + // be physically removed; its FDO must not set/override this. + // + PnpCaps.Removable = WdfFalse; + + // + // Bus driver sets this value to WdfTrue. FDO can override this value (when this irp is on its + // way up) if it determines that this device cannot be safely surprise (not orderly) removed + // without data loss. + // + PnpCaps.SurpriseRemovalOK = WdfTrue; + + pnpCaps.Address = _SerialNo; + pnpCaps.UINumber = _SerialNo; + + WdfDeviceSetPnpCapabilities(ChildDevice, &pnpCaps); + + WDF_DEVICE_POWER_CAPABILITIES_INIT(&powerCaps); + + powerCaps.DeviceD1 = WdfFalse; + powerCaps.DeviceD2 = WdfTrue; + + powerCaps.WakeFromD0 = WdfFalse; + powerCaps.WakeFromD1 = WdfFalse; + powerCaps.WakeFromD2 = WdfTrue; + powerCaps.WakeFromD3 = WdfTrue; + + powerCaps.DeviceWake = PowerDeviceD2; + + powerCaps.DeviceState[PowerSystemWorking] = PowerDeviceD0; + powerCaps.DeviceState[PowerSystemSleeping1] = PowerDeviceD2; + powerCaps.DeviceState[PowerSystemSleeping2] = PowerDeviceD2; + powerCaps.DeviceState[PowerSystemSleeping3] = PowerDeviceD2; + powerCaps.DeviceState[PowerSystemHibernate] = PowerDeviceD3; + powerCaps.DeviceState[PowerSystemShutdown] = PowerDeviceD3; + + WdfDeviceSetPowerCapabilities(ChildDevice, &powerCaps); + + + // + // Configure a default queue so that requests that are not + // configure-forwarded using WdfDeviceConfigureRequestDispatching to goto + // other queues get dispatched here. + // + + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&QueueConfig, WdfIoQueueDispatchParallel); + + // + // Cannot be power managed queue (dispatch only at D0) as + // BthMini issues BthX DDI to get version and capabilities + // before enter D0. A deadlock occurs if this is power managed. + // + QueueConfig.PowerManaged = WdfFalse; + + QueueConfig.EvtIoDeviceControl = PdoIoQuDeviceControl; + + Status = WdfIoQueueCreate(ChildDevice, + &QueueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &Queue); + DoTrace(LEVEL_INFO, TFLAG_PNP, (" WdfIoQueueCreate (%!STATUS!)", Status)); + if (!NT_SUCCESS(Status)) { + goto Cleanup; + } + +Cleanup: + + + // + // Call WdfDeviceInitFree if you encounter an error before the + // device is created. Once the device is created, framework + // NULLs the DeviceInit value. + // + if (!NT_SUCCESS(Status)) { + + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" -PdoCreateDynamic: exit %!STATUS!", Status)); + + if(ChildDevice) { + WdfObjectDelete(ChildDevice); + } + } + else + { + DoTrace(LEVEL_INFO, TFLAG_PNP, (" -PdoCreateDynamic: exit %!STATUS!", Status)); + } + + + return Status; +} + +#endif + + + +NTSTATUS +PdoCreate( + _In_ WDFDEVICE _Device, + _In_ PWSTR _HardwareIds, + _In_ ULONG _SerialNo +) +/*++ + +Routine Description: + + This routine creates and initialize a PDO to service a Bluetooth function. + +Arguments: + + _Device - A framework device object + + _HardwareIds - a hardware ID for this device + + -SerialNo - serial number of the child DO + +Return Value: + + NT Status code. + +--*/ +{ + NTSTATUS Status; + PWDFDEVICE_INIT DeviceInit = NULL; + WDF_PNPPOWER_EVENT_CALLBACKS PnpPowerCallbacks; + PPDO_EXTENSION PdoExtension = NULL; + WDFDEVICE ChildDevice = NULL; + WDF_OBJECT_ATTRIBUTES Attributes; + WDF_DEVICE_PNP_CAPABILITIES PnpCaps; + WDF_DEVICE_POWER_CAPABILITIES PowerCaps; + UNICODE_STRING StaticString = {0}; + UNICODE_STRING DeviceId; + DECLARE_UNICODE_STRING_SIZE(Buffer, MAX_ID_LEN); + UNICODE_STRING ContainerID = {0}; + WDF_PDO_EVENT_CALLBACKS Callbacks; + WDF_IO_QUEUE_CONFIG QueueConfig; + WDFQUEUE Queue; + + DoTrace(LEVEL_INFO, TFLAG_PNP, (" +PdoCreate: HWID(%S), compatID(%S)", _HardwareIds, BT_PDO_COMPATIBLE_IDS)); + + PAGED_CODE(); + + // + // Allocate a WDFDEVICE_INIT structure and set the properties + // so that we can create a device object for the child. + // + DeviceInit = WdfPdoInitAllocate(_Device); + if (DeviceInit == NULL) { + Status = STATUS_INSUFFICIENT_RESOURCES; + goto Cleanup; + } + + // + // Set DeviceType + // + WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_BUS_EXTENDER); + + // + // Provide DeviceID, HardwareIDs, CompatibleIDs and InstanceId + // + RtlInitUnicodeString(&DeviceId, _HardwareIds); + Status = WdfPdoInitAssignDeviceID(DeviceInit, &DeviceId); + if (!NT_SUCCESS(Status)) { + goto Cleanup; + } + + // + // Note: same string is used to initialize hardware id + // + Status = WdfPdoInitAddHardwareID(DeviceInit, &DeviceId); + if (!NT_SUCCESS(Status)) { + goto Cleanup; + } + + RtlInitUnicodeString(&StaticString, BT_PDO_COMPATIBLE_IDS); + Status = WdfPdoInitAddCompatibleID(DeviceInit, &StaticString); + if (!NT_SUCCESS(Status)) { + goto Cleanup; + } + + Status = RtlUnicodeStringPrintf(&Buffer, L"%02d", _SerialNo); + if (!NT_SUCCESS(Status)) { + goto Cleanup; + } + Status = WdfPdoInitAssignInstanceID(DeviceInit, &Buffer); + if (!NT_SUCCESS(Status)) { + goto Cleanup; + } + + // + // Assign the containerID for an internally connected device + // + Status = RtlStringFromGUID(&GUID_CONTAINERID_INTERNALLY_CONNECTED_DEVICE, &ContainerID); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP, ("Failed to generate the ContainerID, %!STATUS!", Status));; + goto Cleanup; + } + + Status = WdfPdoInitAssignContainerID(DeviceInit, &ContainerID); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP, ("Failed to assign the ContainerID, %!STATUS!", Status)); + goto Cleanup; + } + + // + // Provide a description about the device. This text is usually read from + // the device. This text is displayed momentarily by the PnP manager while + // it's looking for a matching INF. If it finds one, it uses the Device + // Description from the INF file or the friendly name created by + // coinstallers to display in the device manager. FriendlyName takes + // precedence over the DeviceDesc from the INF file. + // + Status = RtlUnicodeStringPrintf(&Buffer, L"SerialHciBus_%02d", _SerialNo ); + if (!NT_SUCCESS(Status)) { + goto Cleanup; + } + + // + // You can call WdfPdoInitAddDeviceText multiple times, adding device + // text for multiple locales. When the system displays the text, it + // chooses the text that matches the current locale, if available. + // Otherwise it will use the string for the default locale. + // The driver can specify the driver's default locale by calling + // WdfPdoInitSetDefaultLocale. + // + RtlInitUnicodeString(&StaticString, BT_PDO_DEVICE_LOCATION); + Status = WdfPdoInitAddDeviceText(DeviceInit, + &Buffer, + &StaticString, + 0x409); + if (!NT_SUCCESS(Status)) { + goto Cleanup; + } + + WdfPdoInitSetDefaultLocale(DeviceInit, 0x409); + + // + // Initialize the attributes to specify the size of PDO device extension. + // All the state information private to the PDO will be tracked here. + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attributes, PDO_EXTENSION); + + // + // Set power callbacks to handle idle/active transition of the Bluetooth function + // + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&PnpPowerCallbacks); + + // + // Register PnP callback + // + PnpPowerCallbacks.EvtDevicePrepareHardware = PdoDevPrepareHardware; + PnpPowerCallbacks.EvtDeviceReleaseHardware = PdoDevReleaseHardware; + + // + // Register for Power callback + // + PnpPowerCallbacks.EvtDeviceD0Entry = PdoDevD0Entry; + PnpPowerCallbacks.EvtDeviceD0Exit = PdoDevD0Exit; + + WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, + &PnpPowerCallbacks); + + // + // Allow to forward requests to its FDO of this bus driver by using + // WdfRequestForwardToParentDeviceIoQueue()in the DeviceIoControl callback. + // + WdfPdoInitAllowForwardingRequestToParent(DeviceInit); + + // + // Register to handle bus level power management (arm for wake?) + // + WDF_PDO_EVENT_CALLBACKS_INIT(&Callbacks); + + // + // Arm the device for wake: + // + // When the device is powered down, the framework calls the bus driver's + // EvtDeviceEnableWakeAtBus callback function at the beginning of the shutdown + // sequence, while the child device is still in the D0 state. In this callback + // function, the bus driver must do whatever is required at the bus level to + // enable the wake signal. + // + Callbacks.EvtDeviceEnableWakeAtBus = PdoDevEnableWakeAtBus; + + // + // Disarm the device for wake: + // + // If the child device triggered a wake signal, the system and the framework + // return the device to D0. The framework calls the bus driver's + // EvtDeviceDisableWakeAtBus callback function during startup of the child + // device. In this callback function, the bus driver should do whatever is + // required at the bus level to disable the wake signal, so that the device + // can no longer trigger it. Thus, EvtDeviceDisableWakeAtBus reverses the + // actions of EvtDeviceEnableWakeAtBus. + // + Callbacks.EvtDeviceDisableWakeAtBus = PdoDevDisableWakeAtBus; + + WdfPdoInitSetEventCallbacks(DeviceInit, &Callbacks); + + // + // Create a framework device object to represent PDO of this bus driver. In response + // to this call, framework creates a WDM deviceobject. + // + Status = WdfDeviceCreate(&DeviceInit, + &Attributes, + &ChildDevice); + if (!NT_SUCCESS(Status)) { + goto Cleanup; + } + + // + // Note: Once the device is created successfully, framework frees the + // DeviceInit memory and sets the DeviceInit to NULL. So don't + // call any WdfDeviceInit functions after that. + // + + // + // Initalize the PDO extension + // + PdoExtension = PdoGetExtension(ChildDevice); + + RtlZeroMemory(PdoExtension, sizeof(PDO_EXTENSION)); + + PdoExtension->FdoExtension = FdoGetExtension(_Device); + + PdoExtension->SerialNo = _SerialNo; + + + // + // Set PnP and Power capabilities for this child device. + // + WDF_DEVICE_PNP_CAPABILITIES_INIT(&PnpCaps); // Zeros this structure (note: WdfFalse is 0) + + // + // Bus driver sets this value to WdfFalse for this embedded device, which cannot + // be physically removed; its FDO must not set/override this. + // + PnpCaps.Removable = WdfFalse; + + // + // Bus driver sets this value to WdfTrue. FDO can override this value (when this irp is on its + // way up) if it determines that this device cannot be safely surprise (not orderly) removed + // without data loss. + // + PnpCaps.SurpriseRemovalOK = WdfTrue; + + PnpCaps.Address = _SerialNo; + PnpCaps.UINumber = _SerialNo; + + WdfDeviceSetPnpCapabilities(ChildDevice, &PnpCaps); + + WDF_DEVICE_POWER_CAPABILITIES_INIT(&PowerCaps); + + PowerCaps.DeviceD1 = WdfFalse; + PowerCaps.DeviceD2 = WdfTrue; + + PowerCaps.WakeFromD0 = WdfFalse; + PowerCaps.WakeFromD1 = WdfFalse; + PowerCaps.WakeFromD2 = WdfTrue; + PowerCaps.WakeFromD3 = WdfTrue; + + PowerCaps.DeviceState[PowerSystemWorking] = PowerDeviceD0; + PowerCaps.DeviceState[PowerSystemSleeping1] = PowerDeviceD2; + PowerCaps.DeviceState[PowerSystemSleeping2] = PowerDeviceD2; + PowerCaps.DeviceState[PowerSystemSleeping3] = PowerDeviceD2; + PowerCaps.DeviceState[PowerSystemHibernate] = PowerDeviceD3; + PowerCaps.DeviceState[PowerSystemShutdown] = PowerDeviceD3; + + PowerCaps.DeviceWake = PowerDeviceD2; // Lowest-powered Dx state to send wake signal to system + + WdfDeviceSetPowerCapabilities(ChildDevice, &PowerCaps); + + // + // Configure a default queue so that requests that are not + // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto + // other queues get dispatched here. + // + + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&QueueConfig, WdfIoQueueDispatchParallel); + + // + // Cannot be power managed queue (dispatch only at D0) as + // BthMini issues BthX DDI to get version and capabilities + // before enter D0. A deadlock occurs if this is power managed. + // + QueueConfig.PowerManaged = WdfFalse; + + QueueConfig.EvtIoDeviceControl = PdoIoQuDeviceControl; + + Status = WdfIoQueueCreate(ChildDevice, + &QueueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &Queue); + DoTrace(LEVEL_INFO, TFLAG_PNP, (" WdfIoQueueCreate (%!STATUS!)", Status)); + if (!NT_SUCCESS(Status)) { + goto Cleanup; + } + + // + // Add this device to the FDO's collection of children. + // After the child device is added to the static collection successfully, + // driver must call WdfPdoMarkMissing to get the device deleted. It + // shouldn't delete the child device directly by calling WdfObjectDelete. + // + Status = WdfFdoAddStaticChild(_Device, ChildDevice); + DoTrace(LEVEL_INFO, TFLAG_PNP, (" WdfFdoAddStaticChild (%!STATUS!)", Status)); + if (!NT_SUCCESS(Status)) { + goto Cleanup; + } + +Cleanup: + + // + // Call WdfDeviceInitFree if you encounter an error before the + // device is created. Once the device is created, framework + // NULLs the DeviceInit value. + // + if (!NT_SUCCESS(Status)) { + + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" -PdoCreate: exit %!STATUS!", Status)); + + if (DeviceInit != NULL) { + WdfDeviceInitFree(DeviceInit); + } + + if(ChildDevice) { + WdfObjectDelete(ChildDevice); + } + } + else + { + DoTrace(LEVEL_INFO, TFLAG_PNP, (" -PdoCreate: exit %!STATUS!", Status)); + } + + if (NULL != ContainerID.Buffer) { + RtlFreeUnicodeString(&ContainerID); + } + + return Status; +} + +NTSTATUS +PdoDevPrepareHardware( + _In_ WDFDEVICE _Device, + _In_ WDFCMRESLIST _ResourcesRaw, + _In_ WDFCMRESLIST _ResourcesTranslated + ) +/*++ +Routine Description: + + This PnP CB function take a refernce of its parent so it will not enter DxState while in S0Idle. + +Arguments: + + _Device - WDF Device object + + _ResourcesRaw - (Not referenced) + + _ResourcesTranslated - (Not referenced) + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + WDFDEVICE ParentDevice; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(_ResourcesRaw); + UNREFERENCED_PARAMETER(_ResourcesTranslated); + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+PdoDevPrepareHardware")); + + ParentDevice = WdfPdoGetParent(_Device); + + // + // Take a reference to avoid FDO to enter DxState in IdleS0 + // + Status = WdfDeviceStopIdle(ParentDevice, FALSE); + DoTrace(LEVEL_INFO, TFLAG_PNP, ("WdfDeviceStopIdle %!STATUS!", Status)); + + // + // Any failure Status code should be invesigated to ensure that the reference count is balanced. + // + NT_ASSERT(NT_SUCCESS(Status)); + + DoTrace(LEVEL_INFO, TFLAG_PNP, ("-PdoDevPrepareHardware")); + + return Status; +} + + +NTSTATUS +PdoDevReleaseHardware( + _In_ WDFDEVICE _Device, + _In_ WDFCMRESLIST _ResourcesTranslated + ) +/*++ +Routine Description: + + This PnP CB function release a refcount of its parent so it can enter DxState in S0Idle. + +Arguments: + + _Device - WDF Device object + + _ResourcesTranslated - (Not referenced) + +Return Value: + + NTSTATUS + +--*/ +{ + WDFDEVICE ParentDevice; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(_ResourcesTranslated); + + DoTrace(LEVEL_INFO, TFLAG_PNP,("+PdoDevReleaseHardware")); + + ParentDevice = WdfPdoGetParent(_Device); + + // + // Release a reference to allow FDO to enter DxState in IdleS0 + // + WdfDeviceResumeIdle(ParentDevice); + + return STATUS_SUCCESS; +} + + + +NTSTATUS +PdoDevD0Entry( + _In_ WDFDEVICE _Device, + _In_ WDF_POWER_DEVICE_STATE _PreviousState + ) +/*++ +Routine Description: + + This PnPPower CB function is invoked after device has entered D0 (working) state. + +Arguments: + + _Device - WDF Device object + + PreviousState - Previous device power state + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(_Device); + UNREFERENCED_PARAMETER(_PreviousState); + + DoTrace(LEVEL_INFO, TFLAG_UART, ("+PdoDevD0Entry")); + + // + // Can bring the Bluetooth function back to active state + // + + + DoTrace(LEVEL_INFO, TFLAG_UART, ("-PdoDevD0Entry %!STATUS!", Status)); + + return Status; +} + + +NTSTATUS +PdoDevD0Exit( + _In_ WDFDEVICE _Device, + _In_ WDF_POWER_DEVICE_STATE _TargetState + ) +/*++ +Routine Description: + + This PnP CB function is invoked when device has exited D0 (working) state. + +Arguments: + + _Device - WDF Device object + + _TargetState - Next device power state that it is about to enter + +Return Value: + + NTSTATUS + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(_Device); + UNREFERENCED_PARAMETER(_TargetState); + + DoTrace(LEVEL_INFO, TFLAG_UART, ("+PdoDevD0Exit: D0 -> D%d", _TargetState-WdfPowerDeviceD0)); + + // + // Can prepare the Bluetooth function to enter lower power device state + // + + + DoTrace(LEVEL_INFO, TFLAG_UART, ("-PdoDevD0Exit")); + + return STATUS_SUCCESS; +} + + +VOID +PdoDevDisableWakeAtBus( + _In_ WDFDEVICE _Device + ) +/*++ + +Routine Description: + + This framework callback routine performs bus-level operations that disable + the ability of one of the bus's devices to trigger a wake-up signal. + +Arguments: + + _Device - Framework device object + +Return Value: + + VOID + +--*/ +{ + // + // Do not mark this function pageable to potentially reduce power up time. + // + + DoTrace(LEVEL_INFO, TFLAG_POWER,("<==(D)== PdoDevDisableWakeAtBus")); + + // + // Device specific implementation to disarm for wake + // + DeviceDisableWakeControl(_Device); +} + +NTSTATUS +PdoDevEnableWakeAtBus( + _In_ WDFDEVICE _Device, + _In_ SYSTEM_POWER_STATE _PowerState + ) +/*++ + +Routine Description: + + This framework callback routine performs bus-level operations that enable + one of the bus's devices to trigger a wake-up signal. + +Arguments: + + _Device - Framework device object + + _PowerState - identifies the system power state that the system or device will wake from. + +Return Value: + + NTSTATUS + +--*/ +{ + // + // Do not mark this function pageable to potentially reduce power up time. + // + + DoTrace(LEVEL_INFO, TFLAG_POWER,("==(E)==> PdoDevEnableWakeAtBus from %S", + _PowerState == PowerSystemWorking ? L"S0" : L"Sx")); + + // + // Device specific implementation to arm for wake + // + return DeviceEnableWakeControl(_Device, _PowerState); +} + +VOID +PdoIoQuDeviceControl( + _In_ WDFQUEUE _Queue, + _In_ WDFREQUEST _Request, + _In_ size_t _OutputBufferLength, + _In_ size_t _InputBufferLength, + _In_ ULONG _IoControlCode + ) +/*++ + +Routine Description: + + This routine is the dispatch routine for device control requests. This routine can be invoke + at the DISPATCH level from BthPort/mini. + +Arguments: + + _Queue - Handle to the framework queue object that is associated + with the I/O request. + _Request - Handle to a framework request object. + + _OutputBufferLength - length of the request's output buffer, + if an output buffer is available. + _InputBufferLength - length of the request's input buffer, + if an input buffer is available. + + _IoControlCode - the driver-defined or system-defined I/O control code + (IOCTL) that is associated with the request. + +Return Value: + + VOID + +--*/ +{ + WDFDEVICE Device = NULL; + NTSTATUS Status = STATUS_INVALID_PARAMETER; + WDF_REQUEST_FORWARD_OPTIONS ForwardOptions; + WDFDEVICE ParentDevice; + ULONG ControlCode = (_IoControlCode & 0x00003ffc) >> 2; + + UNREFERENCED_PARAMETER(_OutputBufferLength); + UNREFERENCED_PARAMETER(_InputBufferLength); + + DoTrace(LEVEL_INFO, TFLAG_IOCTL,("+IoDeviceControl - InBufLen:%d, OutBufLen:%d", + (ULONG) _InputBufferLength, (ULONG) _OutputBufferLength)); + + switch (_IoControlCode) { + case IOCTL_BTHX_GET_VERSION: + case IOCTL_BTHX_SET_VERSION: + case IOCTL_BTHX_QUERY_CAPABILITIES: + case IOCTL_BTHX_WRITE_HCI: + case IOCTL_BTHX_READ_HCI: + Device = WdfIoQueueGetDevice(_Queue); + WDF_REQUEST_FORWARD_OPTIONS_INIT(&ForwardOptions); + ForwardOptions.Flags = WDF_REQUEST_FORWARD_OPTION_SEND_AND_FORGET; + ParentDevice = WdfPdoGetParent(Device); + + // + // Forward known IOCTLs to FDO to process + // + Status = WdfRequestForwardToParentDeviceIoQueue(_Request, + WdfDeviceGetDefaultQueue(ParentDevice), + &ForwardOptions); + break; + + default: + // + // Complete this unexptected IOCTL with default STATUS_INVALID_PARAMETER. + // + DoTrace(LEVEL_ERROR, TFLAG_IOCTL,("Unexpected IOCTL_(0x%x, Func %d)", _IoControlCode, ControlCode)); + break; + } + + if (!NT_SUCCESS(Status)){ + DoTrace(LEVEL_ERROR, TFLAG_IOCTL,(" IOCTL_(0x%x, Func %d) failed %!STATUS!", _IoControlCode, ControlCode, Status)); + WdfRequestComplete(_Request, Status); + return; + } + + return; +} + + diff --git a/bluetooth/serialhcibus/public.h b/bluetooth/serialhcibus/public.h new file mode 100644 index 00000000..5ef495d0 --- /dev/null +++ b/bluetooth/serialhcibus/public.h @@ -0,0 +1,47 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + public.h + +Abstract: + + This module contains the common declarations shared by driver + and user applications. + +Environment: + + user and kernel + +--*/ + +#ifndef __PUBLIC_H +#define __PUBLIC_H + +#ifdef DEFINE_GUID + +// +// Vendor: Define a device interface GUID for Bluetooth Radion On/off. +// Need to use uuidgen to create your own GUID instead of reusing this one. +// +DEFINE_GUID(GUID_DEVINTERFACE_BLUETOOTH_RADIO_ONOFF_VENDOR_SPECIFIC, + 0x98899865, 0x63de, 0x427b, 0x84, 0x77, 0x6c, 0xb7, 0x4d, 0x9f, 0xf2, 0xa7); +//{98899865-63de-427b-8477-6cb74d9ff2a7} + +#endif // #ifdef DEFINE_GUID + + +// +// IOCTL definitions to support Radio on/off +// +#define FILE_DEVICE_BUSENUM FILE_DEVICE_BUS_EXTENDER +#define BUSENUM_IOCTL(id, access) CTL_CODE(FILE_DEVICE_BUSENUM, \ + (id), \ + METHOD_BUFFERED, \ + access) + +#define IOCTL_BUSENUM_SET_RADIO_ONOFF_VENDOR_SPECFIC BUSENUM_IOCTL(0x1, FILE_WRITE_DATA) + +#endif diff --git a/bluetooth/serialhcibus/serialhcibus.sln b/bluetooth/serialhcibus/serialhcibus.sln new file mode 100644 index 00000000..06bf3320 --- /dev/null +++ b/bluetooth/serialhcibus/serialhcibus.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0 +MinimumVisualStudioVersion = 12.0 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SerialBusWdk", "WDK\SerialBusWdk.vcxproj", "{B126E3A5-91B5-41B5-9B40-719D474AA513}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B126E3A5-91B5-41B5-9B40-719D474AA513}.Debug|Win32.ActiveCfg = Debug|Win32 + {B126E3A5-91B5-41B5-9B40-719D474AA513}.Debug|Win32.Build.0 = Debug|Win32 + {B126E3A5-91B5-41B5-9B40-719D474AA513}.Release|Win32.ActiveCfg = Release|Win32 + {B126E3A5-91B5-41B5-9B40-719D474AA513}.Release|Win32.Build.0 = Release|Win32 + {B126E3A5-91B5-41B5-9B40-719D474AA513}.Debug|x64.ActiveCfg = Debug|x64 + {B126E3A5-91B5-41B5-9B40-719D474AA513}.Debug|x64.Build.0 = Debug|x64 + {B126E3A5-91B5-41B5-9B40-719D474AA513}.Release|x64.ActiveCfg = Release|x64 + {B126E3A5-91B5-41B5-9B40-719D474AA513}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal |
