[PATCH v1 10/12] KVM: arm64: Rework logic to en/decode VTCR_EL2.{SL0, SL2} fields

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



In order to support 5 level translation, FEAT_LPA2 introduces the 1-bit
SL2 field within VTCR_EL2 to extend the existing 2-bit SL0 field. The
SL2[0]:SL0[1:0] encodings have no simple algorithmic relationship to the
start levels they represent (that I can find, at least), so replace the
existing macros with functions that do lookups to encode and decode the
values. These new functions no longer make hardcoded assumptions about
the maximum level and instead rely on KVM_PGTABLE_FIRST_LEVEL and
KVM_PGTABLE_LAST_LEVEL.

This is preparatory work for enabling 52-bit IPA for 4KB and 16KB pages
with FEAT_LPA2.

No functional change intended.

Signed-off-by: Ryan Roberts <ryan.roberts@xxxxxxx>
---
 arch/arm64/include/asm/kvm_arm.h        | 75 ++++++++++++++-----------
 arch/arm64/include/asm/kvm_pgtable.h    | 33 +++++++++++
 arch/arm64/include/asm/stage2_pgtable.h | 13 ++++-
 arch/arm64/kvm/hyp/pgtable.c            | 67 +++++++++++++++++++++-
 4 files changed, 150 insertions(+), 38 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_arm.h b/arch/arm64/include/asm/kvm_arm.h
index f9619a10d5d9..94bbb05e348f 100644
--- a/arch/arm64/include/asm/kvm_arm.h
+++ b/arch/arm64/include/asm/kvm_arm.h
@@ -150,58 +150,65 @@
 				 VTCR_EL2_IRGN0_WBWA | VTCR_EL2_RES1)
 
 /*
- * VTCR_EL2:SL0 indicates the entry level for Stage2 translation.
- * Interestingly, it depends on the page size.
- * See D.10.2.121, VTCR_EL2, in ARM DDI 0487C.a
+ * VTCR_EL2.{SL0, SL2} indicates the entry level for Stage2 translation.
+ * Interestingly, it depends on the page size. See D17.2.157, VTCR_EL2, in ARM
+ * DDI 0487I.a
  *
- *	-----------------------------------------
- *	| Entry level		|  4K  | 16K/64K |
- *	------------------------------------------
- *	| Level: 0		|  2   |   -     |
- *	------------------------------------------
- *	| Level: 1		|  1   |   2     |
- *	------------------------------------------
- *	| Level: 2		|  0   |   1     |
- *	------------------------------------------
- *	| Level: 3		|  -   |   0     |
- *	------------------------------------------
+ *      ----------------------------------------------------------
+ *      | Entry level           |    4K    |    16K   |    64K   |
+ *      |                       |  SL2:SL0 |  SL2:SL0 |  SL2:SL0 |
+ *      ----------------------------------------------------------
+ *      | Level: -1             |  0b100   |     -    |     -    |
+ *      ----------------------------------------------------------
+ *      | Level: 0              |  0b010   |  0b011   |     -    |
+ *      ----------------------------------------------------------
+ *      | Level: 1              |  0b001   |  0b010   |  0b010   |
+ *      ----------------------------------------------------------
+ *      | Level: 2              |  0b000   |  0b001   |  0b001   |
+ *      ----------------------------------------------------------
+ *      | Level: 3              |  0b011   |  0b000   |  0b000   |
+ *      ----------------------------------------------------------
  *
- * The table roughly translates to :
- *
- *	SL0(PAGE_SIZE, Entry_level) = TGRAN_SL0_BASE - Entry_Level
- *
- * Where TGRAN_SL0_BASE is a magic number depending on the page size:
- * 	TGRAN_SL0_BASE(4K) = 2
- *	TGRAN_SL0_BASE(16K) = 3
- *	TGRAN_SL0_BASE(64K) = 3
- * provided we take care of ruling out the unsupported cases and
- * Entry_Level = 4 - Number_of_levels.
+ * There is no concise algorithm to convert between the SLx encodings and the
+ * level numbers, so we implement 2 helpers kvm_vtcr_el2_sl_encode()
+ * kvm_vtcr_el2_sl_decode() which can convert between the representations. These
+ * helpers use a concatenated form of SLx: SL2[0]:SL0[1:0] as the 3 LSBs in u8.
+ * If an invalid input value is provided, VTCR_EL2_SLx_ENC_INVAL is returned. We
+ * declare the appropriate encoded values here for the compiled in page size.
  *
+ * See kvm_pgtable.h for documentation on the helpers.
  */
