wifi: Add an attribute to keep main PHY on aux PHY link after DL TXOP

This commit is contained in:
Stefano Avallone
2025-03-05 19:00:55 +01:00
parent 879929f3f9
commit 259f260b5b
11 changed files with 104 additions and 27 deletions

View File

@@ -20,6 +20,7 @@ This file is a best-effort approach to solving this issue; we will do our best b
* (wifi) Added a new `AssocType` attribute to `StaWifiMac` to configure the type of association performed by a device, provided that it is supported by the standard configured for the device. By using this attribute, it is possible for an EHT single-link device to perform ML setup with an AP MLD and for an EHT multi-link device to perform legacy association with an AP MLD.
* (wifi) Added a new attribute `Per20CcaSensitivityThreshold` to `EhtConfiguration` for tuning the Per 20MHz CCA threshold when 802.11be is used.
* (wifi) Added a new `MaxRadioBw` attribute to `WifiPhy` to configure the maximum width supported by the radio.
* (wifi) Added a new attribute (`KeepMainPhyAfterDlTxop`) to the `AdvancedEmlsrManager` to control whether, after the end of a DL TXOP carried out on an aux PHY link, the main PHY shall stay on that link (for a switch main PHY back delay) in the attempt to gain an UL TXOP. This attribute is applicable to the case in which aux PHYs are not TX capable and do not switch link.
### Changes to existing API

View File

@@ -1414,6 +1414,14 @@ a reason to postpone the switch. The switch back to the preferred link is postpo
A similar check is applied to possibly postpone the switch back to the preferred link when the
SwitchMainPhyBack timer expires.
The Advanced EMLSR Manager provides the ``KeepMainPhyAfterDlTxop`` attribute to control the behavior
of the main PHY at the end of a DL TXOP carried out on an aux PHY link, in case aux PHYs are not TX
capable and do not switch link. If such attribute is set to false (default value), the main PHY
immediately switches back to the preferred link. If such attribute is set to true, it is checked
whether channel access on the aux PHY link is expected to be gained within a switch main PHY back
delay plus a channel switch delay: if it is, the main PHY stays on the aux PHY link and a switch
main PHY back timer is started; otherwise, the main PHY switches back to the preferred link.
The Advanced EMLSR Manager also connects a callback to the ``NSlotsLeftAlert`` trace source of the
Channel Access Manager, which sends notifications when at most a configurable number of slots remain
until the backoff of an AC expires. It must be noted that this notification is only sent if channel

View File

