1 // 2 // Copyright (c) 2010-2024 Antmicro 3 // Copyright (c) 2011-2015 Realtime Embedded 4 // 5 // This file is licensed under the MIT License. 6 // Full license text is available in 'licenses/MIT.txt'. 7 // 8 using System; 9 using System.Collections.Generic; 10 using System.Linq; 11 using System.Threading; 12 using Antmicro.Migrant; 13 using Antmicro.Renode.Core; 14 using Antmicro.Renode.Exceptions; 15 using Antmicro.Renode.Logging; 16 using Antmicro.Renode.Peripherals; 17 using System.Text; 18 19 namespace Antmicro.Renode.Peripherals.Bus 20 { 21 partial class SystemBus 22 { 23 private interface IReadOnlyPeripheralCollection 24 { 25 IEnumerable<IBusRegistered<IBusPeripheral>> Peripherals { get; } FindAccessMethods(ulong address, out ulong startAddress, out ulong endAddress)26 PeripheralAccessMethods FindAccessMethods(ulong address, out ulong startAddress, out ulong endAddress); 27 #if DEBUG ShowStatistics()28 void ShowStatistics(); 29 #endif 30 } 31 32 private class PeripheralCollection : IReadOnlyPeripheralCollection, ICoalescable<PeripheralCollection> 33 { PeripheralCollection(SystemBus sysbus)34 internal PeripheralCollection(SystemBus sysbus) 35 { 36 this.sysbus = sysbus; 37 blocks = new Block[0]; 38 shortBlocks = new Dictionary<ulong, Block>(); 39 sync = new object(); 40 InvalidateLastBlock(); 41 } 42 43 public IEnumerable<IBusRegistered<IBusPeripheral>> Peripherals 44 { 45 get 46 { 47 lock(sync) 48 { 49 return blocks.Union(shortBlocks.Select(x => x.Value)).Select(x => x.Peripheral).Distinct(); 50 } 51 } 52 } 53 Add(ulong start, ulong end, IBusRegistered<IBusPeripheral> peripheral, PeripheralAccessMethods accessMethods)54 public void Add(ulong start, ulong end, IBusRegistered<IBusPeripheral> peripheral, PeripheralAccessMethods accessMethods) 55 { 56 // the idea here is that we prefer the peripheral to go to dictionary 57 // ideally it can go to dicitonary wholly, but we try to put there as much as we can 58 lock(sync) 59 { 60 var name = string.Format("{0} @ {1}.", peripheral.Peripheral.GetType().Name, peripheral.RegistrationPoint); 61 // TODO: check index (and start/stop) 62 var block = new Block { Start = start, End = end, AccessMethods = accessMethods, Peripheral = peripheral }; 63 // let's decide whether block should go to array, dictionary or both 64 var goToDictionary = true; 65 // is the peripheral properly aligned? 66 if((start & PageAlign) != 0) 67 { 68 sysbus.NoisyLog("{0} is at not aligned address - not using dictionary.", name); 69 goToDictionary = false; 70 } 71 // is the peripheral small enough? 72 var size = end - start; // don't add 1 because `end` is actually one past the end. 73 var numOfPages = size/PageSize; 74 if(numOfPages > NumOfPagesThreshold) 75 { 76 sysbus.NoisyLog("{0} is too large - not using dictionary.", name); 77 goToDictionary = false; 78 } 79 var goToArray = !goToDictionary; // peripheral will go to array if we couldn't put it in dictionary 80 if(goToDictionary && size % PageSize != 0) 81 { 82 // but it should also go to array if it isn't properly aligned on its last page 83 goToArray = true; 84 } 85 if(goToArray) 86 { 87 blocks = blocks.Union(new [] { block }).OrderBy(x => x.Start).ToArray(); 88 sysbus.NoisyLog("Added {0} to binary search array.", name); 89 } 90 if(!goToDictionary) 91 { 92 return; 93 } 94 // note that truncating is in fact good thing here 95 for(var i = 0u; i < numOfPages; i++) 96 { 97 shortBlocks.Add(start + i * PageSize, block); 98 } 99 sysbus.NoisyLog("Added {0} to dictionary.", name); 100 } 101 } 102 Coalesce(PeripheralCollection source)103 public void Coalesce(PeripheralCollection source) 104 { 105 foreach(var block in source.blocks.Union(source.shortBlocks.Values)) 106 { 107 // Don't add overlapping peripherals. 108 // We subtract 1 from the end address because it is actually one past the end. 109 if(FindAccessMethods(block.Start, out _, out _) != null 110 || FindAccessMethods(block.End - 1, out _, out _) != null) 111 { 112 return; 113 } 114 Add(block.Start, block.End, block.Peripheral, block.AccessMethods); 115 } 116 } 117 Move(IBusRegistered<IBusPeripheral> registeredPeripheral, BusRangeRegistration newRegistration)118 public void Move(IBusRegistered<IBusPeripheral> registeredPeripheral, BusRangeRegistration newRegistration) 119 { 120 var newRegisteredPeripheral = new BusRegistered<IBusPeripheral>(registeredPeripheral.Peripheral, newRegistration); 121 lock(sync) 122 { 123 var block = blocks.FirstOrDefault(x => x.Peripheral == registeredPeripheral); 124 if(block.Peripheral == registeredPeripheral) 125 { 126 blocks = blocks.Where(x => x.Peripheral != registeredPeripheral).ToArray(); 127 } 128 else 129 { 130 block = shortBlocks.Values.FirstOrDefault(x => x.Peripheral == registeredPeripheral); 131 if(block.Peripheral != registeredPeripheral) 132 { 133 throw new RecoverableException("Attempted to move a peripheral that does not exist in the collection"); 134 } 135 var toRemove = shortBlocks.Where(x => x.Value.Peripheral != registeredPeripheral).Select(x => x.Key).ToArray(); 136 foreach(var keyToRemove in toRemove) 137 { 138 shortBlocks.Remove(keyToRemove); 139 } 140 } 141 InvalidateLastBlock(); 142 143 var newStart = newRegistration.StartingPoint; 144 var size = newRegistration.Range.Size; 145 // End address is one past the end. 146 Add(newStart, newStart + size, newRegisteredPeripheral, block.AccessMethods); 147 } 148 } 149 Remove(IPeripheral peripheral)150 public void Remove(IPeripheral peripheral) 151 { 152 lock(sync) 153 { 154 // list is scanned first 155 blocks = blocks.Where(x => x.Peripheral.Peripheral != peripheral).ToArray(); 156 // then dictionary 157 var toRemove = shortBlocks.Where(x => x.Value.Peripheral.Peripheral == peripheral).Select(x => x.Key).ToArray(); 158 foreach(var keyToRemove in toRemove) 159 { 160 shortBlocks.Remove(keyToRemove); 161 } 162 InvalidateLastBlock(); 163 } 164 } 165 Remove(ulong start, ulong end)166 public void Remove(ulong start, ulong end) 167 { 168 lock(sync) 169 { 170 blocks = blocks.Where(x => x.Start > end || x.End < start).ToArray(); 171 var toRemove = shortBlocks.Where(x => x.Value.Start >= start && x.Value.End <= end).Select(x => x.Key).ToArray(); 172 foreach(var keyToRemove in toRemove) 173 { 174 shortBlocks.Remove(keyToRemove); 175 } 176 InvalidateLastBlock(); 177 } 178 } 179 VisitAccessMethods(IBusPeripheral peripheral, Func<PeripheralAccessMethods, PeripheralAccessMethods> onPam)180 public void VisitAccessMethods(IBusPeripheral peripheral, Func<PeripheralAccessMethods, PeripheralAccessMethods> onPam) 181 { 182 lock(sync) 183 { 184 blocks = blocks.Select(block => 185 { 186 if(peripheral != null && block.Peripheral.Peripheral != peripheral) 187 { 188 return block; 189 } 190 block.AccessMethods = onPam(block.AccessMethods); 191 return block; 192 }).ToArray(); 193 shortBlocks = shortBlocks.Select(dEntry => 194 { 195 if(peripheral != null && dEntry.Value.Peripheral.Peripheral != peripheral) 196 { 197 return dEntry; 198 } 199 var block = dEntry.Value; 200 block.AccessMethods = onPam(block.AccessMethods); 201 return new KeyValuePair<ulong, Block>(dEntry.Key, block); 202 }).ToDictionary(x => x.Key, x => x.Value); 203 InvalidateLastBlock(); 204 } 205 } 206 FindAccessMethods(ulong address, out ulong startAddress, out ulong endAddress)207 public PeripheralAccessMethods FindAccessMethods(ulong address, out ulong startAddress, out ulong endAddress) 208 { 209 // no need to lock here yet, cause last block is in the thread local storage 210 var lastBlock = lastBlockStorage.Value; 211 #if DEBUG 212 Interlocked.Increment(ref queryCount); 213 #endif 214 /// Note `< End` - End is currently one past the end in reality. Please also change <see cref="ICoalescable{T}.Coalesce"> after changing this. 215 if (address >= lastBlock.Start && address < lastBlock.End) 216 { 217 #if DEBUG 218 Interlocked.Increment(ref lastPeripheralCount); 219 #endif 220 startAddress = lastBlock.Start; 221 endAddress = lastBlock.End; 222 return lastBlock.AccessMethods; 223 } 224 lock(sync) 225 { 226 // let's try dictionary 227 Block block; 228 if(!shortBlocks.TryGetValue(address & ~PageAlign, out block)) 229 { 230 // binary search - our last resort 231 var index = BinarySearch(address); 232 if(index == -1) 233 { 234 startAddress = 0; 235 endAddress = 0; 236 return null; 237 } 238 #if DEBUG 239 Interlocked.Increment(ref binarySearchCount); 240 #endif 241 block = blocks[index]; 242 } 243 #if DEBUG 244 else 245 { 246 Interlocked.Increment(ref dictionaryCount); 247 } 248 #endif 249 startAddress = block.Start; 250 endAddress = block.End; 251 lastBlockStorage.Value = block; 252 return block.AccessMethods; 253 } 254 } 255 256 #if DEBUG ShowStatistics()257 public void ShowStatistics() 258 { 259 var misses = queryCount - lastPeripheralCount - dictionaryCount - binarySearchCount; 260 var line = new StringBuilder("\n Memory queries statistics are as follows:"); 261 if(queryCount > 0) 262 { 263 line.AppendFormat("\tConsecutive hits: {0:00.00} ({1})\n", 100.0 * lastPeripheralCount / queryCount, lastPeripheralCount) 264 .AppendFormat("\tDictionary hits: {0:00.00} ({1})\n", 100.0 * dictionaryCount / queryCount, dictionaryCount) 265 .AppendFormat("\tBinary search: {0:00.00} ({1})\n", 100.0 * binarySearchCount / queryCount, binarySearchCount) 266 .AppendFormat("\tMisses: {0:00.00} ({1})", 100.0 * misses / queryCount, misses); 267 } 268 else 269 { 270 line.AppendLine("\tNo queries"); 271 } 272 sysbus.DebugLog(line.ToString()); 273 } 274 #endif 275 BinarySearch(ulong offset)276 private int BinarySearch(ulong offset) 277 { 278 var min = 0; 279 var max = blocks.Length - 1; 280 if(blocks.Length == 0) 281 { 282 return -1; 283 } 284 do 285 { 286 var current = (min + max) / 2; 287 if (offset >= blocks[current].End) 288 { 289 min = current + 1; 290 } 291 else if (offset < blocks[current].Start) 292 { 293 max = current - 1; 294 } 295 else 296 { 297 return current; 298 } 299 } 300 while(min <= max); 301 return -1; 302 } 303 InvalidateLastBlock()304 private void InvalidateLastBlock() 305 { 306 lastBlockStorage = new ThreadLocal<Block>(); 307 } 308 309 private Dictionary<ulong, Block> shortBlocks; 310 private Block[] blocks; 311 [Constructor] 312 private ThreadLocal<Block> lastBlockStorage; 313 private object sync; 314 private readonly SystemBus sysbus; 315 316 #if DEBUG 317 private long queryCount; 318 private long lastPeripheralCount; 319 private long dictionaryCount; 320 private long binarySearchCount; 321 #endif 322 323 private const ulong PageSize = 1 << 11; 324 private const ulong PageAlign = PageSize - 1; 325 private const long NumOfPagesThreshold = 4; 326 327 private struct Block 328 { 329 public ulong Start; 330 public ulong End; 331 public PeripheralAccessMethods AccessMethods; 332 public IBusRegistered<IBusPeripheral> Peripheral; 333 } 334 } 335 } 336 } 337 338