wifi: CAM stops backoff countdown while no PHY is on the link

...and reset all backoffs if no PHY is on the link for more
than a configured threshold.
This commit is contained in:
Stefano Avallone
2025-02-20 16:33:01 +01:00
parent 384467cd96
commit 28828fe462
4 changed files with 196 additions and 28 deletions

View File

@@ -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
-------------

View File

@@ -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<WifiPhy> phy)
{
NS_LOG_FUNCTION(this << phy);
const auto now = Simulator::Now();
auto phyListener = GetPhyListener(phy);
if (phyListener)
@@ -281,21 +288,32 @@ ChannelAccessManager::SetupPhyListener(Ptr<WifiPhy> 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<PhyListener>(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<WifiPhy> 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<WifiPhy> phy)
{
listener->SetActive(false);
}
if (m_phy == phy)
{
m_phy = nullptr;
}
}
void
@@ -360,19 +377,27 @@ ChannelAccessManager::SetupFrameExchangeManager(Ptr<FrameExchangeManager> 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)

View File

@@ -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

View File

@@ -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<Ptr<TxopTest<TxopType>>> TxopTests; //!< the TXOP tests typedef
Ptr<FrameExchangeManagerStub<TxopType>> m_feManager; //!< the Frame Exchange Manager stubbed
@@ -658,6 +677,12 @@ ChannelAccessManagerTest<TxopType>::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<TxopTest<TxopType>> state = *i;
@@ -671,11 +696,6 @@ ChannelAccessManagerTest<TxopType>::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<TxopType>::AddRxStartEvt(uint64_t at, uint64_t duration
MicroSeconds(duration));
}
template <typename TxopType>
void
ChannelAccessManagerTest<TxopType>::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 <typename TxopType>
void
ChannelAccessManagerTest<TxopType>::AddPhyReconnectEvt(uint64_t at, uint64_t duration)
{
Simulator::Schedule(MicroSeconds(at) - Now(), [=, this]() {
auto newPhy = CreateObject<SpectrumWifiPhy>();
newPhy->SetInterferenceHelper(CreateObject<InterferenceHelper>());
newPhy->AddChannel(DynamicCast<SpectrumChannel>(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<QosTxop>::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<QosTxop>::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();
}
/**