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.",
63 TypeId::ATTR_CONSTRUCT, // prevent setting after construction
64 UintegerValue(20),
67 .AddAttribute("AuxPhyMaxModClass",
68 "The maximum modulation class supported by Aux PHYs. Use "
69 "WIFI_MOD_CLASS_OFDM for non-HT.",
71 TypeId::ATTR_CONSTRUCT, // prevent setting after construction
75 "HR-DSSS",
77 "ERP-OFDM",
79 "OFDM",
81 "HT",
83 "VHT",
85 "HE",
87 "EHT"))
88 .AddAttribute("AuxPhyTxCapable",
89 "Whether Aux PHYs are capable of transmitting PPDUs.",
90 BooleanValue(true),
94 .AddAttribute("InDeviceInterference",
95 "Whether in-device interference is such that a PHY cannot decode "
96 "anything and cannot decrease the backoff counter when another PHY "
97 "of the same device is transmitting.",
98 BooleanValue(false),
102 .AddAttribute("PutAuxPhyToSleep",
103 "Whether Aux PHYs should be put into sleep mode while the Main PHY "
104 "is carrying out a (DL or UL) TXOP. Specifically, for DL TXOPs, aux "
105 "PHYs are put to sleep after receiving the ICF; for UL TXOPs, aux PHYs "
106 "are put to sleep when the CTS frame is received, if RTS/CTS is used, "
107 "or when the transmission of the data frame starts, otherwise. "
108 "Aux PHYs are resumed from sleep when the TXOP ends.",
109 BooleanValue(false),
112 .AddAttribute("UseNotifiedMacHdr",
113 "Whether to use the information about the MAC header of the MPDU "
114 "being received, if notified by the PHY.",
115 BooleanValue(true),
118 .AddAttribute(
119 "EmlsrLinkSet",
120 "IDs of the links on which EMLSR mode will be enabled. An empty set "
121 "indicates to disable EMLSR.",
125 .AddAttribute("ResetCamState",
126 "Whether to reset the state of the ChannelAccessManager associated with "
127 "the link on which the main PHY has just switched to.",
128 BooleanValue(false),
132 .AddTraceSource("MainPhySwitch",
133 "This trace source is fired when the main PHY switches channel to "
134 "operate on another link. Information associated with the main PHY "
135 "switch is provided through a struct that is inherited from struct "
136 "EmlsrMainPhySwitchTrace (use the GetName() method to get the type "
137 "of the provided object).",
139 "ns3::EmlsrManager::MainPhySwitchCallback");
140 return tid;
141}
142
144 // The STA initializes dot11MSDTimerDuration to aPPDUMaxTime defined in Table 36-70
145 // (Sec. 35.3.16.8.1 of 802.11be D3.1)
146 : m_mediumSyncDuration(MicroSeconds(DEFAULT_MSD_DURATION_USEC)),
147 // The default value of dot11MSDOFDMEDthreshold is –72 dBm and the default value of
148 // dot11MSDTXOPMax is 1, respectively (Sec. 35.3.16.8.1 of 802.11be D3.1)
149 m_msdOfdmEdThreshold(DEFAULT_MSD_OFDM_ED_THRESH),
150 m_msdMaxNTxops(DEFAULT_MSD_MAX_N_TXOPS)
151{
152 NS_LOG_FUNCTION(this);
153}
154
159
160void
162{
163 NS_LOG_FUNCTION(this);
164 m_staMac->TraceDisconnectWithoutContext("AckedMpdu", MakeCallback(&EmlsrManager::TxOk, this));
165 m_staMac->TraceDisconnectWithoutContext("DroppedMpdu",
167 m_staMac = nullptr;
169 for (auto& [id, status] : m_mediumSyncDelayStatus)
170 {
171 status.timer.Cancel();
172 }
174}
175
176void
178{
179 NS_LOG_FUNCTION(this << mac);
180 NS_ASSERT(mac);
181 m_staMac = mac;
182
183 NS_ABORT_MSG_IF(!m_staMac->GetEhtConfiguration(), "EmlsrManager requires EHT support");
184 NS_ABORT_MSG_IF(!m_staMac->GetEhtConfiguration()->m_emlsrActivated,
185 "EmlsrManager requires EMLSR to be activated");
186 NS_ABORT_MSG_IF(m_staMac->GetTypeOfStation() != STA,
187 "EmlsrManager can only be installed on non-AP MLDs");
188 NS_ABORT_MSG_IF(m_mainPhyId >= m_staMac->GetDevice()->GetNPhys(),
189 "Main PHY ID (" << +m_mainPhyId << ") invalid given the number of PHY objects ("
190 << +m_staMac->GetDevice()->GetNPhys() << ")");
191
192 m_staMac->TraceConnectWithoutContext("AckedMpdu", MakeCallback(&EmlsrManager::TxOk, this));
193 m_staMac->TraceConnectWithoutContext("DroppedMpdu",
195 m_staMac->TraceConnectWithoutContext(
196 "EmlsrLinkSwitch",
198 DoSetWifiMac(mac);
199}
200
201void
206
207void
208EmlsrManager::EmlsrLinkSwitchCallback(uint8_t linkId, Ptr<WifiPhy> phy, bool connected)
209{
210 NS_LOG_FUNCTION(this << linkId << phy << connected);
211
212 // TODO the ScheduleNow calls in this function can be removed once we get rid of the
213 // instantaneous main PHY switch at the end of ICF reception
214
215 if (!connected)
216 {
217 NS_ASSERT_MSG(phy->GetPhyId() == m_mainPhyId,
218 "Main PHY only is expected to leave a link on which it is operating");
219 NS_LOG_DEBUG("Record that no PHY is operating on link " << +linkId);
220
221 if (phy->GetChannelSwitchDelay().IsZero())
222 {
223 // a PHY that was operating on a link has left the link and the channel switch delay is
224 // zero; this must be an instantaneous switch of the main PHY at the end of ICF
225 // reception. ScheduleNow the setting of m_noPhySince to get the "real" channel switch
226 // delay, so that the actual time the main PHY started switching can be determined. Due
227 // to this scheduling, all other actions must be scheduled now, too
228 Simulator::ScheduleNow([=, this]() {
229 NS_ASSERT(!m_noPhySince.contains(linkId));
230 m_noPhySince[linkId] = Simulator::Now() - phy->GetChannelSwitchDelay();
231 });
232 }
233 else
234 {
235 Simulator::ScheduleNow([=, this]() {
236 NS_ASSERT(!m_noPhySince.contains(linkId));
237 m_noPhySince[linkId] = Simulator::Now();
238 });
239 }
240 return;
241 }
242
244
245 if (phy->GetPhyId() == m_mainPhyId)
246 {
247 // main PHY has been connected to a link
249 }
250
251 Simulator::ScheduleNow([=, this]() {
252 // phy switched to operate on the link with ID equal to linkId
253 auto it = m_noPhySince.find(linkId);
254
255 if (it == m_noPhySince.end())
256 {
257 // phy switched to a link on which another PHY was operating, do nothing
258 return;
259 }
260
261 auto duration = Simulator::Now() - it->second;
262 NS_ASSERT_MSG(duration.IsPositive(), "Interval duration should not be negative");
263
264 NS_LOG_DEBUG("PHY " << +phy->GetPhyId() << " switched to link " << +linkId << " after "
265 << duration.As(Time::US)
266 << " since last time a PHY was operating on this link");
268 {
270 }
271
272 m_noPhySince.erase(it);
273 });
274}
275
276void
278{
279 NS_LOG_FUNCTION(this << mainPhyId);
280 NS_ABORT_MSG_IF(IsInitialized(), "Cannot be called once this object has been initialized");
281 m_mainPhyId = mainPhyId;
282}
283
284uint8_t
286{
287 return m_mainPhyId;
288}
289
290void
292{
293 m_resetCamState = enable;
294}
295
296bool
301
302void
304{
305 m_auxPhyTxCapable = capable;
306}
307
308bool
313
314void
319
320bool
325
326const std::set<uint8_t>&
328{
329 return m_emlsrLinks;
330}
331
334{
335 return m_staMac;
336}
337
339EmlsrManager::GetEhtFem(uint8_t linkId) const
340{
341 return StaticCast<EhtFrameExchangeManager>(m_staMac->GetFrameExchangeManager(linkId));
342}
343
344std::optional<Time>
346{
347 if (const auto statusIt = m_mediumSyncDelayStatus.find(linkId);
348 statusIt != m_mediumSyncDelayStatus.cend() && statusIt->second.timer.IsPending())
349 {
350 return m_mediumSyncDuration - Simulator::GetDelayLeft(statusIt->second.timer);
351 }
352 return std::nullopt;
353}
354
355void
361
362std::optional<Time>
367
368void
370{
371 NS_LOG_FUNCTION(this << duration.As(Time::US));
372 m_mediumSyncDuration = duration;
373}
374
375Time
380
381void
383{
384 NS_LOG_FUNCTION(this << threshold);
385 m_msdOfdmEdThreshold = threshold;
386}
387
388int8_t
393
394void
395EmlsrManager::SetMediumSyncMaxNTxops(std::optional<uint8_t> nTxops)
396{
397 NS_LOG_FUNCTION(this << nTxops.has_value());
398 m_msdMaxNTxops = nTxops;
399}
400
401std::optional<uint8_t>
406
407void
408EmlsrManager::SetEmlsrLinks(const std::set<uint8_t>& linkIds)
409{
410 std::stringstream ss;
411 if (g_log.IsEnabled(ns3::LOG_FUNCTION))
412 {
413 std::copy(linkIds.cbegin(), linkIds.cend(), std::ostream_iterator<uint16_t>(ss, " "));
414 }
415 NS_LOG_FUNCTION(this << ss.str());
416
417 if (linkIds != m_emlsrLinks)
418 {
419 m_nextEmlsrLinks = linkIds;
420 }
421
422 if (GetStaMac() && GetStaMac()->IsAssociated() && GetTransitionTimeout() && m_nextEmlsrLinks)
423 {
424 // Request to enable EMLSR mode on the given links, provided that they have been setup
425 SendEmlOmn();
426 }
427}
428
429void
431{
432 NS_LOG_FUNCTION(this << *mpdu << linkId);
433
434 const auto& hdr = mpdu->GetHeader();
435
436 DoNotifyMgtFrameReceived(mpdu, linkId);
437
438 if (hdr.IsAssocResp() && GetStaMac()->IsAssociated() && GetTransitionTimeout())
439 {
440 // we just completed ML setup with an AP MLD that supports EMLSR
442
443 if (m_nextEmlsrLinks && !m_nextEmlsrLinks->empty())
444 {
445 // a non-empty set of EMLSR links have been configured, hence enable EMLSR mode
446 // on those links
447 SendEmlOmn();
448 }
449 }
450
451 if (hdr.IsAction() && hdr.GetAddr2() == m_staMac->GetBssid(linkId))
452 {
453 // this is an action frame sent by an AP of the AP MLD we are associated with
454 auto [category, action] = WifiActionHeader::Peek(mpdu->GetPacket());
455 if (category == WifiActionHeader::PROTECTED_EHT &&
456 action.protectedEhtAction ==
458 {
460 {
461 // no need to wait until the expiration of the transition timeout
464 }
465 }
466 }
467}
468
469void
471{
472 NS_LOG_FUNCTION(this << linkId);
473
474 NS_ASSERT(m_staMac->IsEmlsrLink(linkId));
475
476 // block transmissions and suspend medium access on all other EMLSR links
477 for (auto id : m_staMac->GetLinkIds())
478 {
479 if (id != linkId && m_staMac->IsEmlsrLink(id))
480 {
482 }
483 }
484
485 auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId);
486 auto rxPhy = m_staMac->GetWifiPhy(linkId);
487
488 const auto receivedByAuxPhy = (rxPhy != mainPhy);
489 const auto mainPhyOnALink = (m_staMac->GetLinkForPhy(mainPhy).has_value());
490 const auto mainPhyIsSwitching =
491 (mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}) == Simulator::Now());
492 // if the main PHY is not operating on a link and it is not switching, then we have postponed
493 // the reconnection of the main PHY to a link because a PPDU reception was ongoing on that link
494 const auto mainPhyToConnect = (!mainPhyOnALink && !mainPhyIsSwitching);
495
496 const auto mainPhyToConnectToOtherLink = mainPhyToConnect && (m_mainPhySwitchInfo.to != linkId);
497
498 if (mainPhyToConnect)
499 {
500 // If ICF was received on a link other than the one the main PHY is waiting to be connected
501 // to, we need to cancel the pending reconnection and request a new main PHY switch.
502 // If ICF was received on the link the main PHY is waiting to be connected to, we cancel
503 // the pending reconnection and explicitly request the reconnection below
504 GetStaMac()->CancelEmlsrPhyConnectEvent(mainPhy->GetPhyId());
505 }
506
507 // We need to request a main PHY switch if:
508 // - the ICF was received by an aux PHY, AND
509 // - the main PHY is not waiting to be connected to a link OR it is waiting to be connected
510 // to a link other than the link on which the ICF is received.
511 if (receivedByAuxPhy && (!mainPhyToConnect || mainPhyToConnectToOtherLink))
512 {
513 SwitchMainPhy(linkId,
514 true, // channel switch should occur instantaneously
517 }
518 else if (mainPhyToConnect && !mainPhyToConnectToOtherLink)
519 {
520 // If the main PHY is waiting to be connected to the link on which the ICF was received, we
521 // have to explicitly perform the connection because the pending event was cancelled above.
522 // We do this way in order to reconnect the main PHY before putting aux PHYs to sleep: if
523 // the aux PHY is put to sleep while still operating on the link on which it received the
524 // ICF, all the MAC events (including scheduled CTS transmission) will be cancelled.
525 m_staMac->NotifySwitchingEmlsrLink(mainPhy, linkId, Time{0});
526 }
527
528 if (receivedByAuxPhy)
529 {
530 // aux PHY received the ICF but main PHY will send the response
531 auto uid = rxPhy->GetPreviouslyRxPpduUid();
532 mainPhy->SetPreviouslyRxPpduUid(uid);
533 }
534
535 // a DL TXOP started, set all aux PHYs to sleep
536 if (m_auxPhyToSleep)
537 {
539 }
540
541 DoNotifyDlTxopStart(linkId);
542}
543
544std::pair<bool, Time>
546{
547 auto phy = m_staMac->GetWifiPhy(linkId);
548 NS_ASSERT_MSG(phy, "No PHY operating on link " << +linkId);
549
550 auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId);
551
552 // check possible reasons to give up the TXOP that apply to both main PHY and aux PHYs
553 if (const auto [startTxop, delay] = DoGetDelayUntilAccessRequest(linkId); !startTxop)
554 {
555 return {false, delay};
556 }
557
558 if (phy == mainPhy)
559 {
560 // no more constraints to check if medium was gained by main PHY
561 return {true, Time{0}};
562 }
563
564 // an aux PHY is operating on the given link; call the appropriate method depending on
565 // whether the aux PHY is TX capable or not
567 {
569 // if the aux PHY is not TX capable, we don't have to request channel access: if the main
570 // PHY switches link, the UL TXOP will be started; if the main PHY does not switch, it is
571 // because it is going to start an UL TXOP on another link and this link will be restarted
572 // at the end of that UL TXOP when this link will be unblocked
573 NS_LOG_DEBUG("Aux PHY is not capable of transmitting a PPDU");
574 return {false, Time{0}};
575 }
576
578}
579
580std::pair<bool, Time>
582{
583 NS_LOG_FUNCTION(this << linkId);
584
585 auto phy = GetStaMac()->GetWifiPhy(linkId);
586
587 if (!phy || !GetStaMac()->IsEmlsrLink(linkId))
588 {
589 NS_LOG_DEBUG("No PHY (" << phy << ") or not an EMLSR link (" << +linkId << ")");
590 return {false, Time{0}};
591 }
592
593 if (auto endPreamble = phy->GetTimeToPreambleDetectionEnd())
594 {
595 NS_LOG_DEBUG("Detecting a PPDU preamble, postpone by " << endPreamble->As(Time::US));
596 return {true, endPreamble.value()};
597 }
598 else if (auto macHdr = GetEhtFem(linkId)->GetReceivedMacHdr(); macHdr && m_useNotifiedMacHdr)
599 {
600 NS_LOG_DEBUG("Receiving the MAC payload of a PSDU and MAC header info can be used");
601 NS_ASSERT(phy->GetState()->GetLastTime({WifiPhyState::RX}) == Simulator::Now());
602
603 if (const auto& hdr = macHdr->get();
604 hdr.IsTrigger() &&
605 (hdr.GetAddr1().IsBroadcast() || hdr.GetAddr1() == GetEhtFem(linkId)->GetAddress()))
606 {
607 // the PSDU being received _may_ be an ICF. Note that we cannot be sure that the PSDU
608 // being received is an ICF addressed to us until we receive the entire PSDU
609 NS_LOG_DEBUG("Based on MAC header, may be an ICF, postpone by "
610 << phy->GetDelayUntilIdle().As(Time::US));
611 return {true, phy->GetDelayUntilIdle()};
612 }
613 }
614 else if (auto txVector = phy->GetInfoIfRxingPhyHeader())
615 {
616 NS_LOG_DEBUG("Receiving PHY header");
617 if (txVector->get().GetModulationClass() < WIFI_MOD_CLASS_HT)
618 {
619 // the PHY header of a non-HT PPDU, which may be an ICF, is being received; check
620 // again after the TX duration of a non-HT PHY header
621 NS_LOG_DEBUG("PHY header of a non-HT PPDU, which may be an ICF, is being received");
622 return {true, EMLSR_RX_PHY_START_DELAY};
623 }
624 }
625 else if (phy->IsStateRx())
626 {
627 // we have not yet received the MAC header or we cannot use its info
628
629 // even if a PHY is in RX state, it may not have info about received MAC header.
630 // This may happen, e.g., when:
631 // - an aux PHY is disconnected from the MAC stack because the main PHY is
632 // operating on its link
633 // - the main PHY notifies the MAC header info to the FEM and then leaves the
634 // link (e.g., because it recognizes that the MPDU is not addressed to the
635 // EMLSR client). Disconnecting the main PHY from the MAC stack causes the
636 // MAC header info to be discarded by the FEM
637 // - the aux PHY is re-connected to the MAC stack and is still in RX state
638 // when the main PHY gets channel access on another link (and we get here)
639 if (auto ongoingRxInfo = GetEhtFem(linkId)->GetOngoingRxInfo();
640 ongoingRxInfo.has_value() &&
641 ongoingRxInfo->get().txVector.GetModulationClass() < WIFI_MOD_CLASS_HT)
642 {
643 if (auto remTime = phy->GetTimeToMacHdrEnd(SU_STA_ID);
644 m_useNotifiedMacHdr && remTime.has_value() && remTime->IsStrictlyPositive())
645 {
646 NS_LOG_DEBUG("Wait until the expected end of the MAC header reception: "
647 << remTime->As(Time::US));
648 return {true, *remTime};
649 }
650
652 "MAC header info will not be available. Wait until the end of PSDU reception: "
653 << phy->GetDelayUntilIdle().As(Time::US));
654 return {true, phy->GetDelayUntilIdle()};
655 }
656 }
657
658 NS_LOG_DEBUG("No ICF being received, state: " << phy->GetState()->GetState());
659 return {false, Time{0}};
660}
661
662std::optional<WifiIcfDrop>
664{
665 auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId);
666
667 const auto delay = mainPhy->GetChannelSwitchDelay();
668 auto lastTime = mainPhy->GetState()->GetLastTime({WifiPhyState::TX});
670
671 if (auto lastSwitch = mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING});
672 m_mainPhySwitchInfo.to != linkId && lastSwitch > lastTime)
673 {
674 lastTime = lastSwitch;
676 }
677 if (auto lastSleep = mainPhy->GetState()->GetLastTime({WifiPhyState::SLEEP});
678 lastSleep > lastTime)
679 {
680 lastTime = lastSleep;
682 }
683 // ignore RX state for now
684
685 if (lastTime > Simulator::Now() - delay)
686 {
687 NS_LOG_DEBUG("Not enough time for the main PHY to switch link; reason = "
688 << reason << " lastTime = " << lastTime.As(Time::US));
689 return reason;
690 }
691 return std::nullopt;
692}
693
694void
696{
697 NS_LOG_FUNCTION(this << linkId);
698
699 if (!m_staMac->IsEmlsrLink(linkId))
700 {
701 NS_LOG_DEBUG("EMLSR is not enabled on link " << +linkId);
702 return;
703 }
704
705 // block transmissions and suspend medium access on all other EMLSR links
706 for (auto id : m_staMac->GetLinkIds())
707 {
708 if (id != linkId && m_staMac->IsEmlsrLink(id))
709 {
711 }
712 }
713
714 DoNotifyUlTxopStart(linkId);
715}
716
717void
719{
720 NS_LOG_FUNCTION(this << *rts << txVector);
721}
722
723void
725{
726 NS_LOG_FUNCTION(this << linkId);
727
728 if (m_auxPhyToSleep && m_staMac->IsEmlsrLink(linkId))
729 {
730 // if main PHY is switching (or has been scheduled to switch) to this link to take over the
731 // UL TXOP, postpone aux PHY sleeping until after the main PHY has completed switching
732 Time delay{0};
733 if (auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId); mainPhy->IsStateSwitching())
734 {
735 delay = mainPhy->GetDelayUntilIdle();
736 }
737 else if (auto it = m_ulMainPhySwitch.find(linkId);
738 it != m_ulMainPhySwitch.end() && it->second.IsPending())
739 {
740 delay = Simulator::GetDelayLeft(it->second) + mainPhy->GetChannelSwitchDelay();
741 }
742
743 if (delay.IsStrictlyPositive())
744 {
745 Simulator::Schedule(delay + TimeStep(1),
747 this,
748 true);
749 }
750 else
751 {
752 // put aux PHYs to sleep
754 }
755 }
756
758}
759
760void
762{
763 NS_LOG_FUNCTION(this << linkId << edca);
764
765 if (!m_staMac->IsEmlsrLink(linkId))
766 {
767 NS_LOG_DEBUG("EMLSR is not enabled on link " << +linkId);
768 return;
769 }
770
771 // If the main PHY has been scheduled to switch to this link, cancel the channel switch.
772 // This happens, e.g., when an aux PHY sent an RTS to start an UL TXOP but it did not
773 // receive a CTS response.
774 if (auto it = m_ulMainPhySwitch.find(linkId); it != m_ulMainPhySwitch.end())
775 {
776 if (it->second.IsPending())
777 {
778 NS_LOG_DEBUG("Cancelling main PHY channel switch event on link " << +linkId);
779 it->second.Cancel();
780 }
781 m_ulMainPhySwitch.erase(it);
782 }
783
784 // Unblock the other EMLSR links and start the MediumSyncDelay timer, provided that the TXOP
785 // included the transmission of at least a frame and there is no ongoing DL TXOP on this link.
786 // Indeed, the UL TXOP may have ended because the transmission of a frame failed and the
787 // corresponding TX timeout (leading to this call) may have occurred after the reception on
788 // this link of an ICF starting a DL TXOP. If the EMLSR Manager unblocked the other EMLSR
789 // links, another TXOP could be started on another EMLSR link (possibly leading to a crash)
790 // while the DL TXOP on this link is ongoing.
791 if (GetEhtFem(linkId)->GetOngoingTxopEndEvent().IsPending())
792 {
793 NS_LOG_DEBUG("DL TXOP ongoing");
794 return;
795 }
796 if (edca)
797 {
798 if (auto txopStart = edca->GetTxopStartTime(linkId);
799 !txopStart || *txopStart == Simulator::Now())
800 {
801 NS_LOG_DEBUG("UL TXOP did not even start");
802 return;
803 }
804 }
805
806 if (m_auxPhyToSleep)
807 {
808 // TXOP ended, resume all aux PHYs from sleep
810 }
811
812 DoNotifyTxopEnd(linkId, edca);
813
814 // unblock transmissions and resume medium access on other EMLSR links
815 std::set<uint8_t> linkIds;
816 for (auto id : m_staMac->GetLinkIds())
817 {
818 if ((id != linkId) && m_staMac->IsEmlsrLink(id))
819 {
820 linkIds.insert(id);
821 }
822 }
824}
825
826void
828{
829 NS_LOG_FUNCTION(this << linkId << duration.As(Time::US));
831
832 // The STA may choose not to (re)start the MediumSyncDelay timer if the transmission duration
833 // is less than or equal to aMediumSyncThreshold. (Sec. 35.3.16.8.1 802.11be D5.1)
835 {
836 return;
837 }
838
839 // iterate over all the other EMLSR links
840 for (auto id : m_staMac->GetLinkIds())
841 {
842 if (id != linkId && m_staMac->IsEmlsrLink(id))
843 {
845 }
846 }
847}
848
849void
851{
852 NS_LOG_FUNCTION(this << phy << linkId);
853
854 // if a MediumSyncDelay timer is running for the link on which the main PHY is going to
855 // operate, set the CCA ED threshold to the MediumSyncDelay OFDM ED threshold
856 if (auto statusIt = m_mediumSyncDelayStatus.find(linkId);
857 statusIt != m_mediumSyncDelayStatus.cend() && statusIt->second.timer.IsPending())
858 {
859 NS_LOG_DEBUG("Setting CCA ED threshold of PHY " << +phy->GetPhyId() << " to "
860 << +m_msdOfdmEdThreshold << " on link "
861 << +linkId);
862
863 // store the current CCA ED threshold in the m_prevCcaEdThreshold map, if not present
864 m_prevCcaEdThreshold.try_emplace(phy, phy->GetCcaEdThreshold());
865
866 phy->SetCcaEdThreshold(m_msdOfdmEdThreshold);
867 }
868 // otherwise, restore the previous value for the CCA ED threshold (if any)
869 else if (auto threshIt = m_prevCcaEdThreshold.find(phy);
870 threshIt != m_prevCcaEdThreshold.cend())
871 {
872 NS_LOG_DEBUG("Resetting CCA ED threshold of PHY "
873 << +phy->GetPhyId() << " to " << threshIt->second << " on link " << +linkId);
874 phy->SetCcaEdThreshold(threshIt->second);
875 m_prevCcaEdThreshold.erase(threshIt);
876 }
877}
878
879void
881 bool noSwitchDelay,
882 bool requestAccess,
883 EmlsrMainPhySwitchTrace&& traceInfo)
884{
885 NS_LOG_FUNCTION(this << linkId << noSwitchDelay << requestAccess << traceInfo.GetName());
886
887 auto mainPhy = m_staMac->GetDevice()->GetPhy(m_mainPhyId);
888
889 NS_ASSERT_MSG(mainPhy != m_staMac->GetWifiPhy(linkId),
890 "Main PHY is already operating on link " << +linkId);
891
892 // find the link on which the main PHY is operating
893 auto currMainPhyLinkId = m_staMac->GetLinkForPhy(mainPhy);
894 traceInfo.fromLinkId = currMainPhyLinkId;
895 traceInfo.toLinkId = linkId;
896 m_mainPhySwitchTrace(traceInfo);
897
898 m_mainPhySwitchInfo.from = currMainPhyLinkId.value_or(m_mainPhySwitchInfo.from);
899 m_mainPhySwitchInfo.to = linkId;
902 m_mainPhySwitchInfo.reason = traceInfo.GetName();
903
904 const auto newMainPhyChannel = GetChannelForMainPhy(linkId);
905
906 NS_LOG_DEBUG("Main PHY (" << mainPhy << ") is about to switch to " << newMainPhyChannel
907 << " to operate on link " << +linkId);
908
909 // if the main PHY is operating on a link, notify the channel access manager of the upcoming
910 // channel switch
911 if (currMainPhyLinkId.has_value())
912 {
913 m_staMac->GetChannelAccessManager(*currMainPhyLinkId)
914 ->NotifySwitchingEmlsrLink(mainPhy, newMainPhyChannel, linkId);
915 }
916
917 // this assert also ensures that the actual channel switch is not delayed
918 NS_ASSERT_MSG(!mainPhy->GetState()->IsStateTx(),
919 "We should not ask the main PHY to switch channel while transmitting");
920
921 // record the aux PHY operating on the link the main PHY is switching to
922 auto auxPhy = GetStaMac()->GetWifiPhy(linkId);
923
924 // request the main PHY to switch channel
925 const auto delay = mainPhy->GetChannelSwitchDelay();
926 const auto pifs = mainPhy->GetSifs() + mainPhy->GetSlot();
927 if (!noSwitchDelay && delay > std::max(m_lastAdvTransitionDelay, pifs))
928 {
929 NS_LOG_WARN("Channel switch delay ("
930 << delay.As(Time::US)
931 << ") should be shorter than the maximum between the Transition delay ("
932 << m_lastAdvTransitionDelay.As(Time::US) << ") and a PIFS ("
933 << pifs.As(Time::US) << ")");
934 }
935 if (noSwitchDelay)
936 {
937 mainPhy->SetAttribute("ChannelSwitchDelay", TimeValue(Seconds(0)));
938 }
939 mainPhy->SetOperatingChannel(newMainPhyChannel);
940 // restore previous channel switch delay
941 if (noSwitchDelay)
942 {
943 mainPhy->SetAttribute("ChannelSwitchDelay", TimeValue(delay));
944 }
945 // re-enable short time slot, if needed
946 if (m_staMac->GetWifiRemoteStationManager(linkId)->GetShortSlotTimeEnabled())
947 {
948 mainPhy->SetSlot(MicroSeconds(9));
949 }
950
951 const auto timeToSwitchEnd = noSwitchDelay ? Seconds(0) : delay;
952
953 // if the main PHY is not operating on any link (because it was switching), it is not connected
954 // to a channel access manager, hence we must notify the MAC of the new link switch
955 if (!currMainPhyLinkId.has_value())
956 {
957 m_staMac->NotifySwitchingEmlsrLink(mainPhy, linkId, timeToSwitchEnd);
958 }
959
960 if (requestAccess)
961 {
962 // schedule channel access request on the new link when switch is completed
963 Simulator::Schedule(timeToSwitchEnd, [=, this]() {
964 for (const auto& [acIndex, ac] : wifiAcList)
965 {
966 m_staMac->GetQosTxop(acIndex)->StartAccessAfterEvent(
967 linkId,
970 }
971 });
972 }
973
974 NotifyMainPhySwitch(currMainPhyLinkId, linkId, auxPhy, timeToSwitchEnd);
975}
976
977void
978EmlsrManager::SwitchAuxPhy(Ptr<WifiPhy> auxPhy, uint8_t currLinkId, uint8_t nextLinkId)
979{
980 NS_LOG_FUNCTION(this << auxPhy << currLinkId << nextLinkId);
981
982 auto newAuxPhyChannel = GetChannelForAuxPhy(nextLinkId);
983
984 NS_LOG_DEBUG("Aux PHY (" << auxPhy << ") is about to switch to " << newAuxPhyChannel
985 << " to operate on link " << +nextLinkId);
986
987 GetStaMac()
988 ->GetChannelAccessManager(currLinkId)
989 ->NotifySwitchingEmlsrLink(auxPhy, newAuxPhyChannel, nextLinkId);
990
991 auxPhy->SetOperatingChannel(newAuxPhyChannel);
992 // re-enable short time slot, if needed
993 if (m_staMac->GetWifiRemoteStationManager(nextLinkId)->GetShortSlotTimeEnabled())
994 {
995 auxPhy->SetSlot(MicroSeconds(9));
996 }
997
998 // schedule channel access request on the new link when switch is completed
999 Simulator::Schedule(auxPhy->GetChannelSwitchDelay(), [=, this]() {
1000 for (const auto& [acIndex, ac] : wifiAcList)
1001 {
1002 m_staMac->GetQosTxop(acIndex)->StartAccessAfterEvent(
1003 nextLinkId,
1004 Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT,
1005 Txop::CHECK_MEDIUM_BUSY);
1006 }
1007 });
1008}
1009
1010void
1011EmlsrManager::StartMediumSyncDelayTimer(uint8_t linkId)
1012{
1013 NS_LOG_FUNCTION(this << linkId);
1014
1015 if (m_mediumSyncDuration.IsZero())
1016 {
1017 NS_LOG_DEBUG("MediumSyncDuration is zero");
1018 return;
1019 }
1020
1021 auto phy = m_staMac->GetWifiPhy(linkId);
1022
1023 if (!phy)
1024 {
1025 NS_LOG_DEBUG("No PHY operating on link " << +linkId);
1026 // MSD timer will be started when a PHY will be operating on this link
1027 return;
1028 }
1029
1030 const auto [it, inserted] = m_mediumSyncDelayStatus.try_emplace(linkId);
1031
1032 // reset the max number of TXOP attempts
1033 it->second.msdNTxopsLeft = m_msdMaxNTxops;
1034
1035 if (!it->second.timer.IsPending())
1036 {
1037 NS_LOG_DEBUG("Setting CCA ED threshold on link "
1038 << +linkId << " to " << +m_msdOfdmEdThreshold << " PHY " << +phy->GetPhyId());
1039 m_prevCcaEdThreshold[phy] = phy->GetCcaEdThreshold();
1040 phy->SetCcaEdThreshold(m_msdOfdmEdThreshold);
1041 }
1042
1043 // (re)start the timer
1044 it->second.timer.Cancel();
1045 NS_LOG_DEBUG("Starting MediumSyncDelay timer for " << m_mediumSyncDuration.As(Time::US)
1046 << " on link " << +linkId);
1047 it->second.timer = Simulator::Schedule(m_mediumSyncDuration,
1048 &EmlsrManager::MediumSyncDelayTimerExpired,
1049 this,
1050 linkId);
1051}
1052
1053void
1054EmlsrManager::CancelMediumSyncDelayTimer(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
1062 timerIt->second.timer.Cancel();
1063 MediumSyncDelayTimerExpired(linkId);
1064}
1065
1066void
1067EmlsrManager::MediumSyncDelayTimerExpired(uint8_t linkId)
1068{
1069 NS_LOG_FUNCTION(this << linkId);
1070
1071 auto timerIt = m_mediumSyncDelayStatus.find(linkId);
1072
1073 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && !timerIt->second.timer.IsPending());
1074
1075 // reset the MSD OFDM ED threshold
1076 auto phy = m_staMac->GetWifiPhy(linkId);
1077
1078 if (!phy)
1079 {
1080 // no PHY is operating on this link. This may happen when a MediumSyncDelay timer expires
1081 // on the link left "uncovered" by the main PHY that is operating on another link (and the
1082 // aux PHY of that link did not switch). In this case, do nothing, since the CCA ED
1083 // threshold on the main PHY will be restored once the main PHY switches back to its link
1084 return;
1085 }
1086
1087 auto threshIt = m_prevCcaEdThreshold.find(phy);
1088 NS_ASSERT_MSG(threshIt != m_prevCcaEdThreshold.cend(),
1089 "No value to restore for CCA ED threshold on PHY " << phy);
1090 NS_LOG_DEBUG("Resetting CCA ED threshold of PHY " << phy << " to " << threshIt->second
1091 << " on link " << +linkId);
1092 phy->SetCcaEdThreshold(threshIt->second);
1093 m_prevCcaEdThreshold.erase(threshIt);
1094}
1095
1096void
1097EmlsrManager::DecrementMediumSyncDelayNTxops(uint8_t linkId)
1098{
1099 NS_LOG_FUNCTION(this << linkId);
1100
1101 const auto timerIt = m_mediumSyncDelayStatus.find(linkId);
1102
1103 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && timerIt->second.timer.IsPending());
1104 NS_ASSERT(timerIt->second.msdNTxopsLeft != 0);
1105
1106 if (timerIt->second.msdNTxopsLeft)
1107 {
1108 --timerIt->second.msdNTxopsLeft.value();
1109 }
1110}
1111
1112void
1113EmlsrManager::ResetMediumSyncDelayNTxops(uint8_t linkId)
1114{
1115 NS_LOG_FUNCTION(this << linkId);
1116
1117 auto timerIt = m_mediumSyncDelayStatus.find(linkId);
1118
1119 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && timerIt->second.timer.IsPending());
1120 timerIt->second.msdNTxopsLeft.reset();
1121}
1122
1123bool
1124EmlsrManager::MediumSyncDelayNTxopsExceeded(uint8_t linkId)
1125{
1126 NS_LOG_FUNCTION(this << linkId);
1127
1128 auto timerIt = m_mediumSyncDelayStatus.find(linkId);
1129
1130 NS_ASSERT(timerIt != m_mediumSyncDelayStatus.cend() && timerIt->second.timer.IsPending());
1131 return timerIt->second.msdNTxopsLeft == 0;
1132}
1133
1135EmlsrManager::GetEmlOmn()
1136{
1137 MgtEmlOmn frame;
1138
1139 // Add the EMLSR Parameter Update field if needed
1140 if (m_lastAdvPaddingDelay != m_emlsrPaddingDelay ||
1141 m_lastAdvTransitionDelay != m_emlsrTransitionDelay)
1142 {
1143 m_lastAdvPaddingDelay = m_emlsrPaddingDelay;
1144 m_lastAdvTransitionDelay = m_emlsrTransitionDelay;
1147 frame.m_emlsrParamUpdate->paddingDelay =
1148 CommonInfoBasicMle::EncodeEmlsrPaddingDelay(m_lastAdvPaddingDelay);
1149 frame.m_emlsrParamUpdate->transitionDelay =
1150 CommonInfoBasicMle::EncodeEmlsrTransitionDelay(m_lastAdvTransitionDelay);
1151 }
1152
1153 // We must verify that the links included in the given EMLSR link set (if any) have been setup.
1154 auto setupLinkIds = m_staMac->GetSetupLinkIds();
1155
1156 for (auto emlsrLinkIt = m_nextEmlsrLinks->begin(); emlsrLinkIt != m_nextEmlsrLinks->end();)
1157 {
1158 if (auto setupLinkIt = setupLinkIds.find(*emlsrLinkIt); setupLinkIt != setupLinkIds.cend())
1159 {
1160 setupLinkIds.erase(setupLinkIt);
1161 frame.SetLinkIdInBitmap(*emlsrLinkIt);
1162 emlsrLinkIt++;
1163 }
1164 else
1165 {
1166 NS_LOG_DEBUG("Link ID " << +(*emlsrLinkIt) << " has not been setup");
1167 emlsrLinkIt = m_nextEmlsrLinks->erase(emlsrLinkIt);
1168 }
1169 }
1170
1171 // EMLSR Mode is enabled if and only if the set of EMLSR links is not empty
1172 frame.m_emlControl.emlsrMode = m_nextEmlsrLinks->empty() ? 0 : 1;
1173
1174 return frame;
1175}
1176
1177void
1178EmlsrManager::SendEmlOmn()
1179{
1180 NS_LOG_FUNCTION(this);
1181
1182 NS_ABORT_MSG_IF(!m_emlsrTransitionTimeout,
1183 "AP did not advertise a Transition Timeout, cannot send EML notification");
1184 NS_ASSERT_MSG(m_nextEmlsrLinks, "Need to set EMLSR links before calling this method");
1185
1186 // TODO if this is a single radio non-AP MLD and not all setup links are in the EMLSR link
1187 // set, we have to put setup links that are not included in the given EMLSR link set (i.e.,
1188 // those remaining in setupLinkIds, if m_nextEmlsrLinks is not empty) in the sleep mode:
1189 // For the EMLSR mode enabled in a single radio non-AP MLD, the STA(s) affiliated with
1190 // the non-AP MLD that operates on the enabled link(s) that corresponds to the bit
1191 // position(s) of the EMLSR Link Bitmap subfield set to 0 shall be in doze state if a
1192 // non-AP STA affiliated with the non-AP MLD that operates on one of the EMLSR links is
1193 // in awake state. (Sec. 35.3.17 of 802.11be D3.0)
1194
1195 auto frame = GetEmlOmn();
1196 auto linkId = GetLinkToSendEmlOmn();
1197 GetEhtFem(linkId)->SendEmlOmn(m_staMac->GetBssid(linkId), frame);
1198}
1199
1200void
1201EmlsrManager::TxOk(Ptr<const WifiMpdu> mpdu)
1202{
1203 NS_LOG_FUNCTION(this << *mpdu);
1204
1205 const auto& hdr = mpdu->GetHeader();
1206
1207 if (hdr.IsAssocReq())
1208 {
1209 // store padding delay and transition delay advertised in AssocReq
1210 MgtAssocRequestHeader assocReq;
1211 mpdu->GetPacket()->PeekHeader(assocReq);
1212 auto& mle = assocReq.Get<MultiLinkElement>();
1213 NS_ASSERT_MSG(mle, "AssocReq should contain a Multi-Link Element");
1214 m_lastAdvPaddingDelay = mle->GetEmlsrPaddingDelay();
1215 m_lastAdvTransitionDelay = mle->GetEmlsrTransitionDelay();
1216 }
1217
1218 if (hdr.IsMgt() && hdr.IsAction())
1219 {
1220 if (auto [category, action] = WifiActionHeader::Peek(mpdu->GetPacket());
1221 category == WifiActionHeader::PROTECTED_EHT &&
1222 action.protectedEhtAction ==
1223 WifiActionHeader::PROTECTED_EHT_EML_OPERATING_MODE_NOTIFICATION)
1224 {
1225 // the EML Operating Mode Notification frame that we sent has been acknowledged.
1226 // Start the transition timeout to wait until the request can be made effective
1227 NS_ASSERT_MSG(m_emlsrTransitionTimeout, "No transition timeout received from AP");
1228 m_transitionTimeoutEvent = Simulator::Schedule(*m_emlsrTransitionTimeout,
1229 &EmlsrManager::ChangeEmlsrMode,
1230 this);
1231 }
1232 }
1233}
1234
1235void
1236EmlsrManager::TxDropped(WifiMacDropReason reason, Ptr<const WifiMpdu> mpdu)
1237{
1238 NS_LOG_FUNCTION(this << reason << *mpdu);
1239
1240 const auto& hdr = mpdu->GetHeader();
1241
1242 if (hdr.IsMgt() && hdr.IsAction())
1243 {
1244 auto pkt = mpdu->GetPacket()->Copy();
1245 if (auto [category, action] = WifiActionHeader::Remove(pkt);
1246 category == WifiActionHeader::PROTECTED_EHT &&
1247 action.protectedEhtAction ==
1248 WifiActionHeader::PROTECTED_EHT_EML_OPERATING_MODE_NOTIFICATION)
1249 {
1250 // the EML Operating Mode Notification frame has been dropped. Ask the subclass
1251 // whether the frame needs to be resent
1252 auto linkId = ResendNotification(mpdu);
1253 if (linkId)
1254 {
1255 MgtEmlOmn frame;
1256 pkt->RemoveHeader(frame);
1257 GetEhtFem(*linkId)->SendEmlOmn(m_staMac->GetBssid(*linkId), frame);
1258 }
1259 else
1260 {
1261 m_nextEmlsrLinks.reset();
1262 }
1263 }
1264 }
1265}
1266
1267void
1268EmlsrManager::ChangeEmlsrMode()
1269{
1270 NS_LOG_FUNCTION(this);
1271
1272 // After the successful transmission of the EML Operating Mode Notification frame by the
1273 // non-AP STA affiliated with the non-AP MLD, the non-AP MLD shall operate in the EMLSR mode
1274 // and the other non-AP STAs operating on the corresponding EMLSR links shall transition to
1275 // active mode after the transition delay indicated in the Transition Timeout subfield in the
1276 // EML Capabilities subfield of the Basic Multi-Link element or immediately after receiving an
1277 // EML Operating Mode Notification frame from one of the APs operating on the EMLSR links and
1278 // affiliated with the AP MLD. (Sec. 35.3.17 of 802.11be D3.0)
1279 NS_ASSERT_MSG(m_nextEmlsrLinks, "No set of EMLSR links stored");
1280 m_emlsrLinks.swap(*m_nextEmlsrLinks);
1281 m_nextEmlsrLinks.reset();
1282
1283 // Make other non-AP STAs operating on the corresponding EMLSR links transition to
1284 // active mode or passive mode (depending on whether EMLSR mode has been enabled or disabled)
1285 m_staMac->NotifyEmlsrModeChanged(m_emlsrLinks);
1286 // Enforce the limit on the max channel width supported by aux PHYs
1287 ApplyMaxChannelWidthAndModClassOnAuxPhys();
1288
1289 NotifyEmlsrModeChanged();
1290}
1291
1292void
1293EmlsrManager::ApplyMaxChannelWidthAndModClassOnAuxPhys()
1294{
1295 NS_LOG_FUNCTION(this);
1296 auto currMainPhyLinkId = m_staMac->GetLinkForPhy(m_mainPhyId);
1297 NS_ASSERT(currMainPhyLinkId);
1298
1299 for (const auto linkId : m_staMac->GetLinkIds())
1300 {
1301 auto auxPhy = m_staMac->GetWifiPhy(linkId);
1302 auto channel = GetChannelForAuxPhy(linkId);
1303
1304 if (linkId == currMainPhyLinkId || !m_staMac->IsEmlsrLink(linkId))
1305 {
1306 continue; // main PHY link or not an EMLSR link
1307 }
1308
1309 auxPhy->SetMaxModulationClassSupported(m_auxPhyMaxModClass);
1310
1311 if (auxPhy->GetOperatingChannel() == channel)
1312 {
1313 continue;
1314 }
1315
1316 NS_LOG_DEBUG("Aux PHY (" << auxPhy << ") is about to switch to " << channel
1317 << " to operate on link " << +linkId);
1318 // We cannot simply set the new channel, because otherwise the MAC will disable
1319 // the setup link. We need to inform the MAC (via the Channel Access Manager) that
1320 // this channel switch must not have such a consequence. We already have a method
1321 // for doing so, i.e., inform the MAC that the PHY is switching channel to operate
1322 // on the "same" link.
1323 auto cam = m_staMac->GetChannelAccessManager(linkId);
1324 cam->NotifySwitchingEmlsrLink(auxPhy, channel, linkId);
1325
1326 // apply channel width limitation assuming an instantaneous channel switch
1327 const auto delay = auxPhy->GetChannelSwitchDelay();
1328 auxPhy->SetAttribute("ChannelSwitchDelay", TimeValue(Time{0}));
1329 auxPhy->SetOperatingChannel(channel);
1330 auxPhy->SetAttribute("ChannelSwitchDelay", TimeValue(delay));
1331 }
1332}
1333
1334void
1335EmlsrManager::ComputeOperatingChannels()
1336{
1337 NS_LOG_FUNCTION(this);
1338
1339 m_mainPhyChannels.clear();
1340 m_auxPhyChannels.clear();
1341
1342 auto linkIds = m_staMac->GetSetupLinkIds();
1343
1344 for (auto linkId : linkIds)
1345 {
1346 const auto& channel = m_staMac->GetWifiPhy(linkId)->GetOperatingChannel();
1347 m_mainPhyChannels.emplace(linkId, channel);
1348
1349 auto mainPhyChWidth = channel.GetWidth();
1350 if (m_auxPhyMaxWidth >= mainPhyChWidth)
1351 {
1352 // same channel can be used by aux PHYs
1353 m_auxPhyChannels.emplace(linkId, channel);
1354 continue;
1355 }
1356 // aux PHYs will operate on a primary subchannel
1357 auto freq = channel.GetPrimaryChannelCenterFrequency(m_auxPhyMaxWidth);
1358 auto chIt = WifiPhyOperatingChannel::FindFirst(0,
1359 freq,
1360 m_auxPhyMaxWidth,
1362 channel.GetPhyBand());
1363 NS_ASSERT_MSG(chIt != WifiPhyOperatingChannel::m_frequencyChannels.end(),
1364 "Primary" << m_auxPhyMaxWidth << " channel not found");
1365 m_auxPhyChannels.emplace(linkId, chIt);
1366 // find the P20 index for the channel used by the aux PHYs
1367 auto p20Index = channel.GetPrimaryChannelIndex(MHz_u{20});
1368 while (mainPhyChWidth > m_auxPhyMaxWidth)
1369 {
1370 mainPhyChWidth /= 2;
1371 p20Index /= 2;
1372 }
1373 m_auxPhyChannels[linkId].SetPrimary20Index(p20Index);
1374 }
1375}
1376
1378EmlsrManager::GetChannelForMainPhy(uint8_t linkId) const
1379{
1380 auto it = m_mainPhyChannels.find(linkId);
1381 NS_ASSERT_MSG(it != m_mainPhyChannels.end(),
1382 "Channel for main PHY on link ID " << +linkId << " not found");
1383 return it->second;
1384}
1385
1387EmlsrManager::GetChannelForAuxPhy(uint8_t linkId) const
1388{
1389 auto it = m_auxPhyChannels.find(linkId);
1390 NS_ASSERT_MSG(it != m_auxPhyChannels.end(),
1391 "Channel for aux PHY on link ID " << +linkId << " not found");
1392 return it->second;
1393}
1394
1395void
1396EmlsrManager::CancelAllSleepEvents()
1397{
1398 NS_LOG_FUNCTION(this);
1399
1400 for (auto& [id, event] : m_auxPhyToSleepEvents)
1401 {
1402 event.Cancel();
1403 }
1404 m_auxPhyToSleepEvents.clear();
1405}
1406
1407void
1408EmlsrManager::SetSleepStateForAllAuxPhys(bool sleep)
1409{
1410 NS_LOG_FUNCTION(this << sleep);
1411
1412 CancelAllSleepEvents();
1413
1414 for (const auto& phy : m_staMac->GetDevice()->GetPhys())
1415 {
1416 if (phy->GetPhyId() == m_mainPhyId)
1417 {
1418 continue; // do not set sleep mode/resume from sleep the main PHY
1419 }
1420
1421 auto linkId = m_staMac->GetLinkForPhy(phy);
1422
1423 if (linkId.has_value() && !m_staMac->IsEmlsrLink(*linkId))
1424 {
1425 continue; // this PHY is not operating on an EMLSR link
1426 }
1427
1428 if (!sleep)
1429 {
1430 if (!phy->IsStateSleep())
1431 {
1432 continue; // nothing to do
1433 }
1434
1435 NS_LOG_DEBUG("PHY " << +phy->GetPhyId() << ": Resuming from sleep");
1436 phy->ResumeFromSleep();
1437
1438 // if this aux PHY is operating on a link, check if it lost medium sync
1439 if (linkId.has_value())
1440 {
1441 auto it = m_startSleep.find(phy->GetPhyId());
1442 NS_ASSERT_MSG(it != m_startSleep.cend(),
1443 "No start sleep info for PHY ID " << phy->GetPhyId());
1444 const auto sleepDuration = Simulator::Now() - it->second;
1445 m_startSleep.erase(it);
1446
1447 if (sleepDuration > MicroSeconds(MEDIUM_SYNC_THRESHOLD_USEC))
1448 {
1449 StartMediumSyncDelayTimer(*linkId);
1450 }
1451 }
1452
1453 continue; // resuming the PHY from sleep has been handled
1454 }
1455
1456 if (phy->IsStateSleep())
1457 {
1458 continue; // nothing to do
1459 }
1460
1461 // we force WifiPhy::SetSleepMode() to abort RX and switch immediately to sleep mode in
1462 // case the state is RX. If the state is TX or SWITCHING, WifiPhy::SetSleepMode() postpones
1463 // setting sleep mode to end of TX or SWITCHING. This is fine, but we schedule events here
1464 // to correctly record the time an aux PHY was put to sleep and to be able to cancel them
1465 // later if needed
1466 std::stringstream ss;
1467 auto s = std::string("PHY ") + std::to_string(phy->GetPhyId()) + ": Setting sleep mode";
1468 if (phy->IsStateTx() || phy->IsStateSwitching())
1469 {
1470 const auto delay = phy->GetDelayUntilIdle();
1471 NS_LOG_DEBUG(s << " in " << delay.As(Time::US));
1472 m_auxPhyToSleepEvents[phy->GetPhyId()] = Simulator::Schedule(delay, [=, this]() {
1473 phy->SetSleepMode(true);
1474 m_startSleep[phy->GetPhyId()] = Simulator::Now();
1475 });
1476 }
1477 else
1478 {
1479 NS_LOG_DEBUG(s);
1480 phy->SetSleepMode(true);
1481 m_startSleep[phy->GetPhyId()] = Simulator::Now();
1482 }
1483 }
1484}
1485
1486} // 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 SwitchMainPhy(uint8_t linkId, bool noSwitchDelay, bool requestAccess, EmlsrMainPhySwitchTrace &&traceInfo)
Switch channel on the Main PHY so that it operates on the given link.
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
virtual void DoNotifyTxopEnd(uint8_t linkId, Ptr< QosTxop > edca)=0
Notify the subclass of the end of a TXOP on the given link.
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.
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
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 NotifyDlTxopStart(uint8_t linkId)
Notify the start of a DL TXOP on the given link.
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
Time m_mediumSyncDuration
duration of the MediumSyncDelay timer
void NotifyTxopEnd(uint8_t linkId, Ptr< QosTxop > edca=nullptr)
Notify the end of a TXOP on the given link.
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 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.
virtual std::optional< WifiIcfDrop > CheckMainPhyTakesOverDlTxop(uint8_t linkId) const
This method is called when an aux PHY has completed reception of an ICF to determine whether there is...
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).
virtual void EmlsrLinkSwitchCallback(uint8_t linkId, Ptr< WifiPhy > phy, bool connected)
Callback connected to the EmlsrLinkSwitch trace source of StaWifiMac.
const WifiPhyOperatingChannel & GetChannelForMainPhy(uint8_t linkId) const
~EmlsrManager() override
void SetMediumSyncDuration(Time duration)
Set the duration of the MediumSyncDelay timer.
Time m_lastAdvTransitionDelay
last advertised transition delay
MainPhySwitchInfo m_mainPhySwitchInfo
main PHY switch info
virtual void DoNotifyDlTxopStart(uint8_t linkId)=0
Notify the subclass of the reception of an initial Control frame on the given link.
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.
virtual void DoNotifyProtectionCompleted(uint8_t linkId)=0
Notify the subclass that protection (if required) is completed and data frame exchange can start on t...
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.
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 EventId ScheduleNow(FUNC f, Ts &&... args)
Schedule an event to expire Now.
Definition simulator.h:595
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_LOG_WARN(msg)
Use NS_LOG to output a message of level LOG_WARN.
Definition log.h:250
#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.
@ TX
The PHY layer is sending a packet.
@ SLEEP
The PHY layer is sleeping.
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
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.
std::string reason
the reason for switching the main PHY
uint8_t from
ID of the link which the main PHY is/has been leaving.
Time start
start of channel switching
uint8_t to
ID of the link which the main PHY is moving to.
bool disconnected
true if the main PHY is not connected to any link, i.e., it is switching or waiting to be connected t...
uint8_t emlsrParamUpdateCtrl
EMLSR Parameter Update Control.
EMLSR Parameter Update field.