MMU: Implement data segment interrupts
authorPaul Mackerras <paulus@ozlabs.org>
Thu, 23 Apr 2020 11:54:08 +0000 (21:54 +1000)
committerPaul Mackerras <paulus@ozlabs.org>
Fri, 8 May 2020 02:12:01 +0000 (12:12 +1000)
A data segment interrupt (DSegI) occurs when an address to be
translated by the MMU is outside the range of the radix tree
or the top two bits of the address (the quadrant) are 01 or 10.
This is detected in a new state of the MMU state machine, and
is sent back to loadstore1 as an error, which sends it on to
execute1 to generate an interrupt to the 0x380 vector.

Signed-off-by: Paul Mackerras <paulus@ozlabs.org>
common.vhdl
execute1.vhdl
loadstore1.vhdl
mmu.vhdl

index d617fa4aed3f356fca90da2bd68c6f09c0976dce..07d1a36b727ebc6d05b4a5cc870a334b0caa392d 100644 (file)
@@ -242,6 +242,7 @@ package common is
 
     type Loadstore1ToExecute1Type is record
         exception : std_ulogic;
+        segment_fault : std_ulogic;
     end record;
 
     type Loadstore1ToDcacheType is record
@@ -280,6 +281,7 @@ package common is
         done    : std_ulogic;
         invalid : std_ulogic;
         badtree : std_ulogic;
+        segerr  : std_ulogic;
         sprval  : std_ulogic_vector(63 downto 0);
     end record;
 
index 5e25efccdffd470c1391bcb6e7f5be5b13366f7e..7181f7f80e7c9b3b7a542d90a2e5a15d8b86e581 100644 (file)
@@ -974,7 +974,11 @@ begin
 
         -- generate DSI for load/store exceptions
         if l_in.exception = '1' then
