diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ee5385 --- /dev/null +++ b/.gitignore @@ -0,0 +1,362 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6ef3ee3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +The Microsoft Public License (MS-PL) +Copyright (c) 2015 Microsoft + +This license governs use of the accompanying software. If you use the software, you + accept this license. If you do not accept the license, do not use the software. + +1. Definitions + The terms "reproduce," "reproduction," "derivative works," and "distribution" have the + same meaning here as under U.S. copyright law. + A "contribution" is the original software, or any additions or changes to the software. + A "contributor" is any person that distributes its contribution under this license. + "Licensed patents" are a contributor's patent claims that read directly on its contribution. + +2. Grant of Rights + (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + +3. Conditions and Limitations + (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. + (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. + (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. + (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a62c5ad --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Cypress Bluetooth over UART Transport Driver for Windows +This project implements a Bluetooth HCI over UART transport (H4) driver for various Cypress/Broadcom Wi-Fi + BT combo chips. + +## Supported hardware +The initialization sequence is virtually the same on all these combo chips, so as long as the proper firmware is provided, the driver should bring-up the device in a fully operational state. + +We currently provide firmware/support for the following chips: +* CYW43455 (BCM4345C0.hcd) - extensively tested on a Raspberry Pi 4 + +**Note:** since this driver uses the H4 protocol for communication, the UART link must support hardware flow control to prevent packet loss. + +## Driver configuration +The registry settings can be found under `HKLM\System\CurrentControlSet\Services\cywbtserialbus\Parameters`: +* `BaudRate` [default=460800] - the UART baud rate for communication between the host and the BT device after firmware download + +* `SkipFwDownload` [default=0] - use the existing ROM firmware (with limited functionality) + +## Credits +This driver is based on the [serialhcibus sample](https://github.com/microsoft/Windows-driver-samples/tree/master/bluetooth/serialhcibus) provided by Microsoft. + +The firmware files come from: https://github.com/RPi-Distro/bluez-firmware/tree/master/broadcom diff --git a/cywbtserialbus.sln b/cywbtserialbus.sln new file mode 100644 index 0000000..99b39b6 --- /dev/null +++ b/cywbtserialbus.sln @@ -0,0 +1,47 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30711.63 +MinimumVisualStudioVersion = 12.0 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cywbtserialbus", "src\vendor\cywbtserialbus.vcxproj", "{F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Debug|ARM.ActiveCfg = Debug|ARM + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Debug|ARM.Build.0 = Debug|ARM + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Debug|ARM.Deploy.0 = Debug|ARM + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Debug|ARM64.Build.0 = Debug|ARM64 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Debug|Win32.ActiveCfg = Debug|Win32 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Debug|Win32.Build.0 = Debug|Win32 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Debug|x64.ActiveCfg = Debug|x64 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Debug|x64.Build.0 = Debug|x64 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Release|ARM.ActiveCfg = Release|ARM + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Release|ARM.Build.0 = Release|ARM + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Release|ARM.Deploy.0 = Release|ARM + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Release|ARM64.ActiveCfg = Release|ARM64 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Release|ARM64.Build.0 = Release|ARM64 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Release|ARM64.Deploy.0 = Release|ARM64 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Release|Win32.ActiveCfg = Release|Win32 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Release|Win32.Build.0 = Release|Win32 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Release|x64.ActiveCfg = Release|x64 + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C17393BB-ACC0-4B0B-B697-9A9DA11D95DC} + EndGlobalSection +EndGlobal diff --git a/src/Fdo.c b/src/Fdo.c new file mode 100644 index 0000000..dd04ca7 --- /dev/null +++ b/src/Fdo.c @@ -0,0 +1,2142 @@ +/*++ + +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 +#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 ); + + WdfObjectDelete(_WorkItem); + + 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; + } + + Status = WdfSpinLockCreate(&Attributes, &FdoExtension->QueueAccessLock); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_PNP, (" WdfSpinLockCreate failed %!STATUS!", Status)); + goto Exit; + } + +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"Invalid 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; + 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) + { + WdfSpinLockAcquire(FdoExtension->QueueAccessLock); + // Queue the new request to preserve sequential order + Status = WdfRequestForwardToIoQueue(_Request, FdoExtension->ReadEventQueue); + if (NT_SUCCESS(Status)) + { + InterlockedIncrement(&FdoExtension->EventQueueCount); + InterlockedIncrement(&FdoExtension->CntEventReq); + } + WdfSpinLockRelease(FdoExtension->QueueAccessLock); + + if (NT_SUCCESS(Status)) + { + Status = ReadRequestComplete(FdoExtension, + HciPacketEvent, + 0, NULL, + FdoExtension->ReadEventQueue, + &FdoExtension->EventQueueCount, + &FdoExtension->ReadEventList, + &FdoExtension->EventListCount); + } + + } + else if (PacketType == HciPacketAclData) + { + WdfSpinLockAcquire(FdoExtension->QueueAccessLock); + // Queue the new request to preserve sequential order + Status = WdfRequestForwardToIoQueue(_Request, FdoExtension->ReadDataQueue); + if (NT_SUCCESS(Status)) + { + InterlockedIncrement(&FdoExtension->DataQueueCount); + InterlockedIncrement(&FdoExtension->CntReadDataReq); + } + WdfSpinLockRelease(FdoExtension->QueueAccessLock); + + 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/src/Io.h b/src/Io.h new file mode 100644 index 0000000..76abb82 --- /dev/null +++ b/src/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 + +// +// 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 + + +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/src/driver.c b/src/driver.c new file mode 100644 index 0000000..7a9264c --- /dev/null +++ b/src/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_CYPRESSBTUART; + + 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/src/driver.h b/src/driver.h new file mode 100644 index 0000000..280705f --- /dev/null +++ b/src/driver.h @@ -0,0 +1,570 @@ +/*++ + +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 +#include + +#define NTSTRSAFE_LIB +#include + +#define INITGUID +#include +#include +#include // Constants and types for access Serial device + +#include // 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_CYPRESSBTUART 'CYBT' // 'CY`press 'B'lue 'T'ooth +#undef ExAllocatePool +#define ExAllocatePool(type, size) \ + ExAllocatePoolWithTag(type, size, POOLTAG_CYPRESSBTUART) + +// +// 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; + +// +// Reset recovery support capability. Multiple implementations are provided to choose from and +// customize as appropriate. +// +typedef enum _RESET_RECOVERY_SUPPORT_TYPE { + ResetRecoveryTypeNone = 0, // Reset-recovery is not implemented in the driver. This may be + // used if the stack does not support reset-recovery at all, or + // if ACPI firmware already implements reset-recovery in the context + // of the child device stack (i.e., the stack that loads on the + // PDO created by this driver). In that case, the bus driver does + // not need any additional code to support reset-recovery. However, + // it does need to support GUID_REENUMERATE_SELF_INTERFACE_STANDARD, + // which is most easily achieved by using WDF dynamic-enumeration + // for creating PDOs. + + ResetRecoveryTypeParentResetInterface = 1, // Delegate to GUID_DEVICE_RESET_INTERFACE_STANDARD + // support in the parent stack. The parent stack, i.e., the + // stack on which this driver loads as the FDO, must have + // support for GUID_DEVICE_RESET_INTERFACE_STANDARD. This is + // typically provided by the ACPI bus driver, if the ACPI device + // supports D3Cold (_PR3) or a power resource for reset (_PRR + + // _RST). See the documentation of + // GUID_DEVICE_RESET_INTERFACE_STANDARD for more information. + + ResetRecoveryTypeDriverImplemented = 2, // Reset recovery is implemented in the driver. + // Hardware-specific techniques are used to power-cycle the controller. + +} RESET_RECOVERY_TYPE; + +// +// Driver author may choose whichever reset-recovery strategy works best for their platform. +// +#define SUPPORTED_RESET_RECOVERY_TYPE ResetRecoveryTypeDriverImplemented + +#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 + // + WDFSPINLOCK 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; + + // + // Type of reset-recovery support implemented. + // + RESET_RECOVERY_TYPE ResetRecoveryType; + +} 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 + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +PdoResetHandlerDynamic(_In_ PVOID _InterfaceContext, + _In_ DEVICE_RESET_TYPE _ResetType, + _In_ ULONG _Flags, + _In_opt_ PVOID _ResetParameters); + +EVT_WDF_CHILD_LIST_CREATE_DEVICE FdoEvtDeviceListCreatePdo; + +_IRQL_requires_max_(PASSIVE_LEVEL) +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; + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +PdoResetHandler(_In_ PVOID _InterfaceContext, + _In_ DEVICE_RESET_TYPE _ResetType, + _In_ ULONG _Flags, + _In_opt_ PVOID _ResetParameters); + +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); + +VOID +DeviceDoPLDR(_In_ WDFDEVICE _Fdo); + +#endif diff --git a/src/io.c b/src/io.c new file mode 100644 index 0000000..16b11d2 --- /dev/null +++ b/src/io.c @@ -0,0 +1,1569 @@ +/*++ + +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, (" 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)); + + WdfSpinLockAcquire(FdoExtension->QueueAccessLock); + // 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); + } + WdfSpinLockRelease(FdoExtension->QueueAccessLock); + + 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 through)")); + + 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 - ")); + 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 + +--*/ +{ + 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. + // + WdfSpinLockAcquire(_FdoExtension->QueueAccessLock); + + 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")); + } + } + + WdfSpinLockRelease(_FdoExtension->QueueAccessLock); + + 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) + (size_t)_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; + + 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; + + WdfSpinLockAcquire(FdoExtension->QueueAccessLock); + PacketEntry = (PHCI_PACKET_ENTRY)RemoveHeadList(&FdoExtension->ReadEventList); + InterlockedDecrement(&FdoExtension->EventListCount); + WdfSpinLockRelease(FdoExtension->QueueAccessLock); + + if (PacketEntry) + { + ExFreePool(PacketEntry); + PacketEntry = NULL; + } + } + NT_ASSERT(FdoExtension->EventListCount == 0); + + while(!IsListEmpty(&FdoExtension->ReadDataList)) + { + PHCI_PACKET_ENTRY PacketEntry; + + WdfSpinLockAcquire(FdoExtension->QueueAccessLock); + PacketEntry = (PHCI_PACKET_ENTRY)RemoveHeadList(&FdoExtension->ReadDataList); + InterlockedDecrement(&FdoExtension->DataListCount); + WdfSpinLockRelease(FdoExtension->QueueAccessLock); + + 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/src/pdo.c b/src/pdo.c new file mode 100644 index 0000000..9033483 --- /dev/null +++ b/src/pdo.c @@ -0,0 +1,1180 @@ +/*++ + +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) +#pragma alloc_text(PAGE, PdoResetHandler) +#ifdef DYNAMIC_ENUM +#pragma alloc_text(PAGE, PdoCreateDynamic) +#pragma alloc_text(PAGE, PdoResetHandlerDynamic) +#endif // DYNAMIC_ENUM +#endif // ALLOC_PRAGMA + +#define MAX_ID_LEN 80 + +#ifdef DYNAMIC_ENUM + +_Use_decl_annotations_ +NTSTATUS +PdoResetHandlerDynamic( + PVOID _InterfaceContext, + DEVICE_RESET_TYPE _ResetType, + ULONG _Flags, + PVOID _ResetParameters + ) +/*++ + +Routine Description: + + This is the reset handler invoked by a driver that queries our GUID_DEVICE_RESET_INTERFACE_STANDARD interface. + This is the version used if PDOs are created using WDF dynamic enumeration. + +Arguments: + + _InterfaceContext - This is expected to be a PPDO_EXTENSION. + + _ResetType - This is expected to be PlatformLevelDeviceReset because that is all we support. + + _Flags - Unused, because no flags are defined currently. + + _ResetParameters - Unused, because it is only used for FunctionLevelDeviceReset. + +Return Value: + + NTSTATUS code. + +--*/ +{ + PPDO_EXTENSION PdoExtension = (PPDO_EXTENSION) _InterfaceContext; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(_Flags); + UNREFERENCED_PARAMETER(_ResetParameters); + + if (_ResetType != PlatformLevelDeviceReset) { + return STATUS_NOT_SUPPORTED; + } + + DeviceDoPLDR(PdoExtension->FdoExtension->WdfDevice); + + // + // The bus driver is expected to cause the device to be surprise removed as part of PLDR. For + // many hardware buses, this happens naturally, but for a software bus like us, we need to do it + // ourselves. WDF dynamic enumeration makes this convenient because it already supports + // GUID_REENUMERATE_SELF_INTERFACE_STANDARD, so we can simply call WdfDeviceSetFailed to + // re-enumerate the device. + // + + WdfDeviceSetFailed((WDFDEVICE) WdfObjectContextGetObject(PdoExtension), WdfDeviceFailedAttemptRestart); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +NTSTATUS +PdoCreateDynamic( + WDFDEVICE _Device, + PWDFDEVICE_INIT _DeviceInit, + PWCHAR _HardwareIds, + 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; + WDF_QUERY_INTERFACE_CONFIG DeviceResetInterfaceConfig; + DEVICE_RESET_INTERFACE_STANDARD ResetInterface; + + 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"cywbtserialbus_%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; + + PdoExtension->ResetRecoveryType = SUPPORTED_RESET_RECOVERY_TYPE; + + // + // 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; + } + + if (PdoExtension->ResetRecoveryType == ResetRecoveryTypeParentResetInterface) { + + // + // Instruct WDF to simply forward a request for the reset interface to the parent stack, so + // that it reaches the ACPI bus/filter driver. That way when the reset interface is invoked, + // it is the parent stack that gets reset and re-enumerated. + // + + WDF_QUERY_INTERFACE_CONFIG_INIT(&DeviceResetInterfaceConfig, NULL, &GUID_DEVICE_RESET_INTERFACE_STANDARD, NULL); + DeviceResetInterfaceConfig.SendQueryToParentStack = TRUE; + + Status = WdfDeviceAddQueryInterface(ChildDevice, &DeviceResetInterfaceConfig); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP, ("WdfDeviceAddQueryInterface failed, %!STATUS!", Status)); + goto Cleanup; + } + + } else if (PdoExtension->ResetRecoveryType == ResetRecoveryTypeDriverImplemented) { + + RtlZeroMemory(&ResetInterface, sizeof(ResetInterface)); + ResetInterface.Size = sizeof(ResetInterface); + ResetInterface.Version = DEVICE_RESET_INTERFACE_VERSION; + ResetInterface.Context = PdoExtension; + + // + // Since this interface is expected to be used only by drivers in the same stack, reference + // counting is not required. If there is an expectation that drivers may query for the + // interface using a remote I/O target to this stack (unusual for this interface), reference + // counting must be implemented. + // + ResetInterface.InterfaceReference = WdfDeviceInterfaceReferenceNoOp; + ResetInterface.InterfaceDereference = WdfDeviceInterfaceDereferenceNoOp; + + ResetInterface.SupportedResetTypes = (1 << PlatformLevelDeviceReset); + ResetInterface.DeviceReset = PdoResetHandlerDynamic; + + WDF_QUERY_INTERFACE_CONFIG_INIT(&DeviceResetInterfaceConfig, (PINTERFACE) &ResetInterface, &GUID_DEVICE_RESET_INTERFACE_STANDARD, NULL); + Status = WdfDeviceAddQueryInterface(ChildDevice, &DeviceResetInterfaceConfig); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_PNP, ("WdfDeviceAddQueryInterface failed, %!STATUS!", 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 + +_Use_decl_annotations_ +NTSTATUS +PdoResetHandler( + PVOID _InterfaceContext, + DEVICE_RESET_TYPE _ResetType, + ULONG _Flags, + PVOID _ResetParameters +) +/*++ + +Routine Description: + + This is the reset handler invoked by a driver that queries our GUID_DEVICE_RESET_INTERFACE_STANDARD interface. + This is the version used if PDOs are created using WDF static enumeration. + +Arguments: + + _InterfaceContext - This is expected to be a PPDO_EXTENSION. + + _ResetType - This is expected to be PlatformLevelDeviceReset because that is all we support. + + _Flags - Unused, because no flags are defined currently. + + _ResetParameters - Unused, because it is only used for FunctionLevelDeviceReset. + +Return Value: + + NTSTATUS code. + +--*/ +{ + PPDO_EXTENSION PdoExtension = (PPDO_EXTENSION) _InterfaceContext; + WDFDEVICE Fdo = PdoExtension->FdoExtension->WdfDevice; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(_Flags); + UNREFERENCED_PARAMETER(_ResetParameters); + + if (_ResetType != PlatformLevelDeviceReset) { + return STATUS_NOT_SUPPORTED; + } + + DeviceDoPLDR(Fdo); + + // + // The bus driver is expected to cause the device to be surprise removed as part of PLDR. For + // many hardware buses, this happens naturally, but for a software bus like us, we need to do it + // ourselves. + // + + FdoRemoveOneChildDevice(Fdo, BLUETOOTH_FUNC_IDS); + FdoCreateOneChildDevice(Fdo, BT_PDO_HARDWARE_IDS, BLUETOOTH_FUNC_IDS); + + return STATUS_SUCCESS; +} + +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; + WDF_QUERY_INTERFACE_CONFIG DeviceResetInterfaceConfig; + DEVICE_RESET_INTERFACE_STANDARD ResetInterface; + + 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"cywbtserialbus_%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; + + PdoExtension->ResetRecoveryType = SUPPORTED_RESET_RECOVERY_TYPE; + + // + // 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; + } + + if (PdoExtension->ResetRecoveryType == ResetRecoveryTypeParentResetInterface) { + + // + // Instruct WDF to simply forward a request for the reset interface to the parent stack, so + // that it reaches the ACPI bus/filter driver. That way when the reset interface is invoked, + // it is the parent stack that gets reset and re-enumerated. + // + + WDF_QUERY_INTERFACE_CONFIG_INIT(&DeviceResetInterfaceConfig, NULL, &GUID_DEVICE_RESET_INTERFACE_STANDARD, NULL); + DeviceResetInterfaceConfig.SendQueryToParentStack = TRUE; + + Status = WdfDeviceAddQueryInterface(ChildDevice, &DeviceResetInterfaceConfig); + if (!NT_SUCCESS(Status)) { + DoTrace(LEVEL_ERROR, TFLAG_PNP, ("WdfDeviceAddQueryInterface failed, %!STATUS!", Status)); + goto Cleanup; + } + + } else if (PdoExtension->ResetRecoveryType == ResetRecoveryTypeDriverImplemented) { + + RtlZeroMemory(&ResetInterface, sizeof(ResetInterface)); + ResetInterface.Size = sizeof(ResetInterface); + ResetInterface.Version = DEVICE_RESET_INTERFACE_VERSION; + ResetInterface.Context = PdoExtension; + + // + // Since this interface is expected to be used only by drivers in the same stack, reference + // counting is not required. If there is an expectation that drivers may query for the + // interface using a remote I/O target to this stack (unusual for this interface), reference + // counting must be implemented. + // + ResetInterface.InterfaceReference = WdfDeviceInterfaceReferenceNoOp; + ResetInterface.InterfaceDereference = WdfDeviceInterfaceDereferenceNoOp; + + ResetInterface.SupportedResetTypes = (1 << PlatformLevelDeviceReset); + ResetInterface.DeviceReset = PdoResetHandler; + + WDF_QUERY_INTERFACE_CONFIG_INIT(&DeviceResetInterfaceConfig, (PINTERFACE) &ResetInterface, &GUID_DEVICE_RESET_INTERFACE_STANDARD, NULL); + Status = WdfDeviceAddQueryInterface(ChildDevice, &DeviceResetInterfaceConfig); + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_PNP, ("WdfDeviceAddQueryInterface failed, %!STATUS!", 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/src/public.h b/src/public.h new file mode 100644 index 0000000..a335a9a --- /dev/null +++ b/src/public.h @@ -0,0 +1,46 @@ +/*++ + +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 + +// +// Device interface GUID for Bluetooth Radio On/off. +// +DEFINE_GUID(GUID_DEVINTERFACE_BLUETOOTH_RADIO_ONOFF_VENDOR_SPECIFIC, + 0xa8357a1d, 0xc311, 0x49d6, 0x94, 0x3e, 0x21, 0x81, 0x62, 0x3a, 0x1f, 0xef); +//{a8357a1d-c311-49d6-943e-2181623a1fef} + +#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/src/vendor/Debugdef.h b/src/vendor/Debugdef.h new file mode 100644 index 0000000..3c0e108 --- /dev/null +++ b/src/vendor/Debugdef.h @@ -0,0 +1,49 @@ +#ifndef __DEBUGDEF_H__ +#define __DEBUGDEF_H__ + +// +// Device control Guid +// {356c5a06-4c26-4b43-a8bb-d209ecd93cde} +// +#define WPP_CONTROL_GUIDS \ + WPP_DEFINE_CONTROL_GUID(cywbtserialbus,(356c5a06,4c26,4b43,a8bb,d209ecd93cde), \ + 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/src/vendor/cywbtserialbus.inx b/src/vendor/cywbtserialbus.inx new file mode 100644 index 0000000..31053c9 Binary files /dev/null and b/src/vendor/cywbtserialbus.inx differ diff --git a/src/vendor/cywbtserialbus.vcxproj b/src/vendor/cywbtserialbus.vcxproj new file mode 100644 index 0000000..89f70fa --- /dev/null +++ b/src/vendor/cywbtserialbus.vcxproj @@ -0,0 +1,369 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {F8EFDBF2-B339-413F-94F5-AEB55DCCBA01} + $(MSBuildProjectName) + 1 + Debug + Win32 + {680EFDDE-117B-443A-9A50-E8A7F919AA4F} + cywbtserialbus + $(LatestTargetPlatformVersion) + + + + Windows10 + False + Universal + KMDF + WindowsKernelModeDriver10.0 + Driver + + + Windows10 + False + Universal + KMDF + WindowsKernelModeDriver10.0 + Driver + + + Windows10 + True + Universal + KMDF + WindowsKernelModeDriver10.0 + Driver + + + Windows10 + True + Universal + KMDF + WindowsKernelModeDriver10.0 + Driver + + + Windows10 + False + Universal + KMDF + WindowsKernelModeDriver10.0 + Driver + + + Windows10 + False + Universal + KMDF + WindowsKernelModeDriver10.0 + Driver + + + Windows10 + True + Universal + KMDF + WindowsKernelModeDriver10.0 + Driver + + + Windows10 + True + Universal + KMDF + WindowsKernelModeDriver10.0 + Driver + + + + $(IntDir) + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + true + DoTrace(LEVEL,FLAG,(MSG,...)) + + + true + true + DoTrace(LEVEL,FLAG,(MSG,...)) + + + + cywbtserialbus + + + cywbtserialbus + + + cywbtserialbus + + + cywbtserialbus + + + cywbtserialbus + + + cywbtserialbus + + + cywbtserialbus + + + cywbtserialbus + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + true + Level4 + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + true + Level4 + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + true + Level4 + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + true + Level4 + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + true + Level4 + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + true + Level4 + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + true + Level4 + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + true + Level4 + + + + + %(AdditionalIncludeDirectories);..\;. + %(PreprocessorDefinitions);RESHUB_USE_HELPER_ROUTINES + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\Ntstrsafe.lib + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/vendor/device.c b/src/vendor/device.c new file mode 100644 index 0000000..be011bd --- /dev/null +++ b/src/vendor/device.c @@ -0,0 +1,1742 @@ +/*++ + +Copyright (c) 2020 Mario Bãlãnicã. All Rights Reserved. +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" +#include + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, DeviceQueryDeviceParameters) +#endif + +// +// Device registry value names +// +#define STR_REG_BAUDRATE L"BaudRate" +#define STR_REG_SKIP_FW_DOWNLOAD L"SkipFwDownload" +#define STR_REG_FW_DIRECTORY L"FwDirectory" + +// +// Default device settings +// +#define DEFAULT_BAUD_RATE 115200 +#define DEFAULT_SKIP_FW_DOWNLOAD 0 +#define DEFAULT_FW_DIRECTORY L"\\SystemRoot\\System32\\drivers\\" + +typedef struct _DEVICE_CONFIG_PARAMETERS +{ + ULONG BaudRate; + ULONG SkipFwDownload; + UNICODE_STRING FwDirectory; +} DEVICE_CONFIG_PARAMETERS, * PDEVICE_CONFIG_PARAMETERS; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONFIG_PARAMETERS, GetDeviceConfigParameters) + +// +// HCI helper definitions +// +#define HCI_COMMAND_SUCCESS 0 +#define BCM_HCI_MIN_EVENT_SIZE 6 + +typedef struct _BCM_HCI_VERBOSE_CONFIG +{ + UCHAR ChipId; + UCHAR TargetId; + USHORT BuildBase; + USHORT BuildNum; +} BCM_HCI_VERBOSE_CONFIG, * PBCM_HCI_VERBOSE_CONFIG; + +#define BCM_ENTER_FW_DOWNLOAD_MODE_DELAY_MICROS 50000 // 50 ms +#define BCM_FW_DOWNLOAD_COMPLETE_DELAY_MICROS 250000 // 250 ms + +#define BCM_INITIAL_LOCAL_NAME_MAX_LENGTH 15 +#define BCM_INITIAL_LOCAL_NAME_PREFIX L"BCM" +#define BCM_FW_EXTENSION L".hcd" + +VOID +SleepMicroseconds( + _In_ ULONG _Time +) +/*++ + +Routine Description: + + This function delays the execution thread for x microseconds. + +Arguments: + + _Time - microseconds to delay + +Return Value: + + None + +--*/ +{ + LARGE_INTEGER Interval; + Interval.QuadPart = _Time * -10LL; + + KeDelayExecutionThread(KernelMode, FALSE, &Interval); +} + +NTSTATUS +AppendStringsToString( + _Inout_ PUNICODE_STRING _BaseString, + _In_ PUNICODE_STRING* _Strings, + _In_ ULONG _NumberOfStrings +) +/*++ + +Routine Description: + + This function appends an array of strings to a string. + +Arguments: + + _BaseString - the resulting string + + _Strings - an array of strings to get appended to _BaseString + + _NumberOfStrings + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + + if (_BaseString == NULL || _Strings == NULL || _NumberOfStrings == 0) + { + Status = STATUS_INVALID_PARAMETER; + goto Done; + } + + for (ULONG Index = 0; Index < _NumberOfStrings; ++Index) + { + Status = RtlAppendUnicodeStringToString(_BaseString, _Strings[Index]); + + if (!NT_SUCCESS(Status)) + goto Done; + } + +Done: + return Status; +} + +NTSTATUS +BuildFirmwarePath( + _Inout_ PUNICODE_STRING _Path, + _In_ PUNICODE_STRING _PathDirectory, + _In_ PUNICODE_STRING _LocalName +) +/*++ + +Routine Description: + + This function builds the full path to the HCD firmware. + +Arguments: + + _Path - the resulting path + + _PathDirectory - directory where the firmware is located (must end with a backslash) + + _LocalName - the name of the chip (will be appended to _PathDirectory) + +Return Value: + + NTSTATUS + + Note: in case of success, a memory buffer that holds the path string is allocated. + Call ExFreePool on the Buffer member to free it. + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + UNICODE_STRING FirmwareExt; + ULONG FirmwarePathMaxLength; + PWCHAR FirmwarePathBuffer; + PUNICODE_STRING PathComponents[3]; + + RtlUnicodeStringInit(&FirmwareExt, BCM_FW_EXTENSION); + + PathComponents[0] = _PathDirectory; + PathComponents[1] = _LocalName; + PathComponents[2] = &FirmwareExt; + + FirmwarePathMaxLength = _PathDirectory->Length + + _LocalName->MaximumLength + + FirmwareExt.Length; + + if (FirmwarePathMaxLength > USHRT_MAX) + { + Status = STATUS_NAME_TOO_LONG; + goto Done; + } + + FirmwarePathBuffer = ExAllocatePool(NonPagedPool, FirmwarePathMaxLength); + + if (FirmwarePathBuffer == NULL) + { + Status = STATUS_INSUFFICIENT_RESOURCES; + goto Done; + } + + RtlInitEmptyUnicodeString(_Path, FirmwarePathBuffer, (USHORT)FirmwarePathMaxLength); + + Status = AppendStringsToString(_Path, + PathComponents, + sizeof(PathComponents) / sizeof(PUNICODE_STRING)); + + if (!NT_SUCCESS(Status)) + { + ExFreePool(FirmwarePathBuffer); + goto Done; + } + +Done: + return Status; +} + +NTSTATUS +SendIoctlToIoTargetSync( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest, + _In_ ULONG _IoControlCode, + _In_opt_ PVOID _InputBuffer, + _In_opt_ ULONG _InputBufferLength, + _Inout_opt_ PVOID _OutputBuffer, + _In_opt_ ULONG _OutputBufferLength, + _Out_opt_ PULONG_PTR _BytesReturned +) +/*++ + +Routine Description: + + This function synchronously sends an IOCTL to an I/O target with timeout. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + + _IoControlCode - the IOCTL code + + _InputBuffer - (optional) + _InputBufferLength - (optional) the size of the _InputBuffer + + _OutputBuffer - (optional) + _OutputBufferLength - (optional) the size of the _OutputBuffer + + _BytesReturned - (optional) the total count of bytes returned by the device + Depending on the device driver, a write operation can be successfully made with 0 bytes returned. + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + WDF_REQUEST_REUSE_PARAMS RequestReuseParams; + WDF_REQUEST_SEND_OPTIONS RequestOptions; + WDF_MEMORY_DESCRIPTOR InputMemoryDescriptor; + WDF_MEMORY_DESCRIPTOR OutputMemoryDescriptor; + BOOLEAN HasInputBuffer = FALSE; + BOOLEAN HasOutputBuffer = FALSE; + ULONG_PTR BytesReturned = 0; + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+SendIoctlToIoTargetSync")); + + if (_ReusableRequest != NULL) + { + WDF_REQUEST_REUSE_PARAMS_INIT(&RequestReuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_SUCCESS); + Status = WdfRequestReuse(_ReusableRequest, &RequestReuseParams); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfRequestReuse failed %!STATUS!", Status)); + goto Done; + } + } + + WDF_REQUEST_SEND_OPTIONS_INIT(&RequestOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT); + WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&RequestOptions, WDF_REL_TIMEOUT_IN_SEC(MAX_WRITE_TIMEOUT_IN_SEC)); + + if (_InputBuffer != NULL && _InputBufferLength > 0) + { + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&InputMemoryDescriptor, + _InputBuffer, + _InputBufferLength); + + HasInputBuffer = TRUE; + } + + if (_OutputBuffer != NULL && _OutputBufferLength > 0) + { + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&OutputMemoryDescriptor, + _OutputBuffer, + _OutputBufferLength); + + HasOutputBuffer = TRUE; + } + + Status = WdfIoTargetSendIoctlSynchronously(_IoTargetSerial, + _ReusableRequest, + _IoControlCode, + HasInputBuffer ? &InputMemoryDescriptor : NULL, + HasOutputBuffer ? &OutputMemoryDescriptor : NULL, + &RequestOptions, + &BytesReturned); + + if (NT_SUCCESS(Status)) + { + DoTrace(LEVEL_INFO, TFLAG_IO, (" WdfIoTargetSendIoctlSynchronously succeeded: %d bytes returned", + (ULONG)BytesReturned)); + + if (_BytesReturned != NULL) + *_BytesReturned = BytesReturned; + } + else + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfIoTargetSendIoctlSynchronously failed %!STATUS!", Status)); + +Done: + DoTrace(LEVEL_INFO, TFLAG_IO, ("-SendIoctlToIoTargetSync %!STATUS!", Status)); + return Status; +} + +NTSTATUS +WriteToIoTargetSync( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest, + _In_ PUCHAR _Data, + _In_ ULONG _Length, + _Out_opt_ PULONG_PTR _BytesWritten +) +/*++ + +Routine Description: + + This function synchronously writes data to an I/O target with timeout. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + + _Data - input data + _Length - the size of the input data + + _BytesWritten - (optional) the total count of bytes written to the device + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + WDF_REQUEST_REUSE_PARAMS RequestReuseParams; + WDF_REQUEST_SEND_OPTIONS RequestOptions; + WDF_MEMORY_DESCRIPTOR MemoryDescriptor; + ULONG_PTR BytesWritten = 0; + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+WriteToIoTargetSync")); + + if (_ReusableRequest != NULL) + { + WDF_REQUEST_REUSE_PARAMS_INIT(&RequestReuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_SUCCESS); + Status = WdfRequestReuse(_ReusableRequest, &RequestReuseParams); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfRequestReuse failed %!STATUS!", Status)); + goto Done; + } + } + + WDF_REQUEST_SEND_OPTIONS_INIT(&RequestOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT); + WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&RequestOptions, WDF_REL_TIMEOUT_IN_SEC(MAX_WRITE_TIMEOUT_IN_SEC)); + + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&MemoryDescriptor, + (PVOID)_Data, + _Length); + + Status = WdfIoTargetSendWriteSynchronously(_IoTargetSerial, + _ReusableRequest, + &MemoryDescriptor, + NULL, + &RequestOptions, + &BytesWritten); + + if (NT_SUCCESS(Status)) + { + DoTrace(LEVEL_INFO, TFLAG_IO, (" WdfIoTargetSendWriteSynchronously succeeded: %d bytes sent, %d bytes written", + _Length, (ULONG)BytesWritten)); + + if (_BytesWritten != NULL) + *_BytesWritten = BytesWritten; + } + else + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfIoTargetSendWriteSynchronously failed %!STATUS!", Status)); + +Done: + DoTrace(LEVEL_INFO, TFLAG_IO, ("-WriteToIoTargetSync %!STATUS!", Status)); + return Status; +} + +NTSTATUS +ReadFromIoTargetSync( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest, + _Inout_ PUCHAR _Data, + _In_ ULONG _Length, + _Out_opt_ PULONG_PTR _BytesRead +) +/*++ + +Routine Description: + + This function synchronously reads data from an I/O target with timeout. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + + _Data - output buffer + _Length - the size of the output buffer + + _BytesRead - (optional) the total count of bytes read from the device + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + WDF_REQUEST_REUSE_PARAMS RequestReuseParams; + WDF_REQUEST_SEND_OPTIONS RequestOptions; + WDF_MEMORY_DESCRIPTOR MemoryDescriptor; + ULONG_PTR BytesRead = 0; + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+ReadFromIoTargetSync")); + + if (_ReusableRequest != NULL) + { + WDF_REQUEST_REUSE_PARAMS_INIT(&RequestReuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_SUCCESS); + Status = WdfRequestReuse(_ReusableRequest, &RequestReuseParams); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfRequestReuse failed %!STATUS!", Status)); + goto Done; + } + } + + WDF_REQUEST_SEND_OPTIONS_INIT(&RequestOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT); + WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&RequestOptions, WDF_REL_TIMEOUT_IN_SEC(MAX_READ_TIMEOUT_IN_SEC)); + + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&MemoryDescriptor, + _Data, + _Length); + + Status = WdfIoTargetSendReadSynchronously(_IoTargetSerial, + _ReusableRequest, + &MemoryDescriptor, + NULL, + &RequestOptions, + &BytesRead); + + if (NT_SUCCESS(Status)) + { + DoTrace(LEVEL_INFO, TFLAG_IO, (" WdfIoTargetSendReadSynchronously succeeded: %d bytes requested, %d bytes received", + _Length, (ULONG)BytesRead)); + + if (_BytesRead != NULL) + *_BytesRead = BytesRead; + } + else + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfIoTargetSendReadSynchronously failed %!STATUS!", Status)); + +Done: + DoTrace(LEVEL_INFO, TFLAG_IO, ("-ReadFromIoTargetSync %!STATUS!", Status)); + return Status; +} + +NTSTATUS +ReadHciEventSync( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest, + _Inout_ PUCHAR _Data, + _In_ ULONG _Length, + _Out_opt_ PULONG_PTR _BytesRead +) +/*++ + +Routine Description: + + This function synchronously reads a HCI event from an I/O target with timeout. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + + _Data - output buffer + _Length - the size of the output buffer + + _BytesRead - (optional) the total count of bytes read from the device + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONG_PTR BytesRead = 0; + ULONG BytesCount = 0; + ULONG ParametersToRead = 0; + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+ReadHciEventSync")); + + if (_Length == 0) + { + Status = STATUS_INVALID_PARAMETER; + goto Done; + } + + // + // 1st byte = packet type + // Read until we get HciPacketEvent (0x04) + // + while (TRUE) + { + UCHAR CurrentByte = 0; + Status = ReadFromIoTargetSync(_IoTargetSerial, + _ReusableRequest, + &CurrentByte, + sizeof(UCHAR), + NULL); + + if (!NT_SUCCESS(Status)) + goto Done; + + if (CurrentByte == (UCHAR)HciPacketEvent) + break; + } + + // + // Read the next 2 bytes + // 2nd byte = event code + // 3rd byte = parameters length + // + while (BytesCount < HCI_EVENT_HEADER_SIZE) + { + Status = ReadFromIoTargetSync(_IoTargetSerial, + _ReusableRequest, + _Data + BytesRead, + (ULONG)(HCI_EVENT_HEADER_SIZE - BytesRead), + &BytesRead); + + if (!NT_SUCCESS(Status)) + goto Done; + + BytesCount += (ULONG)BytesRead; + } + + // Don't read more bytes than requested into the output buffer + if (_Data[1] < (_Length - HCI_EVENT_HEADER_SIZE)) + ParametersToRead = _Data[1]; + else + { + ParametersToRead = _Length - HCI_EVENT_HEADER_SIZE; + + DoTrace(LEVEL_WARNING, TFLAG_IO, (" Warning: output buffer size (%d) is less than the received event total length (%d)", + _Length, _Data[1] + HCI_EVENT_HEADER_SIZE)); + } + + // + // Read the parameters + // + while ((BytesCount - HCI_EVENT_HEADER_SIZE) < ParametersToRead) + { + Status = ReadFromIoTargetSync(_IoTargetSerial, + _ReusableRequest, + _Data + BytesCount, + (ULONG)(ParametersToRead - (BytesCount - HCI_EVENT_HEADER_SIZE)), + &BytesRead); + + if (!NT_SUCCESS(Status)) + goto Done; + + BytesCount += (ULONG)BytesRead; + } + + if (_BytesRead != NULL) + *_BytesRead = BytesCount; + +Done: + DoTrace(LEVEL_INFO, TFLAG_IO, ("-ReadHciEventSync %!STATUS!", Status)); + return Status; +} + +NTSTATUS +SendHciCommandSync( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest, + _Inout_ PUCHAR _Data, + _In_ ULONG _Length, + _Out_opt_ PULONG_PTR _BytesWritten +) +/*++ + +Routine Description: + + This function synchronously sends a HCI command to an I/O target with timeout. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + + _Data - input command data + _Length - the size of the input command data + + _BytesWritten - (optional) the total count of bytes written to the device + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONG_PTR BytesWritten = 0; + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+SendHciCommandSync")); + + if (_Length == 0) + { + Status = STATUS_INVALID_PARAMETER; + goto Done; + } + + // + // Send the packet type + // + UCHAR PacketType = (UCHAR)HciPacketCommand; + Status = WriteToIoTargetSync(_IoTargetSerial, + _ReusableRequest, + &PacketType, + sizeof(UCHAR), + NULL); + + if (!NT_SUCCESS(Status)) + goto Done; + + // + // Send the command itself + // + Status = WriteToIoTargetSync(_IoTargetSerial, + _ReusableRequest, + _Data, + _Length, + &BytesWritten); + + if (!NT_SUCCESS(Status)) + goto Done; + + if (_BytesWritten != NULL) + *_BytesWritten = BytesWritten; + +Done: + DoTrace(LEVEL_INFO, TFLAG_IO, ("-SendHciCommandSync %!STATUS!", Status)); + return Status; +} + +NTSTATUS +HciVerifyEvent( + _In_ PUCHAR _CommandData, + _In_ ULONG _CommandDataLength, + _In_ PUCHAR _EventData, + _In_ ULONG _EventDataLength +) +/*++ + +Routine Description: + + This function validates a HCI event depending on the previously sent command. + +Arguments: + + _CommandData - the executed command + _CommandDataLength - the length of the executed command + + _EventData - the received event + _EventData - the length of the received event + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + + DoTrace(LEVEL_INFO, TFLAG_HCI, ("+HciVerifyEvent")); + + if (!WithinRange(MIN_HCI_CMD_SIZE, _CommandDataLength, MAX_HCI_CMD_SIZE)) + { + DoTrace(LEVEL_ERROR, TFLAG_HCI, (" _CommandDataLength out of range (%d)", _CommandDataLength)); + Status = STATUS_INVALID_PARAMETER; + goto Done; + } + + if (!WithinRange(BCM_HCI_MIN_EVENT_SIZE, _EventDataLength, MAX_HCI_EVENT_SIZE)) + { + DoTrace(LEVEL_ERROR, TFLAG_HCI, (" _EventDataLength out of range (%d)", _EventDataLength)); + Status = STATUS_INVALID_PARAMETER; + goto Done; + } + + DoTrace(LEVEL_INFO, TFLAG_HCI, (" <- HCI EventCode: 0x%x, nRequestedParams: %d, nTotalParams: %d,", + _EventData[0], + _EventDataLength - HCI_EVENT_HEADER_SIZE, + _EventData[1])); + + for (ULONG Index = 0; + Index < MinToPrint(_EventDataLength - HCI_EVENT_HEADER_SIZE, MAX_EVENT_PARAMS_TO_DISPLAY); + Index++) + { + DoTrace(LEVEL_VERBOSE, TFLAG_HCI, (" [%d] 0x%.2x", + Index, _EventData[Index + HCI_EVENT_HEADER_SIZE])); + } + + // + // If everything is right, we should get back the command opcode + // and the completion status + // + if (_EventData[3] != _CommandData[0] // Check LSB (Opcode Command Field) + || _EventData[4] != _CommandData[1] // Check MSB (Opcode Group Field) + || _EventData[5] != HCI_COMMAND_SUCCESS) + { + DoTrace(LEVEL_ERROR, TFLAG_HCI, (" Bad event parameters!")); + Status = STATUS_INVALID_PARAMETER; + goto Done; + } + +Done: + DoTrace(LEVEL_INFO, TFLAG_HCI, ("-HciVerifyEvent %!STATUS!", Status)); + return Status; +} + +NTSTATUS +BcmHciReset( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest +) +/*++ + +Routine Description: + + This function performs a HCI reset. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + UCHAR ReadBuffer[MAX_HCI_EVENT_SIZE] = { 0 }; + ULONG_PTR BytesRead = 0; + + DoTrace(LEVEL_INFO, TFLAG_HCI, ("+BcmHciReset")); + + UCHAR Command[] = { 0x03, 0x0C, 0x00 }; + + Status = SendHciCommandSync(_IoTargetSerial, + _ReusableRequest, + Command, + sizeof(Command), + NULL + ); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = ReadHciEventSync(_IoTargetSerial, + _ReusableRequest, + ReadBuffer, + sizeof(ReadBuffer), + &BytesRead + ); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = HciVerifyEvent(Command, sizeof(Command), ReadBuffer, (ULONG)BytesRead); + + if (!NT_SUCCESS(Status)) + goto Done; +Done: + DoTrace(LEVEL_INFO, TFLAG_HCI, ("-BcmHciReset %!STATUS!", Status)); + return Status; +} + +NTSTATUS +BcmHciGetVerboseConfig( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest, + _Out_ PBCM_HCI_VERBOSE_CONFIG _VerboseConfig +) +/*++ + +Routine Description: + + This function gets the vendor-specific verbose config from the Bluetooth device. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + + _VerboseConfig - returned verbose config + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + UCHAR ReadBuffer[MAX_HCI_EVENT_SIZE] = { 0 }; + ULONG_PTR BytesRead = 0; + + DoTrace(LEVEL_INFO, TFLAG_HCI, ("+BcmHciGetVerboseConfig")); + + UCHAR Command[] = { 0x79, 0xfc, 0x00 }; + + if (_VerboseConfig == NULL) + { + Status = STATUS_INVALID_PARAMETER; + DoTrace(LEVEL_ERROR, TFLAG_HCI, (" _VerboseConfig is NULL!")); + goto Done; + } + + Status = SendHciCommandSync(_IoTargetSerial, + _ReusableRequest, + Command, + sizeof(Command), + NULL); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = ReadHciEventSync(_IoTargetSerial, + _ReusableRequest, + ReadBuffer, + sizeof(ReadBuffer), + &BytesRead); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = HciVerifyEvent(Command, sizeof(Command), ReadBuffer, (ULONG)BytesRead); + + if (!NT_SUCCESS(Status)) + goto Done; + + _VerboseConfig->ChipId = ReadBuffer[6]; + _VerboseConfig->TargetId = ReadBuffer[7]; + _VerboseConfig->BuildBase = ReadBuffer[8] | ReadBuffer[9] << 8; + _VerboseConfig->BuildNum = ReadBuffer[10] | ReadBuffer[11] << 8; + + DoTrace(LEVEL_INFO, TFLAG_HCI, (" ChipId: %d", _VerboseConfig->ChipId)); + DoTrace(LEVEL_INFO, TFLAG_HCI, (" TargetId: %d", _VerboseConfig->TargetId)); + DoTrace(LEVEL_INFO, TFLAG_HCI, (" BuildBase: %d", _VerboseConfig->BuildBase)); + DoTrace(LEVEL_INFO, TFLAG_HCI, (" BuildNum: %d", _VerboseConfig->BuildNum)); + +Done: + DoTrace(LEVEL_INFO, TFLAG_HCI, ("-BcmHciGetVerboseConfig %!STATUS!", Status)); + return Status; +} + +NTSTATUS +BcmHciGetLocalName( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest, + _Inout_ PUNICODE_STRING _Name +) +/*++ + +Routine Description: + + This function gets the local name from the Bluetooth device. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + + _Name - returned name (up to the value of the MaximumLength member) + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + UCHAR ReadBuffer[MAX_HCI_EVENT_SIZE] = { 0 }; + ULONG_PTR BytesRead = 0; + ANSI_STRING NameAnsiString; + + DoTrace(LEVEL_INFO, TFLAG_HCI, ("+BcmHciGetLocalName")); + + UCHAR Command[] = { 0x14, 0x0C, 0x00 }; + + Status = SendHciCommandSync(_IoTargetSerial, + _ReusableRequest, + Command, + sizeof(Command), + NULL + ); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = ReadHciEventSync(_IoTargetSerial, + _ReusableRequest, + ReadBuffer, + sizeof(ReadBuffer), + &BytesRead + ); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = HciVerifyEvent(Command, sizeof(Command), ReadBuffer, (ULONG)BytesRead); + + if (!NT_SUCCESS(Status)) + goto Done; + + RtlInitAnsiString(&NameAnsiString, (PCSZ)ReadBuffer + 6); + + DoTrace(LEVEL_INFO, TFLAG_HCI, (" Complete local name: %s", NameAnsiString.Buffer)); + + // Do not return more characters than requested + if (_Name->MaximumLength / sizeof(WCHAR) < NameAnsiString.MaximumLength) + { + NameAnsiString.MaximumLength = _Name->MaximumLength / sizeof(WCHAR); + NameAnsiString.Length = NameAnsiString.MaximumLength - 1; + } + + Status = RtlAnsiStringToUnicodeString(_Name, &NameAnsiString, FALSE); + + if (NT_SUCCESS(Status)) + DoTrace(LEVEL_INFO, TFLAG_HCI, (" Local name (unicode request): %wZ", _Name)); + else + { + DoTrace(LEVEL_ERROR, TFLAG_HCI, (" RtlAnsiStringToUnicodeString failed %!STATUS!", Status)); + goto Done; + } +Done: + DoTrace(LEVEL_INFO, TFLAG_HCI, ("-BcmHciGetLocalName %!STATUS!", Status)); + return Status; +} + +NTSTATUS +BcmHciEnterFwDownloadMode( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest +) +/*++ + +Routine Description: + + This function is called by BcmHciDownloadFirmware to put the Bluetooth device in + Minidriver download mode through a vendor-specific HCI command. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + UCHAR ReadBuffer[MAX_HCI_EVENT_SIZE] = { 0 }; + ULONG_PTR BytesRead = 0; + + DoTrace(LEVEL_INFO, TFLAG_HCI, ("+BcmHciEnterFwDownloadMode")); + + UCHAR Command[] = { 0x2e, 0xfc, 0x00 }; + + Status = SendHciCommandSync(_IoTargetSerial, + _ReusableRequest, + Command, + sizeof(Command), + NULL + ); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = ReadHciEventSync(_IoTargetSerial, + _ReusableRequest, + ReadBuffer, + sizeof(ReadBuffer), + &BytesRead + ); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = HciVerifyEvent(Command, sizeof(Command), ReadBuffer, (ULONG)BytesRead); + + if (!NT_SUCCESS(Status)) + goto Done; + + SleepMicroseconds(BCM_ENTER_FW_DOWNLOAD_MODE_DELAY_MICROS); +Done: + DoTrace(LEVEL_INFO, TFLAG_HCI, ("-BcmHciEnterFwDownloadMode %!STATUS!", Status)); + return Status; +} + +NTSTATUS +BcmHciDownloadFirmware( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest, + _In_ PUNICODE_STRING _FilePath +) +/*++ + +Routine Description: + + This function downloads a HCD firmware file on the Bluetooth device. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + + _FilePath - the path to the firmware file + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + OBJECT_ATTRIBUTES Attributes; + HANDLE FileHandle = NULL; + IO_STATUS_BLOCK IoStatusBlock; + FILE_STANDARD_INFORMATION FileInfo; + ULONG_PTR HciBytesRead = 0; + LARGE_INTEGER ByteOffset; + UCHAR ReadBuffer[MAX_HCI_EVENT_SIZE] = { 0 }; + UCHAR FileBuffer[1024]; + + DoTrace(LEVEL_INFO, TFLAG_HCI, ("+BcmHciDownloadFirmware (_FilePath: %wZ)", _FilePath)); + + if (KeGetCurrentIrql() != PASSIVE_LEVEL) + { + Status = STATUS_INVALID_DEVICE_STATE; + DoTrace(LEVEL_ERROR, TFLAG_IO, (" IRQL is higher than PASSIVE_LEVEL!")); + goto Done; + } + + InitializeObjectAttributes(&Attributes, _FilePath, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + NULL, NULL); + + Status = ZwCreateFile(&FileHandle, + GENERIC_READ, + &Attributes, &IoStatusBlock, + NULL, + FILE_ATTRIBUTE_NORMAL, + 0, + FILE_OPEN, + FILE_SYNCHRONOUS_IO_NONALERT, + NULL, 0); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" ZwCreateFile failed %!STATUS!", Status)); + goto Done; + } + + Status = ZwQueryInformationFile(FileHandle, + &IoStatusBlock, + &FileInfo, + sizeof(FileInfo), + FileStandardInformation); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" ZwQueryInformationFile failed %!STATUS!", Status)); + goto Done; + } + + Status = BcmHciEnterFwDownloadMode(_IoTargetSerial, _ReusableRequest); + + if (!NT_SUCCESS(Status)) + goto Done; + + ByteOffset.QuadPart = 0; + + while (ByteOffset.QuadPart < FileInfo.EndOfFile.QuadPart) + { + // + // Read opcode (16-bit) + parameters length + // + Status = ZwReadFile(FileHandle, NULL, NULL, NULL, &IoStatusBlock, + FileBuffer, 3, &ByteOffset, NULL); + + if (!NT_SUCCESS(Status)) + goto Done; + + ByteOffset.QuadPart += 3; + + ULONG DataLength = FileBuffer[2]; + + // + // Read the patch data + // + Status = ZwReadFile(FileHandle, NULL, NULL, NULL, &IoStatusBlock, + FileBuffer + 3, DataLength, &ByteOffset, NULL); + + if (!NT_SUCCESS(Status)) + goto Done; + + ByteOffset.QuadPart += DataLength; + + Status = SendHciCommandSync(_IoTargetSerial, + _ReusableRequest, + FileBuffer, + DataLength + 3, + NULL); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = ReadHciEventSync(_IoTargetSerial, + _ReusableRequest, + ReadBuffer, + sizeof(ReadBuffer), + &HciBytesRead); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = HciVerifyEvent(FileBuffer, DataLength + 3, ReadBuffer, (ULONG)HciBytesRead); + + if (!NT_SUCCESS(Status)) + goto Done; + } + + SleepMicroseconds(BCM_FW_DOWNLOAD_COMPLETE_DELAY_MICROS); + +Done: + if (FileHandle != NULL) + ZwClose(FileHandle); + + DoTrace(LEVEL_INFO, TFLAG_HCI, ("-BcmHciDownloadFirmware %!STATUS!", Status)); + return Status; +} + +NTSTATUS +BcmHciSetBaudRate( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest, + _In_ ULONG _BaudRate +) +/*++ + +Routine Description: + + This function sets the baud rate of the Bluetooth device through a vendor-specific HCI command. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + + _BaudRate - the desired baud rate + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + UCHAR ReadBuffer[MAX_HCI_EVENT_SIZE] = { 0 }; + ULONG_PTR BytesRead = 0; + + DoTrace(LEVEL_INFO, TFLAG_HCI, ("+BcmHciSetBaudRate: %d", _BaudRate)); + + UCHAR Command[] = { 0x18, 0xfc, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + Command[5] = (UCHAR)(_BaudRate); + Command[6] = (UCHAR)(_BaudRate >> 8); + Command[7] = (UCHAR)(_BaudRate >> 16); + Command[8] = (UCHAR)(_BaudRate >> 24); + + Status = SendHciCommandSync(_IoTargetSerial, + _ReusableRequest, + Command, + sizeof(Command), + NULL + ); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = ReadHciEventSync(_IoTargetSerial, + _ReusableRequest, + ReadBuffer, + sizeof(ReadBuffer), + &BytesRead + ); + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = HciVerifyEvent(Command, sizeof(Command), ReadBuffer, (ULONG)BytesRead); + + if (!NT_SUCCESS(Status)) + goto Done; +Done: + DoTrace(LEVEL_INFO, TFLAG_HCI, ("-BcmHciSetBaudRate %!STATUS!", Status)); + return Status; +} + +NTSTATUS +SetBaudRate( + _In_ WDFIOTARGET _IoTargetSerial, + _In_opt_ WDFREQUEST _ReusableRequest, + _In_ ULONG _BaudRate +) +/*++ + +Routine Description: + + This function sets the baud rate of both the Bluetooth device + and the host UART controller. + +Arguments: + + _IoTargetSerial - the serial I/O target + _ReusableRequest - (optional) a reusable WDF request to issue serial control + + _BaudRate - the desired baud rate + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status = STATUS_SUCCESS; + SERIAL_BAUD_RATE SerialBaudRate; + SERIAL_COMMPROP SerialProperties = { 0 }; + ULONG_PTR BytesWritten = 0; + + DoTrace(LEVEL_INFO, TFLAG_UART, ("+SetBaudRate (host + target UART): %d", _BaudRate)); + + Status = SendIoctlToIoTargetSync(_IoTargetSerial, + _ReusableRequest, + IOCTL_SERIAL_GET_PROPERTIES, + NULL, + 0, + &SerialProperties, + sizeof(SERIAL_COMMPROP), + &BytesWritten); + + if (!NT_SUCCESS(Status)) + goto Done; + + if (BytesWritten == 0) + { + Status = STATUS_UNSUCCESSFUL; + goto Done; + } + + if (_BaudRate > SerialProperties.MaxBaud) + { + _BaudRate = SerialProperties.MaxBaud; + DoTrace(LEVEL_WARNING, TFLAG_UART, (" Baud rate capped at %d (maximum supported by host UART)", _BaudRate)); + } + + if (!NT_SUCCESS(Status)) + goto Done; + + Status = BcmHciSetBaudRate(_IoTargetSerial, + _ReusableRequest, + _BaudRate); + + if (!NT_SUCCESS(Status)) + goto Done; + + SerialBaudRate.BaudRate = _BaudRate; + + Status = SendIoctlToIoTargetSync(_IoTargetSerial, + _ReusableRequest, + IOCTL_SERIAL_SET_BAUD_RATE, + &SerialBaudRate, + sizeof(SERIAL_BAUD_RATE), + NULL, + 0, + NULL); + + if (!NT_SUCCESS(Status)) + goto Done; + +Done: + DoTrace(LEVEL_INFO, TFLAG_UART, ("-SetBaudRate %!STATUS!", Status)); + return Status; +} + +BOOLEAN +CheckRegQueryOperation( + _In_ NTSTATUS _Status, + _In_ PCUNICODE_STRING _ValueName +) +/*++ + +Routine Description: + + This function checks if the registry query operation was successfully completed, + and prints a WPP trace in case of failure. + +Arguments: + + _Status - NTSTATUS code returned by a call to WdfRegistryQuery[type] + _ValueName - registry key name + +Return Value: + + BOOLEAN + +--*/ +{ + if (NT_SUCCESS(_Status)) + return TRUE; + else + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfRegistryQuery... (_ValueName: %wZ) failed %!STATUS!", _ValueName, _Status)); + return FALSE; + } +} + +VOID +DeviceQueryDeviceParameters( + _In_ WDFDRIVER _Driver +) +/*++ + +Routine Description: + + Query driver's registry location for device specific parameters. + +Arguments: + + _Driver - WDF Driver object + +Return Value: + + None + +--*/ +{ + WDFKEY Key; + NTSTATUS Status; + UNICODE_STRING ValueName; + ULONG Value = 0; + WDF_OBJECT_ATTRIBUTES Attributes; + PDEVICE_CONFIG_PARAMETERS ConfigParams = NULL; + + PAGED_CODE(); + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+DeviceQueryDeviceParameters")); + + WDF_OBJECT_ATTRIBUTES_INIT(&Attributes); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attributes, DEVICE_CONFIG_PARAMETERS); + + Status = WdfObjectAllocateContext(_Driver, &Attributes, &ConfigParams); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfObjectAllocateContext failed %!STATUS!", Status)); + goto Done; + } + + // + // Load the default values first + // + ConfigParams->BaudRate = DEFAULT_BAUD_RATE; + ConfigParams->SkipFwDownload = DEFAULT_SKIP_FW_DOWNLOAD; + RtlInitUnicodeString(&ConfigParams->FwDirectory, DEFAULT_FW_DIRECTORY); + + Status = WdfDriverOpenParametersRegistryKey(_Driver, + GENERIC_READ, + WDF_NO_OBJECT_ATTRIBUTES, + &Key); + + if (NT_SUCCESS(Status)) + { + // + // Read BaudRate + // + RtlInitUnicodeString(&ValueName, STR_REG_BAUDRATE); + Status = WdfRegistryQueryULong(Key, &ValueName, &Value); + if (CheckRegQueryOperation(Status, &ValueName)) + ConfigParams->BaudRate = Value; + + // + // Read SkipFwDownload + // + RtlInitUnicodeString(&ValueName, STR_REG_SKIP_FW_DOWNLOAD); + Status = WdfRegistryQueryULong(Key, &ValueName, &Value); + if (CheckRegQueryOperation(Status, &ValueName)) + ConfigParams->SkipFwDownload = Value; + + // + // Read FwDirectory + // + RtlInitUnicodeString(&ValueName, STR_REG_FW_DIRECTORY); + Status = WdfRegistryQueryUnicodeString(Key, &ValueName, NULL, &ConfigParams->FwDirectory); + if (CheckRegQueryOperation(Status, &ValueName)) + ConfigParams->SkipFwDownload = Value; + + WdfRegistryClose(Key); + } + else + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" WdfDriverOpenParametersRegistryKey failed %!STATUS!", Status)); + goto Done; + } + +Done: + DoTrace(LEVEL_INFO, TFLAG_IO, ("-DeviceQueryDeviceParameters")); +} + +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 performs device specific operations to + bring it into a fully functional state. + +Arguments: + + _FdoExtension - Function device object extension + + _IoTargetSerial - IO Target to issue request to serial port + + _RequestSync - A reusable WDF Request to issue serial control + + -IsUartReset - UART reset is required + +Return Value: + + TRUE if initialization is completed and successful; FALSE otherwise. + +--*/ +{ + UNREFERENCED_PARAMETER(_FdoExtension); + UNREFERENCED_PARAMETER(_IsUartReset); + + NTSTATUS Status = STATUS_SUCCESS; + UNICODE_STRING NamePrefix, LocalName, FirmwarePath; + WCHAR LocalNameBuffer[BCM_INITIAL_LOCAL_NAME_MAX_LENGTH]; + BCM_HCI_VERBOSE_CONFIG BcmVerboseConfig; + + DoTrace(LEVEL_INFO, TFLAG_IO, ("+DeviceInitialize")); + + PDEVICE_CONFIG_PARAMETERS ConfigParameters = GetDeviceConfigParameters(WdfGetDriver()); + + if (ConfigParameters == NULL) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" ConfigParameters context is uninitialized!")); + return FALSE; + } + + RtlUnicodeStringInit(&NamePrefix, BCM_INITIAL_LOCAL_NAME_PREFIX); + RtlInitEmptyUnicodeString(&LocalName, LocalNameBuffer, sizeof(LocalNameBuffer)); + RtlUnicodeStringInit(&FirmwarePath, NULL); + + Status = BcmHciReset(_IoTargetSerial, _RequestSync); + + if (!NT_SUCCESS(Status)) + return FALSE; + + if (!ConfigParameters->SkipFwDownload) + { + Status = BcmHciGetVerboseConfig(_IoTargetSerial, + _RequestSync, + &BcmVerboseConfig); + + if (!NT_SUCCESS(Status)) + return FALSE; + + // + // If BuildNum is 0 then the patch RAM is empty and we have to download the firmware. + // Once downloaded, we can't update it. + // + if (BcmVerboseConfig.BuildNum == 0) + { + Status = BcmHciGetLocalName(_IoTargetSerial, + _RequestSync, + &LocalName); + + if (!NT_SUCCESS(Status)) + return FALSE; + + if (!RtlPrefixUnicodeString(&NamePrefix, &LocalName, FALSE)) + { + // This shouldn't happen unless the user messes with the SkipFwDownload reg key. + DoTrace(LEVEL_ERROR, TFLAG_IO, (" Initial local name was changed. Can't find the firmware!")); + return FALSE; + } + + Status = BuildFirmwarePath(&FirmwarePath, + &ConfigParameters->FwDirectory, + &LocalName); + + if (!NT_SUCCESS(Status)) + { + DoTrace(LEVEL_ERROR, TFLAG_IO, (" BuildFirmwarePath failed %!STATUS!", Status)); + return FALSE; + } + + Status = BcmHciDownloadFirmware(_IoTargetSerial, + _RequestSync, + &FirmwarePath); + + if (FirmwarePath.Buffer != NULL) + ExFreePool(FirmwarePath.Buffer); + + if (!NT_SUCCESS(Status)) + return FALSE; + } + else + DoTrace(LEVEL_INFO, TFLAG_IO, (" Firmware is already installed!")); + } + else + DoTrace(LEVEL_WARNING, TFLAG_IO, (" Firmware download skipped!")); + + Status = SetBaudRate(_IoTargetSerial, + _RequestSync, + ConfigParameters->BaudRate); + + if (!NT_SUCCESS(Status)) + return FALSE; + + DoTrace(LEVEL_INFO, TFLAG_IO, ("-DeviceInitialize")); + + 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; +} + +_Use_decl_annotations_ +VOID +DeviceDoPLDR( + WDFDEVICE _Fdo +) +/*++ + +Routine Description: + + This vendor-specific routine takes appropriate actions necessary to fully reset the device. + +Arguments: + + _Fdo - Framework device object representing the FDO. + +Return Value: + + VOID. + +--*/ +{ + UNREFERENCED_PARAMETER(_Fdo); +} diff --git a/src/vendor/device.h b/src/vendor/device.h new file mode 100644 index 0000000..e38b236 --- /dev/null +++ b/src/vendor/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"CywBtSerialBus\\UART_H4" +#define BT_PDO_COMPATIBLE_IDS L"MS_BTHX_BTHMINI" +#define BT_PDO_DEVICE_LOCATION L"Cypress 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/src/vendor/driver.rc b/src/vendor/driver.rc new file mode 100644 index 0000000..1341816 --- /dev/null +++ b/src/vendor/driver.rc @@ -0,0 +1,49 @@ +// +// Include the necessary resources +// +#include +#include + +#ifdef RC_INVOKED + +// +// Set up debug information +// +#if DBG +#define VER_DBG VS_FF_DEBUG +#else +#define VER_DBG 0 +#endif + +// ------- version info ------------------------------------------------------- + +VS_VERSION_INFO VERSIONINFO +FILEVERSION 1,0,0,0 +PRODUCTVERSION 1,0,0,0 +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +FILEFLAGS VER_DBG +FILEOS VOS_NT +FILETYPE VFT_DRV +FILESUBTYPE VFT2_DRV_SYSTEM +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "Cypress Bluetooth Driver" + VALUE "CompanyName", "Windows on Raspberry Project" + VALUE "FileDescription", "Cypress Bluetooth UART Transport Driver" + VALUE "FileVersion", "1.0.0.0" + VALUE "InternalName", "cywbtserialbus.sys" + VALUE "LegalCopyright", "Copyright (c) 2020 Mario Bãlãnicã. All Rights Reserved." + VALUE "OriginalFilename", "cywbtserialbus.sys" + VALUE "ProductName", "Cypress Bluetooth UART Transport Driver" + VALUE "ProductVersion", "1.0.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409,1200 + END +END +#endif \ No newline at end of file diff --git a/src/vendor/fw/BCM4345C0.hcd b/src/vendor/fw/BCM4345C0.hcd new file mode 100644 index 0000000..bb6ebd6 Binary files /dev/null and b/src/vendor/fw/BCM4345C0.hcd differ