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.
300 // Backoff shall not be reset on the link left by the main PHY because a TXOP ended and
301 // a new backoff value must be generated.
302 if (m_switchAuxPhy || !mainPhy->IsStateSwitching() || m_interruptSwitching)
303 {
306 "Aux PHY next link ID should have a value when interrupting a main PHY switch");
307 uint8_t nextLinkId = m_switchAuxPhy ? m_mainPhySwitchInfo.from : GetMainPhyId();
309 }
310 else
311 {
312 // delay link switch until current channel switching is completed
313 Simulator::Schedule(mainPhy->GetDelayUntilIdle(), [=, this]() {
314 // request the main PHY to switch back to the preferred link only if in the meantime
315 // no TXOP started on another link (which will require the main PHY to switch link)
316 if (!GetEhtFem(linkId)->UsingOtherEmlsrLink())
317 {
318 SwitchMainPhy(GetMainPhyId(), false, DONT_RESET_BACKOFF, REQUEST_ACCESS);
319 }
320 });
321 }
322}
323
324std::pair<bool, Time>
325AdvancedEmlsrManager::GetDelayUnlessMainPhyTakesOverUlTxop(uint8_t linkId)
326{
327 NS_LOG_FUNCTION(this << linkId);
328
329 if (!m_interruptSwitching)
330 {
331 return DefaultEmlsrManager::GetDelayUnlessMainPhyTakesOverUlTxop(linkId);
332 }
333
334 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
335 auto state = mainPhy->GetState()->GetState();
336
337 NS_ABORT_MSG_UNLESS(state == WifiPhyState::SWITCHING || state == WifiPhyState::RX ||
338 state == WifiPhyState::IDLE || state == WifiPhyState::CCA_BUSY,
339 "Main PHY cannot be in state " << state);
340
341 auto timeToCtsEnd = GetTimeToCtsEnd(linkId);
342 auto switchingTime = mainPhy->GetChannelSwitchDelay();
343
344 if (switchingTime > timeToCtsEnd)
345 {
346 // switching takes longer than RTS/CTS exchange, release channel
347 NS_LOG_DEBUG("Not enough time for main PHY to switch link (main PHY state: "
348 << mainPhy->GetState()->GetState() << ")");
349 // retry channel access when the CTS was expected to be received
350 return {false, timeToCtsEnd};
351 }
352
353 // TXOP can be started, main PHY will be scheduled to switch by NotifyRtsSent as soon as the
354 // transmission of the RTS is notified
355 m_switchMainPhyOnRtsTx[linkId] = Simulator::Now();
356
357 return {true, Time{0}};
358}
359
360void
361AdvancedEmlsrManager::CheckNavAndCcaLastPifs(Ptr<WifiPhy> phy, uint8_t linkId, Ptr<QosTxop> edca)
362{
363 NS_LOG_FUNCTION(this << phy->GetPhyId() << linkId << edca->GetAccessCategory());
364
365 const auto caManager = GetStaMac()->GetChannelAccessManager(linkId);
366 const auto pifs = phy->GetSifs() + phy->GetSlot();
367
368 const auto isBusy = caManager->IsBusy(); // check NAV and CCA on primary20
369 // check CCA on the entire channel
370 auto width = caManager->GetLargestIdlePrimaryChannel(pifs, Simulator::Now());
371
372 if (!isBusy && width > 0)
373 {
374 // medium idle, start TXOP
375 width = std::min(width, GetChannelForMainPhy(linkId).GetTotalWidth());
376
377 // if this function is called at the end of the main PHY switch, it is executed before the
378 // main PHY is connected to this link in order to use the CCA information of the aux PHY.
379 // Schedule now the TXOP start so that we first connect the main PHY to this link.
380 m_ccaLastPifs = Simulator::ScheduleNow([=, this]() {
381 if (GetEhtFem(linkId)->HeFrameExchangeManager::StartTransmission(edca, width))
382 {
383 NotifyUlTxopStart(linkId);
384 }
385 else if (!m_switchAuxPhy)
386 {
387 // switch main PHY back to preferred link if SwitchAuxPhy is false
388 SwitchMainPhyBackToPreferredLink(linkId);
389 }
390 });
391 }
392 else
393 {
394 // medium busy, restart channel access
395 NS_LOG_DEBUG("Medium busy in the last PIFS interval");
396 edca->NotifyChannelReleased(linkId); // to set access to NOT_REQUESTED
397 edca->StartAccessAfterEvent(linkId,
398 Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT,
399 Txop::CHECK_MEDIUM_BUSY);
400
401 // the main PHY must stay for some time on this link to check if it gets channel access.
402 // The timer is stopped if a DL or UL TXOP is started. When the timer expires, the main PHY
403 // switches back to the preferred link if SwitchAuxPhy is false
404 m_switchMainPhyBackEvent.Cancel();
405 m_switchMainPhyBackEvent = Simulator::Schedule(m_switchMainPhyBackDelay, [this, linkId]() {
406 if (!m_switchAuxPhy)
407 {
408 SwitchMainPhyBackToPreferredLink(linkId);
409 }
410 });
411 }
412}
413
414void
415AdvancedEmlsrManager::DoNotifyIcfReceived(uint8_t linkId)
416{
417 NS_LOG_FUNCTION(this << linkId);
418 m_switchMainPhyBackEvent.Cancel();
419 m_ccaLastPifs.Cancel();
420}
421
422void
423AdvancedEmlsrManager::DoNotifyUlTxopStart(uint8_t linkId)
424{
425 NS_LOG_FUNCTION(this << linkId);
426 m_switchMainPhyBackEvent.Cancel();
427 m_ccaLastPifs.Cancel();
428}
429
430bool
431AdvancedEmlsrManager::RequestMainPhyToSwitch(uint8_t linkId, AcIndex aci, const Time& delay)
432{
433 NS_LOG_FUNCTION(this << linkId << aci << delay.As(Time::US));
434
435 // the aux PHY is not TX capable; check if main PHY has to switch to the aux PHY's link
436 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
437 const auto mainPhyLinkId = GetStaMac()->GetLinkForPhy(mainPhy);
438
439 // if main PHY is not operating on a link, it is switching, hence do not request another switch
440 if (!mainPhyLinkId.has_value())
441 {
442 NS_LOG_DEBUG("Main PHY is not operating on any link");
443 return false;
444 }
445
446 // if the main PHY is already trying to get access on a link, do not request another switch
447 if (m_ccaLastPifs.IsPending() || m_switchMainPhyBackEvent.IsPending())
448 {
449 NS_LOG_DEBUG("Main PHY is trying to get access on another link");
450 return false;
451 }
452
453 switch (mainPhy->GetState()->GetState())
454 {
455 case WifiPhyState::IDLE:
456 // proceed to try requesting main PHY to switch
457 break;
458 case WifiPhyState::CCA_BUSY:
459 // if the main PHY is receiving the PHY header of a PPDU, we decide to proceed or give up
460 // based on the AllowUlTxopInRx attribute
461 if (mainPhy->IsReceivingPhyHeader() && !m_allowUlTxopInRx)
462 {
463 NS_LOG_DEBUG("Main PHY receiving PHY header and AllowUlTxopInRx is false");
464 return false;
465 }
466 break;
467 case WifiPhyState::RX:
468 if (auto macHdr = GetEhtFem(*mainPhyLinkId)->GetReceivedMacHdr())
469 {
470 // information on the MAC header of the PSDU being received is available; if we cannot
471 // use it or the main PHY is receiving an ICF, give up requesting main PHY to switch
472 if (const auto& hdr = macHdr->get();
473 !m_useNotifiedMacHdr ||
474 (hdr.IsTrigger() && (hdr.GetAddr1().IsBroadcast() ||
475 hdr.GetAddr1() == GetEhtFem(*mainPhyLinkId)->GetAddress())))
476 {
477 NS_LOG_DEBUG("Receiving an ICF or cannot use MAC header information");
478 return false;
479 }
480 }
481 // information on the MAC header of the PSDU being received is not available, we decide to
482 // proceed or give up based on the AllowUlTxopInRx attribute
483 else if (!m_allowUlTxopInRx)
484 {
485 NS_LOG_DEBUG("Receiving PSDU, no MAC header information, AllowUlTxopInRx is false");
486 return false;
487 }
488 break;
489 default:
490 NS_LOG_DEBUG("Cannot request main PHY to switch when in state "
491 << mainPhy->GetState()->GetState());
492 return false;
493 }
494
495 // request to switch main PHY if we expect the main PHY to get channel access on this link more
496 // quickly, i.e., if ALL the ACs with queued frames (that can be transmitted on the link on
497 // which the main PHY is currently operating) and with priority higher than or equal to that of
498 // the AC for which Aux PHY gained TXOP have their backoff counter greater than the maximum
499 // between the expected delay in gaining channel access and the channel switch delay plus PIFS
500
501 auto requestSwitch = false;
502 const auto now = Simulator::Now();
503
504 for (const auto& [acIndex, ac] : wifiAcList)
505 {
506 if (auto edca = GetStaMac()->GetQosTxop(acIndex);
507 acIndex >= aci && edca->HasFramesToTransmit(linkId))
508 {
509 requestSwitch = true;
510
511 const auto backoffEnd =
512 GetStaMac()->GetChannelAccessManager(*mainPhyLinkId)->GetBackoffEndFor(edca);
513 NS_LOG_DEBUG("Backoff end for " << acIndex
514 << " on preferred link: " << backoffEnd.As(Time::US));
515
516 if (const auto minDelay = std::max(delay,
517 mainPhy->GetChannelSwitchDelay() +
518 GetStaMac()->GetWifiPhy(linkId)->GetPifs());
519 backoffEnd <= now + minDelay && edca->HasFramesToTransmit(*mainPhyLinkId))
520 {
521 requestSwitch = false;
522 break;
523 }
524 }
525 }
526
527 return requestSwitch;
528}
529
530void
531AdvancedEmlsrManager::SwitchMainPhyIfTxopGainedByAuxPhy(uint8_t linkId, AcIndex aci)
532{
533 NS_LOG_FUNCTION(this << linkId << aci);
534
535 NS_ASSERT_MSG(!m_auxPhyTxCapable,
536 "This function should only be called if aux PHY is not TX capable");
537 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
538
539 if (mainPhy->IsStateSwitching() && m_mainPhySwitchInfo.to == linkId)
540 {
541 // the main PHY is switching to the link on which the aux PHY gained a TXOP. This can
542 // happen, e.g., if the main PHY was requested to switch to that link before the backoff
543 // counter reached zero. Or, this can happen in case of internal collision: the first AC
544 // requests the main PHY to switch and the second one finds the main PHY to be switching.
545 // In both cases, we do nothing because we have already scheduled the necessary actions
546 NS_LOG_DEBUG("Main PHY is already switching to link " << +linkId);
547 return;
548 }
549
550 if (RequestMainPhyToSwitch(linkId, aci, Time{0}))
551 {
552 const auto auxPhy = GetStaMac()->GetWifiPhy(linkId);
553 const auto pifs = auxPhy->GetSifs() + auxPhy->GetSlot();
554
555 // schedule actions to take based on CCA sensing for a PIFS
556 if (m_useAuxPhyCca || GetChannelForAuxPhy(linkId).GetTotalWidth() >=
557 GetChannelForMainPhy(linkId).GetTotalWidth())
558 {
559 // use aux PHY CCA in the last PIFS interval before main PHY switch end
560 NS_LOG_DEBUG("Schedule CCA check at the end of main PHY switch");
561 m_ccaLastPifs = Simulator::Schedule(mainPhy->GetChannelSwitchDelay(),
562 &AdvancedEmlsrManager::CheckNavAndCcaLastPifs,
563 this,
564 auxPhy,
565 linkId,
566 GetStaMac()->GetQosTxop(aci));
567 }
568 else
569 {
570 // use main PHY CCA in the last PIFS interval after main PHY switch end
571 NS_LOG_DEBUG("Schedule CCA check a PIFS after the end of main PHY switch");
572 m_ccaLastPifs = Simulator::Schedule(mainPhy->GetChannelSwitchDelay() + pifs,
573 &AdvancedEmlsrManager::CheckNavAndCcaLastPifs,
574 this,
575 mainPhy,
576 linkId,
577 GetStaMac()->GetQosTxop(aci));
578 }
579
580 // switch main PHY
581 SwitchMainPhy(linkId, false, RESET_BACKOFF, DONT_REQUEST_ACCESS);
582 return;
583 }
584
585 // Determine if and when we need to request channel access again for the aux PHY based on
586 // the main PHY state.
587 // Note that, if we have requested the main PHY to switch (above), the function has returned
588 // and the EHT FEM will start a TXOP if medium is idle for a PIFS interval preceding/following
589 // the end of the main PHY channel switch.
590 // If the main PHY has been requested to switch by another aux PHY, this aux PHY will request
591 // channel access again when we have completed the CCA assessment on the other link.
592 // If the state is switching, CCA_BUSY or RX, then we request channel access again for the
593 // aux PHY when the main PHY state is back to IDLE.
594 // If the state is TX, it means that the main PHY is involved in a TXOP. Do nothing because
595 // the channel access will be requested when unblocking links at the end of the TXOP.
596 // If the state is IDLE, then either no AC has traffic to send or the backoff on the link
597 // of the main PHY is shorter than the channel switch delay. In the former case, do
598 // nothing because channel access will be triggered when new packets arrive; in the latter
599 // case, do nothing because the main PHY will start a TXOP and at the end of such TXOP
600 // links will be unblocked and the channel access requested on all links
601
602 Time delay{};
603
604 if (m_ccaLastPifs.IsPending() || m_switchMainPhyBackEvent.IsPending())
605 {
606 delay = std::max(Simulator::GetDelayLeft(m_ccaLastPifs),
607 Simulator::GetDelayLeft(m_switchMainPhyBackEvent));
608 }
609 else if (mainPhy->IsStateSwitching() || mainPhy->IsStateCcaBusy() || mainPhy->IsStateRx())
610 {
611 delay = mainPhy->GetDelayUntilIdle();
612 NS_ASSERT(delay.IsStrictlyPositive());
613 }
614
615 NS_LOG_DEBUG("Main PHY state is " << mainPhy->GetState()->GetState());
616
617 if (delay.IsZero())
618 {
619 NS_LOG_DEBUG("Do nothing");
620 return;
621 }
622
623 auto edca = GetStaMac()->GetQosTxop(aci);
624 edca->NotifyChannelReleased(linkId); // to set access to NOT_REQUESTED
625
626 NS_LOG_DEBUG("Schedule channel access request on link "
627 << +linkId << " at time " << (Simulator::Now() + delay).As(Time::NS));
628 Simulator::Schedule(delay, [=]() {
629 edca->StartAccessAfterEvent(linkId,
630 Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT,
631 Txop::CHECK_MEDIUM_BUSY);
632 });
633}
634
635void
636AdvancedEmlsrManager::SwitchMainPhyIfTxopToBeGainedByAuxPhy(uint8_t linkId,
637 AcIndex aci,
638 const Time& delay)
639{
640 NS_LOG_FUNCTION(this << linkId << aci << delay.As(Time::US));
641
642 if (m_auxPhyTxCapable)
643 {
644 NS_LOG_DEBUG("Nothing to do if aux PHY is TX capable");
645 return;
646 }
647
648 if (!delay.IsStrictlyPositive())
649 {
650 NS_LOG_DEBUG("Do nothing if delay is not strictly positive");
651 return;
652 }
653
654 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
655 auto phy = GetStaMac()->GetWifiPhy(linkId);
656
657 if (!phy || phy == mainPhy)
658 {
659 NS_LOG_DEBUG("No aux PHY is operating on link " << +linkId);
660 return;
661 }
662
663 if (!RequestMainPhyToSwitch(linkId, aci, delay))
664 {
665 NS_LOG_DEBUG("Chosen not to request the main PHY to switch");
666 if (const auto untilIdle = mainPhy->GetDelayUntilIdle();
667 untilIdle.IsStrictlyPositive() && untilIdle < delay)
668 {
669 NS_LOG_DEBUG("Retrying in " << untilIdle.As(Time::US));
670 Simulator::Schedule(untilIdle,
671 &AdvancedEmlsrManager::SwitchMainPhyIfTxopToBeGainedByAuxPhy,
672 this,
673 linkId,
674 aci,
675 delay - untilIdle);
676 }
677 return;
678 }
679
680 // switch main PHY
681 SwitchMainPhy(linkId, false, RESET_BACKOFF, DONT_REQUEST_ACCESS);
682
683 // if the remaining backoff time is shorter than PIFS when the main PHY completes the switch,
684 // we need to schedule a CCA check a PIFS after the end of the main PHY switch
685 Simulator::Schedule(mainPhy->GetChannelSwitchDelay(), [=, this]() {
686 const auto edca = GetStaMac()->GetQosTxop(aci);
687 const auto pifs = GetStaMac()->GetWifiPhy(linkId)->GetPifs();
688 if (GetStaMac()->GetChannelAccessManager(linkId)->GetBackoffEndFor(edca) <=
689 Simulator::Now() + pifs)
690 {
691 // use main PHY CCA in the last PIFS interval after main PHY switch end
692 NS_LOG_DEBUG("Schedule CCA check a PIFS after the end of main PHY switch");
693 m_ccaLastPifs = Simulator::Schedule(pifs,
694 &AdvancedEmlsrManager::CheckNavAndCcaLastPifs,
695 this,
696 mainPhy,
697 linkId,
698 edca);
699 }
700 });
701
702 // the main PHY must stay for some time on this link to check if it gets channel access.
703 // The timer is stopped if a DL or UL TXOP is started. When the timer expires, the main PHY
704 // switches back to the preferred link if SwitchAuxPhy is false
705 const auto minDelay =
706 std::max(delay,
707 mainPhy->GetChannelSwitchDelay() + GetStaMac()->GetWifiPhy(linkId)->GetPifs());
708 m_switchMainPhyBackEvent.Cancel();
709 m_switchMainPhyBackEvent =
710 Simulator::Schedule(minDelay + m_switchMainPhyBackDelay, [this, linkId]() {
711 if (!m_switchAuxPhy)
712 {
713 SwitchMainPhyBackToPreferredLink(linkId);
714 }
715 });
716}
717
718} // 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...
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
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:1431
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:1432
Ptr< const AttributeChecker > MakeTimeChecker()
Helper to make an unbounded Time checker.
Definition nstime.h:1452
#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
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.