+#define VTCR_EL2_SLx_ENC_INVAL		255
+
 #ifdef CONFIG_ARM64_64K_PAGES
 
 #define VTCR_EL2_TGRAN			VTCR_EL2_TG0_64K
-#define VTCR_EL2_TGRAN_SL0_BASE		3UL
+#define VTCR_EL2_SLx_ENC_Lm1		VTCR_EL2_SLx_ENC_INVAL
+#define VTCR_EL2_SLx_ENC_L0		VTCR_EL2_SLx_ENC_INVAL
+#define VTCR_EL2_SLx_ENC_Lp1		2
+#define VTCR_EL2_SLx_ENC_Lp2		1
+#define VTCR_EL2_SLx_ENC_Lp3		0
 
 #elif defined(CONFIG_ARM64_16K_PAGES)
 
 #define VTCR_EL2_TGRAN			VTCR_EL2_TG0_16K
-#define VTCR_EL2_TGRAN_SL0_BASE		3UL
+#define VTCR_EL2_SLx_ENC_Lm1		VTCR_EL2_SLx_ENC_INVAL
+#define VTCR_EL2_SLx_ENC_L0		3
+#define VTCR_EL2_SLx_ENC_Lp1		2
+#define VTCR_EL2_SLx_ENC_Lp2		1
+#define VTCR_EL2_SLx_ENC_Lp3		0
 
 #else	/* 4K */
 
 #define VTCR_EL2_TGRAN			VTCR_EL2_TG0_4K
-#define VTCR_EL2_TGRAN_SL0_BASE		2UL
+#define VTCR_EL2_SLx_ENC_Lm1		4
+#define VTCR_EL2_SLx_ENC_L0		2
+#define VTCR_EL2_SLx_ENC_Lp1		1
+#define VTCR_EL2_SLx_ENC_Lp2		0
+#define VTCR_EL2_SLx_ENC_Lp3		3
 
 #endif
 
-#define VTCR_EL2_LVLS_TO_SL0(levels)	\
-	((VTCR_EL2_TGRAN_SL0_BASE - (4 - (levels))) << VTCR_EL2_SL0_SHIFT)
-#define VTCR_EL2_SL0_TO_LVLS(sl0)	\
-	((sl0) + 4 - VTCR_EL2_TGRAN_SL0_BASE)
-#define VTCR_EL2_LVLS(vtcr)		\
-	VTCR_EL2_SL0_TO_LVLS(((vtcr) & VTCR_EL2_SL0_MASK) >> VTCR_EL2_SL0_SHIFT)
-
 #define VTCR_EL2_FLAGS			(VTCR_EL2_COMMON_BITS | VTCR_EL2_TGRAN)
 #define VTCR_EL2_IPA(vtcr)		(64 - ((vtcr) & VTCR_EL2_T0SZ_MASK))
 
