diff --git a/src/wifi/model/eht/eht-frame-exchange-manager.cc b/src/wifi/model/eht/eht-frame-exchange-manager.cc index 1ae15dfff..7e8953159 100644 --- a/src/wifi/model/eht/eht-frame-exchange-manager.cc +++ b/src/wifi/model/eht/eht-frame-exchange-manager.cc @@ -190,6 +190,7 @@ EhtFrameExchangeManager::StartTransmission(Ptr edca, MHz_u allowedWidth) m_mac->GetWifiRemoteStationManager(linkId)->GetEmlsrEnabled( ehtFem->m_txopHolder.value())) { + NS_LOG_DEBUG("Involved in UL TXOP: " << ehtFem->m_txopHolder.value()); emlsrClients.insert(ehtFem->m_txopHolder.value()); } @@ -198,6 +199,7 @@ EhtFrameExchangeManager::StartTransmission(Ptr edca, MHz_u allowedWidth) { if (m_mac->GetWifiRemoteStationManager(linkId)->GetEmlsrEnabled(address)) { + NS_LOG_DEBUG("Involved in DL TXOP: " << address); emlsrClients.insert(address); } } @@ -538,14 +540,15 @@ EhtFrameExchangeManager::IntraBssNavResetTimeout() HeFrameExchangeManager::IntraBssNavResetTimeout(); } -void -EhtFrameExchangeManager::EmlsrSwitchToListening(const Mac48Address& address, const Time& delay) +bool +EhtFrameExchangeManager::UnblockEmlsrLinksIfAllowed(Mac48Address address) { - NS_LOG_FUNCTION(this << address << delay.As(Time::US)); + NS_LOG_FUNCTION(this << address); auto mldAddress = GetWifiRemoteStationManager()->GetMldAddress(address); NS_ASSERT_MSG(mldAddress, "MLD address not found for " << address); NS_ASSERT_MSG(m_apMac, "This function shall only be called by AP MLDs"); + std::set linkIds{m_linkId}; /** * Do nothing if the EMLSR client is involved in a DL or UL TXOP on another EMLSR link. This @@ -576,10 +579,9 @@ EhtFrameExchangeManager::EmlsrSwitchToListening(const Mac48Address& address, con for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); ++linkId) { - if (linkId == m_linkId || - !m_mac->GetWifiRemoteStationManager(linkId)->GetEmlsrEnabled(*mldAddress)) + if (!m_mac->GetWifiRemoteStationManager(linkId)->GetEmlsrEnabled(*mldAddress)) { - continue; + continue; // not an EMLSR link } auto ehtFem = StaticCast(m_mac->GetFrameExchangeManager(linkId)); @@ -590,9 +592,17 @@ EhtFrameExchangeManager::EmlsrSwitchToListening(const Mac48Address& address, con { NS_LOG_DEBUG("EMLSR client " << *mldAddress << " is the holder of an UL TXOP on link " << +linkId << ", do not unblock links"); - return; + return false; } + if (linkId == m_linkId) + { + // no need to check if the EMLSR client is involved in a DL TXOP on this link + continue; + } + + linkIds.insert(linkId); + if (auto linkAddr = m_apMac->GetWifiRemoteStationManager(linkId)->GetAffiliatedStaAddress(*mldAddress); linkAddr && @@ -601,51 +611,66 @@ EhtFrameExchangeManager::EmlsrSwitchToListening(const Mac48Address& address, con { NS_LOG_DEBUG("EMLSR client " << address << " has been sent an ICF, do not unblock links"); - return; + return false; } } - // this EMLSR client switches back to listening operation a transition delay - // after the given delay - auto emlCapabilities = GetWifiRemoteStationManager()->GetStationEmlCapabilities(address); - NS_ASSERT(emlCapabilities); + // unblock DL transmissions with reason USING_OTHER_EMLSR_LINK + m_mac->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK, + *mldAddress, + linkIds); + return true; +} - std::set linkIds; - for (uint8_t linkId = 0; linkId < m_mac->GetNLinks(); linkId++) - { - if (m_mac->GetWifiRemoteStationManager(linkId)->GetEmlsrEnabled(*mldAddress)) - { - linkIds.insert(linkId); - } - } +void +EhtFrameExchangeManager::EmlsrSwitchToListening(Mac48Address address, const Time& delay) +{ + NS_LOG_FUNCTION(this << address << delay.As(Time::US)); + + auto mldAddress = GetWifiRemoteStationManager()->GetMldAddress(address); + NS_ASSERT_MSG(mldAddress, "MLD address not found for " << address); + NS_ASSERT_MSG(m_apMac, "This function shall only be called by AP MLDs"); auto blockLinks = [=, this]() { - // the reason for blocking the other EMLSR links has changed now - m_mac->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK, - *mldAddress, - linkIds); + if (!UnblockEmlsrLinksIfAllowed(address)) + { + NS_LOG_DEBUG("Could not unblock transmissions to " << address); + return; + } + + // this EMLSR client switches back to listening operation + std::set linkIds; + for (uint8_t linkId = 0; linkId < m_mac->GetNLinks(); linkId++) + { + if (m_mac->GetWifiRemoteStationManager(linkId)->GetEmlsrEnabled(*mldAddress)) + { + linkIds.insert(linkId); + } + } // block DL transmissions on this link until transition delay elapses m_mac->BlockUnicastTxOnLinks(WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY, *mldAddress, linkIds); + + auto unblockLinks = [=, this]() { + m_mac->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY, + *mldAddress, + linkIds); + }; + + // unblock all EMLSR links when the transition delay elapses + auto emlCapabilities = GetWifiRemoteStationManager()->GetStationEmlCapabilities(address); + NS_ASSERT(emlCapabilities); + auto endDelay = CommonInfoBasicMle::DecodeEmlsrTransitionDelay( + emlCapabilities->get().emlsrTransitionDelay); + + endDelay.IsZero() ? unblockLinks() + : static_cast(m_transDelayTimer[*mldAddress] = + Simulator::Schedule(endDelay, unblockLinks)); }; delay.IsZero() ? blockLinks() : static_cast(Simulator::Schedule(delay, blockLinks)); - - // unblock all EMLSR links when the transition delay elapses - auto unblockLinks = [=, this]() { - m_mac->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY, - *mldAddress, - linkIds); - }; - - auto endDelay = delay + CommonInfoBasicMle::DecodeEmlsrTransitionDelay( - emlCapabilities->get().emlsrTransitionDelay); - - endDelay.IsZero() ? unblockLinks() - : static_cast(m_transDelayTimer[*mldAddress] = - Simulator::Schedule(endDelay, unblockLinks)); } void @@ -847,19 +872,91 @@ EhtFrameExchangeManager::SendQosNullFramesInTbPpdu(const CtrlTriggerHeader& trig HeFrameExchangeManager::SendQosNullFramesInTbPpdu(trigger, hdr); } +void +EhtFrameExchangeManager::SwitchToListeningOrUnblockLinks(const std::set& clients) +{ + NS_LOG_FUNCTION(this); + + for (const auto& address : clients) + { + if (GetWifiRemoteStationManager()->GetEmlsrEnabled(address)) + { + // EMLSR client switched to listening operations if it was protected, otherwise + // simply unblock transmissions + m_protectedStas.contains(address) ? EmlsrSwitchToListening(address, Seconds(0)) + : (void)(UnblockEmlsrLinksIfAllowed(address)); + m_protectedStas.erase(address); + } + } +} + void EhtFrameExchangeManager::CtsAfterMuRtsTimeout(Ptr muRts, const WifiTxVector& txVector) { NS_LOG_FUNCTION(this << *muRts << txVector); - // check if all the clients solicited by the MU-RTS are EMLSR clients that have sent (or - // are sending) a frame to the AP + const auto crossLinkCollision = IsCrossLinkCollision(m_sentRtsTo); + + SwitchToListeningOrUnblockLinks(m_sentRtsTo); + + const auto apEmlsrManager = m_apMac->GetApEmlsrManager(); + const auto updateFailedCw = + crossLinkCollision && apEmlsrManager ? apEmlsrManager->UpdateCwAfterFailedIcf() : true; + DoCtsAfterMuRtsTimeout(muRts, txVector, updateFailedCw); +} + +void +EhtFrameExchangeManager::TbPpduTimeout(WifiPsduMap* psduMap, std::size_t nSolicitedStations) +{ + NS_LOG_FUNCTION(this << psduMap << nSolicitedStations); + + const auto& staMissedTbPpduFrom = m_txTimer.GetStasExpectedToRespond(); + const auto crossLinkCollision = IsCrossLinkCollision(staMissedTbPpduFrom); + + if (staMissedTbPpduFrom.size() != nSolicitedStations) + { + // some STAs replied, hence the transmission succeeded. EMLSR clients that did not + // respond are switching back to listening operations + SwitchToListeningOrUnblockLinks(staMissedTbPpduFrom); + } + + const auto apEmlsrManager = m_apMac->GetApEmlsrManager(); + const auto updateFailedCw = + crossLinkCollision && apEmlsrManager ? apEmlsrManager->UpdateCwAfterFailedIcf() : true; + DoTbPpduTimeout(psduMap, nSolicitedStations, updateFailedCw); +} + +void +EhtFrameExchangeManager::BlockAcksInTbPpduTimeout(WifiPsduMap* psduMap, + std::size_t nSolicitedStations) +{ + NS_LOG_FUNCTION(this << psduMap << nSolicitedStations); + + const auto& staMissedTbPpduFrom = m_txTimer.GetStasExpectedToRespond(); + + if (staMissedTbPpduFrom.size() != nSolicitedStations) + { + // some STAs replied, hence the transmission succeeded. EMLSR clients that did not + // respond are switching back to listening operations + SwitchToListeningOrUnblockLinks(staMissedTbPpduFrom); + } + + HeFrameExchangeManager::BlockAcksInTbPpduTimeout(psduMap, nSolicitedStations); +} + +bool +EhtFrameExchangeManager::IsCrossLinkCollision(const std::set& staMissedResponseFrom) +{ + NS_LOG_FUNCTION(this << staMissedResponseFrom.size()); + + // check if all the clients that did not respond to the ICF are EMLSR clients that have sent + // (or are sending) a frame to the AP auto crossLinkCollision = true; // we blocked transmissions on the other EMLSR links for the EMLSR clients we sent the ICF to. - // Given that no client responded, we can unblock transmissions for a client if there is no - // ongoing UL TXOP held by that client - for (const auto& address : m_sentRtsTo) + // For clients that did not respond, we can unblock transmissions if there is no ongoing + // UL TXOP held by that client + for (const auto& address : staMissedResponseFrom) { if (!GetWifiRemoteStationManager()->GetEmlsrEnabled(address)) { @@ -908,16 +1005,9 @@ EhtFrameExchangeManager::CtsAfterMuRtsTimeout(Ptr muRts, const WifiTxV { crossLinkCollision = false; } - - linkIds.erase(m_linkId); - m_mac->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK, - *mldAddress, - linkIds); } - auto updateFailedCw = - crossLinkCollision ? m_apMac->GetApEmlsrManager()->UpdateCwAfterFailedIcf() : true; - DoCtsAfterMuRtsTimeout(muRts, txVector, updateFailedCw); + return crossLinkCollision; } void diff --git a/src/wifi/model/eht/eht-frame-exchange-manager.h b/src/wifi/model/eht/eht-frame-exchange-manager.h index 2db07b227..4bb48c1cb 100644 --- a/src/wifi/model/eht/eht-frame-exchange-manager.h +++ b/src/wifi/model/eht/eht-frame-exchange-manager.h @@ -153,7 +153,7 @@ class EhtFrameExchangeManager : public HeFrameExchangeManager * \param address the link MAC address of the given EMLSR client * \param delay the given delay */ - void EmlsrSwitchToListening(const Mac48Address& address, const Time& delay); + void EmlsrSwitchToListening(Mac48Address address, const Time& delay); /** * \return a reference to the event indicating the possible end of the current TXOP (of @@ -202,12 +202,33 @@ class EhtFrameExchangeManager : public HeFrameExchangeManager void ReceivedQosNullAfterBsrpTf(Mac48Address sender) override; void SendQosNullFramesInTbPpdu(const CtrlTriggerHeader& trigger, const WifiMacHeader& hdr) override; + void TbPpduTimeout(WifiPsduMap* psduMap, std::size_t nSolicitedStations) override; + void BlockAcksInTbPpduTimeout(WifiPsduMap* psduMap, std::size_t nSolicitedStations) override; /** * \return whether this is an EMLSR client that cannot respond to an ICF received a SIFS before */ bool EmlsrClientCannotRespondToIcf() const; + /** + * Check whether all the stations that did not respond (to a certain frame) are EMLSR clients + * trying to start an UL TXOP on another link. + * + * \param staMissedResponseFrom stations that did not respond + * \return whether all the stations that did not respond are EMLSR clients trying to start an + * UL TXOP on another link + */ + bool IsCrossLinkCollision(const std::set& staMissedResponseFrom); + + /** + * Unblock transmissions on all the links of the given EMLSR client, provided that the latter + * is not involved in any DL or UL TXOP on another link. + * + * \param address the link MAC address of the given EMLSR client + * \return whether transmissions could be unblocked + */ + bool UnblockEmlsrLinksIfAllowed(Mac48Address address); + private: /** * \return whether the received ICF must be dropped because we are unable to process it @@ -215,6 +236,15 @@ class EhtFrameExchangeManager : public HeFrameExchangeManager */ bool DropReceivedIcf(); + /** + * For each EMLSR client in the given set of clients that did not respond to a frame requesting + * a response from multiple clients, have the client switch to listening or simply unblock + * links depending on whether the EMLSR client was protected or not. + * + * \param clients the given set of clients + */ + void SwitchToListeningOrUnblockLinks(const std::set& clients); + /** * Generate an in-device interference of the given power on the given link for the given * duration. diff --git a/src/wifi/model/he/he-frame-exchange-manager.cc b/src/wifi/model/he/he-frame-exchange-manager.cc index 555bed9a9..14a3a36c3 100644 --- a/src/wifi/model/he/he-frame-exchange-manager.cc +++ b/src/wifi/model/he/he-frame-exchange-manager.cc @@ -1312,9 +1312,19 @@ HeFrameExchangeManager::GetTxDuration(uint32_t ppduPayloadSize, void HeFrameExchangeManager::TbPpduTimeout(WifiPsduMap* psduMap, std::size_t nSolicitedStations) +{ + NS_LOG_FUNCTION(this << psduMap << nSolicitedStations); + DoTbPpduTimeout(psduMap, nSolicitedStations, true); +} + +void +HeFrameExchangeManager::DoTbPpduTimeout(WifiPsduMap* psduMap, + std::size_t nSolicitedStations, + bool updateFailedCw) { const auto& staMissedTbPpduFrom = m_txTimer.GetStasExpectedToRespond(); - NS_LOG_FUNCTION(this << psduMap << staMissedTbPpduFrom.size() << nSolicitedStations); + NS_LOG_FUNCTION(this << psduMap << staMissedTbPpduFrom.size() << nSolicitedStations + << updateFailedCw); NS_ASSERT(psduMap); NS_ASSERT(IsTrigger(*psduMap)); @@ -1326,7 +1336,10 @@ HeFrameExchangeManager::TbPpduTimeout(WifiPsduMap* psduMap, std::size_t nSolicit if (staMissedTbPpduFrom.size() == nSolicitedStations) { // no station replied, the transmission failed - m_edca->UpdateFailedCw(m_linkId); + if (updateFailedCw) + { + m_edca->UpdateFailedCw(m_linkId); + } CtrlTriggerHeader trigger; psduMap->cbegin()->second->GetPayload(0)->PeekHeader(trigger); diff --git a/src/wifi/model/he/he-frame-exchange-manager.h b/src/wifi/model/he/he-frame-exchange-manager.h index c688c68e6..a4ff0d483 100644 --- a/src/wifi/model/he/he-frame-exchange-manager.h +++ b/src/wifi/model/he/he-frame-exchange-manager.h @@ -278,6 +278,17 @@ class HeFrameExchangeManager : public VhtFrameExchangeManager */ virtual void TbPpduTimeout(WifiPsduMap* psduMap, std::size_t nSolicitedStations); + /** + * Take the necessary actions after that some TB PPDUs are missing in + * response to Trigger Frame. This method must not be called if all the + * expected TB PPDUs were received. + * + * \param psduMap a pointer to PSDU map transmitted in a DL MU PPDU + * \param nSolicitedStations the number of stations solicited to send a TB PPDU + * \param updateFailedCw whether to update CW in case the transmission failed + */ + void DoTbPpduTimeout(WifiPsduMap* psduMap, std::size_t nSolicitedStations, bool updateFailedCw); + /** * Take the necessary actions after that a Block Ack is missing after a * TB PPDU solicited through a Trigger Frame.