1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (c) 2026, Microsoft Corporation. 4 * 5 * The /sys/kernel/debug/mshv directory contents. 6 * Contains various statistics data, provided by the hypervisor. 7 * 8 * Authors: Microsoft Linux virtualization team 9 */ 10 11 #include <linux/debugfs.h> 12 #include <linux/stringify.h> 13 #include <asm/mshyperv.h> 14 #include <linux/slab.h> 15 16 #include "mshv.h" 17 #include "mshv_root.h" 18 19 /* Ensure this file is not used elsewhere by accident */ 20 #define MSHV_DEBUGFS_C 21 #include "mshv_debugfs_counters.c" 22 23 #define U32_BUF_SZ 11 24 #define U64_BUF_SZ 21 25 /* Only support SELF and PARENT areas */ 26 #define NUM_STATS_AREAS 2 27 static_assert(HV_STATS_AREA_SELF == 0 && HV_STATS_AREA_PARENT == 1, 28 "SELF and PARENT areas must be usable as indices into an array of size NUM_STATS_AREAS"); 29 /* HV_HYPERVISOR_COUNTER */ 30 #define HV_HYPERVISOR_COUNTER_LOGICAL_PROCESSORS 1 31 32 static struct dentry *mshv_debugfs; 33 static struct dentry *mshv_debugfs_partition; 34 static struct dentry *mshv_debugfs_lp; 35 static struct dentry **parent_vp_stats; 36 static struct dentry *parent_partition_stats; 37 38 static u64 mshv_lps_count; 39 static struct hv_stats_page **mshv_lps_stats; 40 41 static int lp_stats_show(struct seq_file *m, void *v) 42 { 43 const struct hv_stats_page *stats = m->private; 44 int idx; 45 46 for (idx = 0; idx < ARRAY_SIZE(hv_lp_counters); idx++) { 47 char *name = hv_lp_counters[idx]; 48 49 if (!name) 50 continue; 51 seq_printf(m, "%-32s: %llu\n", name, stats->data[idx]); 52 } 53 54 return 0; 55 } 56 DEFINE_SHOW_ATTRIBUTE(lp_stats); 57 58 static void mshv_lp_stats_unmap(u32 lp_index) 59 { 60 union hv_stats_object_identity identity = { 61 .lp.lp_index = lp_index, 62 .lp.stats_area_type = HV_STATS_AREA_SELF, 63 }; 64 int err; 65 66 err = hv_unmap_stats_page(HV_STATS_OBJECT_LOGICAL_PROCESSOR, 67 mshv_lps_stats[lp_index], &identity); 68 if (err) 69 pr_err("%s: failed to unmap logical processor %u stats, err: %d\n", 70 __func__, lp_index, err); 71 72 mshv_lps_stats[lp_index] = NULL; 73 } 74 75 static struct hv_stats_page * __init mshv_lp_stats_map(u32 lp_index) 76 { 77 union hv_stats_object_identity identity = { 78 .lp.lp_index = lp_index, 79 .lp.stats_area_type = HV_STATS_AREA_SELF, 80 }; 81 struct hv_stats_page *stats; 82 int err; 83 84 err = hv_map_stats_page(HV_STATS_OBJECT_LOGICAL_PROCESSOR, &identity, 85 &stats); 86 if (err) { 87 pr_err("%s: failed to map logical processor %u stats, err: %d\n", 88 __func__, lp_index, err); 89 return ERR_PTR(err); 90 } 91 mshv_lps_stats[lp_index] = stats; 92 93 return stats; 94 } 95 96 static struct hv_stats_page * __init lp_debugfs_stats_create(u32 lp_index, 97 struct dentry *parent) 98 { 99 struct dentry *dentry; 100 struct hv_stats_page *stats; 101 102 stats = mshv_lp_stats_map(lp_index); 103 if (IS_ERR(stats)) 104 return stats; 105 106 dentry = debugfs_create_file("stats", 0400, parent, 107 stats, &lp_stats_fops); 108 if (IS_ERR(dentry)) { 109 mshv_lp_stats_unmap(lp_index); 110 return ERR_CAST(dentry); 111 } 112 return stats; 113 } 114 115 static int __init lp_debugfs_create(u32 lp_index, struct dentry *parent) 116 { 117 struct dentry *idx; 118 char lp_idx_str[U32_BUF_SZ]; 119 struct hv_stats_page *stats; 120 int err; 121 122 sprintf(lp_idx_str, "%u", lp_index); 123 124 idx = debugfs_create_dir(lp_idx_str, parent); 125 if (IS_ERR(idx)) 126 return PTR_ERR(idx); 127 128 stats = lp_debugfs_stats_create(lp_index, idx); 129 if (IS_ERR(stats)) { 130 err = PTR_ERR(stats); 131 goto remove_debugfs_lp_idx; 132 } 133 134 return 0; 135 136 remove_debugfs_lp_idx: 137 debugfs_remove_recursive(idx); 138 return err; 139 } 140 141 static void mshv_debugfs_lp_remove(void) 142 { 143 int lp_index; 144 145 debugfs_remove_recursive(mshv_debugfs_lp); 146 147 for (lp_index = 0; lp_index < mshv_lps_count; lp_index++) 148 mshv_lp_stats_unmap(lp_index); 149 150 kfree(mshv_lps_stats); 151 mshv_lps_stats = NULL; 152 } 153 154 static int __init mshv_debugfs_lp_create(struct dentry *parent) 155 { 156 struct dentry *lp_dir; 157 int err, lp_index; 158 159 mshv_lps_stats = kzalloc_objs(*mshv_lps_stats, mshv_lps_count, 160 GFP_KERNEL_ACCOUNT); 161 162 if (!mshv_lps_stats) 163 return -ENOMEM; 164 165 lp_dir = debugfs_create_dir("lp", parent); 166 if (IS_ERR(lp_dir)) { 167 err = PTR_ERR(lp_dir); 168 goto free_lp_stats; 169 } 170 171 for (lp_index = 0; lp_index < mshv_lps_count; lp_index++) { 172 err = lp_debugfs_create(lp_index, lp_dir); 173 if (err) 174 goto remove_debugfs_lps; 175 } 176 177 mshv_debugfs_lp = lp_dir; 178 179 return 0; 180 181 remove_debugfs_lps: 182 for (lp_index -= 1; lp_index >= 0; lp_index--) 183 mshv_lp_stats_unmap(lp_index); 184 debugfs_remove_recursive(lp_dir); 185 free_lp_stats: 186 kfree(mshv_lps_stats); 187 mshv_lps_stats = NULL; 188 189 return err; 190 } 191 192 static int vp_stats_show(struct seq_file *m, void *v) 193 { 194 const struct hv_stats_page **pstats = m->private; 195 u64 parent_val, self_val; 196 int idx; 197 198 /* 199 * For VP and partition stats, there may be two stats areas mapped, 200 * SELF and PARENT. These refer to the privilege level of the data in 201 * each page. Some fields may be 0 in SELF and nonzero in PARENT, or 202 * vice versa. 203 * 204 * Hence, prioritize printing from the PARENT page (more privileged 205 * data), but use the value from the SELF page if the PARENT value is 206 * 0. 207 */ 208 209 for (idx = 0; idx < ARRAY_SIZE(hv_vp_counters); idx++) { 210 char *name = hv_vp_counters[idx]; 211 212 if (!name) 213 continue; 214 215 parent_val = pstats[HV_STATS_AREA_PARENT]->data[idx]; 216 self_val = pstats[HV_STATS_AREA_SELF]->data[idx]; 217 seq_printf(m, "%-43s: %llu\n", name, 218 parent_val ? parent_val : self_val); 219 } 220 221 return 0; 222 } 223 DEFINE_SHOW_ATTRIBUTE(vp_stats); 224 225 static void vp_debugfs_remove(struct dentry *vp_stats) 226 { 227 debugfs_remove_recursive(vp_stats->d_parent); 228 } 229 230 static int vp_debugfs_create(u64 partition_id, u32 vp_index, 231 struct hv_stats_page **pstats, 232 struct dentry **vp_stats_ptr, 233 struct dentry *parent) 234 { 235 struct dentry *vp_idx_dir, *d; 236 char vp_idx_str[U32_BUF_SZ]; 237 int err; 238 239 sprintf(vp_idx_str, "%u", vp_index); 240 241 vp_idx_dir = debugfs_create_dir(vp_idx_str, parent); 242 if (IS_ERR(vp_idx_dir)) 243 return PTR_ERR(vp_idx_dir); 244 245 d = debugfs_create_file("stats", 0400, vp_idx_dir, 246 pstats, &vp_stats_fops); 247 if (IS_ERR(d)) { 248 err = PTR_ERR(d); 249 goto remove_debugfs_vp_idx; 250 } 251 252 *vp_stats_ptr = d; 253 254 return 0; 255 256 remove_debugfs_vp_idx: 257 debugfs_remove_recursive(vp_idx_dir); 258 return err; 259 } 260 261 static int partition_stats_show(struct seq_file *m, void *v) 262 { 263 const struct hv_stats_page **pstats = m->private; 264 u64 parent_val, self_val; 265 int idx; 266 267 for (idx = 0; idx < ARRAY_SIZE(hv_partition_counters); idx++) { 268 char *name = hv_partition_counters[idx]; 269 270 if (!name) 271 continue; 272 273 parent_val = pstats[HV_STATS_AREA_PARENT]->data[idx]; 274 self_val = pstats[HV_STATS_AREA_SELF]->data[idx]; 275 seq_printf(m, "%-37s: %llu\n", name, 276 parent_val ? parent_val : self_val); 277 } 278 279 return 0; 280 } 281 DEFINE_SHOW_ATTRIBUTE(partition_stats); 282 283 static void mshv_partition_stats_unmap(u64 partition_id, 284 struct hv_stats_page *stats_page, 285 enum hv_stats_area_type stats_area_type) 286 { 287 union hv_stats_object_identity identity = { 288 .partition.partition_id = partition_id, 289 .partition.stats_area_type = stats_area_type, 290 }; 291 int err; 292 293 err = hv_unmap_stats_page(HV_STATS_OBJECT_PARTITION, stats_page, 294 &identity); 295 if (err) 296 pr_err("%s: failed to unmap partition %lld %s stats, err: %d\n", 297 __func__, partition_id, 298 (stats_area_type == HV_STATS_AREA_SELF) ? "self" : "parent", 299 err); 300 } 301 302 static struct hv_stats_page *mshv_partition_stats_map(u64 partition_id, 303 enum hv_stats_area_type stats_area_type) 304 { 305 union hv_stats_object_identity identity = { 306 .partition.partition_id = partition_id, 307 .partition.stats_area_type = stats_area_type, 308 }; 309 struct hv_stats_page *stats; 310 int err; 311 312 err = hv_map_stats_page(HV_STATS_OBJECT_PARTITION, &identity, &stats); 313 if (err) { 314 pr_err("%s: failed to map partition %lld %s stats, err: %d\n", 315 __func__, partition_id, 316 (stats_area_type == HV_STATS_AREA_SELF) ? "self" : "parent", 317 err); 318 return ERR_PTR(err); 319 } 320 return stats; 321 } 322 323 static int mshv_debugfs_partition_stats_create(u64 partition_id, 324 struct dentry **partition_stats_ptr, 325 struct dentry *parent) 326 { 327 struct dentry *dentry; 328 struct hv_stats_page **pstats; 329 int err; 330 331 pstats = kzalloc_objs(struct hv_stats_page *, NUM_STATS_AREAS, 332 GFP_KERNEL_ACCOUNT); 333 if (!pstats) 334 return -ENOMEM; 335 336 pstats[HV_STATS_AREA_SELF] = mshv_partition_stats_map(partition_id, 337 HV_STATS_AREA_SELF); 338 if (IS_ERR(pstats[HV_STATS_AREA_SELF])) { 339 err = PTR_ERR(pstats[HV_STATS_AREA_SELF]); 340 goto cleanup; 341 } 342 343 /* 344 * L1VH partition cannot access its partition stats in parent area. 345 */ 346 if (is_l1vh_parent(partition_id)) { 347 pstats[HV_STATS_AREA_PARENT] = pstats[HV_STATS_AREA_SELF]; 348 } else { 349 pstats[HV_STATS_AREA_PARENT] = mshv_partition_stats_map(partition_id, 350 HV_STATS_AREA_PARENT); 351 if (IS_ERR(pstats[HV_STATS_AREA_PARENT])) { 352 err = PTR_ERR(pstats[HV_STATS_AREA_PARENT]); 353 goto unmap_self; 354 } 355 if (!pstats[HV_STATS_AREA_PARENT]) 356 pstats[HV_STATS_AREA_PARENT] = pstats[HV_STATS_AREA_SELF]; 357 } 358 359 dentry = debugfs_create_file("stats", 0400, parent, 360 pstats, &partition_stats_fops); 361 if (IS_ERR(dentry)) { 362 err = PTR_ERR(dentry); 363 goto unmap_partition_stats; 364 } 365 366 *partition_stats_ptr = dentry; 367 return 0; 368 369 unmap_partition_stats: 370 if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF]) 371 mshv_partition_stats_unmap(partition_id, pstats[HV_STATS_AREA_PARENT], 372 HV_STATS_AREA_PARENT); 373 unmap_self: 374 mshv_partition_stats_unmap(partition_id, pstats[HV_STATS_AREA_SELF], 375 HV_STATS_AREA_SELF); 376 cleanup: 377 kfree(pstats); 378 return err; 379 } 380 381 static void partition_debugfs_remove(u64 partition_id, struct dentry *dentry) 382 { 383 struct hv_stats_page **pstats = NULL; 384 385 pstats = dentry->d_inode->i_private; 386 387 debugfs_remove_recursive(dentry->d_parent); 388 389 if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF]) { 390 mshv_partition_stats_unmap(partition_id, 391 pstats[HV_STATS_AREA_PARENT], 392 HV_STATS_AREA_PARENT); 393 } 394 395 mshv_partition_stats_unmap(partition_id, 396 pstats[HV_STATS_AREA_SELF], 397 HV_STATS_AREA_SELF); 398 399 kfree(pstats); 400 } 401 402 static int partition_debugfs_create(u64 partition_id, 403 struct dentry **vp_dir_ptr, 404 struct dentry **partition_stats_ptr, 405 struct dentry *parent) 406 { 407 char part_id_str[U64_BUF_SZ]; 408 struct dentry *part_id_dir, *vp_dir; 409 int err; 410 411 if (is_l1vh_parent(partition_id)) 412 sprintf(part_id_str, "self"); 413 else 414 sprintf(part_id_str, "%llu", partition_id); 415 416 part_id_dir = debugfs_create_dir(part_id_str, parent); 417 if (IS_ERR(part_id_dir)) 418 return PTR_ERR(part_id_dir); 419 420 vp_dir = debugfs_create_dir("vp", part_id_dir); 421 if (IS_ERR(vp_dir)) { 422 err = PTR_ERR(vp_dir); 423 goto remove_debugfs_partition_id; 424 } 425 426 err = mshv_debugfs_partition_stats_create(partition_id, 427 partition_stats_ptr, 428 part_id_dir); 429 if (err) 430 goto remove_debugfs_partition_id; 431 432 *vp_dir_ptr = vp_dir; 433 434 return 0; 435 436 remove_debugfs_partition_id: 437 debugfs_remove_recursive(part_id_dir); 438 return err; 439 } 440 441 static void parent_vp_debugfs_remove(u32 vp_index, 442 struct dentry *vp_stats_ptr) 443 { 444 struct hv_stats_page **pstats; 445 446 pstats = vp_stats_ptr->d_inode->i_private; 447 vp_debugfs_remove(vp_stats_ptr); 448 mshv_vp_stats_unmap(hv_current_partition_id, vp_index, pstats); 449 kfree(pstats); 450 } 451 452 static void mshv_debugfs_parent_partition_remove(void) 453 { 454 int idx; 455 456 for_each_online_cpu(idx) 457 parent_vp_debugfs_remove(hv_vp_index[idx], 458 parent_vp_stats[idx]); 459 460 partition_debugfs_remove(hv_current_partition_id, 461 parent_partition_stats); 462 kfree(parent_vp_stats); 463 parent_vp_stats = NULL; 464 parent_partition_stats = NULL; 465 } 466 467 static int __init parent_vp_debugfs_create(u32 vp_index, 468 struct dentry **vp_stats_ptr, 469 struct dentry *parent) 470 { 471 struct hv_stats_page **pstats; 472 int err; 473 474 pstats = kzalloc_objs(struct hv_stats_page *, NUM_STATS_AREAS, 475 GFP_KERNEL_ACCOUNT); 476 if (!pstats) 477 return -ENOMEM; 478 479 err = mshv_vp_stats_map(hv_current_partition_id, vp_index, pstats); 480 if (err) 481 goto cleanup; 482 483 err = vp_debugfs_create(hv_current_partition_id, vp_index, pstats, 484 vp_stats_ptr, parent); 485 if (err) 486 goto unmap_vp_stats; 487 488 return 0; 489 490 unmap_vp_stats: 491 mshv_vp_stats_unmap(hv_current_partition_id, vp_index, pstats); 492 cleanup: 493 kfree(pstats); 494 return err; 495 } 496 497 static int __init mshv_debugfs_parent_partition_create(void) 498 { 499 struct dentry *vp_dir; 500 int err, idx, i; 501 502 mshv_debugfs_partition = debugfs_create_dir("partition", 503 mshv_debugfs); 504 if (IS_ERR(mshv_debugfs_partition)) 505 return PTR_ERR(mshv_debugfs_partition); 506 507 err = partition_debugfs_create(hv_current_partition_id, 508 &vp_dir, 509 &parent_partition_stats, 510 mshv_debugfs_partition); 511 if (err) 512 goto remove_debugfs_partition; 513 514 parent_vp_stats = kzalloc_objs(*parent_vp_stats, nr_cpu_ids); 515 if (!parent_vp_stats) { 516 err = -ENOMEM; 517 goto remove_debugfs_partition; 518 } 519 520 for_each_online_cpu(idx) { 521 err = parent_vp_debugfs_create(hv_vp_index[idx], 522 &parent_vp_stats[idx], 523 vp_dir); 524 if (err) 525 goto remove_debugfs_partition_vp; 526 } 527 528 return 0; 529 530 remove_debugfs_partition_vp: 531 for_each_online_cpu(i) { 532 if (i >= idx) 533 break; 534 parent_vp_debugfs_remove(i, parent_vp_stats[i]); 535 } 536 partition_debugfs_remove(hv_current_partition_id, 537 parent_partition_stats); 538 539 kfree(parent_vp_stats); 540 parent_vp_stats = NULL; 541 parent_partition_stats = NULL; 542 543 remove_debugfs_partition: 544 debugfs_remove_recursive(mshv_debugfs_partition); 545 mshv_debugfs_partition = NULL; 546 return err; 547 } 548 549 static int hv_stats_show(struct seq_file *m, void *v) 550 { 551 const struct hv_stats_page *stats = m->private; 552 int idx; 553 554 for (idx = 0; idx < ARRAY_SIZE(hv_hypervisor_counters); idx++) { 555 char *name = hv_hypervisor_counters[idx]; 556 557 if (!name) 558 continue; 559 seq_printf(m, "%-27s: %llu\n", name, stats->data[idx]); 560 } 561 562 return 0; 563 } 564 DEFINE_SHOW_ATTRIBUTE(hv_stats); 565 566 static void mshv_hv_stats_unmap(void) 567 { 568 union hv_stats_object_identity identity = { 569 .hv.stats_area_type = HV_STATS_AREA_SELF, 570 }; 571 int err; 572 573 err = hv_unmap_stats_page(HV_STATS_OBJECT_HYPERVISOR, NULL, &identity); 574 if (err) 575 pr_err("%s: failed to unmap hypervisor stats: %d\n", 576 __func__, err); 577 } 578 579 static void * __init mshv_hv_stats_map(void) 580 { 581 union hv_stats_object_identity identity = { 582 .hv.stats_area_type = HV_STATS_AREA_SELF, 583 }; 584 struct hv_stats_page *stats; 585 int err; 586 587 err = hv_map_stats_page(HV_STATS_OBJECT_HYPERVISOR, &identity, &stats); 588 if (err) { 589 pr_err("%s: failed to map hypervisor stats: %d\n", 590 __func__, err); 591 return ERR_PTR(err); 592 } 593 return stats; 594 } 595 596 static int __init mshv_debugfs_hv_stats_create(struct dentry *parent) 597 { 598 struct dentry *dentry; 599 u64 *stats; 600 int err; 601 602 stats = mshv_hv_stats_map(); 603 if (IS_ERR(stats)) 604 return PTR_ERR(stats); 605 606 dentry = debugfs_create_file("stats", 0400, parent, 607 stats, &hv_stats_fops); 608 if (IS_ERR(dentry)) { 609 err = PTR_ERR(dentry); 610 pr_err("%s: failed to create hypervisor stats dentry: %d\n", 611 __func__, err); 612 goto unmap_hv_stats; 613 } 614 615 mshv_lps_count = stats[HV_HYPERVISOR_COUNTER_LOGICAL_PROCESSORS]; 616 617 return 0; 618 619 unmap_hv_stats: 620 mshv_hv_stats_unmap(); 621 return err; 622 } 623 624 int mshv_debugfs_vp_create(struct mshv_vp *vp) 625 { 626 struct mshv_partition *p = vp->vp_partition; 627 628 if (!mshv_debugfs) 629 return 0; 630 631 return vp_debugfs_create(p->pt_id, vp->vp_index, 632 vp->vp_stats_pages, 633 &vp->vp_stats_dentry, 634 p->pt_vp_dentry); 635 } 636 637 void mshv_debugfs_vp_remove(struct mshv_vp *vp) 638 { 639 if (!mshv_debugfs) 640 return; 641 642 vp_debugfs_remove(vp->vp_stats_dentry); 643 } 644 645 int mshv_debugfs_partition_create(struct mshv_partition *partition) 646 { 647 int err; 648 649 if (!mshv_debugfs) 650 return 0; 651 652 err = partition_debugfs_create(partition->pt_id, 653 &partition->pt_vp_dentry, 654 &partition->pt_stats_dentry, 655 mshv_debugfs_partition); 656 if (err) 657 return err; 658 659 return 0; 660 } 661 662 void mshv_debugfs_partition_remove(struct mshv_partition *partition) 663 { 664 if (!mshv_debugfs) 665 return; 666 667 partition_debugfs_remove(partition->pt_id, 668 partition->pt_stats_dentry); 669 } 670 671 int __init mshv_debugfs_init(void) 672 { 673 int err; 674 675 mshv_debugfs = debugfs_create_dir("mshv", NULL); 676 if (IS_ERR(mshv_debugfs)) { 677 pr_err("%s: failed to create debugfs directory\n", __func__); 678 return PTR_ERR(mshv_debugfs); 679 } 680 681 if (hv_root_partition()) { 682 err = mshv_debugfs_hv_stats_create(mshv_debugfs); 683 if (err) 684 goto remove_mshv_dir; 685 686 err = mshv_debugfs_lp_create(mshv_debugfs); 687 if (err) 688 goto unmap_hv_stats; 689 } 690 691 err = mshv_debugfs_parent_partition_create(); 692 if (err) 693 goto unmap_lp_stats; 694 695 return 0; 696 697 unmap_lp_stats: 698 if (hv_root_partition()) { 699 mshv_debugfs_lp_remove(); 700 mshv_debugfs_lp = NULL; 701 } 702 unmap_hv_stats: 703 if (hv_root_partition()) 704 mshv_hv_stats_unmap(); 705 remove_mshv_dir: 706 debugfs_remove_recursive(mshv_debugfs); 707 mshv_debugfs = NULL; 708 return err; 709 } 710 711 void mshv_debugfs_exit(void) 712 { 713 mshv_debugfs_parent_partition_remove(); 714 715 if (hv_root_partition()) { 716 mshv_debugfs_lp_remove(); 717 mshv_debugfs_lp = NULL; 718 mshv_hv_stats_unmap(); 719 } 720 721 debugfs_remove_recursive(mshv_debugfs); 722 mshv_debugfs = NULL; 723 mshv_debugfs_partition = NULL; 724 } 725