mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-06 02:50:49 +09:00
ANDROID: KVM: arm64: Set up S2MPU Memory Protection Table
S2MPU Second-level Memory Protection Table is a PA-contiguous buffer
containing an array of 2-bit read/write entries at given granularity
for a given gigabyte physical address space region. The size of SMPT
varies per granularity but at the finest 4K granularity it is 64KB
PA-contiguous, aligned to 64KB.
Allocate sufficient number of SMPT buffers for the S2MPU driver assuming
4K granularity for 4K/16K PAGE_SIZE, and 64K granularity for 64K
PAGE_SIZE. We also assume that all S2MPUs share SMPTs for a given
gigabyte region. There are 34 gigabyte regions that can be set by the
driver (GBs 4-33 always block all traffic).
Hyp takes ownership of the memory in s2mpu_init and assigns pointers to
the buffers to L1ENTRY_L2TABLE_ADDR registers on init and power-on
events. The pointers remain static as the driver will only change
granularity between 1G and 4K/64K (depending on PAGE_SIZE).
Test: builds, boots
Bug: 190463801
Change-Id: I3fcad8b3ce5d194a987b09d042bd56d59bb35e5e
Signed-off-by: David Brazdil <dbrazdil@google.com>
(cherry picked from commit f0e1de52ef)
Signed-off-by: Mostafa Saleh <smostafa@google.com>
This commit is contained in:
committed by
Mostafa Saleh
parent
e1a271f580
commit
76b86ca887
@@ -29,6 +29,7 @@
|
||||
#define REG_NS_FAULT_PA_LOW(vid) (0x2004 + ((vid) * 0x20))
|
||||
#define REG_NS_FAULT_PA_HIGH(vid) (0x2008 + ((vid) * 0x20))
|
||||
#define REG_NS_FAULT_INFO(vid) (0x2010 + ((vid) * 0x20))
|
||||
#define REG_NS_L1ENTRY_L2TABLE_ADDR(vid, gb) (0x4000 + ((vid) * 0x200) + ((gb) * 0x8))
|
||||
#define REG_NS_L1ENTRY_ATTR(vid, gb) (0x4004 + ((vid) * 0x200) + ((gb) * 0x8))
|
||||
|
||||
#define CTRL0_ENABLE BIT(0)
|
||||
@@ -76,12 +77,40 @@
|
||||
#define FAULT_INFO_LEN_MASK GENMASK(19, 16)
|
||||
#define FAULT_INFO_ID_MASK GENMASK(15, 0)
|
||||
|
||||
#define L1ENTRY_L2TABLE_ADDR(pa) ((pa) >> 4)
|
||||
|
||||
#define L1ENTRY_ATTR_L2TABLE_EN BIT(0)
|
||||
#define L1ENTRY_ATTR_GRAN_4K 0x0
|
||||
#define L1ENTRY_ATTR_GRAN_64K 0x1
|
||||
#define L1ENTRY_ATTR_GRAN_2M 0x2
|
||||
#define L1ENTRY_ATTR_PROT(prot) FIELD_PREP(GENMASK(2, 1), prot)
|
||||
#define L1ENTRY_ATTR_GRAN(gran) FIELD_PREP(GENMASK(5, 4), gran)
|
||||
#define L1ENTRY_ATTR_1G(prot) L1ENTRY_ATTR_PROT(prot)
|
||||
#define L1ENTRY_ATTR_L2(gran) (L1ENTRY_ATTR_GRAN(gran) | \
|
||||
L1ENTRY_ATTR_L2TABLE_EN)
|
||||
|
||||
#define NR_GIGABYTES 64
|
||||
#define RO_GIGABYTES_FIRST 4
|
||||
#define RO_GIGABYTES_LAST 33
|
||||
#define NR_RO_GIGABYTES (RO_GIGABYTES_LAST - RO_GIGABYTES_FIRST + 1)
|
||||
#define NR_RW_GIGABYTES (NR_GIGABYTES - NR_RO_GIGABYTES)
|
||||
|
||||
#ifdef CONFIG_ARM64_64K_PAGES
|
||||
#define SMPT_GRAN SZ_64K
|
||||
#define SMPT_GRAN_ATTR L1ENTRY_ATTR_GRAN_64K
|
||||
#else
|
||||
#define SMPT_GRAN SZ_4K
|
||||
#define SMPT_GRAN_ATTR L1ENTRY_ATTR_GRAN_4K
|
||||
#endif
|
||||
static_assert(SMPT_GRAN <= PAGE_SIZE);
|
||||
|
||||
#define MPT_PROT_BITS 2
|
||||
#define SMPT_WORD_SIZE sizeof(u32)
|
||||
#define SMPT_ELEMS_PER_BYTE (BITS_PER_BYTE / MPT_PROT_BITS)
|
||||
#define SMPT_NUM_ELEMS (SZ_1G / SMPT_GRAN)
|
||||
#define SMPT_NUM_WORDS (SMPT_SIZE / SMPT_WORD_SIZE)
|
||||
#define SMPT_SIZE (SMPT_NUM_ELEMS / SMPT_ELEMS_PER_BYTE)
|
||||
#define SMPT_ORDER get_order(SMPT_SIZE)
|
||||
|
||||
/*
|
||||
* Iterate over S2MPU gigabyte regions. Skip those that cannot be modified
|
||||
@@ -123,10 +152,23 @@ enum mpt_prot {
|
||||
MPT_PROT_MASK = MPT_PROT_RW,
|
||||
};
|
||||
|
||||
struct fmpt {
|
||||
u32 *smpt;
|
||||
bool gran_1g;
|
||||
enum mpt_prot prot;
|
||||
};
|
||||
|
||||
struct mpt {
|
||||
struct fmpt fmpt[NR_GIGABYTES];
|
||||
};
|
||||
|
||||
extern size_t kvm_nvhe_sym(kvm_hyp_nr_s2mpus);
|
||||
#define kvm_hyp_nr_s2mpus kvm_nvhe_sym(kvm_hyp_nr_s2mpus)
|
||||
|
||||
extern struct s2mpu *kvm_nvhe_sym(kvm_hyp_s2mpus);
|
||||
#define kvm_hyp_s2mpus kvm_nvhe_sym(kvm_hyp_s2mpus)
|
||||
|
||||
extern struct mpt kvm_nvhe_sym(kvm_hyp_host_mpt);
|
||||
#define kvm_hyp_host_mpt kvm_nvhe_sym(kvm_hyp_host_mpt)
|
||||
|
||||
#endif /* __ARM64_KVM_S2MPU_H__ */
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <linux/arm-smccc.h>
|
||||
|
||||
#include <nvhe/memory.h>
|
||||
#include <nvhe/mm.h>
|
||||
#include <nvhe/spinlock.h>
|
||||
#include <nvhe/trap_handler.h>
|
||||
@@ -28,6 +29,7 @@
|
||||
|
||||
size_t __ro_after_init kvm_hyp_nr_s2mpus;
|
||||
struct s2mpu __ro_after_init *kvm_hyp_s2mpus;
|
||||
struct mpt kvm_hyp_host_mpt;
|
||||
|
||||
static hyp_spinlock_t s2mpu_lock;
|
||||
|
||||
@@ -122,6 +124,26 @@ static void __set_l1entry_attr_with_prot(struct s2mpu *dev, unsigned int gb,
|
||||
dev->va + REG_NS_L1ENTRY_ATTR(vid, gb));
|
||||
}
|
||||
|
||||
static void __set_l1entry_attr_with_fmpt(struct s2mpu *dev, unsigned int gb,
|
||||
unsigned int vid, struct fmpt *fmpt)
|
||||
{
|
||||
if (fmpt->gran_1g) {
|
||||
__set_l1entry_attr_with_prot(dev, gb, vid, fmpt->prot);
|
||||
} else {
|
||||
/* Order against writes to the SMPT. */
|
||||
writel(L1ENTRY_ATTR_L2(SMPT_GRAN_ATTR),
|
||||
dev->va + REG_NS_L1ENTRY_ATTR(vid, gb));
|
||||
}
|
||||
}
|
||||
|
||||
static void __set_l1entry_l2table_addr(struct s2mpu *dev, unsigned int gb,
|
||||
unsigned int vid, phys_addr_t addr)
|
||||
{
|
||||
/* Order against writes to the SMPT. */
|
||||
writel(L1ENTRY_L2TABLE_ADDR(addr),
|
||||
dev->va + REG_NS_L1ENTRY_L2TABLE_ADDR(vid, gb));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize S2MPU device and set all GB regions to 1G granularity with
|
||||
* given protection bits.
|
||||
@@ -141,6 +163,29 @@ static void initialize_with_prot(struct s2mpu *dev, enum mpt_prot prot)
|
||||
__set_control_regs(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize S2MPU device, set L2 table addresses and configure L1TABLE_ATTR
|
||||
* registers according to the given MPT struct.
|
||||
*/
|
||||
static void initialize_with_mpt(struct s2mpu *dev, struct mpt *mpt)
|
||||
{
|
||||
unsigned int gb, vid;
|
||||
struct fmpt *fmpt;
|
||||
|
||||
/* Must write CONTEXT_CFG_VALID_VID before setting L1ENTRY registers. */
|
||||
__set_context_ids(dev);
|
||||
|
||||
for_each_gb_and_vid(gb, vid) {
|
||||
fmpt = &mpt->fmpt[gb];
|
||||
__set_l1entry_l2table_addr(dev, gb, vid, __hyp_pa(fmpt->smpt));
|
||||
__set_l1entry_attr_with_fmpt(dev, gb, vid, fmpt);
|
||||
}
|
||||
__all_invalidation(dev);
|
||||
|
||||
/* Set control registers, enable the S2MPU. */
|
||||
__set_control_regs(dev);
|
||||
}
|
||||
|
||||
static bool s2mpu_host_smc_handler(struct kvm_cpu_context *host_ctxt)
|
||||
{
|
||||
DECLARE_REG(u64, fn, host_ctxt, 0);
|
||||
@@ -181,7 +226,7 @@ static bool s2mpu_host_smc_handler(struct kvm_cpu_context *host_ctxt)
|
||||
|
||||
if (mode == SMC_MODE_POWER_UP) {
|
||||
dev->power_state = S2MPU_POWER_ON;
|
||||
initialize_with_prot(dev, MPT_PROT_RW);
|
||||
initialize_with_mpt(dev, &kvm_hyp_host_mpt);
|
||||
} else {
|
||||
initialize_with_prot(dev, MPT_PROT_NONE);
|
||||
dev->power_state = S2MPU_POWER_OFF;
|
||||
@@ -197,6 +242,7 @@ static bool s2mpu_host_smc_handler(struct kvm_cpu_context *host_ctxt)
|
||||
static int s2mpu_init(void)
|
||||
{
|
||||
struct s2mpu *dev;
|
||||
unsigned int gb;
|
||||
int ret;
|
||||
|
||||
/* Map data structures in EL2 stage-1. */
|
||||
@@ -206,6 +252,15 @@ static int s2mpu_init(void)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for_each_gb(gb) {
|
||||
ret = pkvm_create_mappings(
|
||||
kvm_hyp_host_mpt.fmpt[gb].smpt,
|
||||
kvm_hyp_host_mpt.fmpt[gb].smpt + SMPT_NUM_WORDS,
|
||||
PAGE_HYP);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Map S2MPU MMIO regions in EL2 stage-1. */
|
||||
for_each_s2mpu(dev) {
|
||||
ret = __pkvm_create_private_mapping(
|
||||
@@ -219,7 +274,7 @@ static int s2mpu_init(void)
|
||||
* the blocking reset state as the bootloader may have programmed them.
|
||||
*/
|
||||
for_each_powered_s2mpu(dev)
|
||||
initialize_with_prot(dev, MPT_PROT_RW);
|
||||
initialize_with_mpt(dev, &kvm_hyp_host_mpt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -301,9 +301,54 @@ static int create_s2mpu_array(struct s2mpu **array)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int alloc_smpts(struct mpt *mpt)
|
||||
{
|
||||
unsigned int gb;
|
||||
|
||||
for_each_gb(gb) {
|
||||
/* The returned buffer is aligned to its size, as required. */
|
||||
mpt->fmpt[gb].smpt = (u32 *)__get_free_pages(GFP_KERNEL, SMPT_ORDER);
|
||||
if (!mpt->fmpt[gb].smpt)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void free_smpts(struct mpt *mpt)
|
||||
{
|
||||
unsigned int gb;
|
||||
|
||||
for_each_gb(gb)
|
||||
free_pages((unsigned long)mpt->fmpt[gb].smpt, SMPT_ORDER);
|
||||
}
|
||||
|
||||
static int init_host_mpt(struct mpt *mpt)
|
||||
{
|
||||
unsigned int gb;
|
||||
int ret;
|
||||
|
||||
ret = alloc_smpts(mpt);
|
||||
if (ret) {
|
||||
kvm_err("Cannot allocate memory for S2MPU host MPT");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Initialize the host MPT. Use 1G mappings with RW permissions. */
|
||||
for_each_gb(gb) {
|
||||
kvm_hyp_host_mpt.fmpt[gb] = (struct fmpt){
|
||||
.gran_1g = true,
|
||||
.prot = MPT_PROT_RW,
|
||||
.smpt = kern_hyp_va(mpt->fmpt[gb].smpt),
|
||||
};
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kvm_s2mpu_init(void)
|
||||
{
|
||||
struct s2mpu *s2mpus = NULL;
|
||||
struct mpt mpt = {};
|
||||
int ret;
|
||||
|
||||
ret = platform_driver_probe(&of_driver, s2mpu_probe);
|
||||
@@ -314,10 +359,16 @@ int kvm_s2mpu_init(void)
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = init_host_mpt(&mpt);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
kvm_info("S2MPU driver initialized\n");
|
||||
|
||||
out:
|
||||
if (ret)
|
||||
if (ret) {
|
||||
free_s2mpu_array(s2mpus);
|
||||
free_smpts(&mpt);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user