A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
advanced-emlsr-manager.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2024 Universita' di Napoli Federico II
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 *
6 * Author: Stefano Avallone <stavallo@unina.it>
7 */
8
10
12
13#include "ns3/boolean.h"
14#include "ns3/log.h"
15#include "ns3/wifi-net-device.h"
16#include "ns3/wifi-phy-listener.h"
17#include "ns3/wifi-phy.h"
18
19#include <algorithm>
20
21namespace ns3
22{
23
24NS_LOG_COMPONENT_DEFINE("AdvancedEmlsrManager");
25
26NS_OBJECT_ENSURE_REGISTERED(AdvancedEmlsrManager);
27
28/**
29 * PHY listener connected to the main PHY while operating on the link of an aux PHY that is
30 * not TX capable.
31 *
32 * PHY notifications are forwarded to this EMLSR manager one timestep later because this EMLSR
33 * manager may then decide to switch the main PHY back to the preferred link. Given that notifying
34 * a PHY listener is only one of the actions that are performed when handling events such as RX end
35 * or CCA busy start, it is not a good idea to request a main PHY switch while performing other
36 * actions. Forwarding notifications a timestep later allows to first complete the handling of the
37 * given event and then (possibly) starting a main PHY switch.
38 */
40{
41 public:
42 /**
43 * Constructor
44 *
45 * @param emlsrManager the EMLSR manager
46 */
48 : m_emlsrManager(emlsrManager)
49 {
50 }
51
58
65
66 void NotifyRxEndError() override
67 {
68 }
69
70 void NotifyTxStart(Time /* duration */, dBm_u /* txPower */) override
71 {
72 }
73
74 void NotifyCcaBusyStart(Time /* duration */,
75 WifiChannelListType /* channelType */,
76 const std::vector<Time>& /* per20MhzDurations */) override
77 {
78 Simulator::Schedule(TimeStep(1),
81 }
82
83 void NotifySwitchingStart(Time /* duration */) override
84 {
85 }
86
87 void NotifySleep() override
88 {
89 }
90
91 void NotifyOff() override
92 {
93 }
94
95 void NotifyWakeup() override
96 {
97 }
98
99 void NotifyOn() override
100 {
101 }
102
103 private:
105};
106
107TypeId
109{
110 static TypeId tid =
111 TypeId("ns3::AdvancedEmlsrManager")
113 .SetGroupName("Wifi")
114 .AddConstructor<AdvancedEmlsrManager>()
115 .AddAttribute("AllowUlTxopInRx",
116 "Whether a (main or aux) PHY is allowed to start an UL TXOP if "
117 "another PHY is receiving a PPDU (possibly starting a DL TXOP). "
118 "If this attribute is true, the PPDU may be dropped.",
119 BooleanValue(false),
122 .AddAttribute("InterruptSwitch",
123 "Whether the main PHY can be interrupted while switching to start "
124 "switching to another link.",
125 BooleanValue(false),
128 .AddAttribute("UseAuxPhyCca",
129 "Whether the CCA performed in the last PIFS interval by a non-TX "
130 "capable aux PHY should be used when the main PHY ends switching to "
131 "the aux PHY's link to determine whether TX can start or not (and what "
132 "bandwidth can be used for transmission) independently of whether the "
133 "aux PHY bandwidth is smaller than the main PHY bandwidth or not.",
134 BooleanValue(false),
137 .AddAttribute("SwitchMainPhyBackDelay",
138 "Duration of the timer started in case of non-TX capable aux PHY (that "
139 "does not switch link) when medium is sensed busy during the PIFS "
140 "interval preceding/following the main PHY switch end. When the timer "
141 "expires, the main PHY is switched back to the preferred link.",
145 return tid;
146}
147
149{
150 NS_LOG_FUNCTION(this);
151 m_phyListener = std::make_shared<EmlsrPhyListener>(this);
152}
153
158
159void
161{
162 NS_LOG_FUNCTION(this);
163 for (auto phy : GetStaMac()->GetDevice()->GetPhys())
164 {
165 phy->TraceDisconnectWithoutContext(
166 "PhyRxMacHeaderEnd",
168 }
169 if (!GetAuxPhyTxCapable())
170 {
171 GetStaMac()->GetDevice()->GetPhy(GetMainPhyId())->UnregisterListener(m_phyListener);
172 }
173 m_phyListener.reset();
175}
176
177void
179{
180 NS_LOG_FUNCTION(this);
181
182 // disconnect callbacks on all links
183 for (const auto& linkId : GetStaMac()->GetLinkIds())
184 {
185 GetStaMac()->GetChannelAccessManager(linkId)->TraceDisconnectWithoutContext(
186 "NSlotsLeftAlert",
188 }
189
190 // connect callbacks on EMLSR links
191 for (const auto& emlsrLinkId : GetEmlsrLinks())
192 {
193 GetStaMac()
194 ->GetChannelAccessManager(emlsrLinkId)
195 ->TraceConnectWithoutContext(
196 "NSlotsLeftAlert",
198 }
199
201}
202
203void
205{
206 NS_LOG_FUNCTION(this << mac);
207
208 for (auto phy : GetStaMac()->GetDevice()->GetPhys())
209 {
210 phy->TraceConnectWithoutContext(
211 "PhyRxMacHeaderEnd",
213 }
214 if (!GetAuxPhyTxCapable())
215 {
216 mac->GetDevice()->GetPhy(GetMainPhyId())->RegisterListener(m_phyListener);
217 }
218}
219
220std::pair<bool, Time>
222{
223 NS_LOG_FUNCTION(this << linkId);
224
225 // prevent or allow an UL TXOP depending on whether another PHY is receiving a PPDU
226 for (const auto id : GetStaMac()->GetLinkIds())
227 {
228 if (id == linkId)
229 {
230 continue;
231 }
232
233 const auto [maybeIcf, delay] = CheckPossiblyReceivingIcf(id);
234
235 if (!maybeIcf)
236 {
237 // not receiving anything or receiving something that is certainly not an ICF
238 continue;
239 }
240
241 // a PPDU that may be an ICF is being received
243 {
244 return {false, delay};
245 }
246 }
247
248 if (GetStaMac()->GetWifiPhy(linkId) == GetStaMac()->GetDevice()->GetPhy(GetMainPhyId()) &&
250 {
251 // main PHY has got access on the link it switched to (because the aux PHY is not TX
252 // capable) before a PIFS interval was elapsed: do not start the TXOP now
253 return {false, Time{0}};
254 }
255
256 return {true, Time{0}};
257}
258
259void
261 const WifiMacHeader& macHdr,
262 const WifiTxVector& txVector,
263 Time psduDuration)
264{
265 auto linkId = GetStaMac()->GetLinkForPhy(phy);
266 if (!linkId.has_value())
267 {
268 return;
269 }
270 NS_LOG_FUNCTION(this << *linkId << macHdr << txVector << psduDuration.As(Time::MS));
271
272 auto& ongoingTxopEnd = GetEhtFem(*linkId)->GetOngoingTxopEndEvent();
273
274 if (m_useNotifiedMacHdr && ongoingTxopEnd.IsPending() &&
275 macHdr.GetAddr1() != GetEhtFem(*linkId)->GetAddress() && !macHdr.GetAddr1().IsBroadcast() &&
276 !(macHdr.IsCts() && macHdr.GetAddr1() == GetEhtFem(*linkId)->GetBssid() /* CTS-to-self */))
277 {
278 // the EMLSR client is no longer involved in the TXOP and switching to listening mode
279 ongoingTxopEnd.Cancel();
280 // this method is a callback connected to the PhyRxMacHeaderEnd trace source of WifiPhy
281 // and is called within a for loop that executes all the callbacks. The call to NotifyTxop
282 // below leads the main PHY to be connected back to the preferred link, thus
283 // the ResetPhy() method of the FEM on the auxiliary link is called, which disconnects
284 // another callback (FEM::ReceivedMacHdr) from the PhyRxMacHeaderEnd trace source of
285 // the main PHY, thus invalidating the list of callbacks on which the for loop iterates.
286 // Hence, schedule the call to NotifyTxopEnd to execute it outside such for loop.
287 Simulator::ScheduleNow(&AdvancedEmlsrManager::NotifyTxopEnd, this, *linkId, false, false);
288 }
289
290 // if the MAC header has been received on the link on which the main PHY is operating, the
291 // switch main PHY back timer is running and channel access is not expected to be gained by
292 // the main PHY before the switch main PHY back timer expires (plus a channel switch delay),
293 // try to switch the main PHY back to the preferred link
295 phy == GetStaMac()->GetDevice()->GetPhy(GetMainPhyId()) &&
298 phy->GetChannelSwitchDelay()))
299 {
302 }
303}
304
305void
307{
308 NS_LOG_FUNCTION(this << linkId);
309
310 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
311
312 if (m_switchAuxPhy && (!mainPhy->IsStateSwitching() || !m_interruptSwitching))
313 {
314 NS_LOG_DEBUG("SwitchAuxPhy true, nothing to do");
315 return;
316 }
317
319 {
320 NS_LOG_DEBUG("SwitchAuxPhy false, nothing to do");
321 return;
322 }
323
324 // we get here if:
325 // - SwitchAuxPhy is true, the main PHY is switching and switching can be interrupted
326 // or
327 // - SwitchAuxPhy is false and there is an aux PHY to reconnect
328
329 // Note that the main PHY may be switching at the end of a TXOP when, e.g., the main PHY
330 // starts switching to a link on which an aux PHY gained a TXOP and sent an RTS, but the CTS
331 // is not received and the UL TXOP ends before the main PHY channel switch is completed.
332 // In such cases, wait until the main PHY channel switch is completed (unless the channel
333 // switching can be interrupted) before requesting a new channel switch.
334 // Backoff shall not be reset on the link left by the main PHY because a TXOP ended and
335 // a new backoff value must be generated.
336 if (m_switchAuxPhy || !mainPhy->IsStateSwitching() || m_interruptSwitching)
337 {
340 "Aux PHY next link ID should have a value when interrupting a main PHY switch");
341 uint8_t nextLinkId = m_switchAuxPhy ? m_mainPhySwitchInfo.from : GetMainPhyId();
342 const auto delay = mainPhy->IsStateSwitching() ? mainPhy->GetDelayUntilIdle() : Time{0};
343 SwitchMainPhy(nextLinkId,
344 false,
347 EmlsrTxopEndedTrace(delay));
348 }
349 else
350 {
351 // delay link switch until current channel switching is completed
352 const auto delay = mainPhy->GetDelayUntilIdle();
353 Simulator::Schedule(delay, [=, this]() {
354 // request the main PHY to switch back to the preferred link only if in the meantime
355 // no TXOP started on another link (which will require the main PHY to switch link)
356 if (!GetEhtFem(linkId)->UsingOtherEmlsrLink())
357 {
359 false,
362 EmlsrTxopEndedTrace(delay));
363 }
364 });
365 }
366}
367
368std::pair<bool, Time>
370{
371 NS_LOG_FUNCTION(this << linkId);
372
374 {
376 }
377
378 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
379 auto state = mainPhy->GetState()->GetState();
380
382 state == WifiPhyState::IDLE || state == WifiPhyState::CCA_BUSY,
383 "Main PHY cannot be in state " << state);
384
385 auto timeToCtsEnd = GetTimeToCtsEnd(linkId);
386 auto switchingTime = mainPhy->GetChannelSwitchDelay();
387
388 if (switchingTime > timeToCtsEnd)
389 {
390 // switching takes longer than RTS/CTS exchange, release channel
391 NS_LOG_DEBUG("Not enough time for main PHY to switch link (main PHY state: "
392 << mainPhy->GetState()->GetState() << ")");
393 // retry channel access when the CTS was expected to be received
394 return {false, timeToCtsEnd};
395 }
396
397 // TXOP can be started, main PHY will be scheduled to switch by NotifyRtsSent as soon as the
398 // transmission of the RTS is notified
400
401 return {true, Time{0}};
402}
403
404void
406{
407 NS_LOG_FUNCTION(this << phy->GetPhyId() << linkId << edca->GetAccessCategory());
408
409 const auto caManager = GetStaMac()->GetChannelAccessManager(linkId);
410 const auto pifs = phy->GetSifs() + phy->GetSlot();
411
412 const auto isBusy = caManager->IsBusy(); // check NAV and CCA on primary20
413 // check CCA on the entire channel
414 auto width = caManager->GetLargestIdlePrimaryChannel(pifs, Simulator::Now());
415
416 if (!isBusy && width > MHz_u{0})
417 {
418 // medium idle, start TXOP
419 width = std::min(width, GetChannelForMainPhy(linkId).GetTotalWidth());
420
421 // if this function is called at the end of the main PHY switch, it is executed before the
422 // main PHY is connected to this link in order to use the CCA information of the aux PHY.
423 // Schedule now the TXOP start so that we first connect the main PHY to this link.
426 {
427 NotifyUlTxopStart(linkId);
428 }
429 else if (!m_switchAuxPhy)
430 {
431 // switch main PHY back to preferred link if SwitchAuxPhy is false
433 }
434 });
435 }
436 else
437 {
438 // medium busy, check when access may be granted
440 m_switchMainPhyBackDelay + phy->GetChannelSwitchDelay()))
441 {
442 NS_LOG_DEBUG("No AC is expected to get backoff soon, switch main PHY back");
443 if (auto mainPhy = GetStaMac()->GetDevice()->GetPhy(GetMainPhyId());
444 !mainPhy->IsStateSwitching())
445 {
447 }
448 return;
449 }
450
451 // medium busy, restart channel access
452 NS_LOG_DEBUG("Medium busy in the last PIFS interval");
453 edca->NotifyChannelReleased(linkId); // to set access to NOT_REQUESTED
454 edca->StartAccessAfterEvent(linkId,
457
458 // the main PHY must stay for some time on this link to check if it gets channel access.
459 // The timer is stopped if a DL or UL TXOP is started. When the timer expires, the main PHY
460 // switches back to the preferred link if SwitchAuxPhy is false
465 this,
466 linkId);
467 }
468}
469
470void
472{
473 NS_LOG_FUNCTION(this << linkId);
474
475 if (m_switchAuxPhy)
476 {
477 return; // nothing to do
478 }
479
480 Time extension{0};
481
482 // check if the timer must be restarted because a frame is being received on any link
483 for (const auto id : GetStaMac()->GetLinkIds())
484 {
485 auto phy = GetStaMac()->GetWifiPhy(id);
486
487 if (!phy || !GetStaMac()->IsEmlsrLink(id))
488 {
489 continue;
490 }
491
492 if (!GetEhtFem(id)->VirtualCsMediumIdle() &&
493 GetEhtFem(id)->GetTxopHolder() != GetEhtFem(id)->GetBssid())
494 {
495 NS_LOG_DEBUG("NAV is set and TXOP holder is not the associated AP MLD on link " << +id);
496 continue;
497 }
498
499 const auto [maybeIcf, delay] = CheckPossiblyReceivingIcf(id);
500
501 if (maybeIcf)
502 {
503 extension = Max(extension, delay);
504 }
505 else if (id == linkId && phy->IsStateIdle())
506 {
507 // this is the link on which the main PHY is operating. If an AC with traffic is
508 // expected to get channel access soon (within a channel switch delay), restart
509 // the timer to have the main PHY stay a bit longer on this link
510 if (GetExpectedAccessWithinDelay(linkId, phy->GetChannelSwitchDelay()))
511 {
512 extension = Max(extension, phy->GetChannelSwitchDelay());
513 }
514 }
515 }
516
517 if (extension.IsStrictlyPositive())
518 {
519 NS_LOG_DEBUG("Restarting the timer, check again in " << extension.As(Time::US));
521 Simulator::Schedule(extension,
523 this,
524 linkId);
525 return;
526 }
527
528 // no need to wait further, switch the main PHY back to the preferred link
530}
531
532void
534{
535 NS_LOG_FUNCTION(this);
536
538 {
539 return; // nothing to do
540 }
541
542 // a busy event occurred, check if the main PHY has to switch back to the preferred link
543 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(GetMainPhyId());
544 auto linkId = GetStaMac()->GetLinkForPhy(GetMainPhyId());
545
546 if (!linkId.has_value())
547 {
548 NS_LOG_DEBUG("Main PHY is not operating on any link");
549 return;
550 }
551
552 const auto delay =
553 Simulator::GetDelayLeft(m_switchMainPhyBackEvent) + mainPhy->GetChannelSwitchDelay();
554 if (!GetExpectedAccessWithinDelay(*linkId, delay))
555 {
558 }
559}
560
561void
568
569void
576
577bool
579{
580 NS_LOG_FUNCTION(this << linkId << aci << delay.As(Time::US));
581
582 // the aux PHY is not TX capable; check if main PHY has to switch to the aux PHY's link
583 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
584 const auto mainPhyLinkId = GetStaMac()->GetLinkForPhy(mainPhy);
585
586 // if main PHY is not operating on a link, it is switching, hence do not request another switch
587 if (!mainPhyLinkId.has_value())
588 {
589 NS_LOG_DEBUG("Main PHY is not operating on any link");
590 return false;
591 }
592
593 // if the main PHY is already trying to get access on a link, do not request another switch
595 {
596 NS_LOG_DEBUG("Main PHY is trying to get access on another link");
597 return false;
598 }
599
600 // delay until the earliest time the main PHY can access medium on the aux PHY link
601 auto minDelay = mainPhy->GetChannelSwitchDelay();
602 if (!m_useAuxPhyCca && (GetChannelForAuxPhy(linkId).GetTotalWidth() <
603 GetChannelForMainPhy(linkId).GetTotalWidth()))
604 {
605 // cannot use aux PHY CCA
606 minDelay += GetStaMac()->GetWifiPhy(linkId)->GetPifs();
607 }
608 minDelay = std::max(delay, minDelay);
609
610 if (const auto elapsed = GetElapsedMediumSyncDelayTimer(linkId);
611 elapsed && MediumSyncDelayNTxopsExceeded(linkId) &&
612 (GetMediumSyncDuration() - *elapsed > minDelay))
613 {
614 NS_LOG_DEBUG("No more TXOP attempts allowed on aux PHY link and MSD timer still running");
615 return false;
616 }
617
618 // DoGetDelayUntilAccessRequest has already checked if the main PHY is receiving an ICF
619 if (const auto state = mainPhy->GetState()->GetState();
620 state != WifiPhyState::IDLE && state != WifiPhyState::CCA_BUSY && state != WifiPhyState::RX)
621 {
622 NS_LOG_DEBUG("Cannot request main PHY to switch when in state " << state);
623 return false;
624 }
625
626 // request to switch main PHY if we expect the main PHY to get channel access on this link more
627 // quickly, i.e., if ALL the ACs with queued frames (that can be transmitted on the link on
628 // which the main PHY is currently operating) and with priority higher than or equal to that of
629 // the AC for which Aux PHY gained TXOP have their backoff counter greater than the maximum
630 // between the expected delay in gaining channel access and the channel switch delay (plus PIFS
631 // if we cannot use aux PHY CCA)
632
633 auto requestSwitch = false;
634 const auto now = Simulator::Now();
635
636 for (const auto& [acIndex, ac] : wifiAcList)
637 {
638 if (auto edca = GetStaMac()->GetQosTxop(acIndex);
639 acIndex >= aci && edca->HasFramesToTransmit(linkId))
640 {
641 requestSwitch = true;
642
643 const auto backoffEnd =
644 GetStaMac()->GetChannelAccessManager(*mainPhyLinkId)->GetBackoffEndFor(edca);
645 NS_LOG_DEBUG("Backoff end for " << acIndex
646 << " on preferred link: " << backoffEnd.As(Time::US));
647
648 if ((backoffEnd <= now + minDelay) && edca->HasFramesToTransmit(*mainPhyLinkId))
649 {
650 requestSwitch = false;
651 break;
652 }
653 }
654 }
655
656 return requestSwitch;
657}
658
659void
661{
662 NS_LOG_FUNCTION(this << linkId << aci);
663
665 "This function should only be called if aux PHY is not TX capable");
666 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
667
668 if (mainPhy->IsStateSwitching() && m_mainPhySwitchInfo.to == linkId)
669 {
670 // the main PHY is switching to the link on which the aux PHY gained a TXOP. This can
671 // happen, e.g., if the main PHY was requested to switch to that link before the backoff
672 // counter reached zero. Or, this can happen in case of internal collision: the first AC
673 // requests the main PHY to switch and the second one finds the main PHY to be switching.
674 // In both cases, we do nothing because we have already scheduled the necessary actions
675 NS_LOG_DEBUG("Main PHY is already switching to link " << +linkId);
676 return;
677 }
678
679 if (RequestMainPhyToSwitch(linkId, aci, Time{0}))
680 {
681 const auto auxPhy = GetStaMac()->GetWifiPhy(linkId);
682 const auto pifs = auxPhy->GetSifs() + auxPhy->GetSlot();
683
684 // schedule actions to take based on CCA sensing for a PIFS
685 if (m_useAuxPhyCca || GetChannelForAuxPhy(linkId).GetTotalWidth() >=
686 GetChannelForMainPhy(linkId).GetTotalWidth())
687 {
688 // use aux PHY CCA in the last PIFS interval before main PHY switch end
689 NS_LOG_DEBUG("Schedule CCA check at the end of main PHY switch");
690 m_ccaLastPifs = Simulator::Schedule(mainPhy->GetChannelSwitchDelay(),
692 this,
693 auxPhy,
694 linkId,
695 GetStaMac()->GetQosTxop(aci));
696 }
697 else
698 {
699 // use main PHY CCA in the last PIFS interval after main PHY switch end
700 NS_LOG_DEBUG("Schedule CCA check a PIFS after the end of main PHY switch");
701 m_ccaLastPifs = Simulator::Schedule(mainPhy->GetChannelSwitchDelay() + pifs,
703 this,
704 mainPhy,
705 linkId,
706 GetStaMac()->GetQosTxop(aci));
707 }
708
709 // switch main PHY
710 Time remNav{0};
711 if (const auto mainPhyLinkId = GetStaMac()->GetLinkForPhy(mainPhy))
712 {
713 auto mainPhyNavEnd = GetStaMac()->GetChannelAccessManager(*mainPhyLinkId)->GetNavEnd();
714 remNav = Max(remNav, mainPhyNavEnd - Simulator::Now());
715 }
716
717 SwitchMainPhy(linkId,
718 false,
722 return;
723 }
724
725 // Determine if and when we need to request channel access again for the aux PHY based on
726 // the main PHY state.
727 // Note that, if we have requested the main PHY to switch (above), the function has returned
728 // and the EHT FEM will start a TXOP if medium is idle for a PIFS interval preceding/following
729 // the end of the main PHY channel switch.
730 // If the main PHY has been requested to switch by another aux PHY, this aux PHY will request
731 // channel access again when we have completed the CCA assessment on the other link.
732 // If the state is switching, CCA_BUSY or RX, then we request channel access again for the
733 // aux PHY when the main PHY state is back to IDLE.
734 // If the state is TX, it means that the main PHY is involved in a TXOP. Do nothing because
735 // the channel access will be requested when unblocking links at the end of the TXOP.
736 // If the state is IDLE, then either no AC has traffic to send or the backoff on the link
737 // of the main PHY is shorter than the channel switch delay. In the former case, do
738 // nothing because channel access will be triggered when new packets arrive; in the latter
739 // case, do nothing because the main PHY will start a TXOP and at the end of such TXOP
740 // links will be unblocked and the channel access requested on all links
741
742 Time delay{};
743
745 {
746 delay = std::max(Simulator::GetDelayLeft(m_ccaLastPifs),
748 }
749 else if (mainPhy->IsStateSwitching() || mainPhy->IsStateCcaBusy() || mainPhy->IsStateRx())
750 {
751 delay = mainPhy->GetDelayUntilIdle();
752 NS_ASSERT(delay.IsStrictlyPositive());
753 }
754
755 NS_LOG_DEBUG("Main PHY state is " << mainPhy->GetState()->GetState());
756
757 if (delay.IsZero())
758 {
759 NS_LOG_DEBUG("Do nothing");
760 return;
761 }
762
763 auto edca = GetStaMac()->GetQosTxop(aci);
764 edca->NotifyChannelReleased(linkId); // to set access to NOT_REQUESTED
765
766 NS_LOG_DEBUG("Schedule channel access request on link "
767 << +linkId << " at time " << (Simulator::Now() + delay).As(Time::NS));
768 Simulator::Schedule(delay, [=]() {
769 edca->StartAccessAfterEvent(linkId,
772 });
773}
774
775void
777 AcIndex aci,
778 const Time& delay)
779{
780 NS_LOG_FUNCTION(this << linkId << aci << delay.As(Time::US));
781
783 {
784 NS_LOG_DEBUG("Nothing to do if aux PHY is TX capable");
785 return;
786 }
787
788 if (!delay.IsStrictlyPositive())
789 {
790 NS_LOG_DEBUG("Do nothing if delay is not strictly positive");
791 return;
792 }
793
794 if (GetEhtFem(linkId)->UsingOtherEmlsrLink())
795 {
796 NS_LOG_DEBUG("Do nothing because another EMLSR link is being used");
797 return;
798 }
799
801 {
802 NS_LOG_DEBUG("Do nothing because a frame is being received on another EMLSR link");
803 return;
804 }
805
806 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
807 auto phy = GetStaMac()->GetWifiPhy(linkId);
808
809 if (!phy || phy == mainPhy)
810 {
811 NS_LOG_DEBUG("No aux PHY is operating on link " << +linkId);
812 return;
813 }
814
815 if (!RequestMainPhyToSwitch(linkId, aci, delay))
816 {
817 NS_LOG_DEBUG("Chosen not to request the main PHY to switch");
818 if (const auto untilIdle = mainPhy->GetDelayUntilIdle();
819 untilIdle.IsStrictlyPositive() && untilIdle < delay)
820 {
821 NS_LOG_DEBUG("Retrying in " << untilIdle.As(Time::US));
822 Simulator::Schedule(untilIdle,
824 this,
825 linkId,
826 aci,
827 delay - untilIdle);
828 }
829 return;
830 }
831
832 // switch main PHY
833
834 // use aux PHY CCA (if allowed) if the backoff has already counted down to zero on the aux PHY
835 // link when the main PHY completes the switch
836 const auto edca = GetStaMac()->GetQosTxop(aci);
837 const auto cam = GetStaMac()->GetChannelAccessManager(linkId);
838 const auto auxPhy = GetStaMac()->GetWifiPhy(linkId);
839 const auto switchDelay = mainPhy->GetChannelSwitchDelay();
840 const auto now = Simulator::Now();
841 const auto auxPhyCcaCanBeUsed =
844 const auto backoffEndBeforeSwitch = (cam->GetBackoffEndFor(edca) - now) <= switchDelay;
845
846 if (auxPhyCcaCanBeUsed && backoffEndBeforeSwitch)
847 {
848 NS_LOG_DEBUG("Schedule CCA check at the end of main PHY switch");
849 m_ccaLastPifs = Simulator::Schedule(switchDelay, [=, this]() {
850 // check NAV and CCA only if the backoff actually counted down to zero already
851 if (cam->GetBackoffEndFor(edca) <= Simulator::Now())
852 {
853 CheckNavAndCcaLastPifs(auxPhy, linkId, edca);
854 }
855 });
856 }
857
858 Time remNav{0};
859 if (const auto mainPhyLinkId = GetStaMac()->GetLinkForPhy(mainPhy))
860 {
861 auto mainPhyNavEnd = GetStaMac()->GetChannelAccessManager(*mainPhyLinkId)->GetNavEnd();
862 remNav = Max(remNav, mainPhyNavEnd - now);
863 }
864
865 SwitchMainPhy(linkId,
866 false,
869 EmlsrUlTxopAuxPhyNotTxCapableTrace(aci, delay, remNav));
870
871 // check expected channel access delay when switch is completed
872 Simulator::Schedule(switchDelay, [=, this]() {
873 const auto accessDelay = cam->GetBackoffEndFor(edca) - Simulator::Now();
874
875 if (auxPhyCcaCanBeUsed && backoffEndBeforeSwitch && accessDelay.IsNegative())
876 {
877 // backoff already counted down to zero and we used aux PHY CCA
878 return;
879 }
880
881 const auto pifs = GetStaMac()->GetWifiPhy(linkId)->GetPifs();
882
883 // if the remaining backoff time is shorter than PIFS when the main PHY completes the
884 // switch, we need to schedule a CCA check a PIFS after the end of the main PHY switch
885 if (accessDelay <= pifs)
886 {
887 // use main PHY CCA in the last PIFS interval after main PHY switch end
888 NS_LOG_DEBUG("Schedule CCA check a PIFS after the end of main PHY switch");
891 this,
892 mainPhy,
893 linkId,
894 edca);
895 }
896 else if (!GetExpectedAccessWithinDelay(linkId,
897 accessDelay + m_switchMainPhyBackDelay +
898 mainPhy->GetChannelSwitchDelay()))
899 {
900 NS_LOG_DEBUG("No AC is expected to get backoff soon, switch main PHY back");
902 }
903 else
904 {
905 // the main PHY must stay for some time on this link to check if it gets channel
906 // access. The timer is stopped if a DL or UL TXOP is started. When the timer
907 // expires, the main PHY switches back to the preferred link if SwitchAuxPhy is
908 // false
913 this,
914 linkId);
915 }
916 });
917}
918
919} // namespace ns3
#define Max(a, b)
AdvancedEmlsrManager is an advanced EMLSR manager.
std::pair< bool, Time > DoGetDelayUntilAccessRequest(uint8_t linkId) override
Subclasses have to provide an implementation for this method, that is called by the base class when t...
void InterruptSwitchMainPhyBackTimerIfNeeded()
This method is called by the PHY listener attached to the main PHY when a switch main PHY back timer ...
std::pair< bool, Time > GetDelayUnlessMainPhyTakesOverUlTxop(uint8_t linkId) override
Subclasses have to provide an implementation for this method, that is called by the base class when t...
bool m_useAuxPhyCca
whether the CCA performed in the last PIFS interval by a non-TX capable aux PHY should be used when t...
bool RequestMainPhyToSwitch(uint8_t linkId, AcIndex aci, const Time &delay)
Determine whether the main PHY shall be requested to switch to the link of an aux PHY that is expecte...
void SwitchMainPhyIfTxopToBeGainedByAuxPhy(uint8_t linkId, AcIndex aci, const Time &delay)
This method is called when the given AC of the EMLSR client is expected to get channel access in the ...
void DoNotifyIcfReceived(uint8_t linkId) override
Notify the subclass of the reception of an initial Control frame on the given link.
bool m_allowUlTxopInRx
whether a (main or aux) PHY is allowed to start an UL TXOP if another PHY is receiving a PPDU
void CheckNavAndCcaLastPifs(Ptr< WifiPhy > phy, uint8_t linkId, Ptr< QosTxop > edca)
Use information from NAV and CCA performed by the given PHY on the given link in the last PIFS interv...
void DoNotifyTxopEnd(uint8_t linkId) override
Notify the subclass of the end of a TXOP on the given link.
void NotifyEmlsrModeChanged() override
Notify subclass that EMLSR mode changed.
static TypeId GetTypeId()
Get the type ID.
void DoNotifyUlTxopStart(uint8_t linkId) override
Notify the subclass of the start of an UL TXOP on the given link.
std::shared_ptr< WifiPhyListener > m_phyListener
PHY listener connected to the main PHY while operating on the link of an aux PHY that is not TX capab...
Time m_switchMainPhyBackDelay
duration of the timer started in case of non-TX capable aux PHY when medium is sensed busy during the...
void DoDispose() override
Destructor implementation.
EventId m_switchMainPhyBackEvent
event scheduled in case of non-TX capable aux PHY when medium is sensed busy during the PIFS interval...
void SwitchMainPhyIfTxopGainedByAuxPhy(uint8_t linkId, AcIndex aci) override
Subclasses have to provide an implementation for this method, that is called by the base class when t...
void DoSetWifiMac(Ptr< StaWifiMac > mac) override
Allow subclasses to take actions when the MAC is set.
void SwitchMainPhyBackDelayExpired(uint8_t linkId)
This method is called when the switch main PHY back delay timer (which is started when the main PHY s...
void ReceivedMacHdr(Ptr< WifiPhy > phy, const WifiMacHeader &macHdr, const WifiTxVector &txVector, Time psduDuration)
Possibly take actions when notified of the MAC header of the MPDU being received by the given PHY.
EventId m_ccaLastPifs
event scheduled in case of non-TX capable aux PHY to determine whether TX can be started based on whe...
bool m_interruptSwitching
whether a main PHY switching can be interrupted to start switching to another link
DefaultEmlsrManager is the default EMLSR manager.
void NotifyEmlsrModeChanged() override
Notify subclass that EMLSR mode changed.
Ptr< WifiPhy > m_auxPhyToReconnect
Aux PHY the ChannelAccessManager of the link on which the main PHY is operating has to connect a list...
bool m_switchAuxPhy
whether Aux PHY should switch channel to operate on the link on which the Main PHY was operating befo...
Time GetTimeToCtsEnd(uint8_t linkId) const
This function is intended to be called when an aux PHY is about to transmit an RTS on the given link ...
std::map< uint8_t, Time > m_switchMainPhyOnRtsTx
link ID-indexed map of the time when an RTS that requires the main PHY to switch link is expected to ...
void SwitchMainPhyBackToPreferredLink(uint8_t linkId, EmlsrMainPhySwitchTrace &&traceInfo)
This method can only be called when aux PHYs do not switch link.
std::pair< bool, Time > GetDelayUnlessMainPhyTakesOverUlTxop(uint8_t linkId) override
Subclasses have to provide an implementation for this method, that is called by the base class when t...
Time GetMediumSyncDuration() const
bool GetExpectedAccessWithinDelay(uint8_t linkId, const Time &delay) const
Get whether channel access is expected to be granted on the given link within the given delay to an A...
bool m_useNotifiedMacHdr
whether to use the information about the MAC header of the MPDU being received (if notified by the PH...
bool m_auxPhyTxCapable
whether Aux PHYs are capable of transmitting PPDUs
bool MediumSyncDelayNTxopsExceeded(uint8_t linkId)
Return whether no more TXOP attempt is allowed on the given link.
Ptr< EhtFrameExchangeManager > GetEhtFem(uint8_t linkId) const
void NotifyUlTxopStart(uint8_t linkId)
Notify the start of an UL TXOP on the given link.
void NotifyTxopEnd(uint8_t linkId, bool ulTxopNotStarted=false, bool ongoingDlTxop=false)
Notify the end of a TXOP on the given link.
uint8_t m_mainPhyId
ID of main PHY (position in the vector of PHYs held by WifiNetDevice)
bool GetAuxPhyTxCapable() const
const std::set< uint8_t > & GetEmlsrLinks() const
std::optional< Time > GetElapsedMediumSyncDelayTimer(uint8_t linkId) const
Check whether the MediumSyncDelay timer is running for the STA operating on the given link.
const WifiPhyOperatingChannel & GetChannelForMainPhy(uint8_t linkId) const
void SwitchMainPhy(uint8_t linkId, bool noSwitchDelay, bool resetBackoff, bool requestAccess, EmlsrMainPhySwitchTrace &&traceInfo)
Switch channel on the Main PHY so that it operates on the given link.
static constexpr bool RESET_BACKOFF
reset backoff on main PHY switch
MainPhySwitchInfo m_mainPhySwitchInfo
main PHY switch info
static constexpr bool REQUEST_ACCESS
request channel access when PHY switch ends
static constexpr bool DONT_REQUEST_ACCESS
do not request channel access when PHY switch ends
Ptr< StaWifiMac > GetStaMac() const
uint8_t GetMainPhyId() const
const WifiPhyOperatingChannel & GetChannelForAuxPhy(uint8_t linkId) const
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.
static constexpr bool DONT_RESET_BACKOFF
do not reset backoff on main PHY switch
PHY listener connected to the main PHY while operating on the link of an aux PHY that is not TX capab...
EmlsrPhyListener(Ptr< AdvancedEmlsrManager > emlsrManager)
Constructor.
void NotifyCcaBusyStart(Time, WifiChannelListType, const std::vector< Time > &) override
void NotifyWakeup() override
Notify listeners that we woke up.
void NotifyOff() override
Notify listeners that we went to switch off.
void NotifyRxEndError() override
We have received the last bit of a packet for which NotifyRxStart was invoked first and,...
void NotifyTxStart(Time, dBm_u) override
void NotifySwitchingStart(Time) override
void NotifyRxStart(Time) override
void NotifyRxEndOk() override
We have received the last bit of a packet for which NotifyRxStart was invoked first and,...
void NotifyOn() override
Notify listeners that we went to switch on.
void NotifySleep() override
Notify listeners that we went to sleep.
Ptr< AdvancedEmlsrManager > m_emlsrManager
the EMLSR manager
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
virtual bool StartTransmission(Ptr< Txop > dcf, MHz_u allowedWidth)
Request the FrameExchangeManager to start a frame exchange sequence.
bool IsBroadcast() const
virtual void DoDispose()
Destructor implementation.
Definition object.cc:433
Smart pointer class similar to boost::intrusive_ptr.
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition simulator.h:560
static Time Now()
Return the current simulation virtual time.
Definition simulator.cc:197
static EventId ScheduleNow(FUNC f, Ts &&... args)
Schedule an event to expire Now.
Definition simulator.h:594
static Time GetDelayLeft(const EventId &id)
Get the remaining time until this event will execute.
Definition simulator.cc:206
Simulation virtual time values and global simulation resolution.
Definition nstime.h:94
TimeWithUnit As(const Unit unit=Time::AUTO) const
Attach a unit to a Time, to facilitate output in a specific unit.
Definition time.cc:404
bool IsStrictlyPositive() const
Exactly equivalent to t > 0.
Definition nstime.h:340
@ US
microsecond
Definition nstime.h:107
@ MS
millisecond
Definition nstime.h:106
@ NS
nanosecond
Definition nstime.h:108
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
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition type-id.cc:1001
Implements the IEEE 802.11 MAC header.
bool IsCts() const
Return true if the header is a CTS header.
Mac48Address GetAddr1() const
Return the address in the Address 1 field.
receive notifications about PHY events.
MHz_u GetTotalWidth() const
Return the width of the whole 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
#define NS_ABORT_MSG_UNLESS(cond, msg)
Abnormal program termination if a condition is false, with a message.
Definition abort.h:133
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition log.h:191
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition log.h:257
#define NS_LOG_FUNCTION_NOARGS()
Output the name of the function.
#define NS_LOG_FUNCTION(parameters)
If log level LOG_FUNCTION is enabled, this macro will output all input parameters separated by ",...
#define NS_OBJECT_ENSURE_REGISTERED(type)
Register an Object subclass with the TypeId system.
Definition object-base.h:35
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1356
WifiChannelListType
Enumeration of the possible channel-list parameter elements defined in Table 8-5 of IEEE 802....
AcIndex
This enumeration defines the Access Categories as an enumeration with values corresponding to the AC ...
Definition qos-utils.h:62
Definition first.py:1
Every class exported by the ns3 library is enclosed in the ns3 namespace.
Ptr< const AttributeChecker > MakeBooleanChecker()
Definition boolean.cc:113
@ SWITCHING
The PHY layer is switching to other channel.
@ IDLE
The PHY layer is IDLE.
@ CCA_BUSY
The PHY layer has sense the medium busy through the CCA mechanism.
@ RX
The PHY layer is receiving a packet.
Ptr< const AttributeAccessor > MakeTimeAccessor(T1 a1)
Definition nstime.h:1432
Callback< R, Args... > MakeCallback(R(T::*memPtr)(Args...), OBJ objPtr)
Build Callbacks for class method members which take varying numbers of arguments and potentially retu...
Definition callback.h:684
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
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Definition boolean.h:70
Ptr< const AttributeChecker > MakeTimeChecker()
Helper to make an unbounded Time checker.
Definition nstime.h:1452
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.
Struct to trace that main PHY switched to leave a link on which an aux PHY was expected to gain a TXO...
Struct to trace that main PHY switched when a (DL or UL) TXOP ended.
Struct to trace that main PHY switched to operate on a link on which an aux PHY that is not TX capabl...