1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Debug helper used to dump the stage-2 pagetables of the system and their 4 * associated permissions. 5 * 6 * Copyright (C) Google, 2024 7 * Author: Sebastian Ene <sebastianene@google.com> 8 */ 9 #include <linux/debugfs.h> 10 #include <linux/kvm_host.h> 11 #include <linux/seq_file.h> 12 13 #include <asm/cpufeature.h> 14 #include <asm/kvm_mmu.h> 15 #include <asm/kvm_pgtable.h> 16 #include <asm/ptdump.h> 17 18 #define MARKERS_LEN 2 19 #define KVM_PGTABLE_MAX_LEVELS (KVM_PGTABLE_LAST_LEVEL + 1) 20 #define S2FNAMESZ sizeof("0x0123456789abcdef-0x0123456789abcdef-s2-disabled") 21 22 struct kvm_ptdump_guest_state { 23 struct kvm_s2_mmu *mmu; 24 struct ptdump_pg_state parser_state; 25 struct addr_marker ipa_marker[MARKERS_LEN]; 26 struct ptdump_pg_level level[KVM_PGTABLE_MAX_LEVELS]; 27 }; 28 29 static const struct ptdump_prot_bits stage2_pte_bits[] = { 30 { 31 .mask = PTE_VALID, 32 .val = PTE_VALID, 33 .set = " ", 34 .clear = "F", 35 }, 36 { 37 .mask = KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R, 38 .val = KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R, 39 .set = "R", 40 .clear = " ", 41 }, 42 { 43 .mask = KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, 44 .val = KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, 45 .set = "W", 46 .clear = " ", 47 }, 48 { 49 .mask = KVM_PTE_LEAF_ATTR_HI_S2_XN, 50 .val = 0b00UL << __bf_shf(KVM_PTE_LEAF_ATTR_HI_S2_XN), 51 .set = "px ux ", 52 }, 53 { 54 .mask = KVM_PTE_LEAF_ATTR_HI_S2_XN, 55 .val = 0b01UL << __bf_shf(KVM_PTE_LEAF_ATTR_HI_S2_XN), 56 .set = "PXNux ", 57 }, 58 { 59 .mask = KVM_PTE_LEAF_ATTR_HI_S2_XN, 60 .val = 0b10UL << __bf_shf(KVM_PTE_LEAF_ATTR_HI_S2_XN), 61 .set = "PXNUXN", 62 }, 63 { 64 .mask = KVM_PTE_LEAF_ATTR_HI_S2_XN, 65 .val = 0b11UL << __bf_shf(KVM_PTE_LEAF_ATTR_HI_S2_XN), 66 .set = "px UXN", 67 }, 68 { 69 .mask = KVM_PTE_LEAF_ATTR_LO_S2_AF, 70 .val = KVM_PTE_LEAF_ATTR_LO_S2_AF, 71 .set = "AF", 72 .clear = " ", 73 }, 74 { 75 .mask = PMD_TYPE_MASK, 76 .val = PMD_TYPE_SECT, 77 .set = "BLK", 78 .clear = " ", 79 }, 80 }; 81 82 static int kvm_ptdump_visitor(const struct kvm_pgtable_visit_ctx *ctx, 83 enum kvm_pgtable_walk_flags visit) 84 { 85 struct ptdump_pg_state *st = ctx->arg; 86 struct ptdump_state *pt_st = &st->ptdump; 87 88 note_page(pt_st, ctx->addr, ctx->level, ctx->old); 89 90 return 0; 91 } 92 93 static int kvm_ptdump_build_levels(struct ptdump_pg_level *level, u32 start_lvl) 94 { 95 u32 i; 96 u64 mask; 97 98 if (WARN_ON_ONCE(start_lvl >= KVM_PGTABLE_LAST_LEVEL)) 99 return -EINVAL; 100 101 mask = 0; 102 for (i = 0; i < ARRAY_SIZE(stage2_pte_bits); i++) 103 mask |= stage2_pte_bits[i].mask; 104 105 for (i = start_lvl; i < KVM_PGTABLE_MAX_LEVELS; i++) { 106 snprintf(level[i].name, sizeof(level[i].name), "%u", i); 107 108 level[i].num = ARRAY_SIZE(stage2_pte_bits); 109 level[i].bits = stage2_pte_bits; 110 level[i].mask = mask; 111 } 112 113 return 0; 114 } 115 116 static struct kvm_ptdump_guest_state *kvm_ptdump_parser_create(struct kvm_s2_mmu *mmu) 117 { 118 struct kvm_ptdump_guest_state *st; 119 struct kvm_pgtable *pgtable = mmu->pgt; 120 int ret; 121 122 st = kzalloc_obj(struct kvm_ptdump_guest_state, GFP_KERNEL_ACCOUNT); 123 if (!st) 124 return ERR_PTR(-ENOMEM); 125 126 ret = kvm_ptdump_build_levels(&st->level[0], pgtable->start_level); 127 if (ret) { 128 kfree(st); 129 return ERR_PTR(ret); 130 } 131 132 st->ipa_marker[0].name = "Guest IPA"; 133 st->ipa_marker[1].start_address = ULONG_MAX; 134 135 st->mmu = mmu; 136 return st; 137 } 138 139 static int kvm_ptdump_guest_show(struct seq_file *m, void *unused) 140 { 141 int ret; 142 struct kvm_ptdump_guest_state *st = m->private; 143 struct kvm_s2_mmu *mmu = st->mmu; 144 struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu); 145 struct kvm_pgtable_walker walker = (struct kvm_pgtable_walker) { 146 .cb = kvm_ptdump_visitor, 147 .arg = &st->parser_state, 148 .flags = KVM_PGTABLE_WALK_LEAF, 149 }; 150 151 guard(write_lock)(&kvm->mmu_lock); 152 st->parser_state = (struct ptdump_pg_state) { 153 .marker = &st->ipa_marker[0], 154 .end_address = BIT(mmu->pgt->ia_bits), 155 .level = -1, 156 .pg_level = &st->level[0], 157 .seq = m, 158 }; 159 160 ret = kvm_pgtable_walk(mmu->pgt, 0, BIT(mmu->pgt->ia_bits), &walker); 161 if (ret) 162 return ret; 163 note_page_flush(&st->parser_state.ptdump); 164 165 return 0; 166 } 167 168 static int kvm_ptdump_guest_open(struct inode *m, struct file *file) 169 { 170 struct kvm_s2_mmu *mmu = m->i_private; 171 struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu); 172 struct kvm_ptdump_guest_state *st; 173 int ret; 174 175 if (!kvm_get_kvm_safe(kvm)) 176 return -ENOENT; 177 178 st = kvm_ptdump_parser_create(mmu); 179 if (IS_ERR(st)) { 180 ret = PTR_ERR(st); 181 goto err_with_kvm_ref; 182 } 183 184 ret = single_open(file, kvm_ptdump_guest_show, st); 185 if (!ret) 186 return 0; 187 188 kfree(st); 189 err_with_kvm_ref: 190 kvm_put_kvm(kvm); 191 return ret; 192 } 193 194 static int kvm_ptdump_guest_close(struct inode *m, struct file *file) 195 { 196 struct kvm *kvm = kvm_s2_mmu_to_kvm(m->i_private); 197 void *st = ((struct seq_file *)file->private_data)->private; 198 199 kfree(st); 200 kvm_put_kvm(kvm); 201 202 return single_release(m, file); 203 } 204 205 static const struct file_operations kvm_ptdump_guest_fops = { 206 .open = kvm_ptdump_guest_open, 207 .read = seq_read, 208 .llseek = seq_lseek, 209 .release = kvm_ptdump_guest_close, 210 }; 211 212 static int kvm_pgtable_range_show(struct seq_file *m, void *unused) 213 { 214 struct kvm_pgtable *pgtable = m->private; 215 216 seq_printf(m, "%2u\n", pgtable->ia_bits); 217 return 0; 218 } 219 220 static int kvm_pgtable_levels_show(struct seq_file *m, void *unused) 221 { 222 struct kvm_pgtable *pgtable = m->private; 223 224 seq_printf(m, "%1d\n", KVM_PGTABLE_MAX_LEVELS - pgtable->start_level); 225 return 0; 226 } 227 228 static int kvm_pgtable_debugfs_open(struct inode *m, struct file *file, 229 int (*show)(struct seq_file *, void *)) 230 { 231 struct kvm_s2_mmu *mmu = m->i_private; 232 struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu); 233 struct kvm_pgtable *pgtable; 234 int ret; 235 236 if (!kvm_get_kvm_safe(kvm)) 237 return -ENOENT; 238 239 pgtable = mmu->pgt; 240 241 ret = single_open(file, show, pgtable); 242 if (ret < 0) 243 kvm_put_kvm(kvm); 244 return ret; 245 } 246 247 static int kvm_pgtable_range_open(struct inode *m, struct file *file) 248 { 249 return kvm_pgtable_debugfs_open(m, file, kvm_pgtable_range_show); 250 } 251 252 static int kvm_pgtable_levels_open(struct inode *m, struct file *file) 253 { 254 return kvm_pgtable_debugfs_open(m, file, kvm_pgtable_levels_show); 255 } 256 257 static int kvm_pgtable_debugfs_close(struct inode *m, struct file *file) 258 { 259 struct kvm *kvm = kvm_s2_mmu_to_kvm(m->i_private); 260 261 kvm_put_kvm(kvm); 262 return single_release(m, file); 263 } 264 265 static const struct file_operations kvm_pgtable_range_fops = { 266 .open = kvm_pgtable_range_open, 267 .read = seq_read, 268 .llseek = seq_lseek, 269 .release = kvm_pgtable_debugfs_close, 270 }; 271 272 static const struct file_operations kvm_pgtable_levels_fops = { 273 .open = kvm_pgtable_levels_open, 274 .read = seq_read, 275 .llseek = seq_lseek, 276 .release = kvm_pgtable_debugfs_close, 277 }; 278 279 void kvm_nested_s2_ptdump_create_debugfs(struct kvm_s2_mmu *mmu) 280 { 281 struct dentry *dent; 282 char file_name[S2FNAMESZ]; 283 284 snprintf(file_name, sizeof(file_name), "0x%016llx-0x%016llx-s2-%sabled", 285 mmu->tlb_vttbr, 286 mmu->tlb_vtcr, 287 mmu->nested_stage2_enabled ? "en" : "dis"); 288 289 dent = debugfs_create_file(file_name, 0400, 290 mmu->arch->debugfs_nv_dentry, mmu, 291 &kvm_ptdump_guest_fops); 292 293 mmu->shadow_pt_debugfs_dentry = dent; 294 } 295 296 void kvm_nested_s2_ptdump_remove_debugfs(struct kvm_s2_mmu *mmu) 297 { 298 debugfs_remove(mmu->shadow_pt_debugfs_dentry); 299 } 300 301 void kvm_s2_ptdump_create_debugfs(struct kvm *kvm) 302 { 303 debugfs_create_file("stage2_page_tables", 0400, kvm->debugfs_dentry, 304 &kvm->arch.mmu, &kvm_ptdump_guest_fops); 305 debugfs_create_file("ipa_range", 0400, kvm->debugfs_dentry, 306 &kvm->arch.mmu, &kvm_pgtable_range_fops); 307 debugfs_create_file("stage2_levels", 0400, kvm->debugfs_dentry, 308 &kvm->arch.mmu, &kvm_pgtable_levels_fops); 309 if (cpus_have_final_cap(ARM64_HAS_NESTED_VIRT)) 310 kvm->arch.debugfs_nv_dentry = debugfs_create_dir("nested", kvm->debugfs_dentry); 311 } 312