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