@@ -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 = {
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.shu@sifive.com> --- hw/misc/riscv_wgchecker.c | 169 +++++++++++++++++++++++++++++++++++++- 1 file changed, 167 insertions(+), 2 deletions(-)