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 }