Implement access permission checks
authorPaul Mackerras <paulus@ozlabs.org>
Mon, 20 Apr 2020 02:43:06 +0000 (12:43 +1000)
committerPaul Mackerras <paulus@ozlabs.org>
Fri, 8 May 2020 02:12:01 +0000 (12:12 +1000)
This adds logic to the dcache to check the permissions encoded in
the PTE that it gets from the dTLB.  The bits that are checked are:

R must be 1
C must be 1 for a store
EAA(0) - if this is 1, MSR[PR] must be 0
EAA(2) must be 1 for a store
EAA(1) | EAA(2) must be 1 for a load

In addition, ATT(0) is used to indicate a cache-inhibited access.

This now implements DSISR bits 36, 38 and 45.

(Bit numbers above correspond to the ISA, i.e. using big-endian
numbering.)

MSR[PR] is now conveyed to loadstore1 for use in permission checking.

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

index 59b374421d76df1ce1bbb680e5ad0e2b93ec6b63..e8ec19e7af78eb4958ade059600add07a65d531d 100644 (file)
@@ -230,12 +230,13 @@ package common is
        xerc : xer_common_t;
         reserve : std_ulogic;                           -- set for larx/stcx.
         rc : std_ulogic;                                -- set for stcx.
-        spr_num : spr_num_t;                            -- SPR number for mfspr/mtspr
         virt_mode : std_ulogic;                         -- do translation through TLB
+        priv_mode : std_ulogic;                         -- privileged mode (MSR[PR] = 0)
+        spr_num : spr_num_t;                            -- SPR number for mfspr/mtspr
     end record;
     constant Execute1ToLoadstore1Init : Execute1ToLoadstore1Type := (valid => '0', op => OP_ILLEGAL, ci => '0', byte_reverse => '0',
                                                                      sign_extend => '0', update => '0', xerc => xerc_init,
-                                                                     reserve => '0', rc => '0', virt_mode => '0',
+                                                                     reserve => '0', rc => '0', virt_mode => '0', priv_mode => '0',
                                                                      spr_num => 0, others => (others => '0'));
 
     type Loadstore1ToExecute1Type is record
@@ -250,6 +251,7 @@ package common is
        nc : std_ulogic;
         reserve : std_ulogic;
         virt_mode : std_ulogic;
+        priv_mode : std_ulogic;
        addr : std_ulogic_vector(63 downto 0);
        data : std_ulogic_vector(63 downto 0);
         byte_sel : std_ulogic_vector(7 downto 0);
@@ -261,6 +263,8 @@ package common is
         store_done : std_ulogic;
         error : std_ulogic;
         tlb_miss : std_ulogic;
+        perm_error : std_ulogic;
+        rc_error : std_ulogic;
     end record;
 
     type Loadstore1ToWritebackType is record
index 7895877893b134cb9e1bfe44088b4714beffcf2a..03b3886f3e997c4047027650fab06d685e67c542 100644 (file)
@@ -149,6 +149,30 @@ architecture rtl of dcache is
     signal r0 : Loadstore1ToDcacheType;
     signal r0_valid : std_ulogic;
 