@@ -142,7 +142,15 @@ AdvancedEmlsrManager::GetTypeId()
"expires, the main PHY is switched back to the preferred link.",
TimeValue(MilliSeconds(5)),
MakeTimeAccessor(&AdvancedEmlsrManager::m_switchMainPhyBackDelay),
MakeTimeChecker());
MakeTimeChecker())
.AddAttribute("KeepMainPhyAfterDlTxop",
"In case aux PHYs are not TX capable and do not switch link, after the "
"end of a DL TXOP carried out on an aux PHY link, the main PHY stays on "
"that link for a switch main PHY back delay, if this attribute is true, "
"or it returns to the preferred link, otherwise.",
BooleanValue(false),
MakeBooleanAccessor(&AdvancedEmlsrManager::m_keepMainPhyAfterDlTxop),
MakeBooleanChecker());
return tid;
}
@@ -310,7 +318,7 @@ AdvancedEmlsrManager::ReceivedMacHdr(Ptr<WifiPhy> phy,
// another callback (FEM::ReceivedMacHdr) from the PhyRxMacHeaderEnd trace source of
// the main PHY, thus invalidating the list of callbacks on which the for loop iterates.
// Hence, schedule the call to NotifyTxopEnd to execute it outside such for loop.
Simulator::ScheduleNow(&AdvancedEmlsrManager::NotifyTxopEnd, this, *linkId, false, false);
Simulator::ScheduleNow(&AdvancedEmlsrManager::NotifyTxopEnd, this, *linkId, nullptr);
}
// if the MAC header has been received on the link on which the main PHY is operating (or on
@@ -335,9 +343,9 @@ AdvancedEmlsrManager::ReceivedMacHdr(Ptr<WifiPhy> phy,
}
void
AdvancedEmlsrManager::DoNotifyTxopEnd(uint8_t linkId)
AdvancedEmlsrManager::DoNotifyTxopEnd(uint8_t linkId, Ptr<QosTxop> edca)
{
NS_LOG_FUNCTION(this << linkId);
NS_LOG_FUNCTION(this << linkId << edca);
auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
@@ -358,6 +366,30 @@ AdvancedEmlsrManager::DoNotifyTxopEnd(uint8_t linkId)
// or
// - SwitchAuxPhy is false and there is an aux PHY to reconnect
if (!m_auxPhyTxCapable && !m_switchAuxPhy && !edca && m_keepMainPhyAfterDlTxop)
{
// DL TXOP ended, check if the main PHY must be kept on this link to try to gain an UL TXOP
NS_ASSERT_MSG(!m_switchMainPhyBackEvent.IsPending(),
"Switch main PHY back timer should not be running at the end of a DL TXOP");
NS_ASSERT_MSG(!mainPhy->IsStateSwitching(),
"Main PHY should not be switching at the end of a DL TXOP");
if (GetStaMac()->GetChannelAccessManager(linkId)->GetExpectedAccessWithin(
m_switchMainPhyBackDelay) == WifiExpectedAccessReason::ACCESS_EXPECTED)
{
NS_LOG_DEBUG("Keep main PHY on link " << +linkId << " to try to gain an UL TXOP");
m_switchMainPhyBackEvent =
Simulator::Schedule(m_switchMainPhyBackDelay,
&AdvancedEmlsrManager::SwitchMainPhyBackDelayExpired,
this,
linkId,
std::nullopt);
// start checking PHY activity on the link the main PHY is operating
RegisterListener(GetStaMac()->GetWifiPhy(linkId));
return;
}
}
std::shared_ptr<EmlsrMainPhySwitchTrace> traceInfo;
if (const auto it = m_rtsStartingUlTxop.find(linkId);

View File

@@ -139,7 +139,7 @@ class AdvancedEmlsrManager : public DefaultEmlsrManager
void UnregisterListener();
private:
void DoNotifyTxopEnd(uint8_t linkId) override;
void DoNotifyTxopEnd(uint8_t linkId, Ptr<QosTxop> edca) override;
void DoNotifyIcfReceived(uint8_t linkId) override;
void DoNotifyUlTxopStart(uint8_t linkId) override;
@@ -154,6 +154,9 @@ class AdvancedEmlsrManager : public DefaultEmlsrManager
Time m_switchMainPhyBackDelay; //!< duration of the timer started in case of non-TX capable aux
//!< PHY when medium is sensed busy during the PIFS interval
//!< preceding/following the main PHY switch end
bool m_keepMainPhyAfterDlTxop; //!< whether the main PHY must stay, for a switch main PHY back
//!< delay, on an aux PHY link after a DL TXOP, in case aux PHYs
//!< are not TX capable and do not switch
EventId m_ccaLastPifs; //!< event scheduled in case of non-TX capable aux PHY to
//!< determine whether TX can be started based on whether
//!< the medium has been idle during the last PIFS interval

View File

@@ -213,9 +213,9 @@ DefaultEmlsrManager::DoNotifyUlTxopStart(uint8_t linkId)
}
void
DefaultEmlsrManager::DoNotifyTxopEnd(uint8_t linkId)
DefaultEmlsrManager::DoNotifyTxopEnd(uint8_t linkId, Ptr<QosTxop> edca)
{
NS_LOG_FUNCTION(this << linkId);
NS_LOG_FUNCTION(this << linkId << edca);
if (m_switchAuxPhy)
{

View File

@@ -111,7 +111,7 @@ class DefaultEmlsrManager : public EmlsrManager
Time duration) override;
void DoNotifyIcfReceived(uint8_t linkId) override;
void DoNotifyUlTxopStart(uint8_t linkId) override;
void DoNotifyTxopEnd(uint8_t linkId) override;
void DoNotifyTxopEnd(uint8_t linkId, Ptr<QosTxop> edca) override;
void DoNotifyProtectionCompleted(uint8_t linkId) override;
};

View File

@@ -1264,12 +1264,9 @@ EhtFrameExchangeManager::NotifyChannelReleased(Ptr<Txop> txop)
// Notify the UL TXOP end to the EMLSR Manager
auto edca = DynamicCast<QosTxop>(txop);
NS_ASSERT(edca);
auto txopStart = edca->GetTxopStartTime(m_linkId);
NS_ASSERT(m_staMac->GetEmlsrManager());
m_staMac->GetEmlsrManager()->NotifyTxopEnd(m_linkId,
(!txopStart || *txopStart == Simulator::Now()),
m_ongoingTxopEnd.IsPending());
m_staMac->GetEmlsrManager()->NotifyTxopEnd(m_linkId, edca);
}
HeFrameExchangeManager::NotifyChannelReleased(txop);

