1 //
2 // Copyright (c) 2010-2025 Antmicro
3 // Copyright (c) 2022-2025 Silicon Labs
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 
9 using System;
10 using System.Collections.Generic;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Time;
13 using Antmicro.Renode.Utilities;
14 using Antmicro.Renode.Utilities.Packets;
15 using Antmicro.Renode.Logging;
16 
17 namespace Antmicro.Renode.Peripherals.Wireless
18 {
19     public interface IInterferenceQueueListener
20     {
InteferenceQueueChangedCallback()21         void InteferenceQueueChangedCallback();
22     }
23 
24     public static class InterferenceQueue
25     {
26         static readonly List<PacketInfo> overTheAirPackets = new List<PacketInfo>();
27         static readonly List<IInterferenceQueueListener> listeners = new List<IInterferenceQueueListener>();
28 
Subscribe(IInterferenceQueueListener listener)29         public static void Subscribe(IInterferenceQueueListener listener)
30         {
31             listeners.Add(listener);
32         }
33 
Add(IRadio sender, RadioPhyId phyId, int channel, int txPowerDbm, byte[] content)34         public static void Add(IRadio sender, RadioPhyId phyId, int channel, int txPowerDbm, byte[] content)
35         {
36             // First we check if there is already a packet from this sender in the queue. If that is the case,
37             // it should be stale and we are going to remove it.
38             PacketInfo entry = PacketLookup(sender);
39             if (entry != null)
40             {
41                 if (!entry.PacketIsStale)
42                 {
43                     Logger.Log(LogLevel.Error, "InterferenceQueue.Add: sender non-stale entry present");
44                     return;
45                 }
46                 Remove(sender);
47             }
48 
49             TimeInterval addTime = IPeripheralExtensions.GetMachine(sender).LocalTimeSource.ElapsedVirtualTime;
50             PacketInfo newEntry = new PacketInfo(sender, phyId, channel, txPowerDbm, content);
51             newEntry.StartTxTimestamp = addTime;
52             overTheAirPackets.Add(newEntry);
53             Logger.Log(LogLevel.Noisy, "InterferenceQueue.Add at {0}: [{1}]", addTime, BitConverter.ToString(content));
54             NotifyListeners();
55         }
56 
Remove(IRadio sender)57         public static void Remove(IRadio sender)
58         {
59             PacketInfo entry = PacketLookup(sender);
60 
61             if (entry == null)
62             {
63                 Logger.Log(LogLevel.Error, "InterferenceQueue.Remove: entry not found");
64                 return;
65             }
66 
67             if (entry.PacketIsStale)
68             {
69                 Logger.Log(LogLevel.Noisy, "InterferenceQueue.Remove at {0}, OTA time={1}: [{2}] - removed",
70                         IPeripheralExtensions.GetMachine(entry.Sender).LocalTimeSource.ElapsedVirtualTime,
71                         IPeripheralExtensions.GetMachine(entry.Sender).LocalTimeSource.ElapsedVirtualTime - entry.StartTxTimestamp,
72                         BitConverter.ToString(entry.PacketContent));
73                         overTheAirPackets.Remove(entry);
74             }
75             else
76             {
77                 Logger.Log(LogLevel.Noisy, "InterferenceQueue.Remove at {0}, OTA time={1}: [{2}] - marking it stale",
78                         IPeripheralExtensions.GetMachine(entry.Sender).LocalTimeSource.ElapsedVirtualTime,
79                         IPeripheralExtensions.GetMachine(entry.Sender).LocalTimeSource.ElapsedVirtualTime - entry.StartTxTimestamp,
80                         BitConverter.ToString(entry.PacketContent));
81                         entry.PacketIsStale = true;
82             }
83 
84             overTheAirPackets.Remove(entry);
85             NotifyListeners();
86         }
87 
GetTxStartTime(IRadio sender)88         public static TimeInterval GetTxStartTime(IRadio sender)
89         {
90             // Here we want to look up also stale packets, since it is possible that the sender already "completed"
91             // the transmission while the receiver hasn't been notified yet (possibly due to a combination of the packet
92             // being short and the QuantumTime being long enough).
93             PacketInfo entry = PacketLookup(sender);
94 
95             if (entry == null)
96             {
97                 Logger.Log(LogLevel.Error, "InterferenceQueue.GetTxStartTime: entry not found");
98                 return TimeInterval.Empty;
99             }
100 
101             return entry.StartTxTimestamp;
102         }
103 
104         // Simple initial implementation: we return a "high" hardcoded RSSI value if there is
105         // at least a packet going over the air on the passed PHY/Channel.
106         // TODO: Eventually we will want to:
107         // - take into account the distance between sender and receiver
108         // - the sender TX power
109         // - interference between different PHYs
110         // - Co-channel interference
GetCurrentRssi(IRadio receiver, RadioPhyId phyId, int channel)111         public static int GetCurrentRssi(IRadio receiver, RadioPhyId phyId, int channel)
112         {
113             if (ForceBusyRssi)
114             {
115                 return RssiBusyChannelHardCodedValueDbm;
116             }
117 
118             foreach(PacketInfo entry in overTheAirPackets)
119             {
120                 if (entry.PhyId == phyId && entry.Channel == channel)
121                 {
122                     return RssiBusyChannelHardCodedValueDbm;
123                 }
124             }
125 
126             return RssiClearChannelHardCodedValueDbm;
127         }
128 
PacketLookup(IRadio sender)129         private static PacketInfo PacketLookup(IRadio sender)
130         {
131             foreach(PacketInfo entry in overTheAirPackets)
132             {
133                 if (entry.Sender == sender)
134                 {
135                     return entry;
136                 }
137             }
138 
139             return null;
140         }
141 
NotifyListeners()142         private static void NotifyListeners()
143         {
144             foreach(IInterferenceQueueListener listener in listeners)
145             {
146                 listener.InteferenceQueueChangedCallback();
147             }
148         }
149 
150         private const int RssiBusyChannelHardCodedValueDbm = 20;
151         private const int RssiClearChannelHardCodedValueDbm = -120;
152         public static bool ForceBusyRssi = false;
153     }
154 
155     public class PacketInfo
156     {
157         // Fields
158         private byte[] packetContent;
159         private IRadio sender;
160         private RadioPhyId phyId;
161         private int channel;
162         private int txPowerDbm;
163         private TimeInterval startTx;
164         private bool packetIsStale;
165 
166         // Methods
167         public IRadio Sender => sender;
168         public RadioPhyId PhyId => phyId;
169         public int Channel => channel;
170         public int TxPowerDbm => txPowerDbm;
171         public byte[] PacketContent => packetContent;
172         public bool PacketIsStale
173         {
174             set
175             {
176                 packetIsStale = value;
177             }
178             get
179             {
180                 return packetIsStale;
181             }
182         }
183         public TimeInterval StartTxTimestamp
184         {
185             set
186             {
187                 startTx = value;
188             }
189             get
190             {
191                 return startTx;
192             }
193         }
PacketInfo(IRadio sender, RadioPhyId phyId, int channel, int txPowerDbm, byte[] content)194         public PacketInfo(IRadio sender, RadioPhyId phyId, int channel, int txPowerDbm, byte[] content)
195         {
196             this.sender = sender;
197             this.phyId = phyId;
198             this.channel = channel;
199             this.txPowerDbm = txPowerDbm;
200 
201             packetIsStale = false;
202             startTx = IPeripheralExtensions.GetMachine(sender).LocalTimeSource.ElapsedVirtualTime;
203             packetContent = new byte[content.Length];
204             Array.Copy(content, packetContent, content.Length);
205         }
206     }
207 
208     public enum RadioPhyId
209     {
210         Phy_802154_2_4GHz_OQPSK = 0,
211         Phy_BLE_2_4GHz_GFSK     = 1,
212     }
213 }