先看一个图
这个图是说明了WiFi模块下,WiFiNetDevice的调用过程。
从WiFiNetDevice向下层调用过程,从这一张图就可以看出来。而从WiFiNetDevice向上层的调用过程,只用从WiFiNetDevice开始。这样隔离开,方便简单一些。
下面从源码的角度,一步步说明图中的调用过程。
WifiNetDevice::Send
bool
WifiNetDevice::Send (Ptr packet, const Address& dest, uint16_t protocolNumber)
{
NS_LOG_FUNCTION (this << packet << dest << protocolNumber);
NS_ASSERT (Mac48Address::IsMatchingType (dest));
Mac48Address realTo = Mac48Address::ConvertFrom (dest);
LlcSnapHeader llc;
llc.SetType (protocolNumber);
packet->AddHeader (llc);
m_mac->NotifyTx (packet);
m_mac->Enqueue (packet, realTo);
return true;
}
m_mac参数的类型是Ptr
WiFiMac::Enqueue
virtual void Enqueue (Ptr packet, Mac48Address to) = 0;
这是WiFiMac::Enqueue方法的声明,是一个纯虚函数,子类必须实现这个方法。
WiFiMac的子类有:
RegularWifiMac::Enqueue
void
RegularWifiMac::Enqueue (Ptr packet,
Mac48Address to, Mac48Address from)
{
//We expect RegularWifiMac subclasses which do support forwarding (e.g.,
//AP) to override this method. Therefore, we throw a fatal error if
//someone tries to invoke this method on a class which has not done
//this.
NS_FATAL_ERROR ("This MAC entity (" << this << ", " << GetAddress ()
<< ") does not support Enqueue() with from address");
}
这个方法并没有什么实质性的功能。并且这个方法默认WiFiMac不支持。
AdhocWifiMac::Enqueue
以AdhocWifiMac::Enqueue函数为例,其他的两个是AP-STA模式下应用。AdhocWifiMac使用与Adhoc网络。
void
AdhocWifiMac::Enqueue (Ptr packet, Mac48Address to)
{
NS_LOG_FUNCTION (this << packet << to);
...
WifiMacHeader hdr;
...
if (m_qosSupported)
{
NS_ASSERT (tid < 8);
m_edca[QosUtilsMapTidToAc (tid)]->Queue (packet, hdr);
}
else
{
m_dca->Queue (packet, hdr);
}
}
AdhocWifiMac::Enqueue函数中有if语句
如果网络支持Qos,就会进入类EdcaTxopN的Queue函数;
如果不支持Qos,就会进入DcaTxop类的Queue函数。
这两个函数区别在于业务类型,EdcaTxopN类有四种业务类型:
* -AC_VO : voice, tid = 6,7 ^
* -AC_VI : video, tid = 4,5 |
* -AC_BE : best-effort, tid = 0,3 | priority
* -AC_BK : background, tid = 1,2 |
DcaTxop类就没有这种业务类型的区别。
这两个类完成的功能就是packet排队,等到信道空闲,节点获取到信道,发送packet。发送packet的代码是一样的,有差别的地方都在这两个类中完成了。
DcaTxop类简单些,以这个类为例,向下说明代码流程。
DcaTxop::Queue
void
DcaTxop::Queue (Ptr packet, const WifiMacHeader &hdr)
{
NS_LOG_FUNCTION (this << packet << &hdr);
WifiMacTrailer fcs;
m_stationManager->PrepareForQueue (hdr.GetAddr1 (), &hdr, packet);
m_queue->Enqueue (packet, hdr);
StartAccessIfNeeded ();
}
从该方法中可以看出,packet加入队列,m_queue的类型为Ptr
StartAccessIfNeeded ();这一行代码就会判断当前信道空闲与否,随机回退值等。
void
DcaTxop::StartAccessIfNeeded (void)
{
NS_LOG_FUNCTION (this);
if (m_currentPacket == 0
&& !m_queue->IsEmpty ()
&& !m_dcf->IsAccessRequested ())
{
m_manager->RequestAccess (m_dcf);
}
}
m_currentPacket 代表当前正在发送的packet。
m_queue 就是packet队列。
m_dcf 设置窗口大小、状态等,同时能够完成回调,通知DcaTxop或者EdcaTxopN获取信道权限、出现碰撞、唤醒、休眠等。
m_manager负责请求信道,判断时间等,并通过m_dcf 对象回调DcaTxop或者EdcaTxopN中的方法。
这里的回调跟Java是一样的。
DcaTxop或者EdcaTxopN类中有m_dcf 对象,将m_dcf 对象传递给m_manager对象,m_manager对象再通过m_dcf 对象调用m_dcf 对象的方法,m_dcf 对象的方法又调用DcaTxop或者EdcaTxopN类中的方法。
DcfManager::RequestAccess
void
DcfManager::RequestAccess (DcfState *state)
{
NS_LOG_FUNCTION (this << state);
...
DoGrantAccess ();
DoRestartAccessTimeoutIfNeeded ();
}
void
DcfManager::DoGrantAccess (void)
{
NS_LOG_FUNCTION (this);
uint32_t k = 0;
for (States::const_iterator i = m_states.begin (); i != m_states.end (); k++)
{
DcfState *state = *i;
if (state->IsAccessRequested ()
&& GetBackoffEndFor (state) <= Simulator::Now () )
{
i++; //go to the next item in the list.
k++;
std::vector internalCollisionStates;
for (States::const_iterator j = i; j != m_states.end (); j++, k++)
{
DcfState *otherState = *j;
if (otherState->IsAccessRequested ()
&& GetBackoffEndFor (otherState) <= Simulator::Now ())
{
internalCollisionStates.push_back (otherState);
}
}
/**
* 现在,我们一次通知所有这些更改。 有必要首先执行哪些状态发生冲突的计算,
* 然后仅应用改变,因为通过通知应用改变可以改变管理器的全局状态,并且因此可以改变计算的结果。
*/
state->NotifyAccessGranted ();
for (std::vector::const_iterator k = internalCollisionStates.begin ();
k != internalCollisionStates.end (); k++)
{
(*k)->NotifyInternalCollision ();
}
break;
}
i++;
}
}
DcfManager::RequestAccess方法调用DcfManager::DoGrantAccess方法,最后调用state->NotifyAccessGranted ()方法回调。state就是上面传递的m_dcf 对象。
DcfState对象的NotifyAccessGranted
void
DcfState::NotifyAccessGranted (void)
{
NS_ASSERT (m_accessRequested);
m_accessRequested = false;
DoNotifyAccessGranted ();
}
virtual void DoNotifyAccessGranted (void)
{
m_txop->NotifyAccessGranted ();
}
m_txop对象就是上文的DcaTxop类对象。
DcaTxop::NotifyAccessGranted
void
DcaTxop::NotifyAccessGranted (void)
{
NS_LOG_FUNCTION (this);
...
...
MacLowTransmissionParameters params;
params.DisableOverrideDurationId ();
/**
* 如果是一个组播,程序走这里
*/
params.DisableOverrideDurationId ();
params.DisableRts ();
params.DisableAck ();
params.DisableNextData ();
if (m_currentHdr.GetAddr1 ().IsGroup ())
{
params.DisableRts ();
params.DisableAck ();
params.DisableNextData ();
Low ()->StartTransmission (m_currentPacket,
&m_currentHdr,
params,
m_transmissionListener);
NS_LOG_DEBUG ("tx broadcast");
}
else
{
params.EnableAck ();
if (NeedFragmentation ())//默认情况下,不需要分片。
{
WifiMacHeader hdr;
Ptr fragment = GetFragmentPacket (&hdr);
if (IsLastFragment ())
{
NS_LOG_DEBUG ("fragmenting last fragment size=" << fragment->GetSize ());
params.DisableNextData ();
}
else
{
NS_LOG_DEBUG ("fragmenting size=" << fragment->GetSize ());
params.EnableNextData (GetNextFragmentSize ());
}
Low ()->StartTransmission (fragment, &hdr, params,
m_transmissionListener);
}
//=====================正常情况下,走这里========================
else
{
params.DisableNextData ();//表示没有下一个fragment分片。
Low ()->StartTransmission (m_currentPacket, &m_currentHdr,
params, m_transmissionListener);
}
}
}
上面的代码会调用Low ()->StartTransmission 方法。Low ()返回的是MacLow类对象。
MacLow::StartTransmission
void
MacLow::StartTransmission (Ptr packet,
const WifiMacHeader* hdr,
MacLowTransmissionParameters params,
MacLowTransmissionListener *listener)
{
NS_LOG_FUNCTION (this << packet << hdr << params << listener);
m_currentPacket = packet->Copy ();
SocketPriorityTag priorityTag;
m_currentPacket->RemovePacketTag (priorityTag);
m_currentHdr = *hdr;
CancelAllEvents ();
m_listener = listener;
m_txParams = params;
m_currentTxVector = GetDataTxVector (m_currentPacket, &m_currentHdr);
...
...
NS_LOG_DEBUG ("startTx size=" << GetSize (m_currentPacket, &m_currentHdr) <<
", to=" << m_currentHdr.GetAddr1 () << ", listener=" << m_listener);
if (m_txParams.MustSendRts ())
{
SendRtsForPacket ();//发送RTS
}
else
{
if ((m_ctsToSelfSupported || m_stationManager->GetUseNonErpProtection ()) && NeedCtsToSelf ())
{
SendCtsToSelf ();//发送CTS
}
else
{
SendDataPacket ();//发送数据
}
}
/*
* 当这个方法完成,我们已经取得媒质的所有权。
* */
NS_ASSERT (m_phy->IsStateTx ());
}
ampdu设计packet聚合之类的东西,麻烦。
一般情况下,三种发送:
SendRtsForPacket ();//发送RTS
SendCtsToSelf ();//发送CTS
SendDataPacket ();//发送数据
RTS/CTS大家应该知道吧。
重点说SendDataPacket ()。
MacLow::SendDataPacket
void
MacLow::SendDataPacket (void)
{
NS_LOG_FUNCTION (this);
...
...
ForwardDown (m_currentPacket, &m_currentHdr, m_currentTxVector, preamble);
m_currentPacket = 0;
}
MacLow::ForwardDown
void
MacLow::ForwardDown (Ptr packet, const WifiMacHeader* hdr,
WifiTxVector txVector, WifiPreamble preamble)
{
NS_LOG_FUNCTION (this << packet << hdr << txVector);
NS_LOG_DEBUG ("send " << hdr->GetTypeString () <<
", to=" << hdr->GetAddr1 () <<
", size=" << packet->GetSize () <<
", mode=" << txVector.GetMode () <<
", duration=" << hdr->GetDuration () <<
", seq=0x" << std::hex << m_currentHdr.GetSequenceControl () << std::dec);
if (!m_ampdu || hdr->IsRts () || hdr->IsBlockAck ())
{
NS_LOG_LOGIC("mac-low.cc if-> m_ampdu:"<isrts:"
<IsRts()<<",hdr->isblockack:"<IsBlockAck());
m_phy->SendPacket (packet, txVector, preamble);
}
...
}
MacLow::ForwardDown 会调用m_phy对象的SendPacket 方法。
m_phy就是WifiPhy类型,代表物理层
WifiPhy
WifiPhy类包含虚函数,不能构建对象。它的子类可以。
其实,上面的m_phy就是YansWifiPhy类类型。
YansWifiPhy::SendPacket
void
YansWifiPhy::SendPacket (Ptr packet, WifiTxVector txVector, WifiPreamble preamble)
{
SendPacket (packet, txVector, preamble, NORMAL_MPDU);
}
void
YansWifiPhy::SendPacket (Ptr packet, WifiTxVector txVector, WifiPreamble preamble,
enum mpduType mpdutype)
{
NS_LOG_FUNCTION (this << packet << txVector.GetMode ()
<< txVector.GetMode ().GetDataRate (txVector)
<< preamble << (uint32_t)txVector.GetTxPowerLevel () << (uint32_t)mpdutype);
NS_ASSERT (!m_state->IsStateTx () && !m_state->IsStateSwitching ());
......
m_channel->Send (this, packet, GetPowerDbm (txVector.GetTxPowerLevel ()) +
GetTxGain (), txVector, preamble, mpdutype, txDuration);
}
YansWifiPhy::SendPacket 方法会调用m_channel->Send方法。
m_channel对象是YansWifiChannel类类型。
YansWifiChannel::Send
void
YansWifiChannel::Send (Ptr sender, Ptr packet, double txPowerDbm,
WifiTxVector txVector, WifiPreamble preamble, enum mpduType mpdutype, Time duration) const
{
Ptr senderMobility = sender->GetMobility ()->GetObject ();
NS_ASSERT (senderMobility != 0);
uint32_t j = 0;
for (PhyList::const_iterator i = m_phyList.begin (); i != m_phyList.end (); i++, j++)
{
if (sender != (*i))
{
//For now don't account for inter channel interference
if ((*i)->GetChannelNumber () != sender->GetChannelNumber ())
{
continue;
}
...
Simulator::ScheduleWithContext (dstNode,
delay, &YansWifiChannel::Receive, this,
j, copy, parameters);
}
}
}
YansWifiChannel::Send里面调用了:
Simulator::ScheduleWithContext (dstNode,
delay, &YansWifiChannel::Receive, this,
j, copy, parameters);
这个方法的意思是经过delay时间后,调用YansWifiChannel::Receive方法,并以 this,j, copy, parameters这四个变量为参数。dstNode是上下文,是一个uint16_t类型值,不重要。
YansWifiChannel::Receive
void
YansWifiChannel::Receive (uint32_t i, Ptr packet, struct Parameters parameters) const
{
m_phyList[i]->StartReceivePreambleAndHeader (packet, parameters.rxPowerDbm,
parameters.txVector, parameters.preamble,
parameters.type, parameters.duration);
}
该方法 调用YansWifiPhy的StartReceivePreambleAndHeader 方法。
YansWifiPhy::StartReceivePreambleAndHeader
void
YansWifiPhy::StartReceivePreambleAndHeader (Ptr packet,
double rxPowerDbm,
WifiTxVector txVector,
enum WifiPreamble preamble,
enum mpduType mpdutype,
Time rxDuration)
{
NS_LOG_FUNCTION (this << packet << rxPowerDbm << txVector.GetMode () << preamble << (uint32_t)mpdutype);
...
switch (m_state->GetState ())
{
case YansWifiPhy::SWITCHING:
NS_LOG_DEBUG ("drop packet because of channel switching");
....
break;
case YansWifiPhy::RX:
NS_LOG_DEBUG ("drop packet because already in Rx (power=" <<
rxPowerW << "W)");
...
break;
case YansWifiPhy::TX:
NS_LOG_DEBUG ("drop packet because already in Tx (power=" <<
rxPowerW << "W)");
...
break;
case YansWifiPhy::CCA_BUSY:
case YansWifiPhy::IDLE:
if (rxPowerW > GetEdThresholdW ())
{
.....
if (preamble != WIFI_PREAMBLE_NONE)
{
NS_ASSERT (m_endPlcpRxEvent.IsExpired ());
m_endPlcpRxEvent = Simulator::Schedule (preambleAndHeaderDuration,
&YansWifiPhy::StartReceivePacket, this,
packet, txVector, preamble, mpdutype, event);
}
NS_ASSERT (m_endRxEvent.IsExpired ());
m_endRxEvent = Simulator::Schedule (rxDuration, &YansWifiPhy::EndReceive, this,
packet, preamble, mpdutype, event);
}
else
{
...
}
break;
case YansWifiPhy::SLEEP:
NotifyRxDrop (packet);
m_plcpSuccess = false;
break;
}
return;
maybeCcaBusy:
...
}
其中两个重要的方法在于:
m_endPlcpRxEvent = Simulator::Schedule (preambleAndHeaderDuration,
&YansWifiPhy::StartReceivePacket, this,
packet, txVector, preamble, mpdutype, event);
m_endRxEvent = Simulator::Schedule (rxDuration, &YansWifiPhy::EndReceive, this,
packet, preamble, mpdutype, event);
先调用YansWifiPhy::StartReceivePacket,后调用YansWifiPhy::EndReceive。
YansWifiPhy::StartReceivePacket方法没有发生跳转,重要的跳转发生在YansWifiPhy::EndReceive。
YansWifiPhy::EndReceive
void
YansWifiPhy::EndReceive (Ptr packet, enum WifiPreamble preamble,
enum mpduType mpdutype, Ptr event)
{
NS_LOG_FUNCTION (this << packet << event);
....
if (m_plcpSuccess == true)
{
....
if (m_random->GetValue () > snrPer.per)
{
...
...
m_state->SwitchFromRxEndOk (packet, snrPer.snr, event->GetTxVector (), event->GetPreambleType ());
}
else
{
/* failure. */
...
m_state->SwitchFromRxEndError (packet, snrPer.snr);
}
}
else
{
m_state->SwitchFromRxEndError (packet, snrPer.snr);
}
...
}
这里的跳转发生在m_state->SwitchFromRxEndOk方法。
m_state对象是WifiPhyStateHelper类。
WifiPhyStateHelper::SwitchFromRxEndOk
void
WifiPhyStateHelper::SwitchFromRxEndOk (Ptr packet, double snr, WifiTxVector txVector,
enum WifiPreamble preamble)
{
m_rxOkTrace (packet, snr, txVector.GetMode (), preamble);
NotifyRxEndOk ();
DoSwitchFromRx ();
if (!m_rxOkCallback.IsNull ())
{
m_rxOkCallback (packet, snr, txVector, preamble);
}
}
m_rxOkCallback 是个回调函数指针,这个指针的值的设置代码如下:
void
MacLow::SetPhy (Ptr phy)
{
m_phy = phy;
m_phy->SetReceiveOkCallback (MakeCallback (&MacLow::DeaggregateAmpduAndReceive, this));
m_phy->SetReceiveErrorCallback (MakeCallback (&MacLow::ReceiveError, this));
SetupPhyMacLowListener (phy);
}
也就是说m_rxOkCallback 指向MacLow::DeaggregateAmpduAndReceive函数。m_rxOkCallback (packet, snr, txVector, preamble)就是MacLow::DeaggregateAmpduAndReceive(packet, snr, txVector, preamble)。
MacLow::DeaggregateAmpduAndReceive
void
MacLow::DeaggregateAmpduAndReceive (Ptr aggregatedPacket, double rxSnr,
WifiTxVector txVector, WifiPreamble preamble)
{
NS_LOG_FUNCTION (this<RemovePacketTag (ampdu))
{
···
}
else
{
ReceiveOk (aggregatedPacket, rxSnr, txVector, preamble, ampduSubframe);
}
}
void
MacLow::ReceiveOk (Ptr packet, double rxSnr, WifiTxVector txVector,
WifiPreamble preamble, bool ampduSubframe)
{
NS_LOG_FUNCTION (this << packet << rxSnr << txVector.GetMode () << GetPreambleStr(preamble));
if (hdr.IsRts ())
{
···
}
else if (hdr.IsCts ()
&& hdr.GetAddr1 () == m_self
&& m_ctsTimeoutEvent.IsRunning ()
&& m_currentPacket != 0)
{
···
}
//=========================ACK================================================
else if (hdr.IsAck ()
&& hdr.GetAddr1 () == m_self
&& (m_normalAckTimeoutEvent.IsRunning ()
|| m_fastAckTimeoutEvent.IsRunning ()
|| m_superFastAckTimeoutEvent.IsRunning ())
&& m_txParams.MustWaitAck ())
{
····
}
else if (hdr.IsBlockAck () && hdr.GetAddr1 () == m_self
&& (m_txParams.MustWaitBasicBlockAck () || m_txParams.MustWaitCompressedBlockAck ())
&& m_blockAckTimeoutEvent.IsRunning ())
{
NS_LOG_DEBUG ("got block ack from " << hdr.GetAddr2 ());
····
}
else if (hdr.IsBlockAckReq () && hdr.GetAddr1 () == m_self)
{
···
}
else if (hdr.IsCtl ())
{
···
}
//=========================m_self================================================
else if (hdr.GetAddr1 () == m_self)
{
···
//=========================m_self and data================================================
else if (hdr.IsData () || hdr.IsMgt ())
{
····
}
goto rxPacket;
}
//=========================GROUP================================================
else if (hdr.GetAddr1 ().IsGroup ())
{
···
}
//=========================promisc================================================
else if (m_promisc)
{
NS_ASSERT (hdr.GetAddr1 () != m_self);
if (hdr.IsData ())
{
goto rxPacket;
}
}
else
{
//NS_LOG_DEBUG_VERBOSE ("rx not-for-me from %d", GetSource (packet));
}
return;
rxPacket:
WifiMacTrailer fcs;
packet->RemoveTrailer (fcs);
m_rxCallback (packet, &hdr);
return;
}
ReceiveOk 方法都会进入rxPacket标记位置,继续运行代码。m_rxCallback 也是一个函数指针。该值的设置代码:
m_low->SetRxCallback (MakeCallback (&MacRxMiddle::Receive, m_rxMiddle));
MacRxMiddle::Receive
void
MacRxMiddle::Receive (Ptr packet, const WifiMacHeader *hdr)
{
NS_LOG_FUNCTION (packet << hdr);
···
m_callback (agregate, hdr);
}
m_callback 也是一个函数指针,设置代码为:
m_rxMiddle->SetForwardCallback (MakeCallback (&RegularWifiMac::Receive, this));
也就是说,m_callback (agregate, hdr)也就是RegularWifiMac::Receive(agregate, hdr)。
但是需要注意的是RegularWifiMac包含虚函数,不能创建对象,我们之前使用的它的子类AdhocWifiMac对象。
所以上面的代码运行应该是AdhocWifiMac::Receive
AdhocWifiMac::Receive
void
AdhocWifiMac::Receive (Ptr packet, const WifiMacHeader *hdr)
{
NS_LOG_FUNCTION (this << packet << hdr);
...
if (hdr->IsData ())
{
if (hdr->IsQosData () && hdr->IsQosAmsdu ())
{
DeaggregateAmsduAndForward (packet, hdr);
}
else
{
ForwardUp (packet, from, to);
}
return;
}
RegularWifiMac::Receive (packet, hdr);
}
上面if语句判断,两种情况。
DeaggregateAmsduAndForward 方法会在聚合情况下调用。
packet布局和情况下会调用ForwardUp 方法。ForwardUp方法是父类RegularWifiMac中的方法。
AdhocWifiMac::Receive方法的最后调用了父类RegularWifiMac::Receive方法。
RegularWifiMac::ForwardUp和RegularWifiMac::Receive
void
RegularWifiMac::ForwardUp (Ptr packet, Mac48Address from, Mac48Address to)
{
NS_LOG_FUNCTION (this << packet << from);
m_forwardUp (packet, from, to);
}
void
RegularWifiMac::Receive (Ptr packet, const WifiMacHeader *hdr)
{
NS_LOG_FUNCTION (this << packet << hdr);
Mac48Address to = hdr->GetAddr1 ();
Mac48Address from = hdr->GetAddr2 ();
if (to != GetAddress ())
{
NS_LOG_LOGIC("RegularWifiMac::Receive() 目的地不是我,直接返回");
return;
}
if (hdr->IsMgt () && hdr->IsAction ())
{
...
}
NS_FATAL_ERROR ("Don't know how to handle frame (type=" << hdr->GetType ());
}
RegularWifiMac::Receive方法主要做一些子类没处理的情况。
RegularWifiMac::ForwardUp 方法会继续想上层传递接收到的packet。m_forwardUp 同样是一个函数指针。设置的代码如下:
m_mac->SetForwardUpCallback (MakeCallback (&WifiNetDevice::ForwardUp, this));
WifiNetDevice::ForwardUp
void
WifiNetDevice::ForwardUp (Ptr packet, Mac48Address from, Mac48Address to)
{
NS_LOG_FUNCTION (this << packet << from << to);
LlcSnapHeader llc;
enum NetDevice::PacketType type;
...
if (type != NetDevice::PACKET_OTHERHOST)
{
m_mac->NotifyRx (packet);
packet->RemoveHeader (llc);
m_forwardUp (this, packet, llc.GetType (), from);
}
else
{
packet->RemoveHeader (llc);
}
if (!m_promiscRx.IsNull ())
{
m_mac->NotifyPromiscRx (packet);
m_promiscRx (this, packet, llc.GetType (), from, to, type);
}
}
然后在WifiNetDevice::ForwardUp方法继续向上层传递接收到的packet。
整个代码传递流程,简写为:
WifiNetDevice::Send WifiNetDevice::ForwardUp
| |
| |
WiFiMac::Enqueue
| |
| |
RegularWifiMac::Enqueue RegularWifiMac::ForwardUp
| |
| |
AdhocWifiMac::Enqueue AdhocWifiMac::Receive
| |
| |
DcaTxop::Queue MacRxMiddle::Receive
| |
| |
DcfManager::RequestAccess ^
| |
| |
DcfState::NotifyAccessGranted ^
| |
| |
DcaTxop::NotifyAccessGranted ^
| |
| |
MacLow::StartTransmission MacLow::DeaggregateAmpduAndReceive
| |
| |
MacLow::SendDataPacket WifiPhyStateHelper::SwitchFromRxEndOk
| |
| |
MacLow::ForwardDown YansWifiPhy::EndReceive
| |
| |
YansWifiPhy::SendPacket YansWifiPhy::StartReceivePreambleAndHeader
| |
| |
YansWifiChannel::Send ----------------------------------> YansWifiChannel::Receive