VOID HvlWaitForCtxSProcessingAndLock(_In_ PHVL pHvl) { HvlLock(pHvl); while (HvlIsCtxSInProgress(pHvl)) { HvlUnlock(pHvl); NdisMSleep(10 * 1000); // 10 msec HvlLock(pHvl); } }
VOID Hvl11UnblockTimedCtxS(_Inout_ PHVL pHvl) { HvlLock(pHvl); pHvl->ulStatusFlags &= ~HVL_TIMED_CTXS_BLOCKED; HvlUnlock(pHvl); }
NDIS_STATUS Hvl11DeregisterVNic( _In_ PHVL pHvl, _In_ PVNIC pVNic ) { NDIS_STATUS ndisStatus = NDIS_STATUS_SUCCESS; HvlLock(pHvl); HvlRemoveVNicFromCtx(pHvl, pVNic); // remove it from the list of VNICs that the HVL maintains RemoveEntryList (&pVNic->VNicLink); InitializeListHead(&pVNic->VNicLink); // if there was a cached notification sourced by this VNIC clear it now if (pHvl->CachedChannelNotification.Header.pSourceVNic == pVNic) { HvlClearCachedNotification(pHvl); } HvlUnlock(pHvl); return ndisStatus; }
NDIS_STATUS Hvl11RegisterVNic( _In_ PHVL pHvl, _In_ PVNIC pVNic ) { NDIS_STATUS ndisStatus = NDIS_STATUS_SUCCESS; PHVL_CONTEXT pCtx = NULL; do { HvlLock(pHvl); // add it to the HVL's list of VNICs InsertTailList(&pHvl->VNiclist, &pVNic->VNicLink); HvlAssignVNicToContext(pHvl, pVNic, &pCtx); /* Initially make it part of the inactive context list. It will be picked up whenever we next context switch to it */ InsertTailList(&pHvl->InactiveContextList, &pCtx->Link); HvlUnlock(pHvl); // notify it of any existing cached notifications if (pHvl->CachedChannelNotification.Header.pSourceVNic) { VNic11Notify(pVNic, &pHvl->CachedChannelNotification); } } while (FALSE); return ndisStatus; }
VOID HvlWaitForPendingThreads( _In_ PHVL pHvl ) { ULONG ulNumIter = 0; BOOLEAN fThreadsFinished = FALSE; /* Wait for any pending threads to complete before freeing stuff */ while (!fThreadsFinished) { if ( ++ulNumIter > 30000) { // 30 seconds have passed - something must be wrong ASSERTMSG("Hvl pending thread count hasn't gone to 0 in last 30 seconds. Check driver state\n", FALSE); } HvlLock(pHvl); if (pHvl->ulNumThreadsPending == 0) { fThreadsFinished = TRUE; } HvlUnlock(pHvl); if (!fThreadsFinished) { NdisMSleep(1000); } } }
VOID HvlNotificationsWorkItem( _In_ PVOID Context, _In_ NDIS_HANDLE NdisIoWorkItemHandle ) { PHVL pHvl = NULL; LIST_ENTRY *pEntryNotif = NULL; PHVL_NOTIFICATION pNotification = NULL; UNREFERENCED_PARAMETER(NdisIoWorkItemHandle); pHvl = (PHVL) Context; HvlLock(pHvl); pEntryNotif = pHvl->NotificationsQueue.Flink; while (pEntryNotif != &pHvl->NotificationsQueue) { pNotification = CONTAINING_RECORD(pEntryNotif, HVL_NOTIFICATION, Link); RemoveEntryList (&pNotification->Link); HvlNotifyAllVNics(pHvl, pNotification->pvNotif); pEntryNotif = pEntryNotif->Flink; HvlFreeNotification(pNotification); } pHvl->fNotificationsWorkItemRunning = FALSE; pHvl->ulNumThreadsPending--; HvlUnlock(pHvl); }
VOID HvlNotifyAllVNics( PHVL pHvl, PVOID pvNotif ) { LIST_ENTRY *pEntryVNic = NULL; PVNIC pVNic = NULL; PNOTIFICATION_DATA_HEADER pHdr = NULL; ASSERT(HvlIsLocked(pHvl)); pHdr = (PNOTIFICATION_DATA_HEADER)pvNotif; pEntryVNic = pHvl->VNiclist.Flink; while (pEntryVNic != &pHvl->VNiclist) { pVNic = CONTAINING_RECORD(pEntryVNic, VNIC, VNicLink); // do not notify if this VNIC is the source of the notification if (pVNic != pHdr->pSourceVNic) { _Analysis_assume_lock_held_(pHvl->Lock.SpinLock); HvlUnlock(pHvl); VNic11Notify(pVNic, pvNotif); HvlLock(pHvl); } pEntryVNic = pEntryVNic->Flink; } // Also send the notification to the helper port pEntryVNic = pHvl->pHelperPortCtx->VNicList.Flink; pVNic = CONTAINING_RECORD(pEntryVNic, VNIC, CtxLink); _Analysis_assume_lock_held_(pHvl->Lock.SpinLock); HvlUnlock(pHvl); VNic11Notify(pVNic, pvNotif); HvlLock(pHvl); }
VOID Hvl11Notify( PHVL pHvl, PVOID pvNotif ) { NDIS_STATUS ndisStatus = NDIS_STATUS_SUCCESS; PNOTIFICATION_DATA_HEADER pHdr = NULL; pHdr = (PNOTIFICATION_DATA_HEADER)pvNotif; HvlLock(pHvl); do { // update our internal state HvlCacheNotification(pHvl, pvNotif); ndisStatus = HvlQueueNotification(pHvl, pvNotif); if (NDIS_STATUS_SUCCESS != ndisStatus) { MpTrace(COMP_HVL, DBG_SERIOUS, ("HvlQueueNotification failed %!x!\n", ndisStatus)); break; } if (!pHvl->fNotificationsWorkItemRunning) { pHvl->fNotificationsWorkItemRunning = TRUE; pHvl->ulNumThreadsPending++; NdisQueueIoWorkItem( pHvl->NotificationsWorkItemHandle, HvlNotificationsWorkItem, pHvl ); } else { // the work item is already running. It will take care of processing this notification } } while (FALSE); HvlUnlock(pHvl); }
NDIS_STATUS Hvl11DeregisterHelperPort( _In_ PHVL pHvl, _In_ PVNIC pVNic ) { NDIS_STATUS ndisStatus = NDIS_STATUS_SUCCESS; UNREFERENCED_PARAMETER(pVNic); ASSERT(HvlIsHelperVNic(pHvl, pVNic)); HvlLock(pHvl); HvlRemoveVNicFromCtx(pHvl, pVNic); HvlUnlock(pHvl); return ndisStatus; }
NDIS_STATUS Hvl11RegisterHelperPort( _In_ PHVL pHvl, _In_ PVNIC pVNic ) { NDIS_STATUS ndisStatus = NDIS_STATUS_SUCCESS; PHVL_CONTEXT pCtx = NULL; do { HvlLock(pHvl); HvlAssignVNicToContext(pHvl, pVNic, &pCtx); pHvl->pHelperPortCtx = pCtx; HvlUnlock(pHvl); } while (FALSE); return ndisStatus; }
VOID Hvl11EnableContextSwitches( _In_ PHVL pHvl ) { HvlLock(pHvl); if (!pHvl->fVirtualizationEnabled) { pHvl->fVirtualizationEnabled = TRUE; NdisQueueIoWorkItem( pHvl->CtxSWorkItemHandle, HvlCtxSWorkItem, pHvl ); // run the context switch logic for the first time KeSetEvent(&pHvl->CtxSEvent, 0, FALSE); } HvlUnlock(pHvl); }
VOID HvlNotifyAllVNicsInContext( PHVL pHvl, PHVL_CONTEXT pCtx, PVNIC_FUNCTION pVnicFn, ULONG ulFlags ) { LIST_ENTRY *pEntryVNic = NULL; PVNIC pVNic = NULL; _Analysis_assume_lock_held_(pHvl->Lock.SpinLock); HvlUnlock(pHvl); pEntryVNic = pCtx->VNicList.Flink; while (pEntryVNic != &pCtx->VNicList) { pVNic = CONTAINING_RECORD(pEntryVNic, VNIC, CtxLink); pVnicFn(pVNic, ulFlags); pEntryVNic = pEntryVNic->Flink; } HvlLock(pHvl); }
VOID HvlCtxSProcessing( PHVL pHvl, PHVL_CONTEXT pNextActiveContext, BOOLEAN fMerged, ULONG ulFlags ) { //NDIS_STATUS ndisStatus = NDIS_STATUS_SUCCESS; PHVL_CONTEXT pCurrCtx = NULL, pNextCtx = NULL; BOOLEAN fSameCtx = FALSE; LIST_ENTRY *pEntryVNic = NULL; PVNIC pVNic = NULL; PVNIC pHelperVNic = NULL; LIST_ENTRY *pHelperEntry = NULL; ASSERT(HvlIsLocked(pHvl)); // we can have only one instance of this routine running ASSERT(!HvlIsCtxSInProgress(pHvl)); HvlSetCtxSInProgress(pHvl); do { // get the current active context pCurrCtx = pHvl->pActiveContext; if (NULL == pNextActiveContext) { // Use our logic to find the next active context pNextCtx = HvlFindNextCtx(pHvl); } else { // the caller specified a particular context to switch to pNextCtx = pNextActiveContext; } if (pCurrCtx && pNextCtx == pCurrCtx) { if (!fMerged) { /* No context switch needs to happen. */ MpTrace(COMP_HVL, DBG_NORMAL, ("The next context (%p) is the same as the current one. Not doing a context switch\n", pNextCtx)); #if DBG pEntryVNic = pNextCtx->VNicList.Flink; while (pEntryVNic != &pNextCtx->VNicList) { pVNic = CONTAINING_RECORD(pEntryVNic, VNIC, CtxLink); ASSERT(pVNic->fActive); pEntryVNic = pEntryVNic->Flink; } #endif break; } else { MpTrace(COMP_HVL, DBG_NORMAL, ("The next context (%p) is the same as the current one. But we have just done a merge. Continuing with the context switch\n", pNextCtx)); /* remembe that our current and next contexts are the same. We need to do this so that we do not update the current context below */ fSameCtx = TRUE; } } if (NULL == pNextCtx) { /* No context switch needs to happen. */ MpTrace(COMP_HVL, DBG_NORMAL, ("No contexts exist. Not doing a context switch\n")); break; } /* Now update the Hvl's state about which VNIC is loosing exclusive access. It is important to update this before VNic11CtxSFromVNic call has been made. This ensures that the VNIC does not gain access while it is being moved away from active context */ if (VNIC_FLAG_HVL_ACTIVATED & ulFlags) { // this is an activation by the helper port ASSERT(pNextActiveContext); ASSERT(pNextActiveContext->ulNumVNics == 1); if (NULL == pNextActiveContext) { return; } pEntryVNic = pNextActiveContext->VNicList.Flink; pVNic = CONTAINING_RECORD(pEntryVNic, VNIC, CtxLink); pHelperEntry = pHvl->pHelperPortCtx->VNicList.Flink; pHelperVNic = CONTAINING_RECORD(pHelperEntry, VNIC, CtxLink); // Helper port is delegating exclusive access to someone else. Take away the delegated access if (pHvl->pExAccessDelegatedVNic && pVNic != pHvl->pExAccessDelegatedVNic ) { pHvl->pExAccessDelegatedVNic = NULL; } } // inform the active VNICs that they are going to be become inactive if (NULL != pCurrCtx) { HvlNotifyAllVNicsInContext(pHvl, pCurrCtx, VNic11CtxSFromVNic, 0); } /* Tell the hardware layer that we are now going to context switch. Note that this has to happen only after the VNICs have been told they are becoming inactive. The reason for this is that receives should continue to happen while the VNICs perform the tasks they need to become inactive (e.g. send PS packet to the AP) Also note that we need to give up our lock before we call into the hardware. This is because the Hw might need to wait for some operations to complete before it can return this call. Since the context switch processing runs in a single thread, we can safely leave the lock and re-acquire it */ HvlUnlock(pHvl); Hw11CtxSStart(pHvl->Hw); HvlLock(pHvl); // ask the VNICs in the new context to program the hardware HvlNotifyAllVNicsInContext(pHvl, pNextCtx, VNic11ProgramHw, 0); /* program the hardware for the new context - this programs any settings that need to be merged across the VNICs */ HvlProgramHw(pHvl, pNextCtx); /* Now update the Hvl's state about which VNIC is active or has exclusive access. It is important to update this after the VNic11ProgramHw and HvlProgramHw calls have been made. This ensures that the VNIC does not gain access prematurely */ if (VNIC_FLAG_HVL_ACTIVATED & ulFlags) { // this is an activation by the helper port // pHelperEntry and pHelperVNic must have been already calculated above ASSERT(pHelperEntry && pHelperVNic); if (pVNic != pHelperVNic) { // Some VNIC is being delegated exclusive access pHvl->pExAccessDelegatedVNic = pVNic; } } else if (VNIC_FLAG_GRANTED_EX_ACCESS & ulFlags) { // a VNIC is being given exclusive access. Update our state to reflect this ASSERT(pNextActiveContext); ASSERT(pNextActiveContext->ulNumVNics == 1); if (NULL == pNextActiveContext) { return; } pEntryVNic = pNextActiveContext->VNicList.Flink; pVNic = CONTAINING_RECORD(pEntryVNic, VNIC, CtxLink); HvlGrantExAccess(pHvl, pVNic); } if (!fSameCtx) { HvlUpdateActiveCtx(pHvl, pCurrCtx, pNextCtx); MpTrace(COMP_HVL, DBG_NORMAL, ("Current context = %p, next context = %p, fSameCtx = %s\n", pCurrCtx, pNextCtx, fSameCtx?"TRUE":"FALSE")); } // tell the hardware that we are ready with the new context HvlUnlock(pHvl); Hw11CtxSComplete(pHvl->Hw); HvlLock(pHvl); // inform the VNICs that they are now active HvlNotifyAllVNicsInContext(pHvl, pNextCtx, VNic11CtxSToVNic, ulFlags); } while (FALSE); HvlCtxSDone(pHvl); return; }
NDIS_STATUS Hvl11ReleaseExAccess(PHVL pHvl, PVNIC pVNic) { NDIS_STATUS ndisStatus = NDIS_STATUS_SUCCESS; PVNIC pExVNic = NULL; PHVL_EX_ACCESS_REQ pExReq = NULL; MpTrace(COMP_HVL, DBG_NORMAL, ("VNic(%d) releasing exclusive access\n", VNIC_PORT_NO)); HvlLock(pHvl); pExReq = HvlDequeueNextReqForVNic(pHvl, pVNic); if (pExReq && 0 == pExReq->ulRefCount) { /* This is the last release by this VNIC. Actually take away exclusive access if this is the current owner of exclusive access */ pExVNic = HvlGetExAccessVNic(pHvl); if (pExVNic == pVNic) { MpTrace(COMP_HVL, DBG_NORMAL, ("VNic(%d) currently owns exclusive access. Giving it up and removing it from the queue\n", VNIC_PORT_NO)); HvlReleaseExAccess(pHvl, pVNic); // set the event so that some other VNIC waiting for access can now get it KeSetEvent(&pHvl->ExAccessEvent, 0, FALSE); } else { /* The VNIC is releasing exclusive access even when it doesn't own it. This can happen - take for example the following 1. There is a helper port and an extensible station port 2. A Pause is happening and the helper port has exclusive access. 3. The station asks its VNIC to perform a dot11 reset 4. The station's VNIC requests exclusive access. Since the helper port has it, its request is queued 5. The helper port activates the station as part of its Pause processing 6. The station VNIC's CtxSToVNic is called and the VNIC learns it has exclusive access 7. The station VNIC completes its reset operation and asks Hvl to release exclusive access 8. The calls comes into the HVL and we would land up in this else clause. The correct thing to do here is to simply remove the station VNIC's request from the queue which is what we have done here */ MpTrace(COMP_HVL, DBG_NORMAL, ("VNic(%d) doesn't own exclusive access currently. Just removing it from the queue\n", VNIC_PORT_NO)); if (NULL == pExVNic) { //no one has exclusive access right now. set the event so that some other //VNIC waiting for access can now get it KeSetEvent(&pHvl->ExAccessEvent, 0, FALSE); } } // delete the request if (HvlIsPreAllocatedRequest(pHvl, pExReq)) { // it is a pre-allocated request - simply reset it HvlInitPreAllocatedOp(pHvl); } else { HvlDeleteExAccessRequest(pHvl, pExReq); } } else { ASSERT(!HvlIsHelperVNic(pHvl, pVNic)); /* This VNIC has more requests for exclusive access pending */ MpTrace(COMP_HVL, DBG_NORMAL, ("VNic(%d) has %d more exclusive access requests. Decremented the refcount. Not removing it from the queue\n", VNIC_PORT_NO, pExReq ? pExReq->ulRefCount : -1)); ndisStatus = NDIS_STATUS_PENDING; } HvlUnlock(pHvl); return ndisStatus; }
NDIS_STATUS Hvl11RequestExAccess( _In_ PHVL pHvl, _In_ PVNIC pVNic, _In_ BOOLEAN fPnPOperation ) { NDIS_STATUS ndisStatus = NDIS_STATUS_SUCCESS; BOOLEAN fLocked = FALSE; do { HvlLock(pHvl); fLocked = TRUE; ndisStatus = HvlQueueExAccessRequest(pHvl, pVNic, fPnPOperation); if (NDIS_STATUS_SUCCESS != ndisStatus) { MpTrace(COMP_HVL, DBG_SERIOUS, ("HvlQueueExAccessRequest failed 0x%x", ndisStatus)); break; } // now determine the return status to be given to the VNIC. if (pHvl->pExAccessDelegatedVNic) { // if the helper port has delegated exclusive access to some VNIC, new request // by only that VNIC can succeed. Everyone else, including the helper port, should // wait if (pHvl->pExAccessDelegatedVNic == pVNic) { MpTrace(COMP_HVL, DBG_NORMAL, ("VNic (%d) has already been delegated exclusive access. Completing its request for exclusive access inline. \n", VNIC_PORT_NO)); ndisStatus = NDIS_STATUS_SUCCESS; } else { ndisStatus = NDIS_STATUS_PENDING; MpTrace(COMP_HVL, DBG_NORMAL, ("VNic (%d) has been delegated exclusive access. Pending VNic(%d)'s request for exclusive access.\n", pHvl->pExAccessDelegatedVNic->PortNumber, VNIC_PORT_NO)); } } else { if (HvlGetExAccessVNic(pHvl) == pVNic) { if (HvlIsHelperVNic(pHvl, pVNic)) { // helper port never gets exclusive access inline MpTrace(COMP_HVL, DBG_NORMAL, ("Helper VNic (%d) has exclusive access. Still pending VNic(%d)'s exclusive access request \n", pHvl->pExAccessVNic->PortNumber, VNIC_PORT_NO)); ndisStatus = NDIS_STATUS_PENDING; } else { // let the VNIC know that it already has exclusive access MpTrace(COMP_HVL, DBG_NORMAL, ("VNic (%d) already has exclusive access. Completing its request for exclusive access inline. \n", VNIC_PORT_NO)); ndisStatus = NDIS_STATUS_SUCCESS; } } else if (HvlGetExAccessVNic(pHvl)) { // the VNIC will get exclusive access later MpTrace(COMP_HVL, DBG_NORMAL, ("VNic (%d) has exclusive access. Pending VNic(%d)'s exclusive access request \n", pHvl->pExAccessVNic->PortNumber, VNIC_PORT_NO)); ndisStatus = NDIS_STATUS_PENDING; } else { // the VNIC will get exclusive access later MpTrace(COMP_HVL, DBG_NORMAL, ("No VNic has exclusive access. Pending VNic(%d)'s exclusive access request \n", VNIC_PORT_NO)); ndisStatus = NDIS_STATUS_PENDING; } } } while (FALSE); if (fLocked) { HvlUnlock(pHvl); } if (NDIS_STATUS_PENDING == ndisStatus) { KeSetEvent(&pHvl->ExAccessEvent, 0, FALSE); } return ndisStatus; }
VOID HvlCtxSWorkItem( PVOID Context, NDIS_HANDLE NdisIoWorkItemHandle ) { NTSTATUS ntStatus = STATUS_SUCCESS; PHVL pHvl = NULL; LARGE_INTEGER waitTime = {0}; #define NUM_EVENTS 3 PVOID EventArray[NUM_EVENTS]; BOOLEAN fTerminating = FALSE; UNREFERENCED_PARAMETER(NdisIoWorkItemHandle); if (NULL == Context) { MPASSERT(Context != NULL); return; } pHvl = (PHVL) Context; EventArray[0] = &pHvl->TerminatingEvent; EventArray[1] = &pHvl->CtxSEvent; EventArray[2] = &pHvl->ExAccessEvent; /* The waitTime should ideally be a function of the beacon period. For now assume a constant */ waitTime.QuadPart = HVL_DEFAULT_CONTEXT_SWITCH_PARK_TIME_MSEC * -1; do { ntStatus = KeWaitForMultipleObjects(NUM_EVENTS, EventArray, WaitAny, Executive, KernelMode, FALSE, &waitTime, NULL); switch (ntStatus) { case STATUS_TIMEOUT: // timed context switch processing. HvlProcessTimedCtxSwitch(pHvl); // optionally set a different waitTime for the next wait break; case STATUS_WAIT_0: // we are terminating. Time to get rid of this work item processing HvlLock(pHvl); pHvl->ulNumThreadsPending--; HvlUnlock(pHvl); fTerminating = TRUE; break; case STATUS_WAIT_1: // we are asked to do context switch processing HvlProcessTimedCtxSwitch(pHvl); break; case STATUS_WAIT_2: // we have a new request for exclusive access HvlProcessExAccessReq(pHvl); break; default: MpTrace(COMP_HVL, DBG_SERIOUS, ("KeWaitForMultipleObjects returned error 0x%x", ntStatus)); break; } } while (!fTerminating); }