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