From d47fbf88d14f2ca90d6b37378d698b9133e66631 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Mon, 20 Apr 2020 12:43:06 +1000 Subject: [PATCH] Implement access permission checks 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 --- common.vhdl | 8 +++++-- dcache.vhdl | 55 +++++++++++++++++++++++++++++++++++++++++++++---- execute1.vhdl | 1 + loadstore1.vhdl | 9 ++++++++ 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/common.vhdl b/common.vhdl index 59b3744..e8ec19e 100644 --- a/common.vhdl +++ b/common.vhdl @@ -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 diff --git a/dcache.vhdl b/dcache.vhdl index 7895877..03b3886 100644 --- a/dcache.vhdl +++ b/dcache.vhdl @@ -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; diff --git a/execute1.vhdl b/execute1.vhdl index e2cb651..5e25efc 100644 --- a/execute1.vhdl +++ b/execute1.vhdl @@ -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; diff --git a/loadstore1.vhdl b/loadstore1.vhdl index 6ab18f5..c54e47b 100644 --- a/loadstore1.vhdl +++ b/loadstore1.vhdl @@ -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