398 lines
10 KiB
C
398 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#define pr_fmt(fmt) "ioremap: " fmt
|
|
|
|
#include <linux/maple_tree.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/arm-smccc.h>
|
|
|
|
#include <asm/hypervisor.h>
|
|
|
|
#ifndef ARM_SMCCC_KVM_FUNC_MMIO_GUARD_INFO
|
|
#define ARM_SMCCC_KVM_FUNC_MMIO_GUARD_INFO 5
|
|
|
|
#define ARM_SMCCC_VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID \
|
|
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \
|
|
ARM_SMCCC_SMC_64, \
|
|
ARM_SMCCC_OWNER_VENDOR_HYP, \
|
|
ARM_SMCCC_KVM_FUNC_MMIO_GUARD_INFO)
|
|
#endif /* ARM_SMCCC_KVM_FUNC_MMIO_GUARD_INFO */
|
|
|
|
#ifndef ARM_SMCCC_KVM_FUNC_MMIO_GUARD_ENROLL
|
|
#define ARM_SMCCC_KVM_FUNC_MMIO_GUARD_ENROLL 6
|
|
|
|
#define ARM_SMCCC_VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID \
|
|
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \
|
|
ARM_SMCCC_SMC_64, \
|
|
ARM_SMCCC_OWNER_VENDOR_HYP, \
|
|
ARM_SMCCC_KVM_FUNC_MMIO_GUARD_ENROLL)
|
|
#endif /* ARM_SMCCC_KVM_FUNC_MMIO_GUARD_ENROLL */
|
|
|
|
#ifndef ARM_SMCCC_KVM_FUNC_MMIO_GUARD_MAP
|
|
#define ARM_SMCCC_KVM_FUNC_MMIO_GUARD_MAP 7
|
|
|
|
#define ARM_SMCCC_VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID \
|
|
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \
|
|
ARM_SMCCC_SMC_64, \
|
|
ARM_SMCCC_OWNER_VENDOR_HYP, \
|
|
ARM_SMCCC_KVM_FUNC_MMIO_GUARD_MAP)
|
|
#endif /* ARM_SMCCC_KVM_FUNC_MMIO_GUARD_MAP */
|
|
|
|
#ifndef ARM_SMCCC_KVM_FUNC_MMIO_GUARD_UNMAP
|
|
#define ARM_SMCCC_KVM_FUNC_MMIO_GUARD_UNMAP 8
|
|
|
|
#define ARM_SMCCC_VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID \
|
|
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \
|
|
ARM_SMCCC_SMC_64, \
|
|
ARM_SMCCC_OWNER_VENDOR_HYP, \
|
|
ARM_SMCCC_KVM_FUNC_MMIO_GUARD_UNMAP)
|
|
#endif /* ARM_SMCCC_KVM_FUNC_MMIO_GUARD_UNMAP */
|
|
|
|
#ifndef ARM_SMCCC_KVM_FUNC_MMIO_RGUARD_MAP
|
|
#define ARM_SMCCC_KVM_FUNC_MMIO_RGUARD_MAP 10
|
|
|
|
#define ARM_SMCCC_VENDOR_HYP_KVM_MMIO_RGUARD_MAP_FUNC_ID \
|
|
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \
|
|
ARM_SMCCC_SMC_64, \
|
|
ARM_SMCCC_OWNER_VENDOR_HYP, \
|
|
ARM_SMCCC_KVM_FUNC_MMIO_RGUARD_MAP)
|
|
#endif /* ARM_SMCCC_KVM_FUNC_MMIO_GUARD_UNMAP */
|
|
|
|
#ifndef ARM_SMCCC_KVM_FUNC_MMIO_RGUARD_UNMAP
|
|
#define ARM_SMCCC_KVM_FUNC_MMIO_RGUARD_UNMAP 11
|
|
|
|
#define ARM_SMCCC_VENDOR_HYP_KVM_MMIO_RGUARD_UNMAP_FUNC_ID \
|
|
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \
|
|
ARM_SMCCC_SMC_64, \
|
|
ARM_SMCCC_OWNER_VENDOR_HYP, \
|
|
ARM_SMCCC_KVM_FUNC_MMIO_RGUARD_UNMAP)
|
|
#endif /* ARM_SMCCC_KVM_FUNC_MMIO_GUARD_UNMAP */
|
|
|
|
static DEFINE_STATIC_KEY_FALSE(ioremap_guard_key);
|
|
static DEFINE_MTREE(ioremap_guard_refcount);
|
|
static DEFINE_MUTEX(ioremap_guard_lock);
|
|
|
|
static bool ioremap_guard __ro_after_init;
|
|
static size_t guard_granule __ro_after_init;
|
|
static bool guard_has_range __ro_after_init;
|
|
|
|
static int __init ioremap_guard_setup(char *str)
|
|
{
|
|
ioremap_guard = true;
|
|
|
|
return 0;
|
|
}
|
|
early_param("ioremap_guard", ioremap_guard_setup);
|
|
|
|
void kvm_init_ioremap_services(void)
|
|
{
|
|
struct arm_smccc_res res;
|
|
size_t granule;
|
|
|
|
if (!ioremap_guard)
|
|
return;
|
|
|
|
/* We need all the functions to be implemented */
|
|
if (!kvm_arm_hyp_service_available(ARM_SMCCC_KVM_FUNC_MMIO_GUARD_INFO) ||
|
|
!kvm_arm_hyp_service_available(ARM_SMCCC_KVM_FUNC_MMIO_GUARD_ENROLL) ||
|
|
!kvm_arm_hyp_service_available(ARM_SMCCC_KVM_FUNC_MMIO_GUARD_MAP) ||
|
|
!kvm_arm_hyp_service_available(ARM_SMCCC_KVM_FUNC_MMIO_GUARD_UNMAP))
|
|
return;
|
|
|
|
arm_smccc_1_1_invoke(ARM_SMCCC_VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID,
|
|
0, 0, 0, &res);
|
|
granule = res.a0;
|
|
if (!granule || (granule & (granule - 1))) {
|
|
pr_warn("KVM MMIO guard initialization failed: "
|
|
"guard granule (%lu), page size (%lu)\n",
|
|
granule, PAGE_SIZE);
|
|
return;
|
|
}
|
|
|
|
guard_has_range = res.a1 & KVM_FUNC_HAS_RANGE;
|
|
|
|
arm_smccc_1_1_invoke(ARM_SMCCC_VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID,
|
|
&res);
|
|
if (res.a0 == SMCCC_RET_SUCCESS) {
|
|
guard_granule = granule;
|
|
static_branch_enable(&ioremap_guard_key);
|
|
pr_info("Using KVM MMIO guard for ioremap\n");
|
|
} else {
|
|
pr_warn("KVM MMIO guard registration failed (%ld)\n", res.a0);
|
|
}
|
|
}
|
|
|
|
static int __invoke_mmioguard(phys_addr_t phys_addr, int nr_granules, bool map,
|
|
int *done)
|
|
{
|
|
u64 nr_granules_arg = guard_has_range ? nr_granules : 0;
|
|
struct arm_smccc_res res;
|
|
u32 func_id;
|
|
|
|
if (guard_has_range && map)
|
|
func_id = ARM_SMCCC_VENDOR_HYP_KVM_MMIO_RGUARD_MAP_FUNC_ID;
|
|
else if (guard_has_range && !map)
|
|
func_id = ARM_SMCCC_VENDOR_HYP_KVM_MMIO_RGUARD_UNMAP_FUNC_ID;
|
|
/* Legacy kernels */
|
|
else if (!guard_has_range && map)
|
|
func_id = ARM_SMCCC_VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID;
|
|
else
|
|
func_id = ARM_SMCCC_VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID;
|
|
|
|
arm_smccc_1_1_hvc(func_id, phys_addr, nr_granules_arg, 0, &res);
|
|
if (res.a0 != SMCCC_RET_SUCCESS)
|
|
return -EINVAL;
|
|
|
|
*done = guard_has_range ? res.a1 : 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __do_xmap_granules(phys_addr_t phys_addr, int nr_granules, bool map)
|
|
{
|
|
int ret, nr_xmapped = 0, __nr_xmapped;
|
|
|
|
while (nr_granules) {
|
|
ret = __invoke_mmioguard(phys_addr, nr_granules, map,
|
|
&__nr_xmapped);
|
|
if (ret)
|
|
break;
|
|
|
|
nr_xmapped += __nr_xmapped;
|
|
|
|
if (WARN_ON(__nr_xmapped > nr_granules))
|
|
break;
|
|
|
|
phys_addr += __nr_xmapped * guard_granule;
|
|
nr_granules -= __nr_xmapped;
|
|
}
|
|
|
|
return nr_xmapped;
|
|
}
|
|
|
|
static int ioremap_unregister_phys_range(phys_addr_t phys_addr, size_t size)
|
|
{
|
|
int nr_granules, unmapped;
|
|
|
|
if (!IS_ALIGNED(phys_addr, guard_granule) ||
|
|
size % guard_granule)
|
|
return -ERANGE;
|
|
|
|
nr_granules = size / guard_granule;
|
|
|
|
unmapped = __do_xmap_granules(phys_addr, nr_granules, false);
|
|
|
|
return unmapped == nr_granules ? 0 : -EINVAL;
|
|
}
|
|
|
|
static int ioremap_register_phys_range(phys_addr_t phys_addr, size_t size)
|
|
{
|
|
int nr_granules, mapped;
|
|
|
|
if (!IS_ALIGNED(phys_addr, guard_granule) ||
|
|
size % guard_granule)
|
|
return -ERANGE;
|
|
|
|
nr_granules = size / guard_granule;
|
|
|
|
mapped = __do_xmap_granules(phys_addr, nr_granules, true);
|
|
if (mapped != nr_granules) {
|
|
pr_err("Failed to register %llx:%llx\n",
|
|
phys_addr, phys_addr + size);
|
|
|
|
WARN_ON(ioremap_unregister_phys_range(phys_addr,
|
|
mapped * guard_granule));
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long mas_end(phys_addr_t phys_addr, size_t size)
|
|
{
|
|
return phys_addr + (unsigned long)size - 1;
|
|
}
|
|
|
|
static size_t mas_size(const struct ma_state *mas)
|
|
{
|
|
return mas->last - mas->index + 1;
|
|
}
|
|
|
|
static int mas_intersect(struct ma_state *mas, phys_addr_t phys_addr, size_t size)
|
|
{
|
|
unsigned long start = max(mas->index, (unsigned long)phys_addr);
|
|
unsigned long end = min(mas->last, mas_end(phys_addr, size));
|
|
|
|
/* No intersection */
|
|
if (WARN_ON(mas->last < (unsigned long)phys_addr) ||
|
|
WARN_ON(mas->index > mas_end(phys_addr, size)))
|
|
return -ERANGE;
|
|
|
|
mas_set_range(mas, start, end);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mas_store_refcount(struct ma_state *mas, int count)
|
|
{
|
|
int ret;
|
|
|
|
/*
|
|
* It is acceptable for the allocation to fail, specially
|
|
* if trying to ioremap something very early on, like with
|
|
* earlycon, which happens long before kmem_cache_init.
|
|
* This page will be permanently accessible, similar to a
|
|
* saturated refcount.
|
|
*/
|
|
if (!slab_is_available())
|
|
return 0;
|
|
|
|
ret = mas_store_gfp(mas, xa_mk_value(count), GFP_KERNEL);
|
|
if (ret) {
|
|
pr_err("Failed to set refcount for 0x%lx:0x%lx\n",
|
|
mas->index, mas->last + 1);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ioremap_phys_range_hook(phys_addr_t phys_addr, size_t size, pgprot_t prot)
|
|
{
|
|
MA_STATE(mas, &ioremap_guard_refcount, phys_addr, ULONG_MAX);
|
|
|
|
if (!static_branch_unlikely(&ioremap_guard_key))
|
|
return;
|
|
|
|
VM_BUG_ON(!PAGE_ALIGNED(phys_addr) || !PAGE_ALIGNED(size));
|
|
|
|
size = ALIGN(size, guard_granule);
|
|
phys_addr = ALIGN_DOWN(phys_addr, guard_granule);
|
|
|
|
mutex_lock(&ioremap_guard_lock);
|
|
mas_lock(&mas);
|
|
|
|
while (size) {
|
|
void *entry = mas_find(&mas, mas_end(phys_addr, size));
|
|
size_t sub_size = size;
|
|
int ret;
|
|
|
|
if (entry) {
|
|
if (mas.index <= phys_addr) {
|
|
mas_intersect(&mas, phys_addr, size);
|
|
sub_size = mas_size(&mas);
|
|
mas_store_refcount(&mas, xa_to_value(entry) + 1);
|
|
goto next;
|
|
}
|
|
|
|
sub_size = mas.last - phys_addr + 1;
|
|
}
|
|
|
|
/* Newly guarded region */
|
|
ret = ioremap_register_phys_range(phys_addr, sub_size);
|
|
if (ret)
|
|
break;
|
|
|
|
mas_set_range(&mas, phys_addr, mas_end(phys_addr, sub_size));
|
|
mas_store_refcount(&mas, 1);
|
|
next:
|
|
size = size_sub(size, sub_size);
|
|
phys_addr += sub_size;
|
|
}
|
|
|
|
mas_unlock(&mas);
|
|
mutex_unlock(&ioremap_guard_lock);
|
|
}
|
|
|
|
void iounmap_phys_range_hook(phys_addr_t phys_addr, size_t size)
|
|
{
|
|
MA_STATE(mas, &ioremap_guard_refcount, phys_addr, ULONG_MAX);
|
|
|
|
if (!static_branch_unlikely(&ioremap_guard_key))
|
|
return;
|
|
|
|
VM_BUG_ON(!PAGE_ALIGNED(phys_addr) || !PAGE_ALIGNED(size));
|
|
|
|
size = ALIGN(size, guard_granule);
|
|
phys_addr = ALIGN_DOWN(phys_addr, guard_granule);
|
|
|
|
mutex_lock(&ioremap_guard_lock);
|
|
mas_lock(&mas);
|
|
|
|
while (size) {
|
|
void *entry = mas_find(&mas, phys_addr + size - 1);
|
|
unsigned long refcount;
|
|
size_t sub_size = size;
|
|
|
|
/*
|
|
* Untracked region, could happen if registered before
|
|
* slab_is_available(). Ignore.
|
|
*/
|
|
if (!entry)
|
|
break;
|
|
|
|
if (mas.index > phys_addr) {
|
|
sub_size = mas.index - phys_addr;
|
|
goto next;
|
|
}
|
|
|
|
refcount = xa_to_value(entry);
|
|
if (WARN_ON(!refcount))
|
|
break;
|
|
|
|
mas_intersect(&mas, phys_addr, size);
|
|
sub_size = mas_size(&mas);
|
|
|
|
if (refcount == 1) {
|
|
if (WARN_ON(ioremap_unregister_phys_range(phys_addr, sub_size)))
|
|
break;
|
|
|
|
/* Split the existing mas if needed before deletion */
|
|
mas_store_refcount(&mas, refcount - 1);
|
|
mas_erase(&mas);
|
|
} else {
|
|
mas_store_refcount(&mas, refcount - 1);
|
|
}
|
|
next:
|
|
size = size_sub(size, sub_size);
|
|
phys_addr += sub_size;
|
|
}
|
|
|
|
mas_unlock(&mas);
|
|
mutex_unlock(&ioremap_guard_lock);
|
|
}
|
|
|
|
void __iomem *ioremap_prot(phys_addr_t phys_addr, size_t size,
|
|
unsigned long prot)
|
|
{
|
|
unsigned long last_addr = phys_addr + size - 1;
|
|
|
|
/* Don't allow outside PHYS_MASK */
|
|
if (last_addr & ~PHYS_MASK)
|
|
return NULL;
|
|
|
|
/* Don't allow RAM to be mapped. */
|
|
if (WARN_ON(pfn_is_map_memory(__phys_to_pfn(phys_addr))))
|
|
return NULL;
|
|
|
|
return generic_ioremap_prot(phys_addr, size, __pgprot(prot));
|
|
}
|
|
EXPORT_SYMBOL(ioremap_prot);
|
|
|
|
/*
|
|
* Must be called after early_fixmap_init
|
|
*/
|
|
void __init early_ioremap_init(void)
|
|
{
|
|
early_ioremap_setup();
|
|
}
|
|
|
|
bool arch_memremap_can_ram_remap(resource_size_t offset, size_t size,
|
|
unsigned long flags)
|
|
{
|
|
unsigned long pfn = PHYS_PFN(offset);
|
|
|
|
return pfn_is_map_memory(pfn);
|
|
}
|