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->GetEhtConfiguration()->m_emlsrActivated,
188 "EmlsrManager requires EMLSR to be activated");
189 NS_ABORT_MSG_IF(m_staMac->GetTypeOfStation() != STA,
190 "EmlsrManager can only be installed on non-AP MLDs");
191 NS_ABORT_MSG_IF(m_mainPhyId >= m_staMac->GetDevice()->GetNPhys(),
192 "Main PHY ID (" << +m_mainPhyId << ") invalid given the number of PHY objects ("
193 << +m_staMac->GetDevice()->GetNPhys() << ")");
194
195 m_staMac->TraceConnectWithoutContext("AckedMpdu", MakeCallback(&EmlsrManager::TxOk, this));
196 m_staMac->TraceConnectWithoutContext("DroppedMpdu",
198 m_staMac->TraceConnectWithoutContext(
199 "EmlsrLinkSwitch",
201 DoSetWifiMac(mac);
202}
203
204void
209
210void
212{
213 NS_LOG_FUNCTION(this << linkId << phy);
214
215 if (!phy)
216 {
217 NS_ASSERT(!m_noPhySince.contains(linkId));
218 NS_LOG_DEBUG("Record that no PHY is operating on link " << +linkId);
219 m_noPhySince[linkId] = Simulator::Now();
220 return;
221 }
222
223 // phy switched to operate on the link with ID equal to linkId
224 auto it = m_noPhySince.find(linkId);
225
226 if (it == m_noPhySince.end())
227 {
228 // phy switched to a link on which another PHY was operating, do nothing
229 return;
230 }
231
232 auto duration = Simulator::Now() - it->second;
233 NS_ASSERT_MSG(duration.IsPositive(), "Interval duration should not be negative");
234
235 NS_LOG_DEBUG("PHY " << +phy->GetPhyId() << " switched to link " << +linkId << " after "
236 << duration.As(Time::US)
237 << " since last time a PHY was operating on this link");
239 {
241 }
242
243 m_noPhySince.erase(it);
244}
245
246void
248{
249 NS_LOG_FUNCTION(this << mainPhyId);
250 NS_ABORT_MSG_IF(IsInitialized(), "Cannot be called once this object has been initialized");
251 m_mainPhyId = mainPhyId;
252}
253
254uint8_t
256{
257 return m_mainPhyId;
258}
259
260void
262{
263 m_resetCamState = enable;
264}
265
266bool
271
272void
274{
275 m_auxPhyTxCapable = capable;
276}
277
278bool
283
284void
289
290bool
295
296const std::set<uint8_t>&
298{
299 return m_emlsrLinks;
300}
301
304{
305 return m_staMac;
306}
307
309EmlsrManager::GetEhtFem(uint8_t linkId) const
310{
311 return StaticCast<EhtFrameExchangeManager>(m_staMac->GetFrameExchangeManager(linkId));
312}
313
314std::optional<Time>
316{
317 if (const auto statusIt = m_mediumSyncDelayStatus.find(linkId);
318 statusIt != m_mediumSyncDelayStatus.cend() && statusIt->second.timer.IsPending())
319 {
320 return m_mediumSyncDuration - Simulator::GetDelayLeft(statusIt->second.timer);
321 }
322 return std::nullopt;
323}
324
325void
331
332std::optional<Time>
337
338void
340{
341 NS_LOG_FUNCTION(this << duration.As(Time::US));
342 m_mediumSyncDuration = duration;
343}
344
345Time
350
351void
353{
354 NS_LOG_FUNCTION(this << threshold);
355 m_msdOfdmEdThreshold = threshold;
356}
357
358int8_t
363
364void
365EmlsrManager::SetMediumSyncMaxNTxops(std::optional<uint8_t> nTxops)
366{
367 NS_LOG_FUNCTION(this << nTxops.has_value());
368 m_msdMaxNTxops = nTxops;
369}
370
371std::optional<uint8_t>
376
377void
378EmlsrManager::SetEmlsrLinks(const std::set<uint8_t>& linkIds)
379{
380 std::stringstream ss;
381 if (g_log.IsEnabled(ns3::LOG_FUNCTION))
382 {
383 std::copy(linkIds.cbegin(), linkIds.cend(), std::ostream_iterator<uint16_t>(ss, " "));
384 }
385 NS_LOG_FUNCTION(this << ss.str());
386
387 if (linkIds != m_emlsrLinks)
388 {
389 m_nextEmlsrLinks = linkIds;
390 }
391
392 if (GetStaMac() && GetStaMac()->IsAssociated() && GetTransitionTimeout() && m_nextEmlsrLinks)
393 {
394 // Request to enable EMLSR mode on the given links, provided that they have been setup
395 SendEmlOmn();
396 }
397}
398
399void
401{
402 NS_LOG_FUNCTION(this << *mpdu << linkId);
403
404 const auto& hdr = mpdu->GetHeader();
405
406 DoNotifyMgtFrameReceived(mpdu, linkId);
407
408 if (hdr.IsAssocResp() && GetStaMac()->IsAssociated() && GetTransitionTimeout())
409 {
410 // we just completed ML setup with an AP MLD that supports EMLSR
412
413 if (m_nextEmlsrLinks && !m_nextEmlsrLinks->empty())
414 {
415 // a non-empty set of EMLSR links have been configured, hence enable EMLSR mode
416 // on those links
417 SendEmlOmn();
418 }
419 }
420
421 if (hdr.IsAction() && hdr.GetAddr2() == m_staMac->GetBssid(linkId))
422 {
423 // this is an action frame sent by an AP of the AP MLD we are associated with
424 auto [category, action] = WifiActionHeader::Peek(mpdu->GetPacket());
425 if (category == WifiActionHeader::PROTECTED_EHT &&
426 action.protectedEhtAction ==
428 {
430 {
431 // no need to wait until the expiration of the transition timeout
434 }
435 }
436 }
437}
438
439void
441{
442 NS_LOG_FUNCTION(this << linkId);
443
444 NS_ASSERT(m_staMac->IsEmlsrLink(linkId));
445
446 // block transmissions and suspend medium access on all other EMLSR links
447 for (auto id : m_staMac->GetLinkIds())
448 {
449 if (id != linkId && m_staMac->IsEmlsrLink(id))
450 {
452 }
453 }
454
455 auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId);
456 auto rxPhy = m_staMac->GetWifiPhy(linkId);
457
458 const auto receivedByAuxPhy = (rxPhy != mainPhy);
459 const auto mainPhyOnALink = (m_staMac->GetLinkForPhy(mainPhy).has_value());
460 const auto mainPhyIsSwitching =
461 (mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}) == Simulator::Now());
462 // if the main PHY is not operating on a link and it is not switching, then we have postponed
463 // the reconnection of the main PHY to a link because a PPDU reception was ongoing on that link
464 const auto mainPhyToConnect = (!mainPhyOnALink && !mainPhyIsSwitching);
465
466 const auto mainPhyToConnectToOtherLink = mainPhyToConnect && (m_mainPhySwitchInfo.to != linkId);
467
468 if (mainPhyToConnect)
469 {
470 // If ICF was received on a link other than the one the main PHY is waiting to be connected
471 // to, we need to cancel the pending reconnection and request a new main PHY switch.
472 // If ICF was received on the link the main PHY is waiting to be connected to, we cancel
473 // the pending reconnection and explicitly request the reconnection below
474 GetStaMac()->CancelEmlsrPhyConnectEvent(mainPhy->GetPhyId());
475 }
476
477 // We need to request a main PHY switch if:
478 // - the ICF was received by an aux PHY, AND
479 // - the main PHY is not waiting to be connected to a link OR it is waiting to be connected
480 // to a link other than the link on which the ICF is received.
481 if (receivedByAuxPhy && (!mainPhyToConnect || mainPhyToConnectToOtherLink))
482 {
483 SwitchMainPhy(linkId,
484 true, // channel switch should occur instantaneously
488 }
489 else if (mainPhyToConnect && !mainPhyToConnectToOtherLink)
490 {
491 // If the main PHY is waiting to be connected to the link on which the ICF was received, we
492 // have to explicitly perform the connection because the pending event was cancelled above.
493 // We do this way in order to reconnect the main PHY before putting aux PHYs to sleep: if
494 // the aux PHY is put to sleep while still operating on the link on which it received the
495 // ICF, all the MAC events (including scheduled CTS transmission) will be cancelled.
496 m_staMac->NotifySwitchingEmlsrLink(mainPhy, linkId, Time{0});
497 }
498
499 if (receivedByAuxPhy)
500 {
501 // aux PHY received the ICF but main PHY will send the response
502 auto uid = rxPhy->GetPreviouslyRxPpduUid();
503 mainPhy->SetPreviouslyRxPpduUid(uid);
504 }
505
506 // a DL TXOP started, set all aux PHYs to sleep
507 if (m_auxPhyToSleep)
508 {
510 }
511
512 DoNotifyIcfReceived(linkId);
513}
514
515std::pair<bool, Time>
517{
518 auto phy = m_staMac->GetWifiPhy(linkId);
519 NS_ASSERT_MSG(phy, "No PHY operating on link " << +linkId);
520
521 auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId);
522
523 // check possible reasons to give up the TXOP that apply to both main PHY and aux PHYs
524 if (const auto [startTxop, delay] = DoGetDelayUntilAccessRequest(linkId); !startTxop)
525 {
526 return {false, delay};
527 }
528
529 if (phy == mainPhy)
530 {
531 // no more constraints to check if medium was gained by main PHY
532 return {true, Time{0}};
533 }
534
535 // an aux PHY is operating on the given link; call the appropriate method depending on
536 // whether the aux PHY is TX capable or not
538 {
540 // if the aux PHY is not TX capable, we don't have to request channel access: if the main
541 // PHY switches link, the UL TXOP will be started; if the main PHY does not switch, it is
542 // because it is going to start an UL TXOP on another link and this link will be restarted
543 // at the end of that UL TXOP when this link will be unblocked
544 NS_LOG_DEBUG("Aux PHY is not capable of transmitting a PPDU");
545 return {false, Time{0}};
546 }
547
549}
550
551std::pair<bool, Time>
553{
554 NS_LOG_FUNCTION(this << linkId);
555
556 auto phy = GetStaMac()->GetWifiPhy(linkId);
557
558 if (!phy || !GetStaMac()->IsEmlsrLink(linkId))
559 {
560 NS_LOG_DEBUG("No PHY (" << phy << ") or not an EMLSR link (" << +linkId << ")");
561 return {false, Time{0}};
562 }
563
564 if (auto macHdr = GetEhtFem(linkId)->GetReceivedMacHdr(); macHdr && m_useNotifiedMacHdr)
565 {
566 NS_LOG_DEBUG("Receiving the MAC payload of a PSDU and MAC header info can be used");
567 NS_ASSERT(phy->GetState()->GetLastTime({WifiPhyState::RX}) == Simulator::Now());
568
569 if (const auto& hdr = macHdr->get();
570 hdr.IsTrigger() &&
571 (hdr.GetAddr1().IsBroadcast() || hdr.GetAddr1() == GetEhtFem(linkId)->GetAddress()))
572 {
573 // the PSDU being received _may_ be an ICF. Note that we cannot be sure that the PSDU
574 // being received is an ICF addressed to us until we receive the entire PSDU
575 NS_LOG_DEBUG("Based on MAC header, may be an ICF, postpone by "
576 << phy->GetDelayUntilIdle().As(Time::US));
577 return {true, phy->GetDelayUntilIdle()};
578 }
579 }
580 else if (auto txVector = phy->GetInfoIfRxingPhyHeader())
581 {
582 NS_LOG_DEBUG("Receiving PHY header");
583 if (txVector->get().GetModulationClass() < WIFI_MOD_CLASS_HT)
584 {
585 // the PHY header of a non-HT PPDU, which may be an ICF, is being received; check
586 // again after the TX duration of a non-HT PHY header
587 NS_LOG_DEBUG("PHY header of a non-HT PPDU, which may be an ICF, is being received");
588 return {true, EMLSR_RX_PHY_START_DELAY};
589 }
590 }
591 else if (phy->IsStateRx())
592 {
593 // we have not yet received the MAC header or we cannot use its info
594
595 auto ongoingRxInfo = GetEhtFem(linkId)->GetOngoingRxInfo();
596 // if a PHY is in RX state, it should have info about received MAC header.
597 // The exception is represented by this situation:
598 // - an aux PHY is disconnected from the MAC stack because the main PHY is
599 // operating on its link
600 // - the main PHY notifies the MAC header info to the FEM and then leaves the
601 // link (e.g., because it recognizes that the MPDU is not addressed to the
602 // EMLSR client). Disconnecting the main PHY from the MAC stack causes the
603 // MAC header info to be discarded by the FEM
604 // - the aux PHY is re-connected to the MAC stack and is still in RX state
605 // when the main PHY gets channel access on another link (and we get here)
606 if (!ongoingRxInfo.has_value())
607 {
608 NS_ASSERT_MSG(phy != GetStaMac()->GetDevice()->GetPhy(GetMainPhyId()),
609 "Main PHY should have MAC header info when in RX state");
610 // we are in the situation described above; if the MPDU being received
611 // by the aux PHY is not addressed to the EMLSR client, we can ignore it
612 }
613 else if (const auto& txVector = ongoingRxInfo->get().txVector;
614 txVector.GetModulationClass() < WIFI_MOD_CLASS_HT)
615 {
616 if (auto remTime = phy->GetTimeToMacHdrEnd(SU_STA_ID);
617 m_useNotifiedMacHdr && remTime.has_value() && remTime->IsStrictlyPositive())
618 {
619 NS_LOG_DEBUG("Wait until the expected end of the MAC header reception: "
620 << remTime->As(Time::US));
621 return {true, *remTime};
622 }
623
625 "MAC header info will not be available. Wait until the end of PSDU reception: "
626 << phy->GetDelayUntilIdle().As(Time::US));
627 return {true, phy->GetDelayUntilIdle()};
628 }
629 }
630
631 NS_LOG_DEBUG("No ICF being received, state: " << phy->GetState()->GetState());
632 return {false, Time{0}};
633}
634
635void
637{
638 NS_LOG_FUNCTION(this << linkId);
639
640 if (!m_staMac->IsEmlsrLink(linkId))
641 {
642 NS_LOG_DEBUG("EMLSR is not enabled on link " << +linkId);
643 return;
644 }
645
646 // block transmissions and suspend medium access on all other EMLSR links
647 for (auto id : m_staMac->GetLinkIds())
648 {
649 if (id != linkId && m_staMac->IsEmlsrLink(id))
650 {
652 }
653 }
654
655 DoNotifyUlTxopStart(linkId);
656}
657
658void
660{
661 NS_LOG_FUNCTION(this << *rts << txVector);
662}
663
664void
666{
667 NS_LOG_FUNCTION(this << linkId);
668
669 if (m_auxPhyToSleep && m_staMac->IsEmlsrLink(linkId))
670 {
671 if (auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId); mainPhy->IsStateSwitching())
672 {
673 // main PHY is switching to this link to take over the UL TXOP. Postpone aux PHY
674 // sleeping until after the main PHY has completed switching
675 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1),
677 this,
678 true);
679 }
680 else
681 {
682 // put aux PHYs to sleep
684 }
685 }
686}
687
688void
689EmlsrManager::NotifyTxopEnd(uint8_t linkId, bool ulTxopNotStarted, bool ongoingDlTxop)
690{
691 NS_LOG_FUNCTION(this << linkId << ulTxopNotStarted << ongoingDlTxop);
692
693 if (!m_staMac->IsEmlsrLink(linkId))
694 {
695 NS_LOG_DEBUG("EMLSR is not enabled on link " << +linkId);
696 return;
697 }
698
699 // If the main PHY has been scheduled to switch to this link, cancel the channel switch.
700 // This happens, e.g., when an aux PHY sent an RTS to start an UL TXOP but it did not
701 // receive a CTS response.
702 if (auto it = m_ulMainPhySwitch.find(linkId); it != m_ulMainPhySwitch.end())
703 {
704 if (it->second.IsPending())
705 {
706 NS_LOG_DEBUG("Cancelling main PHY channel switch event on link " << +linkId);
707 it->second.Cancel();
708 }
709 m_ulMainPhySwitch.erase(it);
710 }
711
712 // Unblock the other EMLSR links and start the MediumSyncDelay timer, provided that the TXOP
713 // included the transmission of at least a frame and there is no ongoing DL TXOP on this link.
714 // Indeed, the UL TXOP may have ended because the transmission of a frame failed and the
715 // corresponding TX timeout (leading to this call) may have occurred after the reception on
716 // this link of an ICF starting a DL TXOP. If the EMLSR Manager unblocked the other EMLSR
717 // links, another TXOP could be started on another EMLSR link (possibly leading to a crash)
718 // while the DL TXOP on this link is ongoing.
719 if (ongoingDlTxop)
720 {
721 NS_LOG_DEBUG("DL TXOP ongoing");
722 return;
723 }
724 if (ulTxopNotStarted)
725 {
726 NS_LOG_DEBUG("TXOP did not even start");
727 return;
728 }
729
730 if (m_auxPhyToSleep)
731 {
732 // TXOP ended, resume all aux PHYs from sleep
734 }
735
736 DoNotifyTxopEnd(linkId);
737
738 // unblock transmissions and resume medium access on other EMLSR links
739 std::set<uint8_t> linkIds;
740 for (auto id : m_staMac->GetLinkIds())
741 {
742 if ((id != linkId) && m_staMac->IsEmlsrLink(id))
743 {
744 linkIds.insert(id);
745 }
746 }
748}
749
750void
752{
753 NS_LOG_FUNCTION(this << linkId << duration.As(Time::US));
755
756 // The STA may choose not to (re)start the MediumSyncDelay timer if the transmission duration
757 // is less than or equal to aMediumSyncThreshold. (Sec. 35.3.16.8.1 802.11be D5.1)
759 {
760 return;
761 }
762
763 // iterate over all the other EMLSR links
764 for (auto id : m_staMac->GetLinkIds())
765 {
766 if (id != linkId && m_staMac->IsEmlsrLink(id))
767 {
769 }
770 }
771}
772
773void
775{
776 NS_LOG_FUNCTION(this << phy << linkId);
777
778 // if a MediumSyncDelay timer is running for the link on which the main PHY is going to
779 // operate, set the CCA ED threshold to the MediumSyncDelay OFDM ED threshold
780 if (auto statusIt = m_mediumSyncDelayStatus.find(linkId);
781 statusIt != m_mediumSyncDelayStatus.cend() && statusIt->second.timer.IsPending())
782 {
783 NS_LOG_DEBUG("Setting CCA ED threshold of PHY " << phy << " to " << +m_msdOfdmEdThreshold
784 << " on link " << +linkId);
785
786 // store the current CCA ED threshold in the m_prevCcaEdThreshold map, if not present
787 m_prevCcaEdThreshold.try_emplace(phy, phy->GetCcaEdThreshold());
788
789 phy->SetCcaEdThreshold(m_msdOfdmEdThreshold);
790 }
791 // otherwise, restore the previous value for the CCA ED threshold (if any)
792 else if (auto threshIt = m_prevCcaEdThreshold.find(phy);
793 threshIt != m_prevCcaEdThreshold.cend())
794 {
795 NS_LOG_DEBUG("Resetting CCA ED threshold of PHY " << phy << " to " << threshIt->second
796 << " on link " << +linkId);
797 phy->SetCcaEdThreshold(threshIt->second);
798 m_prevCcaEdThreshold.erase(threshIt);
799 }
800}
801
802void
804 bool noSwitchDelay,
805 bool resetBackoff,
806 bool requestAccess,
807 EmlsrMainPhySwitchTrace&& traceInfo)
808{
809 NS_LOG_FUNCTION(this << linkId << noSwitchDelay << resetBackoff << requestAccess
810 << traceInfo.GetName());
811
812 auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId);
813
814 NS_ASSERT_MSG(mainPhy != m_staMac->GetWifiPhy(linkId),
815 "Main PHY is already operating on link " << +linkId);
816
817 // find the link on which the main PHY is operating
818 auto currMainPhyLinkId = m_staMac->GetLinkForPhy(mainPhy);
819 traceInfo.fromLinkId = currMainPhyLinkId;
820 traceInfo.toLinkId = linkId;
821 m_mainPhySwitchTrace(traceInfo);
822
823 const auto newMainPhyChannel = GetChannelForMainPhy(linkId);
824
825 NS_LOG_DEBUG("Main PHY (" << mainPhy << ") is about to switch to " << newMainPhyChannel
826 << " to operate on link " << +linkId);
827
828 // if the main PHY is operating on a link, notify the channel access manager of the upcoming
829 // channel switch
830 if (currMainPhyLinkId.has_value())
831 {
832 m_staMac->GetChannelAccessManager(*currMainPhyLinkId)
833 ->NotifySwitchingEmlsrLink(mainPhy, newMainPhyChannel, linkId);
834 }
835
836 // this assert also ensures that the actual channel switch is not delayed
837 NS_ASSERT_MSG(!mainPhy->GetState()->IsStateTx(),
838 "We should not ask the main PHY to switch channel while transmitting");
839
840 // record the aux PHY operating on the link the main PHY is switching to
841 auto auxPhy = GetStaMac()->GetWifiPhy(linkId);
842
843 // request the main PHY to switch channel
844 const auto delay = mainPhy->GetChannelSwitchDelay();
845 const auto pifs = mainPhy->GetSifs() + mainPhy->GetSlot();
846 NS_ASSERT_MSG(noSwitchDelay || delay <= std::max(m_lastAdvTransitionDelay, pifs),
847 "Channel switch delay ("
848 << delay.As(Time::US)
849 << ") should be shorter than the maximum between the Transition delay ("
850 << m_lastAdvTransitionDelay.As(Time::US) << ") and a PIFS ("
851 << pifs.As(Time::US) << ")");
852 if (noSwitchDelay)
853 {
854 mainPhy->SetAttribute("ChannelSwitchDelay", TimeValue(Seconds(0)));
855 }
856 mainPhy->SetOperatingChannel(newMainPhyChannel);
857 // restore previous channel switch delay
858 if (noSwitchDelay)
859 {
860 mainPhy->SetAttribute("ChannelSwitchDelay", TimeValue(delay));
861 }
862 // re-enable short time slot, if needed
863 if (m_staMac->GetWifiRemoteStationManager(linkId)->GetShortSlotTimeEnabled())
864 {
865 mainPhy->SetSlot(MicroSeconds(9));
866 }
867
868 const auto timeToSwitchEnd = noSwitchDelay ? Seconds(0) : delay;
869
870 // if the main PHY is not operating on any link (because it was switching), it is not connected
871 // to a channel access manager, hence we must notify the MAC of the new link switch
872 if (!currMainPhyLinkId.has_value())
873 {
874 m_staMac->NotifySwitchingEmlsrLink(mainPhy, linkId, timeToSwitchEnd);
875 }
876
877 if (resetBackoff && currMainPhyLinkId.has_value())
878 {
879 // reset the backoffs on the link left by the main PHY
880 m_staMac->GetChannelAccessManager(*currMainPhyLinkId)->ResetAllBackoffs();
881 }
882
883 if (requestAccess)
884 {
885 // schedule channel access request on the new link when switch is completed
886 Simulator::Schedule(timeToSwitchEnd, [=, this]() {
887 for (const auto& [acIndex, ac] : wifiAcList)
888 {
889 m_staMac->GetQosTxop(acIndex)->StartAccessAfterEvent(
890 linkId,
893 }
894 });
895 }
896
897 m_mainPhySwitchInfo.from = currMainPhyLinkId.value_or(m_mainPhySwitchInfo.from);
898 m_mainPhySwitchInfo.to = linkId;
899 m_mainPhySwitchInfo.end = Simulator::Now() + timeToSwitchEnd;
900
901 SetCcaEdThresholdOnLinkSwitch(mainPhy, linkId);
902 NotifyMainPhySwitch(currMainPhyLinkId, linkId, auxPhy, timeToSwitchEnd);
903}
904
905void
906EmlsrManager::SwitchAuxPhy(Ptr<WifiPhy> auxPhy, uint8_t currLinkId, uint8_t nextLinkId)
907{
908 NS_LOG_FUNCTION(this << auxPhy << currLinkId << nextLinkId);
909
910 auto newAuxPhyChannel = GetChannelForAuxPhy(nextLinkId);
911
912 NS_LOG_DEBUG("Aux PHY (" << auxPhy << ") is about to switch to " << newAuxPhyChannel
913 << " to operate on link " << +nextLinkId);
914
915 GetStaMac()
916 ->GetChannelAccessManager(currLinkId)
917 ->NotifySwitchingEmlsrLink(auxPhy, newAuxPhyChannel, nextLinkId);
918
919 auxPhy->SetOperatingChannel(newAuxPhyChannel);
920 // re-enable short time slot, if needed
921 if (m_staMac->GetWifiRemoteStationManager(nextLinkId)->GetShortSlotTimeEnabled())
922 {
923 auxPhy->SetSlot(MicroSeconds(9));
924 }
925
926 // schedule channel access request on the new link when switch is completed
927 Simulator::Schedule(auxPhy->GetChannelSwitchDelay(), [=, this]() {
928 for (const auto& [acIndex, ac] : wifiAcList)
929 {
930 m_staMac->GetQosTxop(acIndex)->StartAccessAfterEvent(
931 nextLinkId,
932 Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT,
933 Txop::CHECK_MEDIUM_BUSY);
934 }
935 });
936
937 SetCcaEdThresholdOnLinkSwitch(auxPhy, nextLinkId);
938}
939
940void
941EmlsrManager::StartMediumSyncDelayTimer(uint8_t linkId)
942{
943 NS_LOG_FUNCTION(this << linkId);
944
945 if (m_mediumSyncDuration.IsZero())
946 {
947 NS_LOG_DEBUG("MediumSyncDuration is zero");
948 return;
949 }
950
951 auto phy = m_staMac->GetWifiPhy(linkId);
952
953 if (!phy)
954 {
955 NS_LOG_DEBUG("No PHY operating on link " << +linkId);
956 // MSD timer will be started when a PHY will be operating on this link
957 return;
958 }
959
960 const auto [it, inserted] = m_mediumSyncDelayStatus.try_emplace(linkId);
961
962 // reset the max number of TXOP attempts
963 it->second.msdNTxopsLeft = m_msdMaxNTxops;
964
965 if (!it->second.timer.IsPending())
966 {
967 NS_LOG_DEBUG("Setting CCA ED threshold on link "
968 << +linkId << " to " << +m_msdOfdmEdThreshold << " PHY " << phy);
969 m_prevCcaEdThreshold[phy] = phy->GetCcaEdThreshold();
970 phy->SetCcaEdThreshold(m_msdOfdmEdThreshold);
971 }
972
973 // (re)start the timer
974 it->second.timer.Cancel();
975 NS_LOG_DEBUG("Starting MediumSyncDelay timer for " << m_mediumSyncDuration.As(Time::US)
976 << " on link " << +linkId);
977 it->second.timer = Simulator::Schedule(m_mediumSyncDuration,
978 &EmlsrManager::MediumSyncDelayTimerExpired,
979 this,
980 linkId);
981}
982
983void
984EmlsrManager::CancelMediumSyncDelayTimer(uint8_t linkId)
985{
986 NS_LOG_FUNCTION(this << linkId);
987
988 auto timerIt = m_mediumSyncDelayStatus.find(linkId);
989
990 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && timerIt->second.timer.IsPending());
991
992 timerIt->second.timer.Cancel();
993 MediumSyncDelayTimerExpired(linkId);
994}
995
996void
997EmlsrManager::MediumSyncDelayTimerExpired(uint8_t linkId)
998{
999 NS_LOG_FUNCTION(this << linkId);
1000
1001 auto timerIt = m_mediumSyncDelayStatus.find(linkId);
1002
1003 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && !timerIt->second.timer.IsPending());
1004
1005 // reset the MSD OFDM ED threshold
1006 auto phy = m_staMac->GetWifiPhy(linkId);
1007
1008 if (!phy)
1009 {
1010 // no PHY is operating on this link. This may happen when a MediumSyncDelay timer expires
1011 // on the link left "uncovered" by the main PHY that is operating on another link (and the
1012 // aux PHY of that link did not switch). In this case, do nothing, since the CCA ED
1013 // threshold on the main PHY will be restored once the main PHY switches back to its link
1014 return;
1015 }
1016
1017 auto threshIt = m_prevCcaEdThreshold.find(phy);
1018 NS_ASSERT_MSG(threshIt != m_prevCcaEdThreshold.cend(),
1019 "No value to restore for CCA ED threshold on PHY " << phy);
1020 NS_LOG_DEBUG("Resetting CCA ED threshold of PHY " << phy << " to " << threshIt->second
1021 << " on link " << +linkId);
1022 phy->SetCcaEdThreshold(threshIt->second);
1023 m_prevCcaEdThreshold.erase(threshIt);
1024}
1025
1026void
1027EmlsrManager::DecrementMediumSyncDelayNTxops(uint8_t linkId)
1028{
1029 NS_LOG_FUNCTION(this << linkId);
1030
1031 const auto timerIt = m_mediumSyncDelayStatus.find(linkId);
1032
1033 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && timerIt->second.timer.IsPending());
1034 NS_ASSERT(timerIt->second.msdNTxopsLeft != 0);
1035
1036 if (timerIt->second.msdNTxopsLeft)
1037 {
1038 --timerIt->second.msdNTxopsLeft.value();
1039 }
1040}
1041
1042void
1043EmlsrManager::ResetMediumSyncDelayNTxops(uint8_t linkId)
1044{
1045 NS_LOG_FUNCTION(this << linkId);
1046
1047 auto timerIt = m_mediumSyncDelayStatus.find(linkId);
1048
1049 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && timerIt->second.timer.IsPending());
1050 timerIt->second.msdNTxopsLeft.reset();
1051}
1052
1053bool
1054EmlsrManager::MediumSyncDelayNTxopsExceeded(uint8_t linkId)
1055{
1056 NS_LOG_FUNCTION(this << linkId);
1057
1058 auto timerIt = m_mediumSyncDelayStatus.find(linkId);
1059
1060 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && timerIt->second.timer.IsPending());
1061 return timerIt->second.msdNTxopsLeft == 0;
1062}
1063
1064bool
1065EmlsrManager::GetExpectedAccessWithinDelay(uint8_t linkId, const Time& delay) const
1066{
1067 const auto deadline = Simulator::Now() + delay;
1068 for (const auto& [acIndex, ac] : wifiAcList)
1069 {
1070 if (auto edca = m_staMac->GetQosTxop(acIndex); edca->HasFramesToTransmit(linkId))
1071 {
1072 const auto backoffEnd =
1073 m_staMac->GetChannelAccessManager(linkId)->GetBackoffEndFor(edca);
1074
1075 if (backoffEnd <= deadline)
1076 {
1077 NS_LOG_DEBUG("Backoff end for " << acIndex << " on link " << +linkId << ": "
1078 << backoffEnd.As(Time::US));
1079 return true;
1080 }
1081 }
1082 }
1083 return false;
1084}
1085
1087EmlsrManager::GetEmlOmn()
1088{
1089 MgtEmlOmn frame;
1090
1091 // Add the EMLSR Parameter Update field if needed
1092 if (m_lastAdvPaddingDelay != m_emlsrPaddingDelay ||
1093 m_lastAdvTransitionDelay != m_emlsrTransitionDelay)
1094 {
1095 m_lastAdvPaddingDelay = m_emlsrPaddingDelay;
1096 m_lastAdvTransitionDelay = m_emlsrTransitionDelay;
1099 frame.m_emlsrParamUpdate->paddingDelay =
1100 CommonInfoBasicMle::EncodeEmlsrPaddingDelay(m_lastAdvPaddingDelay);
1101 frame.m_emlsrParamUpdate->transitionDelay =
1102 CommonInfoBasicMle::EncodeEmlsrTransitionDelay(m_lastAdvTransitionDelay);
1103 }
1104
1105 // We must verify that the links included in the given EMLSR link set (if any) have been setup.
1106 auto setupLinkIds = m_staMac->GetSetupLinkIds();
1107
1108 for (auto emlsrLinkIt = m_nextEmlsrLinks->begin(); emlsrLinkIt != m_nextEmlsrLinks->end();)
1109 {
1110 if (auto setupLinkIt = setupLinkIds.find(*emlsrLinkIt); setupLinkIt != setupLinkIds.cend())
1111 {
1112 setupLinkIds.erase(setupLinkIt);
1113 frame.SetLinkIdInBitmap(*emlsrLinkIt);
1114 emlsrLinkIt++;
1115 }
1116 else
1117 {
1118 NS_LOG_DEBUG("Link ID " << +(*emlsrLinkIt) << " has not been setup");
1119 emlsrLinkIt = m_nextEmlsrLinks->erase(emlsrLinkIt);
1120 }
1121 }
1122
1123 // EMLSR Mode is enabled if and only if the set of EMLSR links is not empty
1124 frame.m_emlControl.emlsrMode = m_nextEmlsrLinks->empty() ? 0 : 1;
1125
1126 return frame;
1127}
1128
1129void
1130EmlsrManager::SendEmlOmn()
1131{
1132 NS_LOG_FUNCTION(this);
1133
1134 NS_ABORT_MSG_IF(!m_emlsrTransitionTimeout,
1135 "AP did not advertise a Transition Timeout, cannot send EML notification");
1136 NS_ASSERT_MSG(m_nextEmlsrLinks, "Need to set EMLSR links before calling this method");
1137
1138 // TODO if this is a single radio non-AP MLD and not all setup links are in the EMLSR link
1139 // set, we have to put setup links that are not included in the given EMLSR link set (i.e.,
1140 // those remaining in setupLinkIds, if m_nextEmlsrLinks is not empty) in the sleep mode:
1141 // For the EMLSR mode enabled in a single radio non-AP MLD, the STA(s) affiliated with
1142 // the non-AP MLD that operates on the enabled link(s) that corresponds to the bit
1143 // position(s) of the EMLSR Link Bitmap subfield set to 0 shall be in doze state if a
1144 // non-AP STA affiliated with the non-AP MLD that operates on one of the EMLSR links is
1145 // in awake state. (Sec. 35.3.17 of 802.11be D3.0)
1146
1147 auto frame = GetEmlOmn();
1148 auto linkId = GetLinkToSendEmlOmn();
1149 GetEhtFem(linkId)->SendEmlOmn(m_staMac->GetBssid(linkId), frame);
1150}
1151
1152void
1153EmlsrManager::TxOk(Ptr<const WifiMpdu> mpdu)
1154{
1155 NS_LOG_FUNCTION(this << *mpdu);
1156
1157 const auto& hdr = mpdu->GetHeader();
1158
1159 if (hdr.IsAssocReq())
1160 {
1161 // store padding delay and transition delay advertised in AssocReq
1162 MgtAssocRequestHeader assocReq;
1163 mpdu->GetPacket()->PeekHeader(assocReq);
1164 auto& mle = assocReq.Get<MultiLinkElement>();
1165 NS_ASSERT_MSG(mle, "AssocReq should contain a Multi-Link Element");
1166 m_lastAdvPaddingDelay = mle->GetEmlsrPaddingDelay();
1167 m_lastAdvTransitionDelay = mle->GetEmlsrTransitionDelay();
1168 }
1169
1170 if (hdr.IsMgt() && hdr.IsAction())
1171 {
1172 if (auto [category, action] = WifiActionHeader::Peek(mpdu->GetPacket());
1173 category == WifiActionHeader::PROTECTED_EHT &&
1174 action.protectedEhtAction ==
1175 WifiActionHeader::PROTECTED_EHT_EML_OPERATING_MODE_NOTIFICATION)
1176 {
1177 // the EML Operating Mode Notification frame that we sent has been acknowledged.
1178 // Start the transition timeout to wait until the request can be made effective
1179 NS_ASSERT_MSG(m_emlsrTransitionTimeout, "No transition timeout received from AP");
1180 m_transitionTimeoutEvent = Simulator::Schedule(*m_emlsrTransitionTimeout,
1181 &EmlsrManager::ChangeEmlsrMode,
1182 this);
1183 }
1184 }
1185}
1186
1187void
1188EmlsrManager::TxDropped(WifiMacDropReason reason, Ptr<const WifiMpdu> mpdu)
1189{
1190 NS_LOG_FUNCTION(this << reason << *mpdu);
1191
1192 const auto& hdr = mpdu->GetHeader();
1193
1194 if (hdr.IsMgt() && hdr.IsAction())
1195 {
1196 auto pkt = mpdu->GetPacket()->Copy();
1197 if (auto [category, action] = WifiActionHeader::Remove(pkt);
1198 category == WifiActionHeader::PROTECTED_EHT &&
1199 action.protectedEhtAction ==
1200 WifiActionHeader::PROTECTED_EHT_EML_OPERATING_MODE_NOTIFICATION)
1201 {
1202 // the EML Operating Mode Notification frame has been dropped. Ask the subclass
1203 // whether the frame needs to be resent
1204 auto linkId = ResendNotification(mpdu);
1205 if (linkId)
1206 {
1207 MgtEmlOmn frame;
1208 pkt->RemoveHeader(frame);
1209 GetEhtFem(*linkId)->SendEmlOmn(m_staMac->GetBssid(*linkId), frame);
1210 }
1211 else
1212 {
1213 m_nextEmlsrLinks.reset();
1214 }
1215 }
1216 }
1217}
1218
1219void
1220EmlsrManager::ChangeEmlsrMode()
1221{
1222 NS_LOG_FUNCTION(this);
1223
1224 // After the successful transmission of the EML Operating Mode Notification frame by the
1225 // non-AP STA affiliated with the non-AP MLD, the non-AP MLD shall operate in the EMLSR mode
1226 // and the other non-AP STAs operating on the corresponding EMLSR links shall transition to
1227 // active mode after the transition delay indicated in the Transition Timeout subfield in the
1228 // EML Capabilities subfield of the Basic Multi-Link element or immediately after receiving an
1229 // EML Operating Mode Notification frame from one of the APs operating on the EMLSR links and
1230 // affiliated with the AP MLD. (Sec. 35.3.17 of 802.11be D3.0)
1231 NS_ASSERT_MSG(m_nextEmlsrLinks, "No set of EMLSR links stored");
1232 m_emlsrLinks.swap(*m_nextEmlsrLinks);
1233 m_nextEmlsrLinks.reset();
1234
1235 // Make other non-AP STAs operating on the corresponding EMLSR links transition to
1236 // active mode or passive mode (depending on whether EMLSR mode has been enabled or disabled)
1237 m_staMac->NotifyEmlsrModeChanged(m_emlsrLinks);
1238 // Enforce the limit on the max channel width supported by aux PHYs
1239 ApplyMaxChannelWidthAndModClassOnAuxPhys();
1240
1241 NotifyEmlsrModeChanged();
1242}
1243
1244void
1245EmlsrManager::ApplyMaxChannelWidthAndModClassOnAuxPhys()
1246{
1247 NS_LOG_FUNCTION(this);
1248 auto currMainPhyLinkId = m_staMac->GetLinkForPhy(m_mainPhyId);
1249 NS_ASSERT(currMainPhyLinkId);
1250
1251 for (const auto linkId : m_staMac->GetLinkIds())
1252 {
1253 auto auxPhy = m_staMac->GetWifiPhy(linkId);
1254 auto channel = GetChannelForAuxPhy(linkId);
1255
1256 if (linkId == currMainPhyLinkId || !m_staMac->IsEmlsrLink(linkId) ||
1257 auxPhy->GetOperatingChannel() == channel)
1258 {
1259 continue;
1260 }
1261
1262 auxPhy->SetMaxModulationClassSupported(m_auxPhyMaxModClass);
1263
1264 NS_LOG_DEBUG("Aux PHY (" << auxPhy << ") is about to switch to " << channel
1265 << " to operate on link " << +linkId);
1266 // We cannot simply set the new channel, because otherwise the MAC will disable
1267 // the setup link. We need to inform the MAC (via the Channel Access Manager) that
1268 // this channel switch must not have such a consequence. We already have a method
1269 // for doing so, i.e., inform the MAC that the PHY is switching channel to operate
1270 // on the "same" link.
1271 auto cam = m_staMac->GetChannelAccessManager(linkId);
1272 cam->NotifySwitchingEmlsrLink(auxPhy, channel, linkId);
1273
1274 // apply channel width limitation assuming an instantaneous channel switch
1275 const auto delay = auxPhy->GetChannelSwitchDelay();
1276 auxPhy->SetAttribute("ChannelSwitchDelay", TimeValue(Time{0}));
1277 auxPhy->SetOperatingChannel(channel);
1278 auxPhy->SetAttribute("ChannelSwitchDelay", TimeValue(delay));
1279
1280 // the way the ChannelAccessManager handles EMLSR link switch implies that a PHY listener
1281 // is removed when the channel switch starts and another one is attached when the channel
1282 // switch ends. In the meantime, no PHY is connected to the ChannelAccessManager. Thus,
1283 // reset all backoffs (so that access timeout is also cancelled) when the channel switch
1284 // starts and request channel access (if needed) when the channel switch ends.
1285 cam->ResetAllBackoffs();
1286 for (const auto& [acIndex, ac] : wifiAcList)
1287 {
1288 m_staMac->GetQosTxop(acIndex)->StartAccessAfterEvent(
1289 linkId,
1290 Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT,
1291 Txop::CHECK_MEDIUM_BUSY);
1292 }
1293 }
1294}
1295
1296void
1297EmlsrManager::ComputeOperatingChannels()
1298{
1299 NS_LOG_FUNCTION(this);
1300
1301 m_mainPhyChannels.clear();
1302 m_auxPhyChannels.clear();
1303
1304 auto linkIds = m_staMac->GetSetupLinkIds();
1305
1306 for (auto linkId : linkIds)
1307 {
1308 const auto& channel = m_staMac->GetWifiPhy(linkId)->GetOperatingChannel();
1309 m_mainPhyChannels.emplace(linkId, channel);
1310
1311 auto mainPhyChWidth = channel.GetWidth();
1312 auto auxPhyMaxWidth =
1313 std::min(m_auxPhyMaxWidth, GetMaximumChannelWidth(m_auxPhyMaxModClass));
1314 if (auxPhyMaxWidth >= mainPhyChWidth)
1315 {
1316 // same channel can be used by aux PHYs
1317 m_auxPhyChannels.emplace(linkId, channel);
1318 continue;
1319 }
1320 // aux PHYs will operate on a primary subchannel
1321 auto freq = channel.GetPrimaryChannelCenterFrequency(auxPhyMaxWidth);
1322 auto chIt = WifiPhyOperatingChannel::FindFirst(0,
1323 freq,
1324 auxPhyMaxWidth,
1326 channel.GetPhyBand());
1327 NS_ASSERT_MSG(chIt != WifiPhyOperatingChannel::m_frequencyChannels.end(),
1328 "Primary" << auxPhyMaxWidth << " channel not found");
1329 m_auxPhyChannels.emplace(linkId, chIt);
1330 // find the P20 index for the channel used by the aux PHYs
1331 auto p20Index = channel.GetPrimaryChannelIndex(MHz_u{20});
1332 while (mainPhyChWidth > auxPhyMaxWidth)
1333 {
1334 mainPhyChWidth /= 2;
1335 p20Index /= 2;
1336 }
1337 m_auxPhyChannels[linkId].SetPrimary20Index(p20Index);
1338 }
1339}
1340
1342EmlsrManager::GetChannelForMainPhy(uint8_t linkId) const
1343{
1344 auto it = m_mainPhyChannels.find(linkId);
1345 NS_ASSERT_MSG(it != m_mainPhyChannels.end(),
1346 "Channel for main PHY on link ID " << +linkId << " not found");
1347 return it->second;
1348}
1349
1351EmlsrManager::GetChannelForAuxPhy(uint8_t linkId) const
1352{
1353 auto it = m_auxPhyChannels.find(linkId);
1354 NS_ASSERT_MSG(it != m_auxPhyChannels.end(),
1355 "Channel for aux PHY on link ID " << +linkId << " not found");
1356 return it->second;
1357}
1358
1359void
1360EmlsrManager::CancelAllSleepEvents()
1361{
1362 NS_LOG_FUNCTION(this);
1363
1364 for (auto& [id, event] : m_auxPhyToSleepEvents)
1365 {
1366 event.Cancel();
1367 }
1368 m_auxPhyToSleepEvents.clear();
1369}
1370
1371void
1372EmlsrManager::SetSleepStateForAllAuxPhys(bool sleep)
1373{
1374 NS_LOG_FUNCTION(this << sleep);
1375
1376 CancelAllSleepEvents();
1377
1378 for (const auto& phy : m_staMac->GetDevice()->GetPhys())
1379 {
1380 if (phy->GetPhyId() == m_mainPhyId)
1381 {
1382 continue; // do not set sleep mode/resume from sleep the main PHY
1383 }
1384
1385 auto linkId = m_staMac->GetLinkForPhy(phy);
1386
1387 if (linkId.has_value() && !m_staMac->IsEmlsrLink(*linkId))
1388 {
1389 continue; // this PHY is not operating on an EMLSR link
1390 }
1391
1392 if (!sleep)
1393 {
1394 if (!phy->IsStateSleep())
1395 {
1396 continue; // nothing to do
1397 }
1398
1399 NS_LOG_DEBUG("PHY " << +phy->GetPhyId() << ": Resuming from sleep");
1400 phy->ResumeFromSleep();
1401
1402 // if this aux PHY is operating on a link, check if it lost medium sync
1403 if (linkId.has_value())
1404 {
1405 auto it = m_startSleep.find(phy->GetPhyId());
1406 NS_ASSERT_MSG(it != m_startSleep.cend(),
1407 "No start sleep info for PHY ID " << phy->GetPhyId());
1408 const auto sleepDuration = Simulator::Now() - it->second;
1409 m_startSleep.erase(it);
1410
1411 if (sleepDuration > MicroSeconds(MEDIUM_SYNC_THRESHOLD_USEC))
1412 {
1413 StartMediumSyncDelayTimer(*linkId);
1414 }
1415 }
1416
1417 continue; // resuming the PHY from sleep has been handled
1418 }
1419
1420 if (phy->IsStateSleep())
1421 {
1422 continue; // nothing to do
1423 }
1424
1425 // we force WifiPhy::SetSleepMode() to abort RX and switch immediately to sleep mode in
1426 // case the state is RX. If the state is TX or SWITCHING, WifiPhy::SetSleepMode() postpones
1427 // setting sleep mode to end of TX or SWITCHING. This is fine, but we schedule events here
1428 // to correctly record the time an aux PHY was put to sleep and to be able to cancel them
1429 // later if needed
1430 std::stringstream ss;
1431 auto s = std::string("PHY ") + std::to_string(phy->GetPhyId()) + ": Setting sleep mode";
1432 if (phy->IsStateTx() || phy->IsStateSwitching())
1433 {
1434 const auto delay = phy->GetDelayUntilIdle();
1435 NS_LOG_DEBUG(s << " in " << delay.As(Time::US));
1436 m_auxPhyToSleepEvents[phy->GetPhyId()] = Simulator::Schedule(delay, [=, this]() {
1437 phy->SetSleepMode(true);
1438 m_startSleep[phy->GetPhyId()] = Simulator::Now();
1439 });
1440 }
1441 else
1442 {
1443 NS_LOG_DEBUG(s);
1444 phy->SetSleepMode(true);
1445 m_startSleep[phy->GetPhyId()] = Simulator::Now();
1446 }
1447 }
1448}
1449
1450} // namespace ns3
A container for one type of attribute.
AttributeValue implementation for Boolean.
Definition boolean.h:26
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:561
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:403
@ US
microsecond
Definition nstime.h:107
AttributeValue implementation for Time.
Definition nstime.h:1432
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.
Ptr< const AttributeChecker > MakeBooleanChecker()
Definition boolean.cc:113
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition boolean.h:70
Ptr< const AttributeAccessor > MakeEnumAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition enum.h:221
Ptr< const AttributeAccessor > MakeTimeAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition nstime.h:1433
Ptr< const AttributeChecker > MakeTimeChecker()
Helper to make an unbounded Time checker.
Definition nstime.h:1453
Ptr< const AttributeChecker > MakeUintegerChecker()
Definition uinteger.h:85
Ptr< const AttributeAccessor > MakeUintegerAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition uinteger.h:35
#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:1369
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1345
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.
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.
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 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
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)
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.