diff --git a/src/wifi/doc/source/wifi-design.rst b/src/wifi/doc/source/wifi-design.rst index a789c6a01..3a0af6307 100644 --- a/src/wifi/doc/source/wifi-design.rst +++ b/src/wifi/doc/source/wifi-design.rst @@ -1212,6 +1212,13 @@ for UL TXOPs, aux PHYs are put to sleep when the CTS frame is received, if RTS/C the transmission of the data frame starts, otherwise. Aux PHYs are resumed from sleep when the TXOP ends. +EMLSR operations, as detailed below, may lead to situations in which no PHY operates on a link for +a certain time interval. Whether to freeze or reset the backoff counters for that link during such +intervals is controlled by the ``ResetBackoffThreshold`` attribute of the ``ChannelAccessManager``: +if the duration of the interval during which no PHY operates on a link is longer than the value of +this attribute, the backoff counters are reset; otherwise, they are frozen until a PHY is connected +to the link. + Downlink TXOP ------------- diff --git a/src/wifi/model/channel-access-manager.cc b/src/wifi/model/channel-access-manager.cc index 9f6b525a0..47ccb909a 100644 --- a/src/wifi/model/channel-access-manager.cc +++ b/src/wifi/model/channel-access-manager.cc @@ -188,6 +188,12 @@ ChannelAccessManager::GetTypeId() BooleanValue(false), MakeBooleanAccessor(&ChannelAccessManager::m_proactiveBackoff), MakeBooleanChecker()) + .AddAttribute("ResetBackoffThreshold", + "If no PHY operates on this link for a period greater than this " + "threshold, all the backoffs are reset.", + TimeValue(Time{0}), + MakeTimeAccessor(&ChannelAccessManager::m_resetBackoffThreshold), + MakeTimeChecker()) .AddAttribute("NSlotsLeft", "The NSlotsLeftAlert trace source is fired when the number of remaining " "backoff slots for any AC is equal to or less than the value of this " @@ -268,6 +274,7 @@ ChannelAccessManager::SetupPhyListener(Ptr phy) { NS_LOG_FUNCTION(this << phy); + const auto now = Simulator::Now(); auto phyListener = GetPhyListener(phy); if (phyListener) @@ -281,21 +288,32 @@ ChannelAccessManager::SetupPhyListener(Ptr phy) // channel access manager; unregister the listener and register again (below) to get // updated CCA busy information phy->UnregisterListener(phyListener); + // we expect that the PHY is reconnected immediately after the other PHY left the link: + // reset the start of m_lastNoPhy so as to ignore this event + NS_ASSERT(m_lastNoPhy.start == now); + NS_ASSERT(m_lastNoPhy.end <= m_lastNoPhy.start); + m_lastNoPhy.start = m_lastNoPhy.end; } else { phyListener = std::make_shared(this); m_phyListeners.emplace(phy, phyListener); - if (!m_phy) + if (m_phy) + { + DeactivatePhyListener(m_phy); + } + else { // no PHY operating on this link and no previous PHY listener to reactivate - m_lastSwitchingEnd = Simulator::Now(); + m_lastSwitchingEnd = now; + m_lastNoPhy.end = now; + if (now - m_lastNoPhy.start > m_resetBackoffThreshold) + { + ResetAllBackoffs(); + } } } - if (m_phy) - { - DeactivatePhyListener(m_phy); - } + m_phy = phy; // this is the new active PHY ResizeLastBusyStructs(); phy->RegisterListener(phyListener); @@ -312,7 +330,10 @@ ChannelAccessManager::RemovePhyListener(Ptr phy) // reset m_phy if we are removing listener registered for the active PHY if (m_phy == phy) { + UpdateBackoff(); + UpdateLastIdlePeriod(); m_phy = nullptr; + m_lastNoPhy.start = Simulator::Now(); } } } @@ -325,10 +346,6 @@ ChannelAccessManager::DeactivatePhyListener(Ptr phy) { listener->SetActive(false); } - if (m_phy == phy) - { - m_phy = nullptr; - } } void @@ -360,19 +377,27 @@ ChannelAccessManager::SetupFrameExchangeManager(Ptr feMana Time ChannelAccessManager::GetSlot() const { - return m_phy->GetSlot(); + if (m_phy) + { + m_cachedSlot = m_phy->GetSlot(); + } + return m_cachedSlot; } Time ChannelAccessManager::GetSifs() const { - return m_phy->GetSifs(); + if (m_phy) + { + m_cachedSifs = m_phy->GetSifs(); + } + return m_cachedSifs; } Time ChannelAccessManager::GetEifsNoDifs() const { - return m_phy->GetSifs() + m_phy->GetAckTxTime(); + return m_eifsNoDifs; } void @@ -660,6 +685,13 @@ void ChannelAccessManager::AccessTimeout() { NS_LOG_FUNCTION(this); + + if (!m_phy && Simulator::Now() - m_lastNoPhy.start > m_resetBackoffThreshold) + { + ResetAllBackoffs(); + return; + } + UpdateBackoff(); DoGrantDcfAccess(); DoRestartAccessTimeoutIfNeeded(); @@ -669,8 +701,9 @@ Time ChannelAccessManager::GetAccessGrantStart(bool ignoreNav) const { NS_LOG_FUNCTION(this << ignoreNav); + const auto now = Simulator::Now(); auto rxAccessStart = m_lastRx.end; - if ((m_lastRx.end <= Simulator::Now()) && !m_lastRxReceivedOk) + if ((m_lastRx.end <= now) && !m_lastRxReceivedOk) { rxAccessStart += GetEifsNoDifs(); } @@ -678,6 +711,7 @@ ChannelAccessManager::GetAccessGrantStart(bool ignoreNav) const // (Sec. 10.23.2.5 of IEEE 802.11-2020) const auto busyAccessStart = m_lastBusyEnd.at(WIFI_CHANLIST_PRIMARY); const auto navAccessStart = ignoreNav ? Time{0} : m_lastNavEnd; + const auto noPhyStart = m_phy ? m_lastNoPhy.end : now; const auto accessGrantedStart = std::max({rxAccessStart, busyAccessStart, @@ -686,6 +720,7 @@ ChannelAccessManager::GetAccessGrantStart(bool ignoreNav) const m_lastAckTimeoutEnd, m_lastCtsTimeoutEnd, m_lastSwitchingEnd, + noPhyStart, m_lastSleepEnd, m_lastOffEnd}); @@ -695,6 +730,7 @@ ChannelAccessManager::GetAccessGrantStart(bool ignoreNav) const << busyAccessStart.As(Time::US) << ", tx access start=" << m_lastTxEnd.As(Time::US) << ", nav access start=" << navAccessStart.As(Time::US) << ", switching access start=" << m_lastSwitchingEnd.As(Time::US) + << ", no PHY start=" << noPhyStart.As(Time::US) << ", sleep access start=" << m_lastSleepEnd.As(Time::US) << ", off access start=" << m_lastOffEnd.As(Time::US)); return accessGrantedStart + GetSifs(); @@ -956,6 +992,7 @@ ChannelAccessManager::NotifyRxEndErrorNow() // we expect the PHY to notify us of the start of a CCA busy period, if needed m_lastRx.end = Simulator::Now(); m_lastRxReceivedOk = false; + m_eifsNoDifs = m_phy->GetSifs() + m_phy->GetAckTxTime(); } void @@ -1087,6 +1124,7 @@ ChannelAccessManager::ResetState() m_lastNavEnd = std::min(m_lastNavEnd, now); m_lastAckTimeoutEnd = std::min(m_lastAckTimeoutEnd, now); m_lastCtsTimeoutEnd = std::min(m_lastCtsTimeoutEnd, now); + m_lastNoPhy.end = std::min(m_lastNoPhy.end, now); InitLastBusyStructs(); } @@ -1240,8 +1278,12 @@ void ChannelAccessManager::UpdateLastIdlePeriod() { NS_LOG_FUNCTION(this); - Time idleStart = - std::max({m_lastTxEnd, m_lastRx.end, m_lastSwitchingEnd, m_lastSleepEnd, m_lastOffEnd}); + Time idleStart = std::max({m_lastTxEnd, + m_lastRx.end, + m_lastSwitchingEnd, + m_lastNoPhy.end, + m_lastSleepEnd, + m_lastOffEnd}); Time now = Simulator::Now(); if (idleStart >= now) diff --git a/src/wifi/model/channel-access-manager.h b/src/wifi/model/channel-access-manager.h index 83871661a..6c0847929 100644 --- a/src/wifi/model/channel-access-manager.h +++ b/src/wifi/model/channel-access-manager.h @@ -483,6 +483,9 @@ class ChannelAccessManager : public Object Time m_lastSleepEnd; //!< the last sleep end time Time m_lastOffEnd; //!< the last off end time Time m_eifsNoDifs; //!< EIFS no DIFS time + Timespan m_lastNoPhy; //!< the last start and end time no PHY was operating on the link + mutable Time m_cachedSifs; //!< cached value for SIFS, to be only used without a PHY + mutable Time m_cachedSlot; //!< cached value for slot, to be only used without a PHY EventId m_accessTimeout; //!< the access timeout ID bool m_generateBackoffOnNoTx; //!< whether the backoff should be invoked when the AC gains the //!< right to start a TXOP but it does not transmit any frame @@ -490,6 +493,8 @@ class ChannelAccessManager : public Object //!< provided that the queue is not actually empty bool m_proactiveBackoff; //!< whether a new backoff value is generated when a CCA busy period //!< starts and the backoff counter is zero + Time m_resetBackoffThreshold; //!< if no PHY operates on a link for a period greater than this + //!< threshold, the backoff on that link is reset /// Information associated with each PHY that is going to operate on another EMLSR link struct EmlsrLinkSwitchInfo diff --git a/src/wifi/test/channel-access-manager-test.cc b/src/wifi/test/channel-access-manager-test.cc index cd61ee82f..71b33be0f 100644 --- a/src/wifi/test/channel-access-manager-test.cc +++ b/src/wifi/test/channel-access-manager-test.cc @@ -414,6 +414,25 @@ class ChannelAccessManagerTest : public TestCase */ void AddRxStartEvt(uint64_t at, uint64_t duration); + /** + * Add a PHY disconnect event consisting in the PHY leaving the link and returning after a + * given time. + * + * @param at the event time + * @param duration the duration of the interval during which no PHY is connected + * @param threshold the value for the ResetBackoffThreshold attribute + * @param from the index of the Txop that has to request channel access when PHY is reconnected + */ + void AddPhyDisconnectEvt(uint64_t at, uint64_t duration, uint64_t threshold, uint32_t from); + + /** + * Add a PHY reconnect event consisting in another PHY operating on the link for the given time. + * + * @param at the event time + * @param duration the duration of the interval during which another PHY is connected + */ + void AddPhyReconnectEvt(uint64_t at, uint64_t duration); + typedef std::vector>> TxopTests; //!< the TXOP tests typedef Ptr> m_feManager; //!< the Frame Exchange Manager stubbed @@ -658,6 +677,12 @@ ChannelAccessManagerTest::EndTest() { Simulator::Run(); + m_ChannelAccessManager->RemovePhyListener(m_phy); + m_phy->Dispose(); + m_ChannelAccessManager->Dispose(); + m_ChannelAccessManager = nullptr; + m_feManager = nullptr; + for (auto i = m_txop.begin(); i != m_txop.end(); i++) { Ptr> state = *i; @@ -671,11 +696,6 @@ ChannelAccessManagerTest::EndTest() } m_txop.clear(); - m_ChannelAccessManager->RemovePhyListener(m_phy); - m_phy->Dispose(); - m_ChannelAccessManager->Dispose(); - m_ChannelAccessManager = nullptr; - m_feManager = nullptr; Simulator::Destroy(); } @@ -859,6 +879,59 @@ ChannelAccessManagerTest::AddRxStartEvt(uint64_t at, uint64_t duration MicroSeconds(duration)); } +template +void +ChannelAccessManagerTest::AddPhyDisconnectEvt(uint64_t at, + uint64_t duration, + uint64_t threshold, + uint32_t from) +{ + m_ChannelAccessManager->SetAttribute("ResetBackoffThreshold", + TimeValue(MicroSeconds(threshold))); + + Simulator::Schedule(MicroSeconds(at) - Now(), + &ChannelAccessManager::RemovePhyListener, + m_ChannelAccessManager, + m_phy); + + Simulator::Schedule(MicroSeconds(at + duration) - Now(), [=, this]() { + auto txop = m_txop[from]; + auto hadFramesToTransmit = txop->HasFramesToTransmit(SINGLE_LINK_OP_ID); + m_ChannelAccessManager->SetupPhyListener(m_phy); + if (duration > threshold) + { + // request channel access again because all backoffs have been reset + if (m_ChannelAccessManager->NeedBackoffUponAccess(txop, hadFramesToTransmit, true)) + { + txop->GenerateBackoff(0); + } + m_ChannelAccessManager->RequestAccess(txop); + } + }); +} + +template +void +ChannelAccessManagerTest::AddPhyReconnectEvt(uint64_t at, uint64_t duration) +{ + Simulator::Schedule(MicroSeconds(at) - Now(), [=, this]() { + auto newPhy = CreateObject(); + newPhy->SetInterferenceHelper(CreateObject()); + newPhy->AddChannel(DynamicCast(m_phy->GetChannel())); + newPhy->SetOperatingChannel(m_phy->GetOperatingChannel()); + newPhy->ConfigureStandard(WIFI_STANDARD_80211be); + // connect new PHY + m_ChannelAccessManager->SetupPhyListener(newPhy); + + Simulator::Schedule(MicroSeconds(duration), [=, this]() { + // disconnect new PHY + m_ChannelAccessManager->RemovePhyListener(newPhy); + // reconnect previous PHY + m_ChannelAccessManager->SetupPhyListener(m_phy); + }); + }); +} + /* * Specialization of DoRun () method for DCF */ @@ -1310,13 +1383,11 @@ ChannelAccessManagerTest::DoRun() EndTest(); // Check backoff decrement at slot boundaries. Medium becomes busy during backoff - // 20 50 56 60 61 71 77 81 85 87 97 103 - // 107 127 - // | rx | sifs | aifsn | idle | rx | sifs | aifsn | idle | idle | rx | sifs | - // aifsn | tx | - // | | | | - // 30 request access. decrement decrement decrement - // backoff slots: 3 slots: 2 slots: 1 slots: 0 + // 20 50 56 60 61 71 77 81 85 87 97 103 107 127 + // | rx | sifs | aifsn | idle | rx | sifs | aifsn | idle | idle | rx | sifs | aifsn | tx | + // | | | | + // 30 request access. decrement decrement decrement + // backoff slots: 3 slots: 2 slots: 1 slots: 0 StartTest(4, 6, 10); AddTxop(1); AddRxOkEvt(20, 30); @@ -1325,6 +1396,49 @@ ChannelAccessManagerTest::DoRun() AddAccessRequest(30, 20, 107, 0); ExpectBackoff(30, 3, 0); EndTest(); + + // Check backoff reset after no PHY operates on a link for more than the threshold. + // 20 50 56 60 61 71 77 81 101 + // | rx | sifs | aifsn | idle | no phy | sifs | aifsn | tx | + // | | | + // 30 request access. decrement reset + // backoff slots: 3 slots: 2 backoff + StartTest(4, 6, 10); + AddTxop(1); + AddRxOkEvt(20, 30); + AddAccessRequest(30, 20, 81, 0); + ExpectBackoff(30, 3, 0); + AddPhyDisconnectEvt(61, 10, 0, 0); + EndTest(); + + // Check backoff freeze while no PHY operates on a link for less than the threshold. + // 20 50 56 60 61 71 77 81 85 89 109 + // | rx | sifs | aifsn | idle | no phy | sifs | aifsn | idle | idle | tx | + // | | | | | + // 30 request access. decrement resume decrement decrement + // backoff slots: 3 slots: 2 backoff slots: 1 slots: 0 + StartTest(4, 6, 10); + AddTxop(1); + AddRxOkEvt(20, 30); + AddAccessRequest(30, 20, 89, 0); + ExpectBackoff(30, 3, 0); + AddPhyDisconnectEvt(61, 10, 20, 0); + EndTest(); + + // Check backoff left unmodified when previous PHY is reconnected to the link + // 20 50 56 60 61 64 68 71 72 76 96 + // | | | | |----- new PHY -----| | | | + // | rx | sifs | aifsn | idle | idle | idle | idle | tx | + // | | | | | + // 30 request access. decrement decrement decrement decrement + // backoff slots: 4 slots: 3 slots: 2 slots: 1 slots: 0 + StartTest(4, 6, 10); + AddTxop(1); + AddRxOkEvt(20, 30); + AddAccessRequest(30, 20, 76, 0); + ExpectBackoff(30, 4, 0); + AddPhyReconnectEvt(61, 10); + EndTest(); } /**