// // Copyright (c) 2010-2024 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.Collections.Generic; using Antmicro.Renode.Peripherals; using Antmicro.Renode.Exceptions; using Antmicro.Renode.Logging; using Antmicro.Renode.Utilities; using System; namespace Antmicro.Renode.Core.Structure.Registers { public sealed class QuadWordRegisterCollection : BaseRegisterCollection, IRegisterCollection { public QuadWordRegisterCollection(IPeripheral parent, IDictionary registersMap = null) : base(parent, registersMap) { } } public sealed class DoubleWordRegisterCollection : BaseRegisterCollection, IRegisterCollection { public DoubleWordRegisterCollection(IPeripheral parent, IDictionary registersMap = null) : base(parent, registersMap) { } } public sealed class WordRegisterCollection : BaseRegisterCollection, IRegisterCollection { public WordRegisterCollection(IPeripheral parent, IDictionary registersMap = null) : base(parent, registersMap) { } } public sealed class ByteRegisterCollection : BaseRegisterCollection, IRegisterCollection { public ByteRegisterCollection(IPeripheral parent, IDictionary registersMap = null) : base(parent, registersMap) { } } public static class RegisterCollectionHookExtensions { public static void AddBeforeReadHook(this IProvidesRegisterCollection @this, long offset, Func func) where T: struct where R: IRegisterCollection { if(!(@this.RegistersCollection is IRegisterCollection registerCollection)) { throw new ArgumentException($"{@this} is not valid argument", "this"); } registerCollection.AddBeforeReadHook(offset, func); } public static void AddAfterReadHook(this IProvidesRegisterCollection @this, long offset, Func func) where T: struct where R: IRegisterCollection { if(!(@this.RegistersCollection is IRegisterCollection registerCollection)) { throw new ArgumentException($"{@this} is not valid argument", "this"); } registerCollection.AddAfterReadHook(offset, func); } public static void AddBeforeWriteHook(this IProvidesRegisterCollection @this, long offset, Func func) where T: struct where R: IRegisterCollection { if(!(@this.RegistersCollection is IRegisterCollection registerCollection)) { throw new ArgumentException($"{@this} is not valid argument", "this"); } registerCollection.AddBeforeWriteHook(offset, func); } public static void AddAfterWriteHook(this IProvidesRegisterCollection @this, long offset, Action func) where T: struct where R: IRegisterCollection { if(!(@this.RegistersCollection is IRegisterCollection registerCollection)) { throw new ArgumentException($"{@this} is not valid argument", "this"); } registerCollection.AddAfterWriteHook(offset, func); } public static void RemoveBeforeReadHook(this IProvidesRegisterCollection @this, long offset) where R: IRegisterCollection { @this.RegistersCollection.RemoveBeforeReadHook(offset); } public static void RemoveAfterReadHook(this IProvidesRegisterCollection @this, long offset) where R: IRegisterCollection { @this.RegistersCollection.RemoveAfterReadHook(offset); } public static void RemoveBeforeWriteHook(this IProvidesRegisterCollection @this, long offset) where R: IRegisterCollection { @this.RegistersCollection.RemoveBeforeWriteHook(offset); } public static void RemoveAfterWriteHook(this IProvidesRegisterCollection @this, long offset) where R: IRegisterCollection { @this.RegistersCollection.RemoveAfterWriteHook(offset); } } public abstract class BaseRegisterCollection : IRegisterCollection where R: PeripheralRegister, IPeripheralRegister where T: struct { /// /// Initializes a new instance of the class. /// /// Parent peripheral (for logging purposes). /// Map of register offsets and registers. public BaseRegisterCollection(IPeripheral parent, IDictionary registersMap = null) { this.parent = parent; this.beforeReadHooks = new Dictionary>(); this.afterReadHooks = new Dictionary>(); this.beforeWriteHooks = new Dictionary>(); this.afterWriteHooks = new Dictionary>(); this.registers = new Dictionary>(); if(registersMap != null) { foreach(KeyValuePair pair in registersMap) { AddRegisterInner(pair.Key, pair.Value); } } } /// /// Returns the value of a register in a specified offset. If no such register is found, a logger message is issued. /// /// Register offset. public T Read(long offset) { T result; if(TryRead(offset, out result)) { return result; } parent.LogUnhandledRead(offset); return default(T); } /// /// Tries to read from a register in a specified offset. /// /// true, if register was found, false otherwise. /// Register offset. /// Read value. public bool TryRead(long offset, out T result) { if(beforeReadHooks.TryGetValue(offset, out var beforeReadHook)) { var hookOutput = beforeReadHook(offset); if(hookOutput != null) { result = (T)hookOutput; return true; } } T? output = null; if(registers.TryGetValue(offset, out var register)) { output = register.Read(); } if(afterReadHooks.TryGetValue(offset, out var afterReadHook)) { var hookOutput = afterReadHook(offset, output ?? default(T)); if(hookOutput != null) { output = hookOutput; } } result = output ?? default(T); return output != null; } /// /// Writes to a register in a specified offset. If no such register is found, a logger message is issued. /// /// Register offset. /// Value to write. public void Write(long offset, T value) { if(!TryWrite(offset, value)) { parent.LogUnhandledWrite(offset, Misc.CastToULong(value)); } } /// /// Tries to write to a register in a specified offset. /// /// true, if register was found, false otherwise. /// Register offset. /// Value to write. public bool TryWrite(long offset, T value) { if(registers.TryGetValue(offset, out var register)) { if(beforeWriteHooks.TryGetValue(offset, out var beforeWriteHook)) { var hookOutput = beforeWriteHook(offset, value); value = hookOutput ?? value; } register.Write(offset, value); if(afterWriteHooks.TryGetValue(offset, out var afterWriteHook)) { afterWriteHook(offset, value); } return true; } return false; } /// /// Check if collection has register defined at given offset. /// /// Register offset. public bool HasRegisterAtOffset(long offset) { return registers.TryGetValue(offset, out var selector) && selector.HasRegister(); } /// /// Resets all registers in this collection. /// public void Reset() { foreach(var register in registers.Values) { register.Reset(); } } /// /// Adds hook which will be executed before any value has been read from the register. /// First argument of the callback is the same as the provided offset. /// Return value can be a number, in which case the underlying read to register will be bypassed, /// or null to continue normal execution. /// /// Register offset. /// Callback which will be run. public void AddBeforeReadHook(long offset, Func hook) { if(beforeReadHooks.ContainsKey(offset)) { throw new RecoverableException($"Before-read hook for 0x{offset:X} is already registered"); } beforeReadHooks.Add(offset, hook); } /// /// Adds hook which will be executed after the value has been read from the register. /// First argument of the callback is the same as the provided offset. /// Second argument of the callback is the read value. /// Return value can be a number, in which case the read value will be overridden, /// or null to continue normal execution. /// /// Register offset. /// Callback which will be run. public void AddAfterReadHook(long offset, Func hook) { if(afterReadHooks.ContainsKey(offset)) { throw new RecoverableException($"After-read hook for 0x{offset:X} is already registered"); } afterReadHooks.Add(offset, hook); } /// /// Adds hook which will be executed before any value has been written to the register. /// First argument of the callback is the same as the provided offset. /// Second argument of the callback is a value that is going to be written to the register. /// Return value can be a number, in which case the value will be overridden, /// or null to continue normal execution. /// /// Register offset. /// Callback which will be run. public void AddBeforeWriteHook(long offset, Func hook) { if(beforeWriteHooks.ContainsKey(offset)) { throw new RecoverableException($"Before-write hook for 0x{offset:X} is already registered"); } beforeWriteHooks.Add(offset, hook); } /// /// Adds hook which will be executed before any value has been written to the register. /// First argument of the callback is the same as the provided offset. /// Second argument of the callback is the value that has been written to the register. /// /// Register offset. /// Callback which will be run. public void AddAfterWriteHook(long offset, Action hook) { if(afterWriteHooks.ContainsKey(offset)) { throw new RecoverableException($"After-write hook for 0x{offset:X} is already registered"); } afterWriteHooks.Add(offset, hook); } /// /// Removes before-read hook at given offset. /// /// Register offset. public void RemoveBeforeReadHook(long offset) { if(!beforeReadHooks.ContainsKey(offset)) { throw new RecoverableException("Before-read hook for 0x{0:X} doesn't exist"); } beforeReadHooks.Remove(offset); } /// /// Removes after-read hook at given offset. /// /// Register offset. public void RemoveAfterReadHook(long offset) { if(!afterReadHooks.ContainsKey(offset)) { throw new RecoverableException("After-read hook for 0x{0:X} doesn't exist"); } afterReadHooks.Remove(offset); } /// /// Removes before-write hook at given offset. /// /// Register offset. public void RemoveBeforeWriteHook(long offset) { if(!beforeWriteHooks.ContainsKey(offset)) { throw new RecoverableException("Before-write hook for 0x{0:X} doesn't exist"); } beforeWriteHooks.Remove(offset); } /// /// Removes after-write hook at given offset. /// /// Register offset. public void RemoveAfterWriteHook(long offset) { if(!afterWriteHooks.ContainsKey(offset)) { throw new RecoverableException("After-write hook for 0x{0:X} doesn't exist"); } afterWriteHooks.Remove(offset); } /// /// Defines a new register and adds it to the collection. /// /// Register offset. /// Register reset value. /// Indicates if the register is cleared on soft reset. /// Newly added register. public R DefineRegister(long offset, T resetValue = default(T), bool softResettable = true) { return DefineConditionalRegister(offset, null, resetValue, softResettable); } /// /// Defines a new conditional register and adds it to the collection. /// /// Register offset. /// Register reset value. /// Condition based on which a register is selected. /// Indicates if the register is cleared on soft reset. /// Newly added register. public R DefineConditionalRegister(long offset, Func condition, T resetValue = default(T), bool softResettable = true) { var constructor = typeof(R).GetConstructor(new Type[] { typeof(IPeripheral), typeof(ulong), typeof(bool) }); var reg = (R)constructor.Invoke(new object[] { parent, Misc.CastToULong(resetValue), softResettable }); AddRegisterInner(offset, reg, condition); return reg; } /// /// Adds an existing register to the collection. /// /// Register offset. /// Register to add. /// Added register (the same passed in argument). public R AddRegister(long offset, R register) { AddRegisterInner(offset, register); return register; } /// /// Adds an existing register with a condition to the collection. /// /// Register offset. /// Register to add. /// Condition based on which a register is selected. /// Added register (the same passed in argument). public R AddConditionalRegister(long offset, R register, Func condition) { AddRegisterInner(offset, register, condition); return register; } /// /// Adds a register and condition to a new or existing selector. /// /// Register offset. /// Rgister to add. /// Condition based on which a register is selected. private void AddRegisterInner(long offset, R register, Func condition = null) { if(!registers.ContainsKey(offset)) { registers.Add(offset, new RegisterSelector()); } try { registers[offset].AddRegister(register, condition); } catch(Exception e) { throw new ConstructionException($"At offset 0x{offset:x}: {e.Message}", e); } } private readonly IPeripheral parent; private readonly IDictionary> registers; private readonly IDictionary> beforeReadHooks; private readonly IDictionary> afterReadHooks; private readonly IDictionary> beforeWriteHooks; private readonly IDictionary> afterWriteHooks; } public interface IRegisterCollection { void Reset(); void RemoveBeforeReadHook(long offset); void RemoveAfterReadHook(long offset); void RemoveBeforeWriteHook(long offset); void RemoveAfterWriteHook(long offset); } public interface IRegisterCollection : IRegisterCollection where T: struct { void AddBeforeReadHook(long offset, Func hook); void AddAfterReadHook(long offset, Func hook); void AddBeforeWriteHook(long offset, Func hook); void AddAfterWriteHook(long offset, Action hook); } public interface IProvidesRegisterCollection where T : IRegisterCollection { T RegistersCollection { get; } } }