The wgChecker is configurable for whether blocked accesses: * should cause a bus error or just read return zero and write ignore * should generate the interrupt or not
Signed-off-by: Jim Shu <jim....@sifive.com> --- hw/misc/riscv_wgchecker.c | 169 +++++++++++++++++++++++++++++++++++++- 1 file changed, 167 insertions(+), 2 deletions(-) diff --git a/hw/misc/riscv_wgchecker.c b/hw/misc/riscv_wgchecker.c index ab03fd671f..55e5e8127f 100644 --- a/hw/misc/riscv_wgchecker.c +++ b/hw/misc/riscv_wgchecker.c @@ -100,6 +100,169 @@ REG32(SLOT_CFG, 0x010) #define P_READ (1 << 0) #define P_WRITE (1 << 1) +static void decode_napot(hwaddr a, hwaddr *sa, hwaddr *ea) +{ + /* + * aaaa...aaa0 8-byte NAPOT range + * aaaa...aa01 16-byte NAPOT range + * aaaa...a011 32-byte NAPOT range + * ... + * aa01...1111 2^XLEN-byte NAPOT range + * a011...1111 2^(XLEN+1)-byte NAPOT range + * 0111...1111 2^(XLEN+2)-byte NAPOT range + * 1111...1111 Reserved + */ + + a = FROM_SLOT_ADDR(a) | 0x3; + + if (sa) { + *sa = a & (a + 1); + } + if (ea) { + *ea = a | (a + 1); + } +} + +typedef struct WgAccessResult WgAccessResult; +struct WgAccessResult { + bool is_success; + bool has_bus_error; + bool has_interrupt; + uint8_t perm:2; +}; + +static WgAccessResult wgc_check_access( + RISCVWgCheckerState *s, hwaddr phys_addr, uint32_t wid, bool is_write) +{ + WgCheckerSlot *slot, *prev_slot; + uint32_t cfg_a, prev_cfg_a; + uint64_t start, end; + int slot_id, wgc_perm = 0; + WgAccessResult result = { 0 }; + + bool is_matching = false; + bool slot0_be, slot0_ip; + bool matched_slot_be = false, matched_slot_ip = false; + + for (slot_id = 0; slot_id < s->slot_count; slot_id++) { + slot = &s->slots[slot_id + 1]; + cfg_a = FIELD_EX32(slot->cfg, SLOT_CFG, A); + + if (cfg_a == A_TOR) { + prev_slot = &s->slots[slot_id]; + + prev_cfg_a = FIELD_EX32(prev_slot->cfg, SLOT_CFG, A); + if (prev_cfg_a == A_NA4) { + start = FROM_SLOT_ADDR(prev_slot->addr) + 4; + } else if (prev_cfg_a == A_NAPOT) { + decode_napot(prev_slot->addr, NULL, &start); + start += 1; + } else { /* A_TOR or A_OFF */ + start = FROM_SLOT_ADDR(prev_slot->addr); + } + end = FROM_SLOT_ADDR(slot->addr); + } else if (cfg_a == A_NA4) { + start = FROM_SLOT_ADDR(slot->addr); + end = start + 4; + } else if (cfg_a == A_NAPOT) { + decode_napot(slot->addr, &start, &end); + end += 1; + } else { + /* A_OFF: not in slot range. */ + continue; + } + + /* wgChecker slot range is between start to (end - 1). */ + if ((start <= phys_addr) && (phys_addr < end)) { + /* Match the wgC slot */ + int perm = ((slot->perm >> (wid * 2)) & 0x3); + + /* If any matching rule permits access, the access is permitted. */ + wgc_perm |= perm; + + /* + * If any matching rule wants to report error (IRQ or Bus Error), + * the denied access should report error. + */ + is_matching = true; + if (is_write) { + matched_slot_be |= FIELD_EX64(slot->cfg, SLOT_CFG, EW); + matched_slot_ip |= FIELD_EX64(slot->cfg, SLOT_CFG, IW); + } else { + matched_slot_be |= FIELD_EX64(slot->cfg, SLOT_CFG, ER); + matched_slot_ip |= FIELD_EX64(slot->cfg, SLOT_CFG, IR); + } + } + } + + /* If no matching rule, error reporting depends on the slot0's config. */ + if (is_write) { + slot0_be = FIELD_EX64(s->slots[0].cfg, SLOT_CFG, EW); + slot0_ip = FIELD_EX64(s->slots[0].cfg, SLOT_CFG, IW); + } else { + slot0_be = FIELD_EX64(s->slots[0].cfg, SLOT_CFG, ER); + slot0_ip = FIELD_EX64(s->slots[0].cfg, SLOT_CFG, IR); + } + + result.is_success = is_write ? (wgc_perm & P_WRITE) : (wgc_perm & P_READ); + result.perm = wgc_perm; + if (!result.is_success) { + if (is_matching) { + result.has_bus_error = matched_slot_be; + result.has_interrupt = matched_slot_ip; + } else { + result.has_bus_error = slot0_be; + result.has_interrupt = slot0_ip; + } + } + + return result; +} + +static MemTxResult riscv_wgc_handle_blocked_access( + WgCheckerRegion *region, hwaddr addr, uint32_t wid, bool is_write) +{ + RISCVWgCheckerState *s = RISCV_WGCHECKER(region->wgchecker); + bool be, ip; + WgAccessResult result; + hwaddr phys_addr; + + be = FIELD_EX64(s->errcause, ERRCAUSE, BE); + ip = FIELD_EX64(s->errcause, ERRCAUSE, IP); + phys_addr = addr + region->region_offset; + + /* + * Check if this blocked access trigger IRQ (Bus Error) or not. + * It depends on wgChecker slot config (cfg.IR/IW/ER/EW bits). + */ + result = wgc_check_access(s, phys_addr, wid, is_write); + + if (!be && !ip) { + /* + * With either of the be or ip bits is set, further violations do not + * update the errcause or erraddr registers. Also, new interrupts + * cannot be generated until the be and ip fields are cleared. + */ + if (result.has_interrupt || result.has_bus_error) { + s->errcause = FIELD_DP64(s->errcause, ERRCAUSE, WID, wid); + s->errcause = FIELD_DP64(s->errcause, ERRCAUSE, R, !is_write); + s->errcause = FIELD_DP64(s->errcause, ERRCAUSE, W, is_write); + s->erraddr = TO_SLOT_ADDR(phys_addr); + } + + if (result.has_interrupt) { + s->errcause = FIELD_DP64(s->errcause, ERRCAUSE, IP, 1); + qemu_irq_raise(s->irq); + } + + if (result.has_bus_error) { + s->errcause = FIELD_DP64(s->errcause, ERRCAUSE, BE, 1); + } + } + + return result.has_bus_error ? MEMTX_ERROR : MEMTX_OK; +} + /* * Accesses only reach these read and write functions if the wgChecker * is blocking them; non-blocked accesses go directly to the downstream @@ -109,23 +272,25 @@ static MemTxResult riscv_wgc_mem_blocked_read(void *opaque, hwaddr addr, uint64_t *pdata, unsigned size, MemTxAttrs attrs) { + WgCheckerRegion *region = opaque; uint32_t wid = mem_attrs_to_wid(attrs); trace_riscv_wgc_mem_blocked_read(addr, size, wid); *pdata = 0; - return MEMTX_OK; + return riscv_wgc_handle_blocked_access(region, addr, wid, false); } static MemTxResult riscv_wgc_mem_blocked_write(void *opaque, hwaddr addr, uint64_t value, unsigned size, MemTxAttrs attrs) { + WgCheckerRegion *region = opaque; uint32_t wid = mem_attrs_to_wid(attrs); trace_riscv_wgc_mem_blocked_write(addr, value, size, wid); - return MEMTX_OK; + return riscv_wgc_handle_blocked_access(region, addr, wid, true); } static const MemoryRegionOps riscv_wgc_mem_blocked_ops = { -- 2.17.1