Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions arch/riscv/include/asm/kvm_gstage.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
bool page_rdonly, bool page_exec,
struct kvm_gstage_mapping *out_map);

int kvm_riscv_gstage_split_huge(struct kvm_gstage *gstage,
struct kvm_mmu_memory_cache *pcache,
gpa_t addr, u32 target_level, bool flush);

enum kvm_riscv_gstage_op {
GSTAGE_OP_NOP = 0, /* Nothing */
GSTAGE_OP_CLEAR, /* Clear/Unmap */
Expand Down
85 changes: 81 additions & 4 deletions arch/riscv/kvm/gstage.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
{
pgprot_t prot;
int ret;
pte_t *ptep;
u32 ptep_level;
bool found_leaf;

out_map->addr = gpa;
out_map->level = 0;
Expand All @@ -179,6 +182,12 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
if (ret)
return ret;

found_leaf = kvm_riscv_gstage_get_leaf(gstage, gpa, &ptep, &ptep_level);
if (found_leaf && ptep_level > out_map->level) {
kvm_riscv_gstage_split_huge(gstage, pcache, gpa,
out_map->level, true);
}

/*
* A RISC-V implementation can choose to either:
* 1) Update 'A' and 'D' PTE bits in hardware
Expand Down Expand Up @@ -209,6 +218,75 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
return kvm_riscv_gstage_set_pte(gstage, pcache, out_map);
}

static inline unsigned long make_child_pte(unsigned long huge_pte, int index,
unsigned long child_page_size)
{
unsigned long child_pte = huge_pte;
unsigned long child_pfn_offset;

/*
* The child_pte already has the base address of the huge page being
* split. So we just have to OR in the offset to the page at the next
* lower level for the given index.
*/
child_pfn_offset = index * (child_page_size / PAGE_SIZE);
child_pte |= pte_val(pfn_pte(child_pfn_offset, __pgprot(0)));

return child_pte;
}

int kvm_riscv_gstage_split_huge(struct kvm_gstage *gstage,
struct kvm_mmu_memory_cache *pcache,
gpa_t addr, u32 target_level, bool flush)
{
u32 current_level = kvm_riscv_gstage_pgd_levels - 1;
pte_t *next_ptep = (pte_t *)gstage->pgd;
pte_t *ptep;
unsigned long huge_pte, child_pte;
unsigned long child_page_size;
int i, ret;

while(current_level > target_level) {
ptep = (pte_t *)&next_ptep[gstage_pte_index(addr, current_level)];

if (!pte_val(ptep_get(ptep)))
break;

if (!gstage_pte_leaf(ptep)) {
next_ptep = (pte_t *)gstage_pte_page_vaddr(ptep_get(ptep));
current_level--;
continue;
}

huge_pte = pte_val(ptep_get(ptep));

ret = gstage_level_to_page_size(current_level - 1, &child_page_size);
if (ret)
return ret;

if (!pcache)
return -ENOMEM;
next_ptep = kvm_mmu_memory_cache_alloc(pcache);
if (!next_ptep)
return -ENOMEM;

set_pte(ptep, pfn_pte(PFN_DOWN(__pa(next_ptep)),
__pgprot(_PAGE_TABLE)));

for (i = 0; i < PTRS_PER_PTE; i++) {
child_pte = make_child_pte(huge_pte, i, child_page_size);
set_pte((pte_t *)&next_ptep[i], __pte(child_pte));
}

if (flush)
gstage_tlb_flush(gstage, current_level, addr);

current_level--;
}

return 0;
}

void kvm_riscv_gstage_op_pte(struct kvm_gstage *gstage, gpa_t addr,
pte_t *ptep, u32 ptep_level, enum kvm_riscv_gstage_op op)
{
Expand Down Expand Up @@ -304,10 +382,9 @@ void kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end
if (!found_leaf)
goto next;

if (!(addr & (page_size - 1)) && ((end - addr) >= page_size))
kvm_riscv_gstage_op_pte(gstage, addr, ptep,
ptep_level, GSTAGE_OP_WP);

addr = ALIGN_DOWN(addr, page_size);
kvm_riscv_gstage_op_pte(gstage, addr, ptep,
ptep_level, GSTAGE_OP_WP);
next:
addr += page_size;
}
Expand Down
Loading