| /* |
| * Copyright (c) 2016, The OpenThread Authors. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the copyright holder nor the |
| * names of its contributors may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * @file |
| * @brief |
| * This file implements the functions for creating new notifications for IOCTL clients. |
| */ |
| |
| #include "precomp.h" |
| #include "eventprocessing.tmh" |
| |
| typedef struct _OTLWF_ADDR_EVENT |
| { |
| LIST_ENTRY Link; |
| MIB_NOTIFICATION_TYPE NotificationType; |
| IN6_ADDR Address; |
| |
| } OTLWF_ADDR_EVENT, *POTLWF_ADDR_EVENT; |
| |
| typedef struct _OTLWF_NBL_EVENT |
| { |
| LIST_ENTRY Link; |
| PNET_BUFFER_LIST NetBufferLists; |
| |
| } OTLWF_NBL_EVENT, *POTLWF_NBL_EVENT; |
| |
| typedef struct _OTLWF_MAC_FRAME_EVENT |
| { |
| LIST_ENTRY Link; |
| uint8_t BufferLength; |
| uint8_t Buffer[0]; |
| |
| } OTLWF_MAC_FRAME_EVENT, *POTLWF_MAC_FRAME_EVENT; |
| |
| |
| KSTART_ROUTINE otLwfEventWorkerThread; |
| |
| _IRQL_requires_max_(DISPATCH_LEVEL) |
| VOID |
| otLwfCompleteNBLs( |
| _In_ PMS_FILTER pFilter, |
| _In_ BOOLEAN DispatchLevel, |
| _In_ PNET_BUFFER_LIST NetBufferLists, |
| _In_ NTSTATUS Status |
| ); |
| |
| // Starts the event queue processing |
| _IRQL_requires_max_(PASSIVE_LEVEL) |
| NTSTATUS |
| otLwfEventProcessingStart( |
| _In_ PMS_FILTER pFilter |
| ) |
| { |
| NTSTATUS status = STATUS_SUCCESS; |
| HANDLE threadHandle = NULL; |
| |
| LogFuncEntryMsg(DRIVER_DEFAULT, "Filter: %p, TimeIncrement = %u", pFilter, KeQueryTimeIncrement()); |
| |
| pFilter->NextAlarmTickCount.QuadPart = 0; |
| |
| NT_ASSERT(pFilter->EventWorkerThread == NULL); |
| if (pFilter->EventWorkerThread != NULL) |
| { |
| status = STATUS_ALREADY_REGISTERED; |
| goto error; |
| } |
| |
| // Make sure to reset the necessary events |
| KeResetEvent(&pFilter->EventWorkerThreadStopEvent); |
| KeResetEvent(&pFilter->SendNetBufferListComplete); |
| KeResetEvent(&pFilter->EventWorkerThreadEnergyScanComplete); |
| |
| // Start the worker thread |
| status = PsCreateSystemThread( |
| &threadHandle, // ThreadHandle |
| THREAD_ALL_ACCESS, // DesiredAccess |
| NULL, // ObjectAttributes |
| NULL, // ProcessHandle |
| NULL, // ClientId |
| otLwfEventWorkerThread, // StartRoutine |
| pFilter // StartContext |
| ); |
| if (!NT_SUCCESS(status)) |
| { |
| LogError(DRIVER_DEFAULT, "PsCreateSystemThread failed, %!STATUS!", status); |
| goto error; |
| } |
| |
| // Grab the object reference to the worker thread |
| status = ObReferenceObjectByHandle( |
| threadHandle, |
| THREAD_ALL_ACCESS, |
| *PsThreadType, |
| KernelMode, |
| &pFilter->EventWorkerThread, |
| NULL |
| ); |
| if (!NT_VERIFYMSG("ObReferenceObjectByHandle can't fail with a valid kernel handle", NT_SUCCESS(status))) |
| { |
| LogError(DRIVER_DEFAULT, "ObReferenceObjectByHandle failed, %!STATUS!", status); |
| KeSetEvent(&pFilter->EventWorkerThreadStopEvent, IO_NO_INCREMENT, FALSE); |
| } |
| |
| ZwClose(threadHandle); |
| |
| error: |
| |
| if (!NT_SUCCESS(status)) |
| { |
| ExSetTimerResolution(0, FALSE); |
| } |
| |
| LogFuncExitNT(DRIVER_DEFAULT, status); |
| |
| return status; |
| } |
| |
| // Stops the event queue processing |
| _IRQL_requires_max_(PASSIVE_LEVEL) |
| VOID |
| otLwfEventProcessingStop( |
| _In_ PMS_FILTER pFilter |
| ) |
| { |
| PLIST_ENTRY Link = NULL; |
| |
| LogFuncEntryMsg(DRIVER_DEFAULT, "Filter: %p", pFilter); |
| |
| // By this point, we have disabled the Data Path, so no more |
| // NBLs should be queued up. |
| |
| // Clean up worker thread |
| if (pFilter->EventWorkerThread) |
| { |
| LogInfo(DRIVER_DEFAULT, "Stopping event processing worker thread and waiting for it to complete."); |
| |
| // Send event to shutdown worker thread |
| KeSetEvent(&pFilter->EventWorkerThreadStopEvent, 0, FALSE); |
| |
| // Wait for worker thread to finish |
| KeWaitForSingleObject( |
| pFilter->EventWorkerThread, |
| Executive, |
| KernelMode, |
| FALSE, |
| NULL |
| ); |
| |
| // Free worker thread |
| ObDereferenceObject(pFilter->EventWorkerThread); |
| pFilter->EventWorkerThread = NULL; |
| |
| LogInfo(DRIVER_DEFAULT, "Event processing worker thread cleaned up."); |
| } |
| |
| // Clean up any left over events |
| if (pFilter->AddressChangesHead.Flink) |
| { |
| Link = pFilter->AddressChangesHead.Flink; |
| while (Link != &pFilter->AddressChangesHead) |
| { |
| POTLWF_ADDR_EVENT Event = CONTAINING_RECORD(Link, OTLWF_ADDR_EVENT, Link); |
| Link = Link->Flink; |
| |
| // Delete the event |
| NdisFreeMemory(Event, 0, 0); |
| } |
| } |
| |
| // Clean up any left over events |
| if (pFilter->NBLsHead.Flink) |
| { |
| Link = pFilter->NBLsHead.Flink; |
| while (Link != &pFilter->NBLsHead) |
| { |
| POTLWF_NBL_EVENT Event = CONTAINING_RECORD(Link, OTLWF_NBL_EVENT, Link); |
| Link = Link->Flink; |
| |
| otLwfCompleteNBLs(pFilter, FALSE, Event->NetBufferLists, STATUS_CANCELLED); |
| |
| // Delete the event |
| NdisFreeMemory(Event, 0, 0); |
| } |
| } |
| |
| // Clean up any left over events |
| if (pFilter->MacFramesHead.Flink) |
| { |
| Link = pFilter->MacFramesHead.Flink; |
| while (Link != &pFilter->MacFramesHead) |
| { |
| POTLWF_MAC_FRAME_EVENT Event = CONTAINING_RECORD(Link, OTLWF_MAC_FRAME_EVENT, Link); |
| Link = Link->Flink; |
| |
| // Delete the event |
| NdisFreeMemory(Event, 0, 0); |
| } |
| } |
| |
| // Reinitialize the list head |
| InitializeListHead(&pFilter->AddressChangesHead); |
| InitializeListHead(&pFilter->NBLsHead); |
| InitializeListHead(&pFilter->MacFramesHead); |
| |
| if (pFilter->EventIrpListHead.Flink) |
| { |
| FILTER_ACQUIRE_LOCK(&pFilter->EventsLock, FALSE); |
| |
| // Clean up any left over IRPs |
| Link = pFilter->EventIrpListHead.Flink; |
| while (Link != &pFilter->EventIrpListHead) |
| { |
| PIRP Irp = CONTAINING_RECORD(Link, IRP, Tail.Overlay.ListEntry); |
| Link = Link->Flink; |
| |
| // Before we are allowed to complete the pending IRP, we must remove the cancel routine |
| KIRQL irql; |
| IoAcquireCancelSpinLock(&irql); |
| IoSetCancelRoutine(Irp, NULL); |
| IoReleaseCancelSpinLock(irql); |
| |
| Irp->IoStatus.Status = STATUS_CANCELLED; |
| Irp->IoStatus.Information = 0; |
| IoCompleteRequest(Irp, IO_NO_INCREMENT); |
| } |
| |
| // Reinitialize the list head |
| InitializeListHead(&pFilter->EventIrpListHead); |
| |
| FILTER_RELEASE_LOCK(&pFilter->EventsLock, FALSE); |
| } |
| |
| LogFuncExit(DRIVER_DEFAULT); |
| } |
| |
| // Updates the wait time for the alarm |
| _IRQL_requires_max_(PASSIVE_LEVEL) |
| VOID |
| otLwfEventProcessingIndicateNewWaitTime( |
| _In_ PMS_FILTER pFilter, |
| _In_ ULONG waitTime |
| ) |
| { |
| BOOLEAN FireUpdateEvent = TRUE; |
| |
| // Cancel previous timer |
| if (ExCancelTimer(pFilter->EventHighPrecisionTimer, NULL)) |
| { |
| pFilter->EventTimerState = OT_EVENT_TIMER_NOT_RUNNING; |
| } |
| |
| if (waitTime == (ULONG)(-1)) |
| { |
| // Ignore if we are already stopped |
| if (pFilter->NextAlarmTickCount.QuadPart == 0) return; |
| pFilter->NextAlarmTickCount.QuadPart = 0; |
| } |
| else |
| { |
| if (waitTime == 0) |
| { |
| #ifdef DEBUG_TIMING |
| LogInfo(DRIVER_DEFAULT, "Event processing updating to fire timer immediately."); |
| #endif |
| pFilter->EventTimerState = OT_EVENT_TIMER_FIRED; |
| pFilter->NextAlarmTickCount.QuadPart = 0; |
| } |
| else if (waitTime * 10000ll < (KeQueryTimeIncrement() - 30000)) |
| { |
| #ifdef DEBUG_TIMING |
| LogInfo(DRIVER_DEFAULT, "Event processing starting high precision timer for %u ms.", waitTime); |
| #endif |
| pFilter->EventTimerState = OT_EVENT_TIMER_RUNNING; |
| pFilter->NextAlarmTickCount.QuadPart = 0; |
| FireUpdateEvent = FALSE; |
| ExSetTimer(pFilter->EventHighPrecisionTimer, waitTime * -10000ll, 0, NULL); |
| } |
| else |
| { |
| |
| ULONG TickWaitTime = (waitTime * 10000ll) / KeQueryTimeIncrement(); |
| if (TickWaitTime == 0) TickWaitTime = 1; |
| #ifdef DEBUG_TIMING |
| LogInfo(DRIVER_DEFAULT, "Event processing updating wait ticks to %u.", TickWaitTime); |
| #endif |
| |
| // Update the time to be 'waitTime' ms from 'now', saved in TickCounts |
| KeQueryTickCount(&pFilter->NextAlarmTickCount); |
| pFilter->NextAlarmTickCount.QuadPart += TickWaitTime; |
| } |
| } |
| |
| // Indicate event to worker thread to update the wait time |
| KeSetEvent(&pFilter->EventWorkerThreadWaitTimeUpdated, 0, FALSE); |
| } |
| |
| // Indicates another tasklet needs to be processed |
| _IRQL_requires_max_(PASSIVE_LEVEL) |
| VOID |
| otLwfEventProcessingIndicateNewTasklet( |
| _In_ PMS_FILTER pFilter |
| ) |
| { |
| KeSetEvent(&pFilter->EventWorkerThreadProcessTasklets, 0, FALSE); |
| } |
| |
| // Called to indicate that we have an Address change to process |
| _IRQL_requires_max_(PASSIVE_LEVEL) |
| VOID |
| otLwfEventProcessingIndicateAddressChange( |
| _In_ PMS_FILTER pFilter, |
| _In_ MIB_NOTIFICATION_TYPE NotificationType, |
| _In_ PIN6_ADDR pAddr |
| ) |
| { |
| LogFuncEntryMsg(DRIVER_DEFAULT, "Filter: %p", pFilter); |
| |
| NT_ASSERT(pFilter->DeviceStatus == OTLWF_DEVICE_STATUS_RADIO_MODE); |
| |
| POTLWF_ADDR_EVENT Event = FILTER_ALLOC_MEM(pFilter->FilterHandle, sizeof(OTLWF_ADDR_EVENT)); |
| if (Event == NULL) |
| { |
| LogWarning(DRIVER_DEFAULT, "Failed to alloc new OTLWF_ADDR_EVENT"); |
| } |
| else |
| { |
| Event->NotificationType = NotificationType; |
| Event->Address = *pAddr; |
| |
| // Add the event to the queue |
| NdisAcquireSpinLock(&pFilter->EventsLock); |
| InsertTailList(&pFilter->AddressChangesHead, &Event->Link); |
| NdisReleaseSpinLock(&pFilter->EventsLock); |
| |
| // Set the event to indicate we have a new address to process |
| KeSetEvent(&pFilter->EventWorkerThreadProcessAddressChanges, 0, FALSE); |
| } |
| |
| LogFuncExit(DRIVER_DEFAULT); |
| } |
| |
| // Called to indicate that we have a NetBufferLists to process |
| _IRQL_requires_max_(DISPATCH_LEVEL) |
| VOID |
| otLwfEventProcessingIndicateNewNetBufferLists( |
| _In_ PMS_FILTER pFilter, |
| _In_ BOOLEAN DispatchLevel, |
| _In_ PNET_BUFFER_LIST NetBufferLists |
| ) |
| { |
| POTLWF_NBL_EVENT Event = FILTER_ALLOC_MEM(pFilter->FilterHandle, sizeof(OTLWF_NBL_EVENT)); |
| if (Event == NULL) |
| { |
| LogWarning(DRIVER_DATA_PATH, "Failed to alloc new OTLWF_NBL_EVENT"); |
| otLwfCompleteNBLs(pFilter, DispatchLevel, NetBufferLists, STATUS_INSUFFICIENT_RESOURCES); |
| return; |
| } |
| |
| Event->NetBufferLists = NetBufferLists; |
| |
| // Add the event to the queue |
| FILTER_ACQUIRE_LOCK(&pFilter->EventsLock, DispatchLevel); |
| InsertTailList(&pFilter->NBLsHead, &Event->Link); |
| FILTER_RELEASE_LOCK(&pFilter->EventsLock, DispatchLevel); |
| |
| // Set the event to indicate we have a new NBL to process |
| KeSetEvent(&pFilter->EventWorkerThreadProcessNBLs, 0, FALSE); |
| } |
| |
| _IRQL_requires_max_(DISPATCH_LEVEL) |
| VOID |
| otLwfEventProcessingIndicateNewMacFrameCommand( |
| _In_ PMS_FILTER pFilter, |
| _In_ BOOLEAN DispatchLevel, |
| _In_reads_bytes_(BufferLength) |
| const uint8_t* Buffer, |
| _In_ uint8_t BufferLength |
| ) |
| { |
| POTLWF_MAC_FRAME_EVENT Event = FILTER_ALLOC_MEM(pFilter->FilterHandle, FIELD_OFFSET(OTLWF_MAC_FRAME_EVENT, Buffer) + BufferLength); |
| if (Event == NULL) |
| { |
| LogWarning(DRIVER_DATA_PATH, "Failed to alloc new OTLWF_MAC_FRAME_EVENT"); |
| return; |
| } |
| |
| Event->BufferLength = BufferLength; |
| memcpy(Event->Buffer, Buffer, BufferLength); |
| |
| // Add the event to the queue |
| FILTER_ACQUIRE_LOCK(&pFilter->EventsLock, DispatchLevel); |
| InsertTailList(&pFilter->MacFramesHead, &Event->Link); |
| FILTER_RELEASE_LOCK(&pFilter->EventsLock, DispatchLevel); |
| |
| // Set the event to indicate we have a new Mac Frame to process |
| KeSetEvent(&pFilter->EventWorkerThreadProcessMacFrames, 0, FALSE); |
| } |
| |
| _IRQL_requires_max_(DISPATCH_LEVEL) |
| VOID |
| otLwfEventProcessingIndicateNetBufferListsCancelled( |
| _In_ PMS_FILTER pFilter, |
| _In_ PVOID CancelId |
| ) |
| { |
| PLIST_ENTRY Link = NULL; |
| LIST_ENTRY CancelList = {0}; |
| InitializeListHead(&CancelList); |
| |
| // Build up a local list of all NBLs that need to be cancelled |
| NdisAcquireSpinLock(&pFilter->EventsLock); |
| Link = pFilter->NBLsHead.Flink; |
| while (Link != &pFilter->NBLsHead) |
| { |
| POTLWF_NBL_EVENT Event = CONTAINING_RECORD(Link, OTLWF_NBL_EVENT, Link); |
| Link = Link->Flink; |
| |
| if (NDIS_GET_NET_BUFFER_LIST_CANCEL_ID(Event->NetBufferLists) == CancelId) |
| { |
| RemoveEntryList(&Event->Link); |
| InsertTailList(&CancelList, &Event->Link); |
| } |
| } |
| NdisReleaseSpinLock(&pFilter->EventsLock); |
| |
| // Cancel all the NBLs |
| Link = CancelList.Flink; |
| while (Link != &CancelList) |
| { |
| POTLWF_NBL_EVENT Event = CONTAINING_RECORD(Link, OTLWF_NBL_EVENT, Link); |
| Link = Link->Flink; |
| |
| otLwfCompleteNBLs(pFilter, FALSE, Event->NetBufferLists, STATUS_CANCELLED); |
| |
| // Delete the event |
| NdisFreeMemory(Event, 0, 0); |
| } |
| } |
| |
| // Completes the NetBufferLists in the event |
| _IRQL_requires_max_(DISPATCH_LEVEL) |
| VOID |
| otLwfCompleteNBLs( |
| _In_ PMS_FILTER pFilter, |
| _In_ BOOLEAN DispatchLevel, |
| _In_ PNET_BUFFER_LIST NetBufferLists, |
| _In_ NTSTATUS Status |
| ) |
| { |
| LogVerbose(DRIVER_DATA_PATH, "otLwfCompleteNBLs, Filter:%p, NBL:%p, Status:%!STATUS!", pFilter, NetBufferLists, Status); |
| |
| // Set the status for all the NBLs |
| PNET_BUFFER_LIST CurrNbl = NetBufferLists; |
| while (CurrNbl) |
| { |
| NET_BUFFER_LIST_STATUS(CurrNbl) = Status; |
| CurrNbl = NET_BUFFER_LIST_NEXT_NBL(CurrNbl); |
| } |
| |
| NT_ASSERT(NetBufferLists); |
| |
| // Indicate the completion |
| NdisFSendNetBufferListsComplete( |
| pFilter->FilterHandle, |
| NetBufferLists, |
| DispatchLevel ? NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL : 0 |
| ); |
| } |
| |
| _Function_class_(DRIVER_CANCEL) |
| _Requires_lock_held_(_Global_cancel_spin_lock_) |
| _Releases_lock_(_Global_cancel_spin_lock_) |
| _IRQL_requires_min_(DISPATCH_LEVEL) |
| _IRQL_requires_(DISPATCH_LEVEL) |
| VOID |
| otLwfEventProcessingCancelIrp( |
| _Inout_ struct _DEVICE_OBJECT *DeviceObject, |
| _Inout_ _IRQL_uses_cancel_ struct _IRP *Irp |
| ) |
| { |
| PIRP IrpToCancel = NULL; |
| |
| UNREFERENCED_PARAMETER(DeviceObject); |
| |
| LogFuncEntryMsg(DRIVER_IOCTL, "Irp=%p", Irp); |
| |
| IoReleaseCancelSpinLock(Irp->CancelIrql); |
| |
| // |
| // Search for a queued up Irp and cancel it if we find it |
| // |
| |
| NdisAcquireSpinLock(&FilterListLock); |
| |
| // Iterate through each filter instance |
| for (PLIST_ENTRY Link = FilterModuleList.Flink; Link != &FilterModuleList; Link = Link->Flink) |
| { |
| PMS_FILTER pFilter = CONTAINING_RECORD(Link, MS_FILTER, FilterModuleLink); |
| |
| FILTER_ACQUIRE_LOCK(&pFilter->EventsLock, TRUE); |
| |
| // Iterate through all queued IRPs for the filter |
| PLIST_ENTRY IrpLink = pFilter->EventIrpListHead.Flink; |
| while (IrpLink != &pFilter->EventIrpListHead) |
| { |
| PIRP QueuedIrp = CONTAINING_RECORD(IrpLink, IRP, Tail.Overlay.ListEntry); |
| IrpLink = IrpLink->Flink; |
| |
| // If we find it, remove from the and prepare to complete it |
| if (QueuedIrp == Irp) |
| { |
| RemoveEntryList(&QueuedIrp->Tail.Overlay.ListEntry); |
| IrpToCancel = QueuedIrp; |
| break; |
| } |
| } |
| |
| FILTER_RELEASE_LOCK(&pFilter->EventsLock, TRUE); |
| |
| if (IrpToCancel) break; |
| } |
| |
| NdisReleaseSpinLock(&FilterListLock); |
| |
| if (IrpToCancel) |
| { |
| IrpToCancel->IoStatus.Status = STATUS_CANCELLED; |
| IrpToCancel->IoStatus.Information = 0; |
| IoCompleteRequest(IrpToCancel, IO_NO_INCREMENT); |
| } |
| |
| LogFuncExit(DRIVER_IOCTL); |
| } |
| |
| // Queues an Irp for processing |
| _IRQL_requires_max_(PASSIVE_LEVEL) |
| VOID |
| otLwfEventProcessingIndicateIrp( |
| _In_ PMS_FILTER pFilter, |
| _In_ PIRP Irp |
| ) |
| { |
| LogFuncEntryMsg(DRIVER_IOCTL, "Irp=%p", Irp); |
| |
| // Mark the Irp as pending |
| IoMarkIrpPending(Irp); |
| |
| FILTER_ACQUIRE_LOCK(&pFilter->EventsLock, FALSE); |
| |
| // Set the cancel routine for the Irp |
| IoSetCancelRoutine(Irp, otLwfEventProcessingCancelIrp); |
| |
| // Queue the Irp up for processing |
| InsertTailList(&pFilter->EventIrpListHead, &Irp->Tail.Overlay.ListEntry); |
| |
| FILTER_RELEASE_LOCK(&pFilter->EventsLock, FALSE); |
| |
| // Set the event to indicate we have an Irp to process |
| KeSetEvent(&pFilter->EventWorkerThreadProcessIrp, 0, FALSE); |
| |
| LogFuncExit(DRIVER_IOCTL); |
| } |
| |
| // Processes the next OpenThread IoCtl Irp |
| _IRQL_requires_max_(PASSIVE_LEVEL) |
| VOID |
| otLwfEventProcessingNextIrp( |
| _In_ PMS_FILTER pFilter |
| ) |
| { |
| PIRP Irp = NULL; |
| |
| LogFuncEntry(DRIVER_IOCTL); |
| |
| do |
| { |
| // Reset pointer |
| Irp = NULL; |
| |
| // Get the next Irp in the queue |
| FILTER_ACQUIRE_LOCK(&pFilter->EventsLock, FALSE); |
| if (!IsListEmpty(&pFilter->EventIrpListHead)) |
| { |
| PLIST_ENTRY Link = RemoveHeadList(&pFilter->EventIrpListHead); |
| Irp = CONTAINING_RECORD(Link, IRP, Tail.Overlay.ListEntry); |
| |
| // Clear the cancel routine since we are processing this now |
| KIRQL irql; |
| IoAcquireCancelSpinLock(&irql); |
| IoSetCancelRoutine(Irp, NULL); |
| IoReleaseCancelSpinLock(irql); |
| } |
| FILTER_RELEASE_LOCK(&pFilter->EventsLock, FALSE); |
| |
| if (Irp) |
| { |
| otLwfCompleteOpenThreadIrp(pFilter, Irp); |
| } |
| |
| } while (Irp); |
| |
| LogFuncExit(DRIVER_IOCTL); |
| } |
| |
| // Indicates a energy scan was completed |
| _IRQL_requires_max_(DISPATCH_LEVEL) |
| VOID |
| otLwfEventProcessingIndicateEnergyScanResult( |
| _In_ PMS_FILTER pFilter, |
| _In_ CHAR MaxRssi |
| ) |
| { |
| LogFuncEntry(DRIVER_IOCTL); |
| |
| // Cache the Rssi |
| pFilter->otLastEnergyScanMaxRssi = MaxRssi; |
| |
| // Set the event to indicate we should indicate the state back to OpenThread |
| KeSetEvent(&pFilter->EventWorkerThreadEnergyScanComplete, 0, FALSE); |
| |
| LogFuncExit(DRIVER_IOCTL); |
| } |
| |
| // Helper function to copy data out of a NET_BUFFER |
| __forceinline |
| NTSTATUS |
| CopyDataBuffer( |
| _In_ PNET_BUFFER NetBuffer, |
| _In_ ULONG Size, |
| _Out_writes_bytes_all_(Size) |
| PVOID Destination |
| ) |
| { |
| NTSTATUS status = STATUS_SUCCESS; |
| |
| // Read the data out of the NetBuffer |
| PVOID mem = NdisGetDataBuffer(NetBuffer, Size, Destination, 1, 0); |
| if (mem == NULL) |
| { |
| NT_ASSERT(FALSE); |
| status = STATUS_INSUFFICIENT_RESOURCES; |
| goto error; |
| } |
| |
| // If we get a different output memory address, then copy that data to Destination; |
| // otherwise, it was already copied there |
| if (mem != Destination) |
| { |
| RtlCopyMemory(Destination, mem, Size); |
| } |
| |
| error: |
| |
| return status; |
| } |
| |
| _Function_class_(EXT_CALLBACK) |
| _IRQL_requires_(DISPATCH_LEVEL) |
| _IRQL_requires_same_ |
| VOID |
| otLwfEventProcessingTimer( |
| _In_ PEX_TIMER Timer, |
| _In_opt_ PVOID Context |
| ) |
| { |
| if (Context == NULL) return; |
| |
| PMS_FILTER pFilter = (PMS_FILTER)Context; |
| UNREFERENCED_PARAMETER(Timer); |
| |
| #ifdef DEBUG_TIMING |
| LogInfo(DRIVER_DEFAULT, "Event processing high precision timer fired."); |
| #endif |
| |
| pFilter->EventTimerState = OT_EVENT_TIMER_FIRED; |
| |
| // Indicate event to worker thread to update the wait time |
| KeSetEvent(&pFilter->EventWorkerThreadWaitTimeUpdated, 0, FALSE); |
| } |
| |
| // Worker thread for processing all events |
| _Use_decl_annotations_ |
| VOID |
| otLwfEventWorkerThread( |
| PVOID Context |
| ) |
| { |
| PMS_FILTER pFilter = (PMS_FILTER)Context; |
| NT_ASSERT(pFilter); |
| |
| LogFuncEntry(DRIVER_DEFAULT); |
| |
| PKEVENT WaitEvents[] = |
| { |
| &pFilter->EventWorkerThreadStopEvent, |
| &pFilter->EventWorkerThreadProcessNBLs, |
| &pFilter->EventWorkerThreadProcessMacFrames, |
| &pFilter->EventWorkerThreadWaitTimeUpdated, |
| &pFilter->EventWorkerThreadProcessTasklets, |
| &pFilter->SendNetBufferListComplete, |
| &pFilter->EventWorkerThreadProcessIrp, |
| &pFilter->EventWorkerThreadProcessAddressChanges, |
| &pFilter->EventWorkerThreadEnergyScanComplete |
| }; |
| |
| KWAIT_BLOCK WaitBlocks[ARRAYSIZE(WaitEvents)] = { 0 }; |
| |
| // Space to processing buffers |
| const ULONG MessageBufferSize = 1280; |
| PUCHAR MessageBuffer = FILTER_ALLOC_MEM(pFilter->FilterHandle, MessageBufferSize); |
| if (MessageBuffer == NULL) |
| { |
| LogError(DRIVER_DATA_PATH, "Failed to allocate 1280 bytes for MessageBuffer!"); |
| return; |
| } |
| |
| #if DEBUG_ALLOC |
| // Initialize the list head for allocations |
| InitializeListHead(&pFilter->otOutStandingAllocations); |
| |
| // Cache the Thread ID |
| pFilter->otThreadId = PsGetCurrentThreadId(); |
| #endif |
| |
| // Initialize the radio layer |
| otLwfRadioInit(pFilter); |
| |
| // Calculate the size of the otInstance and allocate it |
| pFilter->otInstanceSize = 0; |
| (VOID)otInstanceInit(NULL, &pFilter->otInstanceSize); |
| NT_ASSERT(pFilter->otInstanceSize != 0); |
| |
| // Add space for a pointer back to the filter |
| pFilter->otInstanceSize += sizeof(PMS_FILTER); |
| |
| // Allocate the buffer |
| pFilter->otInstanceBuffer = (PUCHAR)FILTER_ALLOC_MEM(pFilter->FilterHandle, (ULONG)pFilter->otInstanceSize); |
| if (pFilter == NULL) |
| { |
| LogWarning(DRIVER_DEFAULT, "Failed to allocate otInstance buffer, 0x%x bytes", (ULONG)pFilter->otInstanceSize); |
| goto exit; |
| } |
| RtlZeroMemory(pFilter->otInstanceBuffer, pFilter->otInstanceSize); |
| |
| // Store the pointer and decrement the size |
| memcpy(pFilter->otInstanceBuffer, &pFilter, sizeof(PMS_FILTER)); |
| pFilter->otInstanceSize -= sizeof(PMS_FILTER); |
| |
| // Initialize the OpenThread library |
| pFilter->otCachedRole = OT_DEVICE_ROLE_DISABLED; |
| pFilter->otCtx = otInstanceInit(pFilter->otInstanceBuffer + sizeof(PMS_FILTER), &pFilter->otInstanceSize); |
| NT_ASSERT(pFilter->otCtx); |
| if (pFilter->otCtx == NULL) |
| { |
| LogError(DRIVER_DEFAULT, "otInstanceInit failed, otInstanceSize = %u bytes", (ULONG)pFilter->otInstanceSize); |
| goto exit; |
| } |
| |
| // Make sure our helper function returns the right pointer for the filter, given the openthread instance |
| NT_ASSERT(otCtxToFilter(pFilter->otCtx) == pFilter); |
| |
| // Disable Icmp (ping) handling |
| otIcmp6SetEchoEnabled(pFilter->otCtx, FALSE); |
| |
| // Register callbacks with OpenThread |
| otSetStateChangedCallback(pFilter->otCtx, otLwfStateChangedCallback, pFilter); |
| otIp6SetReceiveCallback(pFilter->otCtx, otLwfReceiveIp6DatagramCallback, pFilter); |
| |
| // Query the current addresses from TCPIP and cache them |
| (void)otLwfInitializeAddresses(pFilter); |
| |
| // Initialze media connect state to disconnected |
| otLwfIndicateLinkState(pFilter, MediaConnectStateDisconnected); |
| |
| for (;;) |
| { |
| NTSTATUS status = STATUS_SUCCESS; |
| |
| if (pFilter->NextAlarmTickCount.QuadPart == 0) |
| { |
| #ifdef DEBUG_TIMING |
| LogVerbose(DRIVER_DEFAULT, "Event Processing waiting for next event."); |
| #endif |
| |
| // Wait for event to stop or process event to fire |
| status = KeWaitForMultipleObjects(ARRAYSIZE(WaitEvents), (PVOID*)WaitEvents, WaitAny, Executive, KernelMode, FALSE, NULL, WaitBlocks); |
| } |
| else |
| { |
| LARGE_INTEGER SystemTickCount; |
| KeQueryTickCount(&SystemTickCount); |
| |
| if (pFilter->NextAlarmTickCount.QuadPart > SystemTickCount.QuadPart) |
| { |
| // Create the relative (negative) time to wait on |
| LARGE_INTEGER Timeout; |
| Timeout.QuadPart = (SystemTickCount.QuadPart - pFilter->NextAlarmTickCount.QuadPart) * KeQueryTimeIncrement(); |
| |
| #ifdef DEBUG_TIMING |
| LogVerbose(DRIVER_DEFAULT, "Event Processing waiting for next event, with timeout, %d ms.", (int)(Timeout.QuadPart / -10000)); |
| #endif |
| |
| // Wait for event to stop or process event to fire or timeout |
| status = KeWaitForMultipleObjects(ARRAYSIZE(WaitEvents), (PVOID*)WaitEvents, WaitAny, Executive, KernelMode, FALSE, &Timeout, WaitBlocks); |
| } |
| else |
| { |
| #ifdef DEBUG_TIMING |
| LogInfo(DRIVER_DEFAULT, "Event Processing running immediately."); |
| #endif |
| |
| // No need to wait |
| status = STATUS_TIMEOUT; |
| } |
| } |
| |
| // If it is the first event, then we are shutting down. Exit loop and terminate thread |
| if (status == STATUS_WAIT_0) |
| { |
| LogInfo(DRIVER_DEFAULT, "Received event worker thread shutdown event."); |
| break; |
| } |
| |
| #ifdef DEBUG_TIMING |
| LogVerbose(DRIVER_DEFAULT, "Event Processing status=0x%x", status); |
| #endif |
| |
| // |
| // Event fired to process events |
| // |
| |
| if (status == STATUS_TIMEOUT || |
| (pFilter->EventTimerState == OT_EVENT_TIMER_FIRED && status == STATUS_WAIT_0 + 3)) |
| { |
| // Reset the wait timeout |
| pFilter->NextAlarmTickCount.QuadPart = 0; |
| pFilter->EventTimerState = OT_EVENT_TIMER_NOT_RUNNING; |
| |
| // Indicate to OpenThread that the alarm has fired |
| otPlatAlarmFired(pFilter->otCtx); |
| } |
| else if (status == STATUS_WAIT_0 + 1) // EventWorkerThreadProcessNBLs fired |
| { |
| // Go through the queue until there are no more items |
| for (;;) |
| { |
| POTLWF_NBL_EVENT Event = NULL; |
| NdisAcquireSpinLock(&pFilter->EventsLock); |
| |
| // Just get the first item, if available |
| if (!IsListEmpty(&pFilter->NBLsHead)) |
| { |
| PLIST_ENTRY Link = RemoveHeadList(&pFilter->NBLsHead); |
| Event = CONTAINING_RECORD(Link, OTLWF_NBL_EVENT, Link); |
| } |
| |
| NdisReleaseSpinLock(&pFilter->EventsLock); |
| |
| // Break out of the loop if we have emptied the queue |
| if (Event == NULL) break; |
| |
| NT_ASSERT(Event->NetBufferLists); |
| NTSTATUS NblStatus = STATUS_INSUFFICIENT_RESOURCES; |
| |
| // Process the event |
| PNET_BUFFER_LIST CurrNbl = Event->NetBufferLists; |
| while (CurrNbl != NULL) |
| { |
| PNET_BUFFER CurrNb = NET_BUFFER_LIST_FIRST_NB(CurrNbl); |
| while (CurrNb != NULL) |
| { |
| NT_ASSERT(NET_BUFFER_DATA_LENGTH(CurrNb) <= MessageBufferSize); |
| if (NET_BUFFER_DATA_LENGTH(CurrNb) <= MessageBufferSize) |
| { |
| // Copy NB data into message |
| if (NT_SUCCESS(CopyDataBuffer(CurrNb, NET_BUFFER_DATA_LENGTH(CurrNb), MessageBuffer))) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| // Create a new message |
| otMessage *message = otIp6NewMessage(pFilter->otCtx, TRUE); |
| if (message) |
| { |
| // Write to the message |
| error = otMessageAppend(message, MessageBuffer, (uint16_t)NET_BUFFER_DATA_LENGTH(CurrNb)); |
| if (error != OT_ERROR_NONE) |
| { |
| LogError(DRIVER_DATA_PATH, "otAppendMessage failed with %!otError!", error); |
| otMessageFree(message); |
| } |
| else |
| { |
| IPV6_HEADER* v6Header = (IPV6_HEADER*)MessageBuffer; |
| |
| LogVerbose(DRIVER_DATA_PATH, "Filter: %p, IP6_SEND: %p : %!IPV6ADDR! => %!IPV6ADDR! (%u bytes)", |
| pFilter, CurrNbl, &v6Header->SourceAddress, &v6Header->DestinationAddress, |
| NET_BUFFER_DATA_LENGTH(CurrNb)); |
| |
| #ifdef LOG_BUFFERS |
| otLogBuffer(MessageBuffer, NET_BUFFER_DATA_LENGTH(CurrNb)); |
| #endif |
| |
| // Send message (it will free 'message') |
| error = otIp6Send(pFilter->otCtx, message); |
| if (error != OT_ERROR_NONE) |
| { |
| LogError(DRIVER_DATA_PATH, "otSendIp6Datagram failed with %!otError!", error); |
| } |
| else |
| { |
| NblStatus = STATUS_SUCCESS; |
| } |
| } |
| } |
| else |
| { |
| LogError(DRIVER_DATA_PATH, "otNewIPv6Message failed!"); |
| } |
| } |
| } |
| |
| CurrNb = NET_BUFFER_NEXT_NB(CurrNb); |
| } |
| |
| CurrNbl = NET_BUFFER_LIST_NEXT_NBL(CurrNbl); |
| } |
| |
| if (Event->NetBufferLists) |
| { |
| // Complete the NBLs |
| otLwfCompleteNBLs(pFilter, FALSE, Event->NetBufferLists, NblStatus); |
| } |
| |
| // Free the event |
| NdisFreeMemory(Event, 0, 0); |
| } |
| } |
| else if (status == STATUS_WAIT_0 + 2) // EventWorkerThreadProcessMacFrames fired |
| { |
| // Go through the queue until there are no more items |
| for (;;) |
| { |
| POTLWF_MAC_FRAME_EVENT Event = NULL; |
| NdisAcquireSpinLock(&pFilter->EventsLock); |
| |
| // Just get the first item, if available |
| if (!IsListEmpty(&pFilter->MacFramesHead)) |
| { |
| PLIST_ENTRY Link = RemoveHeadList(&pFilter->MacFramesHead); |
| Event = CONTAINING_RECORD(Link, OTLWF_MAC_FRAME_EVENT, Link); |
| } |
| |
| NdisReleaseSpinLock(&pFilter->EventsLock); |
| |
| // Break out of the loop if we have emptied the queue |
| if (Event == NULL) break; |
| |
| // Read the initial length value and validate |
| uint16_t packetLength = 0; |
| if (try_spinel_datatype_unpack( |
| Event->Buffer, |
| Event->BufferLength, |
| SPINEL_DATATYPE_UINT16_S, |
| &packetLength) && |
| packetLength <= sizeof(pFilter->otReceiveMessage) && |
| Event->BufferLength > sizeof(uint16_t) + packetLength) |
| { |
| pFilter->otReceiveFrame.mLength = (uint8_t)packetLength; |
| |
| uint8_t offset = 2; |
| uint8_t length = Event->BufferLength - 2; |
| |
| if (packetLength != 0) |
| { |
| memcpy(&pFilter->otReceiveMessage, Event->Buffer + offset, packetLength); |
| offset += pFilter->otReceiveFrame.mLength; |
| length -= pFilter->otReceiveFrame.mLength; |
| } |
| |
| otError errorCode; |
| int8_t noiseFloor = -128; |
| uint16_t flags = 0; |
| if (try_spinel_datatype_unpack( |
| Event->Buffer + offset, |
| length, |
| SPINEL_DATATYPE_INT8_S |
| SPINEL_DATATYPE_INT8_S |
| SPINEL_DATATYPE_UINT16_S |
| SPINEL_DATATYPE_STRUCT_S( // PHY-data |
| SPINEL_DATATYPE_UINT8_S // 802.15.4 channel |
| SPINEL_DATATYPE_UINT8_S // 802.15.4 LQI |
| ) |
| SPINEL_DATATYPE_STRUCT_S( // Vendor-data |
| SPINEL_DATATYPE_UINT_PACKED_S |
| ), |
| &pFilter->otReceiveFrame.mPower, |
| &noiseFloor, |
| &flags, |
| &pFilter->otReceiveFrame.mChannel, |
| &pFilter->otReceiveFrame.mLqi, |
| &errorCode)) |
| { |
| otLwfRadioReceiveFrame(pFilter, errorCode); |
| } |
| } |
| |
| // Free the event |
| NdisFreeMemory(Event, 0, 0); |
| } |
| } |
| else if (status == STATUS_WAIT_0 + 3) // EventWorkerThreadWaitTimeUpdated fired |
| { |
| // Nothing to do, the next time we wait, we will be using the updated time |
| } |
| else if (status == STATUS_WAIT_0 + 4) // EventWorkerThreadProcessTasklets fired |
| { |
| // Process all tasklets that were indicated to us from OpenThread |
| otTaskletsProcess(pFilter->otCtx); |
| } |
| else if (status == STATUS_WAIT_0 + 5) // SendNetBufferListComplete fired |
| { |
| // Handle the completion of the NBL send |
| otLwfRadioTransmitFrameDone(pFilter); |
| } |
| else if (status == STATUS_WAIT_0 + 6) // EventWorkerThreadProcessIrp fired |
| { |
| // Process any IRPs that were pended |
| otLwfEventProcessingNextIrp(pFilter); |
| } |
| else if (status == STATUS_WAIT_0 + 7) // EventWorkerThreadProcessAddressChanges fired |
| { |
| // Go through the queue until there are no more items |
| for (;;) |
| { |
| POTLWF_ADDR_EVENT Event = NULL; |
| NdisAcquireSpinLock(&pFilter->EventsLock); |
| |
| // Get the next item, if available |
| if (!IsListEmpty(&pFilter->AddressChangesHead)) |
| { |
| PLIST_ENTRY Link = RemoveHeadList(&pFilter->AddressChangesHead); |
| Event = CONTAINING_RECORD(Link, OTLWF_ADDR_EVENT, Link); |
| } |
| |
| NdisReleaseSpinLock(&pFilter->EventsLock); |
| |
| // Break out of the loop if we have emptied the queue |
| if (Event == NULL) break; |
| |
| // Process the address change on the Openthread thread |
| otLwfEventProcessingAddressChanged(pFilter, Event->NotificationType, &Event->Address); |
| |
| // Free the event |
| NdisFreeMemory(Event, 0, 0); |
| } |
| } |
| else if (status == STATUS_WAIT_0 + 8) // EventWorkerThreadEnergyScanComplete fired |
| { |
| // Indicate energy scan complete |
| otPlatRadioEnergyScanDone(pFilter->otCtx, pFilter->otLastEnergyScanMaxRssi); |
| } |
| else |
| { |
| LogWarning(DRIVER_DEFAULT, "Unexpected wait result, %!STATUS!", status); |
| } |
| |
| // If we have a frame ready to transmit, do it now if we are allowed to transmit |
| if (pFilter->otRadioState == OT_RADIO_STATE_TRANSMIT && !pFilter->SendPending) |
| { |
| otLwfRadioTransmitFrame(pFilter); |
| } |
| } |
| |
| exit: |
| |
| otLwfReleaseInstance(pFilter); |
| |
| if (pFilter->otInstanceBuffer != NULL) |
| { |
| NdisFreeMemory(pFilter->otInstanceBuffer, 0, 0); |
| } |
| |
| LogFuncExit(DRIVER_DEFAULT); |
| |
| FILTER_FREE_MEM(MessageBuffer); |
| |
| PsTerminateSystemThread(STATUS_SUCCESS); |
| } |