blob: 235754dc43d7b801f178f27d485faadaf58f6296 [file]
//
// 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;
}
}