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:
@@ -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
|
||||
-------------
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user