// // Copyright (c) 2010-2025 Antmicro // Copyright (c) 2011-2015 Realtime Embedded // // This file is licensed under the MIT License. // Full license text is available in 'licenses/MIT.txt'. // using System; using System.Linq; using System.Globalization; using System.Collections.Generic; using Antmicro.Renode.Core; using Antmicro.Renode.Core.Structure; using Antmicro.Renode.Exceptions; using Antmicro.Renode.Logging; using Antmicro.Renode.Peripherals.Bus.Wrappers; using Antmicro.Renode.Peripherals.CPU; using Antmicro.Renode.Utilities; using System.Threading; using System.Collections.ObjectModel; using System.Text; using Machine = Antmicro.Renode.Core.Machine; using Antmicro.Migrant; using Antmicro.Migrant.Hooks; using ELFSharp.ELF; using ELFSharp.ELF.Segments; using ELFSharp.UImage; using System.IO; using Antmicro.Renode.Core.Extensions; using System.Reflection; using Antmicro.Renode.UserInterface; using Antmicro.Renode.Peripherals.Memory; using Range = Antmicro.Renode.Core.Range; namespace Antmicro.Renode.Peripherals.Bus { /// /// The SystemBus is the main system class, where all data passes through. /// [Icon("sysbus")] [ControllerMask(typeof(IPeripheral))] public sealed partial class SystemBus : IBusController, IDisposable { internal SystemBus(IMachine machine) { this.Machine = machine; cpuSync = new object(); binaryFingerprints = new List(); cpuById = new Dictionary(); idByCpu = new Dictionary(); hooksOnRead = new Dictionary>(); hooksOnWrite = new Dictionary>(); pcCache.OnChanged += HandleChangedSymbols; InitStructures(); this.Log(LogLevel.Info, "System bus created."); } public void LoadFileChunks(string path, IEnumerable chunks, ICPU cpu) { var minAddr = this.LoadFileChunks(chunks, cpu); AddFingerprint(path); UpdateLowestLoadedAddress(minAddr); this.DebugLog(path + " File loaded."); } public void Unregister(IBusPeripheral peripheral) { using(Machine.ObtainPausedState(true)) { Machine.UnregisterAsAChildOf(this, peripheral); UnregisterInner(peripheral); } } public void Unregister(IBusRegistered busRegisteredPeripheral) { using(Machine.ObtainPausedState(true)) { Machine.UnregisterAsAChildOf(this, busRegisteredPeripheral.RegistrationPoint); UnregisterInner(busRegisteredPeripheral); } } public void Unregister(IPeripheral peripheral) { using(Machine.ObtainPausedState(true)) { Machine.UnregisterAsAChildOf(this, peripheral); RemoveContextKeys(peripheral); } } public void Register(IPeripheral peripheral, NullRegistrationPoint registrationPoint) { using(Machine.ObtainPausedState(true)) { // NullRegistrationPoint peripherals are not mapped on the bus and // are not directly accessible from the emulated software. Machine.RegisterAsAChildOf(this, peripheral, registrationPoint); } } public void Register(IBusPeripheral peripheral, BusRangeRegistration registrationPoint) { var methods = PeripheralAccessMethods.CreateWithLock(); if(registrationPoint is BusParametrizedRegistration parametrizedRegistrationPoint) { parametrizedRegistrationPoint.RegisterForEachContext((contextRegistration) => { // Prepare accessor methods in the context of registration, // as it may want to fill them according to the CPU context. methods = PeripheralAccessMethods.CreateWithLock(); contextRegistration.FillAccessMethods(peripheral, ref methods); FillAccessMethodsWithDefaultMethods(peripheral, ref methods); RegisterInner(peripheral, methods, contextRegistration, context: contextRegistration.Initiator); }); } else if(registrationPoint is BusMultiRegistration multiRegistrationPoint) { if(peripheral is IMapped) { throw new ConstructionException(string.Format("It is not allowed to register `{0}` peripheral using `{1}`", typeof(IMapped).Name, typeof(BusMultiRegistration).Name)); } FillAccessMethodsWithTaggedMethods(peripheral, multiRegistrationPoint.ConnectionRegionName, ref methods); multiRegistrationPoint.RegisterForEachContext((contextRegistration) => RegisterInner(peripheral, methods, contextRegistration, context: contextRegistration.Initiator)); } else { FillAccessMethodsWithDefaultMethods(peripheral, ref methods); registrationPoint.RegisterForEachContext((contextRegistration) => RegisterInner(peripheral, methods, contextRegistration, context: contextRegistration.Initiator)); } } public void Register(IBusPeripheral peripheral, BusMultiRegistration registrationPoint) { Register(peripheral, (BusRangeRegistration)registrationPoint); } public void Register(IBusPeripheral peripheral, BusParametrizedRegistration registrationPoint) { Register(peripheral, (BusRangeRegistration)registrationPoint); } void IPeripheralRegister.Unregister(IBusPeripheral peripheral) { Unregister(peripheral); } public void Register(IKnownSize peripheral, BusPointRegistration registrationPoint) { Register(peripheral, registrationPoint.ToRangeRegistration(checked((ulong)peripheral.Size))); } public void Unregister(IKnownSize peripheral) { Unregister((IBusPeripheral)peripheral); } public void MoveRegistrationWithinContext(IBusPeripheral peripheral, BusRangeRegistration newRegistration, ICPU context, Func>, IBusRegistered> selector = null) { if(context == null && !TryFindCurrentThreadCPUAndId(out context, out var _)) { throw new RecoverableException("Moving a peripheral is supported only from CPU thread if context isn't explicitly set"); } var wasMapped = RemoveMappingsForPeripheral(peripheral); var busRegisteredEntries = peripheralsCollectionByContext[context].Peripherals.Where(x => x.Peripheral == peripheral).ToList(); if(busRegisteredEntries.Count == 0) { throw new RecoverableException("Attempted to move a peripheral that isn't registered within current context"); } IBusRegistered busRegistered; if(selector != null) { busRegistered = selector(busRegisteredEntries); if(busRegistered == null) { throw new RecoverableException("Provided selector failed to find a suitable registration point"); } } else { if(busRegisteredEntries.Count > 1) { throw new RecoverableException($"Unable to unambiguously determine a registration point, found multiple candidates: {String.Join(", ", busRegisteredEntries.Select(x => x.RegistrationPoint))}"); } busRegistered = busRegisteredEntries[0]; } if(IsAddressRangeLocked(busRegistered.RegistrationPoint.Range, context)) { throw new RecoverableException("Moving a peripheral to a locked address range is not supported"); } UnregisterAccessFlags(busRegistered.RegistrationPoint, context); peripheralsCollectionByContext.WithStateCollection(context, null, collection => { collection.Move(busRegistered, newRegistration); }); Machine.ExchangeRegistrationPointForPeripheral(this, peripheral, busRegistered.RegistrationPoint, newRegistration); if(wasMapped) { AddMappingsForPeripheral(peripheral, newRegistration, context); } if(peripheral is ArrayMemory) { foreach(var cpu in GetCPUsForContext(context)) { var range = newRegistration.Range; cpu.RegisterAccessFlags(range.StartAddress, range.Size, isIoMemory: true); } } } public void Register(ICPU cpu, CPURegistrationPoint registrationPoint) { lock(cpuSync) { if(mappingsRemoved) { throw new RegistrationException("Currently cannot register CPU after some memory mappings have been dynamically removed."); } if(!registrationPoint.Slot.HasValue) { var i = 0; while(cpuById.ContainsKey(i)) { i++; } registrationPoint = new CPURegistrationPoint(i); } Machine.RegisterAsAChildOf(this, cpu, registrationPoint); cpuById.Add(registrationPoint.Slot.Value, cpu); idByCpu.Add(cpu, registrationPoint.Slot.Value); AddContextKeys(cpu); if(cpu is ICPUWithMappedMemory memoryMappedCpu) { foreach(var mapping in mappingsForPeripheral.SelectMany(x => x.Value) .Where(x => x.Context == null || x.Context == cpu)) { memoryMappedCpu.MapMemory(mapping); } } if(cpu.Endianness != Endianess) { hasCpuWithMismatchedEndianness = true; UpdateAccessMethods(); } if(GetCPUs().Select(x => x.Endianness).Distinct().Count() > 1) { throw new RegistrationException("Currently there can't be CPUs with different endiannesses on the same bus."); } } } public void Unregister(ICPU cpu) { using(Machine.ObtainPausedState(true)) { Machine.UnregisterFromParent(cpu); lock(cpuSync) { var id = idByCpu[cpu]; idByCpu.Remove(cpu); cpuById.Remove(id); RemoveContextKeys(cpu); } } } public void SetPCOnAllCores(ulong pc) { using(Machine.ObtainPausedState(true)) { lock(cpuSync) { foreach(var p in idByCpu.Keys.Cast()) { p.PC = pc; } } } } public void LogAllPeripheralsAccess(bool enable = true) { lock(cpuSync) { foreach(var p in allPeripherals.SelectMany(x => x.Peripherals)) { LogPeripheralAccess(p.Peripheral, enable); } } } public void LogPeripheralAccess(IBusPeripheral busPeripheral, bool enable = true) { foreach(var peripherals in allPeripherals) { peripherals.VisitAccessMethods(busPeripheral, pam => { // first check whether logging is already enabled, method should be idempotent var loggingAlreadEnabled = pam.WriteByte.Target is HookWrapper; this.Log(LogLevel.Info, "Logging already enabled: {0}.", loggingAlreadEnabled); if(enable == loggingAlreadEnabled) { return pam; } if(enable) { pam.WrapMethods(typeof(ReadLoggingWrapper<>), typeof(WriteLoggingWrapper<>)); return pam; } else { pam.RemoveWrappersOfType(typeof(ReadLoggingWrapper<>), typeof(WriteLoggingWrapper<>)); return pam; } }); } } public void EnableAllTranslations(bool enable = true) { foreach(var p in allPeripherals.SelectMany(x => x.Peripherals)) { EnableAllTranslations(p.Peripheral, enable); } } public void EnableAllTranslations(IBusPeripheral busPeripheral, bool enable = true) { foreach(var peripherals in allPeripherals) { peripherals.VisitAccessMethods(busPeripheral, pam => { pam.EnableAllTranslations(enable, Endianess); return pam; }); } } public IEnumerable GetCPUs() { lock(cpuSync) { return new ReadOnlyCollection(idByCpu.Keys.ToList()); } } public int GetCPUSlot(ICPU cpu) { lock(cpuSync) { if(idByCpu.ContainsKey(cpu)) { return idByCpu[cpu]; } throw new KeyNotFoundException("Given CPU is not registered."); } } public ICPU GetCurrentCPU() { ICPU cpu; if(!TryGetCurrentCPU(out cpu)) { // TODO: inline throw new RecoverableException(CantFindCpuIdMessage); } return cpu; } public int GetCurrentCPUId() { int id; if(!TryGetCurrentCPUId(out id)) { throw new RecoverableException(CantFindCpuIdMessage); } return id; } public IEnumerable GetAllContextKeys() { return peripheralsCollectionByContext.GetAllContextKeys(); } public bool TryGetCurrentContextState(out IPeripheralWithTransactionState cpu, out T cpuState) { cpu = null; cpuState = default; if(!threadLocalContext.InUse || !threadLocalContext.InitiatorState.HasValue) { return false; } cpu = threadLocalContext.Initiator as IPeripheralWithTransactionState; if((cpu == null) || !cpu.TryConvertUlongToStateObj(threadLocalContext.InitiatorState.Value, out var state)) { return false; } if(state is T requestedTypeSTate) { cpuState = requestedTypeSTate; return true; } return false; } public bool TryConvertStateToUlongForContext(IPeripheral context, IContextState cpuStateObj, out ulong? state) { state = null; if(!(context is IPeripheralWithTransactionState peripheralWithTransactionState) || !peripheralWithTransactionState.TryConvertStateObjToUlong(cpuStateObj, out state)) { return false; } return true; } private void HandleChangedSymbols() { OnSymbolsChanged?.Invoke(Machine); } private IEnumerable GetCPUsForContext(IPeripheral context) { return GetCPUs().Where(x => context == null || x == context); } private IEnumerable GetCPUsForContext(IPeripheral context) where T : IPeripheral { return GetCPUsForContext(context).OfType(); } private bool TryGetCurrentCPUId(out int cpuId) { if(threadLocalContext.InUse && threadLocalContext.Initiator is ICPU cpu) { cpuId = idByCpu[cpu]; return true; } /* * Because getting cpu id can possibly be a heavy operation, we cache the * obtained ID in the thread local storage. Note that we assume here that the * thread with such storage won't be used for another purposes than it was * used originally (i.e. cpu loop). */ if(cachedCpuId.IsValueCreated) { cpuId = cachedCpuId.Value; return true; } return TryFindCurrentThreadCPUAndId(out var _, out cpuId); } private bool TryFindCurrentThreadCPUAndId(out ICPU cpu, out int cpuId) { lock(cpuSync) { foreach(var entry in cpuById) { cpu = entry.Value; if(!cpu.OnPossessedThread) { continue; } cpuId = entry.Key; cachedCpuId.Value = cpuId; return true; } cpu = default(ICPU); cpuId = -1; return false; } } public bool TryGetCurrentCPU(out ICPU cpu) { lock(cpuSync) { int id; if(TryGetCurrentCPUId(out id)) { cpu = cpuById[id]; return true; } cpu = null; return false; } } /// /// Unregister peripheral from the specified address. /// /// NOTE: After calling this method, peripheral may still be /// registered in the SystemBus at another address. In order /// to remove peripheral completely use 'Unregister' method. /// /// Address on system bus where the peripheral is registered. /// /// CPU context in which peripherals should be scanned. /// This is useful when some peripherals are only accessible from selected CPUs. /// /// If not provided, the global peripherals collection (i.e., peripherals available for all CPUs) is searched. /// public void UnregisterFromAddress(ulong address, ICPU context = null) { var busRegisteredPeripheral = WhatIsAt(address, context); if(busRegisteredPeripheral == null) { throw new RecoverableException(string.Format( "There is no peripheral registered at 0x{0:X}.", address)); } Unregister(busRegisteredPeripheral); } public void Dispose() { cachedCpuId.Dispose(); threadLocalContext.Dispose(); globalLookup.Dispose(); foreach(var lookup in localLookups.Values) { lookup.Dispose(); } #if DEBUG foreach(var peripherals in allPeripherals) { peripherals.ShowStatistics(); } #endif } /// Checks what is at a given address. /// /// A with the address to check. /// /// /// CPU context in which peripherals should be scanned. /// This is useful when some peripherals are only accessible from selected CPUs. /// /// If not provided, the global peripherals collection (i.e., peripherals available for all CPUs) is searched. /// /// /// A peripheral which is at the given address. /// public IBusRegistered WhatIsAt(ulong address, IPeripheral context = null) { return GetAccessiblePeripheralsForContext(context).FirstOrDefault(x => x.RegistrationPoint.Range.Contains(address)); } public IPeripheral WhatPeripheralIsAt(ulong address, IPeripheral context = null) { var registered = WhatIsAt(address, context); if(registered != null) { return registered.Peripheral; } return null; } public IBusRegistered FindMemory(ulong address, ICPU context = null) { return GetAccessiblePeripheralsForContext(context) .Where(x => x.Peripheral is MappedMemory) .Convert() .FirstOrDefault(x => x.RegistrationPoint.Range.Contains(address)); } public bool IsMemory(ulong address, ICPU context = null) { return GetAccessiblePeripheralsForContext(context) .Any(x => x.Peripheral is IMemory && x.RegistrationPoint.Range.Contains(address)); } public IEnumerable> GetMappedPeripherals(IPeripheral context = null) { return GetAccessiblePeripheralsForContext(context) .Where(x => x.Peripheral is IMapped) .Convert(); } public IEnumerable> GetRegistrationsForPeripheralType(IPeripheral context = null) { return GetAccessiblePeripheralsForContext(context) .Where(x => x.Peripheral is T); } public IEnumerable> GetRegisteredPeripherals(IPeripheral context = null) { return GetAccessiblePeripheralsForContext(context); } public void SilenceRange(Range range) { var silencer = new Silencer(); Register(silencer, new BusRangeRegistration(range)); } public void ReadBytes(ulong address, int count, byte[] destination, int startIndex, bool onlyMemory = false, IPeripheral context = null) { using(SetLocalContext(context)) { var targets = FindTargets(address, checked((ulong)count), context); if(onlyMemory) { ThrowIfNotAllMemory(targets); } foreach(var target in targets) { var memory = target.What.Peripheral as MappedMemory; if(memory != null) { checked { memory.ReadBytes(checked((long)(target.Offset - target.What.RegistrationPoint.Range.StartAddress + target.What.RegistrationPoint.Offset)), (int)target.SourceLength, destination, startIndex + (int)target.SourceIndex); } } else { for(var i = 0UL; i < target.SourceLength; ++i) { destination[checked((ulong)startIndex) + target.SourceIndex + i] = ReadByte(target.Offset + i, context); } } } } } public byte[] ReadBytes(ulong address, int count, bool onlyMemory = false, IPeripheral context = null) { var result = new byte[count]; ReadBytes(address, count, result, 0, onlyMemory, context); return result; } public byte[] ReadBytes(long offset, int count, IPeripheral context = null) { return ReadBytes((ulong)offset, count, context: context); } public void WriteBytes(byte[] bytes, ulong address, bool onlyMemory = false, IPeripheral context = null) { WriteBytes(bytes, address, bytes.Length, onlyMemory, context); } public void WriteBytes(byte[] bytes, ulong address, int startingIndex, long count, bool onlyMemory = false, IPeripheral context = null) { using(SetLocalContext(context)) { var targets = FindTargets(address, checked((ulong)count), context); if(onlyMemory) { ThrowIfNotAllMemory(targets); } foreach(var target in targets) { var multibytePeripheral = target.What.Peripheral as IMultibyteWritePeripheral; if(multibytePeripheral != null) { checked { multibytePeripheral.WriteBytes(checked((long)(target.Offset - target.What.RegistrationPoint.Range.StartAddress + target.What.RegistrationPoint.Offset)), bytes, startingIndex + (int)target.SourceIndex, (int)target.SourceLength); } } else { for(var i = 0UL; i < target.SourceLength; ++i) { WriteByte(target.Offset + i, bytes[target.SourceIndex + (ulong)startingIndex + i]); } } } } } public void WriteBytes(byte[] bytes, ulong address, long count, bool onlyMemory = false, IPeripheral context = null) { WriteBytes(bytes, address, 0, count, onlyMemory, context); } public void WriteBytes(long offset, byte[] array, int startingIndex, int count, IPeripheral context = null) { WriteBytes(array, (ulong)offset, startingIndex, count, context: context); } public void ZeroRange(Range range, IPeripheral context = null) { var zeroBlock = new byte[1024 * 1024]; var blocksNo = range.Size / (ulong)zeroBlock.Length; for(var i = 0UL; i < blocksNo; i++) { WriteBytes(zeroBlock, range.StartAddress + i * (ulong)zeroBlock.Length, context: context); } WriteBytes(zeroBlock, range.StartAddress + blocksNo * (ulong)zeroBlock.Length, (int)range.Size % zeroBlock.Length, context: context); } // Specifying `textAddress` will override the address of the program text - the symbols will be applied // as if the first loaded segment started at the specified address. This is equivalent to the ADDR parameter // to GDB's add-symbol-file. public void LoadSymbolsFrom(IELF elf, bool useVirtualAddress = false, ulong? textAddress = null, ICPU context = null) { GetOrCreateLookup(context).LoadELF(elf, useVirtualAddress, textAddress); pcCache.Invalidate(); } public void ClearSymbols(ICPU context = null) { RemoveLookup(context); pcCache.Invalidate(); } public void AddSymbol(Range address, string name, bool isThumb = false, ICPU context = null) { checked { GetOrCreateLookup(context).InsertSymbol(name, address.StartAddress, address.Size); } pcCache.Invalidate(); } public void LoadUImage(ReadFilePath fileName, IInitableCPU cpu = null) { if(!Machine.IsPaused) { throw new RecoverableException("Cannot load ELF on an unpaused machine."); } UImage uImage; this.DebugLog("Loading uImage {0}.", fileName); switch(UImageReader.TryLoad(fileName, out uImage)) { case UImageResult.NotUImage: throw new RecoverableException(string.Format("Given file '{0}' is not a U-Boot image.", fileName)); case UImageResult.BadChecksum: throw new RecoverableException(string.Format("Header checksum does not match for the U-Boot image '{0}'.", fileName)); case UImageResult.NotSupportedImageType: throw new RecoverableException(string.Format("Given file '{0}' is not of a supported image type.", fileName)); } byte[] toLoad; switch(uImage.TryGetImageData(out toLoad)) { case ImageDataResult.BadChecksum: throw new RecoverableException("Bad image checksum, probably corrupted image."); case ImageDataResult.UnsupportedCompressionFormat: throw new RecoverableException(string.Format("Unsupported compression format '{0}'.", uImage.Compression)); } WriteBytes(toLoad, uImage.LoadAddress, context: cpu); if(cpu != null) { cpu.InitFromUImage(uImage); } else { foreach(var c in GetCPUs().OfType()) { c.InitFromUImage(uImage); } } this.Log(LogLevel.Info, string.Format( "Loaded U-Boot image '{0}'\n" + "load address: 0x{1:X}\n" + "size: {2}B = {3}B\n" + "timestamp: {4}\n" + "entry point: 0x{5:X}\n" + "architecture: {6}\n" + "OS: {7}", uImage.Name, uImage.LoadAddress, uImage.Size, Misc.NormalizeBinary(uImage.Size), uImage.Timestamp, uImage.EntryPoint, uImage.Architecture, uImage.OperatingSystem )); AddFingerprint(fileName); UpdateLowestLoadedAddress(uImage.LoadAddress); } public IEnumerable GetLoadedFingerprints() { return binaryFingerprints.ToArray(); } public BinaryFingerprint GetFingerprint(ReadFilePath fileName) { return new BinaryFingerprint(fileName); } public bool TryGetAllSymbolAddresses(string symbolName, out IEnumerable symbolAddresses, ICPU context = null) { var result = GetLookup(context).TryGetSymbolsByName(symbolName, out var symbols); symbolAddresses = symbols.Select(symbol => symbol.Start.RawValue); return result; } public IEnumerable GetAllSymbolAddresses(string symbolName, ICPU context = null) { if(!TryGetAllSymbolAddresses(symbolName, out var symbolAddresses, context)) { throw new RecoverableException(string.Format("No symbol with name `{0}` found.", symbolName)); } return symbolAddresses; } public string FindSymbolAt(ulong offset, ICPU context = null) { if(!TryFindSymbolAt(offset, out var name, out var _, context)) { return null; } return name; } public bool TryFindSymbolAt(ulong offset, out string name, out Symbol symbol, ICPU context = null) { if(!pcCache.TryGetValue(offset, out var entry)) { if(!GetLookup(context).TryGetSymbolByAddress(offset, out symbol)) { symbol = null; name = null; return false; } else { name = symbol.ToStringRelative(offset); } pcCache.Add(offset, Tuple.Create(name, symbol)); } else { name = entry.Item1; symbol = entry.Item2; } return true; } public void MapMemory(IMappedSegment segment, IBusPeripheral owner, bool relative = true, ICPUWithMappedMemory context = null) { if(relative) { var wrappers = new List(); foreach(var registrationPoint in GetRegistrationPoints(owner, context)) { var wrapper = FromRegistrationPointToSegmentWrapper(segment, registrationPoint, context); if(wrapper != null) { wrappers.Add(wrapper); } } AddMappings(wrappers, owner); } else { AddMappings(new [] { new MappedSegmentWrapper(segment, 0, long.MaxValue, context) }, owner); } } public void UnmapMemory(Range range, ICPU context = null) { lock(cpuSync) { foreach(var cpu in GetCPUsForContext(context)) { mappingsRemoved = true; cpu.UnmapMemory(range); } } } public void SetPageAccessViaIo(ulong address) { foreach(var cpu in cpuById.Values.OfType()) { cpu.SetPageAccessViaIo(address); } } public void ClearPageAccessViaIo(ulong address) { foreach(var cpu in cpuById.Values.OfType()) { cpu.ClearPageAccessViaIo(address); } } public void AddWatchpointHook(ulong address, SysbusAccessWidth width, Access access, BusHookDelegate hook) { if(!Enum.IsDefined(typeof(Access), access)) { throw new RecoverableException("Undefined access value."); } if(((((int)width) & 15) != (int)width) || width == 0) { throw new RecoverableException("Undefined width value."); } var handler = new BusHookHandler(hook, width); var dictionariesToUpdate = new List>>(); if((access & Access.Read) != 0) { dictionariesToUpdate.Add(hooksOnRead); } if((access & Access.Write) != 0) { dictionariesToUpdate.Add(hooksOnWrite); } foreach(var dictionary in dictionariesToUpdate) { if(dictionary.ContainsKey(address)) { dictionary[address].Add(handler); } else { dictionary[address] = new List { handler }; } } UpdatePageAccesses(); } public void RemoveWatchpointHook(ulong address, BusHookDelegate hook) { foreach(var hookDictionary in new [] { hooksOnRead, hooksOnWrite }) { List handlers; if(hookDictionary.TryGetValue(address, out handlers)) { handlers.RemoveAll(x => x.ContainsAction(hook)); if(handlers.Count == 0) { hookDictionary.Remove(address); } } } ClearPageAccessViaIo(address); UpdatePageAccesses(); } public void RemoveAllWatchpointHooks(ulong address) { hooksOnRead.Remove(address); hooksOnWrite.Remove(address); ClearPageAccessViaIo(address); UpdatePageAccesses(); } public bool TryGetWatchpointsAt(ulong address, Access access, out List result) { if(access == Access.ReadAndWrite || access == Access.Read) { if(hooksOnRead.TryGetValue(address, out result)) { return true; } else if(access == Access.Read) { result = null; return false; } } return hooksOnWrite.TryGetValue(address, out result); } /// Doesn't include peripherals registered using NullRegistrationPoints. public IEnumerable GetRegistrationPoints(IBusPeripheral peripheral, ICPU context = null) { return GetAccessiblePeripheralsForContext(context) .Where(x => x.Peripheral == peripheral) .Select(x => x.RegistrationPoint); } /// Doesn't include peripherals registered using NullRegistrationPoints. public IEnumerable GetRegistrationPoints(IBusPeripheral peripheral) { // try to detect the CPU context based on the current thread TryGetCurrentCPU(out var context); return GetRegistrationPoints(peripheral, context); } public void ApplySVD(string path) { var svdDevice = new SVDParser(path, this); svdDevices.Add(svdDevice); } public void Tag(Range range, string tag, ulong defaultValue = 0, bool pausing = false) { var intersectings = tags.Where(x => x.Key.Intersects(range)).ToArray(); if(intersectings.Length == 0) { tags.Add(range, new TagEntry { Name = tag, DefaultValue = defaultValue }); if(pausing) { pausingTags.Add(tag); } return; } // tag splitting if(intersectings.Length != 1) { throw new RecoverableException(string.Format( "Currently subtag has to be completely contained in other tag. Given one intersects with tags: {0}", intersectings.Select(x => x.Value.Name).Aggregate((x, y) => x + ", " + y))); } var parentRange = intersectings[0].Key; var parentName = intersectings[0].Value.Name; var parentDefaultValue = intersectings[0].Value.DefaultValue; var parentPausing = pausingTags.Contains(parentName); if(!parentRange.Contains(range)) { throw new RecoverableException(string.Format( "Currently subtag has to be completely contained in other tag, in this case {0}.", parentName)); } RemoveTag(parentRange.StartAddress); var parentRangeAfterSplitSizeLeft = range.StartAddress - parentRange.StartAddress; if(parentRangeAfterSplitSizeLeft > 0) { Tag(new Range(parentRange.StartAddress, parentRangeAfterSplitSizeLeft), parentName, parentDefaultValue, parentPausing); } var parentRangeAfterSplitSizeRight = parentRange.EndAddress - range.EndAddress; if(parentRangeAfterSplitSizeRight > 0) { Tag(new Range(range.EndAddress + 1, parentRangeAfterSplitSizeRight), parentName, parentDefaultValue, parentPausing); } Tag(range, string.Format("{0}/{1}", parentName, tag), defaultValue, pausing); } public void RemoveTag(ulong address) { var tagsToRemove = tags.Where(x => x.Key.Contains(address)).ToArray(); if(tagsToRemove.Length == 0) { throw new RecoverableException(string.Format("There is no tag at address 0x{0:X}.", address)); } foreach(var tag in tagsToRemove) { tags.Remove(tag.Key); pausingTags.Remove(tag.Value.Name); } } public void SetAddressRangeLocked(Range range, bool locked, IPeripheral context = null) { lock(lockedRangesCollectionByContext) { // Check if `range` needs to be locked or unlocked at all. if(locked ? lockedRangesCollectionByContext[context].ContainsWholeRange(range) : !IsAddressRangeLocked(range, context)) { return; } using(Machine.ObtainPausedState(internalPause: true)) { RelockRange(range, locked, context); lockedRangesCollectionByContext.WithStateCollection(context, stateMask: null, collection => { if(locked) { collection.Add(range); } else { collection.Remove(range); } }); } } } public void SetPeripheralEnabled(IPeripheral peripheral, bool enabled) { if(enabled) { lockedPeripherals.Remove(peripheral); } else { if(peripheral != null) { lockedPeripherals.Add(peripheral); } } } /// True if any part of the range is locked for the given CPU (globally if null passed as context). public bool IsAddressRangeLocked(Range range, IPeripheral context = null) { // The locked range is either mapped for the CPU context, or globally (that is the reason for the OR) return (lockedRangesCollectionByContext.TryGetValue(context, initiatorState: null, out var collection) && collection.ContainsOverlappingRange(range)) || lockedRangesCollectionByContext[null].ContainsOverlappingRange(range); } public bool IsPeripheralEnabled(IPeripheral peripheral) { if(lockedPeripherals.Contains(peripheral)) { return false; } return true; } public void Clear() { ClearAll(); } public void Reset() { LowestLoadedAddress = null; globalLookup = new SymbolLookup(); localLookups = new Dictionary(); pcCache.Invalidate(); } public string DecorateWithCPUNameAndPC(string str) { if(!TryGetCurrentCPU(out var cpu) || !Machine.TryGetLocalName(cpu, out var cpuName)) { return str; } // you probably wonder why 26? // * we assume at least 3 characters for cpu name // * 64-bit PC value nees 18 characters // * there are 5 more separating characters var builder = new StringBuilder(str.Length + 26); builder .Append("[") .Append(cpuName); builder.AppendFormat(": 0x{0:X}", cpu.PC.RawValue); builder .Append("] ") .Append(str); return builder.ToString(); } public SymbolLookup GetLookup(ICPU context = null) { if(context == null || !localLookups.TryGetValue(context, out var cpuLookup)) { return globalLookup; } return cpuLookup; } public IMachine Machine { get; } public int UnexpectedReads { get { return Interlocked.CompareExchange(ref unexpectedReads, 0, 0); } } public int UnexpectedWrites { get { return Interlocked.CompareExchange(ref unexpectedWrites, 0, 0); } } public ulong? LowestLoadedAddress { get; private set; } /// /// The returned IEnumerable always enumerates all peripherals registered globally /// but also peripherals registered per CPU are enumerated if called in CPU context. /// public IEnumerable> Children { get { foreach(var peripheral in GetPeripheralsForCurrentCPU()) { yield return peripheral; } } } public bool IsMultiCore { get { return cpuById.Count() > 1; } } public Endianess Endianess { get { return endianess; } set { if(peripheralRegistered) { throw new RecoverableException("Currently one has to set endianess before any peripheral is registered."); } endianess = value; } } public UnhandledAccessBehaviour UnhandledAccessBehaviour { get; set; } public event Action OnSymbolsChanged; private SymbolLookup GetOrCreateLookup(ICPU context) { if(context == null) { return globalLookup; } if(!localLookups.TryGetValue(context, out var lookup)) { lookup = new SymbolLookup(); localLookups[context] = lookup; } return lookup; } private void RemoveLookup(ICPU context = null) { if(context == null) { globalLookup = new SymbolLookup(); } else { localLookups.Remove(context); } } private void UnregisterInner(IBusPeripheral peripheral) { RemoveMappingsForPeripheral(peripheral); // remove the peripheral from all cpu-local and the global mappings foreach(var pair in peripheralsCollectionByContext.GetAllContextKeys() .SelectMany(context => peripheralsCollectionByContext.GetAllStateKeys(context).Select(stateMask => new { context, stateMask }))) { peripheralsCollectionByContext.WithStateCollection(pair.context, stateMask: pair.stateMask, collection => { collection.Remove(peripheral); }); } foreach(var stateMask in peripheralsCollectionByContext.GetAllStateKeys(context: null)) { peripheralsCollectionByContext.WithStateCollection(context: null, stateMask, collection => { collection.Remove(peripheral); }); } RemoveContextKeys(peripheral); } private void UnregisterInner(IBusRegistered busRegistered) { if(mappingsForPeripheral.ContainsKey(busRegistered.Peripheral)) { var toRemove = new HashSet(); // it is assumed that mapped segment cannot be partially outside the registration point range foreach(var mapping in mappingsForPeripheral[busRegistered.Peripheral].Where(x => busRegistered.RegistrationPoint.Range.Contains(x.StartingOffset))) { UnmapMemory(new Range(mapping.StartingOffset, checked((ulong)mapping.Size))); toRemove.Add(mapping); } mappingsForPeripheral[busRegistered.Peripheral].RemoveAll(x => toRemove.Contains(x)); if(mappingsForPeripheral[busRegistered.Peripheral].Count == 0) { mappingsForPeripheral.Remove(busRegistered.Peripheral); } } var perCoreRegistration = busRegistered.RegistrationPoint as IBusRegistration; peripheralsCollectionByContext.WithStateCollection(perCoreRegistration.Initiator, perCoreRegistration.StateMask, collection => { collection.Remove(busRegistered.RegistrationPoint.Range.StartAddress, busRegistered.RegistrationPoint.Range.EndAddress); }); RemoveContextKeys(busRegistered.Peripheral); } // this wrapper is to avoid compiler crashing on Ubuntu 20.04; // for unknown reasons calling `TryGetCurrentCPU` in the `Children` getter // caused the compiler to throw na InternalErrorException/NullReferenceException // when building sources private IEnumerable> GetPeripheralsForCurrentCPU() { TryGetCurrentCPU(out var context); return GetAccessiblePeripheralsForContext(context); } /// /// This method will return all accessible peripherals for the given context. /// This means all peripherals accessible only in the current context, and peripherals accessible globally (from any - `null` - context). /// private IEnumerable> GetAccessiblePeripheralsForContext(IPeripheral context, ulong? initiatorState = null) { var locals = peripheralsCollectionByContext.GetValue(context, initiatorState).Peripherals; return context != null ? locals.Concat(peripheralsCollectionByContext[null].Peripherals) : locals; } private void FillAccessMethodsWithTaggedMethods(IBusPeripheral peripheral, string tag, ref PeripheralAccessMethods methods) { methods.Peripheral = peripheral; methods.Tag = tag; var customAccessMethods = new Dictionary, MethodInfo>(); bool noRegion = true; foreach(var method in peripheral.GetType().GetMethods()) { Type signature = null; if(!Misc.TryGetMatchingSignature(BusAccess.Delegates, method, out signature)) { continue; } var accessGroupAttribute = (ConnectionRegionAttribute)method.GetCustomAttributes(typeof(ConnectionRegionAttribute), true).FirstOrDefault(); if(accessGroupAttribute == null || accessGroupAttribute.Name != tag) { continue; } var accessMethod = BusAccess.GetMethodFromSignature(signature); var accessOperation = BusAccess.GetOperationFromSignature(signature); var tuple = Tuple.Create(accessMethod, accessOperation); if(customAccessMethods.ContainsKey(tuple)) { throw new RegistrationException(string.Format("Only one method for operation {0} accessing {1} registers is allowed.", accessOperation, accessMethod)); } customAccessMethods[tuple] = method; methods.SetMethod(method, peripheral, accessOperation, accessMethod); noRegion = false; } if(noRegion) { throw new RegistrationException($"No region \"{tag}\" is available for {peripheral}."); } foreach(var tuple in customAccessMethods) { var complementingOperation = BusAccess.GetComplementingOperation(tuple.Key.Item2); if(!customAccessMethods.ContainsKey(Tuple.Create(tuple.Key.Item1, complementingOperation))) { throw new RegistrationException($"{complementingOperation}{tuple.Key.Item1} is not specified for {tag}"); } } FillAccessMethodsWithDefaultMethods(peripheral, ref methods); } private void FillAccessMethodsWithDefaultMethods(IBusPeripheral peripheral, ref PeripheralAccessMethods methods) { methods.Peripheral = peripheral; var bytePeripheral = peripheral as IBytePeripheral; var wordPeripheral = peripheral as IWordPeripheral; var dwordPeripheral = peripheral as IDoubleWordPeripheral; var qwordPeripheral = peripheral as IQuadWordPeripheral; BytePeripheralWrapper byteWrapper = null; WordPeripheralWrapper wordWrapper = null; DoubleWordPeripheralWrapper dwordWrapper = null; QuadWordPeripheralWrapper qwordWrapper = null; if(methods.ReadByte != null) { byteWrapper = new BytePeripheralWrapper(methods.ReadByte, methods.WriteByte); } if(methods.ReadWord != null) { // why there are such wrappers? since device can be registered through // method specific registration points wordWrapper = new WordPeripheralWrapper(methods.ReadWord, methods.WriteWord); } if(methods.ReadDoubleWord != null) { dwordWrapper = new DoubleWordPeripheralWrapper(methods.ReadDoubleWord, methods.WriteDoubleWord); } if(methods.ReadQuadWord != null) { qwordWrapper = new QuadWordPeripheralWrapper(methods.ReadQuadWord, methods.WriteQuadWord); } if(bytePeripheral == null && wordPeripheral == null && dwordPeripheral == null && qwordPeripheral == null && byteWrapper == null && wordWrapper == null && dwordWrapper == null && qwordWrapper == null) { throw new RegistrationException(string.Format("Cannot register peripheral {0}, it does not implement any of IBusPeripheral derived interfaces," + "nor any other methods were pointed.", peripheral)); } // We need to pass in Endianess as a default because at this point the peripheral // is not yet associated with a machine. Endianess periEndianess = peripheral.GetEndianness(Endianess); // Note that the condition for applying ReadByteUsingDoubleWordBigEndian et al is different than the condition // for byte-swapping correctly-sized accesses! The former is needed for all big-endian peripherals, the // latter - only in the cross-endianness case. This is to ensure that on a big-endian bus, reading a byte // at offset 0 from a peripheral that only supports double word access and has a single register with value // 0x11223344 correctly returns 0x11, and not 0x44 as it would if ReadByteUsingDoubleWord were used. var translatedAccessNeedsSwap = Endianess == Endianess.BigEndian; var matchingAccessNeedsSwap = periEndianess != Endianess; var allowedTranslations = default(AllowedTranslation); var allowedTranslationsAttributes = peripheral.GetType().GetCustomAttributes(typeof(AllowedTranslationsAttribute), true); if(allowedTranslationsAttributes.Length != 0) { allowedTranslations = ((AllowedTranslationsAttribute)allowedTranslationsAttributes[0]).AllowedTranslations; } // If the CPU endianness does not match the bus endianness, this endianness mismatch // is basically an additional swap. Instead of performing it we reverse the condition. // This is the case on PowerPC, for example: there the translation library is always built // in big-endian mode, and if the CPU is running in little-endian mode, it performs byte // swapping separately. This is to support switching endianness at runtime, but note that // if this is actually done, peripheral accesses will be reversed. if(hasCpuWithMismatchedEndianness) { matchingAccessNeedsSwap = !matchingAccessNeedsSwap; translatedAccessNeedsSwap = !translatedAccessNeedsSwap; // Other translation types will behave incorrectly in this case. allowedTranslations &= AllowedTranslation.ByteToWord | AllowedTranslation.ByteToDoubleWord | AllowedTranslation.ByteToQuadWord | AllowedTranslation.WordToByte | AllowedTranslation.DoubleWordToByte | AllowedTranslation.QuadWordToByte; } // When methods don't have tag, it means they are regular peripheral methods (not regions). // Use peripheral methods for accesses. if(methods.Tag == null) { if(methods.ReadByte == null && bytePeripheral != null) { methods.ReadByte = bytePeripheral.ReadByte; methods.WriteByte = bytePeripheral.WriteByte; } if(methods.ReadWord == null && wordPeripheral != null) { methods.ReadWord = matchingAccessNeedsSwap ? (BusAccess.WordReadMethod)wordPeripheral.ReadWordBigEndian : wordPeripheral.ReadWord; methods.WriteWord = matchingAccessNeedsSwap ? (BusAccess.WordWriteMethod)wordPeripheral.WriteWordBigEndian : wordPeripheral.WriteWord; } if(methods.ReadDoubleWord == null && dwordPeripheral != null) { methods.ReadDoubleWord = matchingAccessNeedsSwap ? (BusAccess.DoubleWordReadMethod)dwordPeripheral.ReadDoubleWordBigEndian : dwordPeripheral.ReadDoubleWord; methods.WriteDoubleWord = matchingAccessNeedsSwap ? (BusAccess.DoubleWordWriteMethod)dwordPeripheral.WriteDoubleWordBigEndian : dwordPeripheral.WriteDoubleWord; } if(methods.ReadQuadWord == null && qwordPeripheral != null) { methods.ReadQuadWord = matchingAccessNeedsSwap ? (BusAccess.QuadWordReadMethod)qwordPeripheral.ReadQuadWordBigEndian : qwordPeripheral.ReadQuadWord; methods.WriteQuadWord = matchingAccessNeedsSwap ? (BusAccess.QuadWordWriteMethod)qwordPeripheral.WriteQuadWordBigEndian : qwordPeripheral.WriteQuadWord; } } if(methods.ReadByte == null) // they are null or not always in pairs { if(qwordWrapper != null && (allowedTranslations & AllowedTranslation.ByteToQuadWord) != 0) { methods.ReadByte = translatedAccessNeedsSwap ? (BusAccess.ByteReadMethod)qwordWrapper.ReadByteUsingQuadWordBigEndian : qwordWrapper.ReadByteUsingQuadWord; methods.WriteByte = translatedAccessNeedsSwap ? (BusAccess.ByteWriteMethod)qwordWrapper.WriteByteUsingQuadWordBigEndian : qwordWrapper.WriteByteUsingQuadWord; } else if(dwordWrapper != null && (allowedTranslations & AllowedTranslation.ByteToDoubleWord) != 0) { methods.ReadByte = translatedAccessNeedsSwap ? (BusAccess.ByteReadMethod)dwordWrapper.ReadByteUsingDoubleWordBigEndian : dwordWrapper.ReadByteUsingDoubleWord; methods.WriteByte = translatedAccessNeedsSwap ? (BusAccess.ByteWriteMethod)dwordWrapper.WriteByteUsingDoubleWordBigEndian : dwordWrapper.WriteByteUsingDoubleWord; } else if(wordWrapper != null && (allowedTranslations & AllowedTranslation.ByteToWord) != 0) { methods.ReadByte = translatedAccessNeedsSwap ? (BusAccess.ByteReadMethod)wordWrapper.ReadByteUsingWordBigEndian : wordWrapper.ReadByteUsingWord; methods.WriteByte = translatedAccessNeedsSwap ? (BusAccess.ByteWriteMethod)wordWrapper.WriteByteUsingWordBigEndian : wordWrapper.WriteByteUsingWord; } else if(qwordPeripheral != null && (allowedTranslations & AllowedTranslation.ByteToQuadWord) != 0) { methods.ReadByte = translatedAccessNeedsSwap ? (BusAccess.ByteReadMethod)qwordPeripheral.ReadByteUsingQuadWordBigEndian : qwordPeripheral.ReadByteUsingQuadWord; methods.WriteByte = translatedAccessNeedsSwap ? (BusAccess.ByteWriteMethod)qwordPeripheral.WriteByteUsingQuadWordBigEndian : qwordPeripheral.WriteByteUsingQuadWord; } else if(dwordPeripheral != null && (allowedTranslations & AllowedTranslation.ByteToDoubleWord) != 0) { methods.ReadByte = translatedAccessNeedsSwap ? (BusAccess.ByteReadMethod)dwordPeripheral.ReadByteUsingDoubleWordBigEndian : dwordPeripheral.ReadByteUsingDoubleWord; methods.WriteByte = translatedAccessNeedsSwap ? (BusAccess.ByteWriteMethod)dwordPeripheral.WriteByteUsingDoubleWordBigEndian : dwordPeripheral.WriteByteUsingDoubleWord; } else if(wordPeripheral != null && (allowedTranslations & AllowedTranslation.ByteToWord) != 0) { methods.ReadByte = translatedAccessNeedsSwap ? (BusAccess.ByteReadMethod)wordPeripheral.ReadByteUsingWordBigEndian : wordPeripheral.ReadByteUsingWord; methods.WriteByte = translatedAccessNeedsSwap ? (BusAccess.ByteWriteMethod)wordPeripheral.WriteByteUsingWordBigEndian : wordPeripheral.WriteByteUsingWord; } else { methods.ReadByte = peripheral.ReadByteNotTranslated; methods.WriteByte = peripheral.WriteByteNotTranslated; } } if(methods.ReadWord == null) { if(qwordWrapper != null && (allowedTranslations & AllowedTranslation.WordToQuadWord) != 0) { methods.ReadWord = translatedAccessNeedsSwap ? (BusAccess.WordReadMethod)qwordWrapper.ReadWordUsingQuadWordBigEndian : qwordWrapper.ReadWordUsingQuadWord; methods.WriteWord = translatedAccessNeedsSwap ? (BusAccess.WordWriteMethod)qwordWrapper.WriteWordUsingQuadWordBigEndian : qwordWrapper.WriteWordUsingQuadWord; } else if(dwordWrapper != null && (allowedTranslations & AllowedTranslation.WordToDoubleWord) != 0) { methods.ReadWord = translatedAccessNeedsSwap ? (BusAccess.WordReadMethod)dwordWrapper.ReadWordUsingDoubleWordBigEndian : dwordWrapper.ReadWordUsingDoubleWord; methods.WriteWord = translatedAccessNeedsSwap ? (BusAccess.WordWriteMethod)dwordWrapper.WriteWordUsingDoubleWordBigEndian : dwordWrapper.WriteWordUsingDoubleWord; } else if(byteWrapper != null && (allowedTranslations & AllowedTranslation.WordToByte) != 0) { methods.ReadWord = translatedAccessNeedsSwap ? (BusAccess.WordReadMethod)byteWrapper.ReadWordUsingByteBigEndian : byteWrapper.ReadWordUsingByte; methods.WriteWord = translatedAccessNeedsSwap ? (BusAccess.WordWriteMethod)byteWrapper.WriteWordUsingByteBigEndian : byteWrapper.WriteWordUsingByte; } else if(qwordPeripheral != null && (allowedTranslations & AllowedTranslation.WordToQuadWord) != 0) { methods.ReadWord = translatedAccessNeedsSwap ? (BusAccess.WordReadMethod)qwordPeripheral.ReadWordUsingQuadWordBigEndian : qwordPeripheral.ReadWordUsingQuadWord; methods.WriteWord = translatedAccessNeedsSwap ? (BusAccess.WordWriteMethod)qwordPeripheral.WriteWordUsingQuadWordBigEndian : qwordPeripheral.WriteWordUsingQuadWord; } else if(dwordPeripheral != null && (allowedTranslations & AllowedTranslation.WordToDoubleWord) != 0) { methods.ReadWord = translatedAccessNeedsSwap ? (BusAccess.WordReadMethod)dwordPeripheral.ReadWordUsingDoubleWordBigEndian : dwordPeripheral.ReadWordUsingDoubleWord; methods.WriteWord = translatedAccessNeedsSwap ? (BusAccess.WordWriteMethod)dwordPeripheral.WriteWordUsingDoubleWordBigEndian : dwordPeripheral.WriteWordUsingDoubleWord; } else if(bytePeripheral != null && (allowedTranslations & AllowedTranslation.WordToByte) != 0) { methods.ReadWord = translatedAccessNeedsSwap ? (BusAccess.WordReadMethod)bytePeripheral.ReadWordUsingByteBigEndian : bytePeripheral.ReadWordUsingByte; methods.WriteWord = translatedAccessNeedsSwap ? (BusAccess.WordWriteMethod)bytePeripheral.WriteWordUsingByteBigEndian : bytePeripheral.WriteWordUsingByte; } else { methods.ReadWord = peripheral.ReadWordNotTranslated; methods.WriteWord = peripheral.WriteWordNotTranslated; } } else if(matchingAccessNeedsSwap && wordWrapper != null) { methods.ReadWord = (BusAccess.WordReadMethod)wordWrapper.ReadWordBigEndian; methods.WriteWord = (BusAccess.WordWriteMethod)wordWrapper.WriteWordBigEndian; } if(methods.ReadDoubleWord == null) { if(qwordWrapper != null && (allowedTranslations & AllowedTranslation.DoubleWordToQuadWord) != 0) { methods.ReadDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordReadMethod)qwordWrapper.ReadDoubleWordUsingQuadWordBigEndian : qwordWrapper.ReadDoubleWordUsingQuadWord; methods.WriteDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordWriteMethod)qwordWrapper.WriteDoubleWordUsingQuadWordBigEndian : qwordWrapper.WriteDoubleWordUsingQuadWord; } else if(wordWrapper != null && (allowedTranslations & AllowedTranslation.DoubleWordToWord) != 0) { methods.ReadDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordReadMethod)wordWrapper.ReadDoubleWordUsingWordBigEndian : wordWrapper.ReadDoubleWordUsingWord; methods.WriteDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordWriteMethod)wordWrapper.WriteDoubleWordUsingWordBigEndian : wordWrapper.WriteDoubleWordUsingWord; } else if(byteWrapper != null && (allowedTranslations & AllowedTranslation.DoubleWordToByte) != 0) { methods.ReadDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordReadMethod)byteWrapper.ReadDoubleWordUsingByteBigEndian : byteWrapper.ReadDoubleWordUsingByte; methods.WriteDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordWriteMethod)byteWrapper.WriteDoubleWordUsingByteBigEndian : byteWrapper.WriteDoubleWordUsingByte; } else if(qwordPeripheral != null && (allowedTranslations & AllowedTranslation.DoubleWordToQuadWord) != 0) { methods.ReadDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordReadMethod)qwordPeripheral.ReadDoubleWordUsingQuadWordBigEndian : qwordPeripheral.ReadDoubleWordUsingQuadWord; methods.WriteDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordWriteMethod)qwordPeripheral.WriteDoubleWordUsingQuadWordBigEndian : qwordPeripheral.WriteDoubleWordUsingQuadWord; } else if(wordPeripheral != null && (allowedTranslations & AllowedTranslation.DoubleWordToWord) != 0) { methods.ReadDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordReadMethod)wordPeripheral.ReadDoubleWordUsingWordBigEndian : wordPeripheral.ReadDoubleWordUsingWord; methods.WriteDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordWriteMethod)wordPeripheral.WriteDoubleWordUsingWordBigEndian : wordPeripheral.WriteDoubleWordUsingWord; } else if(bytePeripheral != null && (allowedTranslations & AllowedTranslation.DoubleWordToByte) != 0) { methods.ReadDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordReadMethod)bytePeripheral.ReadDoubleWordUsingByteBigEndian : bytePeripheral.ReadDoubleWordUsingByte; methods.WriteDoubleWord = translatedAccessNeedsSwap ? (BusAccess.DoubleWordWriteMethod)bytePeripheral.WriteDoubleWordUsingByteBigEndian : bytePeripheral.WriteDoubleWordUsingByte; } else { methods.ReadDoubleWord = peripheral.ReadDoubleWordNotTranslated; methods.WriteDoubleWord = peripheral.WriteDoubleWordNotTranslated; } } else if(matchingAccessNeedsSwap && dwordWrapper != null) { methods.ReadDoubleWord = (BusAccess.DoubleWordReadMethod)dwordWrapper.ReadDoubleWordBigEndian; methods.WriteDoubleWord = (BusAccess.DoubleWordWriteMethod)dwordWrapper.WriteDoubleWordBigEndian; } if(methods.ReadQuadWord == null) { if(dwordWrapper != null && (allowedTranslations & AllowedTranslation.QuadWordToDoubleWord) != 0) { methods.ReadQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordReadMethod)dwordWrapper.ReadQuadWordUsingDoubleWordBigEndian : dwordWrapper.ReadQuadWordUsingDoubleWord; methods.WriteQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordWriteMethod)dwordWrapper.WriteQuadWordUsingDoubleWordBigEndian : dwordWrapper.WriteQuadWordUsingDoubleWord; } else if(wordWrapper != null && (allowedTranslations & AllowedTranslation.QuadWordToWord) != 0) { methods.ReadQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordReadMethod)wordWrapper.ReadQuadWordUsingWordBigEndian : wordWrapper.ReadQuadWordUsingWord; methods.WriteQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordWriteMethod)wordWrapper.WriteQuadWordUsingWordBigEndian : wordWrapper.WriteQuadWordUsingWord; } else if(byteWrapper != null && (allowedTranslations & AllowedTranslation.QuadWordToByte) != 0) { methods.ReadQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordReadMethod)byteWrapper.ReadQuadWordUsingByteBigEndian : byteWrapper.ReadQuadWordUsingByte; methods.WriteQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordWriteMethod)byteWrapper.WriteQuadWordUsingByteBigEndian : byteWrapper.WriteQuadWordUsingByte; } else if(dwordPeripheral != null && (allowedTranslations & AllowedTranslation.QuadWordToDoubleWord) != 0) { methods.ReadQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordReadMethod)dwordPeripheral.ReadQuadWordUsingDoubleWordBigEndian : dwordPeripheral.ReadQuadWordUsingDoubleWord; methods.WriteQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordWriteMethod)dwordPeripheral.WriteQuadWordUsingDoubleWordBigEndian : dwordPeripheral.WriteQuadWordUsingDoubleWord; } else if(wordPeripheral != null && (allowedTranslations & AllowedTranslation.QuadWordToWord) != 0) { methods.ReadQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordReadMethod)wordPeripheral.ReadQuadWordUsingWordBigEndian : wordPeripheral.ReadQuadWordUsingWord; methods.WriteQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordWriteMethod)wordPeripheral.WriteQuadWordUsingWordBigEndian : wordPeripheral.WriteQuadWordUsingWord; } else if(bytePeripheral != null && (allowedTranslations & AllowedTranslation.QuadWordToByte) != 0) { methods.ReadQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordReadMethod)bytePeripheral.ReadQuadWordUsingByteBigEndian : bytePeripheral.ReadQuadWordUsingByte; methods.WriteQuadWord = translatedAccessNeedsSwap ? (BusAccess.QuadWordWriteMethod)bytePeripheral.WriteQuadWordUsingByteBigEndian : bytePeripheral.WriteQuadWordUsingByte; } else { methods.ReadQuadWord = peripheral.ReadQuadWordNotTranslated; methods.WriteQuadWord = peripheral.WriteQuadWordNotTranslated; } } else if(matchingAccessNeedsSwap && qwordWrapper != null) { methods.ReadQuadWord = (BusAccess.QuadWordReadMethod)qwordWrapper.ReadQuadWordBigEndian; methods.WriteQuadWord = (BusAccess.QuadWordWriteMethod)qwordWrapper.WriteQuadWordBigEndian; } } private void UpdateAccessMethods() { foreach(var peripherals in allPeripherals) { peripherals.VisitAccessMethods(null, pam => { if(pam.Tag != null) { FillAccessMethodsWithTaggedMethods(pam.Peripheral, pam.Tag, ref pam); } else { FillAccessMethodsWithDefaultMethods(pam.Peripheral, ref pam); } return pam; }); } } private void RegisterInner(IBusPeripheral peripheral, PeripheralAccessMethods methods, BusRangeRegistration registrationPoint, IPeripheral context) { using(Machine.ObtainPausedState(true)) { // Ensure the context exists if we need one, since this peripheral might be getting registered before its context is. // This only applies for contexts that are not CPUs, since those are always created immediately, but it's harmless to do // for CPUs. if(context != null) { AddContextKeys(context); } var stateMask = registrationPoint.StateMask; var busRegisteredInContext = peripheralsCollectionByContext.GetValue(context, stateMask?.State).Peripherals; var intersecting = busRegisteredInContext .FirstOrDefault(x => x.RegistrationPoint.Range.Intersects(registrationPoint.Range) && x.RegistrationPoint.StateMask?.Mask == registrationPoint.StateMask?.Mask); if(intersecting != null) { throw new RegistrationException($"Given address {registrationPoint.Range} for peripheral {peripheral} conflicts with address {intersecting.RegistrationPoint.Range} of peripheral {intersecting.Peripheral}", "address"); } var registeredPeripheral = new BusRegistered(peripheral, registrationPoint); if(IsAddressRangeLocked(registrationPoint.Range, context)) { // If it's impossible to re-lock the range, the peripheral can't be registered if(!CanRangeBeLocked(registrationPoint.Range, context, out var _, out var __)) { throw new RegistrationException($"Cannot register peripheral {peripheral} on locked range {registrationPoint.Range}"); } } // we also have to put missing methods var absoluteAddressAware = peripheral as IAbsoluteAddressAware; if(absoluteAddressAware != null) { methods.SetAbsoluteAddress = absoluteAddressAware.SetAbsoluteAddress; } peripheralsCollectionByContext.WithStateCollection(context, stateMask, collection => { // Currently the end address is actually "one past the end address". collection.Add(registrationPoint.Range.StartAddress, registrationPoint.Range.EndAddress + 1, registeredPeripheral, methods); }); // let's add new mappings AddMappingsForPeripheral(peripheral, registrationPoint, context); // After adding new mappings, if the address range is locked, the mappings possibly have to be modified/unmapped on the CPU's side // RelockRange to make this happen if(IsAddressRangeLocked(registrationPoint.Range, context)) { // We've checked before if the range can be locked, so this should succeed RelockRange(registrationPoint.Range, true, context); } Machine.RegisterAsAChildOf(this, peripheral, registrationPoint); Machine.RegisterBusController(peripheral, this); } peripheralRegistered = true; } /// /// This method can be used to re-trigger locking/unlocking actions on a range which is already registered as locked. /// This is useful when adding or relocating peripherals to the locked range. /// /// /// Locking should almost always be done by calling /// since this method doesn't update locked ranges and so should be used with caution. /// private void RelockRange(Range range, bool locked, IPeripheral context) { using(Machine.ObtainPausedState(internalPause: true)) { var cpusWithMappedMemory = idByCpu.Keys.OfType(); if(cpusWithMappedMemory.Any()) { if(!CanRangeBeLocked(range, context, out var mappedInRange, out var onlyPartiallyInRange)) { throw new RecoverableException( $"Mapped peripherals registered at the given range {range} have to be fully included:\n" + $"* {string.Join("\n* ", onlyPartiallyInRange)}" ); } foreach(var busRegistered in mappedInRange) { var registrationContext = busRegistered.RegistrationPoint.Initiator; var registrationRange = busRegistered.RegistrationPoint.Range; foreach(var cpu in GetCPUsForContext(registrationContext)) { // There's no need to remove mappings from `mappingsForPeripheral`. // They're only used when a new ICPUWithMappedMemory gets registered // which isn't possible after `mappingsRemoved` is set in `UnmapMemory`. cpu.SetMappedMemoryEnabled(registrationRange, enabled: !locked); } } } } } private bool CanRangeBeLocked(Range range, IPeripheral context, out IEnumerable> mappedInRange, out IEnumerable> onlyPartiallyInRange) { mappedInRange = GetMappedPeripherals(context).Where(x => x.RegistrationPoint.Range.Intersects(range)); // Only allow including whole registration range of IMapped peripherals. onlyPartiallyInRange = mappedInRange.Where(x => !range.Contains(x.RegistrationPoint.Range)); return !onlyPartiallyInRange.Any(); } private IEnumerable FindTargets(ulong address, ulong count, IPeripheral context = null) { var result = new List(); var written = 0UL; while(written < count) { var currentPosition = address + written; // what peripheral is at the current write position? var what = WhatIsAt(currentPosition, context); if(what == null) { var holeStart = currentPosition; // we can omit part of the array // but how much? var nextPeripheral = GetAccessiblePeripheralsForContext(context).OrderBy(x => x.RegistrationPoint.Range.StartAddress).FirstOrDefault(x => x.RegistrationPoint.Range.StartAddress > currentPosition); if(nextPeripheral == null) { // hole reaches the end of the required range written = count; } else { written += Math.Min(nextPeripheral.RegistrationPoint.Range.StartAddress - currentPosition, count - written); } var holeSize = address + written - currentPosition; this.Log(LogLevel.Warning, "Tried to access bytes at non-existing peripheral in range {0}.", new Range(holeStart, holeSize)); continue; } var toWrite = Math.Min(count - written, what.RegistrationPoint.Range.EndAddress - currentPosition + 1); var singleResult = new PeripheralLookupResult(); singleResult.What = what; singleResult.SourceIndex = written; singleResult.SourceLength = toWrite; singleResult.Offset = currentPosition; written += toWrite; result.Add(singleResult); } return result; } private static void ThrowIfNotAllMemory(IEnumerable targets) { foreach(var target in targets) { var iMemory = target.What.Peripheral as IMemory; var redirector = target.What.Peripheral as Redirector; if(iMemory == null && redirector == null) { throw new RecoverableException(String.Format("Tried to access {0} but only memory accesses were allowed.", target.What.Peripheral)); } } } private void UpdatePageAccesses() { foreach(var address in hooksOnRead.Select(x => x.Key).Union(hooksOnWrite.Select(x => x.Key))) { SetPageAccessViaIo(address); } } private void ClearAll() { lock(cpuSync) { foreach(var group in Machine.PeripheralsGroups.ActiveGroups) { group.Unregister(); } foreach(var p in allPeripherals.SelectMany(x => x.Peripherals).Select(x => x.Peripheral).Distinct().Union(GetCPUs().Cast()).ToList()) { Machine.UnregisterFromParent(p); } mappingsRemoved = false; InitStructures(); } } private void UpdateLowestLoadedAddress(ulong lowestLoadedAddress) { if(!LowestLoadedAddress.HasValue) { LowestLoadedAddress = lowestLoadedAddress; return; } LowestLoadedAddress = Math.Min(LowestLoadedAddress.Value, lowestLoadedAddress); } private void AddFingerprint(string fileName) { binaryFingerprints.Add(new BinaryFingerprint(fileName)); } private void InitStructures() { cpuById.Clear(); idByCpu.Clear(); hooksOnRead.Clear(); hooksOnWrite.Clear(); pcCache.Invalidate(); globalLookup = new SymbolLookup(); localLookups = new Dictionary(); cachedCpuId = new ThreadLocal(); peripheralsCollectionByContext = new ContextKeyDictionary(() => new PeripheralCollection(this)); lockedPeripherals = new HashSet(); lockedRangesCollectionByContext = new ContextKeyDictionary(() => new MinimalRangesCollection()); mappingsForPeripheral = new Dictionary>(); tags = new Dictionary(); svdDevices = new List(); pausingTags = new HashSet(); PostDeserializationInitStructures(); } private List ObtainMemoryList() { return allPeripherals.SelectMany(x => x.Peripherals).Where(x => x.Peripheral is MappedMemory).OrderBy(x => x.RegistrationPoint.Range.StartAddress). Select(x => x.Peripheral).Cast().Distinct().ToList(); } private void AddMappings(IEnumerable newMappings, IBusPeripheral owner) { using(Machine.ObtainPausedState(true)) { lock(cpuSync) { var mappingsList = newMappings.ToList(); if(mappingsForPeripheral.ContainsKey(owner)) { mappingsForPeripheral[owner].AddRange(newMappings); } else { mappingsForPeripheral[owner] = mappingsList; } // Old mappings are given to the CPU in the moment of its registration. foreach(var mapping in mappingsList) { foreach(var cpu in GetCPUsForContext(mapping.Context)) { cpu.MapMemory(mapping); } } } } } private void AddMappingsForPeripheral(IBusPeripheral peripheral, BusRangeRegistration registrationPoint, IPeripheral context) { var mappedPeripheral = peripheral as IMapped; if(mappedPeripheral == null) // The context can be null to map it globally { return; } var cpuWithMappedMemory = context as ICPUWithMappedMemory; var segments = mappedPeripheral.MappedSegments; var mappings = segments.Select(x => FromRegistrationPointToSegmentWrapper(x, registrationPoint, cpuWithMappedMemory)).Where(x => x != null); AddMappings(mappings, mappedPeripheral); } private void UnregisterAccessFlags(BusRangeRegistration registrationPoint, ICPU context) { foreach(var cpu in GetCPUsForContext(context)) { var range = registrationPoint.Range; cpu.RegisterAccessFlags(range.StartAddress, range.Size); } } private bool RemoveMappingsForPeripheral(IBusPeripheral peripheral) { if(!mappingsForPeripheral.ContainsKey(peripheral)) { return false; } foreach(var mapping in mappingsForPeripheral[peripheral]) { UnmapMemory(new Range(mapping.StartingOffset, mapping.Size)); } mappingsForPeripheral.Remove(peripheral); return true; } private string TryGetTag(ulong address, out ulong defaultValue) { // The `return` inside is intentional; we just want to find the first tag. // `FirstOrDefault` isn't used cause the default `Range` is a valid `<0, 0>` range. // `Any` + `First` aren't used to avoid analyzing `tags` keys up to the matching one twice. // `ToArray` isn't used to avoid analyzing all `tags` keys since we only use the first one. foreach(var tag in tags.Where(x => x.Key.Contains(address)).Select(x => x.Value)) { defaultValue = tag.DefaultValue; return tag.Name; } defaultValue = default(ulong); return null; } private string EnterTag(string str, ulong address, out bool tagEntered, out ulong defaultValue) { // TODO: also pausing here in a bit hacky way var tag = TryGetTag(address, out defaultValue); if(tag == null) { tagEntered = false; return str; } tagEntered = true; if(pausingTags.Contains(tag)) { Machine.Pause(); } return string.Format("(tag: '{0}') {1}", tag, str); } private ulong ReportNonExistingRead(ulong address, SysbusAccessWidth type) { Interlocked.Increment(ref unexpectedReads); bool tagged; ulong defaultValue; var warning = EnterTag(NonExistingRead, address, out tagged, out defaultValue); warning = DecorateWithCPUNameAndPC(warning); if(UnhandledAccessBehaviour == UnhandledAccessBehaviour.DoNotReport) { return defaultValue; } if((UnhandledAccessBehaviour == UnhandledAccessBehaviour.ReportIfTagged && !tagged) || (UnhandledAccessBehaviour == UnhandledAccessBehaviour.ReportIfNotTagged && tagged)) { return defaultValue; } if(tagged) { //strange parsing of default value ensures we get the right width, depending on the access type this.Log(LogLevel.Warning, warning.TrimEnd('.') + ", returning 0x{2}.", address, type, defaultValue.ToString("X16").Substring(16 - (int)type * 2)); } else { ulong value; foreach(var svdDevice in svdDevices) { if(svdDevice.TryReadAccess(address, out value, type)) { return value; } } this.Log(LogLevel.Warning, warning, address, type); } return defaultValue; } private void ReportNonExistingWrite(ulong address, ulong value, SysbusAccessWidth type) { Interlocked.Increment(ref unexpectedWrites); if(UnhandledAccessBehaviour == UnhandledAccessBehaviour.DoNotReport) { return; } bool tagged; var warning = EnterTag(NonExistingWrite, address, out tagged, out var _); warning = DecorateWithCPUNameAndPC(warning); if((UnhandledAccessBehaviour == UnhandledAccessBehaviour.ReportIfTagged && !tagged) || (UnhandledAccessBehaviour == UnhandledAccessBehaviour.ReportIfNotTagged && tagged)) { return; } foreach(var svdDevice in svdDevices) { if(svdDevice.TryWriteAccess(address, value, type)) { return; } } this.Log(LogLevel.Warning, warning, address, value, type); } private IDisposable SetLocalContext(IPeripheral context, ulong? initiatorState = null) { return threadLocalContext.Initialize(context, initiatorState); } private void AddContextKeys(IPeripheral peripheral) { peripheralsCollectionByContext.AddContextKey(peripheral); lockedRangesCollectionByContext.AddContextKey(peripheral); } private void RemoveContextKeys(IPeripheral peripheral) { peripheralsCollectionByContext.RemoveContextKey(peripheral); lockedRangesCollectionByContext.RemoveContextKey(peripheral); } [PostDeserialization] private void PostDeserializationInitStructures() { threadLocalContext = new ThreadLocalContext(this); } private static MappedSegmentWrapper FromRegistrationPointToSegmentWrapper(IMappedSegment segment, BusRangeRegistration registrationPoint, ICPUWithMappedMemory context) { if(segment.StartingOffset >= registrationPoint.Range.Size + registrationPoint.Offset) { return null; } var desiredSize = Math.Min(segment.Size, registrationPoint.Range.Size + registrationPoint.Offset - segment.StartingOffset); return new MappedSegmentWrapper(segment, registrationPoint.Range.StartAddress - registrationPoint.Offset, desiredSize, context); } private IEnumerable allPeripherals => peripheralsCollectionByContext.GetAllDistinctValues(); private ISet lockedPeripherals; private ContextKeyDictionary peripheralsCollectionByContext; private ContextKeyDictionary lockedRangesCollectionByContext; // It doesn't implement IDictionary because serialization doesn't work correctly for dictionaries without two generic parameters. private class ContextKeyDictionary where TValue : TReadOnlyValue, ICoalescable where TReadOnlyValue : class { public ContextKeyDictionary(Func defaultFactory) { this.defaultFactory = defaultFactory; globalAllAccess = globalValue[StateMask.AllAccess] = defaultFactory(); } // Adding the context key might happen on peripheral registration, or on first use. public void AddContextKey(IPeripheral key) { if(key == null) { throw new ArgumentNullException(nameof(key)); } if(cpuLocalValues.ContainsKey(key)) { return; } cpuLocalValues[key] = new Dictionary(); cpuAllAccess[key] = cpuLocalValues[key][StateMask.AllAccess] = defaultFactory(); } public void RemoveContextKey(IPeripheral key) { if(key == null) { throw new ArgumentNullException(nameof(key)); } cpuLocalValues.Remove(key); DropCaches(); // has the effect of dropping caches when a peripheral is unregistered } public TReadOnlyValue this[IPeripheral key] => GetValue(key); public TReadOnlyValue GetValue(IPeripheral key, ulong? initiatorState = null) { if(!TryGetValue(key, initiatorState, out var value)) { throw new RecoverableException($"{typeof(TValue).Name} not found for the given context: {key.GetName()} in state: {initiatorState}"); } return value; } // Perform a mutation on the value collection for a specific initiator state and mask pair (for example to add a new peripheral) public void WithStateCollection(IPeripheral context, StateMask? stateMask, Action action) { var collection = context == null ? globalValue : cpuLocalValues[context]; var effectiveMask = stateMask ?? StateMask.AllAccess; if(!collection.ContainsKey(effectiveMask)) { collection[effectiveMask] = defaultFactory(); } action(collection[effectiveMask]); DropCaches(); } public IEnumerable GetAllDistinctValues() { return cpuLocalValues.Values.SelectMany(v => v.Values).Concat(globalValue.Values).Distinct(); } public IEnumerable GetAllContextKeys() { return cpuLocalValues.Keys; } public IEnumerable GetAllStateKeys(IPeripheral context) { return (context == null ? globalValue : cpuLocalValues[context]).Keys; } public bool TryGetValue(IPeripheral key, ulong? initiatorState, out TValue value) { if(!initiatorState.HasValue) { if(key == null) { value = globalAllAccess; return true; } // Handle reads initiated by peripherals that are never used as a context and thus have no collections. // Then this will fail and it's up to the caller to fall back to the global collection. return cpuAllAccess.TryGetValue(key, out value); } if(key != null && !cpuInStateCache.ContainsKey(key)) { cpuInStateCache[key] = new Dictionary(); } var cache = key == null ? globalCache : cpuInStateCache[key]; if(cache.TryGetValue(initiatorState.Value, out var cachedValue)) { value = cachedValue; return true; } else { cache[initiatorState.Value] = cachedValue = defaultFactory(); return TryGetValueForState(cachedValue, key, initiatorState, out value); } } private bool TryGetValueForState(TValue cachedValue, IPeripheral key, ulong? initiatorState, out TValue value) { // The core of the state filtering logic. Look up which elements fit the current initiator state. // Then join all found state-specific collections of elements into the cached one for this state. var collectionToSearch = globalValue.AsEnumerable(); if(key != null && cpuLocalValues.TryGetValue(key, out var thisCpuValues)) { // Note that in order for the 'local peripheral overrides the global one' behavior to work as intended, // we must check the local peripherals first. That way they 'reserve' their spot in the cache. This function // also checks the global collection when called with a context argument, but it does it only after going // through the context-specific one to ensure this. collectionToSearch = thisCpuValues.Concat(collectionToSearch); } bool anyHit = false; foreach(var pair in collectionToSearch) { var stateMask = pair.Key; if((initiatorState.Value & stateMask.Mask) == stateMask.State) { anyHit = true; /// doesn't add overlapping peripherals. This means that for the 'local peripheral /// overrides the global one' behavior to work as intended we must check the local peripherals first, as we did above. cachedValue.Coalesce(pair.Value); } } value = anyHit ? cachedValue : default(TValue); return anyHit; } private void DropCaches() { cpuInStateCache.Clear(); globalCache.Clear(); } private readonly Dictionary> cpuLocalValues = new Dictionary>(); private readonly Dictionary> cpuInStateCache = new Dictionary>(); private readonly Func defaultFactory; private readonly Dictionary globalValue = new Dictionary(); private readonly Dictionary globalCache = new Dictionary(); // Please take performance into account before removing these caches (they were added because looking up a value // in a Dictionary was very slow) private readonly Dictionary cpuAllAccess = new Dictionary(); private readonly TValue globalAllAccess; } private Dictionary> mappingsForPeripheral; private bool mappingsRemoved; private bool peripheralRegistered; private Endianess endianess; private bool hasCpuWithMismatchedEndianness; private readonly Dictionary idByCpu; private readonly Dictionary cpuById; private readonly Dictionary> hooksOnRead; private readonly Dictionary> hooksOnWrite; [Constructor] private ThreadLocal cachedCpuId; [Transient] private ThreadLocalContext threadLocalContext; private object cpuSync; private SymbolLookup globalLookup; private Dictionary localLookups; private Dictionary tags; private List svdDevices; private HashSet pausingTags; private readonly List binaryFingerprints; private const string NonExistingRead = "Read{1} from non existing peripheral at 0x{0:X}."; private const string NonExistingWrite = "Write{2} to non existing peripheral at 0x{0:X}, value 0x{1:X}."; private const string IOExceptionMessage = "I/O error while loading ELF: {0}."; private const string CantFindCpuIdMessage = "Can't verify current CPU in the given context."; private int unexpectedReads; private int unexpectedWrites; private LRUCache> pcCache = new LRUCache>(10000); public class MappedSegmentWrapper : IMappedSegment { public MappedSegmentWrapper(IMappedSegment wrappedSegment, ulong peripheralOffset, ulong maximumSize, ICPUWithMappedMemory context) { this.wrappedSegment = wrappedSegment; this.peripheralOffset = peripheralOffset; usedSize = Math.Min(maximumSize, wrappedSegment.Size); this.context = context; } public void Touch() { wrappedSegment.Touch(); } public override string ToString() { return string.Format("[MappedSegmentWrapper: StartingOffset=0x{0:X}, Size=0x{1:X}, OriginalStartingOffset=0x{2:X}, PeripheralOffset=0x{3:X}, Context={4}]", StartingOffset, Size, OriginalStartingOffset, PeripheralOffset, context); } public ICPUWithMappedMemory Context { get { return context; } } public ulong StartingOffset { get { return peripheralOffset + wrappedSegment.StartingOffset; } } public ulong Size { get { return usedSize; } } public IntPtr Pointer { get { return wrappedSegment.Pointer; } } public ulong OriginalStartingOffset { get { return wrappedSegment.StartingOffset; } } public ulong PeripheralOffset { get { return peripheralOffset; } } public override bool Equals(object obj) { var objAsMappedSegmentWrapper = obj as MappedSegmentWrapper; if(objAsMappedSegmentWrapper == null) { return false; } return wrappedSegment.Equals(objAsMappedSegmentWrapper.wrappedSegment) && peripheralOffset == objAsMappedSegmentWrapper.peripheralOffset && usedSize == objAsMappedSegmentWrapper.usedSize; } public override int GetHashCode() { unchecked { var hash = 17; hash = hash * 23 + wrappedSegment.GetHashCode(); hash = hash * 23 + (int)peripheralOffset; hash = hash * 23 + (int)usedSize; return hash; } } private readonly IMappedSegment wrappedSegment; private readonly ulong peripheralOffset; private readonly ulong usedSize; private readonly ICPUWithMappedMemory context; } private struct PeripheralLookupResult { public IBusRegistered What; public ulong Offset; public ulong SourceIndex; public ulong SourceLength; } private struct TagEntry { public string Name; public ulong DefaultValue; } private class ThreadLocalContext : IDisposable { public ThreadLocalContext(IBusController parent) { context = new ThreadLocal>(() => null, true); emptyDisposable.Disable(); } public IDisposable Initialize(IPeripheral cpu, ulong? initiatorState) { if(cpu == null) { return emptyDisposable; } var previousContext = context.Value; context.Value = Tuple.Create(cpu, initiatorState); return DisposableWrapper.New(() => { context.Value = previousContext; }); } public void Dispose() { context.Dispose(); } public bool InUse => context.Value != null; public IPeripheral Initiator => context.Value.Item1; public ulong? InitiatorState => context.Value.Item2; private readonly ThreadLocal> context; private static readonly DisposableWrapper emptyDisposable = new DisposableWrapper(); } } }