diff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h
index a282a3d5ddbc..3e0b64052c51 100644
--- a/arch/arm64/include/asm/kvm_pgtable.h
+++ b/arch/arm64/include/asm/kvm_pgtable.h
@@ -328,6 +328,39 @@ int kvm_pgtable_hyp_map(struct kvm_pgtable *pgt, u64 addr, u64 size, u64 phys,
  */
 u64 kvm_pgtable_hyp_unmap(struct kvm_pgtable *pgt, u64 addr, u64 size);
 
+/**
+ * kvm_vtcr_el2_sl_encode() - Helper to encode start level for vtcr_el2.
+ * @sl_dec:     Start level to be encoded.
+ *
+ * Takes an unencoded translation start level value and returns it encoded for
+ * use in vtcr_el2 register. The returned value has SL0 (a 2 bit field) in bits
+ * [1:0] and SL2 (a 1 bit field) in bit [2]. The user is responsible for
+ * extracting and packing in the correct locations of vctr_el2.
+ *
+ * Do not call this function with a value that is out of range for the page size
+ * in operation. A warning will be output if this is detected and the function
+ * returns VTCR_EL2_SLx_ENC_INVAL. See comment in kvm_arm.h for more info.
+ *
+ * Return: 3 bit value containing SL2[0]:SL0[1:0], or VTCR_EL2_SLx_ENC_INVAL.
+ */
+u8 kvm_vtcr_el2_sl_encode(s8 sl_dec);
+
+/**
+ * kvm_vtcr_el2_sl_decode() - Helper to decode start level for vtcr_el2.
+ * @sl_enc:     Start level encoded as SL2[0]:SL0[1:0].
+ *
+ * Takes an encoded translation start level value, as used in the vtcr_el2
+ * register and returns it decoded. See kvm_vtcr_el2_sl_encode() for description
+ * of input encoding.
+ *
+ * Do not call this function with a value that is invalid for the page size in
+ * operation. A warning will be output if this is detected and the function
+ * returns VTCR_EL2_SLx_ENC_INVAL. See comment in kvm_arm.h for more info.
+ *
+ * Return: Decoded start level, or VTCR_EL2_SLx_ENC_INVAL.
+ */
+s8 kvm_vtcr_el2_sl_decode(u8 sl_enc);
+
 /**
  * kvm_get_vtcr() - Helper to construct VTCR_EL2
  * @mmfr0:	Sanitized value of SYS_ID_AA64MMFR0_EL1 register.
diff --git a/arch/arm64/include/asm/stage2_pgtable.h b/arch/arm64/include/asm/stage2_pgtable.h
index c8dca8ae359c..02c5e04d4958 100644
--- a/arch/arm64/include/asm/stage2_pgtable.h
+++ b/arch/arm64/include/asm/stage2_pgtable.h
@@ -21,7 +21,18 @@
  * (IPA_SHIFT - 4).
  */
 #define stage2_pgtable_levels(ipa)	ARM64_HW_PGTABLE_LEVELS((ipa) - 4)
-#define kvm_stage2_levels(kvm)		VTCR_EL2_LVLS(kvm->arch.vtcr)
+static inline s8 kvm_stage2_levels(struct kvm *kvm)
+{
+	u64 vtcr = kvm->arch.vtcr;
+	u8 slx;
+	s8 start_level;
+
+	slx = FIELD_GET(VTCR_EL2_SL0_MASK, vtcr);
+	slx |= FIELD_GET(VTCR_EL2_SL2_MASK, vtcr) << 2;
+	start_level = kvm_vtcr_el2_sl_decode(slx);
+
+	return KVM_PGTABLE_LAST_LEVEL + 1 - start_level;
+}
 
 /*
  * kvm_mmmu_cache_min_pages() is the number of pages required to install
diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
index 274f839bd0d7..8ebd9aaed2c4 100644
--- a/arch/arm64/kvm/hyp/pgtable.c
+++ b/arch/arm64/kvm/hyp/pgtable.c
@@ -595,12 +595,67 @@ struct stage2_map_data {
 	bool				force_pte;
 };
 
+u8 kvm_vtcr_el2_sl_encode(s8 sl_dec)
+{
+	u8 sl_enc = VTCR_EL2_SLx_ENC_INVAL;
+
+	BUILD_BUG_ON(KVM_PGTABLE_FIRST_LEVEL < -1);
+	BUILD_BUG_ON(KVM_PGTABLE_LAST_LEVEL > 3);
+
+	switch (sl_dec) {
+	case -1:
+		sl_enc = VTCR_EL2_SLx_ENC_Lm1;
+		break;
+	case 0:
+		sl_enc = VTCR_EL2_SLx_ENC_L0;
+		break;
+	case 1:
+		sl_enc = VTCR_EL2_SLx_ENC_Lp1;
+		break;
+	case 2:
+		sl_enc = VTCR_EL2_SLx_ENC_Lp2;
+		break;
+	case 3:
+		sl_enc = VTCR_EL2_SLx_ENC_Lp3;
+		break;
+	}
+
+	WARN_ON_ONCE(sl_enc == VTCR_EL2_SLx_ENC_INVAL);
+	return sl_enc;
+}
+
+s8 kvm_vtcr_el2_sl_decode(u8 sl_enc)
+{
+	s8 sl_dec = VTCR_EL2_SLx_ENC_INVAL;
+
+	BUILD_BUG_ON(KVM_PGTABLE_FIRST_LEVEL < -1);
+	BUILD_BUG_ON(KVM_PGTABLE_LAST_LEVEL > 3);
+
+	if (sl_enc == VTCR_EL2_SLx_ENC_Lm1)
+		sl_dec = -1;
+	else if (sl_enc == VTCR_EL2_SLx_ENC_L0)
+		sl_dec = 0;
+	else if (sl_enc == VTCR_EL2_SLx_ENC_Lp1)
+		sl_dec = 1;
+	else if (sl_enc == VTCR_EL2_SLx_ENC_Lp2)
+		sl_dec = 2;
+	else if (sl_enc == VTCR_EL2_SLx_ENC_Lp3)
+		sl_dec = 3;
+
+	if (WARN_ON_ONCE(sl_dec == VTCR_EL2_SLx_ENC_INVAL ||
+			 sl_enc == VTCR_EL2_SLx_ENC_INVAL))
+		sl_dec = VTCR_EL2_SLx_ENC_INVAL;
+
+	return sl_dec;
+}
+
 u64 kvm_get_vtcr(u64 mmfr0, u64 mmfr1, u32 phys_shift)
 {
 	u64 vtcr = VTCR_EL2_FLAGS;
 	s8 levels;
 	u64 parange;
 	bool lpa2_ena = false;
+	u8 slx;
 
 	/*
 	 * If stage 2 reports that it supports FEAT_LPA2 for our page size, then
@@ -625,7 +680,9 @@ u64 kvm_get_vtcr(u64 mmfr0, u64 mmfr1, u32 phys_shift)
 	levels = stage2_pgtable_levels(phys_shift);
 	if (levels < 2)
 		levels = 2;
-	vtcr |= VTCR_EL2_LVLS_TO_SL0(levels);
+	slx = kvm_vtcr_el2_sl_encode(KVM_PGTABLE_LAST_LEVEL + 1 - levels);
+	vtcr |= FIELD_PREP(VTCR_EL2_SL0_MASK, slx);
+	vtcr |= FIELD_PREP(VTCR_EL2_SL2_MASK, slx >> 2);
 
 	/*
 	 * Enable the Hardware Access Flag management, unconditionally
@@ -1215,10 +1272,14 @@ int __kvm_pgtable_stage2_init(struct kvm_pgtable *pgt, struct kvm_s2_mmu *mmu,
 	size_t pgd_sz;
 	u64 vtcr = mmu->arch->vtcr;
 	u32 ia_bits = VTCR_EL2_IPA(vtcr);
-	u32 sl0 = FIELD_GET(VTCR_EL2_SL0_MASK, vtcr);
-	s8 start_level = VTCR_EL2_TGRAN_SL0_BASE - sl0;
+	u8 slx;
+	s8 start_level;
 	bool lpa2_ena = (vtcr & VTCR_EL2_DS) != 0;
 
+	slx = FIELD_GET(VTCR_EL2_SL0_MASK, vtcr);
+	slx |= FIELD_GET(VTCR_EL2_SL2_MASK, vtcr) << 2;
+	start_level = kvm_vtcr_el2_sl_decode(slx);
+
 	pgd_sz = kvm_pgd_pages(ia_bits, start_level) * PAGE_SIZE;
 	pgt->pgd = mm_ops->zalloc_pages_exact(pgd_sz);
 	if (!pgt->pgd)
-- 
2.25.1

_______________________________________________
kvmarm mailing list
kvmarm@xxxxxxxxxxxxxxxxxxxxx
https://lists.cs.columbia.edu/mailman/listinfo/kvmarm



[Index of Archives]     [Linux KVM]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux