diff --git a/src/wifi/model/eht/default-emlsr-manager.cc b/src/wifi/model/eht/default-emlsr-manager.cc index dd229bd3e..040bbd3ea 100644 --- a/src/wifi/model/eht/default-emlsr-manager.cc +++ b/src/wifi/model/eht/default-emlsr-manager.cc @@ -24,6 +24,7 @@ #include "ns3/boolean.h" #include "ns3/channel-access-manager.h" #include "ns3/log.h" +#include "ns3/qos-txop.h" #include "ns3/wifi-mpdu.h" #include "ns3/wifi-net-device.h" #include "ns3/wifi-phy.h" @@ -192,4 +193,109 @@ DefaultEmlsrManager::DoNotifyTxopEnd(uint8_t linkId) } } +bool +DefaultEmlsrManager::SwitchMainPhyIfTxopGainedByAuxPhy(uint8_t linkId) +{ + NS_LOG_FUNCTION(this << linkId); + + NS_ASSERT_MSG(!m_auxPhyTxCapable, + "This function should only be called if aux PHY is not TX capable"); + + // the aux PHY is not TX capable; check if main PHY has to switch to the aux PHY's link + auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId); + + // if the main PHY is idle, check whether the remaining backoff counter on at least an AC with + // queued packets is greater than the main PHY channel switch delay + auto backoffGreaterThanSwitchDelay = false; + + if (mainPhy->IsStateIdle()) + { + auto mainPhyLinkId = GetStaMac()->GetLinkForPhy(mainPhy); + NS_ASSERT(mainPhyLinkId.has_value()); + + // update backoff on main PHY link for all ACs + GetStaMac() + ->GetChannelAccessManager(*mainPhyLinkId) + ->NeedBackoffUponAccess(GetStaMac()->GetQosTxop(AC_BE), + Txop::HAD_FRAMES_TO_TRANSMIT, + Txop::CHECK_MEDIUM_BUSY); + + for (const auto& [aci, ac] : wifiAcList) + { + if (auto edca = GetStaMac()->GetQosTxop(aci); edca->HasFramesToTransmit(linkId)) + { + auto backoffEnd = + GetStaMac()->GetChannelAccessManager(*mainPhyLinkId)->GetBackoffEndFor(edca); + NS_LOG_DEBUG("Backoff end for " << aci + << " on primary link: " << backoffEnd.As(Time::US)); + + if (backoffEnd > Simulator::Now() + mainPhy->GetChannelSwitchDelay() + + GetStaMac()->GetWifiPhy(linkId)->GetPifs()) + { + backoffGreaterThanSwitchDelay = true; + break; + } + } + } + } + + if ((mainPhy->IsStateCcaBusy() && !mainPhy->IsReceivingPhyHeader()) || + (mainPhy->IsStateIdle() && backoffGreaterThanSwitchDelay)) + { + // switch main PHY + SwitchMainPhy(linkId, false, RESET_BACKOFF, REQUEST_ACCESS); + + return true; + } + + // Determine if and when we need to request channel access again for the aux PHY based on + // the main PHY state. + // Note that, if we have requested the main PHY to switch (above), the function has returned + // and the EHT FEM will start a TXOP if the medium is idle for a PIFS interval following + // the end of the main PHY channel switch. + // If the state is switching, but we have not requested the main PHY to switch, then we + // request channel access again for the aux PHY a PIFS after that the main PHY state is back + // to IDLE (to avoid stealing the main PHY from the non-primary link which the main PHY is + // switching to), and then we will determine if the main PHY has to switch link. + // If the state is CCA_BUSY, the medium is busy but the main PHY is not receiving a PPDU. + // In this case, we request channel access again for the aux PHY a PIFS after that the main + // PHY state is back to IDLE, and then we will determine if the main PHY has to switch link. + // If the state is TX or RX, it means that the main PHY is involved in a TXOP. In this + // case, do nothing because the channel access will be requested when unblocking links + // at the end of the TXOP. + // If the state is IDLE, then either no AC has traffic to send or the backoff on the link + // of the main PHY is shorter than the channel switch delay. In the former case, do + // nothing because channel access will be triggered when new packets arrive; in the latter + // case, do nothing because the main PHY will start a TXOP and at the end of such TXOP + // links will be unblocked and the channel access requested on all links + + if (!mainPhy->IsStateSwitching() && !mainPhy->IsStateCcaBusy()) + { + NS_LOG_DEBUG("Main PHY state is " << mainPhy->GetState()->GetState() << ". Do nothing"); + return false; + } + + auto delay = mainPhy->GetDelayUntilIdle(); + NS_ASSERT(delay.IsStrictlyPositive()); + delay += mainPhy->GetSifs() + mainPhy->GetSlot(); + + NS_LOG_DEBUG("Main PHY state is " << mainPhy->GetState()->GetState() + << ". Schedule channel access request on link " << +linkId + << " at time " << (Simulator::Now() + delay).As(Time::NS)); + Simulator::Schedule(delay, [=, this]() { + for (const auto& [aci, ac] : wifiAcList) + { + auto edca = GetStaMac()->GetQosTxop(aci); + if (edca->GetAccessStatus(linkId) != Txop::REQUESTED && + edca->HasFramesToTransmit(linkId)) + { + NS_LOG_DEBUG("Request channel access on link " << +linkId << " for " << aci); + GetStaMac()->GetChannelAccessManager(linkId)->RequestAccess(edca); + } + } + }); + + return false; +} + } // namespace ns3 diff --git a/src/wifi/model/eht/default-emlsr-manager.h b/src/wifi/model/eht/default-emlsr-manager.h index 8a7c567fe..5f1223813 100644 --- a/src/wifi/model/eht/default-emlsr-manager.h +++ b/src/wifi/model/eht/default-emlsr-manager.h @@ -44,6 +44,8 @@ class DefaultEmlsrManager : public EmlsrManager DefaultEmlsrManager(); ~DefaultEmlsrManager() override; + bool SwitchMainPhyIfTxopGainedByAuxPhy(uint8_t linkId) override; + protected: uint8_t GetLinkToSendEmlOmn() override; std::optional ResendNotification(Ptr mpdu) override; diff --git a/src/wifi/model/eht/eht-frame-exchange-manager.cc b/src/wifi/model/eht/eht-frame-exchange-manager.cc index 673978d48..035e56a1a 100644 --- a/src/wifi/model/eht/eht-frame-exchange-manager.cc +++ b/src/wifi/model/eht/eht-frame-exchange-manager.cc @@ -285,27 +285,51 @@ EhtFrameExchangeManager::StartTransmission(Ptr edca, ChannelWidthMhz allow mainPhy != m_phy) { // an aux PHY is operating on this link + if (!emlsrManager->GetAuxPhyTxCapable()) { NS_LOG_DEBUG("Aux PHY is not capable of transmitting a PPDU"); + + if (emlsrManager->SwitchMainPhyIfTxopGainedByAuxPhy(m_linkId)) + { + NS_ASSERT_MSG(mainPhy->IsStateSwitching(), + "SwitchMainPhyIfTxopGainedByAuxPhy returned true but main PHY is " + "not switching"); + + const auto pifs = m_phy->GetSifs() + m_phy->GetSlot(); + auto checkMediumLastPifs = [=, this]() { + // check if the medium has been idle for the last PIFS interval + auto width = m_staMac->GetChannelAccessManager(m_linkId) + ->GetLargestIdlePrimaryChannel(pifs, Simulator::Now()); + + if (width == 0) + { + NS_LOG_DEBUG("Medium busy in the last PIFS after channel switch end"); + edca->StartAccessAfterEvent(m_linkId, + Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT, + Txop::CHECK_MEDIUM_BUSY); + return; + } + + // medium idle, start a TXOP + if (HeFrameExchangeManager::StartTransmission(edca, width)) + { + // notify the EMLSR Manager of the UL TXOP start on an EMLSR link + emlsrManager->NotifyUlTxopStart(m_linkId, std::nullopt); + } + }; + Simulator::Schedule(mainPhy->GetDelayUntilIdle() + pifs, checkMediumLastPifs); + } + NotifyChannelReleased(edca); return false; } - if (mainPhy->IsStateRx()) - { - NS_LOG_DEBUG( - "Main PHY is receiving a PPDU (may be, e.g., an ICF or a Beacon); do not " - "transmit to avoid dropping that PPDU due to the main PHY switching to this " - "link to take over the TXOP"); - // Note that we do not prevent a (main or aux) PHY from starting a TXOP when - // an(other) aux PHY is receiving a PPDU. The reason is that if the aux PHY is - // receiving a Beacon frame, the aux PHY will not be affected by the start of - // a TXOP; if the aux PHY is receiving an ICF, the ICF will be dropped by - // ReceiveMpdu because another EMLSR link is being used. - NotifyChannelReleased(edca); - return false; - } + // Note that we do not prevent a (main or aux) PHY from starting a TXOP when + // an(other) aux PHY is receiving a PPDU. The reason is that if the aux PHY is + // receiving a Beacon frame, the aux PHY will not be affected by the start of + // a TXOP; if the aux PHY is receiving an ICF, the ICF will be dropped by + // ReceiveMpdu because another EMLSR link is being used. const auto rtsTxVector = GetWifiRemoteStationManager()->GetRtsTxVector(m_bssid, allowedWidth); @@ -323,21 +347,37 @@ EhtFrameExchangeManager::StartTransmission(Ptr edca, ChannelWidthMhz allow auto switchingTime = mainPhy->GetChannelSwitchDelay(); - if (mainPhy->IsStateSwitching()) + switch (mainPhy->GetState()->GetState()) { - // the main PHY is switching (to another link), hence the remaining time to the - // end of the current channel switch needs to be added up + case WifiPhyState::RX: + case WifiPhyState::SWITCHING: + // the main PHY is receiving or switching (to another link), hence the remaining + // time to the end of the current reception/channel switch needs to be added up switchingTime += mainPhy->GetDelayUntilIdle(); - } - - if (switchingTime > timeToCtsEnd) - { - // switching takes longer than RTS/CTS exchange, do not transmit anything to - // avoid that the main PHY is requested to switch while already switching - NS_LOG_DEBUG("Main PHY will still be switching channel when RTS/CTS ends, thus it " - "will not be able to take over this TXOP"); + [[fallthrough]]; + case WifiPhyState::IDLE: + case WifiPhyState::CCA_BUSY: + if (!mainPhy->IsReceivingPhyHeader() && switchingTime <= timeToCtsEnd) + { + break; // start TXOP + } + // release channel + if (mainPhy->IsReceivingPhyHeader()) + { + NS_LOG_DEBUG( + "Aux PHY cannot start TXOP because main PHY is receiving a PHY header"); + } + else + { + // switching takes longer than RTS/CTS exchange, do not transmit anything to + // avoid that the main PHY is requested to switch while already switching + NS_LOG_DEBUG("Not enough time for main PHY to switch link (main PHY state: " + << mainPhy->GetState() << ")"); + } NotifyChannelReleased(edca); return false; + default: + NS_ABORT_MSG("Main PHY cannot be in state " << mainPhy->GetState()); } } } diff --git a/src/wifi/model/eht/emlsr-manager.h b/src/wifi/model/eht/emlsr-manager.h index 4dc507aeb..16366a208 100644 --- a/src/wifi/model/eht/emlsr-manager.h +++ b/src/wifi/model/eht/emlsr-manager.h @@ -194,6 +194,16 @@ class EmlsrManager : public Object */ void NotifyTxopEnd(uint8_t linkId, bool ulTxopNotStarted = false, bool ongoingDlTxop = false); + /** + * This method is intended to notify the EMLSR Manager that an aux PHY that is NOT TX capable + * has gained a TXOP on a given link and returns whether the main PHY has been requested to + * switch to the given link to take over the TXOP. + * + * \param linkId the ID of the given link + * \return whether main PHY has been requested to switch + */ + virtual bool SwitchMainPhyIfTxopGainedByAuxPhy(uint8_t linkId) = 0; + /** * Check whether the MediumSyncDelay timer is running for the STA operating on the given link. * If so, returns the time elapsed since the timer started.