blob: a63cfabe4c1943e3a601955a5ab93e8030daad66 [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Antmicro.Renode.Core;
using Antmicro.Renode.Logging;
using Antmicro.Renode.Peripherals.Bus;
namespace Antmicro.Renode.Peripherals
{
// NOTE: Log messages don't seem to work correctly if called from a sub-object
// of a peripheral, so the per-endpoint implementation is in Mailbox below.
public class MailboxEndpoint
{
public MailboxEndpoint(string name, MailboxFifo fifo_r, MailboxFifo fifo_w,
GPIO wtirq, GPIO rtirq, GPIO eirq)
{
this.name = name;
this.fifo_r = fifo_r;
this.fifo_w = fifo_w;
this.wtirq = wtirq;
this.rtirq = rtirq;
this.eirq = eirq;
}
public void Reset()
{
intr_state = 0;
intr_enable = 0;
intr_test = 0;
error = 0;
wirqt = 0;
rirqt = 0;
}
public string name;
public MailboxFifo fifo_r;
public MailboxFifo fifo_w;
public GPIO wtirq;
public GPIO rtirq;
public GPIO eirq;
public uint intr_state;
public uint intr_enable;
public uint intr_test;
public uint error;
public uint wirqt; // Write fifo IRQ threshold
public uint rirqt; // Read fifo IRQ threshold
}
//----------------------------------------------------------------------------
public class MailboxFifo
{
public void Push(uint value)
{
if (!Full())
{
fifo[cursor_w] = value;
cursor_w = (cursor_w + 1) & FIFO_MASK;
count++;
}
}
public uint Pop()
{
uint result = 0;
if (!Empty())
{
result = fifo[cursor_r];
cursor_r = (cursor_r + 1) & FIFO_MASK;
count--;
}
return result;
}
public bool Empty()
{
return count == 0;
}
public bool Full()
{
return count == FIFO_SIZE;
}
public void Flush()
{
cursor_r = 0;
cursor_w = 0;
count = 0;
for (int i = 0; i < FIFO_SIZE; i++)
fifo[i] = 0;
}
const uint FIFO_SIZE = 8;
const uint FIFO_MASK = FIFO_SIZE - 1;
public uint cursor_r = 0;
public uint cursor_w = 0;
public uint count = 0;
public uint[] fifo = new uint[FIFO_SIZE];
}
//----------------------------------------------------------------------------
public class Mailbox : IBusPeripheral
{
MailboxEndpoint endpoint_A;
MailboxEndpoint endpoint_B;
MailboxFifo fifo_AB;
MailboxFifo fifo_BA;
public GPIO wtirq_A { get; }
public GPIO rtirq_A { get; }
public GPIO eirq_A { get; }
public GPIO wtirq_B { get; }
public GPIO rtirq_B { get; }
public GPIO eirq_B { get; }
public Mailbox(string endpoint_a_name, string endpoint_b_name)
{
this.Log(LogLevel.Noisy, "Mailbox()");
wtirq_A = new GPIO();
rtirq_A = new GPIO();
eirq_A = new GPIO();
wtirq_B = new GPIO();
rtirq_B = new GPIO();
eirq_B = new GPIO();
fifo_AB = new MailboxFifo();
fifo_BA = new MailboxFifo();
endpoint_A = new MailboxEndpoint(endpoint_a_name, fifo_BA, fifo_AB,
wtirq_A, rtirq_A, eirq_A);
endpoint_B = new MailboxEndpoint(endpoint_b_name, fifo_AB, fifo_BA,
wtirq_B, rtirq_B, eirq_B);
this.Log(LogLevel.Noisy, "Mailbox() done");
}
public void Reset()
{
endpoint_A.Reset();
endpoint_B.Reset();
fifo_AB.Flush();
fifo_BA.Flush();
}
[ConnectionRegion("endpoint_a")]
public uint ReadDoubleWord_A(long offset)
{
uint value = ReadDoubleWord(endpoint_A, offset);
UpdateRegisters(endpoint_A);
UpdateRegisters(endpoint_B);
return value;
}
[ConnectionRegion("endpoint_a")]
public void WriteDoubleWord_A(long offset, uint value)
{
WriteDoubleWord(endpoint_A, offset, value);
UpdateRegisters(endpoint_A);
UpdateRegisters(endpoint_B);
}
[ConnectionRegion("endpoint_b")]
public uint ReadDoubleWord_B(long offset)
{
uint value = ReadDoubleWord(endpoint_B, offset);
UpdateRegisters(endpoint_A);
UpdateRegisters(endpoint_B);
return value;
}
[ConnectionRegion("endpoint_b")]
public void WriteDoubleWord_B(long offset, uint value)
{
WriteDoubleWord(endpoint_B, offset, value);
UpdateRegisters(endpoint_A);
UpdateRegisters(endpoint_B);
}
public uint ReadDoubleWord(MailboxEndpoint ep, long offset)
{
uint value = 0;
switch (offset)
{
case REG_INTR_STATE:
value = ep.intr_state;
break;
case REG_INTR_ENABLE:
value = ep.intr_enable;
break;
case REG_INTR_TEST:
value = ep.intr_test;
break;
case REG_MBOXW:
this.Log(LogLevel.Error, "Tried to read from {0}'s write port",
ep.name);
break;
case REG_MBOXR:
if (!ep.fifo_r.Empty())
{
value = ep.fifo_r.Pop();
}
else
{
this.Log(LogLevel.Error, "Tried to read from {0}'s empty fifo",
ep.name);
ep.error |= ERROR_BIT_READ;
ep.intr_state |= INTR_STATE_BIT_EIRQ;
}
break;
case REG_STATUS:
if (ep.fifo_r.Empty())
value |= STATUS_BIT_EMPTY;
if (ep.fifo_w.Full())
value |= STATUS_BIT_FULL;
if (ep.fifo_w.count > ep.wirqt)
value |= STATUS_BIT_WFIFOL;
if (ep.fifo_r.count > ep.rirqt)
value |= STATUS_BIT_RFIFOL;
break;
case REG_ERROR:
break;
case REG_WIRQT:
value = ep.wirqt;
break;
case REG_RIRQT:
value = ep.rirqt;
break;
case REG_CTRL:
// "On write, the fifo is cleared and the register is reset"
// So this can only read as 0.
value = 0;
break;
}
this.Log(LogLevel.Noisy, "{0}.ReadDoubleWord({1}) = 0x{2:X8}", ep.name,
reg_names[offset >> 2], value);
return value;
}
public void WriteDoubleWord(MailboxEndpoint ep, long offset, uint value)
{
switch (offset)
{
case REG_INTR_STATE:
ep.intr_state &= ~(value & INTR_STATE_MASK);
break;
case REG_INTR_ENABLE:
ep.intr_enable = value & INTR_ENABLE_MASK;
break;
case REG_INTR_TEST:
ep.intr_test = value & INTR_TEST_MASK;
ep.intr_state |= value & INTR_STATE_MASK;
break;
case REG_MBOXW:
if (!ep.fifo_w.Full())
{
ep.fifo_w.Push(value);
}
else
{
this.Log(LogLevel.Error, "Tried to write to {0}'s full fifo",
ep.name);
ep.error |= ERROR_BIT_WRITE;
ep.intr_state |= INTR_STATE_BIT_EIRQ;
}
break;
case REG_MBOXR:
this.Log(LogLevel.Error, "Tried to write to {0}'s read port",
ep.name);
ep.intr_state |= INTR_STATE_BIT_EIRQ;
break;
case REG_STATUS:
this.Log(LogLevel.Error, "Tried to write to {0}'s status register",
ep.name);
ep.intr_state |= INTR_STATE_BIT_EIRQ;
break;
case REG_ERROR:
break;
case REG_WIRQT:
ep.wirqt = value & WIRQT_MASK;
break;
case REG_RIRQT:
ep.rirqt = value & RIRQT_MASK;
break;
case REG_CTRL:
if ((value & CTRL_BIT_FLUSH_WFIFO) != 0)
ep.fifo_w.Flush();
if ((value & CTRL_BIT_FLUSH_RFIFO) != 0)
ep.fifo_r.Flush();
break;
}
this.Log(LogLevel.Noisy, "{0}.WriteDoubleWord({1}) = 0x{2:X8}", ep.name,
reg_names[offset >> 2], value);
}
public void UpdateRegisters(MailboxEndpoint ep)
{
if (ep.fifo_w.count > ep.wirqt)
ep.intr_state |= INTR_STATE_BIT_WTIRQ;
if (ep.fifo_r.count > ep.rirqt)
ep.intr_state |= INTR_STATE_BIT_RTIRQ;
bool new_wtirq =
(ep.intr_enable & ep.intr_state & INTR_ENABLE_BIT_WTIRQ) != 0;
bool new_rtirq =
(ep.intr_enable & ep.intr_state & INTR_ENABLE_BIT_RTIRQ) != 0;
bool new_eirq =
(ep.intr_enable & ep.intr_state & INTR_ENABLE_BIT_EIRQ) != 0;
if (!ep.wtirq.IsSet && new_wtirq)
this.Log(LogLevel.Noisy, "{0}.WIRQ triggered", ep.name);
if (!ep.rtirq.IsSet && new_rtirq)
this.Log(LogLevel.Noisy, "{0}.RIRQ triggered", ep.name);
if (!ep.eirq.IsSet && new_eirq)
this.Log(LogLevel.Noisy, "{0}.EIRQ triggered", ep.name);
if (ep.wtirq.IsSet && !new_wtirq)
this.Log(LogLevel.Noisy, "{0}.WIRQ cleared", ep.name);
if (ep.rtirq.IsSet && !new_rtirq)
this.Log(LogLevel.Noisy, "{0}.RIRQ cleared", ep.name);
if (ep.eirq.IsSet && !new_eirq)
this.Log(LogLevel.Noisy, "{0}.EIRQ cleared", ep.name);
ep.eirq.Set(new_eirq);
ep.wtirq.Set(new_wtirq);
ep.rtirq.Set(new_rtirq);
}
public static readonly string[] reg_names = {
"INTR_STATE",
"INTR_ENABLE",
"INTR_TEST",
"MBOXW",
"MBOXR",
"STATUS",
"ERROR",
"WIRQT",
"RIRQT",
"CTRL"
};
const uint REG_INTR_STATE = 0x000; // R/W1C
const uint REG_INTR_ENABLE = 0x004; // R/W
const uint REG_INTR_TEST = 0x008; // R/W
const uint REG_MBOXW = 0x00C; // W
const uint REG_MBOXR = 0x010; // R
const uint REG_STATUS = 0x014; // R
const uint REG_ERROR = 0x018; // R
const uint REG_WIRQT = 0x01C; // R/W
const uint REG_RIRQT = 0x020; // R/W
const uint REG_CTRL = 0x024; // R/W
const uint INTR_STATE_BIT_WTIRQ = 0b001;
const uint INTR_STATE_BIT_RTIRQ = 0b010;
const uint INTR_STATE_BIT_EIRQ = 0b100;
const uint INTR_STATE_MASK = 0b111;
const uint INTR_ENABLE_BIT_WTIRQ = 0b001;
const uint INTR_ENABLE_BIT_RTIRQ = 0b010;
const uint INTR_ENABLE_BIT_EIRQ = 0b100;
const uint INTR_ENABLE_MASK = 0b111;
const uint INTR_TEST_BIT_WTIRQ = 0b001;
const uint INTR_TEST_BIT_RTIRQ = 0b010;
const uint INTR_TEST_BIT_EIRQ = 0b100;
const uint INTR_TEST_MASK = 0b111;
const uint STATUS_BIT_EMPTY = 0b0001;
const uint STATUS_BIT_FULL = 0b0010;
const uint STATUS_BIT_WFIFOL = 0b0100;
const uint STATUS_BIT_RFIFOL = 0b1000;
const uint STATUS_MASK = 0b1111;
const uint ERROR_BIT_READ = 0b01;
const uint ERROR_BIT_WRITE = 0b10;
const uint ERROR_MASK = 0b11;
const uint FIFO_SIZE = 8;
const uint FIFO_MASK = FIFO_SIZE - 1;
const uint WIRQT_MASK = FIFO_MASK;
const uint RIRQT_MASK = FIFO_MASK;
const uint CTRL_BIT_FLUSH_WFIFO = 0b01;
const uint CTRL_BIT_FLUSH_RFIFO = 0b10;
const uint CTRL_MASK = 0b11;
}
//----------------------------------------------------------------------------
}