-            ctrl_tmp.irq_nia <= std_logic_vector(to_unsigned(16#300#, 64));
+            if l_in.segment_fault = '0' then
+                ctrl_tmp.irq_nia <= std_logic_vector(to_unsigned(16#300#, 64));
+            else
+                ctrl_tmp.irq_nia <= std_logic_vector(to_unsigned(16#380#, 64));
+            end if;
             ctrl_tmp.srr1 <= msr_copy(ctrl.msr);
             v.e.exc_write_enable := '1';
             v.e.exc_write_reg := fast_spr_num(SPR_SRR0);
index 03aaa6f98c9bd49501b4a4a1505a682b5eb48fee..a29564b58a791f5a2fd00e0c7674bf21e289340f 100644 (file)
@@ -372,7 +372,7 @@ begin
                 byte_sel := r.first_bytes;
             end if;
             if m_in.done = '1' then
-                if m_in.invalid = '0' and m_in.badtree = '0' then
+                if m_in.invalid = '0' and m_in.badtree = '0' and m_in.segerr = '0' then
                     -- retry the request now that the MMU has installed a TLB entry
                     req := '1';
                     if r.state = MMU_LOOKUP_1ST then
@@ -480,9 +480,12 @@ begin
 
         -- update exception info back to execute1
         e_out.exception <= exception;
+        e_out.segment_fault <= m_in.segerr;
         if exception = '1' then
             v.dar := addr;
-            v.dsisr := dsisr;
+            if m_in.segerr = '0' then
+                v.dsisr := dsisr;
+            end if;
         end if;
 
         stall_out <= stall;
index fe6ad1651bff9fc3138fbe3331fcff67a55e40ec..293b7a80c8017dfd0588cc696c01a1f2ef4b52f9 100644 (file)
--- a/mmu.vhdl
+++ b/mmu.vhdl
@@ -26,11 +26,11 @@ architecture behave of mmu is
 
     type state_t is (IDLE,
                      TLB_WAIT,
+                     SEGMENT_CHECK,
                      RADIX_LOOKUP,
                      RADIX_READ_WAIT,
                      RADIX_LOAD_TLB,
-                     RADIX_NO_TRANS,
-                     RADIX_BAD_TREE
+                     RADIX_ERROR
                      );
 
     type reg_stage_t is record
@@ -44,6 +44,9 @@ architecture behave of mmu is
         mask_size : unsigned(4 downto 0);
         pgbase    : std_ulogic_vector(55 downto 0);
         pde       : std_ulogic_vector(63 downto 0);
+        invalid   : std_ulogic;
+        badtree   : std_ulogic;
+        segerror  : std_ulogic;
     end record;
 
     signal r, rin : reg_stage_t;
@@ -155,8 +158,6 @@ begin
         variable v : reg_stage_t;
         variable dcreq : std_ulogic;
         variable done : std_ulogic;
-        variable invalid : std_ulogic;
-        variable badtree : std_ulogic;
         variable tlb_load : std_ulogic;
         variable tlbie_req : std_ulogic;
         variable rts : unsigned(5 downto 0);
@@ -164,13 +165,15 @@ begin
         variable pgtable_addr : std_ulogic_vector(63 downto 0);
         variable pte : std_ulogic_vector(63 downto 0);
         variable data : std_ulogic_vector(63 downto 0);
+        variable nonzero : std_ulogic;
     begin
         v := r;
         v.valid := '0';
         dcreq := '0';
         done := '0';
-        invalid := '0';
-        badtree := '0';
+        v.invalid := '0';
+        v.badtree := '0';
+        v.segerror := '0';
         tlb_load := '0';
         tlbie_req := '0';
 
@@ -183,10 +186,11 @@ begin
         case r.state is
         when IDLE =>
             -- rts == radix tree size, # address bits being translated
-            rts := unsigned('0' & r.pgtbl0(62 downto 61) & r.pgtbl0(7 downto 5)) + (31 - 12);
+            rts := unsigned('0' & r.pgtbl0(62 downto 61) & r.pgtbl0(7 downto 5));
             -- mbits == # address bits to index top level of tree
             mbits := unsigned('0' & r.pgtbl0(4 downto 0));
-            v.shift := rts - mbits;
+            -- set v.shift to rts so that we can use finalmask for the segment check
+            v.shift := rts;
             v.mask_size := mbits(4 downto 0);
             v.pgbase := r.pgtbl0(55 downto 8) & x"00";
 
@@ -198,13 +202,12 @@ begin
                     v.state := TLB_WAIT;
                 else
                     v.valid := '1';
-                    -- for now, take RPDS = 0 to disable radix translation
+                    -- Use RPDS = 0 to disable radix tree walks
                     if mbits = 0 then
-                        v.state := RADIX_NO_TRANS;
-                    elsif mbits < 5 or mbits > 16 or mbits > rts then
-                        v.state := RADIX_BAD_TREE;
+                        v.state := RADIX_ERROR;
+                        v.invalid := '1';
                     else
-                        v.state := RADIX_LOOKUP;
+                        v.state := SEGMENT_CHECK;
                     end if;
                 end if;
             end if;
@@ -218,6 +221,20 @@ begin
                 v.state := IDLE;
             end if;
 
+        when SEGMENT_CHECK =>
+            mbits := '0' & r.mask_size;
+            v.shift := r.shift + (31 - 12) - mbits;
+            nonzero := or(r.addr(61 downto 31) and not finalmask(30 downto 0));
+            if r.addr(63) /= r.addr(62) or nonzero = '1' then
+                v.state := RADIX_ERROR;
+                v.segerror := '1';
+            elsif mbits < 5 or mbits > 16 or mbits > (r.shift + (31 - 12)) then
+                v.state := RADIX_ERROR;
+                v.badtree := '1';
+            else
+                v.state := RADIX_LOOKUP;
+            end if;
+
         when RADIX_LOOKUP =>
             dcreq := '1';
             v.state := RADIX_READ_WAIT;
@@ -234,7 +251,8 @@ begin
                         else
                             mbits := unsigned('0' & data(4 downto 0));
                             if mbits < 5 or mbits > 16 or mbits > r.shift then
-                                v.state := RADIX_BAD_TREE;
+                                v.state := RADIX_ERROR;
+                                v.badtree := '1';
                             else
                                 v.shift := v.shift - mbits;
                                 v.mask_size := mbits(4 downto 0);
@@ -244,10 +262,12 @@ begin
                         end if;
                     else
                         -- non-present PTE, generate a DSI
-                        v.state := RADIX_NO_TRANS;
+                        v.state := RADIX_ERROR;
+                        v.invalid := '1';
                     end if;
                 else
-                    v.state := RADIX_BAD_TREE;
+                    v.state := RADIX_ERROR;
+                    v.badtree := '1';
                 end if;
             end if;
 
@@ -256,15 +276,10 @@ begin
             dcreq := '1';
             v.state := TLB_WAIT;
 
-        when RADIX_NO_TRANS =>
+        when RADIX_ERROR =>
             done := '1';
-            invalid := '1';
             v.state := IDLE;
 
-        when RADIX_BAD_TREE =>
-            done := '1';
-            badtree := '1';
-            v.state := IDLE;
         end case;
 
         pgtable_addr := x"00" & r.pgbase(55 downto 19) &
@@ -279,8 +294,9 @@ begin
 
         -- drive outputs
         l_out.done <= done;
-        l_out.invalid <= invalid;
-        l_out.badtree <= badtree;
+        l_out.invalid <= r.invalid;
+        l_out.badtree <= r.badtree;
+        l_out.segerr <= r.segerror;
 
         d_out.valid <= dcreq;
         d_out.tlbie <= tlbie_req;