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.h"
17
18#include <algorithm>
19
20namespace ns3
21{
22
23NS_LOG_COMPONENT_DEFINE("AdvancedEmlsrManager");
24
25NS_OBJECT_ENSURE_REGISTERED(AdvancedEmlsrManager);
26
27TypeId
29{
30 static TypeId tid =
31 TypeId("ns3::AdvancedEmlsrManager")
33 .SetGroupName("Wifi")
34 .AddConstructor<AdvancedEmlsrManager>()
35 .AddAttribute("UseNotifiedMacHdr",
36 "Whether to use the information about the MAC header of the MPDU "
37 "being received, if notified by the PHY.",
38 BooleanValue(true),
41 .AddAttribute("AllowUlTxopInRx",
42 "Whether a (main or aux) PHY is allowed to start an UL TXOP if "
43 "another PHY is receiving a PPDU (possibly starting a DL TXOP). "
44 "If this attribute is true, the PPDU may be dropped.",
45 BooleanValue(false),
48 .AddAttribute("InterruptSwitch",
49 "Whether the main PHY can be interrupted while switching to start "
50 "switching to another link.",
51 BooleanValue(false),
54 .AddAttribute("UseAuxPhyCca",
55 "Whether the CCA performed in the last PIFS interval by a non-TX "
56 "capable aux PHY should be used when the main PHY ends switching to "
57 "the aux PHY's link to determine whether TX can start or not (and what "
58 "bandwidth can be used for transmission) independently of whether the "
59 "aux PHY bandwidth is smaller than the main PHY bandwidth or not.",
60 BooleanValue(false),
63 .AddAttribute("SwitchMainPhyBackDelay",
64 "Duration of the timer started in case of non-TX capable aux PHY (that "
65 "does not switch link) when medium is sensed busy during the PIFS "
66 "interval preceding/following the main PHY switch end. When the timer "
67 "expires, the main PHY is switched back to the preferred link.",
71 return tid;
72}
73
78
83
84void
86{
87 NS_LOG_FUNCTION(this);
88 for (auto phy : GetStaMac()->GetDevice()->GetPhys())
89 {
90 phy->TraceDisconnectWithoutContext(
91 "PhyRxMacHeaderEnd",
93 }
95}
96
97void
99{
100 NS_LOG_FUNCTION(this);
101
102 // disconnect callbacks on all links
103 for (const auto& linkId : GetStaMac()->GetLinkIds())
104 {
105 GetStaMac()->GetChannelAccessManager(linkId)->TraceDisconnectWithoutContext(
106 "NSlotsLeftAlert",
108 }
109
110 // connect callbacks on EMLSR links
111 for (const auto& emlsrLinkId : GetEmlsrLinks())
112 {
113 GetStaMac()
114 ->GetChannelAccessManager(emlsrLinkId)
115 ->TraceConnectWithoutContext(
116 "NSlotsLeftAlert",
118 }
119
121}
122
123void
125{
126 NS_LOG_FUNCTION(this << mac);
127
128 for (auto phy : GetStaMac()->GetDevice()->GetPhys())
129 {
130 phy->TraceConnectWithoutContext(
131 "PhyRxMacHeaderEnd",
133 }
134}
135
136std::pair<bool, Time>
138{
139 NS_LOG_FUNCTION(this << linkId);
140
141 // prevent or allow an UL TXOP depending on whether another PHY is receiving a PPDU
142 for (const auto id : GetStaMac()->GetLinkIds())
143 {
144 if (id != linkId && GetStaMac()->IsEmlsrLink(id))
145 {
146 auto phy = GetStaMac()->GetWifiPhy(id);
147
148 if (auto macHdr = GetEhtFem(id)->GetReceivedMacHdr(); macHdr && m_useNotifiedMacHdr)
149 {
150 NS_ASSERT(phy &&
151 phy->GetState()->GetLastTime({WifiPhyState::RX}) == Simulator::Now());
152 // we are receiving the MAC payload of a PSDU; if the PSDU being received on
153 // another link is an ICF, give up the TXOP and restart channel access at the
154 // end of PSDU reception. Note that we cannot be sure that the PSDU being received
155 // is an ICF addressed to us until we receive the entire PSDU
156 if (const auto& hdr = macHdr->get();
157 hdr.IsTrigger() &&
158 (hdr.GetAddr1().IsBroadcast() || hdr.GetAddr1() == GetEhtFem(id)->GetAddress()))
159 {
160 return {false, phy->GetDelayUntilIdle()};
161 }
162 continue;
163 }
164
165 if (phy && phy->IsReceivingPhyHeader())
166 {
167 // we don't know yet the type of the frame being received; prevent or allow
168 // the UL TXOP based on user configuration
170 {
171 // retry channel access after the end of the current PHY header field
172 return {false, phy->GetDelayUntilIdle()};
173 }
174 continue;
175 }
176
177 if (phy && phy->IsStateRx())
178 {
179 // we don't know yet the type of the frame being received; prevent or allow
180 // the UL TXOP based on user configuration
182 {
184 {
185 // restart channel access at the end of PSDU reception
186 return {false, phy->GetDelayUntilIdle()};
187 }
188
189 // retry channel access after the expected end of the MAC header reception
191 4 /* A-MPDU subframe header length */;
192 auto ongoingRxInfo = GetEhtFem(id)->GetOngoingRxInfo();
193 // if a PHY is in RX state, it should have info about received MAC header.
194 // The exception is represented by this situation:
195 // - an aux PHY is disconnected from the MAC stack because the main PHY is
196 // operating on its link
197 // - the main PHY notifies the MAC header info to the FEM and then leaves the
198 // link (e.g., because it recognizes that the MPDU is not addressed to the
199 // EMLSR client). Disconnecting the main PHY from the MAC stack causes the
200 // MAC header info to be discarded by the FEM
201 // - the aux PHY is re-connected to the MAC stack and is still in RX state
202 // when the main PHY gets channel access on another link (and we get here)
203 if (!ongoingRxInfo.has_value())
204 {
205 NS_ASSERT_MSG(phy != GetStaMac()->GetDevice()->GetPhy(GetMainPhyId()),
206 "Main PHY should have MAC header info when in RX state");
207 // we are in the situation described above; if the MPDU being received
208 // by the aux PHY is not addressed to the EMLSR client, we can ignore it
209 continue;
210 }
211 const auto& txVector = ongoingRxInfo->get().txVector;
212 if (txVector.IsMu())
213 {
214 // this is not an ICF, ignore it
215 continue;
216 }
217 auto macHdrDuration = DataRate(txVector.GetMode().GetDataRate(txVector))
218 .CalculateBytesTxTime(macHdrSize);
219 const auto timeSinceRxStart =
220 Simulator::Now() - phy->GetState()->GetLastTime({WifiPhyState::CCA_BUSY});
221 return {false, Max(macHdrDuration - timeSinceRxStart, Time{0})};
222 }
223 continue;
224 }
225 }
226 }
227
228 if (GetStaMac()->GetWifiPhy(linkId) == GetStaMac()->GetDevice()->GetPhy(GetMainPhyId()) &&
230 {
231 // main PHY has got access on the link it switched to (because the aux PHY is not TX
232 // capable) before a PIFS interval was elapsed: do not start the TXOP now
233 return {false, Time{0}};
234 }
235
236 return {true, Time{0}};
237}
238
239void
241 const WifiMacHeader& macHdr,
242 const WifiTxVector& txVector,
243 Time psduDuration)
244{
245 auto linkId = GetStaMac()->GetLinkForPhy(phy);
246 if (!linkId.has_value())
247 {
248 return;
249 }
250 NS_LOG_FUNCTION(this << *linkId << macHdr << txVector << psduDuration.As(Time::MS));
251
252 auto& ongoingTxopEnd = GetEhtFem(*linkId)->GetOngoingTxopEndEvent();
253
254 if (m_useNotifiedMacHdr && ongoingTxopEnd.IsPending() &&
255 macHdr.GetAddr1() != GetEhtFem(*linkId)->GetAddress() && !macHdr.GetAddr1().IsBroadcast() &&
256 !(macHdr.IsCts() && macHdr.GetAddr1() == GetEhtFem(*linkId)->GetBssid() /* CTS-to-self */))
257 {
258 // the EMLSR client is no longer involved in the TXOP and switching to listening mode
259 ongoingTxopEnd.Cancel();
260 // this method is a callback connected to the PhyRxMacHeaderEnd trace source of WifiPhy
261 // and is called within a for loop that executes all the callbacks. The call to NotifyTxop
262 // below leads the main PHY to be connected back to the preferred link, thus
263 // the ResetPhy() method of the FEM on the auxiliary link is called, which disconnects
264 // another callback (FEM::ReceivedMacHdr) from the PhyRxMacHeaderEnd trace source of
265 // the main PHY, thus invalidating the list of callbacks on which the for loop iterates.
266 // Hence, schedule the call to NotifyTxopEnd to execute it outside such for loop.
267 Simulator::ScheduleNow(&AdvancedEmlsrManager::NotifyTxopEnd, this, *linkId, false, false);
268 }
269}
270
271void
273{
274 NS_LOG_FUNCTION(this << linkId);
275
276 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
277
278 if (m_switchAuxPhy && (!mainPhy->IsStateSwitching() || !m_interruptSwitching))
279 {
280 NS_LOG_DEBUG("SwitchAuxPhy true, nothing to do");
281 return;
282 }
283
285 {
286 NS_LOG_DEBUG("SwitchAuxPhy false, nothing to do");
287 return;
288 }
289
290 // we get here if:
291 // - SwitchAuxPhy is true, the main PHY is switching and switching can be interrupted
292 // or
293 // - SwitchAuxPhy is false and there is an aux PHY to reconnect
294
295 // Note that the main PHY may be switching at the end of a TXOP when, e.g., the main PHY
296 // starts switching to a link on which an aux PHY gained a TXOP and sent an RTS, but the CTS
297 // is not received and the UL TXOP ends before the main PHY channel switch is completed.
298 // In such cases, wait until the main PHY channel switch is completed (unless the channel
299 // switching can be interrupted) before requesting a new channel switch. Given that the
300 // TXOP ended, the event to put the aux PHY to sleep can be cancelled.
301 // Backoff shall not be reset on the link left by the main PHY because a TXOP ended and
302 // a new backoff value must be generated.
304
305 if (m_switchAuxPhy || !mainPhy->IsStateSwitching() || m_interruptSwitching)
306 {
309 "Aux PHY next link ID should have a value when interrupting a main PHY switch");
310 uint8_t nextLinkId = m_switchAuxPhy ? m_mainPhySwitchInfo.from : GetMainPhyId();
312 }
313 else
314 {
315 // delay link switch until current channel switching is completed
316 Simulator::Schedule(mainPhy->GetDelayUntilIdle(), [=, this]() {
317 // request the main PHY to switch back to the preferred link only if in the meantime
318 // no TXOP started on another link (which will require the main PHY to switch link)
319 if (!GetEhtFem(linkId)->UsingOtherEmlsrLink())
320 {
321 SwitchMainPhy(GetMainPhyId(), false, DONT_RESET_BACKOFF, REQUEST_ACCESS);
322 }
323 });
324 }
325}
326
327std::pair<bool, Time>
328AdvancedEmlsrManager::GetDelayUnlessMainPhyTakesOverUlTxop(uint8_t linkId)
329{
330 NS_LOG_FUNCTION(this << linkId);
331
332 if (!m_interruptSwitching)
333 {
334 return DefaultEmlsrManager::GetDelayUnlessMainPhyTakesOverUlTxop(linkId);
335 }
336
337 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
338 auto state = mainPhy->GetState()->GetState();
339
340 NS_ABORT_MSG_UNLESS(state == WifiPhyState::SWITCHING || state == WifiPhyState::RX ||
341 state == WifiPhyState::IDLE || state == WifiPhyState::CCA_BUSY,
342 "Main PHY cannot be in state " << state);
343
344 auto timeToCtsEnd = GetTimeToCtsEnd(linkId);
345 auto switchingTime = mainPhy->GetChannelSwitchDelay();
346
347 if (switchingTime > timeToCtsEnd)
348 {
349 // switching takes longer than RTS/CTS exchange, release channel
350 NS_LOG_DEBUG("Not enough time for main PHY to switch link (main PHY state: "
351 << mainPhy->GetState()->GetState() << ")");
352 // retry channel access when the CTS was expected to be received
353 return {false, timeToCtsEnd};
354 }
355
356 // TXOP can be started, schedule main PHY switch. Main PHY shall terminate the channel switch
357 // at the end of CTS reception
358 const auto delay = timeToCtsEnd - switchingTime;
359
360 NS_ASSERT(delay.IsPositive());
361 NS_LOG_DEBUG("Schedule main Phy switch in " << delay.As(Time::US));
362 m_ulMainPhySwitch[linkId] = Simulator::Schedule(delay,
363 &AdvancedEmlsrManager::SwitchMainPhy,
364 this,
365 linkId,
366 false,
367 RESET_BACKOFF,
368 DONT_REQUEST_ACCESS);
369
370 return {true, Time{0}};
371}
372
373void
374AdvancedEmlsrManager::CheckNavAndCcaLastPifs(Ptr<WifiPhy> phy, uint8_t linkId, Ptr<QosTxop> edca)
375{
376 NS_LOG_FUNCTION(this << phy->GetPhyId() << linkId << edca->GetAccessCategory());
377
378 const auto caManager = GetStaMac()->GetChannelAccessManager(linkId);
379 const auto pifs = phy->GetSifs() + phy->GetSlot();
380
381 const auto isBusy = caManager->IsBusy(); // check NAV and CCA on primary20
382 // check CCA on the entire channel
383 auto width = caManager->GetLargestIdlePrimaryChannel(pifs, Simulator::Now());
384
385 if (!isBusy && width > 0)
386 {
387 // medium idle, start TXOP
388 width = std::min(width, GetChannelForMainPhy(linkId).GetTotalWidth());
389
390 // if this function is called at the end of the main PHY switch, it is executed before the
391 // main PHY is connected to this link in order to use the CCA information of the aux PHY.
392 // Schedule now the TXOP start so that we first connect the main PHY to this link.
393 m_ccaLastPifs = Simulator::ScheduleNow([=, this]() {
394 if (GetEhtFem(linkId)->HeFrameExchangeManager::StartTransmission(edca, width))
395 {
396 NotifyUlTxopStart(linkId);
397 }
398 else if (!m_switchAuxPhy)
399 {
400 // switch main PHY back to preferred link if SwitchAuxPhy is false
401 SwitchMainPhyBackToPreferredLink(linkId);
402 }
403 });
404 }
405 else
406 {
407 // medium busy, restart channel access
408 NS_LOG_DEBUG("Medium busy in the last PIFS interval");
409 edca->NotifyChannelReleased(linkId); // to set access to NOT_REQUESTED
410 edca->StartAccessAfterEvent(linkId,
411 Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT,
412 Txop::CHECK_MEDIUM_BUSY);
413
414 // the main PHY must stay for some time on this link to check if it gets channel access.
415 // The timer is stopped if a DL or UL TXOP is started. When the timer expires, the main PHY
416 // switches back to the preferred link if SwitchAuxPhy is false
417 m_switchMainPhyBackEvent.Cancel();
418 m_switchMainPhyBackEvent = Simulator::Schedule(m_switchMainPhyBackDelay, [this, linkId]() {
419 if (!m_switchAuxPhy)
420 {
421 SwitchMainPhyBackToPreferredLink(linkId);
422 }
423 });
424 }
425}
426
427void
428AdvancedEmlsrManager::DoNotifyIcfReceived(uint8_t linkId)
429{
430 NS_LOG_FUNCTION(this << linkId);
431 m_switchMainPhyBackEvent.Cancel();
432 m_ccaLastPifs.Cancel();
433}
434
435void
436AdvancedEmlsrManager::DoNotifyUlTxopStart(uint8_t linkId)
437{
438 NS_LOG_FUNCTION(this << linkId);
439 m_switchMainPhyBackEvent.Cancel();
440 m_ccaLastPifs.Cancel();
441}
442
443bool
444AdvancedEmlsrManager::RequestMainPhyToSwitch(uint8_t linkId, AcIndex aci, const Time& delay)
445{
446 NS_LOG_FUNCTION(this << linkId << aci << delay.As(Time::US));
447
448 // the aux PHY is not TX capable; check if main PHY has to switch to the aux PHY's link
449 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
450 const auto mainPhyLinkId = GetStaMac()->GetLinkForPhy(mainPhy);
451
452 // if main PHY is not operating on a link, it is switching, hence do not request another switch
453 if (!mainPhyLinkId.has_value())
454 {
455 NS_LOG_DEBUG("Main PHY is not operating on any link");
456 return false;
457 }
458
459 // if the main PHY is already trying to get access on a link, do not request another switch
460 if (m_ccaLastPifs.IsPending() || m_switchMainPhyBackEvent.IsPending())
461 {
462 NS_LOG_DEBUG("Main PHY is trying to get access on another link");
463 return false;
464 }
465
466 switch (mainPhy->GetState()->GetState())
467 {
468 case WifiPhyState::IDLE:
469 // proceed to try requesting main PHY to switch
470 break;
471 case WifiPhyState::CCA_BUSY:
472 // if the main PHY is receiving the PHY header of a PPDU, we decide to proceed or give up
473 // based on the AllowUlTxopInRx attribute
474 if (mainPhy->IsReceivingPhyHeader() && !m_allowUlTxopInRx)
475 {
476 NS_LOG_DEBUG("Main PHY receiving PHY header and AllowUlTxopInRx is false");
477 return false;
478 }
479 break;
480 case WifiPhyState::RX:
481 if (auto macHdr = GetEhtFem(*mainPhyLinkId)->GetReceivedMacHdr())
482 {
483 // information on the MAC header of the PSDU being received is available; if we cannot
484 // use it or the main PHY is receiving an ICF, give up requesting main PHY to switch
485 if (const auto& hdr = macHdr->get();
486 !m_useNotifiedMacHdr ||
487 (hdr.IsTrigger() && (hdr.GetAddr1().IsBroadcast() ||
488 hdr.GetAddr1() == GetEhtFem(*mainPhyLinkId)->GetAddress())))
489 {
490 NS_LOG_DEBUG("Receiving an ICF or cannot use MAC header information");
491 return false;
492 }
493 }
494 // information on the MAC header of the PSDU being received is not available, we decide to
495 // proceed or give up based on the AllowUlTxopInRx attribute
496 else if (!m_allowUlTxopInRx)
497 {
498 NS_LOG_DEBUG("Receiving PSDU, no MAC header information, AllowUlTxopInRx is false");
499 return false;
500 }
501 break;
502 default:
503 NS_LOG_DEBUG("Cannot request main PHY to switch when in state "
504 << mainPhy->GetState()->GetState());
505 return false;
506 }
507
508 // request to switch main PHY if we expect the main PHY to get channel access on this link more
509 // quickly, i.e., if ALL the ACs with queued frames (that can be transmitted on the link on
510 // which the main PHY is currently operating) and with priority higher than or equal to that of
511 // the AC for which Aux PHY gained TXOP have their backoff counter greater than the maximum
512 // between the expected delay in gaining channel access and the channel switch delay plus PIFS
513
514 auto requestSwitch = false;
515 const auto now = Simulator::Now();
516
517 for (const auto& [acIndex, ac] : wifiAcList)
518 {
519 if (auto edca = GetStaMac()->GetQosTxop(acIndex);
520 acIndex >= aci && edca->HasFramesToTransmit(linkId))
521 {
522 requestSwitch = true;
523
524 const auto backoffEnd =
525 GetStaMac()->GetChannelAccessManager(*mainPhyLinkId)->GetBackoffEndFor(edca);
526 NS_LOG_DEBUG("Backoff end for " << acIndex
527 << " on preferred link: " << backoffEnd.As(Time::US));
528
529 if (const auto minDelay = std::max(delay,
530 mainPhy->GetChannelSwitchDelay() +
531 GetStaMac()->GetWifiPhy(linkId)->GetPifs());
532 backoffEnd <= now + minDelay && edca->HasFramesToTransmit(*mainPhyLinkId))
533 {
534 requestSwitch = false;
535 break;
536 }
537 }
538 }
539
540 return requestSwitch;
541}
542
543void
544AdvancedEmlsrManager::SwitchMainPhyIfTxopGainedByAuxPhy(uint8_t linkId, AcIndex aci)
545{
546 NS_LOG_FUNCTION(this << linkId << aci);
547
548 NS_ASSERT_MSG(!m_auxPhyTxCapable,
549 "This function should only be called if aux PHY is not TX capable");
550 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
551
552 if (mainPhy->IsStateSwitching() && m_mainPhySwitchInfo.to == linkId)
553 {
554 // the main PHY is switching to the link on which the aux PHY gained a TXOP. This can
555 // happen, e.g., if the main PHY was requested to switch to that link before the backoff
556 // counter reached zero. Or, this can happen in case of internal collision: the first AC
557 // requests the main PHY to switch and the second one finds the main PHY to be switching.
558 // In both cases, we do nothing because we have already scheduled the necessary actions
559 NS_LOG_DEBUG("Main PHY is already switching to link " << +linkId);
560 return;
561 }
562
563 if (RequestMainPhyToSwitch(linkId, aci, Time{0}))
564 {
565 const auto auxPhy = GetStaMac()->GetWifiPhy(linkId);
566 const auto pifs = auxPhy->GetSifs() + auxPhy->GetSlot();
567
568 // schedule actions to take based on CCA sensing for a PIFS
569 if (m_useAuxPhyCca || GetChannelForAuxPhy(linkId).GetTotalWidth() >=
570 GetChannelForMainPhy(linkId).GetTotalWidth())
571 {
572 // use aux PHY CCA in the last PIFS interval before main PHY switch end
573 NS_LOG_DEBUG("Schedule CCA check at the end of main PHY switch");
574 m_ccaLastPifs = Simulator::Schedule(mainPhy->GetChannelSwitchDelay(),
575 &AdvancedEmlsrManager::CheckNavAndCcaLastPifs,
576 this,
577 auxPhy,
578 linkId,
579 GetStaMac()->GetQosTxop(aci));
580 }
581 else
582 {
583 // use main PHY CCA in the last PIFS interval after main PHY switch end
584 NS_LOG_DEBUG("Schedule CCA check a PIFS after the end of main PHY switch");
585 m_ccaLastPifs = Simulator::Schedule(mainPhy->GetChannelSwitchDelay() + pifs,
586 &AdvancedEmlsrManager::CheckNavAndCcaLastPifs,
587 this,
588 mainPhy,
589 linkId,
590 GetStaMac()->GetQosTxop(aci));
591 }
592
593 // switch main PHY
594 SwitchMainPhy(linkId, false, RESET_BACKOFF, DONT_REQUEST_ACCESS);
595 return;
596 }
597
598 // Determine if and when we need to request channel access again for the aux PHY based on
599 // the main PHY state.
600 // Note that, if we have requested the main PHY to switch (above), the function has returned
601 // and the EHT FEM will start a TXOP if medium is idle for a PIFS interval preceding/following
602 // the end of the main PHY channel switch.
603 // If the main PHY has been requested to switch by another aux PHY, this aux PHY will request
604 // channel access again when we have completed the CCA assessment on the other link.
605 // If the state is switching, CCA_BUSY or RX, then we request channel access again for the
606 // aux PHY when the main PHY state is back to IDLE.
607 // If the state is TX, it means that the main PHY is involved in a TXOP. Do nothing because
608 // the channel access will be requested when unblocking links at the end of the TXOP.
609 // If the state is IDLE, then either no AC has traffic to send or the backoff on the link
610 // of the main PHY is shorter than the channel switch delay. In the former case, do
611 // nothing because channel access will be triggered when new packets arrive; in the latter
612 // case, do nothing because the main PHY will start a TXOP and at the end of such TXOP
613 // links will be unblocked and the channel access requested on all links
614
615 Time delay{};
616
617 if (m_ccaLastPifs.IsPending() || m_switchMainPhyBackEvent.IsPending())
618 {
619 delay = std::max(Simulator::GetDelayLeft(m_ccaLastPifs),
620 Simulator::GetDelayLeft(m_switchMainPhyBackEvent));
621 }
622 else if (mainPhy->IsStateSwitching() || mainPhy->IsStateCcaBusy() || mainPhy->IsStateRx())
623 {
624 delay = mainPhy->GetDelayUntilIdle();
625 NS_ASSERT(delay.IsStrictlyPositive());
626 }
627
628 NS_LOG_DEBUG("Main PHY state is " << mainPhy->GetState()->GetState());
629
630 if (delay.IsZero())
631 {
632 NS_LOG_DEBUG("Do nothing");
633 return;
634 }
635
636 auto edca = GetStaMac()->GetQosTxop(aci);
637 edca->NotifyChannelReleased(linkId); // to set access to NOT_REQUESTED
638
639 NS_LOG_DEBUG("Schedule channel access request on link "
640 << +linkId << " at time " << (Simulator::Now() + delay).As(Time::NS));
641 Simulator::Schedule(delay, [=]() {
642 edca->StartAccessAfterEvent(linkId,
643 Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT,
644 Txop::CHECK_MEDIUM_BUSY);
645 });
646}
647
648void
649AdvancedEmlsrManager::SwitchMainPhyIfTxopToBeGainedByAuxPhy(uint8_t linkId,
650 AcIndex aci,
651 const Time& delay)
652{
653 NS_LOG_FUNCTION(this << linkId << aci << delay.As(Time::US));
654
655 if (m_auxPhyTxCapable)
656 {
657 NS_LOG_DEBUG("Nothing to do if aux PHY is TX capable");
658 return;
659 }
660
661 if (!delay.IsStrictlyPositive())
662 {
663 NS_LOG_DEBUG("Do nothing if delay is not strictly positive");
664 return;
665 }
666
667 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
668 auto phy = GetStaMac()->GetWifiPhy(linkId);
669
670 if (!phy || phy == mainPhy)
671 {
672 NS_LOG_DEBUG("No aux PHY is operating on link " << +linkId);
673 return;
674 }
675
676 if (!RequestMainPhyToSwitch(linkId, aci, delay))
677 {
678 NS_LOG_DEBUG("Chosen not to request the main PHY to switch");
679 if (const auto untilIdle = mainPhy->GetDelayUntilIdle();
680 untilIdle.IsStrictlyPositive() && untilIdle < delay)
681 {
682 NS_LOG_DEBUG("Retrying in " << untilIdle.As(Time::US));
683 Simulator::Schedule(untilIdle,
684 &AdvancedEmlsrManager::SwitchMainPhyIfTxopToBeGainedByAuxPhy,
685 this,
686 linkId,
687 aci,
688 delay - untilIdle);
689 }
690 return;
691 }
692
693 // switch main PHY
694 SwitchMainPhy(linkId, false, RESET_BACKOFF, DONT_REQUEST_ACCESS);
695
696 // if the remaining backoff time is shorter than PIFS when the main PHY completes the switch,
697 // we need to schedule a CCA check a PIFS after the end of the main PHY switch
698 Simulator::Schedule(mainPhy->GetChannelSwitchDelay(), [=, this]() {
699 const auto edca = GetStaMac()->GetQosTxop(aci);
700 const auto pifs = GetStaMac()->GetWifiPhy(linkId)->GetPifs();
701 if (GetStaMac()->GetChannelAccessManager(linkId)->GetBackoffEndFor(edca) <=
702 Simulator::Now() + pifs)
703 {
704 // use main PHY CCA in the last PIFS interval after main PHY switch end
705 NS_LOG_DEBUG("Schedule CCA check a PIFS after the end of main PHY switch");
706 m_ccaLastPifs = Simulator::Schedule(pifs,
707 &AdvancedEmlsrManager::CheckNavAndCcaLastPifs,
708 this,
709 mainPhy,
710 linkId,
711 edca);
712 }
713 });
714
715 // the main PHY must stay for some time on this link to check if it gets channel access.
716 // The timer is stopped if a DL or UL TXOP is started. When the timer expires, the main PHY
717 // switches back to the preferred link if SwitchAuxPhy is false
718 const auto minDelay =
719 std::max(delay,
720 mainPhy->GetChannelSwitchDelay() + GetStaMac()->GetWifiPhy(linkId)->GetPifs());
721 m_switchMainPhyBackEvent.Cancel();
722 m_switchMainPhyBackEvent =
723 Simulator::Schedule(minDelay + m_switchMainPhyBackDelay, [this, linkId]() {
724 if (!m_switchAuxPhy)
725 {
726 SwitchMainPhyBackToPreferredLink(linkId);
727 }
728 });
729}
730
731} // 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...
bool m_useAuxPhyCca
whether the CCA performed in the last PIFS interval by a non-TX capable aux PHY should be used when t...
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 ...
bool m_allowUlTxopInRx
whether a (main or aux) PHY is allowed to start an UL TXOP if another PHY is receiving a PPDU
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.
bool m_useNotifiedMacHdr
whether to use the information about the MAC header of the MPDU being received (if notified by the PH...
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.
void DoSetWifiMac(Ptr< StaWifiMac > mac) override
Allow subclasses to take actions when the MAC is set.
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
AttributeValue implementation for Boolean.
Definition boolean.h:26
Class for representing data rates.
Definition data-rate.h:78
Time CalculateBytesTxTime(uint32_t bytes) const
Calculate transmission time.
Definition data-rate.cc:220
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...
EventId m_auxPhyToSleepEvent
the event scheduled to put an Aux PHY into sleep mode
MainPhySwitchInfo m_mainPhySwitchInfo
main PHY switch info
bool m_auxPhyTxCapable
whether Aux PHYs are capable of transmitting PPDUs
Ptr< EhtFrameExchangeManager > GetEhtFem(uint8_t linkId) const
void NotifyTxopEnd(uint8_t linkId, bool ulTxopNotStarted=false, bool ongoingDlTxop=false)
Notify the end of a TXOP on the given link.
void SwitchMainPhy(uint8_t linkId, bool noSwitchDelay, bool resetBackoff, bool requestAccess)
Switch channel on the Main PHY so that it operates on the given link.
uint8_t m_mainPhyId
ID of main PHY (position in the vector of PHYs held by WifiNetDevice)
const std::set< uint8_t > & GetEmlsrLinks() const
static constexpr bool REQUEST_ACCESS
request channel access when PHY switch ends
Ptr< StaWifiMac > GetStaMac() const
uint8_t GetMainPhyId() const
static constexpr bool DONT_RESET_BACKOFF
do not reset backoff on main PHY switch
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
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
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
@ MS
millisecond
Definition nstime.h:106
AttributeValue implementation for Time.
Definition nstime.h:1395
a unique identifier for an interface.
Definition type-id.h:48
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.
uint32_t GetSerializedSize() const override
Mac48Address GetAddr1() const
Return the address in the Address 1 field.
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< 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 > MakeTimeAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition nstime.h:1396
Ptr< const AttributeChecker > MakeTimeChecker()
Helper to make an unbounded Time checker.
Definition nstime.h:1416
#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:1320
AcIndex
This enumeration defines the Access Categories as an enumeration with values corresponding to the AC ...
Definition qos-utils.h:62
Every class exported by the ns3 library is enclosed in the ns3 namespace.
@ CCA_BUSY
The PHY layer has sense the medium busy through the CCA mechanism.
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
@ WIFI_MAC_QOSDATA
uint8_t from
ID of the link which the main PHY is/has been leaving.