mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-06 10:58:48 +09:00
ANDROID: KVM: arm64: s2mpu: Remove all EL1 code
EL2 S2MPU driver relied on EL1 code which parsed the DT and populated
EL2 driver data before deprivileging of the host. The driver is now
moving to later initialization from kernel modules, which will take over
the role of parsing the DT and power management. Remove the unused code.
Bug: 190463801
Change-Id: Ie6e21ba02b84494e5066c7681f85612a09f93f6d
Signed-off-by: David Brazdil <dbrazdil@google.com>
(cherry picked from commit 167332a9fa)
Signed-off-by: Mostafa Saleh <smostafa@google.com>
This commit is contained in:
committed by
Mostafa Saleh
parent
23fba443b3
commit
cfdfc4b47d
@@ -384,15 +384,8 @@ extern u64 kvm_nvhe_sym(hyp_cpu_logical_map)[NR_CPUS];
|
||||
|
||||
enum kvm_iommu_driver {
|
||||
KVM_IOMMU_DRIVER_NONE,
|
||||
KVM_IOMMU_DRIVER_S2MPU,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_KVM_S2MPU
|
||||
int kvm_s2mpu_init(void);
|
||||
#else
|
||||
static inline int kvm_s2mpu_init(void) { return -ENODEV; }
|
||||
#endif
|
||||
|
||||
enum pkvm_iommu_driver_id {
|
||||
PKVM_IOMMU_NR_DRIVERS,
|
||||
};
|
||||
|
||||
@@ -189,15 +189,6 @@ enum mpt_update_flags {
|
||||
MPT_UPDATE_L2 = BIT(1),
|
||||
};
|
||||
|
||||
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)
|
||||
|
||||
/* Set protection bits of SMPT in a given range without using memset. */
|
||||
static inline void __set_smpt_range_slow(u32 *smpt, size_t start_gb_byte,
|
||||
size_t end_gb_byte, enum mpt_prot prot)
|
||||
|
||||
@@ -1927,13 +1927,7 @@ static bool init_psci_relay(void)
|
||||
|
||||
static int init_stage2_iommu(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = kvm_s2mpu_init();
|
||||
if (!ret)
|
||||
return KVM_IOMMU_DRIVER_S2MPU;
|
||||
|
||||
return (ret == -ENODEV) ? KVM_IOMMU_DRIVER_NONE : ret;
|
||||
return KVM_IOMMU_DRIVER_NONE;
|
||||
}
|
||||
|
||||
static int init_subsystems(void)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#define PA_MAX ((phys_addr_t)SZ_1G * NR_GIGABYTES)
|
||||
|
||||
#define for_each_s2mpu(i) \
|
||||
for ((i) = &kvm_hyp_s2mpus[0]; (i) != &kvm_hyp_s2mpus[kvm_hyp_nr_s2mpus]; (i)++)
|
||||
for ((i) = &s2mpus[0]; (i) != &s2mpus[nr_s2mpus]; (i)++)
|
||||
|
||||
#define for_each_powered_s2mpu(i) \
|
||||
for_each_s2mpu((i)) if (is_powered_on((i)))
|
||||
@@ -34,11 +34,10 @@
|
||||
(CONTEXT_CFG_VALID_VID_CTX_VID(ctxid, vid) \
|
||||
| (((ctxid) < (nr_ctx)) ? CONTEXT_CFG_VALID_VID_CTX_VALID(ctxid) : 0))
|
||||
|
||||
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;
|
||||
static size_t __ro_after_init nr_s2mpus;
|
||||
static struct s2mpu __ro_after_init *s2mpus;
|
||||
static struct mpt host_mpt;
|
||||
static hyp_spinlock_t s2mpu_lock;
|
||||
|
||||
static bool is_version(struct s2mpu *dev, u32 version)
|
||||
{
|
||||
@@ -349,7 +348,7 @@ static void s2mpu_host_stage2_set_owner(phys_addr_t addr, size_t size,
|
||||
return;
|
||||
|
||||
hyp_spin_lock(&s2mpu_lock);
|
||||
set_mpt_range_locked(&kvm_hyp_host_mpt,
|
||||
set_mpt_range_locked(&host_mpt,
|
||||
ALIGN_DOWN(addr, SMPT_GRAN),
|
||||
ALIGN(addr + size, SMPT_GRAN) - 1,
|
||||
prot);
|
||||
@@ -427,7 +426,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;
|
||||
ret = initialize_with_mpt(dev, &kvm_hyp_host_mpt);
|
||||
ret = initialize_with_mpt(dev, &host_mpt);
|
||||
} else {
|
||||
ret = initialize_with_prot(dev, MPT_PROT_NONE);
|
||||
dev->power_state = S2MPU_POWER_OFF;
|
||||
@@ -512,16 +511,16 @@ static int s2mpu_init(void)
|
||||
int ret;
|
||||
|
||||
/* Map data structures in EL2 stage-1. */
|
||||
ret = pkvm_create_mappings(kvm_hyp_s2mpus,
|
||||
kvm_hyp_s2mpus + kvm_hyp_nr_s2mpus,
|
||||
ret = pkvm_create_mappings(s2mpus,
|
||||
s2mpus + nr_s2mpus,
|
||||
PAGE_HYP);
|
||||
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,
|
||||
host_mpt.fmpt[gb].smpt,
|
||||
host_mpt.fmpt[gb].smpt + SMPT_NUM_WORDS,
|
||||
PAGE_HYP);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -540,7 +539,7 @@ static int s2mpu_init(void)
|
||||
* the blocking reset state as the bootloader may have programmed them.
|
||||
*/
|
||||
for_each_powered_s2mpu(dev) {
|
||||
ret = initialize_with_mpt(dev, &kvm_hyp_host_mpt);
|
||||
ret = initialize_with_mpt(dev, &host_mpt);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -307,12 +307,6 @@ static int select_iommu_ops(enum kvm_iommu_driver driver)
|
||||
switch (driver) {
|
||||
case KVM_IOMMU_DRIVER_NONE:
|
||||
return 0;
|
||||
case KVM_IOMMU_DRIVER_S2MPU:
|
||||
if (IS_ENABLED(CONFIG_KVM_S2MPU)) {
|
||||
kvm_iommu_ops = kvm_s2mpu_ops;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
|
||||
@@ -4,311 +4,5 @@
|
||||
* Author: David Brazdil <dbrazdil@google.com>
|
||||
*/
|
||||
|
||||
#include <linux/io-64-nonatomic-hi-lo.h>
|
||||
#include <linux/kvm_host.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/sort.h>
|
||||
|
||||
#include <asm/kvm_mmu.h>
|
||||
#include <asm/kvm_s2mpu.h>
|
||||
|
||||
struct s2mpu_irq_info {
|
||||
struct device *dev;
|
||||
void __iomem *va;
|
||||
};
|
||||
|
||||
struct s2mpu_list_entry {
|
||||
struct list_head list;
|
||||
struct device *dev;
|
||||
struct s2mpu info;
|
||||
};
|
||||
|
||||
static LIST_HEAD(s2mpu_list);
|
||||
|
||||
static irqreturn_t s2mpu_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct s2mpu_irq_info *info = data;
|
||||
unsigned int vid;
|
||||
u32 vid_bmap, fault_info;
|
||||
phys_addr_t fault_pa;
|
||||
const char *fault_type;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
|
||||
while ((vid_bmap = readl_relaxed(info->va + REG_NS_FAULT_STATUS))) {
|
||||
WARN_ON_ONCE(vid_bmap & (~ALL_VIDS_BITMAP));
|
||||
vid = __ffs(vid_bmap);
|
||||
|
||||
fault_pa = hi_lo_readq_relaxed(info->va + REG_NS_FAULT_PA_HIGH_LOW(vid));
|
||||
fault_info = readl_relaxed(info->va + REG_NS_FAULT_INFO(vid));
|
||||
WARN_ON(FIELD_GET(FAULT_INFO_VID_MASK, fault_info) != vid);
|
||||
|
||||
switch (FIELD_GET(FAULT_INFO_TYPE_MASK, fault_info)) {
|
||||
case FAULT_INFO_TYPE_MPTW:
|
||||
fault_type = "MPTW fault";
|
||||
break;
|
||||
case FAULT_INFO_TYPE_AP:
|
||||
fault_type = "access permission fault";
|
||||
break;
|
||||
case FAULT_INFO_TYPE_CONTEXT:
|
||||
fault_type = "context fault";
|
||||
break;
|
||||
default:
|
||||
fault_type = "unknown fault";
|
||||
break;
|
||||
}
|
||||
|
||||
dev_err(info->dev, "\n"
|
||||
"============== S2MPU FAULT DETECTED ==============\n"
|
||||
" PA=0x%pap, FAULT_INFO=0x%08x\n"
|
||||
" DIRECTION: %s, TYPE: %s\n"
|
||||
" VID=%u, REQ_LENGTH=%lu, REQ_AXI_ID=%lu\n"
|
||||
"==================================================\n",
|
||||
&fault_pa, fault_info,
|
||||
(fault_info & FAULT_INFO_RW_BIT) ? "write" : "read",
|
||||
fault_type, vid,
|
||||
FIELD_GET(FAULT_INFO_LEN_MASK, fault_info),
|
||||
FIELD_GET(FAULT_INFO_ID_MASK, fault_info));
|
||||
|
||||
writel_relaxed(BIT(vid), info->va + REG_NS_INTERRUPT_CLEAR);
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse interrupt information from DT and if found, register IRQ handler.
|
||||
* This is considered optional and will not fail even if the initialization is
|
||||
* unsuccessful. In that case the IRQ will remain masked.
|
||||
*/
|
||||
static void s2mpu_probe_irq(struct platform_device *pdev, void __iomem *kaddr)
|
||||
{
|
||||
struct s2mpu_irq_info *irq_info;
|
||||
int ret, irq;
|
||||
|
||||
irq = platform_get_irq_optional(pdev, 0);
|
||||
|
||||
if (irq == -ENXIO)
|
||||
return; /* No IRQ specified. */
|
||||
|
||||
if (irq < 0) {
|
||||
/* IRQ specified but failed to parse. */
|
||||
dev_err(&pdev->dev, "failed to parse IRQ, IRQ not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
irq_info = devm_kmalloc(&pdev->dev, sizeof(*irq_info), GFP_KERNEL);
|
||||
if (!irq_info)
|
||||
return;
|
||||
|
||||
*irq_info = (struct s2mpu_irq_info){
|
||||
.dev = &pdev->dev,
|
||||
.va = kaddr,
|
||||
};
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, irq, s2mpu_irq_handler, 0,
|
||||
dev_name(&pdev->dev), irq_info);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register IRQ, IRQ not enabled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static int s2mpu_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
void __iomem *kaddr;
|
||||
size_t res_size;
|
||||
struct s2mpu_list_entry *entry;
|
||||
struct s2mpu *info;
|
||||
int ret;
|
||||
|
||||
entry = devm_kzalloc(&pdev->dev, sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
entry->dev = &pdev->dev;
|
||||
info = &entry->info;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "failed to parse 'reg'");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* devm_ioremap_resource internally calls devm_request_mem_region. */
|
||||
kaddr = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(kaddr)) {
|
||||
dev_err(&pdev->dev, "could not ioremap resource: %ld",
|
||||
PTR_ERR(kaddr));
|
||||
return PTR_ERR(kaddr);
|
||||
}
|
||||
|
||||
if (!PAGE_ALIGNED(res->start)) {
|
||||
dev_err(&pdev->dev, "base address must be page-aligned (0x%llx)",
|
||||
res->start);
|
||||
return -EINVAL;
|
||||
}
|
||||
info->pa = res->start;
|
||||
|
||||
res_size = resource_size(res);
|
||||
if (res_size != S2MPU_MMIO_SIZE) {
|
||||
dev_err(&pdev->dev,
|
||||
"unexpected device region size (expected=%u, actual=%lu)",
|
||||
S2MPU_MMIO_SIZE, res_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(pdev->dev.of_node, "power-domain-id",
|
||||
&info->power_domain_id);
|
||||
if (!ret) {
|
||||
info->power_state = S2MPU_POWER_ON;
|
||||
} else if (ret != -EINVAL) {
|
||||
dev_err(&pdev->dev, "failed to parse power-domain-id: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to parse IRQ information. This is optional as it only affects
|
||||
* runtime fault reporting, and therefore errors do not fail the whole
|
||||
* driver initialization.
|
||||
*/
|
||||
s2mpu_probe_irq(pdev, kaddr);
|
||||
|
||||
/* Insert successfully parsed devices to a list later copied to hyp. */
|
||||
list_add_tail(&entry->list, &s2mpu_list);
|
||||
kvm_hyp_nr_s2mpus++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id of_table[] = {
|
||||
{ .compatible = "google,s2mpu" },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver of_driver = {
|
||||
.driver = {
|
||||
.name = "kvm,s2mpu",
|
||||
.of_match_table = of_table,
|
||||
},
|
||||
};
|
||||
|
||||
static struct s2mpu *alloc_s2mpu_array(void)
|
||||
{
|
||||
unsigned int order;
|
||||
|
||||
order = get_order(kvm_hyp_nr_s2mpus * sizeof(struct s2mpu));
|
||||
return (struct s2mpu *)__get_free_pages(GFP_KERNEL, order);
|
||||
}
|
||||
|
||||
static void free_s2mpu_array(struct s2mpu *array)
|
||||
{
|
||||
unsigned int order;
|
||||
|
||||
order = get_order(kvm_hyp_nr_s2mpus * sizeof(struct s2mpu));
|
||||
free_pages((unsigned long)array, order);
|
||||
}
|
||||
|
||||
static int cmp_s2mpu(const void *p1, const void *p2)
|
||||
{
|
||||
const struct s2mpu *a = p1, *b = p2;
|
||||
|
||||
return (a->pa > b->pa) - (a->pa < b->pa);
|
||||
}
|
||||
|
||||
static int create_s2mpu_array(struct s2mpu **array)
|
||||
{
|
||||
struct s2mpu_list_entry *entry, *tmp;
|
||||
size_t i;
|
||||
|
||||
*array = alloc_s2mpu_array();
|
||||
if (!*array)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Copy list to hyp array and destroy the list in the process. */
|
||||
i = 0;
|
||||
list_for_each_entry_safe(entry, tmp, &s2mpu_list, list) {
|
||||
(*array)[i++] = entry->info;
|
||||
list_del(&entry->list);
|
||||
devm_kfree(entry->dev, entry);
|
||||
}
|
||||
WARN_ON(i != kvm_hyp_nr_s2mpus);
|
||||
|
||||
/* Searching through the list assumes that it is sorted. */
|
||||
sort(*array, kvm_hyp_nr_s2mpus, sizeof(struct s2mpu), cmp_s2mpu, NULL);
|
||||
|
||||
kvm_hyp_s2mpus = kern_hyp_va(*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);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = create_s2mpu_array(&s2mpus);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = init_host_mpt(&mpt);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
kvm_info("S2MPU driver initialized\n");
|
||||
|
||||
out:
|
||||
if (ret) {
|
||||
free_s2mpu_array(s2mpus);
|
||||
free_smpts(&mpt);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user