| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2021 Google LLC |
| * Author: Fuad Tabba <tabba@google.com> |
| */ |
| |
| #include <linux/kvm_host.h> |
| #include <linux/mm.h> |
| #include <nvhe/fixed_config.h> |
| #include <nvhe/trap_handler.h> |
| |
| /* |
| * Set trap register values based on features in ID_AA64PFR0. |
| */ |
| static void pvm_init_traps_aa64pfr0(struct kvm_vcpu *vcpu) |
| { |
| const u64 feature_ids = pvm_read_id_reg(vcpu, SYS_ID_AA64PFR0_EL1); |
| u64 hcr_set = HCR_RW; |
| u64 hcr_clear = 0; |
| u64 cptr_set = 0; |
| |
| /* Protected KVM does not support AArch32 guests. */ |
| BUILD_BUG_ON(FIELD_GET(ARM64_FEATURE_MASK(ID_AA64PFR0_EL0), |
| PVM_ID_AA64PFR0_RESTRICT_UNSIGNED) != ID_AA64PFR0_ELx_64BIT_ONLY); |
| BUILD_BUG_ON(FIELD_GET(ARM64_FEATURE_MASK(ID_AA64PFR0_EL1), |
| PVM_ID_AA64PFR0_RESTRICT_UNSIGNED) != ID_AA64PFR0_ELx_64BIT_ONLY); |
| |
| /* |
| * Linux guests assume support for floating-point and Advanced SIMD. Do |
| * not change the trapping behavior for these from the KVM default. |
| */ |
| BUILD_BUG_ON(!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64PFR0_FP), |
| PVM_ID_AA64PFR0_ALLOW)); |
| BUILD_BUG_ON(!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64PFR0_ASIMD), |
| PVM_ID_AA64PFR0_ALLOW)); |
| |
| /* Trap RAS unless all current versions are supported */ |
| if (FIELD_GET(ARM64_FEATURE_MASK(ID_AA64PFR0_RAS), feature_ids) < |
| ID_AA64PFR0_RAS_V1P1) { |
| hcr_set |= HCR_TERR | HCR_TEA; |
| hcr_clear |= HCR_FIEN; |
| } |
| |
| /* Trap AMU */ |
| if (!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64PFR0_AMU), feature_ids)) { |
| hcr_clear |= HCR_AMVOFFEN; |
| cptr_set |= CPTR_EL2_TAM; |
| } |
| |
| /* Trap SVE */ |
| if (!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64PFR0_SVE), feature_ids)) |
| cptr_set |= CPTR_EL2_TZ; |
| |
| vcpu->arch.hcr_el2 |= hcr_set; |
| vcpu->arch.hcr_el2 &= ~hcr_clear; |
| vcpu->arch.cptr_el2 |= cptr_set; |
| } |
| |
| /* |
| * Set trap register values based on features in ID_AA64PFR1. |
| */ |
| static void pvm_init_traps_aa64pfr1(struct kvm_vcpu *vcpu) |
| { |
| const u64 feature_ids = pvm_read_id_reg(vcpu, SYS_ID_AA64PFR1_EL1); |
| u64 hcr_set = 0; |
| u64 hcr_clear = 0; |
| |
| /* Memory Tagging: Trap and Treat as Untagged if not supported. */ |
| if (!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64PFR1_MTE), feature_ids)) { |
| hcr_set |= HCR_TID5; |
| hcr_clear |= HCR_DCT | HCR_ATA; |
| } |
| |
| vcpu->arch.hcr_el2 |= hcr_set; |
| vcpu->arch.hcr_el2 &= ~hcr_clear; |
| } |
| |
| /* |
| * Set trap register values based on features in ID_AA64DFR0. |
| */ |
| static void pvm_init_traps_aa64dfr0(struct kvm_vcpu *vcpu) |
| { |
| const u64 feature_ids = pvm_read_id_reg(vcpu, SYS_ID_AA64DFR0_EL1); |
| u64 mdcr_set = 0; |
| u64 mdcr_clear = 0; |
| u64 cptr_set = 0; |
| |
| /* Trap/constrain PMU */ |
| if (!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64DFR0_PMUVER), feature_ids)) { |
| mdcr_set |= MDCR_EL2_TPM | MDCR_EL2_TPMCR; |
| mdcr_clear |= MDCR_EL2_HPME | MDCR_EL2_MTPME | |
| MDCR_EL2_HPMN_MASK; |
| } |
| |
| /* Trap Debug */ |
| if (!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64DFR0_DEBUGVER), feature_ids)) |
| mdcr_set |= MDCR_EL2_TDRA | MDCR_EL2_TDA | MDCR_EL2_TDE; |
| |
| /* Trap OS Double Lock */ |
| if (!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64DFR0_DOUBLELOCK), feature_ids)) |
| mdcr_set |= MDCR_EL2_TDOSA; |
| |
| /* Trap SPE */ |
| if (!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64DFR0_PMSVER), feature_ids)) { |
| mdcr_set |= MDCR_EL2_TPMS; |
| mdcr_clear |= MDCR_EL2_E2PB_MASK << MDCR_EL2_E2PB_SHIFT; |
| } |
| |
| /* Trap Trace Filter */ |
| if (!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64DFR0_TRACE_FILT), feature_ids)) |
| mdcr_set |= MDCR_EL2_TTRF; |
| |
| /* Trap Trace */ |
| if (!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64DFR0_TRACEVER), feature_ids)) |
| cptr_set |= CPTR_EL2_TTA; |
| |
| vcpu->arch.mdcr_el2 |= mdcr_set; |
| vcpu->arch.mdcr_el2 &= ~mdcr_clear; |
| vcpu->arch.cptr_el2 |= cptr_set; |
| } |
| |
| /* |
| * Set trap register values based on features in ID_AA64MMFR0. |
| */ |
| static void pvm_init_traps_aa64mmfr0(struct kvm_vcpu *vcpu) |
| { |
| const u64 feature_ids = pvm_read_id_reg(vcpu, SYS_ID_AA64MMFR0_EL1); |
| u64 mdcr_set = 0; |
| |
| /* Trap Debug Communications Channel registers */ |
| if (!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64MMFR0_FGT), feature_ids)) |
| mdcr_set |= MDCR_EL2_TDCC; |
| |
| vcpu->arch.mdcr_el2 |= mdcr_set; |
| } |
| |
| /* |
| * Set trap register values based on features in ID_AA64MMFR1. |
| */ |
| static void pvm_init_traps_aa64mmfr1(struct kvm_vcpu *vcpu) |
| { |
| const u64 feature_ids = pvm_read_id_reg(vcpu, SYS_ID_AA64MMFR1_EL1); |
| u64 hcr_set = 0; |
| |
| /* Trap LOR */ |
| if (!FIELD_GET(ARM64_FEATURE_MASK(ID_AA64MMFR1_LOR), feature_ids)) |
| hcr_set |= HCR_TLOR; |
| |
| vcpu->arch.hcr_el2 |= hcr_set; |
| } |
| |
| /* |
| * Set baseline trap register values. |
| */ |
| static void pvm_init_trap_regs(struct kvm_vcpu *vcpu) |
| { |
| const u64 hcr_trap_feat_regs = HCR_TID3; |
| const u64 hcr_trap_impdef = HCR_TACR | HCR_TIDCP | HCR_TID1; |
| |
| /* |
| * Always trap: |
| * - Feature id registers: to control features exposed to guests |
| * - Implementation-defined features |
| */ |
| vcpu->arch.hcr_el2 |= hcr_trap_feat_regs | hcr_trap_impdef; |
| |
| /* Clear res0 and set res1 bits to trap potential new features. */ |
| vcpu->arch.hcr_el2 &= ~(HCR_RES0); |
| vcpu->arch.mdcr_el2 &= ~(MDCR_EL2_RES0); |
| vcpu->arch.cptr_el2 |= CPTR_NVHE_EL2_RES1; |
| vcpu->arch.cptr_el2 &= ~(CPTR_NVHE_EL2_RES0); |
| } |
| |
| /* |
| * Initialize trap register values for protected VMs. |
| */ |
| void __pkvm_vcpu_init_traps(struct kvm_vcpu *vcpu) |
| { |
| pvm_init_trap_regs(vcpu); |
| pvm_init_traps_aa64pfr0(vcpu); |
| pvm_init_traps_aa64pfr1(vcpu); |
| pvm_init_traps_aa64dfr0(vcpu); |
| pvm_init_traps_aa64mmfr0(vcpu); |
| pvm_init_traps_aa64mmfr1(vcpu); |
| } |