|  | // 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; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------------- | 
|  | } |