From nobody Sat Nov 16 15:32:36 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1605112369; cv=none; d=zohomail.com; s=zohoarc; b=Ju7HHiLlg2MUWw8k79c8yv8i2ku3pZgidTfFRh6tBu5gQVgOwXzRMNETOp/oQP8cetK+0cxmVeWgVKBjxBe7Hm8SGTI/zf/pCAXzoBMbaZwTwVo3w0FkUEvbB8TCgBGwMC9GWtC17GworCe3eh29qCB/xamkuVvleNCD3loQNn0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1605112369; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=rLpKyyve7vm7piGKiNxX6qLh0HX6GxCNsINgcT3CcxI=; b=G9n9k7fRn4b0oWaEJYVjNzPwcdb/0lyekSgJ8kKLUu7jJTPHZjHbR3ZecKqeWZIkfbWBybAMX+UONzyHuJsFV29mwd49jdj4oBGPuEHgVFWdC5PxCum1/ObcijsEc3obASmr3GQSGhvLBHRGAmty9YGBHiILxF3aRJRe/6seFcI= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail header.from= (p=none dis=none) header.from= Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1605112369808667.4175551302599; Wed, 11 Nov 2020 08:32:49 -0800 (PST) Received: from localhost ([::1]:44090 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kct3A-00056a-LO for importer@patchew.org; Wed, 11 Nov 2020 11:32:48 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:37244) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kcspG-0000nw-FK for qemu-devel@nongnu.org; Wed, 11 Nov 2020 11:18:26 -0500 Received: from mail-wr1-x435.google.com ([2a00:1450:4864:20::435]:37995) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1kcspB-0007zZ-A3 for qemu-devel@nongnu.org; Wed, 11 Nov 2020 11:18:26 -0500 Received: by mail-wr1-x435.google.com with SMTP id p8so3089412wrx.5 for ; Wed, 11 Nov 2020 08:18:20 -0800 (PST) Received: from cmiranda-laptop.localdomain (bl19-104-46.dsl.telepac.pt. [2.80.104.46]) by smtp.gmail.com with ESMTPSA id s188sm3115178wmf.45.2020.11.11.08.18.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 11 Nov 2020 08:18:18 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=rLpKyyve7vm7piGKiNxX6qLh0HX6GxCNsINgcT3CcxI=; b=DngdFFANj9TVRrm9UoBXBhgDOSKKBPOyDMJ0cawixqa/99wXEPwogjQ3MLGsI1F5oA bxq5yWTXJynAvSynbOjuseFjUw/B+8GGtJ46nh7lZDf/2SGNBtLtUGld06fvdfbkElg/ djovvfv7wH+0hLtZdhLG09qLDPZW3yROE8omLCE7Ee1UKSex9FRsp8sxtBPoT4GaDep/ j81n1XmOJJFzgQPaZmCadfrIV2/OCsxuqbMyiBpoOgGUeJNMdkrepwKJNPwQJFVKjAB5 U4mOk/5yz6HL1IHQAva6ZWcQIktRU2O/zov1VCYBxXbh0lD1wx1BpLOCKy6Zns3to3aM 41ng== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=rLpKyyve7vm7piGKiNxX6qLh0HX6GxCNsINgcT3CcxI=; b=QCt/uSv9BsSAkBl6Ow9MdVUrDABnmRnFA2N6brZhPSQDyu0gAVHWKutkFe3dNkFzuf hPyQz1y6TGTmMbeFQJYw/PrsyUi2LsAWf9opHczjImHrJTwOfo127iwW8g7pYHEq8dUR q0SqY/jSF6kc2ue5lDpmotbMh9VZIsHnyTCNOwiy0hlHqXWfcnhU5QMQrcD0JQTQYqn6 nXHVtYFkMG0HJFMdcHooOqCkXdM4kpEDAPrme19QD6Q5bF2/2KzY7BfIuMtC6uRGqRYh 5LJvCmBKTswwjJcnShg8HEceRdJq79UwOrZsr0u8R5M8TLoIvGxUf1xbeolZi2p/9hNs 9wbA== X-Gm-Message-State: AOAM531ucG5hg94ChmfDzsTeAyN8a6+LnfmyJcXkTsV87ivDjLv2uT6K uUl8ox5a9MmAmZbS/tKa6IEl9hEUjF+hhg== X-Google-Smtp-Source: ABdhPJz8Km4pv+gA9pLWesmkkWGrdnihINZY9InojvHvGRiHvRItj9KECFKKWE9n80Y8sK6YAor7iA== X-Received: by 2002:adf:ea03:: with SMTP id q3mr1462860wrm.141.1605111498921; Wed, 11 Nov 2020 08:18:18 -0800 (PST) From: cupertinomiranda@gmail.com To: qemu-devel@nongnu.org Subject: [PATCH 10/15] arc: Add memory protection unit (MPU) support Date: Wed, 11 Nov 2020 16:17:53 +0000 Message-Id: <20201111161758.9636-11-cupertinomiranda@gmail.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201111161758.9636-1-cupertinomiranda@gmail.com> References: <20201111161758.9636-1-cupertinomiranda@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::435; envelope-from=cupertinomiranda@gmail.com; helo=mail-wr1-x435.google.com X-detected-operating-system: by eggs.gnu.org: No matching host in p0f cache. That's all we know. X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Claudiu Zissulescu , Cupertino Miranda , Shahab Vahedi , Shahab Vahedi , Cupertino Miranda , linux-snps-arc@lists.infradead.org, Claudiu Zissulescu Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) Content-Type: text/plain; charset="utf-8" From: Shahab Vahedi 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 --- 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 =3D (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 valu= e) +{ + perms->KR =3D (value >> MPU_EN_KR_BIT) & 1; + perms->KW =3D (value >> MPU_EN_KW_BIT) & 1; + perms->KE =3D (value >> MPU_EN_KE_BIT) & 1; + perms->UR =3D (value >> MPU_EN_UR_BIT) & 1; + perms->UW =3D (value >> MPU_EN_UW_BIT) & 1; + perms->UE =3D (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 =3D value & MPU_BASE_ADDR_MASK; + mpurdb->valid =3D 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 =3D + size_bits & MPU_WIDTH_TO_MASK(MPU_PERMS_REG_LOWER_SIZE_WIDTH); + uint32_t higher =3D size_bits >> MPU_PERMS_REG_LOWER_SIZE_WIDTH; + higher &=3D 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 =3D + value & MPU_WIDTH_TO_MASK(MPU_PERMS_REG_LOWER_SIZE_WIDTH); + uint8_t higher =3D value >> MPU_PERMS_REG_HIGHER_SIZE_POS; + higher &=3D MPU_WIDTH_TO_MASK(MPU_PERMS_REG_HIGHER_SIZE_WIDTH); + *size_bits =3D (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 =3D 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 >=3D 4 && size_bits < 31) { + region_offset_mask =3D (2 << size_bits) - 1; + } else if (size_bits =3D=3D 31) { + region_offset_mask =3D 0xffffffff; + } else { + qemu_log_mask(LOG_GUEST_ERROR, "[MPU] %hu as size of a region is " + "undefined behaviour.\n", size_bits); + } + *mask =3D ~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 =3D (mpurdp->size_bits < 4) ? 32 : (2ul << mpurdp->size_b= its); + 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 =3D {0}; + ARCMPU *mpu =3D &cpu->env.mpu; + size_t idx =3D 0; + + /* Maybe the version must be determinded also based on CPU type */ + mpu->reg_bcr.version =3D cpu->cfg.has_mpu ? ARC_MPU_VERSION : 0; + mpu->reg_bcr.regions =3D 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 =3D false; + + mpu->reg_enable.enabled =3D false; + mpu->reg_enable.permission =3D INITIAL_PERMS; + + mpu->reg_ecr.region =3D 0; + mpu->reg_ecr.violation =3D 0; + mpu->exception.number =3D ARC_MPU_ECR_VEC_NUM; + mpu->exception.code =3D 0; + mpu->exception.param =3D ARC_MPU_ECR_PARAM; + + for (idx =3D 0; idx < ARC_MPU_MAX_NR_REGIONS; ++idx) { + mpu->reg_base[idx].valid =3D false; + mpu->reg_base[idx].addr =3D 0; + + mpu->reg_perm[idx].size_bits =3D 0; + mpu->reg_perm[idx].mask =3D 0xffffffff; + mpu->reg_perm[idx].permission =3D 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 =3D=3D 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 *dat= a) +{ + validate_mpu_regs_access((CPUARCState *) data); + ARCMPU *mpu =3D &(((CPUARCState *) data)->mpu); + uint32_t reg =3D 0; + + switch (aux_reg_detail->id) { + case AUX_ID_mpu_build: + reg =3D (mpu->reg_bcr.regions << 8) | mpu->reg_bcr.version; + break; + case AUX_ID_mpuen: + reg =3D pack_enable_reg(&mpu->reg_enable); + break; + case AUX_ID_mpuic: + reg =3D pack_ecr(&mpu->reg_ecr); + break; + case AUX_ID_mpurdb0 ... AUX_ID_mpurdb15: { + const uint8_t rgn =3D aux_reg_detail->id - AUX_ID_mpurdb0; + validate_region_number(mpu, rgn); + reg =3D pack_base_reg(&mpu->reg_base[rgn]); + break; + } + case AUX_ID_mpurdp0 ... AUX_ID_mpurdp15: { + const uint8_t rgn =3D aux_reg_detail->id - AUX_ID_mpurdp0; + validate_region_number(mpu, rgn); + reg =3D 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] =3D " 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 =3D 0; r < mpu->reg_bcr.regions; ++r) { + const MPUBaseReg *rb =3D &mpu->reg_base[r]; + const MPUPermReg *rp =3D &mpu->reg_perm[r]; + const MPUPermissions *p =3D &rp->permission; + if (rp->size >=3D 0x40000000) { + suffix[0] =3D 'G'; + size =3D rp->size >> 30; + } else if (rp->size >=3D 0x00100000) { + suffix[0] =3D 'M'; + size =3D rp->size >> 20; + } else if (rp->size >=3D 0x00000400) { + suffix[0] =3D 'K'; + size =3D rp->size >> 10; + } else { + suffix[0] =3D ' '; + size =3D 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 =3D &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 =3D &(((CPUARCState *) data)->mpu); + + switch (aux_reg_detail->id) { + case AUX_ID_mpuen: + unpack_enable_reg(&mpu->reg_enable, value); + mpu->enabled =3D mpu->reg_enable.enabled; + break; + case AUX_ID_mpurdb0 ... AUX_ID_mpurdb15: { + const uint8_t rgn =3D 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 =3D 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 =3D user_mode ? perm->UR : perm->KR | perm->UR; + effective->write =3D user_mode ? perm->UW : perm->KW | perm->UW; + effective->exec =3D 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 =3D 0; + switch (access) { + case MMU_INST_FETCH: + vt =3D MPU_CAUSE_FETCH; + break; + case MMU_DATA_LOAD: + vt =3D MPU_CAUSE_READ; + break; + case MMU_DATA_STORE: + vt =3D 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 =3D { }; + 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 =3D { }; + 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 =3D=3D MPU_CAUSE_FETCH) ? "fetch" : + (violation =3D=3D MPU_CAUSE_READ) ? "read" : + (violation =3D=3D MPU_CAUSE_WRITE) ? "write" : + (violation =3D=3D 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 =3D &env->mpu.reg_ecr; + ecr->violation =3D qemu_access_to_violation(access); + ecr->region =3D region; + + /* this info is used by the caller to trigger the exception */ + MPUException *excp =3D &env->mpu.exception; + excp->number =3D EXCP_PROTV; + excp->code =3D ecr->violation; + excp->param =3D ARC_MPU_ECR_PARAM; + + qemu_log_mask(CPU_LOG_MMU, + "[MPU] exception: region=3D%hu, addr=3D0x%08x, violation=3D%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=3D0x%08x\n", addr); + for (uint8_t r =3D 0; r < mpu->reg_bcr.regions; ++r) { + if (!mpu->reg_base[r].valid) { + continue; + } + const uint32_t mask =3D mpu->reg_perm[r].mask; + /* 'addr' falls under the current region? */ + if ((mpu->reg_base[r].addr & mask) =3D=3D (addr & mask)) { + qemu_log_mask(CPU_LOG_MMU, + "[MPU] region match: region=3D%u, base=3D0x%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 =3D=3D 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 =3D=3D 0) { + return true; + } else if (current_region =3D=3D MPU_DEFAULT_REGION_NR) { + /* Make the "default region number" fit in this function */ + current_region =3D mpu->reg_bcr.regions; + } + + assert(current_region <=3D mpu->reg_bcr.regions); + + target_ulong page_addr =3D addr & PAGE_MASK; + /* + * Going through every region that has higher priority than + * the current one. + */ + for (uint8_t r =3D 0; r < current_region; ++r) { + if (mpu->reg_base[r].valid && + page_addr =3D=3D (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 =3D env_cpu(env); + /* By default, only add entry for 'addr' */ + target_ulong tlb_addr =3D addr; + target_ulong tlb_size =3D 1; + bool check_for_overlap =3D true; + int prot =3D 0; + + if (region !=3D MPU_DEFAULT_REGION_NR) { + MPUPermReg *perm =3D &env->mpu.reg_perm[region]; + prot =3D 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 =3D (perm->size >=3D TARGET_PAGE_SIZE); + } + /* Default region */ + else { + prot =3D 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 =3D addr & PAGE_MASK; + tlb_size =3D 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=3D0x%08x, " + "prot=3D%c%c%c, mmu_idx=3D%u, page_size=3D%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 =3D &env->mpu; + + uint8_t region =3D get_matching_region(mpu, addr); + const MPUPermissions *perms =3D 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=3D4 sw=3D4 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 =3D 0x00, + MPU_CAUSE_READ =3D 0x01, + MPU_CAUSE_WRITE =3D 0x02, + MPU_CAUSE_RW =3D 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 *dat= a); + +/* 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 */ --=20 2.20.1