| // | |
| // Copyright (c) 2021 Google LLC | |
| // | |
| // This file is licensed under the MIT License. | |
| // Full license text is available in 'licenses/MIT.txt'. | |
| // | |
| using System; | |
| using Antmicro.Renode.Core; | |
| using System.Linq; | |
| using Antmicro.Renode.Exceptions; | |
| using Antmicro.Renode.Peripherals.Bus; | |
| using Antmicro.Renode.Peripherals.Memory; | |
| using Antmicro.Renode.Logging; | |
| namespace Antmicro.Renode.Peripherals.CPU | |
| { | |
| /* | |
| * The WindowMMU performs minimal memory address translation and protection | |
| * by providing one or more windows of software configurable length and offset | |
| * into another address space. The configuration of the WindowMMU is intended | |
| * to be handled by a secure operating context, and in the case of Matcha via | |
| * the System Management Core through Springbok's configuration register file. | |
| * | |
| * The WindowMMU has four implementation defined configuration parameters: | |
| * o Number of windows | |
| * This defines how many address translation windows the WindowMMU has. | |
| * It can be any number, but to keep things small in Matcha we have 2 for | |
| * the Vector Core's instruction fetch WindowMMU and 4 for the data | |
| * WindowMMU. | |
| * o Window size | |
| * This defines the input virtual address aperture for each window. | |
| * The total virtual address aperture for the WindowMMU is the first power | |
| * of two greater or equal to Window Size times Number of Windows (in | |
| * other words, log2ceil(WINDOW_SIZE * NUMBER_OF_WINDOWS)). This must be a | |
| * power-of-two multiple of page size. In Matcha this is 4 MiB. | |
| * o TCM size | |
| * This defines the output physical address aperture for the whole | |
| * WindowMMU. This does not need to be the same as window size, though in | |
| * Matcha it is. | |
| * o Page size | |
| * This defines the granularity of window offsets and lengths. It must be | |
| * a power-of-two multiple of the largest single access size. In Matcha | |
| * to simplify hardware design and match this up with the SMC's MMU's page | |
| * size it is set to 4 KiB. | |
| * | |
| * Pseudo-code describing a translation: | |
| * | |
| * uint window_length[number_of_windows]; | |
| * uint window_offset[number_of_windows]; | |
| * bool window_writable[number_of_windows]; | |
| * bool window_readable[number_of_windows]; | |
| * | |
| * uint getTranslation(uint addr, bool write) { | |
| * uint window = addr / window_size; | |
| * uint local_addr = addr % window_size; | |
| * bool permitted = write? window_writable[window] : | |
| * window_readable[window]; | |
| * | |
| * if (permitted && local_addr < (window_length[window] + page_size)) | |
| * return local_addr + window_offset[window]; | |
| * else | |
| * throw access_fault; | |
| * } | |
| */ | |
| public class SpringbokRiscV32_WindowMMU : IBytePeripheral, IWordPeripheral, IDoubleWordPeripheral, IMultibyteWritePeripheral, IKnownSize | |
| { | |
| private uint GetNextPowerOfTwo(uint input) | |
| { | |
| uint retval = input - 1; | |
| for (int i = 1; i <= 16; i <<= 1) | |
| { | |
| retval |= retval >> i; | |
| } | |
| return retval + 1; | |
| } | |
| private uint GetLog2Ceil(uint input) | |
| { | |
| uint retval = 0; | |
| uint v = GetNextPowerOfTwo(input); | |
| while (v > 1) | |
| { | |
| retval++; | |
| v >>= 1; | |
| } | |
| return retval; | |
| } | |
| public SpringbokRiscV32_WindowMMU(Machine machine, | |
| MappedMemory tcm, | |
| long windowSize, | |
| long tcmSize, | |
| long pageSize, | |
| long numWindows) | |
| { | |
| this.tcm = tcm; | |
| this.windowSize = (uint)windowSize; | |
| this.tcmSize = (uint)tcmSize; | |
| this.pageSize = (uint)pageSize; | |
| this.numWindows = (uint)numWindows; | |
| this.faultCallback = null; | |
| windowSizeBits = GetLog2Ceil(this.windowSize); | |
| tcmSizeBits = GetLog2Ceil(this.tcmSize); | |
| pageSizeBits = GetLog2Ceil(this.pageSize); | |
| numWindowsBits = GetLog2Ceil(this.numWindows); | |
| windowSizeMask = (uint)((1 << (int)windowSizeBits) - 1); | |
| tcmSizeMask = (uint)((1 << (int)tcmSizeBits) - 1); | |
| pageSizeMask = (uint)((1 << (int)pageSizeBits) - 1); | |
| offsetLengthMask = GetNextPowerOfTwo(this.tcmSize) / this.pageSize - 1; | |
| Reset(); | |
| } | |
| private enum AccessType { | |
| Read, | |
| Write, | |
| ReadOrWrite, | |
| ReadAndWrite | |
| }; | |
| private string AccessTypeToString(AccessType type) { | |
| switch (type) { | |
| case AccessType.Read: | |
| return "Read"; | |
| case AccessType.Write: | |
| return "Write"; | |
| case AccessType.ReadOrWrite: | |
| return "ReadOrWrite"; | |
| case AccessType.ReadAndWrite: | |
| return "ReadAndWrite"; | |
| default: | |
| return "<unknown>"; | |
| } | |
| } | |
| private bool GetTranslatedAddress(uint offset, | |
| AccessType accessType, | |
| out uint translatedAddress) | |
| { | |
| if (enabled) | |
| { | |
| uint window = offset >> (int)windowSizeBits; | |
| if (window >= numWindows) { | |
| translatedAddress = 0; | |
| this.Log(LogLevel.Error, | |
| "Translation request for {0} at 0x{1:X}: failure, no window at address", | |
| AccessTypeToString(accessType), offset); | |
| return false; | |
| } | |
| uint windowAddr = offset & windowSizeMask; | |
| uint windowOffset = windowOffsets[window]; | |
| uint windowLength = windowLengths[window] + pageSize; | |
| uint windowPermission = windowPermissions[window]; | |
| bool hasPermission = false; | |
| switch (accessType) { | |
| case AccessType.Read: | |
| hasPermission = (windowPermission & 1) != 0; | |
| break; | |
| case AccessType.Write: | |
| hasPermission = (windowPermission & 2) != 0; | |
| break; | |
| case AccessType.ReadOrWrite: | |
| hasPermission = (windowPermission & 3) != 0; | |
| break; | |
| case AccessType.ReadAndWrite: | |
| hasPermission = (windowPermission & 3) == 3; | |
| break; | |
| } | |
| if (!hasPermission || (windowAddr >= windowLength)) | |
| { | |
| translatedAddress = 0; | |
| if (!hasPermission) { | |
| this.Log(LogLevel.Error, | |
| "Translation request for {0} at 0x{1:X}: failure on window {2}, no permission, window permissions are 0x{3:X}", | |
| AccessTypeToString(accessType), offset, window, windowPermission); | |
| } | |
| else | |
| { | |
| this.Log(LogLevel.Error, | |
| "Translation request for {0} at 0x{1:X}: failure on window {2}, out of range, window size is 0x{3:X}", | |
| AccessTypeToString(accessType), offset, window, windowLength); | |
| } | |
| return false; | |
| } | |
| translatedAddress = windowOffset + windowAddr; | |
| this.Log(LogLevel.Noisy, | |
| "Translation request for {0} at 0x{1:X}: success on window {2}, to tcm+0x{3:X}", | |
| AccessTypeToString(accessType), offset, window, translatedAddress); | |
| return true; | |
| } | |
| else | |
| { | |
| translatedAddress = offset; | |
| this.Log(LogLevel.Noisy, | |
| "Translation request for {0} at 0x{1:X}: pass-through, to tcm+0x{2:X}", | |
| AccessTypeToString(accessType), offset, translatedAddress); | |
| return true; | |
| } | |
| } | |
| public byte ReadByte(long offset) | |
| { | |
| uint translatedAddress; | |
| if (GetTranslatedAddress(checked((uint)offset), AccessType.Read, out translatedAddress)) | |
| { | |
| return tcm.ReadByte(translatedAddress); | |
| } | |
| else | |
| { | |
| // Trigger interrupt | |
| if (faultCallback != null) | |
| { | |
| faultCallback(); | |
| } | |
| return 0; | |
| } | |
| } | |
| public void WriteByte(long offset, byte value) | |
| { | |
| uint translatedAddress; | |
| if (GetTranslatedAddress(checked((uint)offset), AccessType.Write, out translatedAddress)) | |
| { | |
| tcm.WriteByte(translatedAddress, value); | |
| } | |
| else | |
| { | |
| // Trigger interrupt | |
| if (faultCallback != null) | |
| { | |
| faultCallback(); | |
| } | |
| } | |
| } | |
| public ushort ReadWord(long offset) | |
| { | |
| uint translatedAddress; | |
| if (GetTranslatedAddress(checked((uint)offset), AccessType.Read, out translatedAddress)) | |
| { | |
| return tcm.ReadWord(translatedAddress); | |
| } | |
| else | |
| { | |
| // Trigger interrupt | |
| if (faultCallback != null) | |
| { | |
| faultCallback(); | |
| } | |
| return 0; | |
| } | |
| } | |
| public void WriteWord(long offset, ushort value) | |
| { | |
| uint translatedAddress; | |
| if (GetTranslatedAddress(checked((uint)offset), AccessType.Write, out translatedAddress)) | |
| { | |
| tcm.WriteWord(translatedAddress, value); | |
| } | |
| else | |
| { | |
| // Trigger interrupt | |
| if (faultCallback != null) | |
| { | |
| faultCallback(); | |
| } | |
| } | |
| } | |
| public uint ReadDoubleWord(long offset) | |
| { | |
| uint translatedAddress; | |
| if (GetTranslatedAddress(checked((uint)offset), AccessType.Read, out translatedAddress)) | |
| { | |
| return tcm.ReadDoubleWord(translatedAddress); | |
| } | |
| else | |
| { | |
| // Trigger interrupt | |
| if (faultCallback != null) | |
| { | |
| faultCallback(); | |
| } | |
| return 0; | |
| } | |
| } | |
| public void WriteDoubleWord(long offset, uint value) | |
| { | |
| uint translatedAddress; | |
| if (GetTranslatedAddress(checked((uint)offset), AccessType.Write, out translatedAddress)) | |
| { | |
| tcm.WriteDoubleWord(translatedAddress, value); | |
| } | |
| else | |
| { | |
| // Trigger interrupt | |
| if (faultCallback != null) | |
| { | |
| faultCallback(); | |
| } | |
| } | |
| } | |
| public ulong TranslateAbsolute(ulong address) | |
| { | |
| foreach (var range in systemBus.GetRegistrationPoints(this).Select(x => x.Range)) | |
| { | |
| if (range.Contains(address)) | |
| { | |
| ulong offset = address - range.StartAddress; | |
| uint translatedAddress; | |
| if (GetTranslatedAddress(checked((uint)offset), AccessType.ReadOrWrite, out translatedAddress)) { | |
| return translatedAddress; | |
| } | |
| } | |
| } | |
| throw new RecoverableException("Cannot translate address that does not lay in redirector."); | |
| } | |
| public byte[] ReadBytes(long offset, int count) | |
| { | |
| uint startTranslatedAddress, lastTranslatedAddress; | |
| bool startValid = GetTranslatedAddress(checked((uint)offset), AccessType.Read, out startTranslatedAddress); | |
| bool endValid = GetTranslatedAddress(checked((uint)(offset + count - 1)), AccessType.Read, out lastTranslatedAddress); | |
| if (startValid && endValid) | |
| { | |
| return tcm.ReadBytes(startTranslatedAddress, count); | |
| } | |
| else | |
| { | |
| // Trigger interrupt | |
| if (faultCallback != null) | |
| { | |
| faultCallback(); | |
| } | |
| return new byte[count]; | |
| } | |
| } | |
| public void WriteBytes(long offset, byte[] array, int startingIndex, int count) | |
| { | |
| uint startTranslatedAddress, lastTranslatedAddress; | |
| bool startValid = GetTranslatedAddress(checked((uint)offset), AccessType.Write, out startTranslatedAddress); | |
| bool endValid = GetTranslatedAddress(checked((uint)(offset + count - 1)), AccessType.Write, out lastTranslatedAddress); | |
| if (startValid && endValid) | |
| { | |
| tcm.WriteBytes(startTranslatedAddress, array, startingIndex, count); | |
| } | |
| else | |
| { | |
| // Trigger interrupt | |
| if (faultCallback != null) | |
| { | |
| faultCallback(); | |
| } | |
| } | |
| } | |
| public void Reset() | |
| { | |
| enabled = true; | |
| windowOffsets = new uint[numWindows]; | |
| windowLengths = new uint[numWindows]; | |
| windowPermissions = new uint[numWindows]; | |
| } | |
| public uint GetWindowOffset(uint window) | |
| { | |
| if (window < windowSize) | |
| { | |
| return windowOffsets[window]; | |
| } | |
| else | |
| { | |
| this.Log(LogLevel.Error, "Invalid window number {0}", window); | |
| return 0; | |
| } | |
| } | |
| public uint GetWindowLength(uint window) | |
| { | |
| if (window < windowSize) | |
| { | |
| return windowLengths[window]; | |
| } | |
| else | |
| { | |
| this.Log(LogLevel.Error, "Invalid window number {0}", window); | |
| return 0; | |
| } | |
| } | |
| public uint GetWindowPermission(uint window) | |
| { | |
| if (window < windowSize) | |
| { | |
| return windowPermissions[window]; | |
| } | |
| else | |
| { | |
| this.Log(LogLevel.Error, "Invalid window number {0}", window); | |
| return 0; | |
| } | |
| } | |
| public void SetWindowOffset(uint window, uint value) | |
| { | |
| if (value > tcmSizeMask) | |
| { | |
| this.Log(LogLevel.Noisy, "Window length value {0} out of range", value); | |
| } | |
| if (window < windowSize) | |
| { | |
| windowOffsets[window] = value & tcmSizeMask & ~pageSizeMask; | |
| } | |
| else | |
| { | |
| this.Log(LogLevel.Error, "Invalid window number {0}", window); | |
| } | |
| } | |
| public void SetWindowLength(uint window, uint value) | |
| { | |
| if (value > tcmSizeMask) | |
| { | |
| this.Log(LogLevel.Noisy, "Window length value {0} out of range", value); | |
| } | |
| if (window < windowSize) | |
| { | |
| windowLengths[window] = value & tcmSizeMask & ~pageSizeMask; | |
| } | |
| else | |
| { | |
| this.Log(LogLevel.Error, "Invalid window number {0}", window); | |
| } | |
| } | |
| public void SetWindowPermission(uint window, uint value) | |
| { | |
| if (value > 3) | |
| { | |
| this.Log(LogLevel.Noisy, "Window permission value {0} out of range", value); | |
| } | |
| if (window < windowSize) | |
| { | |
| windowPermissions[window] = value & 3; | |
| } | |
| else | |
| { | |
| this.Log(LogLevel.Error, "Invalid window number {0}", window); | |
| } | |
| } | |
| public void RegisterFaultCallback(Action faultCallback) | |
| { | |
| this.faultCallback = faultCallback; | |
| } | |
| public long Size => (long)GetNextPowerOfTwo(windowSize * numWindows); | |
| private readonly SystemBus systemBus; | |
| private readonly MappedMemory tcm; | |
| public readonly uint windowSize; | |
| public readonly uint tcmSize; | |
| public readonly uint pageSize; | |
| public readonly uint numWindows; | |
| public readonly uint windowSizeBits; | |
| public readonly uint tcmSizeBits; | |
| public readonly uint pageSizeBits; | |
| public readonly uint numWindowsBits; | |
| public readonly uint windowSizeMask; | |
| public readonly uint tcmSizeMask; | |
| public readonly uint pageSizeMask; | |
| public readonly uint offsetLengthMask; | |
| // TO-DO(jonathantate): Need to plumb the enable bit to the control block register file | |
| public bool enabled; | |
| private uint[] windowOffsets; | |
| private uint[] windowLengths; | |
| private uint[] windowPermissions; | |
| private Action faultCallback; | |
| } | |
| } |