diff --git a/rtl/ibex_core.sv b/rtl/ibex_core.sv index 0f43faef..aafc06fb 100644 --- a/rtl/ibex_core.sv +++ b/rtl/ibex_core.sv @@ -268,11 +268,12 @@ module ibex_core #( logic [31:0] csr_mepc, csr_depc; // PMP signals - logic [33:0] csr_pmp_addr [PMPNumRegions]; - pmp_cfg_t csr_pmp_cfg [PMPNumRegions]; - logic pmp_req_err [PMP_NUM_CHAN]; - logic instr_req_out; - logic data_req_out; + logic [33:0] csr_pmp_addr [PMPNumRegions]; + pmp_cfg_t csr_pmp_cfg [PMPNumRegions]; + pmp_mseccfg_t csr_pmp_mseccfg; + logic pmp_req_err [PMP_NUM_CHAN]; + logic instr_req_out; + logic data_req_out; logic csr_save_if; logic csr_save_id; @@ -1017,6 +1018,7 @@ module ibex_core #( // PMP .csr_pmp_cfg_o ( csr_pmp_cfg ), .csr_pmp_addr_o ( csr_pmp_addr ), + .csr_pmp_mseccfg_o ( csr_pmp_mseccfg ), // debug .csr_depc_o ( csr_depc ), @@ -1086,30 +1088,33 @@ module ibex_core #( assign pmp_priv_lvl[PMP_D] = priv_mode_lsu; ibex_pmp #( - .PMPGranularity ( PMPGranularity ), - .PMPNumChan ( PMP_NUM_CHAN ), - .PMPNumRegions ( PMPNumRegions ) + .PMPGranularity ( PMPGranularity ), + .PMPNumChan ( PMP_NUM_CHAN ), + .PMPNumRegions ( PMPNumRegions ) ) pmp_i ( - .clk_i ( clk ), - .rst_ni ( rst_ni ), + .clk_i ( clk ), + .rst_ni ( rst_ni ), // Interface to CSRs - .csr_pmp_cfg_i ( csr_pmp_cfg ), - .csr_pmp_addr_i ( csr_pmp_addr ), - .priv_mode_i ( pmp_priv_lvl ), + .csr_pmp_cfg_i ( csr_pmp_cfg ), + .csr_pmp_addr_i ( csr_pmp_addr ), + .csr_pmp_mseccfg_i ( csr_pmp_mseccfg ), + .priv_mode_i ( pmp_priv_lvl ), // Access checking channels - .pmp_req_addr_i ( pmp_req_addr ), - .pmp_req_type_i ( pmp_req_type ), - .pmp_req_err_o ( pmp_req_err ) + .pmp_req_addr_i ( pmp_req_addr ), + .pmp_req_type_i ( pmp_req_type ), + .pmp_req_err_o ( pmp_req_err ) ); end else begin : g_no_pmp // Unused signal tieoff priv_lvl_e unused_priv_lvl_if, unused_priv_lvl_ls; logic [33:0] unused_csr_pmp_addr [PMPNumRegions]; pmp_cfg_t unused_csr_pmp_cfg [PMPNumRegions]; + pmp_mseccfg_t unused_csr_pmp_mseccfg; assign unused_priv_lvl_if = priv_mode_if; assign unused_priv_lvl_ls = priv_mode_lsu; assign unused_csr_pmp_addr = csr_pmp_addr; assign unused_csr_pmp_cfg = csr_pmp_cfg; + assign unused_csr_pmp_mseccfg = csr_pmp_mseccfg; // Output tieoff assign pmp_req_err[PMP_I] = 1'b0; diff --git a/rtl/ibex_cs_registers.sv b/rtl/ibex_cs_registers.sv index 1b9efc0c..738d3620 100644 --- a/rtl/ibex_cs_registers.sv +++ b/rtl/ibex_cs_registers.sv @@ -66,8 +66,9 @@ module ibex_cs_registers #( output logic [31:0] csr_mepc_o, // PMP - output ibex_pkg::pmp_cfg_t csr_pmp_cfg_o [PMPNumRegions], - output logic [33:0] csr_pmp_addr_o [PMPNumRegions], + output ibex_pkg::pmp_cfg_t csr_pmp_cfg_o [PMPNumRegions], + output logic [33:0] csr_pmp_addr_o [PMPNumRegions], + output ibex_pkg::pmp_mseccfg_t csr_pmp_mseccfg_o, // debug input logic debug_mode_i, @@ -220,6 +221,7 @@ module ibex_cs_registers #( logic [31:0] pmp_addr_rdata [PMP_MAX_REGIONS]; logic [PMP_CFG_W-1:0] pmp_cfg_rdata [PMP_MAX_REGIONS]; logic pmp_csr_err; + pmp_mseccfg_t pmp_mseccfg; // Hardware performance monitor signals logic [31:0] mcountinhibit; @@ -340,6 +342,13 @@ module ibex_cs_registers #( csr_rdata_int[CSR_MFIX_BIT_HIGH:CSR_MFIX_BIT_LOW] = mip.irq_fast; end + CSR_MSECCFG: begin + csr_rdata_int = '0; + csr_rdata_int[CSR_MSECCFG_MML_BIT] = pmp_mseccfg.mml; + csr_rdata_int[CSR_MSECCFG_MMWP_BIT] = pmp_mseccfg.mmwp; + csr_rdata_int[CSR_MSECCFG_RLB_BIT] = pmp_mseccfg.rlb; + end + // PMP registers CSR_PMPCFG0: csr_rdata_int = {pmp_cfg_rdata[3], pmp_cfg_rdata[2], pmp_cfg_rdata[1], pmp_cfg_rdata[0]}; @@ -956,13 +965,18 @@ module ibex_cs_registers #( // ----------------- if (PMPEnable) begin : g_pmp_registers + pmp_mseccfg_t pmp_mseccfg_q, pmp_mseccfg_d; + logic pmp_mseccfg_we; + logic pmp_mseccfg_err; pmp_cfg_t pmp_cfg [PMPNumRegions]; + logic [PMPNumRegions-1:0] pmp_cfg_locked; pmp_cfg_t pmp_cfg_wdata [PMPNumRegions]; logic [PMPAddrWidth-1:0] pmp_addr [PMPNumRegions]; logic [PMPNumRegions-1:0] pmp_cfg_we; logic [PMPNumRegions-1:0] pmp_cfg_err; logic [PMPNumRegions-1:0] pmp_addr_we; logic [PMPNumRegions-1:0] pmp_addr_err; + logic any_pmp_entry_locked; // Expanded / qualified register read data for (genvar i = 0; i < PMP_MAX_REGIONS; i++) begin : g_exp_rd_data @@ -1011,7 +1025,7 @@ module ibex_cs_registers #( // ------------------------- // Instantiate cfg registers // ------------------------- - assign pmp_cfg_we[i] = csr_we_int & ~pmp_cfg[i].lock & + assign pmp_cfg_we[i] = csr_we_int & ~pmp_cfg_locked[i] & (csr_addr == (CSR_OFF_PMP_CFG + (i[11:0] >> 2))); // Select the correct WDATA (each CSR contains 4 CFG fields, each with 2 RES bits) @@ -1028,8 +1042,10 @@ module ibex_cs_registers #( endcase end assign pmp_cfg_wdata[i].exec = csr_wdata_int[(i%4)*PMP_CFG_W+2]; - // W = 1, R = 0 is a reserved combination. For now, we force W to 0 if R == 0 - assign pmp_cfg_wdata[i].write = &csr_wdata_int[(i%4)*PMP_CFG_W+:2]; + // When MSECCFG.MML is unset, W = 1, R = 0 is a reserved combination, so force W to 0 if R == + // 0. Otherwise allow all possible values to be written. + assign pmp_cfg_wdata[i].write = pmp_mseccfg_q.mml ? csr_wdata_int[(i%4)*PMP_CFG_W+1] : + &csr_wdata_int[(i%4)*PMP_CFG_W+:2]; assign pmp_cfg_wdata[i].read = csr_wdata_int[(i%4)*PMP_CFG_W]; ibex_csr #( @@ -1045,15 +1061,19 @@ module ibex_cs_registers #( .rd_error_o (pmp_cfg_err[i]) ); + // MSECCFG.RLB allows the lock bit to be bypassed (allowing cfg writes when MSECCFG.RLB is + // set). + assign pmp_cfg_locked[i] = pmp_cfg[i].lock & ~pmp_mseccfg_q.rlb; + // -------------------------- // Instantiate addr registers // -------------------------- if (i < PMPNumRegions - 1) begin : g_lower - assign pmp_addr_we[i] = csr_we_int & ~pmp_cfg[i].lock & - (~pmp_cfg[i+1].lock | (pmp_cfg[i+1].mode != PMP_MODE_TOR)) & + assign pmp_addr_we[i] = csr_we_int & ~pmp_cfg_locked[i] & + (~pmp_cfg_locked[i+1] | (pmp_cfg[i+1].mode != PMP_MODE_TOR)) & (csr_addr == (CSR_OFF_PMP_ADDR + i[11:0])); end else begin : g_upper - assign pmp_addr_we[i] = csr_we_int & ~pmp_cfg[i].lock & + assign pmp_addr_we[i] = csr_we_int & ~pmp_cfg_locked[i] & (csr_addr == (CSR_OFF_PMP_ADDR + i[11:0])); end @@ -1074,7 +1094,35 @@ module ibex_cs_registers #( assign csr_pmp_addr_o[i] = {pmp_addr_rdata[i], 2'b00}; end - assign pmp_csr_err = (|pmp_cfg_err) | (|pmp_addr_err); + assign pmp_mseccfg_we = csr_we_int & (csr_addr == CSR_MSECCFG); + + // MSECCFG.MML/MSECCFG.MMWP cannot be unset once set + assign pmp_mseccfg_d.mml = pmp_mseccfg_q.mml ? 1'b1 : csr_wdata_int[CSR_MSECCFG_MML_BIT]; + assign pmp_mseccfg_d.mmwp = pmp_mseccfg_q.mmwp ? 1'b1 : csr_wdata_int[CSR_MSECCFG_MMWP_BIT]; + + // pmp_cfg_locked factors in MSECCFG.RLB so any_pmp_entry_locked will only be set if MSECCFG.RLB + // is unset + assign any_pmp_entry_locked = |pmp_cfg_locked; + + // When any PMP entry is locked (A PMP entry has the L bit set and MSECCFG.RLB is unset), + // MSECCFG.RLB cannot be set again + assign pmp_mseccfg_d.rlb = any_pmp_entry_locked ? 1'b0 : csr_wdata_int[CSR_MSECCFG_RLB_BIT]; + + ibex_csr #( + .Width ($bits(pmp_mseccfg_t)), + .ShadowCopy (ShadowCSR), + .ResetValue ('0) + ) u_pmp_mseccfg ( + .clk_i (clk_i), + .rst_ni (rst_ni), + .wr_data_i (pmp_mseccfg_d), + .wr_en_i (pmp_mseccfg_we), + .rd_data_o (pmp_mseccfg_q), + .rd_error_o (pmp_mseccfg_err) + ); + + assign pmp_csr_err = (|pmp_cfg_err) | (|pmp_addr_err) | pmp_mseccfg_err; + assign pmp_mseccfg = pmp_mseccfg_q; end else begin : g_no_pmp_tieoffs // Generate tieoffs when PMP is not configured @@ -1087,8 +1135,11 @@ module ibex_cs_registers #( assign csr_pmp_addr_o[i] = '0; end assign pmp_csr_err = 1'b0; + assign pmp_mseccfg = '0; end + assign csr_pmp_mseccfg_o = pmp_mseccfg; + ////////////////////////// // Performance monitor // ////////////////////////// diff --git a/rtl/ibex_pkg.sv b/rtl/ibex_pkg.sv index 42ac4863..28171d30 100644 --- a/rtl/ibex_pkg.sv +++ b/rtl/ibex_pkg.sv @@ -330,6 +330,13 @@ typedef struct packed { logic read; } pmp_cfg_t; +// Machine Security Configuration (ePMP) +typedef struct packed { + logic rlb; // Rule Locking Bypass + logic mmwp; // Machine Mode Whitelist Policy + logic mml; // Machine Mode Lockdown +} pmp_mseccfg_t; + // CSRs typedef enum logic[11:0] { // Machine information @@ -348,6 +355,8 @@ typedef enum logic[11:0] { CSR_MTVAL = 12'h343, CSR_MIP = 12'h344, + CSR_MSECCFG = 12'h390, + // Physical memory protection CSR_PMPCFG0 = 12'h3A0, CSR_PMPCFG1 = 12'h3A1, @@ -505,4 +514,9 @@ parameter int unsigned CSR_MEIX_BIT = 11; parameter int unsigned CSR_MFIX_BIT_LOW = 16; parameter int unsigned CSR_MFIX_BIT_HIGH = 30; +// CSR Machine Security Configuration bits +parameter int unsigned CSR_MSECCFG_MML_BIT = 0; +parameter int unsigned CSR_MSECCFG_MMWP_BIT = 1; +parameter int unsigned CSR_MSECCFG_RLB_BIT = 2; + endpackage diff --git a/rtl/ibex_pmp.sv b/rtl/ibex_pmp.sv index 3c6bd37b..3d110f65 100644 --- a/rtl/ibex_pmp.sv +++ b/rtl/ibex_pmp.sv @@ -16,8 +16,9 @@ module ibex_pmp #( input logic rst_ni, // Interface to CSRs - input ibex_pkg::pmp_cfg_t csr_pmp_cfg_i [PMPNumRegions], - input logic [33:0] csr_pmp_addr_i [PMPNumRegions], + input ibex_pkg::pmp_cfg_t csr_pmp_cfg_i [PMPNumRegions], + input logic [33:0] csr_pmp_addr_i [PMPNumRegions], + input ibex_pkg::pmp_mseccfg_t csr_pmp_mseccfg_i, input ibex_pkg::priv_lvl_e priv_mode_i [PMPNumChan], // Access checking channels @@ -36,7 +37,8 @@ module ibex_pmp #( logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_match_lt; logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_match_eq; logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_match_all; - logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_perm_check; + logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_basic_perm_check; + logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_mml_perm_check; logic [PMPNumChan-1:0] access_fault; @@ -102,27 +104,72 @@ module ibex_pmp #( end // Check specific required permissions - assign region_perm_check[c][r] = + assign region_basic_perm_check[c][r] = ((pmp_req_type_i[c] == PMP_ACC_EXEC) & csr_pmp_cfg_i[r].exec) | ((pmp_req_type_i[c] == PMP_ACC_WRITE) & csr_pmp_cfg_i[r].write) | ((pmp_req_type_i[c] == PMP_ACC_READ) & csr_pmp_cfg_i[r].read); + + + // Compute permission checks that apply when MSECCFG.MML is set. + always_comb begin + region_mml_perm_check[c][r] = 1'b0; + + if (!csr_pmp_cfg_i[r].read && csr_pmp_cfg_i[r].write) begin + // Special-case shared regions where R = 0, W = 1 + unique case ({csr_pmp_cfg_i[r].lock, csr_pmp_cfg_i[r].exec}) + // Read/write in M, read only in S/U + 2'b00: region_mml_perm_check[c][r] = + (pmp_req_type_i[c] == PMP_ACC_READ) | + ((pmp_req_type_i[c] == PMP_ACC_WRITE) & (priv_mode_i[c] == PRIV_LVL_M)); + // Read/write in M/S/U + 2'b01: region_mml_perm_check[c][r] = + (pmp_req_type_i[c] == PMP_ACC_READ) | (pmp_req_type_i[c] == PMP_ACC_WRITE); + // Execute only on M/S/U + 2'b10: region_mml_perm_check[c][r] = (pmp_req_type_i[c] == PMP_ACC_EXEC); + // Read/execute in M, execute only on S/U + 2'b11: region_mml_perm_check[c][r] = + (pmp_req_type_i[c] == PMP_ACC_EXEC) | + ((pmp_req_type_i[c] == PMP_ACC_READ) & (priv_mode_i[c] == PRIV_LVL_M)); + default: ; + endcase + end else begin + if (csr_pmp_cfg_i[r].read & csr_pmp_cfg_i[r].write & csr_pmp_cfg_i[r].exec + & csr_pmp_cfg_i[r].lock) begin + // Special-case shared read only region when R = 1, W = 1, X = 1, L = 1 + region_mml_perm_check[c][r] = pmp_req_type_i[c] == PMP_ACC_READ; + end else begin + // Otherwise use basic permission check. Permission is always denied if in S/U mode and + // L is set or if in M mode and L is unset. + region_mml_perm_check[c][r] = + priv_mode_i[c] == PRIV_LVL_M ? csr_pmp_cfg_i[r].lock & region_basic_perm_check[c][r] : + ~csr_pmp_cfg_i[r].lock & region_basic_perm_check[c][r]; + end + end + end end // Access fault determination / prioritization always_comb begin - // Default is allow for M-mode, deny for other modes - access_fault[c] = (priv_mode_i[c] != PRIV_LVL_M); + // When MSECCFG.MMWP is set default deny always, otherwise allow for M-mode, deny for other + // modes + access_fault[c] = csr_pmp_mseccfg_i.mmwp | (priv_mode_i[c] != PRIV_LVL_M); // PMP entries are statically prioritized, from 0 to N-1 // The lowest-numbered PMP entry which matches an address determines accessability for (int r = PMPNumRegions-1; r >= 0; r--) begin if (region_match_all[c][r]) begin - access_fault[c] = (priv_mode_i[c] == PRIV_LVL_M) ? - // For M-mode, any region which matches with the L-bit clear, or with sufficient - // access permissions will be allowed - (csr_pmp_cfg_i[r].lock & ~region_perm_check[c][r]) : - // For other modes, the lock bit doesn't matter - ~region_perm_check[c][r]; + if (csr_pmp_mseccfg_i.mml) begin + // When MSECCFG.MML is set use MML specific permission check + access_fault[c] = ~region_mml_perm_check[c][r]; + end else begin + // Otherwise use original PMP behaviour + access_fault[c] = (priv_mode_i[c] == PRIV_LVL_M) ? + // For M-mode, any region which matches with the L-bit clear, or with sufficient + // access permissions will be allowed + (csr_pmp_cfg_i[r].lock & ~region_basic_perm_check[c][r]) : + // For other modes, the lock bit doesn't matter + ~region_basic_perm_check[c][r]; + end end end end @@ -130,4 +177,8 @@ module ibex_pmp #( assign pmp_req_err_o[c] = access_fault[c]; end + // RLB, rule locking bypass, is only relevant to ibex_cs_registers which controls writes to the + // PMP CSRs. Tie to unused signal here to prevent lint warnings. + logic unused_csr_pmp_mseccfg_rlb; + assign unused_csr_pmp_mseccfg_rlb = csr_pmp_mseccfg_i.rlb; endmodule