Change Mailbox to use the RTL mailbox register interface.

Change-Id: I90031927d518696220c8e4dae4680fc594e1401b
diff --git a/platforms/shodan.repl b/platforms/shodan.repl
index fffb2d5..fe181bb 100644
--- a/platforms/shodan.repl
+++ b/platforms/shodan.repl
@@ -4,13 +4,13 @@
 
 // Core 0 Mailbox           [‘h400F_0000 - ‘h400F_0020)   32B
 mailbox0: Mailbox @ sysbus 0x400F0000
-    MailboxSEND -> plic@100 // TODO(aappleby) these plic lines are bogus, fix them
-    MailboxRECV -> plic@101
+    mailbox_hiwater -> plic@100 // TODO(aappleby) these plic lines are bogus, fix them
+    mailbox_lowater -> plic@101
 
 // Core 1 Mailbox           [‘h400F_1000 - ‘h400F_1020)   32B
 mailbox1: Mailbox @ sysbus 0x400F1000
-    MailboxSEND -> smc_plic@9 // TODO(aappleby) these plic lines are bogus, fix them
-    MailboxRECV -> smc_plic@10
+    mailbox_hiwater -> smc_plic@9 // TODO(aappleby) these plic lines are bogus, fix them
+    mailbox_lowater -> smc_plic@10
 
 vec_controlblock :
     HostReqIRQ -> smc_plic@11
diff --git a/shodan_infrastructure/Mailbox.cs b/shodan_infrastructure/Mailbox.cs
index ab61bb3..5da7a84 100644
--- a/shodan_infrastructure/Mailbox.cs
+++ b/shodan_infrastructure/Mailbox.cs
@@ -6,70 +6,276 @@
 {
   public class Mailbox : IDoubleWordPeripheral, IKnownSize
   {
+    //--------------------------------------------------------------------------
+
     public Mailbox()
     {
-      MailboxSEND = new GPIO();
-      MailboxRECV = new GPIO();
+      this.Log(LogLevel.Noisy, "Mailbox()");
+      mailbox_error = new GPIO();
+      mailbox_hiwater = new GPIO();
+      mailbox_lowater = new GPIO();
     }
 
-    public void Reset() // IPeripheral
+    public void Reset() // IPeripheral 
     {
-      for (int i = 0; i < 8; i++) message[i] = 0;
+      for (int i = 0; i < FIFO_SIZE; i++) fifo[i] = 0;
     }
 
+    //--------------------------------------------------------------------------
+
     public uint ReadDoubleWord(long offset) // IDoubleWordPeripheral
     {
       uint result = 0;
-      switch(offset) {
-        case 0x000: result = message[0]; break;
-        case 0x004: result = message[1]; break;
-        case 0x008: result = message[2]; break;
-        case 0x00C: result = message[3]; break;
-        case 0x010: result = message[4]; break;
-        case 0x014: result = message[5]; break;
-        case 0x018: result = message[6]; break;
-        case 0x01C: result = message[7]; break;
-        case 0x020: result = MailboxSEND.IsSet ? 1u : 0u; break;
-        case 0x024: result = MailboxRECV.IsSet ? 1u : 0u; break;
-        default: {
-          this.Log(LogLevel.Error, "Mailbox::ReadDoubleWord - bad offset {0}", offset);
+      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 0x000: message[0] = value; break;
-        case 0x004: message[1] = value; break;
-        case 0x008: message[2] = value; break;
-        case 0x00C: message[3] = value; break;
-        case 0x010: message[4] = value; break;
-        case 0x014: message[5] = value; break;
-        case 0x018: message[6] = value; break;
-        case 0x01C: message[7] = value; break;
-        case 0x020: {
-          this.Log(LogLevel.Noisy, "Setting MailboxSEND to {0}", value != 0);
-          MailboxSEND.Set(value != 0);
+      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 0x024: {
-          this.Log(LogLevel.Noisy, "Setting MailboxRECV to {0}", value != 0);
-          MailboxRECV.Set(value != 0);
+
+        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;
         }
-        default: {
-          this.Log(LogLevel.Error, "Mailbox::WriteDoubleWord - bad offset {0}", offset);
+
+        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
-    private uint[] message = new uint[8];
-    public GPIO MailboxSEND { get; }
-    public GPIO MailboxRECV { get; }
+
+    //--------------------------------------------------------------------------
   }
 }