blob: 5da7a84d375ea6196b0244c34c2112841aa30293 [file]
using Antmicro.Renode.Core;
using Antmicro.Renode.Logging;
using Antmicro.Renode.Peripherals.Bus;
namespace Antmicro.Renode.Peripherals
{
public class Mailbox : IDoubleWordPeripheral, IKnownSize
{
//--------------------------------------------------------------------------
public Mailbox()
{
this.Log(LogLevel.Noisy, "Mailbox()");
mailbox_error = new GPIO();
mailbox_hiwater = new GPIO();
mailbox_lowater = new GPIO();
}
public void Reset() // IPeripheral
{
for (int i = 0; i < FIFO_SIZE; i++) fifo[i] = 0;
}
//--------------------------------------------------------------------------
public uint ReadDoubleWord(long offset) // IDoubleWordPeripheral
{
uint result = 0;
switch (offset)
{
case REG_ISTATE: {
this.Log(LogLevel.Noisy, "read reg_istate = 0b{0}", System.Convert.ToString(reg_istate, 2));
result = reg_istate;
break;
}
case REG_IENABLE: {
this.Log(LogLevel.Noisy, "read reg_ienable = 0b{0}", System.Convert.ToString(reg_ienable, 2));
result = reg_ienable;
break;
}
case REG_ITEST: {
this.Log(LogLevel.Noisy, "read reg_itest = 0b{0}", System.Convert.ToString(reg_itest, 2));
result = reg_itest;
break;
}
case REG_WRITE: {
this.Log(LogLevel.Error, "Tried to read from the mailbox write register");
reg_istate |= IRQ_ERROR; break;
}
case REG_READ:
if (fifo_count == 0)
{
this.Log(LogLevel.Error, "Tried to read from empty mailbox");
reg_istate |= IRQ_ERROR;
reg_error |= ERR_READ;
break;
}
else
{
this.Log(LogLevel.Noisy, "read fifo[{0}] = 0x{1:X8}, fifo_count {2}", cursor_r, fifo[cursor_r], fifo_count - 1);
result = fifo[cursor_r];
cursor_r = (cursor_r + 1) & FIFO_MASK;
fifo_count--;
if (fifo_count <= reg_lowater) reg_istate |= IRQ_LOWATER;
break;
}
case REG_STATUS: {
this.Log(LogLevel.Noisy, "read reg_status = 0b{0}", System.Convert.ToString(reg_status, 2));
result = reg_status;
break;
}
case REG_ERROR: {
// Reading clears pending errors.
this.Log(LogLevel.Noisy, "read reg_error = 0b{0}", System.Convert.ToString(reg_error, 2));
result = reg_error;
reg_error = 0;
break;
}
case REG_HIWATER: {
this.Log(LogLevel.Noisy, "read reg_hiwater = {0}", reg_hiwater);
result = reg_hiwater;
break;
}
case REG_LOWATER: {
this.Log(LogLevel.Noisy, "read reg_lowater = {0}", reg_lowater);
result = reg_lowater;
break;
}
case REG_FLUSH: {
this.Log(LogLevel.Error, "Tried to read from the mailbox flush register");
reg_istate |= IRQ_ERROR;
break;
}
}
update_regs();
return result;
}
//--------------------------------------------------------------------------
public void WriteDoubleWord(long offset, uint value) // IDoubleWordPeripheral
{
switch (offset)
{
case REG_ISTATE: {
// Interrupt state is write-to-clear.
uint new_istate = (reg_istate & ~value) & IRQ_MASK;
this.Log(LogLevel.Noisy, "write reg_istate 0b{0} -> 0b{1}", System.Convert.ToString(reg_istate, 2), System.Convert.ToString(new_istate, 2));
reg_istate = new_istate;
break;
}
case REG_IENABLE: {
this.Log(LogLevel.Noisy, "write reg_ienable 0b{0} -> 0b{1}", System.Convert.ToString(reg_ienable, 2), System.Convert.ToString(value & IRQ_MASK, 2));
reg_ienable = value & IRQ_MASK;
break;
}
case REG_ITEST: {
this.Log(LogLevel.Noisy, "write reg_itest 0b{0} -> 0b{1}", System.Convert.ToString(reg_itest, 2), System.Convert.ToString(value & IRQ_MASK, 2));
reg_itest = value & IRQ_MASK;
break;
}
case REG_WRITE:
if (fifo_count == FIFO_SIZE)
{
this.Log(LogLevel.Error, "Tried to write to full mailbox");
reg_istate |= IRQ_ERROR;
reg_error |= ERR_WRITE;
break;
}
else
{
this.Log(LogLevel.Noisy, "write fifo[{0}] = 0x{1:X8}, fifo_count {2}", cursor_w, value, fifo_count + 1);
fifo[cursor_w] = value;
cursor_w = (cursor_w + 1) & FIFO_MASK;
fifo_count++;
if (fifo_count >= reg_hiwater) reg_istate |= IRQ_HIWATER;
break;
}
case REG_READ: {
this.Log(LogLevel.Error, "Tried to write 0x{0:X8} to the mailbox read register", value);
reg_istate |= IRQ_ERROR;
break;
}
case REG_STATUS: {
this.Log(LogLevel.Noisy, "write 0x{0:X8} to reg_status", value);
reg_istate |= IRQ_ERROR;
break;
}
case REG_ERROR: {
this.Log(LogLevel.Noisy, "write 0x{0:X8} to reg_status", value);
reg_istate |= IRQ_ERROR;
break;
}
case REG_HIWATER: {
this.Log(LogLevel.Noisy, "write reg_hiwater {0} -> {1}", reg_hiwater, value);
reg_hiwater = System.Math.Min(value, FIFO_SIZE - 1);
break;
}
case REG_LOWATER: {
this.Log(LogLevel.Noisy, "write reg_lowater {0} -> {1}", reg_lowater, value);
reg_lowater = System.Math.Min(value, FIFO_SIZE - 1);
break;
}
case REG_FLUSH:
this.Log(LogLevel.Noisy, "write reg_flush = 0b{0}", System.Convert.ToString(value, 2));
// FIXME - not 100% sure that flushing makes sense w/ this setup...
if ((value & FLUSH_READ) != 0) fifo_count = 0;
if ((value & FLUSH_WRITE) != 0) fifo_count = 0;
break;
}
update_regs();
}
//--------------------------------------------------------------------------
public void update_regs()
{
reg_status = 0;
if (fifo_count == 0) reg_status |= STATUS_EMPTY;
if (fifo_count == FIFO_SIZE) reg_status |= STATUS_FULL;
if (fifo_count <= reg_lowater) reg_status |= STATUS_LOWATER;
if (fifo_count >= reg_hiwater) reg_status |= STATUS_HIWATER;
bool new_irq_error = ((reg_ienable & IRQ_ERROR) != 0) && ((reg_istate & IRQ_ERROR) != 0);
bool new_irq_hiwater = ((reg_ienable & IRQ_HIWATER) != 0) && ((reg_istate & IRQ_HIWATER) != 0);
bool new_irq_lowater = ((reg_ienable & IRQ_LOWATER) != 0) && ((reg_istate & IRQ_LOWATER) != 0);
if (!mailbox_error.IsSet && new_irq_error) this.Log(LogLevel.Noisy, "Triggering error IRQ");
if (!mailbox_hiwater.IsSet && new_irq_hiwater) this.Log(LogLevel.Noisy, "Triggering hiwater IRQ");
if (!mailbox_lowater.IsSet && new_irq_lowater) this.Log(LogLevel.Noisy, "Triggering lowater IRQ");
if (mailbox_error.IsSet && !new_irq_error) this.Log(LogLevel.Noisy, "Clearing error IRQ");
if (mailbox_hiwater.IsSet && !new_irq_hiwater) this.Log(LogLevel.Noisy, "Clearing hiwater IRQ");
if (mailbox_lowater.IsSet && !new_irq_lowater) this.Log(LogLevel.Noisy, "Clearing lowater IRQ");
mailbox_error.Set(new_irq_error);
mailbox_hiwater.Set(new_irq_hiwater);
mailbox_lowater.Set(new_irq_lowater);
}
//--------------------------------------------------------------------------
const uint FIFO_SIZE = 8;
const uint FIFO_MASK = FIFO_SIZE - 1;
const uint REG_ISTATE = 0x000;
const uint REG_IENABLE = 0x004;
const uint REG_ITEST = 0x008;
const uint REG_WRITE = 0x00C;
const uint REG_READ = 0x010;
const uint REG_STATUS = 0x014;
const uint REG_ERROR = 0x018;
const uint REG_HIWATER = 0x01C;
const uint REG_LOWATER = 0x020;
const uint REG_FLUSH = 0x024;
const uint STATUS_EMPTY = 0b0001;
const uint STATUS_FULL = 0b0010;
const uint STATUS_HIWATER = 0b0100;
const uint STATUS_LOWATER = 0b1000;
const uint STATUS_MASK = 0b1111;
const uint ERR_READ = 0b01;
const uint ERR_WRITE = 0b10;
const uint IRQ_ERROR = 0b001;
const uint IRQ_HIWATER = 0b010;
const uint IRQ_LOWATER = 0b100;
const uint IRQ_MASK = 0b111;
const uint FLUSH_READ = 0b01;
const uint FLUSH_WRITE = 0b10;
//--------------------------------------------------------------------------
private uint reg_istate;
private uint reg_ienable;
private uint reg_itest;
private uint reg_mboxw;
private uint reg_mboxr;
private uint reg_status;
private uint reg_error;
private uint reg_lowater;
private uint reg_hiwater;
private uint cursor_r = 0;
private uint cursor_w = 0;
private uint fifo_count = 0;
private uint[] fifo = new uint[FIFO_SIZE];
public GPIO mailbox_error { get; }
public GPIO mailbox_hiwater { get; }
public GPIO mailbox_lowater { get; }
public long Size => 40; // IKnownSize
//--------------------------------------------------------------------------
}
}