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