A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
emlsr-manager.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2023 Universita' degli Studi di Napoli Federico II
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 *
6 * Author: Stefano Avallone <stavallo@unina.it>
7 */
8
9#include "emlsr-manager.h"
10
11#include "eht-configuration.h"
13
14#include "ns3/abort.h"
15#include "ns3/assert.h"
16#include "ns3/attribute-container.h"
17#include "ns3/log.h"
18#include "ns3/mgt-action-headers.h"
19#include "ns3/wifi-mpdu.h"
20#include "ns3/wifi-net-device.h"
21#include "ns3/wifi-phy-state-helper.h"
22
23#include <iterator>
24#include <sstream>
25
26namespace ns3
27{
28
29NS_LOG_COMPONENT_DEFINE("EmlsrManager");
30
31NS_OBJECT_ENSURE_REGISTERED(EmlsrManager);
32
33TypeId
35{
36 static TypeId tid =
37 TypeId("ns3::EmlsrManager")
39 .SetGroupName("Wifi")
40 .AddAttribute("EmlsrPaddingDelay",
41 "The EMLSR Paddind Delay (not used by AP MLDs). "
42 "Possible values are 0 us, 32 us, 64 us, 128 us or 256 us.",
46 .AddAttribute("EmlsrTransitionDelay",
47 "The EMLSR Transition Delay (not used by AP MLDs). "
48 "Possible values are 0 us, 16 us, 32 us, 64 us, 128 us or 256 us.",
52 .AddAttribute(
53 "MainPhyId",
54 "The ID of the main PHY (position in the vector of PHYs held by "
55 "WifiNetDevice). This attribute cannot be set after construction.",
56 TypeId::ATTR_GET | TypeId::ATTR_CONSTRUCT, // prevent setting after construction
60 .AddAttribute("AuxPhyChannelWidth",
61 "The maximum channel width (MHz) supported by Aux PHYs. Note that the "
62 "maximum channel width is capped to the maximum channel width supported "
63 "by the configured maximum modulation class supported.",
65 TypeId::ATTR_CONSTRUCT, // prevent setting after construction
66 UintegerValue(20),
69 .AddAttribute("AuxPhyMaxModClass",
70 "The maximum modulation class supported by Aux PHYs. Use "
71 "WIFI_MOD_CLASS_OFDM for non-HT.",
73 TypeId::ATTR_CONSTRUCT, // prevent setting after construction
77 "HR-DSSS",
79 "ERP-OFDM",
81 "OFDM",
83 "HT",
85 "VHT",
87 "HE",
89 "EHT"))
90 .AddAttribute("AuxPhyTxCapable",
91 "Whether Aux PHYs are capable of transmitting PPDUs.",
92 BooleanValue(true),
96 .AddAttribute("InDeviceInterference",
97 "Whether in-device interference is such that a PHY cannot decode "
98 "anything and cannot decrease the backoff counter when another PHY "
99 "of the same device is transmitting.",
100 BooleanValue(false),
104 .AddAttribute("PutAuxPhyToSleep",
105 "Whether Aux PHYs should be put into sleep mode while the Main PHY "
106 "is carrying out a (DL or UL) TXOP. Specifically, for DL TXOPs, aux "
107 "PHYs are put to sleep after receiving the ICF; for UL TXOPs, aux PHYs "
108 "are put to sleep when the CTS frame is received, if RTS/CTS is used, "
109 "or when the transmission of the data frame starts, otherwise. "
110 "Aux PHYs are resumed from sleep when the TXOP ends.",
111 BooleanValue(false),
114 .AddAttribute("UseNotifiedMacHdr",
115 "Whether to use the information about the MAC header of the MPDU "
116 "being received, if notified by the PHY.",
117 BooleanValue(true),
120 .AddAttribute(
121 "EmlsrLinkSet",
122 "IDs of the links on which EMLSR mode will be enabled. An empty set "
123 "indicates to disable EMLSR.",
127 .AddAttribute("ResetCamState",
128 "Whether to reset the state of the ChannelAccessManager associated with "
129 "the link on which the main PHY has just switched to.",
130 BooleanValue(false),
134 .AddTraceSource("MainPhySwitch",
135 "This trace source is fired when the main PHY switches channel to "
136 "operate on another link. Information associated with the main PHY "
137 "switch is provided through a struct that is inherited from struct "
138 "EmlsrMainPhySwitchTrace (use the GetName() method to get the type "
139 "of the provided object).",
141 "ns3::EmlsrManager::MainPhySwitchCallback");
142 return tid;
143}
144
146 : m_mainPhySwitchInfo{},
147 // The STA initializes dot11MSDTimerDuration to aPPDUMaxTime defined in Table 36-70
148 // (Sec. 35.3.16.8.1 of 802.11be D3.1)
149 m_mediumSyncDuration(MicroSeconds(DEFAULT_MSD_DURATION_USEC)),
150 // The default value of dot11MSDOFDMEDthreshold is –72 dBm and the default value of
151 // dot11MSDTXOPMax is 1, respectively (Sec. 35.3.16.8.1 of 802.11be D3.1)
152 m_msdOfdmEdThreshold(DEFAULT_MSD_OFDM_ED_THRESH),
153 m_msdMaxNTxops(DEFAULT_MSD_MAX_N_TXOPS)
154{
155 NS_LOG_FUNCTION(this);
156}
157
162
163void
165{
166 NS_LOG_FUNCTION(this);
167 m_staMac->TraceDisconnectWithoutContext("AckedMpdu", MakeCallback(&EmlsrManager::TxOk, this));
168 m_staMac->TraceDisconnectWithoutContext("DroppedMpdu",
170 m_staMac = nullptr;
172 for (auto& [id, status] : m_mediumSyncDelayStatus)
173 {
174 status.timer.Cancel();
175 }
177}
178
179void
181{
182 NS_LOG_FUNCTION(this << mac);
183 NS_ASSERT(mac);
184 m_staMac = mac;
185
186 NS_ABORT_MSG_IF(!m_staMac->GetEhtConfiguration(), "EmlsrManager requires EHT support");
187 NS_ABORT_MSG_IF(m_staMac->GetNLinks() <= 1, "EmlsrManager can only be installed on MLDs");
188 NS_ABORT_MSG_IF(m_staMac->GetTypeOfStation() != STA,
189 "EmlsrManager can only be installed on non-AP MLDs");
190
191 m_staMac->TraceConnectWithoutContext("AckedMpdu", MakeCallback(&EmlsrManager::TxOk, this));
192 m_staMac->TraceConnectWithoutContext("DroppedMpdu",
194 m_staMac->TraceConnectWithoutContext(
195 "EmlsrLinkSwitch",
197 DoSetWifiMac(mac);
198}
199
200void
205
206void
208{
209 NS_LOG_FUNCTION(this << linkId << phy);
210
211 if (!phy)
212 {
213 NS_ASSERT(!m_noPhySince.contains(linkId));
214 NS_LOG_DEBUG("Record that no PHY is operating on link " << +linkId);
215 m_noPhySince[linkId] = Simulator::Now();
216 return;
217 }
218
219 // phy switched to operate on the link with ID equal to linkId
220 auto it = m_noPhySince.find(linkId);
221
222 if (it == m_noPhySince.end())
223 {
224 // phy switched to a link on which another PHY was operating, do nothing
225 return;
226 }
227
228 auto duration = Simulator::Now() - it->second;
229 NS_ASSERT_MSG(duration.IsPositive(), "Interval duration should not be negative");
230
231 NS_LOG_DEBUG("PHY " << +phy->GetPhyId() << " switched to link " << +linkId << " after "
232 << duration.As(Time::US)
233 << " since last time a PHY was operating on this link");
235 {
237 }
238
239 m_noPhySince.erase(it);
240}
241
242void
244{
245 NS_LOG_FUNCTION(this << mainPhyId);
246 NS_ABORT_MSG_IF(IsInitialized(), "Cannot be called once this object has been initialized");
247 m_mainPhyId = mainPhyId;
248}
249
250uint8_t
252{
253 return m_mainPhyId;
254}
255
256void
258{
259 m_resetCamState = enable;
260}
261
262bool
267
268void
270{
271 m_auxPhyTxCapable = capable;
272}
273
274bool
279
280void
285
286bool
291
292const std::set<uint8_t>&
294{
295 return m_emlsrLinks;
296}
297
300{
301 return m_staMac;
302}
303
305EmlsrManager::GetEhtFem(uint8_t linkId) const
306{
307 return StaticCast<EhtFrameExchangeManager>(m_staMac->GetFrameExchangeManager(linkId));
308}
309
310std::optional<Time>
312{
313 if (const auto statusIt = m_mediumSyncDelayStatus.find(linkId);
314 statusIt != m_mediumSyncDelayStatus.cend() && statusIt->second.timer.IsPending())
315 {
316 return m_mediumSyncDuration - Simulator::GetDelayLeft(statusIt->second.timer);
317 }
318 return std::nullopt;
319}
320
321void
327
328std::optional<Time>
333
334void
336{
337 NS_LOG_FUNCTION(this << duration.As(Time::US));
338 m_mediumSyncDuration = duration;
339}
340
341Time
346
347void
349{
350 NS_LOG_FUNCTION(this << threshold);
351 m_msdOfdmEdThreshold = threshold;
352}
353
354int8_t
359
360void
361EmlsrManager::SetMediumSyncMaxNTxops(std::optional<uint8_t> nTxops)
362{
363 NS_LOG_FUNCTION(this << nTxops.has_value());
364 m_msdMaxNTxops = nTxops;
365}
366
367std::optional<uint8_t>
372
373void
374EmlsrManager::SetEmlsrLinks(const std::set<uint8_t>& linkIds)
375{
376 std::stringstream ss;
377 if (g_log.IsEnabled(ns3::LOG_FUNCTION))
378 {
379 std::copy(linkIds.cbegin(), linkIds.cend(), std::ostream_iterator<uint16_t>(ss, " "));
380 }
381 NS_LOG_FUNCTION(this << ss.str());
382 NS_ABORT_MSG_IF(linkIds.size() == 1, "Cannot enable EMLSR mode on a single link");
383
384 if (linkIds != m_emlsrLinks)
385 {
386 m_nextEmlsrLinks = linkIds;
387 }
388
389 if (GetStaMac() && GetStaMac()->IsAssociated() && GetTransitionTimeout() && m_nextEmlsrLinks)
390 {
391 // Request to enable EMLSR mode on the given links, provided that they have been setup
392 SendEmlOmn();
393 }
394}
395
396void
398{
399 NS_LOG_FUNCTION(this << *mpdu << linkId);
400
401 const auto& hdr = mpdu->GetHeader();
402
403 DoNotifyMgtFrameReceived(mpdu, linkId);
404
405 if (hdr.IsAssocResp() && GetStaMac()->IsAssociated() && GetTransitionTimeout())
406 {
407 // we just completed ML setup with an AP MLD that supports EMLSR
409
410 if (m_nextEmlsrLinks && !m_nextEmlsrLinks->empty())
411 {
412 // a non-empty set of EMLSR links have been configured, hence enable EMLSR mode
413 // on those links
414 SendEmlOmn();
415 }
416 }
417
418 if (hdr.IsAction() && hdr.GetAddr2() == m_staMac->GetBssid(linkId))
419 {
420 // this is an action frame sent by an AP of the AP MLD we are associated with
421 auto [category, action] = WifiActionHeader::Peek(mpdu->GetPacket());
422 if (category == WifiActionHeader::PROTECTED_EHT &&
423 action.protectedEhtAction ==
425 {
427 {
428 // no need to wait until the expiration of the transition timeout
431 }
432 }
433 }
434}
435
436void
438{
439 NS_LOG_FUNCTION(this << linkId);
440
441 NS_ASSERT(m_staMac->IsEmlsrLink(linkId));
442
443 // block transmissions and suspend medium access on all other EMLSR links
444 for (auto id : m_staMac->GetLinkIds())
445 {
446 if (id != linkId && m_staMac->IsEmlsrLink(id))
447 {
449 }
450 }
451
452 auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId);
453 auto rxPhy = m_staMac->GetWifiPhy(linkId);
454
455 const auto receivedByAuxPhy = (rxPhy != mainPhy);
456 const auto mainPhyOnALink = (m_staMac->GetLinkForPhy(mainPhy).has_value());
457 const auto mainPhyIsSwitching =
458 (mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}) == Simulator::Now());
459 // if the main PHY is not operating on a link and it is not switching, then we have postponed
460 // the reconnection of the main PHY to a link because a PPDU reception was ongoing on that link
461 const auto mainPhyToConnect = (!mainPhyOnALink && !mainPhyIsSwitching);
462
463 const auto mainPhyToConnectToOtherLink = mainPhyToConnect && (m_mainPhySwitchInfo.to != linkId);
464
465 if (mainPhyToConnect)
466 {
467 // If ICF was received on a link other than the one the main PHY is waiting to be connected
468 // to, we need to cancel the pending reconnection and request a new main PHY switch.
469 // If ICF was received on the link the main PHY is waiting to be connected to, we cancel
470 // the pending reconnection and explicitly request the reconnection below
471 GetStaMac()->CancelEmlsrPhyConnectEvent(mainPhy->GetPhyId());
472 }
473
474 // We need to request a main PHY switch if:
475 // - the ICF was received by an aux PHY, AND
476 // - the main PHY is not waiting to be connected to a link OR it is waiting to be connected
477 // to a link other than the link on which the ICF is received.
478 if (receivedByAuxPhy && (!mainPhyToConnect || mainPhyToConnectToOtherLink))
479 {
480 SwitchMainPhy(linkId,
481 true, // channel switch should occur instantaneously
485 }
486 else if (mainPhyToConnect && !mainPhyToConnectToOtherLink)
487 {
488 // If the main PHY is waiting to be connected to the link on which the ICF was received, we
489 // have to explicitly perform the connection because the pending event was cancelled above.
490 // We do this way in order to reconnect the main PHY before putting aux PHYs to sleep: if
491 // the aux PHY is put to sleep while still operating on the link on which it received the
492 // ICF, all the MAC events (including scheduled CTS transmission) will be cancelled.
493 m_staMac->NotifySwitchingEmlsrLink(mainPhy, linkId, Time{0});
494 }
495
496 if (receivedByAuxPhy)
497 {
498 // aux PHY received the ICF but main PHY will send the response
499 auto uid = rxPhy->GetPreviouslyRxPpduUid();
500 mainPhy->SetPreviouslyRxPpduUid(uid);
501 }
502
503 // a DL TXOP started, set all aux PHYs to sleep
504 if (m_auxPhyToSleep)
505 {
507 }
508
509 DoNotifyIcfReceived(linkId);
510}
511
512std::pair<bool, Time>
514{
515 auto phy = m_staMac->GetWifiPhy(linkId);
516 NS_ASSERT_MSG(phy, "No PHY operating on link " << +linkId);
517
518 auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId);
519
520 // check possible reasons to give up the TXOP that apply to both main PHY and aux PHYs
521 if (const auto [startTxop, delay] = DoGetDelayUntilAccessRequest(linkId); !startTxop)
522 {
523 return {false, delay};
524 }
525
526 if (phy == mainPhy)
527 {
528 // no more constraints to check if medium was gained by main PHY
529 return {true, Time{0}};
530 }
531
532 // an aux PHY is operating on the given link; call the appropriate method depending on
533 // whether the aux PHY is TX capable or not
535 {
537 // if the aux PHY is not TX capable, we don't have to request channel access: if the main
538 // PHY switches link, the UL TXOP will be started; if the main PHY does not switch, it is
539 // because it is going to start an UL TXOP on another link and this link will be restarted
540 // at the end of that UL TXOP when this link will be unblocked
541 NS_LOG_DEBUG("Aux PHY is not capable of transmitting a PPDU");
542 return {false, Time{0}};
543 }
544
546}
547
548std::pair<bool, Time>
550{
551 NS_LOG_FUNCTION(this << linkId);
552
553 auto phy = GetStaMac()->GetWifiPhy(linkId);
554
555 if (!phy || !GetStaMac()->IsEmlsrLink(linkId))
556 {
557 NS_LOG_DEBUG("No PHY (" << phy << ") or not an EMLSR link (" << +linkId << ")");
558 return {false, Time{0}};
559 }
560
561 if (auto macHdr = GetEhtFem(linkId)->GetReceivedMacHdr(); macHdr && m_useNotifiedMacHdr)
562 {
563 NS_LOG_DEBUG("Receiving the MAC payload of a PSDU and MAC header info can be used");
564 NS_ASSERT(phy->GetState()->GetLastTime({WifiPhyState::RX}) == Simulator::Now());
565
566 if (const auto& hdr = macHdr->get();
567 hdr.IsTrigger() &&
568 (hdr.GetAddr1().IsBroadcast() || hdr.GetAddr1() == GetEhtFem(linkId)->GetAddress()))
569 {
570 // the PSDU being received _may_ be an ICF. Note that we cannot be sure that the PSDU
571 // being received is an ICF addressed to us until we receive the entire PSDU
572 NS_LOG_DEBUG("Based on MAC header, may be an ICF, postpone by "
573 << phy->GetDelayUntilIdle().As(Time::US));
574 return {true, phy->GetDelayUntilIdle()};
575 }
576 }
577 else if (auto txVector = phy->GetInfoIfRxingPhyHeader())
578 {
579 NS_LOG_DEBUG("Receiving PHY header");
580 if (txVector->get().GetModulationClass() < WIFI_MOD_CLASS_HT)
581 {
582 // the PHY header of a non-HT PPDU, which may be an ICF, is being received; check
583 // again after the TX duration of a non-HT PHY header
584 NS_LOG_DEBUG("PHY header of a non-HT PPDU, which may be an ICF, is being received");
585 return {true, EMLSR_RX_PHY_START_DELAY};
586 }
587 }
588 else if (phy->IsStateRx())
589 {
590 // we have not yet received the MAC header or we cannot use its info
591
592 auto ongoingRxInfo = GetEhtFem(linkId)->GetOngoingRxInfo();
593 // if a PHY is in RX state, it should have info about received MAC header.
594 // The exception is represented by this situation:
595 // - an aux PHY is disconnected from the MAC stack because the main PHY is
596 // operating on its link
597 // - the main PHY notifies the MAC header info to the FEM and then leaves the
598 // link (e.g., because it recognizes that the MPDU is not addressed to the
599 // EMLSR client). Disconnecting the main PHY from the MAC stack causes the
600 // MAC header info to be discarded by the FEM
601 // - the aux PHY is re-connected to the MAC stack and is still in RX state
602 // when the main PHY gets channel access on another link (and we get here)
603 if (!ongoingRxInfo.has_value())
604 {
605 NS_ASSERT_MSG(phy != GetStaMac()->GetDevice()->GetPhy(GetMainPhyId()),
606 "Main PHY should have MAC header info when in RX state");
607 // we are in the situation described above; if the MPDU being received
608 // by the aux PHY is not addressed to the EMLSR client, we can ignore it
609 }
610 else if (const auto& txVector = ongoingRxInfo->get().txVector;
611 txVector.GetModulationClass() < WIFI_MOD_CLASS_HT)
612 {
613 if (auto remTime = phy->GetTimeToMacHdrEnd(SU_STA_ID);
614 m_useNotifiedMacHdr && remTime.has_value() && remTime->IsStrictlyPositive())
615 {
616 NS_LOG_DEBUG("Wait until the expected end of the MAC header reception: "
617 << remTime->As(Time::US));
618 return {true, *remTime};
619 }
620
622 "MAC header info will not be available. Wait until the end of PSDU reception: "
623 << phy->GetDelayUntilIdle().As(Time::US));
624 return {true, phy->GetDelayUntilIdle()};
625 }
626 }
627
628 NS_LOG_DEBUG("No ICF being received, state: " << phy->GetState()->GetState());
629 return {false, Time{0}};
630}
631
632void
634{
635 NS_LOG_FUNCTION(this << linkId);
636
637 if (!m_staMac->IsEmlsrLink(linkId))
638 {
639 NS_LOG_DEBUG("EMLSR is not enabled on link " << +linkId);
640 return;
641 }
642
643 // block transmissions and suspend medium access on all other EMLSR links
644 for (auto id : m_staMac->GetLinkIds())
645 {
646 if (id != linkId && m_staMac->IsEmlsrLink(id))
647 {
649 }
650 }
651
652 DoNotifyUlTxopStart(linkId);
653}
654
655void
657{
658 NS_LOG_FUNCTION(this << *rts << txVector);
659}
660
661void
663{
664 NS_LOG_FUNCTION(this << linkId);
665
666 if (m_auxPhyToSleep && m_staMac->IsEmlsrLink(linkId))
667 {
668 if (auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId); mainPhy->IsStateSwitching())
669 {
670 // main PHY is switching to this link to take over the UL TXOP. Postpone aux PHY
671 // sleeping until after the main PHY has completed switching
672 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1),
674 this,
675 true);
676 }
677 else
678 {
679 // put aux PHYs to sleep
681 }
682 }
683}
684
685void
686EmlsrManager::NotifyTxopEnd(uint8_t linkId, bool ulTxopNotStarted, bool ongoingDlTxop)
687{
688 NS_LOG_FUNCTION(this << linkId << ulTxopNotStarted << ongoingDlTxop);
689
690 if (!m_staMac->IsEmlsrLink(linkId))
691 {
692 NS_LOG_DEBUG("EMLSR is not enabled on link " << +linkId);
693 return;
694 }
695
696 // If the main PHY has been scheduled to switch to this link, cancel the channel switch.
697 // This happens, e.g., when an aux PHY sent an RTS to start an UL TXOP but it did not
698 // receive a CTS response.
699 if (auto it = m_ulMainPhySwitch.find(linkId); it != m_ulMainPhySwitch.end())
700 {
701 if (it->second.IsPending())
702 {
703 NS_LOG_DEBUG("Cancelling main PHY channel switch event on link " << +linkId);
704 it->second.Cancel();
705 }
706 m_ulMainPhySwitch.erase(it);
707 }
708
709 // Unblock the other EMLSR links and start the MediumSyncDelay timer, provided that the TXOP
710 // included the transmission of at least a frame and there is no ongoing DL TXOP on this link.
711 // Indeed, the UL TXOP may have ended because the transmission of a frame failed and the
712 // corresponding TX timeout (leading to this call) may have occurred after the reception on
713 // this link of an ICF starting a DL TXOP. If the EMLSR Manager unblocked the other EMLSR
714 // links, another TXOP could be started on another EMLSR link (possibly leading to a crash)
715 // while the DL TXOP on this link is ongoing.
716 if (ongoingDlTxop)
717 {
718 NS_LOG_DEBUG("DL TXOP ongoing");
719 return;
720 }
721 if (ulTxopNotStarted)
722 {
723 NS_LOG_DEBUG("TXOP did not even start");
724 return;
725 }
726
727 if (m_auxPhyToSleep)
728 {
729 // TXOP ended, resume all aux PHYs from sleep
731 }
732
733 DoNotifyTxopEnd(linkId);
734
735 // unblock transmissions and resume medium access on other EMLSR links
736 std::set<uint8_t> linkIds;
737 for (auto id : m_staMac->GetLinkIds())
738 {
739 if ((id != linkId) && m_staMac->IsEmlsrLink(id))
740 {
741 linkIds.insert(id);
742 }
743 }
745}
746
747void
749{
750 NS_LOG_FUNCTION(this << linkId << duration.As(Time::US));
752
753 // The STA may choose not to (re)start the MediumSyncDelay timer if the transmission duration
754 // is less than or equal to aMediumSyncThreshold. (Sec. 35.3.16.8.1 802.11be D5.1)
756 {
757 return;
758 }
759
760 // iterate over all the other EMLSR links
761 for (auto id : m_staMac->GetLinkIds())
762 {
763 if (id != linkId && m_staMac->IsEmlsrLink(id))
764 {
766 }
767 }
768}
769
770void
772{
773 NS_LOG_FUNCTION(this << phy << linkId);
774
775 // if a MediumSyncDelay timer is running for the link on which the main PHY is going to
776 // operate, set the CCA ED threshold to the MediumSyncDelay OFDM ED threshold
777 if (auto statusIt = m_mediumSyncDelayStatus.find(linkId);
778 statusIt != m_mediumSyncDelayStatus.cend() && statusIt->second.timer.IsPending())
779 {
780 NS_LOG_DEBUG("Setting CCA ED threshold of PHY " << phy << " to " << +m_msdOfdmEdThreshold
781 << " on link " << +linkId);
782
783 // store the current CCA ED threshold in the m_prevCcaEdThreshold map, if not present
784 m_prevCcaEdThreshold.try_emplace(phy, phy->GetCcaEdThreshold());
785
786 phy->SetCcaEdThreshold(m_msdOfdmEdThreshold);
787 }
788 // otherwise, restore the previous value for the CCA ED threshold (if any)
789 else if (auto threshIt = m_prevCcaEdThreshold.find(phy);
790 threshIt != m_prevCcaEdThreshold.cend())
791 {
792 NS_LOG_DEBUG("Resetting CCA ED threshold of PHY " << phy << " to " << threshIt->second
793 << " on link " << +linkId);
794 phy->SetCcaEdThreshold(threshIt->second);
795 m_prevCcaEdThreshold.erase(threshIt);
796 }
797}
798
799void
801 bool noSwitchDelay,
802 bool resetBackoff,
803 bool requestAccess,
804 EmlsrMainPhySwitchTrace&& traceInfo)
805{
806 NS_LOG_FUNCTION(this << linkId << noSwitchDelay << resetBackoff << requestAccess
807 << traceInfo.GetName());
808
809 auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId);
810
811 NS_ASSERT_MSG(mainPhy != m_staMac->GetWifiPhy(linkId),
812 "Main PHY is already operating on link " << +linkId);
813
814 // find the link on which the main PHY is operating
815 auto currMainPhyLinkId = m_staMac->GetLinkForPhy(mainPhy);
816 traceInfo.fromLinkId = currMainPhyLinkId;
817 traceInfo.toLinkId = linkId;
818 m_mainPhySwitchTrace(traceInfo);
819
820 const auto newMainPhyChannel = GetChannelForMainPhy(linkId);
821
822 NS_LOG_DEBUG("Main PHY (" << mainPhy << ") is about to switch to " << newMainPhyChannel
823 << " to operate on link " << +linkId);
824
825 // if the main PHY is operating on a link, notify the channel access manager of the upcoming
826 // channel switch
827 if (currMainPhyLinkId.has_value())
828 {
829 m_staMac->GetChannelAccessManager(*currMainPhyLinkId)
830 ->NotifySwitchingEmlsrLink(mainPhy, newMainPhyChannel, linkId);
831 }
832
833 // this assert also ensures that the actual channel switch is not delayed
834 NS_ASSERT_MSG(!mainPhy->GetState()->IsStateTx(),
835 "We should not ask the main PHY to switch channel while transmitting");
836
837 // record the aux PHY operating on the link the main PHY is switching to
838 auto auxPhy = GetStaMac()->GetWifiPhy(linkId);
839
840 // request the main PHY to switch channel
841 const auto delay = mainPhy->GetChannelSwitchDelay();
842 const auto pifs = mainPhy->GetSifs() + mainPhy->GetSlot();
843 NS_ASSERT_MSG(noSwitchDelay || delay <= std::max(m_lastAdvTransitionDelay, pifs),
844 "Channel switch delay ("
845 << delay.As(Time::US)
846 << ") should be shorter than the maximum between the Transition delay ("
847 << m_lastAdvTransitionDelay.As(Time::US) << ") and a PIFS ("
848 << pifs.As(Time::US) << ")");
849 if (noSwitchDelay)
850 {
851 mainPhy->SetAttribute("ChannelSwitchDelay", TimeValue(Seconds(0)));
852 }
853 mainPhy->SetOperatingChannel(newMainPhyChannel);
854 // restore previous channel switch delay
855 if (noSwitchDelay)
856 {
857 mainPhy->SetAttribute("ChannelSwitchDelay", TimeValue(delay));
858 }
859 // re-enable short time slot, if needed
860 if (m_staMac->GetWifiRemoteStationManager(linkId)->GetShortSlotTimeEnabled())
861 {
862 mainPhy->SetSlot(MicroSeconds(9));
863 }
864
865 const auto timeToSwitchEnd = noSwitchDelay ? Seconds(0) : delay;
866
867 // if the main PHY is not operating on any link (because it was switching), it is not connected
868 // to a channel access manager, hence we must notify the MAC of the new link switch
869 if (!currMainPhyLinkId.has_value())
870 {
871 m_staMac->NotifySwitchingEmlsrLink(mainPhy, linkId, timeToSwitchEnd);
872 }
873
874 if (resetBackoff && currMainPhyLinkId.has_value())
875 {
876 // reset the backoffs on the link left by the main PHY
877 m_staMac->GetChannelAccessManager(*currMainPhyLinkId)->ResetAllBackoffs();
878 }
879
880 if (requestAccess)
881 {
882 // schedule channel access request on the new link when switch is completed
883 Simulator::Schedule(timeToSwitchEnd, [=, this]() {
884 for (const auto& [acIndex, ac] : wifiAcList)
885 {
886 m_staMac->GetQosTxop(acIndex)->StartAccessAfterEvent(
887 linkId,
890 }
891 });
892 }
893
894 m_mainPhySwitchInfo.from = currMainPhyLinkId.value_or(m_mainPhySwitchInfo.from);
895 m_mainPhySwitchInfo.to = linkId;
896 m_mainPhySwitchInfo.end = Simulator::Now() + timeToSwitchEnd;
897
898 SetCcaEdThresholdOnLinkSwitch(mainPhy, linkId);
899 NotifyMainPhySwitch(currMainPhyLinkId, linkId, auxPhy, timeToSwitchEnd);
900}
901
902void
903EmlsrManager::SwitchAuxPhy(Ptr<WifiPhy> auxPhy, uint8_t currLinkId, uint8_t nextLinkId)
904{
905 NS_LOG_FUNCTION(this << auxPhy << currLinkId << nextLinkId);
906
907 auto newAuxPhyChannel = GetChannelForAuxPhy(nextLinkId);
908
909 NS_LOG_DEBUG("Aux PHY (" << auxPhy << ") is about to switch to " << newAuxPhyChannel
910 << " to operate on link " << +nextLinkId);
911
912 GetStaMac()
913 ->GetChannelAccessManager(currLinkId)
914 ->NotifySwitchingEmlsrLink(auxPhy, newAuxPhyChannel, nextLinkId);
915
916 auxPhy->SetOperatingChannel(newAuxPhyChannel);
917 // re-enable short time slot, if needed
918 if (m_staMac->GetWifiRemoteStationManager(nextLinkId)->GetShortSlotTimeEnabled())
919 {
920 auxPhy->SetSlot(MicroSeconds(9));
921 }
922
923 // schedule channel access request on the new link when switch is completed
924 Simulator::Schedule(auxPhy->GetChannelSwitchDelay(), [=, this]() {
925 for (const auto& [acIndex, ac] : wifiAcList)
926 {
927 m_staMac->GetQosTxop(acIndex)->StartAccessAfterEvent(
928 nextLinkId,
929 Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT,
930 Txop::CHECK_MEDIUM_BUSY);
931 }
932 });
933
934 SetCcaEdThresholdOnLinkSwitch(auxPhy, nextLinkId);
935}
936
937void
938EmlsrManager::StartMediumSyncDelayTimer(uint8_t linkId)
939{
940 NS_LOG_FUNCTION(this << linkId);
941
942 if (m_mediumSyncDuration.IsZero())
943 {
944 NS_LOG_DEBUG("MediumSyncDuration is zero");
945 return;
946 }
947
948 auto phy = m_staMac->GetWifiPhy(linkId);
949
950 if (!phy)
951 {
952 NS_LOG_DEBUG("No PHY operating on link " << +linkId);
953 // MSD timer will be started when a PHY will be operating on this link
954 return;
955 }
956
957 const auto [it, inserted] = m_mediumSyncDelayStatus.try_emplace(linkId);
958
959 // reset the max number of TXOP attempts
960 it->second.msdNTxopsLeft = m_msdMaxNTxops;
961
962 if (!it->second.timer.IsPending())
963 {
964 NS_LOG_DEBUG("Setting CCA ED threshold on link "
965 << +linkId << " to " << +m_msdOfdmEdThreshold << " PHY " << phy);
966 m_prevCcaEdThreshold[phy] = phy->GetCcaEdThreshold();
967 phy->SetCcaEdThreshold(m_msdOfdmEdThreshold);
968 }
969
970 // (re)start the timer
971 it->second.timer.Cancel();
972 NS_LOG_DEBUG("Starting MediumSyncDelay timer for " << m_mediumSyncDuration.As(Time::US)
973 << " on link " << +linkId);
974 it->second.timer = Simulator::Schedule(m_mediumSyncDuration,
975 &EmlsrManager::MediumSyncDelayTimerExpired,
976 this,
977 linkId);
978}
979
980void
981EmlsrManager::CancelMediumSyncDelayTimer(uint8_t linkId)
982{
983 NS_LOG_FUNCTION(this << linkId);
984
985 auto timerIt = m_mediumSyncDelayStatus.find(linkId);
986
987 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && timerIt->second.timer.IsPending());
988
989 timerIt->second.timer.Cancel();
990 MediumSyncDelayTimerExpired(linkId);
991}
992
993void
994EmlsrManager::MediumSyncDelayTimerExpired(uint8_t linkId)
995{
996 NS_LOG_FUNCTION(this << linkId);
997
998 auto timerIt = m_mediumSyncDelayStatus.find(linkId);
999
1000 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && !timerIt->second.timer.IsPending());
1001
1002 // reset the MSD OFDM ED threshold
1003 auto phy = m_staMac->GetWifiPhy(linkId);
1004
1005 if (!phy)
1006 {
1007 // no PHY is operating on this link. This may happen when a MediumSyncDelay timer expires
1008 // on the link left "uncovered" by the main PHY that is operating on another link (and the
1009 // aux PHY of that link did not switch). In this case, do nothing, since the CCA ED
1010 // threshold on the main PHY will be restored once the main PHY switches back to its link
1011 return;
1012 }
1013
1014 auto threshIt = m_prevCcaEdThreshold.find(phy);
1015 NS_ASSERT_MSG(threshIt != m_prevCcaEdThreshold.cend(),
1016 "No value to restore for CCA ED threshold on PHY " << phy);
1017 NS_LOG_DEBUG("Resetting CCA ED threshold of PHY " << phy << " to " << threshIt->second
1018 << " on link " << +linkId);
1019 phy->SetCcaEdThreshold(threshIt->second);
1020 m_prevCcaEdThreshold.erase(threshIt);
1021}
1022
1023void
1024EmlsrManager::DecrementMediumSyncDelayNTxops(uint8_t linkId)
1025{
1026 NS_LOG_FUNCTION(this << linkId);
1027
1028 const auto timerIt = m_mediumSyncDelayStatus.find(linkId);
1029
1030 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && timerIt->second.timer.IsPending());
1031 NS_ASSERT(timerIt->second.msdNTxopsLeft != 0);
1032
1033 if (timerIt->second.msdNTxopsLeft)
1034 {
1035 --timerIt->second.msdNTxopsLeft.value();
1036 }
1037}
1038
1039void
1040EmlsrManager::ResetMediumSyncDelayNTxops(uint8_t linkId)
1041{
1042 NS_LOG_FUNCTION(this << linkId);
1043
1044 auto timerIt = m_mediumSyncDelayStatus.find(linkId);
1045
1046 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && timerIt->second.timer.IsPending());
1047 timerIt->second.msdNTxopsLeft.reset();
1048}
1049
1050bool
1051EmlsrManager::MediumSyncDelayNTxopsExceeded(uint8_t linkId)
1052{
1053 NS_LOG_FUNCTION(this << linkId);
1054
1055 auto timerIt = m_mediumSyncDelayStatus.find(linkId);
1056
1057 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && timerIt->second.timer.IsPending());
1058 return timerIt->second.msdNTxopsLeft == 0;
1059}
1060
1061bool
1062EmlsrManager::GetExpectedAccessWithinDelay(uint8_t linkId, const Time& delay) const
1063{
1064 const auto deadline = Simulator::Now() + delay;
1065 for (const auto& [acIndex, ac] : wifiAcList)
1066 {
1067 if (auto edca = m_staMac->GetQosTxop(acIndex); edca->HasFramesToTransmit(linkId))
1068 {
1069 const auto backoffEnd =
1070 m_staMac->GetChannelAccessManager(linkId)->GetBackoffEndFor(edca);
1071
1072 if (backoffEnd <= deadline)
1073 {
1074 NS_LOG_DEBUG("Backoff end for " << acIndex << " on link " << +linkId << ": "
1075 << backoffEnd.As(Time::US));
1076 return true;
1077 }
1078 }
1079 }
1080 return false;
1081}
1082
1084EmlsrManager::GetEmlOmn()
1085{
1086 MgtEmlOmn frame;
1087
1088 // Add the EMLSR Parameter Update field if needed
1089 if (m_lastAdvPaddingDelay != m_emlsrPaddingDelay ||
1090 m_lastAdvTransitionDelay != m_emlsrTransitionDelay)
1091 {
1092 m_lastAdvPaddingDelay = m_emlsrPaddingDelay;
1093 m_lastAdvTransitionDelay = m_emlsrTransitionDelay;
1096 frame.m_emlsrParamUpdate->paddingDelay =
1097 CommonInfoBasicMle::EncodeEmlsrPaddingDelay(m_lastAdvPaddingDelay);
1098 frame.m_emlsrParamUpdate->transitionDelay =
1099 CommonInfoBasicMle::EncodeEmlsrTransitionDelay(m_lastAdvTransitionDelay);
1100 }
1101
1102 // We must verify that the links included in the given EMLSR link set (if any) have been setup.
1103 auto setupLinkIds = m_staMac->GetSetupLinkIds();
1104
1105 for (auto emlsrLinkIt = m_nextEmlsrLinks->begin(); emlsrLinkIt != m_nextEmlsrLinks->end();)
1106 {
1107 if (auto setupLinkIt = setupLinkIds.find(*emlsrLinkIt); setupLinkIt != setupLinkIds.cend())
1108 {
1109 setupLinkIds.erase(setupLinkIt);
1110 frame.SetLinkIdInBitmap(*emlsrLinkIt);
1111 emlsrLinkIt++;
1112 }
1113 else
1114 {
1115 NS_LOG_DEBUG("Link ID " << +(*emlsrLinkIt) << " has not been setup");
1116 emlsrLinkIt = m_nextEmlsrLinks->erase(emlsrLinkIt);
1117 }
1118 }
1119
1120 // EMLSR Mode is enabled if and only if the set of EMLSR links is not empty
1121 frame.m_emlControl.emlsrMode = m_nextEmlsrLinks->empty() ? 0 : 1;
1122
1123 return frame;
1124}
1125
1126void
1127EmlsrManager::SendEmlOmn()
1128{
1129 NS_LOG_FUNCTION(this);
1130
1131 NS_ABORT_MSG_IF(!m_emlsrTransitionTimeout,
1132 "AP did not advertise a Transition Timeout, cannot send EML notification");
1133 NS_ASSERT_MSG(m_nextEmlsrLinks, "Need to set EMLSR links before calling this method");
1134
1135 // TODO if this is a single radio non-AP MLD and not all setup links are in the EMLSR link
1136 // set, we have to put setup links that are not included in the given EMLSR link set (i.e.,
1137 // those remaining in setupLinkIds, if m_nextEmlsrLinks is not empty) in the sleep mode:
1138 // For the EMLSR mode enabled in a single radio non-AP MLD, the STA(s) affiliated with
1139 // the non-AP MLD that operates on the enabled link(s) that corresponds to the bit
1140 // position(s) of the EMLSR Link Bitmap subfield set to 0 shall be in doze state if a
1141 // non-AP STA affiliated with the non-AP MLD that operates on one of the EMLSR links is
1142 // in awake state. (Sec. 35.3.17 of 802.11be D3.0)
1143
1144 auto frame = GetEmlOmn();
1145 auto linkId = GetLinkToSendEmlOmn();
1146 GetEhtFem(linkId)->SendEmlOmn(m_staMac->GetBssid(linkId), frame);
1147}
1148
1149void
1150EmlsrManager::TxOk(Ptr<const WifiMpdu> mpdu)
1151{
1152 NS_LOG_FUNCTION(this << *mpdu);
1153
1154 const auto& hdr = mpdu->GetHeader();
1155
1156 if (hdr.IsAssocReq())
1157 {
1158 // store padding delay and transition delay advertised in AssocReq
1159 MgtAssocRequestHeader assocReq;
1160 mpdu->GetPacket()->PeekHeader(assocReq);
1161 auto& mle = assocReq.Get<MultiLinkElement>();
1162 NS_ASSERT_MSG(mle, "AssocReq should contain a Multi-Link Element");
1163 m_lastAdvPaddingDelay = mle->GetEmlsrPaddingDelay();
1164 m_lastAdvTransitionDelay = mle->GetEmlsrTransitionDelay();
1165 }
1166
1167 if (hdr.IsMgt() && hdr.IsAction())
1168 {
1169 if (auto [category, action] = WifiActionHeader::Peek(mpdu->GetPacket());
1170 category == WifiActionHeader::PROTECTED_EHT &&
1171 action.protectedEhtAction ==
1172 WifiActionHeader::PROTECTED_EHT_EML_OPERATING_MODE_NOTIFICATION)
1173 {
1174 // the EML Operating Mode Notification frame that we sent has been acknowledged.
1175 // Start the transition timeout to wait until the request can be made effective
1176 NS_ASSERT_MSG(m_emlsrTransitionTimeout, "No transition timeout received from AP");
1177 m_transitionTimeoutEvent = Simulator::Schedule(*m_emlsrTransitionTimeout,
1178 &EmlsrManager::ChangeEmlsrMode,
1179 this);
1180 }
1181 }
1182}
1183
1184void
1185EmlsrManager::TxDropped(WifiMacDropReason reason, Ptr<const WifiMpdu> mpdu)
1186{
1187 NS_LOG_FUNCTION(this << reason << *mpdu);
1188
1189 const auto& hdr = mpdu->GetHeader();
1190
1191 if (hdr.IsMgt() && hdr.IsAction())
1192 {
1193 auto pkt = mpdu->GetPacket()->Copy();
1194 if (auto [category, action] = WifiActionHeader::Remove(pkt);
1195 category == WifiActionHeader::PROTECTED_EHT &&
1196 action.protectedEhtAction ==
1197 WifiActionHeader::PROTECTED_EHT_EML_OPERATING_MODE_NOTIFICATION)
1198 {
1199 // the EML Operating Mode Notification frame has been dropped. Ask the subclass
1200 // whether the frame needs to be resent
1201 auto linkId = ResendNotification(mpdu);
1202 if (linkId)
1203 {
1204 MgtEmlOmn frame;
1205 pkt->RemoveHeader(frame);
1206 GetEhtFem(*linkId)->SendEmlOmn(m_staMac->GetBssid(*linkId), frame);
1207 }
1208 else
1209 {
1210 m_nextEmlsrLinks.reset();
1211 }
1212 }
1213 }
1214}
1215
1216void
1217EmlsrManager::ChangeEmlsrMode()
1218{
1219 NS_LOG_FUNCTION(this);
1220
1221 // After the successful transmission of the EML Operating Mode Notification frame by the
1222 // non-AP STA affiliated with the non-AP MLD, the non-AP MLD shall operate in the EMLSR mode
1223 // and the other non-AP STAs operating on the corresponding EMLSR links shall transition to
1224 // active mode after the transition delay indicated in the Transition Timeout subfield in the
1225 // EML Capabilities subfield of the Basic Multi-Link element or immediately after receiving an
1226 // EML Operating Mode Notification frame from one of the APs operating on the EMLSR links and
1227 // affiliated with the AP MLD. (Sec. 35.3.17 of 802.11be D3.0)
1228 NS_ASSERT_MSG(m_nextEmlsrLinks, "No set of EMLSR links stored");
1229 m_emlsrLinks.swap(*m_nextEmlsrLinks);
1230 m_nextEmlsrLinks.reset();
1231
1232 // Make other non-AP STAs operating on the corresponding EMLSR links transition to
1233 // active mode or passive mode (depending on whether EMLSR mode has been enabled or disabled)
1234 m_staMac->NotifyEmlsrModeChanged(m_emlsrLinks);
1235 // Enforce the limit on the max channel width supported by aux PHYs
1236 ApplyMaxChannelWidthAndModClassOnAuxPhys();
1237
1238 NotifyEmlsrModeChanged();
1239}
1240
1241void
1242EmlsrManager::ApplyMaxChannelWidthAndModClassOnAuxPhys()
1243{
1244 NS_LOG_FUNCTION(this);
1245 auto currMainPhyLinkId = m_staMac->GetLinkForPhy(m_mainPhyId);
1246 NS_ASSERT(currMainPhyLinkId);
1247
1248 for (const auto linkId : m_staMac->GetLinkIds())
1249 {
1250 auto auxPhy = m_staMac->GetWifiPhy(linkId);
1251 auto channel = GetChannelForAuxPhy(linkId);
1252
1253 if (linkId == currMainPhyLinkId || !m_staMac->IsEmlsrLink(linkId) ||
1254 auxPhy->GetOperatingChannel() == channel)
1255 {
1256 continue;
1257 }
1258
1259 auxPhy->SetMaxModulationClassSupported(m_auxPhyMaxModClass);
1260
1261 NS_LOG_DEBUG("Aux PHY (" << auxPhy << ") is about to switch to " << channel
1262 << " to operate on link " << +linkId);
1263 // We cannot simply set the new channel, because otherwise the MAC will disable
1264 // the setup link. We need to inform the MAC (via the Channel Access Manager) that
1265 // this channel switch must not have such a consequence. We already have a method
1266 // for doing so, i.e., inform the MAC that the PHY is switching channel to operate
1267 // on the "same" link.
1268 auto cam = m_staMac->GetChannelAccessManager(linkId);
1269 cam->NotifySwitchingEmlsrLink(auxPhy, channel, linkId);
1270
1271 // apply channel width limitation assuming an instantaneous channel switch
1272 const auto delay = auxPhy->GetChannelSwitchDelay();
1273 auxPhy->SetAttribute("ChannelSwitchDelay", TimeValue(Time{0}));
1274 auxPhy->SetOperatingChannel(channel);
1275 auxPhy->SetAttribute("ChannelSwitchDelay", TimeValue(delay));
1276
1277 // the way the ChannelAccessManager handles EMLSR link switch implies that a PHY listener
1278 // is removed when the channel switch starts and another one is attached when the channel
1279 // switch ends. In the meantime, no PHY is connected to the ChannelAccessManager. Thus,
1280 // reset all backoffs (so that access timeout is also cancelled) when the channel switch
1281 // starts and request channel access (if needed) when the channel switch ends.
1282 cam->ResetAllBackoffs();
1283 for (const auto& [acIndex, ac] : wifiAcList)
1284 {
1285 m_staMac->GetQosTxop(acIndex)->StartAccessAfterEvent(
1286 linkId,
1287 Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT,
1288 Txop::CHECK_MEDIUM_BUSY);
1289 }
1290 }
1291}
1292
1293void
1294EmlsrManager::ComputeOperatingChannels()
1295{
1296 NS_LOG_FUNCTION(this);
1297
1298 m_mainPhyChannels.clear();
1299 m_auxPhyChannels.clear();
1300
1301 auto linkIds = m_staMac->GetSetupLinkIds();
1302
1303 for (auto linkId : linkIds)
1304 {
1305 const auto& channel = m_staMac->GetWifiPhy(linkId)->GetOperatingChannel();
1306 m_mainPhyChannels.emplace(linkId, channel);
1307
1308 auto mainPhyChWidth = channel.GetWidth();
1309 auto auxPhyMaxWidth =
1310 std::min(m_auxPhyMaxWidth, GetMaximumChannelWidth(m_auxPhyMaxModClass));
1311 if (auxPhyMaxWidth >= mainPhyChWidth)
1312 {
1313 // same channel can be used by aux PHYs
1314 m_auxPhyChannels.emplace(linkId, channel);
1315 continue;
1316 }
1317 // aux PHYs will operate on a primary subchannel
1318 auto freq = channel.GetPrimaryChannelCenterFrequency(auxPhyMaxWidth);
1319 auto chIt = WifiPhyOperatingChannel::FindFirst(0,
1320 freq,
1321 auxPhyMaxWidth,
1323 channel.GetPhyBand());
1324 NS_ASSERT_MSG(chIt != WifiPhyOperatingChannel::m_frequencyChannels.end(),
1325 "Primary" << auxPhyMaxWidth << " channel not found");
1326 m_auxPhyChannels.emplace(linkId, chIt);
1327 // find the P20 index for the channel used by the aux PHYs
1328 auto p20Index = channel.GetPrimaryChannelIndex(MHz_u{20});
1329 while (mainPhyChWidth > auxPhyMaxWidth)
1330 {
1331 mainPhyChWidth /= 2;
1332 p20Index /= 2;
1333 }
1334 m_auxPhyChannels[linkId].SetPrimary20Index(p20Index);
1335 }
1336}
1337
1339EmlsrManager::GetChannelForMainPhy(uint8_t linkId) const
1340{
1341 auto it = m_mainPhyChannels.find(linkId);
1342 NS_ASSERT_MSG(it != m_mainPhyChannels.end(),
1343 "Channel for main PHY on link ID " << +linkId << " not found");
1344 return it->second;
1345}
1346
1348EmlsrManager::GetChannelForAuxPhy(uint8_t linkId) const
1349{
1350 auto it = m_auxPhyChannels.find(linkId);
1351 NS_ASSERT_MSG(it != m_auxPhyChannels.end(),
1352 "Channel for aux PHY on link ID " << +linkId << " not found");
1353 return it->second;
1354}
1355
1356void
1357EmlsrManager::CancelAllSleepEvents()
1358{
1359 NS_LOG_FUNCTION(this);
1360
1361 for (auto& [id, event] : m_auxPhyToSleepEvents)
1362 {
1363 event.Cancel();
1364 }
1365 m_auxPhyToSleepEvents.clear();
1366}
1367
1368void
1369EmlsrManager::SetSleepStateForAllAuxPhys(bool sleep)
1370{
1371 NS_LOG_FUNCTION(this << sleep);
1372
1373 CancelAllSleepEvents();
1374
1375 for (const auto& phy : m_staMac->GetDevice()->GetPhys())
1376 {
1377 if (phy->GetPhyId() == m_mainPhyId)
1378 {
1379 continue; // do not set sleep mode/resume from sleep the main PHY
1380 }
1381
1382 auto linkId = m_staMac->GetLinkForPhy(phy);
1383
1384 if (linkId.has_value() && !m_staMac->IsEmlsrLink(*linkId))
1385 {
1386 continue; // this PHY is not operating on an EMLSR link
1387 }
1388
1389 if (!sleep)
1390 {
1391 if (!phy->IsStateSleep())
1392 {
1393 continue; // nothing to do
1394 }
1395
1396 NS_LOG_DEBUG("PHY " << +phy->GetPhyId() << ": Resuming from sleep");
1397 phy->ResumeFromSleep();
1398
1399 // if this aux PHY is operating on a link, check if it lost medium sync
1400 if (linkId.has_value())
1401 {
1402 auto it = m_startSleep.find(phy->GetPhyId());
1403 NS_ASSERT_MSG(it != m_startSleep.cend(),
1404 "No start sleep info for PHY ID " << phy->GetPhyId());
1405 const auto sleepDuration = Simulator::Now() - it->second;
1406 m_startSleep.erase(it);
1407
1408 if (sleepDuration > MicroSeconds(MEDIUM_SYNC_THRESHOLD_USEC))
1409 {
1410 StartMediumSyncDelayTimer(*linkId);
1411 }
1412 }
1413
1414 continue; // resuming the PHY from sleep has been handled
1415 }
1416
1417 if (phy->IsStateSleep())
1418 {
1419 continue; // nothing to do
1420 }
1421
1422 // we force WifiPhy::SetSleepMode() to abort RX and switch immediately to sleep mode in
1423 // case the state is RX. If the state is TX or SWITCHING, WifiPhy::SetSleepMode() postpones
1424 // setting sleep mode to end of TX or SWITCHING. This is fine, but we schedule events here
1425 // to correctly record the time an aux PHY was put to sleep and to be able to cancel them
1426 // later if needed
1427 std::stringstream ss;
1428 auto s = std::string("PHY ") + std::to_string(phy->GetPhyId()) + ": Setting sleep mode";
1429 if (phy->IsStateTx() || phy->IsStateSwitching())
1430 {
1431 const auto delay = phy->GetDelayUntilIdle();
1432 NS_LOG_DEBUG(s << " in " << delay.As(Time::US));
1433 m_auxPhyToSleepEvents[phy->GetPhyId()] = Simulator::Schedule(delay, [=, this]() {
1434 phy->SetSleepMode(true);
1435 m_startSleep[phy->GetPhyId()] = Simulator::Now();
1436 });
1437 }
1438 else
1439 {
1440 NS_LOG_DEBUG(s);
1441 phy->SetSleepMode(true);
1442 m_startSleep[phy->GetPhyId()] = Simulator::Now();
1443 }
1444 }
1445}
1446
1447} // namespace ns3
A container for one type of attribute.
void SendEmlOmn()
Send an EML Operating Mode Notification frame.
Time GetMediumSyncDuration() const
void ComputeOperatingChannels()
Compute the operating channels that the main PHY and the aux PHY(s) must switch to in order to operat...
void NotifyProtectionCompleted(uint8_t linkId)
Notify that protection (if required) is completed and data frame exchange can start on the given link...
void SetTransitionTimeout(Time timeout)
Set the Transition Timeout advertised by the associated AP with EMLSR activated.
bool m_useNotifiedMacHdr
whether to use the information about the MAC header of the MPDU being received (if notified by the PH...
static constexpr uint16_t MEDIUM_SYNC_THRESHOLD_USEC
The aMediumSyncThreshold defined by Sec. 35.3.16.18.1 of 802.11be D4.0.
bool m_auxPhyTxCapable
whether Aux PHYs are capable of transmitting PPDUs
std::optional< Time > GetTransitionTimeout() const
std::pair< bool, Time > GetDelayUntilAccessRequest(uint8_t linkId, AcIndex aci)
Notify that an UL TXOP is gained on the given link by the given AC.
virtual std::pair< bool, Time > DoGetDelayUntilAccessRequest(uint8_t linkId)=0
Subclasses have to provide an implementation for this method, that is called by the base class when t...
Ptr< EhtFrameExchangeManager > GetEhtFem(uint8_t linkId) const
void TxDropped(WifiMacDropReason reason, Ptr< const WifiMpdu > mpdu)
Notify that the given MPDU has been discarded for the given reason.
void NotifyUlTxopStart(uint8_t linkId)
Notify the start of an UL TXOP on the given link.
void TxOk(Ptr< const WifiMpdu > mpdu)
Notify the acknowledgment of the given MPDU.
void NotifyTxopEnd(uint8_t linkId, bool ulTxopNotStarted=false, bool ongoingDlTxop=false)
Notify the end of a TXOP on the given link.
std::map< uint8_t, EventId > m_ulMainPhySwitch
link ID-indexed map of timers started when an aux PHY gains an UL TXOP and schedules a channel switch...
bool m_inDeviceInterference
whether in-device interference is such that a PHY cannot decode anything and cannot decrease the back...
bool GetCamStateReset() const
void SetEmlsrLinks(const std::set< uint8_t > &linkIds)
Take actions to enable EMLSR mode on the given set of links, if non-empty, or disable EMLSR mode,...
void SetMediumSyncOfdmEdThreshold(int8_t threshold)
Set the Medium Synchronization OFDM ED threshold (dBm) to use while the MediumSyncDelay timer is runn...
uint8_t m_mainPhyId
ID of main PHY (position in the vector of PHYs held by WifiNetDevice)
int8_t GetMediumSyncOfdmEdThreshold() const
void NotifyIcfReceived(uint8_t linkId)
Notify the reception of an initial Control frame on the given link.
std::map< uint8_t, MediumSyncDelayStatus > m_mediumSyncDelayStatus
the status of MediumSyncDelay timers (link ID-indexed)
bool m_auxPhyToSleep
whether Aux PHYs should be put into sleep mode while the Main PHY is carrying out a (DL or UL) TXOP
virtual std::pair< bool, Time > GetDelayUnlessMainPhyTakesOverUlTxop(uint8_t linkId)=0
Subclasses have to provide an implementation for this method, that is called by the base class when t...
virtual void SwitchMainPhyIfTxopGainedByAuxPhy(uint8_t linkId, AcIndex aci)=0
Subclasses have to provide an implementation for this method, that is called by the base class when t...
void NotifyMgtFrameReceived(Ptr< const WifiMpdu > mpdu, uint8_t linkId)
Notify the reception of a management frame addressed to us.
virtual void DoNotifyUlTxopStart(uint8_t linkId)=0
Notify the subclass of the start of an UL TXOP on the given link.
Ptr< StaWifiMac > m_staMac
the MAC of the managed non-AP MLD
virtual void DoNotifyMgtFrameReceived(Ptr< const WifiMpdu > mpdu, uint8_t linkId)=0
Notify the subclass of the reception of a management frame addressed to us.
Time m_emlsrPaddingDelay
EMLSR Padding delay.
bool GetInDeviceInterference() const
virtual void NotifyMainPhySwitch(std::optional< uint8_t > currLinkId, uint8_t nextLinkId, Ptr< WifiPhy > auxPhy, Time duration)=0
Notify subclass that the main PHY is switching channel to operate on another link.
MHz_u m_auxPhyMaxWidth
max channel width supported by aux PHYs
virtual void NotifyInDeviceInterferenceStart(uint8_t linkId, Time duration)
Notify that an STA affiliated with the EMLSR client is causing in-device interference for the given a...
void SetMediumSyncMaxNTxops(std::optional< uint8_t > nTxops)
Set the maximum number of TXOPs a non-AP STA is allowed to attempt to initiate while the MediumSyncDe...
Time m_emlsrTransitionDelay
EMLSR Transition delay.
void SetWifiMac(Ptr< StaWifiMac > mac)
Set the wifi MAC.
bool GetAuxPhyTxCapable() const
const std::set< uint8_t > & GetEmlsrLinks() const
MainPhySwitchTracedCallback m_mainPhySwitchTrace
main PHY switch trace source
virtual void EmlsrLinkSwitchCallback(uint8_t linkId, Ptr< WifiPhy > phy)
Callback connected to the EmlsrLinkSwitch trace source of StaWifiMac.
Time m_mediumSyncDuration
duration of the MediumSyncDelay timer
std::optional< Time > m_emlsrTransitionTimeout
Transition timeout advertised by APs with EMLSR activated.
void SwitchAuxPhy(Ptr< WifiPhy > auxPhy, uint8_t currLinkId, uint8_t nextLinkId)
Switch channel on the Aux PHY operating on the given current link so that it operates on the given ne...
std::map< Ptr< WifiPhy >, dBm_u > m_prevCcaEdThreshold
the CCA sensitivity threshold to restore once the MediumSyncDelay timer expires or the PHY moves to a...
std::optional< Time > GetElapsedMediumSyncDelayTimer(uint8_t linkId) const
Check whether the MediumSyncDelay timer is running for the STA operating on the given link.
virtual void DoNotifyTxopEnd(uint8_t linkId)=0
Notify the subclass of the end of a TXOP on the given link.
virtual void DoSetWifiMac(Ptr< StaWifiMac > mac)
Allow subclasses to take actions when the MAC is set.
std::map< uint8_t, Time > m_noPhySince
link ID-indexed map of the time since no PHY is operating on the link
void SetAuxPhyTxCapable(bool capable)
Set the member variable indicating whether Aux PHYs are capable of transmitting PPDUs.
std::optional< std::set< uint8_t > > m_nextEmlsrLinks
ID of the links that will become the EMLSR links when the pending notification frame is acknowledged.
void SetCcaEdThresholdOnLinkSwitch(Ptr< WifiPhy > phy, uint8_t linkId)
Set the CCA ED threshold (if needed) on the given PHY that is switching channel to operate on the giv...
void SetMainPhyId(uint8_t mainPhyId)
Set the ID of main PHY (position in the vector of PHYs held by WifiNetDevice).
const WifiPhyOperatingChannel & GetChannelForMainPhy(uint8_t linkId) const
~EmlsrManager() override
void SetMediumSyncDuration(Time duration)
Set the duration of the MediumSyncDelay timer.
void SwitchMainPhy(uint8_t linkId, bool noSwitchDelay, bool resetBackoff, bool requestAccess, EmlsrMainPhySwitchTrace &&traceInfo)
Switch channel on the Main PHY so that it operates on the given link.
static constexpr bool RESET_BACKOFF
reset backoff on main PHY switch
Time m_lastAdvTransitionDelay
last advertised transition delay
MainPhySwitchInfo m_mainPhySwitchInfo
main PHY switch info
static constexpr bool DONT_REQUEST_ACCESS
do not request channel access when PHY switch ends
void DoDispose() override
Destructor implementation.
void StartMediumSyncDelayTimer(uint8_t linkId)
Start the MediumSyncDelay timer and take the appropriate actions.
int8_t m_msdOfdmEdThreshold
MediumSyncDelay OFDM ED threshold.
std::optional< uint8_t > m_msdMaxNTxops
MediumSyncDelay max number of TXOPs.
Ptr< StaWifiMac > GetStaMac() const
WifiModulationClass m_auxPhyMaxModClass
max modulation class supported by aux PHYs
uint8_t GetMainPhyId() const
std::optional< uint8_t > GetMediumSyncMaxNTxops() const
void SetCamStateReset(bool enable)
Set the member variable indicating whether the state of the CAM should be reset when the main PHY swi...
virtual void NotifyRtsSent(uint8_t linkId, Ptr< const WifiPsdu > rts, const WifiTxVector &txVector)
Notify that RTS transmission is starting on the given link.
EventId m_transitionTimeoutEvent
Timer started after the successful transmission of an EML Operating Mode Notification frame.
virtual void DoNotifyIcfReceived(uint8_t linkId)=0
Notify the subclass of the reception of an initial Control frame on the given link.
void SetInDeviceInterference(bool enable)
Set the member variable indicating whether in-device interference is such that a PHY cannot decode an...
const WifiPhyOperatingChannel & GetChannelForAuxPhy(uint8_t linkId) const
bool m_resetCamState
whether to reset the state of CAM when main PHY switches channel
static TypeId GetTypeId()
Get the type ID.
std::pair< bool, Time > CheckPossiblyReceivingIcf(uint8_t linkId) const
Check whether a PPDU that may be an ICF is being received on the given link.
std::set< uint8_t > m_emlsrLinks
ID of the EMLSR links (empty if EMLSR mode is disabled)
void SetSleepStateForAllAuxPhys(bool sleep)
Set sleep state or awake state for all aux PHYs.
Hold variables of type enum.
Definition enum.h:52
void Cancel()
This method is syntactic sugar for the ns3::Simulator::Cancel method.
Definition event-id.cc:44
bool IsPending() const
This method is syntactic sugar for !IsExpired().
Definition event-id.cc:65
EventImpl * PeekEventImpl() const
Definition event-id.cc:78
void Invoke()
Called by the simulation engine to notify the event that it is time to execute.
Definition event-impl.cc:36
Implement the header for management frames of type association request.
Implement the header for Action frames of type EML Operating Mode Notification.
void SetLinkIdInBitmap(uint8_t linkId)
Set the bit position in the link bitmap corresponding to the given link.
EmlControl m_emlControl
EML Control field.
std::optional< EmlsrParamUpdate > m_emlsrParamUpdate
EMLSR Parameter Update field.
A base class which provides memory management and object aggregation.
Definition object.h:78
virtual void DoDispose()
Destructor implementation.
Definition object.cc:433
bool IsInitialized() const
Check if the object has been initialized.
Definition object.cc:240
Smart pointer class similar to boost::intrusive_ptr.
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition simulator.h:560
static Time Now()
Return the current simulation virtual time.
Definition simulator.cc:197
static Time GetDelayLeft(const EventId &id)
Get the remaining time until this event will execute.
Definition simulator.cc:206
Simulation virtual time values and global simulation resolution.
Definition nstime.h:94
TimeWithUnit As(const Unit unit=Time::AUTO) const
Attach a unit to a Time, to facilitate output in a specific unit.
Definition time.cc:404
@ US
microsecond
Definition nstime.h:107
static constexpr bool DIDNT_HAVE_FRAMES_TO_TRANSMIT
no packet available for transmission was in the queue
Definition txop.h:409
static constexpr bool CHECK_MEDIUM_BUSY
generation of backoff (also) depends on the busy/idle state of the medium
Definition txop.h:411
a unique identifier for an interface.
Definition type-id.h:49
@ ATTR_GET
The attribute can be read.
Definition type-id.h:54
@ ATTR_CONSTRUCT
The attribute can be written at construction-time.
Definition type-id.h:56
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition type-id.cc:1001
Hold an unsigned integer type.
Definition uinteger.h:34
static std::pair< CategoryValue, ActionValue > Peek(Ptr< const Packet > pkt)
Peek an Action header from the given packet.
Class that keeps track of all information about the current PHY operating channel.
This class mimics the TXVECTOR which is to be passed to the PHY in order to define the parameters whi...
#define NS_ASSERT(condition)
At runtime, in debugging builds, if this condition is not true, the program prints the source file,...
Definition assert.h:55
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition assert.h:75
Ptr< AttributeChecker > MakeAttributeContainerChecker()
Make uninitialized AttributeContainerChecker using explicit types.
Ptr< const AttributeAccessor > MakeAttributeContainerAccessor(T1 a1)
Make AttributeContainerAccessor using explicit types.
#define NS_ABORT_MSG_IF(cond, msg)
Abnormal program termination if a condition is true, with a message.
Definition abort.h:97
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition log.h:191
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition log.h:257
#define NS_LOG_FUNCTION_NOARGS()
Output the name of the function.
#define NS_LOG_FUNCTION(parameters)
If log level LOG_FUNCTION is enabled, this macro will output all input parameters separated by ",...
#define NS_OBJECT_ENSURE_REGISTERED(type)
Register an Object subclass with the TypeId system.
Definition object-base.h:35
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1368
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1344
Ptr< const TraceSourceAccessor > MakeTraceSourceAccessor(T a)
Create a TraceSourceAccessor which will control access to the underlying trace source.
WifiMacDropReason
The reason why an MPDU was dropped.
Definition wifi-mac.h:71
AcIndex
This enumeration defines the Access Categories as an enumeration with values corresponding to the AC ...
Definition qos-utils.h:62
@ STA
Definition wifi-mac.h:59
@ WIFI_STANDARD_UNSPECIFIED
@ WIFI_MOD_CLASS_OFDM
OFDM (Clause 17)
@ WIFI_MOD_CLASS_HR_DSSS
HR/DSSS (Clause 16)
@ WIFI_MOD_CLASS_HT
HT (Clause 19)
@ WIFI_MOD_CLASS_EHT
EHT (Clause 36)
@ WIFI_MOD_CLASS_VHT
VHT (Clause 22)
@ WIFI_MOD_CLASS_HE
HE (Clause 27)
@ WIFI_MOD_CLASS_ERP_OFDM
ERP-OFDM (18.4)
Every class exported by the ns3 library is enclosed in the ns3 namespace.
Ptr< const AttributeChecker > MakeBooleanChecker()
Definition boolean.cc:113
Ptr< const AttributeChecker > MakeUintegerChecker()
Definition uinteger.h:85
static constexpr uint8_t DEFAULT_MSD_MAX_N_TXOPS
default MediumSyncDelay max number of TXOP attempts
@ SWITCHING
The PHY layer is switching to other channel.
Ptr< const AttributeAccessor > MakeTimeAccessor(T1 a1)
Definition nstime.h:1432
Callback< R, Args... > MakeCallback(R(T::*memPtr)(Args...), OBJ objPtr)
Build Callbacks for class method members which take varying numbers of arguments and potentially retu...
Definition callback.h:684
Ptr< const AttributeAccessor > MakeUintegerAccessor(T1 a1)
Definition uinteger.h:35
Ptr< const AttributeChecker > MakeEnumChecker(T v, std::string n, Ts... args)
Make an EnumChecker pre-configured with a set of allowed values by name.
Definition enum.h:179
MHz_u GetMaximumChannelWidth(WifiModulationClass modulation)
Get the maximum channel width allowed for the given modulation class.
const std::map< AcIndex, WifiAc > wifiAcList
Map containing the four ACs in increasing order of priority (according to Table 10-1 "UP-to-AC Mappin...
Definition qos-utils.cc:115
static constexpr int8_t DEFAULT_MSD_OFDM_ED_THRESH
default MediumSyncDelay timer OFDM ED threshold
@ LOG_FUNCTION
Function tracing for non-trivial function calls.
Definition log.h:95
Ptr< T1 > StaticCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:587
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Definition boolean.h:70
static constexpr uint16_t SU_STA_ID
STA_ID to identify a single user (SU)
Definition wifi-mode.h:24
const Time EMLSR_RX_PHY_START_DELAY
aRxPHYStartDelay value to use when waiting for a new frame in the context of EMLSR operations (Sec.
static constexpr uint16_t DEFAULT_MSD_DURATION_USEC
default MediumSyncDelay timer duration (max PPDU TX time rounded to a multiple of 32 us)
Ptr< const AttributeAccessor > MakeEnumAccessor(T1 a1)
Definition enum.h:221
Ptr< const AttributeChecker > MakeTimeChecker()
Helper to make an unbounded Time checker.
Definition nstime.h:1452
ns3::Time timeout
Struct to trace that main PHY switched to start a DL TXOP after that an aux PHY received an ICF.
Base struct for EMLSR Main PHY switch traces.
Time end
end of channel switching
uint8_t from
ID of the link which the main PHY is/has been leaving.
uint8_t to
ID of the link which the main PHY is moving to.
uint8_t emlsrParamUpdateCtrl
EMLSR Parameter Update Control.
EMLSR Parameter Update field.