Skip to content

Commit aa6948f

Browse files
Quentin PerretMarc Zyngier
authored andcommitted
KVM: arm64: Add per-cpu fixmap infrastructure at EL2
Mapping pages in a guest page-table from within the pKVM hypervisor at EL2 may require cache maintenance to ensure that the initialised page contents is visible even to non-cacheable (e.g. MMU-off) accesses from the guest. In preparation for performing this maintenance at EL2, introduce a per-vCPU fixmap which allows the pKVM hypervisor to map guest pages temporarily into its stage-1 page-table for the purposes of cache maintenance and, in future, poisoning on the reclaim path. The use of a fixmap avoids the need for memory allocation or locking on the map() path. Tested-by: Vincent Donnefort <[email protected]> Signed-off-by: Quentin Perret <[email protected]> Co-developed-by: Will Deacon <[email protected]> Signed-off-by: Will Deacon <[email protected]> Signed-off-by: Marc Zyngier <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 9d0c063 commit aa6948f

File tree

7 files changed

+128
-13
lines changed

7 files changed

+128
-13
lines changed

arch/arm64/include/asm/kvm_pgtable.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ typedef u64 kvm_pte_t;
4242
#define KVM_PTE_ADDR_MASK GENMASK(47, PAGE_SHIFT)
4343
#define KVM_PTE_ADDR_51_48 GENMASK(15, 12)
4444

45+
#define KVM_PHYS_INVALID (-1ULL)
46+
4547
static inline bool kvm_pte_valid(kvm_pte_t pte)
4648
{
4749
return pte & KVM_PTE_VALID;
@@ -57,6 +59,18 @@ static inline u64 kvm_pte_to_phys(kvm_pte_t pte)
5759
return pa;
5860
}
5961

