Commit 2608563b authored by Oliver Upton's avatar Oliver Upton
Browse files

KVM: arm64: Add support for FEAT_XNX stage-2 permissions



FEAT_XNX adds support for encoding separate execute permissions for EL0
and EL1 at stage-2. Add support for this to the page table library,
hiding the unintuitive encoding scheme behind generic pX and uX
permission flags.

Reviewed-by: default avatarMarc Zyngier <maz@kernel.org>
Tested-by: default avatarMarc Zyngier <maz@kernel.org>
Link: https://msgid.link/20251124190158.177318-3-oupton@kernel.org


Signed-off-by: default avatarOliver Upton <oupton@kernel.org>
parent dc311243
Loading
Loading
Loading
Loading
+10 −7
Original line number Diff line number Diff line
@@ -89,7 +89,7 @@ typedef u64 kvm_pte_t;

#define KVM_PTE_LEAF_ATTR_HI_S1_XN	BIT(54)

#define KVM_PTE_LEAF_ATTR_HI_S2_XN	BIT(54)
#define KVM_PTE_LEAF_ATTR_HI_S2_XN	GENMASK(54, 53)

#define KVM_PTE_LEAF_ATTR_HI_S1_GP	BIT(50)

@@ -251,12 +251,15 @@ enum kvm_pgtable_stage2_flags {
 * @KVM_PGTABLE_PROT_SW3:	Software bit 3.
 */
enum kvm_pgtable_prot {
	KVM_PGTABLE_PROT_X			= BIT(0),
	KVM_PGTABLE_PROT_W			= BIT(1),
	KVM_PGTABLE_PROT_R			= BIT(2),

	KVM_PGTABLE_PROT_DEVICE			= BIT(3),
	KVM_PGTABLE_PROT_NORMAL_NC		= BIT(4),
	KVM_PGTABLE_PROT_PX			= BIT(0),
	KVM_PGTABLE_PROT_UX			= BIT(1),
	KVM_PGTABLE_PROT_X			= KVM_PGTABLE_PROT_PX	|
						  KVM_PGTABLE_PROT_UX,
	KVM_PGTABLE_PROT_W			= BIT(2),
	KVM_PGTABLE_PROT_R			= BIT(3),

	KVM_PGTABLE_PROT_DEVICE			= BIT(4),
	KVM_PGTABLE_PROT_NORMAL_NC		= BIT(5),

	KVM_PGTABLE_PROT_SW0			= BIT(55),
	KVM_PGTABLE_PROT_SW1			= BIT(56),
+50 −8
Original line number Diff line number Diff line
@@ -661,11 +661,37 @@ void kvm_tlb_flush_vmid_range(struct kvm_s2_mmu *mmu,

#define KVM_S2_MEMATTR(pgt, attr) PAGE_S2_MEMATTR(attr, stage2_has_fwb(pgt))

static int stage2_set_xn_attr(enum kvm_pgtable_prot prot, kvm_pte_t *attr)
{
	bool px, ux;
	u8 xn;

	px = prot & KVM_PGTABLE_PROT_PX;
	ux = prot & KVM_PGTABLE_PROT_UX;

	if (!cpus_have_final_cap(ARM64_HAS_XNX) && px != ux)
		return -EINVAL;

	if (px && ux)
		xn = 0b00;
	else if (!px && ux)
		xn = 0b01;
	else if (!px && !ux)
		xn = 0b10;
	else
		xn = 0b11;

	*attr &= ~KVM_PTE_LEAF_ATTR_HI_S2_XN;
	*attr |= FIELD_PREP(KVM_PTE_LEAF_ATTR_HI_S2_XN, xn);
	return 0;
}

static int stage2_set_prot_attr(struct kvm_pgtable *pgt, enum kvm_pgtable_prot prot,
				kvm_pte_t *ptep)
{
	kvm_pte_t attr;
	u32 sh = KVM_PTE_LEAF_ATTR_LO_S2_SH_IS;
	int r;

	switch (prot & (KVM_PGTABLE_PROT_DEVICE |
			KVM_PGTABLE_PROT_NORMAL_NC)) {
@@ -685,8 +711,9 @@ static int stage2_set_prot_attr(struct kvm_pgtable *pgt, enum kvm_pgtable_prot p
		attr = KVM_S2_MEMATTR(pgt, NORMAL);
	}

	if (!(prot & KVM_PGTABLE_PROT_X))
		attr |= KVM_PTE_LEAF_ATTR_HI_S2_XN;
	r = stage2_set_xn_attr(prot, &attr);
	if (r)
		return r;

	if (prot & KVM_PGTABLE_PROT_R)
		attr |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R;
@@ -715,8 +742,19 @@ enum kvm_pgtable_prot kvm_pgtable_stage2_pte_prot(kvm_pte_t pte)
		prot |= KVM_PGTABLE_PROT_R;
	if (pte & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W)
		prot |= KVM_PGTABLE_PROT_W;
	if (!(pte & KVM_PTE_LEAF_ATTR_HI_S2_XN))
		prot |= KVM_PGTABLE_PROT_X;

	switch (FIELD_GET(KVM_PTE_LEAF_ATTR_HI_S2_XN, pte)) {
	case 0b00:
		prot |= KVM_PGTABLE_PROT_PX | KVM_PGTABLE_PROT_UX;
		break;
	case 0b01:
		prot |= KVM_PGTABLE_PROT_UX;
		break;
	case 0b11:
		prot |= KVM_PGTABLE_PROT_PX;
		break;
	default:
	}

	return prot;
}
@@ -1290,9 +1328,9 @@ bool kvm_pgtable_stage2_test_clear_young(struct kvm_pgtable *pgt, u64 addr,
int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr,
				   enum kvm_pgtable_prot prot, enum kvm_pgtable_walk_flags flags)
{
	int ret;
	kvm_pte_t xn = 0, set = 0, clr = 0;
	s8 level;
	kvm_pte_t set = 0, clr = 0;
	int ret;

	if (prot & KVM_PTE_LEAF_ATTR_HI_SW)
		return -EINVAL;
@@ -1303,8 +1341,12 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr,
	if (prot & KVM_PGTABLE_PROT_W)
		set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W;

	if (prot & KVM_PGTABLE_PROT_X)
		clr |= KVM_PTE_LEAF_ATTR_HI_S2_XN;
	ret = stage2_set_xn_attr(prot, &xn);
	if (ret)
		return ret;

	set |= xn & KVM_PTE_LEAF_ATTR_HI_S2_XN;
	clr |= ~xn & KVM_PTE_LEAF_ATTR_HI_S2_XN;

	ret = stage2_update_leaf_attrs(pgt, addr, 1, set, clr, NULL, &level, flags);
	if (!ret || ret == -EAGAIN)