[PATCH 10/15] arc: Add memory protection unit (MPU) support

cupertinomiranda
2020-11-11T16:18:38Z

From: Shahab Vahedi <shahab@synopsys.com>

Add memory implementation for Synopsys MPU unit version 3.
Synopsys MPU allows to create memory regions against unauthorized
execution/read/writes accesses.

Signed-off-by: Shahab Vahedi <shahab@synopsys.com>
---
target/arc/mpu.c | 656 +++++++++++++++++++++++++++++++++++++++++++++++
target/arc/mpu.h | 142 ++++++++++
2 files changed, 798 insertions(+)
create mode 100644 target/arc/mpu.c
create mode 100644 target/arc/mpu.h

diff --git a/target/arc/mpu.c b/target/arc/mpu.c
new file mode 100644
index 0000000000..2d04f9c43e
--- /dev/null
+++ b/target/arc/mpu.c
@@ -0,0 +1,656 @@
+/*
+ * QEMU ARC CPU
+ *
+ * Copyright (c) 2020 Synppsys Inc.
+ * Contributed by Shahab Vahedi (Synopsys)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * http://www.gnu.org/licenses/lgpl-2.1.html
+ */
+
+#include "qemu/osdep.h"
+#include "mpu.h"
+#include "cpu.h"
+#include "exec/exec-all.h"
+#include "mmu.h"
+
+/*
+ * In case of exception, this signals the effective region
+ * was the default one
+ */
+#define MPU_DEFAULT_REGION_NR 0xff
+
+/* Defines used by in-house functions */
+#define MPU_EN_EN_BIT 30
+#define MPU_EN_KR_BIT 8
+#define MPU_EN_KW_BIT 7
+#define MPU_EN_KE_BIT 6
+#define MPU_EN_UR_BIT 5
+#define MPU_EN_UW_BIT 4
+#define MPU_EN_UE_BIT 3
+
+#define MPU_ECR_EC_CODE_BIT 16
+#define MPU_ECR_VT_BIT 8
+
+#define MPU_BASE_ADDR_MASK 0xffffffe0 /* ignore least 5 bits */
+#define MPU_BASE_VALID_MASK 0x00000001 /* bit #0 */
+
+/*
+ * Given a number of bits as width, calc the mask to
+ * "and" with. e.g.: 3 bits --> 8 - 1 --> 7 (111b)
+ */
+#define MPU_WIDTH_TO_MASK(w) ((1 << (w)) - 1)
+#define MPU_PERMS_REG_LOWER_SIZE_WIDTH 2
+#define MPU_PERMS_REG_HIGHER_SIZE_WIDTH 3
+#define MPU_PERMS_REG_HIGHER_SIZE_POS 9
+
+/*
+ * After knowing the operating mode (user/kernel),
+ * this struct represents the effective permissions.
+ */
+typedef struct MPUEffectPerm {
+ bool read;
+ bool write;
+ bool exec;
+} MPUEffectPerm;
+
+/* Packer and unpackers (local to this translation unit) */
+static inline uint32_t pack_enable(const bool ena)
+{
+ return ena << MPU_EN_EN_BIT;
+}
+
+static inline void unpack_enable(bool *enabled, uint32_t value)
+{
+ *enabled = (value >> MPU_EN_EN_BIT) & 1;
+}
+
+static inline uint32_t pack_permissions(const MPUPermissions *perms)
+{
+ return perms->KR << MPU_EN_KR_BIT |
+ perms->KW << MPU_EN_KW_BIT |
+ perms->KE << MPU_EN_KE_BIT |
+ perms->UR << MPU_EN_UR_BIT |
+ perms->UW << MPU_EN_UW_BIT |
+ perms->UE << MPU_EN_UE_BIT;
+}
+
+static inline void unpack_permissions(MPUPermissions *perms, uint32_t value)
+{
+ perms->KR = (value >> MPU_EN_KR_BIT) & 1;
+ perms->KW = (value >> MPU_EN_KW_BIT) & 1;
+ perms->KE = (value >> MPU_EN_KE_BIT) & 1;
+ perms->UR = (value >> MPU_EN_UR_BIT) & 1;
+ perms->UW = (value >> MPU_EN_UW_BIT) & 1;
+ perms->UE = (value >> MPU_EN_UE_BIT) & 1;
+}
+
+static inline uint32_t pack_enable_reg(const MPUEnableReg *mpuen)
+{
+ return pack_enable(mpuen->enabled) |
+ pack_permissions(&mpuen->permission);
+}
+
+static inline void unpack_enable_reg(MPUEnableReg *mpuen, uint32_t value)
+{
+ unpack_enable(&mpuen->enabled, value);
+ unpack_permissions(&mpuen->permission, value);
+}
+
+static inline uint32_t pack_ecr(const MPUECR *mpuecr)
+{
+ return ARC_MPU_ECR_VEC_NUM << MPU_ECR_EC_CODE_BIT |
+ (mpuecr->violation & 3) << MPU_ECR_VT_BIT |
+ mpuecr->region;
+}
+
+static inline uint32_t pack_base_reg(const MPUBaseReg *mpurdb)
+{
+ return mpurdb->addr | mpurdb->valid;
+}
+
+static inline void unpack_base_reg(MPUBaseReg *mpurdb, uint32_t value)
+{
+ mpurdb->addr = value & MPU_BASE_ADDR_MASK;
+ mpurdb->valid = value & MPU_BASE_VALID_MASK;
+}
+
+
+/*
+ * Break the "size" field into "higher" and "lower" parts
+ * e.g.: a b c d e --> a b c . . . d e
+ * higher lower
+ */
+static uint32_t pack_region_size_bits(uint8_t size_bits)
+{
+ uint32_t lower =
+ size_bits & MPU_WIDTH_TO_MASK(MPU_PERMS_REG_LOWER_SIZE_WIDTH);
+ uint32_t higher = size_bits >> MPU_PERMS_REG_LOWER_SIZE_WIDTH;
+ higher &= MPU_WIDTH_TO_MASK(MPU_PERMS_REG_HIGHER_SIZE_WIDTH);
+ return (higher << MPU_PERMS_REG_HIGHER_SIZE_POS) | lower;
+}
+
+/*
+ * Put the higher and lower parts of "size" field together
+ * e.g.: a b c . . . d e ---> abcde
+ * higher lower
+ */
+static void unpack_region_size_bits(uint8_t *size_bits, uint32_t value)
+{
+ uint8_t lower =
+ value & MPU_WIDTH_TO_MASK(MPU_PERMS_REG_LOWER_SIZE_WIDTH);
+ uint8_t higher = value >> MPU_PERMS_REG_HIGHER_SIZE_POS;
+ higher &= MPU_WIDTH_TO_MASK(MPU_PERMS_REG_HIGHER_SIZE_WIDTH);
+ *size_bits = (higher << MPU_PERMS_REG_LOWER_SIZE_WIDTH) | lower;
+}
+
+static void set_region_mask(uint32_t *mask, uint8_t size_bits)
+{
+ uint32_t region_offset_mask = 0;
+ /*
+ * size_bits: 00100b (4) --> 32 bytes --> least 5 bits are 0
+ * size_bits: 00101b (5) --> 64 bytes --> least 6 bits are 0
+ * ...
+ * size_bits: 11111b (31) --> 4 gb --> least 32 bits are 0
+ */
+ if (size_bits >= 4 && size_bits < 31) {
+ region_offset_mask = (2 << size_bits) - 1;
+ } else if (size_bits == 31) {
+ region_offset_mask = 0xffffffff;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "[MPU] %hu as size of a region is "
+ "undefined behaviour.\n", size_bits);
+ }
+ *mask = ~region_offset_mask;
+}
+
+static inline uint32_t pack_perm_reg(const MPUPermReg *mpurdp)
+{
+ return pack_region_size_bits(mpurdp->size_bits) |
+ pack_permissions(&mpurdp->permission);
+}
+
+static void unpack_perm_reg(MPUPermReg *mpurdp, uint32_t value)
+{
+ unpack_region_size_bits(&mpurdp->size_bits, value);
+ /* size_bits of below 4 are undefined --> Assuming min region size. */
+ mpurdp->size = (mpurdp->size_bits < 4) ? 32 : (2ul << mpurdp->size_bits);
+ unpack_permissions(&mpurdp->permission, value);
+ /* The mask is a facilitator to find the corresponding region easier */
+ set_region_mask(&mpurdp->mask, mpurdp->size_bits);
+}
+
+
+/* Extern function: To be called at reset() */
+void arc_mpu_init(struct ARCCPU *cpu)
+{
+ static const MPUPermissions INITIAL_PERMS = {0};
+ ARCMPU *mpu = &cpu->env.mpu;
+ size_t idx = 0;
+
+ /* Maybe the version must be determinded also based on CPU type */
+ mpu->reg_bcr.version = cpu->cfg.has_mpu ? ARC_MPU_VERSION : 0;
+ mpu->reg_bcr.regions = cpu->cfg.has_mpu ? cpu->cfg.mpu_num_regions : 0;
+ switch (mpu->reg_bcr.regions) {
+ case 0 ... 2:
+ case 4:
+ case 8:
+ case 16:
+ break;
+ default:
+ assert(!"Invalid number of MPU regions.");
+ }
+
+ /*
+ * We use this flag to determine if MPU is in motion or not.
+ * This is most of the time the same as reg_enable.enabled,
+ * However, in case of a double exception (Machine Check)
+ * this becomes false while reg_enable.enabled holds its
+ * value. As a result, there is no MPU anymore after a
+ * Machine Check is raised.
+ */
+ mpu->enabled = false;
+
+ mpu->reg_enable.enabled = false;
+ mpu->reg_enable.permission = INITIAL_PERMS;
+
+ mpu->reg_ecr.region = 0;
+ mpu->reg_ecr.violation = 0;
+ mpu->exception.number = ARC_MPU_ECR_VEC_NUM;
+ mpu->exception.code = 0;
+ mpu->exception.param = ARC_MPU_ECR_PARAM;
+
+ for (idx = 0; idx < ARC_MPU_MAX_NR_REGIONS; ++idx) {
+ mpu->reg_base[idx].valid = false;
+ mpu->reg_base[idx].addr = 0;
+
+ mpu->reg_perm[idx].size_bits = 0;
+ mpu->reg_perm[idx].mask = 0xffffffff;
+ mpu->reg_perm[idx].permission = INITIAL_PERMS;
+ }
+}
+
+/* Checking the sanity of situation before accessing MPU registers */
+static void validate_mpu_regs_access(CPUARCState *env)
+{
+ /* MPU registers are only accessible in kernel mode */
+ if (is_user_mode(env)) {
+ arc_raise_exception(env, EXCP_PRIVILEGEV);
+ }
+ /* No MPU, no getting any */
+ else if ((env_archcpu(env))->cfg.has_mpu == false) {
+ arc_raise_exception(env, EXCP_INST_ERROR);
+ }
+}
+
+/* If 'rgn' is higher than configured region number, throw an exception */
+static inline void validate_region_number(const ARCMPU *mpu, uint8_t rgn)
+{
+ if (!(rgn < mpu->reg_bcr.regions)) {
+ arc_raise_exception(container_of(mpu, CPUARCState, mpu), /* env */
+ EXCP_INST_ERROR);
+ }
+}
+
+/* Extern function: Getter for MPU registers */
+uint32_t
+arc_mpu_aux_get(const struct arc_aux_reg_detail *aux_reg_detail, void *data)
+{
+ validate_mpu_regs_access((CPUARCState *) data);
+ ARCMPU *mpu = &(((CPUARCState *) data)->mpu);
+ uint32_t reg = 0;
+
+ switch (aux_reg_detail->id) {
+ case AUX_ID_mpu_build:
+ reg = (mpu->reg_bcr.regions << 8) | mpu->reg_bcr.version;
+ break;
+ case AUX_ID_mpuen:
+ reg = pack_enable_reg(&mpu->reg_enable);
+ break;
+ case AUX_ID_mpuic:
+ reg = pack_ecr(&mpu->reg_ecr);
+ break;
+ case AUX_ID_mpurdb0 ... AUX_ID_mpurdb15: {
+ const uint8_t rgn = aux_reg_detail->id - AUX_ID_mpurdb0;
+ validate_region_number(mpu, rgn);
+ reg = pack_base_reg(&mpu->reg_base[rgn]);
+ break;
+ }
+ case AUX_ID_mpurdp0 ... AUX_ID_mpurdp15: {
+ const uint8_t rgn = aux_reg_detail->id - AUX_ID_mpurdp0;
+ validate_region_number(mpu, rgn);
+ reg = pack_perm_reg(&mpu->reg_perm[rgn]);
+ break;
+ }
+ default:
+ g_assert_not_reached();
+ }
+ return reg;
+}
+
+/* Log the MPU sensitive information */
+static void log_mpu_data(const ARCMPU *mpu)
+{
+ char suffix[4] = " B";
+ uint32_t size;
+ /* Log header */
+ qemu_log_mask(CPU_LOG_MMU,
+ "[MPU] ,--------.-------.------------.--------.---"
+ "--------------------.--------------.------------.\n");
+ qemu_log_mask(CPU_LOG_MMU,
+ "[MPU] | region | valid | address | size | "
+ "effective address | kernel perm. | user perm. |\n");
+ qemu_log_mask(CPU_LOG_MMU,
+ "[MPU] |--------+-------+------------+--------+---"
+ "--------------------+--------------+------------|\n");
+ /* Now its every regions turn */
+ for (uint8_t r = 0; r < mpu->reg_bcr.regions; ++r) {
+ const MPUBaseReg *rb = &mpu->reg_base[r];
+ const MPUPermReg *rp = &mpu->reg_perm[r];
+ const MPUPermissions *p = &rp->permission;
+ if (rp->size >= 0x40000000) {
+ suffix[0] = 'G';
+ size = rp->size >> 30;
+ } else if (rp->size >= 0x00100000) {
+ suffix[0] = 'M';
+ size = rp->size >> 20;
+ } else if (rp->size >= 0x00000400) {
+ suffix[0] = 'K';
+ size = rp->size >> 10;
+ } else {
+ suffix[0] = ' ';
+ size = rp->size & 0x3FF;
+ }
+ qemu_log_mask(CPU_LOG_MMU,
+ "[MPU] | %02u | %s | 0x%08x | %3u %s | 0x%08x-0x%08x "
+ "| %c%c%c | %c%c%c |\n",
+ r, rb->valid ? "true " : "false", rb->addr, size, suffix,
+ rb->addr & rp->mask,
+ (rb->addr & rp->mask) + ((uint32_t) rp->size),
+ p->KR ? 'r' : '-', p->KW ? 'w' : '-', p->KE ? 'x' : '-',
+ p->UR ? 'r' : '-', p->UW ? 'w' : '-', p->UE ? 'x' : '-');
+ }
+ /* Default region */
+ const MPUPermissions *defp = &mpu->reg_enable.permission;
+ qemu_log_mask(CPU_LOG_MMU,
+ "[MPU] | def. | | | | "
+ " | %c%c%c | %c%c%c |\n",
+ defp->KR ? 'r' : '-', defp->KW ? 'w' : '-', defp->KE ? 'x' : '-',
+ defp->UR ? 'r' : '-', defp->UW ? 'w' : '-', defp->UE ? 'x' : '-');
+ /* Wrap it up */
+ qemu_log_mask(CPU_LOG_MMU,
+ "[MPU] `--------^-------^------------^--------^---"
+ "--------------------^--------------^------------'\n");
+}
+
+/* Extern function: Setter for MPU registers */
+void
+arc_mpu_aux_set(const struct arc_aux_reg_detail *aux_reg_detail,
+ const uint32_t value, void *data)
+{
+ validate_mpu_regs_access((CPUARCState *) data);
+ ARCMPU *mpu = &(((CPUARCState *) data)->mpu);
+
+ switch (aux_reg_detail->id) {
+ case AUX_ID_mpuen:
+ unpack_enable_reg(&mpu->reg_enable, value);
+ mpu->enabled = mpu->reg_enable.enabled;
+ break;
+ case AUX_ID_mpurdb0 ... AUX_ID_mpurdb15: {
+ const uint8_t rgn = aux_reg_detail->id - AUX_ID_mpurdb0;
+ validate_region_number(mpu, rgn);
+ unpack_base_reg(&mpu->reg_base[rgn], value);
+ break;
+ }
+ case AUX_ID_mpurdp0 ... AUX_ID_mpurdp15: {
+ const uint8_t rgn = aux_reg_detail->id - AUX_ID_mpurdp0;
+ validate_region_number(mpu, rgn);
+ unpack_perm_reg(&mpu->reg_perm[rgn], value);
+ break;
+ }
+ default:
+ g_assert_not_reached();
+ }
+ /* Invalidate the entries in qemu's translation buffer */
+ tlb_flush(env_cpu((CPUARCState *) data));
+ /* If MPU is enabled, log its data */
+ if (mpu->enabled) {
+ log_mpu_data(mpu);
+ }
+}
+
+/*
+ * If user mode, return the user permission only.
+ * If kernel mode, return the aggregation of both permissions.
+ */
+static void get_effective_rwe(const MPUPermissions *perm,
+ bool user_mode, MPUEffectPerm *effective)
+{
+ effective->read = user_mode ? perm->UR : perm->KR | perm->UR;
+ effective->write = user_mode ? perm->UW : perm->KW | perm->UW;
+ effective->exec = user_mode ? perm->UE : perm->KE | perm->UE;
+}
+
+/* Translate internal QEMU's access type to an MPU violation type */
+static inline uint8_t qemu_access_to_violation(MMUAccessType access)
+{
+ uint8_t vt = 0;
+ switch (access) {
+ case MMU_INST_FETCH:
+ vt = MPU_CAUSE_FETCH;
+ break;
+ case MMU_DATA_LOAD:
+ vt = MPU_CAUSE_READ;
+ break;
+ case MMU_DATA_STORE:
+ vt = MPU_CAUSE_WRITE;
+ break;
+ /* TODO: there must be an MPU_CAUSE_RW as well, but how? */
+ default:
+ g_assert_not_reached();
+ }
+ return vt;
+}
+
+/* Translate MPU's permission to QEMU's tlb permission */
+static inline uint8_t mpu_permission_to_qemu(const MPUPermissions *perm,
+ bool user_mode)
+{
+ MPUEffectPerm effective = { };
+ get_effective_rwe(perm, user_mode, &effective);
+ return (effective.read ? PAGE_READ : 0) |
+ (effective.write ? PAGE_WRITE : 0) |
+ (effective.exec ? PAGE_EXEC : 0);
+}
+
+/*
+ * Check if the 'access' is according to 'perm'ission.
+ * Note that a user mode permission is also implied for kernel.
+ */
+static bool allowed(MMUAccessType access, bool user_mode,
+ const MPUPermissions *perm)
+{
+ MPUEffectPerm effective_perm = { };
+ get_effective_rwe(perm, user_mode, &effective_perm);
+
+ switch (access) {
+ case MMU_INST_FETCH:
+ return effective_perm.exec;
+ case MMU_DATA_LOAD:
+ return effective_perm.read;
+ case MMU_DATA_STORE:
+ return effective_perm.write;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+/* Used for logging purposes */
+static inline const char *log_violation_to_str(uint8_t violation)
+{
+ return (violation == MPU_CAUSE_FETCH) ? "fetch" :
+ (violation == MPU_CAUSE_READ) ? "read" :
+ (violation == MPU_CAUSE_WRITE) ? "write" :
+ (violation == MPU_CAUSE_RW) ? "read-write" :
+ "unknown";
+}
+
+/* Sets the exception data */
+static void set_exception(CPUARCState *env, uint32_t addr,
+ uint8_t region, MMUAccessType access)
+{
+ MPUECR *ecr = &env->mpu.reg_ecr;
+ ecr->violation = qemu_access_to_violation(access);
+ ecr->region = region;
+
+ /* this info is used by the caller to trigger the exception */
+ MPUException *excp = &env->mpu.exception;
+ excp->number = EXCP_PROTV;
+ excp->code = ecr->violation;
+ excp->param = ARC_MPU_ECR_PARAM;
+
+ qemu_log_mask(CPU_LOG_MMU,
+ "[MPU] exception: region=%hu, addr=0x%08x, violation=%s\n",
+ region, addr, log_violation_to_str(ecr->violation));
+}
+
+/*
+ * Given an 'addr', finds the region it belongs to. If no match
+ * is found, then it signals this by returning MPU_DEFAULT_REGION_NR.
+ * Since regions with lower index has higher priority, the first match
+ * is the correct one even if there is overlap among regions.
+ */
+static uint8_t get_matching_region(const ARCMPU *mpu, uint32_t addr)
+{
+ qemu_log_mask(CPU_LOG_MMU, "[MPU] looking up: addr=0x%08x\n", addr);
+ for (uint8_t r = 0; r < mpu->reg_bcr.regions; ++r) {
+ if (!mpu->reg_base[r].valid) {
+ continue;
+ }
+ const uint32_t mask = mpu->reg_perm[r].mask;
+ /* 'addr' falls under the current region? */
+ if ((mpu->reg_base[r].addr & mask) == (addr & mask)) {
+ qemu_log_mask(CPU_LOG_MMU,
+ "[MPU] region match: region=%u, base=0x%08x\n",
+ r, mpu->reg_base[r].addr);
+ return r;
+ }
+ }
+ /* If we are here, then no corresponding region is found */
+ qemu_log_mask(CPU_LOG_MMU, "[MPU] default region will be used.\n");
+ return MPU_DEFAULT_REGION_NR;
+}
+
+/*
+ * Returns the corresponding permission for the given 'region'.
+ * If 'region' is MPU_DEFAULT_REGION_NR, then the default permission
+ * from MPU_EN register is returned.
+ */
+static const MPUPermissions *get_permission(const ARCMPU *mpu,
+ uint8_t region)
+{
+ if (region < mpu->reg_bcr.regions) {
+ return &mpu->reg_perm[region].permission;
+ } else if (region == MPU_DEFAULT_REGION_NR) {
+ return &mpu->reg_enable.permission;
+ }
+ g_assert_not_reached();
+}
+
+/*
+ * Have the following example in mind:
+ * ,------------.
+ * | region 5 |
+ * | |
+ * | | first page of region 5
+ * | |
+ * |............|
+ * | |
+ * |,----------.| second page of region 5
+ * || region 4 ||
+ * |`----------'|
+ * `------------'
+ * Here region four's size is half a page size.
+ *
+ * This function checks if the page that the address belongs to,
+ * overlaps with another higher priority region. regions with lower
+ * priority don't matter because they cannot influence the permission.
+ *
+ * The logic is to check if any of the valid regions is contained in
+ * the page that 'addr' belongs to.
+ */
+static bool is_overlap_free(const ARCMPU *mpu, target_ulong addr,
+ uint8_t current_region)
+{
+ /* Nothing has higher priority than region 0 */
+ if (current_region == 0) {
+ return true;
+ } else if (current_region == MPU_DEFAULT_REGION_NR) {
+ /* Make the "default region number" fit in this function */
+ current_region = mpu->reg_bcr.regions;
+ }
+
+ assert(current_region <= mpu->reg_bcr.regions);
+
+ target_ulong page_addr = addr & PAGE_MASK;
+ /*
+ * Going through every region that has higher priority than
+ * the current one.
+ */
+ for (uint8_t r = 0; r < current_region; ++r) {
+ if (mpu->reg_base[r].valid &&
+ page_addr == (mpu->reg_base[r].addr & PAGE_MASK)) {
+ return false;
+ }
+ }
+ /* No overlap with a higher priority region */
+ return true;
+}
+
+/*
+ * Update QEMU's TLB with region's permission.
+ * One thing to remember is that if the region size
+ * is smaller than TARGET_PAGE_SIZE, QEMU will always
+ * consult tlb_fill() for any access to that region.
+ * So there is no point in fine tunning TLB entry sizes
+ * to reflect the real region size. On the other hand,
+ * if the region size is big ( > TARGET_PAGE_SIZE), we
+ * still go with TARGET_PAGE_SIZE, because it can be
+ * memory demanding for host process.
+ */
+static void update_tlb_page(CPUARCState *env, uint8_t region,
+ target_ulong addr, int mmu_idx)
+{
+ CPUState *cs = env_cpu(env);
+ /* By default, only add entry for 'addr' */
+ target_ulong tlb_addr = addr;
+ target_ulong tlb_size = 1;
+ bool check_for_overlap = true;
+ int prot = 0;
+
+ if (region != MPU_DEFAULT_REGION_NR) {
+ MPUPermReg *perm = &env->mpu.reg_perm[region];
+ prot = mpu_permission_to_qemu(
+ &perm->permission, is_user_mode(env));
+ /*
+ * If the region's size is big enough, we'll check for overlap.
+ * Later if we find no overlap, then we add the permission for
+ * the whole page to qemu's tlb.
+ */
+ check_for_overlap = (perm->size >= TARGET_PAGE_SIZE);
+ }
+ /* Default region */
+ else {
+ prot = mpu_permission_to_qemu(
+ &env->mpu.reg_enable.permission, is_user_mode(env));
+ }
+
+ /*
+ * If the region completely covers the 'page' that 'addr'
+ * belongs to, _and_ is not overlapping with any other region
+ * then add a 'page'wise entry.
+ */
+ if (check_for_overlap &&
+ is_overlap_free(&env->mpu, addr, region)) {
+ tlb_addr = addr & PAGE_MASK;
+ tlb_size = TARGET_PAGE_SIZE;
+ }
+
+ tlb_set_page(cs, tlb_addr, tlb_addr, prot, mmu_idx, tlb_size);
+ qemu_log_mask(CPU_LOG_MMU, "[MPU] TLB update: addr=0x%08x, "
+ "prot=%c%c%c, mmu_idx=%u, page_size=%u\n", tlb_addr,
+ (prot & PAGE_READ) ? 'r' : '-', (prot & PAGE_WRITE) ? 'w' : '-',
+ (prot & PAGE_EXEC) ? 'x' : '-', mmu_idx, tlb_size);
+}
+
+/* The MPU entry point for any memory access */
+int
+arc_mpu_translate(CPUARCState *env, target_ulong addr,
+ MMUAccessType access, int mmu_idx)
+{
+ ARCMPU *mpu = &env->mpu;
+
+ uint8_t region = get_matching_region(mpu, addr);
+ const MPUPermissions *perms = get_permission(mpu, region);
+ if (!allowed(access, is_user_mode(env), perms)) {
+ set_exception(env, addr, region, access);
+ return MPU_FAULT;
+ }
+ update_tlb_page(env, region, addr, mmu_idx);
+
+ return MPU_SUCCESS;
+}
+
+/*-*-indent-tabs-mode:nil;tab-width:4;indent-line-function:'insert-tab'-*-*/
+/* vim: set ts=4 sw=4 et: */
diff --git a/target/arc/mpu.h b/target/arc/mpu.h
new file mode 100644
index 0000000000..d23d289beb
--- /dev/null
+++ b/target/arc/mpu.h
@@ -0,0 +1,142 @@
+/*
+ * QEMU ARC CPU
+ *
+ * Copyright (c) 2020 Synppsys Inc.
+ * Contributed by Shahab Vahedi (Synopsys)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * http://www.gnu.org/licenses/lgpl-2.1.html
+ */
+
+#ifndef ARC_MPU_H
+#define ARC_MPU_H
+
+#include "target/arc/regs.h"
+#include "cpu-qom.h"
+
+/* These values are based on ARCv2 ISA PRM for ARC HS processors */
+#define ARC_MPU_VERSION 0x03 /* MPU version supported */
+#define ARC_MPU_MAX_NR_REGIONS 16 /* Number of regions to protect */
+#define ARC_MPU_ECR_VEC_NUM 0x06 /* EV_ProtV: Protection Violation */
+#define ARC_MPU_ECR_PARAM 0x04 /* MPU (as opposed to MMU, ...) */
+
+/* MPU Build Configuration Register */
+typedef struct MPUBCR {
+ uint8_t version; /* 0 (disabled), 0x03 */
+ uint8_t regions; /* 0, 1, 2, 4, 8, 16 */
+} MPUBCR;
+
+typedef struct MPUPermissions {
+ bool KR; /* Kernel read */
+ bool KW; /* Kernel write */
+ bool KE; /* Kernel execute */
+ bool UR; /* User read */
+ bool UW; /* User write */
+ bool UE; /* User execute */
+} MPUPermissions;
+
+/* MPU Enable Register */
+typedef struct MPUEnableReg {
+ bool enabled; /* Is MPU enabled? */
+ MPUPermissions permission; /* Default region permissions */
+} MPUEnableReg;
+
+/* Determines during which type of operation a violation occurred */
+enum MPUCauseCode {
+ MPU_CAUSE_FETCH = 0x00,
+ MPU_CAUSE_READ = 0x01,
+ MPU_CAUSE_WRITE = 0x02,
+ MPU_CAUSE_RW = 0x03
+};
+
+/* The exception to be set */
+typedef struct MPUException {
+ uint8_t number; /* Exception vector number: 0x06 -> EV_ProtV */
+ uint8_t code; /* Cause code: fetch, read, write, read/write */
+ uint8_t param; /* Always 0x04 to represent MPU */
+} MPUException;
+
+/* MPU Exception Cause Register */
+typedef struct MPUECR {
+ uint8_t region;
+ uint8_t violation; /* Fetch, read, write, read/write */
+} MPUECR;
+
+/* MPU Region Descriptor Base Register */
+typedef struct MPUBaseReg {
+ bool valid; /* Is this region valid? */
+ uint32_t addr; /* Minimum size is 32 bytes --> bits[4:0] are 0 */
+} MPUBaseReg;
+
+/* MPU Region Descriptor Permissions Register */
+typedef struct MPUPermReg {
+ /* size_bits: 00100b ... 11111b */
+ uint8_t size_bits;
+ /*
+ * We need normal notation of size to set qemu's tlb page size later.
+ * Region's size: 32 bytes, 64 bytes, ..., 4 gigabytes
+ */
+ uint64_t size; /* 2 << size_bits */
+ /*
+ * Region offset: 0x1f, 0x3f, ..., 0xffffffff
+ * Hence region mask: 0xffffffe0, 0xfffffc0, ..., 0x00000000
+ */
+ uint32_t mask;
+ MPUPermissions permission; /* region's permissions */
+} MPUPermReg;
+
+typedef struct ARCMPU {
+ bool enabled;
+
+ MPUBCR reg_bcr;
+ MPUEnableReg reg_enable;
+ MPUECR reg_ecr;
+ /* Base and permission registers are paired */
+ MPUBaseReg reg_base[ARC_MPU_MAX_NR_REGIONS];
+ MPUPermReg reg_perm[ARC_MPU_MAX_NR_REGIONS];
+
+ MPUException exception;
+} ARCMPU;
+
+enum ARCMPUVerifyRet {
+ MPU_SUCCESS,
+ MPU_FAULT
+};
+
+struct ARCCPU;
+struct CPUARCState;
+
+/* Used during a reset */
+extern void arc_mpu_init(struct ARCCPU *cpu);
+
+/* Get auxiliary MPU registers */
+extern uint32_t
+arc_mpu_aux_get(const struct arc_aux_reg_detail *aux_reg_detail, void *data);
+
+/* Set auxiliary MPU registers */
+extern void
+arc_mpu_aux_set(const struct arc_aux_reg_detail *aux_reg_detail,
+ const uint32_t val, void *data);
+
+/*
+ * Verifies if 'access' to 'addr' is allowed or not.
+ * possible return values:
+ * MPU_SUCCESS - allowed; 'prot' holds permissions
+ * MPU_FAULT - not allowed; corresponding exception parameters are set
+ */
+extern int
+arc_mpu_translate(struct CPUARCState *env, uint32_t addr,
+ MMUAccessType access, int mmu_idx);
+
+#endif /* ARC_MPU_H */
--
2.20.1