View File

@@ -765,9 +765,9 @@ EmlsrManager::NotifyProtectionCompleted(uint8_t linkId)
}
void
EmlsrManager::NotifyTxopEnd(uint8_t linkId, bool ulTxopNotStarted, bool ongoingDlTxop)
EmlsrManager::NotifyTxopEnd(uint8_t linkId, Ptr<QosTxop> edca)
{
NS_LOG_FUNCTION(this << linkId << ulTxopNotStarted << ongoingDlTxop);
NS_LOG_FUNCTION(this << linkId << edca);
if (!m_staMac->IsEmlsrLink(linkId))
{
@@ -795,15 +795,19 @@ EmlsrManager::NotifyTxopEnd(uint8_t linkId, bool ulTxopNotStarted, bool ongoingD
// this link of an ICF starting a DL TXOP. If the EMLSR Manager unblocked the other EMLSR
// links, another TXOP could be started on another EMLSR link (possibly leading to a crash)
// while the DL TXOP on this link is ongoing.
if (ongoingDlTxop)
if (GetEhtFem(linkId)->GetOngoingTxopEndEvent().IsPending())
{
NS_LOG_DEBUG("DL TXOP ongoing");
return;
}
if (ulTxopNotStarted)
if (edca)
{
NS_LOG_DEBUG("TXOP did not even start");
return;
if (auto txopStart = edca->GetTxopStartTime(linkId);
!txopStart || *txopStart == Simulator::Now())
{
NS_LOG_DEBUG("UL TXOP did not even start");
return;
}
}
if (m_auxPhyToSleep)
@@ -812,7 +816,7 @@ EmlsrManager::NotifyTxopEnd(uint8_t linkId, bool ulTxopNotStarted, bool ongoingD
SetSleepStateForAllAuxPhys(false);
}
DoNotifyTxopEnd(linkId);
DoNotifyTxopEnd(linkId, edca);
// unblock transmissions and resume medium access on other EMLSR links
std::set<uint8_t> linkIds;

View File

@@ -270,12 +270,10 @@ class EmlsrManager : public Object
* Notify the end of a TXOP on the given link.
*
* @param linkId the ID of the given link
* @param ulTxopNotStarted whether this is a notification of the end of an UL TXOP that did
* not even start (no frame transmitted)
* @param ongoingDlTxop whether a DL TXOP is ongoing on the given link (if true, this is
* a notification of the end of an UL TXOP)
* @param edca the EDCAF that carried out the TXOP, in case of UL TXOP, or a null pointer,
* in case of DL TXOP
*/
void NotifyTxopEnd(uint8_t linkId, bool ulTxopNotStarted = false, bool ongoingDlTxop = false);
void NotifyTxopEnd(uint8_t linkId, Ptr<QosTxop> edca = nullptr);
/**
* Notify that an STA affiliated with the EMLSR client is causing in-device interference
@@ -602,8 +600,10 @@ class EmlsrManager : public Object
* Notify the subclass of the end of a TXOP on the given link.
*
* @param linkId the ID of the given link
* @param edca the EDCAF that carried out the TXOP, in case of UL TXOP, or a null pointer,
* in case of DL TXOP
*/
virtual void DoNotifyTxopEnd(uint8_t linkId) = 0;
virtual void DoNotifyTxopEnd(uint8_t linkId, Ptr<QosTxop> edca) = 0;
/**
* Notify the acknowledgment of the given MPDU.

View File

@@ -5459,14 +5459,18 @@ EmlsrIcfSentDuringMainPhySwitchTest::RunOne()
const auto interruptSwitch = ((m_testIndex & 0b010) != 0);
const auto switchToOtherLink = ((m_testIndex & 0b100) != 0);
const auto keepMainPhyAfterDlTxop = useMacHdrInfo;
m_staMacs[0]->GetEmlsrManager()->SetAttribute("UseNotifiedMacHdr", BooleanValue(useMacHdrInfo));
auto advEmlsrMgr = DynamicCast<AdvancedEmlsrManager>(m_staMacs[0]->GetEmlsrManager());
NS_TEST_ASSERT_MSG_NE(advEmlsrMgr, nullptr, "Advanced EMLSR Manager required");
advEmlsrMgr->SetAttribute("InterruptSwitch", BooleanValue(interruptSwitch));
advEmlsrMgr->SetAttribute("KeepMainPhyAfterDlTxop", BooleanValue(keepMainPhyAfterDlTxop));
m_testStr = "SwitchToOtherLink=" + std::to_string(switchToOtherLink) +
", InterruptSwitch=" + std::to_string(interruptSwitch) +
", UseMacHdrInfo=" + std::to_string(useMacHdrInfo) +
", KeepMainPhyAfterDlTxop=" + std::to_string(keepMainPhyAfterDlTxop) +
", ChannelSwitchDurationIdx=" + std::to_string(m_csdIndex);
NS_LOG_INFO("Starting test: " << m_testStr << "\n");
@@ -5682,6 +5686,16 @@ EmlsrIcfSentDuringMainPhySwitchTest::RunOne()
Simulator::ScheduleNow([=, this]() {
CheckInDeviceInterference(m_testStr + ", ACK", linkId, txDuration);
});
// check the KeepMainPhyAfterDlTxop attribute
Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
auto shouldSwitch = (!keepMainPhyAfterDlTxop || switchToOtherLink);
NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
shouldSwitch,
m_testStr << ": Main PHY should "
<< (shouldSwitch ? "" : "not")
<< " be switching back after DL TXOP end");
});
});
// Uplink TXOP
@@ -5701,10 +5715,21 @@ EmlsrIcfSentDuringMainPhySwitchTest::RunOne()
m_events.emplace_back(
WIFI_MAC_CTL_ACK,
[=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
const auto txDuration =
WifiPhy::CalculateTxDuration(psdu,
txVector,
m_staMacs[0]->GetWifiPhy(linkId)->GetPhyBand());
// check that main PHY switches back after UL TXOP
Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
NS_TEST_EXPECT_MSG_EQ(
mainPhy->IsStateSwitching(),
true,
m_testStr << ": Main PHY should be switching back after UL TXOP end");
});
// Continue with the next test scenario
Simulator::Schedule(MilliSeconds(2), [=, this]() {
NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
m_csdIndex = static_cast<ChannelSwitchEnd>(static_cast<uint8_t>(m_csdIndex) + 1);
if (m_csdIndex == CSD_COUNT)
{

View File

@@ -1131,7 +1131,14 @@ class SingleLinkEmlsrTest : public EmlsrOperationsTestBase
* - if the main PHY switches to another link, the UL TXOP does not start because it is detected
* that a frame which could be an ICF is being received on another link
*
* The UL TXOP is started following the DL TXOP end.
* At the end of the DL TXOP, it is checked that:
* - if the KeepMainPhyAfterDlTxop attribute of the AdvancedEmlsrManager is false, the main PHY
* switches back to the preferred link
* - if the KeepMainPhyAfterDlTxop attribute of the AdvancedEmlsrManager is true, the main PHY
* stays on the current link to start an UL TXOP, if the UL frame can be sent on the same link
* as the DL frame, or switches back to the preferred link, otherwise
*
* At the end of the UL TXOP, the main PHY returns to the preferred link.
*
* It is also checked that the in-device interference generated by every transmission of the EMLSR
* client is tracked by all the PHY interfaces of all the PHYs but the PHY that is transmitting