[rtl] Add ePMP support to Ibex

This implemements the RISC-V Trusted Execution Environment (TEE) working
group proposal 'PMP Enhancements for memory access and execution
prevention on Machine mode'. The proposal is awaiting ratification and
is not expected to change beyond minor tweaks before it becomes part of
the RISC-V priviledged specification.

No seperate 'classic' PMP only mode is provided as different PMP
behaviour only occurs when the MSECCFG CSR is written to. This CSR is
introduced by the proposal and has no specified function in the current
RISC-V priviledged specification.
This commit is contained in:
Greg Chadwick 2021-01-11 16:38:24 +00:00
parent 373212ee89
commit 374e05ec65
4 changed files with 158 additions and 37 deletions

View file

@ -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;

View file

@ -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 //
//////////////////////////

View file

@ -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

View file

@ -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