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