62+
static inline kvm_pte_t kvm_phys_to_pte(u64 pa)
63+
{
64+
kvm_pte_t pte = pa & KVM_PTE_ADDR_MASK;
65+
66+
if (PAGE_SHIFT == 16) {
67+
pa &= GENMASK(51, 48);
68+
pte |= FIELD_PREP(KVM_PTE_ADDR_51_48, pa >> 48);
69+
}
70+
71+
return pte;
72+
}
73+
6074
static inline u64 kvm_granule_shift(u32 level)
6175
{
6276
/* Assumes KVM_PGTABLE_MAX_LEVELS is 4 */

arch/arm64/kvm/hyp/include/nvhe/mem_protect.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ enum pkvm_component_id {
5959
PKVM_ID_HYP,
6060
};
6161

62+
extern unsigned long hyp_nr_cpus;
63+
6264
int __pkvm_prot_finalize(void);
6365
int __pkvm_host_share_hyp(u64 pfn);
6466
int __pkvm_host_unshare_hyp(u64 pfn);

arch/arm64/kvm/hyp/include/nvhe/mm.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
extern struct kvm_pgtable pkvm_pgtable;
1414
extern hyp_spinlock_t pkvm_pgd_lock;
1515

16+
int hyp_create_pcpu_fixmap(void);
17+
void *hyp_fixmap_map(phys_addr_t phys);
18+
void hyp_fixmap_unmap(void);
19+
1620
int hyp_create_idmap(u32 hyp_va_bits);
1721
int hyp_map_vectors(void);
1822
int hyp_back_vmemmap(phys_addr_t back);

arch/arm64/kvm/hyp/nvhe/mem_protect.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
#define KVM_HOST_S2_FLAGS (KVM_PGTABLE_S2_NOFWB | KVM_PGTABLE_S2_IDMAP)
2323

24-
extern unsigned long hyp_nr_cpus;
2524
struct host_mmu host_mmu;
2625

2726
static struct hyp_pool host_s2_pool;

arch/arm64/kvm/hyp/nvhe/mm.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <nvhe/early_alloc.h>
1515
#include <nvhe/gfp.h>
1616
#include <nvhe/memory.h>
17+
#include <nvhe/mem_protect.h>
1718
#include <nvhe/mm.h>
1819
#include <nvhe/spinlock.h>
1920

@@ -25,6 +26,12 @@ unsigned int hyp_memblock_nr;
2526

2627
static u64 __io_map_base;
2728

29+
struct hyp_fixmap_slot {
30+
u64 addr;
31+
kvm_pte_t *ptep;
32+
};
33+
static DEFINE_PER_CPU(struct hyp_fixmap_slot, fixmap_slots);
34+
2835
static int __pkvm_create_mappings(unsigned long start, unsigned long size,
2936
unsigned long phys, enum kvm_pgtable_prot prot)
3037
{
@@ -212,6 +219,103 @@ int hyp_map_vectors(void)
212219
return 0;
213220
}
214221

222+
void *hyp_fixmap_map(phys_addr_t phys)
223+
{
224+
struct hyp_fixmap_slot *slot = this_cpu_ptr(&fixmap_slots);
225+
kvm_pte_t pte, *ptep = slot->ptep;
226+
227+
pte = *ptep;
228+
pte &= ~kvm_phys_to_pte(KVM_PHYS_INVALID);
229+
pte |= kvm_phys_to_pte(phys) | KVM_PTE_VALID;
230+
WRITE_ONCE(*ptep, pte);
231+
dsb(ishst);
232+
233+
return (void *)slot->addr;
234+
}
235+
236+
static void fixmap_clear_slot(struct hyp_fixmap_slot *slot)
237+
{
238+
kvm_pte_t *ptep = slot->ptep;
239+
u64 addr = slot->addr;
240+
241+
WRITE_ONCE(*ptep, *ptep & ~KVM_PTE_VALID);
242+
243+
/*
244+
* Irritatingly, the architecture requires that we use inner-shareable
245+
* broadcast TLB invalidation here in case another CPU speculates
246+
* through our fixmap and decides to create an "amalagamation of the
247+
* values held in the TLB" due to the apparent lack of a
248+
* break-before-make sequence.
249+
*
250+
* https://lore.kernel.org/kvm/[email protected]/T/#mf10dfbaf1eaef9274c581b81c53758918c1d0f03
251+
*/
252+
dsb(ishst);
253+
__tlbi_level(vale2is, __TLBI_VADDR(addr, 0), (KVM_PGTABLE_MAX_LEVELS - 1));
254+
dsb(ish);
255+
isb();
256+
}
257+
258+
void hyp_fixmap_unmap(void)
259+
{
260+
fixmap_clear_slot(this_cpu_ptr(&fixmap_slots));
261+
}
262+
263+
static int __create_fixmap_slot_cb(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
264+
enum kvm_pgtable_walk_flags flag,
265+
void * const arg)
266+
{
267+
struct hyp_fixmap_slot *slot = per_cpu_ptr(&fixmap_slots, (u64)arg);
268+
269+
if (!kvm_pte_valid(*ptep) || level != KVM_PGTABLE_MAX_LEVELS - 1)
270+
return -EINVAL;
271+
272+
slot->addr = addr;
273+
slot->ptep = ptep;
274+
275+
/*
276+
* Clear the PTE, but keep the page-table page refcount elevated to
277+
* prevent it from ever being freed. This lets us manipulate the PTEs
278+
* by hand safely without ever needing to allocate memory.
279+
*/
280+
fixmap_clear_slot(slot);
281+
282+
return 0;
283+
}
284+
285+
static int create_fixmap_slot(u64 addr, u64 cpu)
286+
{
287+
struct kvm_pgtable_walker walker = {
288+
.cb = __create_fixmap_slot_cb,
289+
.flags = KVM_PGTABLE_WALK_LEAF,
290+
.arg = (void *)cpu,
291+
};
292+
293+
return kvm_pgtable_walk(&pkvm_pgtable, addr, PAGE_SIZE, &walker);
294+
}
295+
296+
int hyp_create_pcpu_fixmap(void)
297+
{
298+
unsigned long addr, i;
299+
int ret;
300+
301+
for (i = 0; i < hyp_nr_cpus; i++) {
302+
ret = pkvm_alloc_private_va_range(PAGE_SIZE, &addr);
303+
if (ret)
304+
return ret;
305+
306+
ret = kvm_pgtable_hyp_map(&pkvm_pgtable, addr, PAGE_SIZE,
307+
__hyp_pa(__hyp_bss_start), PAGE_HYP);
308+
if (ret)
309+
return ret;
310+
311+
ret = create_fixmap_slot(addr, i);
312+
if (ret)
313+
return ret;
314+
}
315+
316+
return 0;
317+
}
318+
215319
int hyp_create_idmap(u32 hyp_va_bits)
216320
{
217321
unsigned long start, end;

arch/arm64/kvm/hyp/nvhe/setup.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ void __noreturn __pkvm_init_finalise(void)
321321
if (ret)
322322
goto out;
323323

324+
ret = hyp_create_pcpu_fixmap();
325+
if (ret)
326+
goto out;
327+
324328
pkvm_hyp_vm_table_init(vm_table_base);
325329
out:
326330
/*

arch/arm64/kvm/hyp/pgtable.c

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ struct kvm_pgtable_walk_data {
5757
u64 end;
5858
};
5959

60-
#define KVM_PHYS_INVALID (-1ULL)
61-
6260
static bool kvm_phys_is_valid(u64 phys)
6361
{
6462
return phys < BIT(id_aa64mmfr0_parange_to_phys_shift(ID_AA64MMFR0_EL1_PARANGE_MAX));
@@ -122,16 +120,6 @@ static bool kvm_pte_table(kvm_pte_t pte, u32 level)
122120
return FIELD_GET(KVM_PTE_TYPE, pte) == KVM_PTE_TYPE_TABLE;
123121
}
124122

125-
static kvm_pte_t kvm_phys_to_pte(u64 pa)
126-
{
127-
kvm_pte_t pte = pa & KVM_PTE_ADDR_MASK;
128-
129-
if (PAGE_SHIFT == 16)
130-
pte |= FIELD_PREP(KVM_PTE_ADDR_51_48, pa >> 48);
131-
132-
return pte;
133-
}
134-
135123
static kvm_pte_t *kvm_pte_follow(kvm_pte_t pte, struct kvm_pgtable_mm_ops *mm_ops)
136124
{
137125
return mm_ops->phys_to_virt(kvm_pte_to_phys(pte));

0 commit comments

Comments
 (0)