diff --git a/src/wifi/model/eht/advanced-emlsr-manager.cc b/src/wifi/model/eht/advanced-emlsr-manager.cc index 37428bea2..36992cc49 100644 --- a/src/wifi/model/eht/advanced-emlsr-manager.cc +++ b/src/wifi/model/eht/advanced-emlsr-manager.cc @@ -266,6 +266,20 @@ AdvancedEmlsrManager::ReceivedMacHdr(Ptr phy, // Hence, schedule the call to NotifyTxopEnd to execute it outside such for loop. Simulator::ScheduleNow(&AdvancedEmlsrManager::NotifyTxopEnd, this, *linkId, false, false); } + + // if the MAC header has been received on the link on which the main PHY is operating, the + // switch main PHY back timer is running and channel access is not expected to be gained by + // the main PHY before the switch main PHY back timer expires (plus a channel switch delay), + // try to switch the main PHY back to the preferred link + if (m_switchMainPhyBackEvent.IsPending() && + phy == GetStaMac()->GetDevice()->GetPhy(GetMainPhyId()) && + !GetExpectedAccessWithinDelay(*linkId, + Simulator::GetDelayLeft(m_switchMainPhyBackEvent) + + phy->GetChannelSwitchDelay())) + { + m_switchMainPhyBackEvent.Cancel(); + SwitchMainPhyBackDelayExpired(*linkId); + } } void @@ -401,6 +415,19 @@ AdvancedEmlsrManager::CheckNavAndCcaLastPifs(Ptr phy, uint8_t linkId, P } else { + // medium busy, check when access may be granted + if (!GetExpectedAccessWithinDelay(linkId, + m_switchMainPhyBackDelay + phy->GetChannelSwitchDelay())) + { + NS_LOG_DEBUG("No AC is expected to get backoff soon, switch main PHY back"); + if (auto mainPhy = GetStaMac()->GetDevice()->GetPhy(GetMainPhyId()); + !mainPhy->IsStateSwitching()) + { + SwitchMainPhyBackDelayExpired(linkId); + } + return; + } + // medium busy, restart channel access NS_LOG_DEBUG("Medium busy in the last PIFS interval"); edca->NotifyChannelReleased(linkId); // to set access to NOT_REQUESTED @@ -412,15 +439,93 @@ AdvancedEmlsrManager::CheckNavAndCcaLastPifs(Ptr phy, uint8_t linkId, P // The timer is stopped if a DL or UL TXOP is started. When the timer expires, the main PHY // switches back to the preferred link if SwitchAuxPhy is false m_switchMainPhyBackEvent.Cancel(); - m_switchMainPhyBackEvent = Simulator::Schedule(m_switchMainPhyBackDelay, [this, linkId]() { - if (!m_switchAuxPhy) - { - SwitchMainPhyBackToPreferredLink(linkId, EmlsrSwitchMainPhyBackTrace(false)); - } - }); + m_switchMainPhyBackEvent = + Simulator::Schedule(m_switchMainPhyBackDelay, + &AdvancedEmlsrManager::SwitchMainPhyBackDelayExpired, + this, + linkId); } } +void +AdvancedEmlsrManager::SwitchMainPhyBackDelayExpired(uint8_t linkId) +{ + NS_LOG_FUNCTION(this << linkId); + + if (m_switchAuxPhy) + { + return; // nothing to do + } + + Time delay{0}; + + // check if the timer must be restarted because a frame is being received on any link + for (const auto id : GetStaMac()->GetLinkIds()) + { + auto phy = GetStaMac()->GetWifiPhy(id); + + if (!phy || !GetStaMac()->IsEmlsrLink(id)) + { + continue; + } + + if (!GetEhtFem(id)->VirtualCsMediumIdle() && + GetEhtFem(id)->GetTxopHolder() != GetEhtFem(id)->GetBssid()) + { + NS_LOG_DEBUG("NAV is set and TXOP holder is not the associated AP MLD on link " << +id); + continue; + } + + if (auto macHdr = GetEhtFem(id)->GetReceivedMacHdr(); macHdr && m_useNotifiedMacHdr) + { + // the MAC header has been received; if this is a Trigger Frame, we shall restart the + // timer, so that we do not yet switch the main PHY back to the preferred link + if (const auto& hdr = macHdr->get(); + hdr.IsTrigger() && + (hdr.GetAddr1().IsBroadcast() || hdr.GetAddr1() == GetEhtFem(id)->GetAddress())) + { + delay = Max(delay, phy->GetDelayUntilIdle()); + } + } + else if (phy->IsStateRx()) + { + if (auto ongoingRxInfo = GetEhtFem(id)->GetOngoingRxInfo(); + ongoingRxInfo && + ongoingRxInfo->get().txVector.GetModulationClass() < WIFI_MOD_CLASS_HT) + { + // the MAC header of a non-HT PPDU, which may be an ICF, has not been received yet + // (or we cannot use its info); restart the timer, we will be called back when the + // MAC header is received + delay = Max(delay, phy->GetDelayUntilIdle()); + } + } + else if (id == linkId && phy->IsStateIdle()) + { + // this is the link on which the main PHY is operating. If an AC with traffic is + // expected to get channel access soon (within a channel switch delay), restart + // the timer to have the main PHY stay a bit longer on this link + if (GetExpectedAccessWithinDelay(linkId, phy->GetChannelSwitchDelay())) + { + delay = Max(delay, phy->GetChannelSwitchDelay()); + } + } + } + + if (delay.IsStrictlyPositive()) + { + NS_LOG_DEBUG("Restarting the timer, check again in " << delay.As(Time::US)); + m_switchMainPhyBackEvent = + Simulator::Schedule(delay, + &AdvancedEmlsrManager::SwitchMainPhyBackDelayExpired, + this, + linkId); + return; + } + + // no need to wait further, switch the main PHY back to the preferred link + SwitchMainPhyBackToPreferredLink(linkId, EmlsrSwitchMainPhyBackTrace(false)); +} + void AdvancedEmlsrManager::DoNotifyIcfReceived(uint8_t linkId) { @@ -717,12 +822,10 @@ AdvancedEmlsrManager::SwitchMainPhyIfTxopToBeGainedByAuxPhy(uint8_t linkId, mainPhy->GetChannelSwitchDelay() + GetStaMac()->GetWifiPhy(linkId)->GetPifs()); m_switchMainPhyBackEvent.Cancel(); m_switchMainPhyBackEvent = - Simulator::Schedule(minDelay + m_switchMainPhyBackDelay, [this, linkId]() { - if (!m_switchAuxPhy) - { - SwitchMainPhyBackToPreferredLink(linkId, EmlsrSwitchMainPhyBackTrace(false)); - } - }); + Simulator::Schedule(minDelay + m_switchMainPhyBackDelay, + &AdvancedEmlsrManager::SwitchMainPhyBackDelayExpired, + this, + linkId); } } // namespace ns3 diff --git a/src/wifi/model/eht/advanced-emlsr-manager.h b/src/wifi/model/eht/advanced-emlsr-manager.h index c2396e88e..5acc00222 100644 --- a/src/wifi/model/eht/advanced-emlsr-manager.h +++ b/src/wifi/model/eht/advanced-emlsr-manager.h @@ -90,6 +90,16 @@ class AdvancedEmlsrManager : public DefaultEmlsrManager */ void SwitchMainPhyIfTxopToBeGainedByAuxPhy(uint8_t linkId, AcIndex aci, const Time& delay); + /** + * This method is called when the switch main PHY back delay timer (which is started when the + * main PHY switches to the link of an aux PHY that does not switch and is not TX capable) + * expires and decides whether to delay the request to switch the main PHY back to the preferred + * link or to execute it immediately. + * + * @param linkId the ID of the link that the main PHY is leaving + */ + void SwitchMainPhyBackDelayExpired(uint8_t linkId); + private: void DoNotifyTxopEnd(uint8_t linkId) override; void DoNotifyIcfReceived(uint8_t linkId) override; diff --git a/src/wifi/model/qos-frame-exchange-manager.cc b/src/wifi/model/qos-frame-exchange-manager.cc index 34510fff7..f814fe455 100644 --- a/src/wifi/model/qos-frame-exchange-manager.cc +++ b/src/wifi/model/qos-frame-exchange-manager.cc @@ -727,6 +727,12 @@ QosFrameExchangeManager::SetTxopHolder(Ptr psdu, const WifiTxVec } } +std::optional +QosFrameExchangeManager::GetTxopHolder() const +{ + return m_txopHolder; +} + std::optional QosFrameExchangeManager::FindTxopHolder(const WifiMacHeader& hdr, const WifiTxVector& txVector) { diff --git a/src/wifi/model/qos-frame-exchange-manager.h b/src/wifi/model/qos-frame-exchange-manager.h index f9f2a6f40..aec171de8 100644 --- a/src/wifi/model/qos-frame-exchange-manager.h +++ b/src/wifi/model/qos-frame-exchange-manager.h @@ -93,6 +93,11 @@ class QosFrameExchangeManager : public FrameExchangeManager */ virtual Ptr CreateAliasIfNeeded(Ptr mpdu) const; + /** + * @return the TXOP holder (if any) + */ + std::optional GetTxopHolder() const; + protected: void DoDispose() override;