+    -- Record for storing permission, attribute, etc. bits from a PTE
+    type perm_attr_t is record
+        reference : std_ulogic;
+        changed   : std_ulogic;
+        nocache   : std_ulogic;
+        priv      : std_ulogic;
+        rd_perm   : std_ulogic;
+        wr_perm   : std_ulogic;
+    end record;
+
+    function extract_perm_attr(pte : std_ulogic_vector(TLB_PTE_BITS - 1 downto 0)) return perm_attr_t is
+        variable pa : perm_attr_t;
+    begin
+        pa.reference := pte(8);
+        pa.changed := pte(7);
+        pa.nocache := pte(5);
+        pa.priv := pte(3);
+        pa.rd_perm := pte(2);
+        pa.wr_perm := pte(1);
+        return pa;
+    end;
+
+    constant real_mode_perm_attr : perm_attr_t := (nocache => '0', others => '1');
+
     -- Type of operation on a "valid" input
     type op_t is (OP_NONE,
                  OP_LOAD_HIT,      -- Cache hit on load
@@ -208,7 +232,9 @@ architecture rtl of dcache is
 
         -- Signals to complete with error
         error_done       : std_ulogic;
-        tlb_miss         : std_ulogic;
+        tlb_miss         : std_ulogic;          -- No entry found in TLB
+        perm_error       : std_ulogic;          -- Permissions don't allow access
+        rc_error         : std_ulogic;          -- Reference or change bit clear
 
         -- completion signal for tlbie
         tlbie_done       : std_ulogic;
@@ -262,6 +288,9 @@ architecture rtl of dcache is
     signal pte : tlb_pte_t;
     signal ra : std_ulogic_vector(REAL_ADDR_BITS - 1 downto 0);
     signal valid_ra : std_ulogic;
+    signal perm_attr : perm_attr_t;
+    signal rc_ok : std_ulogic;
+    signal perm_ok : std_ulogic;
 
     -- TLB PLRU output interface
     type tlb_plru_out_t is array(tlb_index_t) of std_ulogic_vector(TLB_WAY_BITS-1 downto 0);
@@ -490,8 +519,10 @@ begin
         if r0.virt_mode = '1' then
             ra <= pte(REAL_ADDR_BITS - 1 downto TLB_LG_PGSZ) &
                   r0.addr(TLB_LG_PGSZ - 1 downto 0);
+            perm_attr <= extract_perm_attr(pte);
         else
             ra <= r0.addr(REAL_ADDR_BITS - 1 downto 0);
+            perm_attr <= real_mode_perm_attr;
         end if;
     end process;
 
@@ -588,6 +619,7 @@ begin
        variable op      : op_t;
        variable opsel   : std_ulogic_vector(2 downto 0);
         variable go      : std_ulogic;
+        variable nc      : std_ulogic;
         variable s_hit   : std_ulogic;
         variable s_tag   : cache_tag_t;
         variable s_pte   : tlb_pte_t;
@@ -655,13 +687,20 @@ begin
        -- The way to replace on a miss
        replace_way <= to_integer(unsigned(plru_victim(req_index)));
 
-       -- Combine the request and cache his status to decide what
+        -- work out whether we have permission for this access
+        -- NB we don't yet implement AMR, thus no KUAP
+        rc_ok <= perm_attr.reference and (r0.load or perm_attr.changed);
+        perm_ok <= (r0.priv_mode or not perm_attr.priv) and
+                   (perm_attr.wr_perm or (r0.load and perm_attr.rd_perm));
+
+       -- Combine the request and cache hit status to decide what
        -- operation needs to be done
        --
+        nc := r0.nc or perm_attr.nocache;
         op := OP_NONE;
         if go = '1' then
-            if valid_ra = '1' then
-                opsel := r0.load & r0.nc & is_hit;
+            if valid_ra = '1' and rc_ok = '1' and perm_ok = '1' then
+                opsel := r0.load & nc & is_hit;
                 case opsel is
                     when "101" => op := OP_LOAD_HIT;
                     when "100" => op := OP_LOAD_MISS;
@@ -742,6 +781,8 @@ begin
         d_out.store_done <= '0';
         d_out.error <= '0';
         d_out.tlb_miss <= '0';
+        d_out.perm_error <= '0';
+        d_out.rc_error <= '0';
 
        -- We have a valid load or store hit or we just completed a slow
        -- op such as a load miss, a NC load or a store
@@ -772,6 +813,8 @@ begin
             report "completing ld/st with error";
             d_out.error <= '1';
             d_out.tlb_miss <= r1.tlb_miss;
+            d_out.perm_error <= r1.perm_error;
+            d_out.rc_error <= r1.rc_error;
             d_out.valid <= '1';
         end if;
 
@@ -918,8 +961,12 @@ begin
            end if;
 
             if req_op = OP_BAD then
+                report "Signalling ld/st error valid_ra=" & " rc_ok=" & std_ulogic'image(rc_ok) &
+                    " perm_ok=" & std_ulogic'image(perm_ok);
                 r1.error_done <= '1';
                 r1.tlb_miss <= not valid_ra;
+                r1.perm_error <= valid_ra and not perm_ok;
+                r1.rc_error <= valid_ra and perm_ok and not rc_ok;
             else
                 r1.error_done <= '0';
             end if;
index e2cb6510aef8ee81d941050ce935d164f993d473..5e25efccdffd470c1391bcb6e7f5be5b13366f7e 100644 (file)
@@ -1004,6 +1004,7 @@ begin
             lv.ci := '1';
         end if;
         lv.virt_mode := ctrl.msr(MSR_DR);
+        lv.priv_mode := not ctrl.msr(MSR_PR);
 
        -- Update registers
        rin <= v;
index 6ab18f5f693102245e36db3cbaba0eda981a8d34..c54e47b970ed73b2b858f1e53dfef3d0c4885e13 100644 (file)
@@ -60,6 +60,7 @@ architecture behave of loadstore1 is
         rc           : std_ulogic;
         nc           : std_ulogic;              -- non-cacheable access
         virt_mode    : std_ulogic;
+        priv_mode    : std_ulogic;
         state        : state_t;
         second_bytes : std_ulogic_vector(7 downto 0);
         dar          : std_ulogic_vector(63 downto 0);
@@ -266,6 +267,7 @@ begin
                 v.rc := l_in.rc;
                 v.nc := l_in.ci;
                 v.virt_mode := l_in.virt_mode;
+                v.priv_mode := l_in.priv_mode;
 
                 -- XXX Temporary hack. Mark the op as non-cachable if the address
                 -- is the form 0xc------- for a real-mode access.
@@ -323,6 +325,9 @@ begin
                     -- dcache will discard the second request
                     exception := '1';
                     dsisr(30) := d_in.tlb_miss;
+                    dsisr(63 - 36) := d_in.perm_error;
+                    dsisr(63 - 38) := not r.load;
+                    dsisr(63 - 45) := d_in.rc_error;
                     v.state := IDLE;
                 else
                     v.state := LAST_ACK_WAIT;
@@ -343,6 +348,9 @@ begin
                     end if;
                     exception := '1';
                     dsisr(30) := d_in.tlb_miss;
+                    dsisr(63 - 36) := d_in.perm_error;
+                    dsisr(63 - 38) := not r.load;
+                    dsisr(63 - 45) := d_in.rc_error;
                     v.state := IDLE;
                 else
                     write_enable := r.load;
@@ -376,6 +384,7 @@ begin
         d_out.data <= v.store_data;
         d_out.byte_sel <= byte_sel;
         d_out.virt_mode <= v.virt_mode;
+        d_out.priv_mode <= v.priv_mode;
 
         -- Update outputs to writeback
         -- Multiplex either cache data to the destination GPR or