diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h index 72a7c4a58fa6..c7ba12748591 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h +++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h @@ -68,6 +68,9 @@ int __pkvm_host_share_guest(u64 pfn, u64 gfn, struct kvm_vcpu *vcpu); int __pkvm_host_donate_guest(u64 pfn, u64 gfn, struct kvm_vcpu *vcpu); int __pkvm_guest_share_host(struct kvm_vcpu *vcpu, u64 ipa); int __pkvm_guest_unshare_host(struct kvm_vcpu *vcpu, u64 ipa); +int __pkvm_install_ioguard_page(struct kvm_vcpu *vcpu, u64 ipa); +int __pkvm_remove_ioguard_page(struct kvm_vcpu *vcpu, u64 ipa); +bool __pkvm_check_ioguard_page(struct kvm_vcpu *vcpu); bool addr_is_memory(phys_addr_t phys); int host_stage2_idmap_locked(phys_addr_t addr, u64 size, enum kvm_pgtable_prot prot); diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c index 6025f18c910a..88293c4e9f3a 100644 --- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c +++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c @@ -1819,3 +1819,108 @@ unlock: return ret; } + +/* Replace this with something more structured once day */ +#define MMIO_NOTE (('M' << 24 | 'M' << 16 | 'I' << 8 | 'O') << 1) + +static bool __check_ioguard_page(struct kvm_vcpu *vcpu, u64 ipa) +{ + struct kvm_shadow_vm *vm = vcpu->arch.pkvm.shadow_vm; + kvm_pte_t pte; + u32 level; + int ret; + + ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level); + if (ret) + return false; + + /* Must be a PAGE_SIZE mapping with our annotation */ + return (BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(level)) == PAGE_SIZE && + pte == MMIO_NOTE); +} + +int __pkvm_install_ioguard_page(struct kvm_vcpu *vcpu, u64 ipa) +{ + struct kvm_shadow_vm *vm; + kvm_pte_t pte; + u32 level; + int ret; + + vm = vcpu->arch.pkvm.shadow_vm; + + if (!test_bit(KVM_ARCH_FLAG_MMIO_GUARD, &vm->arch.flags)) + return -EINVAL; + + if (ipa & ~PAGE_MASK) + return -EINVAL; + + guest_lock_component(vcpu); + + ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level); + if (ret) + goto unlock; + + if (pte && BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(level)) == PAGE_SIZE) { + /* + * Already flagged as MMIO, let's accept it, and fail + * otherwise + */ + if (pte != MMIO_NOTE) + ret = -EBUSY; + + goto unlock; + } + + ret = kvm_pgtable_stage2_annotate(&vm->pgt, ipa, PAGE_SIZE, + &vcpu->arch.pkvm_memcache, + MMIO_NOTE); + +unlock: + guest_unlock_component(vcpu); + return ret; +} + +int __pkvm_remove_ioguard_page(struct kvm_vcpu *vcpu, u64 ipa) +{ + struct kvm_shadow_vm *vm = vcpu->arch.pkvm.shadow_vm; + + if (!test_bit(KVM_ARCH_FLAG_MMIO_GUARD, &vm->arch.flags)) + return -EINVAL; + + guest_lock_component(vcpu); + + if (__check_ioguard_page(vcpu, ipa)) { + struct kvm_shadow_vm *vm = vcpu->arch.pkvm.shadow_vm; + + kvm_pgtable_stage2_unmap(&vm->pgt, + ALIGN_DOWN(ipa, PAGE_SIZE), PAGE_SIZE); + } + + guest_unlock_component(vcpu); + return 0; +} + +bool __pkvm_check_ioguard_page(struct kvm_vcpu *vcpu) +{ + struct kvm_shadow_vm *vm = vcpu->arch.pkvm.shadow_vm; + u64 ipa, end; + bool ret; + + if (!kvm_vcpu_dabt_isvalid(vcpu)) + return false; + + if (!test_bit(KVM_ARCH_FLAG_MMIO_GUARD, &vm->arch.flags)) + return true; + + ipa = kvm_vcpu_get_fault_ipa(vcpu); + ipa |= kvm_vcpu_get_hfar(vcpu) & FAR_MASK; + end = ipa + kvm_vcpu_dabt_get_as(vcpu) - 1; + + guest_lock_component(vcpu); + ret = __check_ioguard_page(vcpu, ipa); + if ((end & PAGE_MASK) != (ipa & PAGE_MASK)) + ret &= __check_ioguard_page(vcpu, end); + guest_unlock_component(vcpu); + + return ret; +}