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 = kcalloc(mshv_lps_count, 160 sizeof(*mshv_lps_stats), 161 GFP_KERNEL_ACCOUNT); 162 163 if (!mshv_lps_stats) 164 return -ENOMEM; 165 166 lp_dir = debugfs_create_dir("lp", parent); 167 if (IS_ERR(lp_dir)) { 168 err = PTR_ERR(lp_dir); 169 goto free_lp_stats; 170 } 171 172 for (lp_index = 0; lp_index < mshv_lps_count; lp_index++) { 173 err = lp_debugfs_create(lp_index, lp_dir); 174 if (err) 175 goto remove_debugfs_lps; 176 } 177 178 mshv_debugfs_lp = lp_dir; 179 180 return 0; 181 182 remove_debugfs_lps: 183 for (lp_index -= 1; lp_index >= 0; lp_index--) 184 mshv_lp_stats_unmap(lp_index); 185 debugfs_remove_recursive(lp_dir); 186 free_lp_stats: 187 kfree(mshv_lps_stats); 188 mshv_lps_stats = NULL; 189 190 return err; 191 } 192 193 static int vp_stats_show(struct seq_file *m, void *v) 194 { 195 const struct hv_stats_page **pstats = m->private; 196 u64 parent_val, self_val; 197 int idx; 198 199 /* 200 * For VP and partition stats, there may be two stats areas mapped, 201 * SELF and PARENT. These refer to the privilege level of the data in 202 * each page. Some fields may be 0 in SELF and nonzero in PARENT, or 203 * vice versa. 204 * 205 * Hence, prioritize printing from the PARENT page (more privileged 206 * data), but use the value from the SELF page if the PARENT value is 207 * 0. 208 */ 209 210 for (idx = 0; idx < ARRAY_SIZE(hv_vp_counters); idx++) { 211 char *name = hv_vp_counters[idx]; 212 213 if (!name) 214 continue; 215 216 parent_val = pstats[HV_STATS_AREA_PARENT]->data[idx]; 217 self_val = pstats[HV_STATS_AREA_SELF]->data[idx]; 218 seq_printf(m, "%-43s: %llu\n", name, 219 parent_val ? parent_val : self_val); 220 } 221 222 return 0; 223 } 224 DEFINE_SHOW_ATTRIBUTE(vp_stats); 225 226 static void vp_debugfs_remove(struct dentry *vp_stats) 227 { 228 debugfs_remove_recursive(vp_stats->d_parent); 229 } 230 231 static int vp_debugfs_create(u64 partition_id, u32 vp_index, 232 struct hv_stats_page **pstats, 233 struct dentry **vp_stats_ptr, 234 struct dentry *parent) 235 { 236 struct dentry *vp_idx_dir, *d; 237 char vp_idx_str[U32_BUF_SZ]; 238 int err; 239 240 sprintf(vp_idx_str, "%u", vp_index); 241 242 vp_idx_dir = debugfs_create_dir(vp_idx_str, parent); 243 if (IS_ERR(vp_idx_dir)) 244 return PTR_ERR(vp_idx_dir); 245 246 d = debugfs_create_file("stats", 0400, vp_idx_dir, 247 pstats, &vp_stats_fops); 248 if (IS_ERR(d)) { 249 err = PTR_ERR(d); 250 goto remove_debugfs_vp_idx; 251 } 252 253 *vp_stats_ptr = d; 254 255 return 0; 256 257 remove_debugfs_vp_idx: 258 debugfs_remove_recursive(vp_idx_dir); 259 return err; 260 } 261 262 static int partition_stats_show(struct seq_file *m, void *v) 263 { 264 const struct hv_stats_page **pstats = m->private; 265 u64 parent_val, self_val; 266 int idx; 267 268 for (idx = 0; idx < ARRAY_SIZE(hv_partition_counters); idx++) { 269 char *name = hv_partition_counters[idx]; 270 271 if (!name) 272 continue; 273 274 parent_val = pstats[HV_STATS_AREA_PARENT]->data[idx]; 275 self_val = pstats[HV_STATS_AREA_SELF]->data[idx]; 276 seq_printf(m, "%-37s: %llu\n", name, 277 parent_val ? parent_val : self_val); 278 } 279 280 return 0; 281 } 282 DEFINE_SHOW_ATTRIBUTE(partition_stats); 283 284 static void mshv_partition_stats_unmap(u64 partition_id, 285 struct hv_stats_page *stats_page, 286 enum hv_stats_area_type stats_area_type) 287 { 288 union hv_stats_object_identity identity = { 289 .partition.partition_id = partition_id, 290 .partition.stats_area_type = stats_area_type, 291 }; 292 int err; 293 294 err = hv_unmap_stats_page(HV_STATS_OBJECT_PARTITION, stats_page, 295 &identity); 296 if (err) 297 pr_err("%s: failed to unmap partition %lld %s stats, err: %d\n", 298 __func__, partition_id, 299 (stats_area_type == HV_STATS_AREA_SELF) ? "self" : "parent", 300 err); 301 } 302 303 static struct hv_stats_page *mshv_partition_stats_map(u64 partition_id, 304 enum hv_stats_area_type stats_area_type) 305 { 306 union hv_stats_object_identity identity = { 307 .partition.partition_id = partition_id, 308 .partition.stats_area_type = stats_area_type, 309 }; 310 struct hv_stats_page *stats; 311 int err; 312 313 err = hv_map_stats_page(HV_STATS_OBJECT_PARTITION, &identity, &stats); 314 if (err) { 315 pr_err("%s: failed to map partition %lld %s stats, err: %d\n", 316 __func__, partition_id, 317 (stats_area_type == HV_STATS_AREA_SELF) ? "self" : "parent", 318 err); 319 return ERR_PTR(err); 320 } 321 return stats; 322 } 323 324 static int mshv_debugfs_partition_stats_create(u64 partition_id, 325 struct dentry **partition_stats_ptr, 326 struct dentry *parent) 327 { 328 struct dentry *dentry; 329 struct hv_stats_page **pstats; 330 int err; 331 332 pstats = kcalloc(NUM_STATS_AREAS, sizeof(struct hv_stats_page *), 333 GFP_KERNEL_ACCOUNT); 334 if (!pstats) 335 return -ENOMEM; 336 337 pstats[HV_STATS_AREA_SELF] = mshv_partition_stats_map(partition_id, 338 HV_STATS_AREA_SELF); 339 if (IS_ERR(pstats[HV_STATS_AREA_SELF])) { 340 err = PTR_ERR(pstats[HV_STATS_AREA_SELF]); 341 goto cleanup; 342 } 343 344 /* 345 * L1VH partition cannot access its partition stats in parent area. 346 */ 347 if (is_l1vh_parent(partition_id)) { 348 pstats[HV_STATS_AREA_PARENT] = pstats[HV_STATS_AREA_SELF]; 349 } else { 350 pstats[HV_STATS_AREA_PARENT] = mshv_partition_stats_map(partition_id, 351 HV_STATS_AREA_PARENT); 352 if (IS_ERR(pstats[HV_STATS_AREA_PARENT])) { 353 err = PTR_ERR(pstats[HV_STATS_AREA_PARENT]); 354 goto unmap_self; 355 } 356 if (!pstats[HV_STATS_AREA_PARENT]) 357 pstats[HV_STATS_AREA_PARENT] = pstats[HV_STATS_AREA_SELF]; 358 } 359 360 dentry = debugfs_create_file("stats", 0400, parent, 361 pstats, &partition_stats_fops); 362 if (IS_ERR(dentry)) { 363 err = PTR_ERR(dentry); 364 goto unmap_partition_stats; 365 } 366 367 *partition_stats_ptr = dentry; 368 return 0; 369 370 unmap_partition_stats: 371 if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF]) 372 mshv_partition_stats_unmap(partition_id, pstats[HV_STATS_AREA_PARENT], 373 HV_STATS_AREA_PARENT); 374 unmap_self: 375 mshv_partition_stats_unmap(partition_id, pstats[HV_STATS_AREA_SELF], 376 HV_STATS_AREA_SELF); 377 cleanup: 378 kfree(pstats); 379 return err; 380 } 381 382 static void partition_debugfs_remove(u64 partition_id, struct dentry *dentry) 383 { 384 struct hv_stats_page **pstats = NULL; 385 386 pstats = dentry->d_inode->i_private; 387 388 debugfs_remove_recursive(dentry->d_parent); 389 390 if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF]) { 391 mshv_partition_stats_unmap(partition_id, 392 pstats[HV_STATS_AREA_PARENT], 393 HV_STATS_AREA_PARENT); 394 } 395 396 mshv_partition_stats_unmap(partition_id, 397 pstats[HV_STATS_AREA_SELF], 398 HV_STATS_AREA_SELF); 399 400 kfree(pstats); 401 } 402 403 static int partition_debugfs_create(u64 partition_id, 404 struct dentry **vp_dir_ptr, 405 struct dentry **partition_stats_ptr, 406 struct dentry *parent) 407 { 408 char part_id_str[U64_BUF_SZ]; 409 struct dentry *part_id_dir, *vp_dir; 410 int err; 411 412 if (is_l1vh_parent(partition_id)) 413 sprintf(part_id_str, "self"); 414 else 415 sprintf(part_id_str, "%llu", partition_id); 416 417 part_id_dir = debugfs_create_dir(part_id_str, parent); 418 if (IS_ERR(part_id_dir)) 419 return PTR_ERR(part_id_dir); 420 421 vp_dir = debugfs_create_dir("vp", part_id_dir); 422 if (IS_ERR(vp_dir)) { 423 err = PTR_ERR(vp_dir); 424 goto remove_debugfs_partition_id; 425 } 426 427 err = mshv_debugfs_partition_stats_create(partition_id, 428 partition_stats_ptr, 429 part_id_dir); 430 if (err) 431 goto remove_debugfs_partition_id; 432 433 *vp_dir_ptr = vp_dir; 434 435 return 0; 436 437 remove_debugfs_partition_id: 438 debugfs_remove_recursive(part_id_dir); 439 return err; 440 } 441 442 static void parent_vp_debugfs_remove(u32 vp_index, 443 struct dentry *vp_stats_ptr) 444 { 445 struct hv_stats_page **pstats; 446 447 pstats = vp_stats_ptr->d_inode->i_private; 448 vp_debugfs_remove(vp_stats_ptr); 449 mshv_vp_stats_unmap(hv_current_partition_id, vp_index, pstats); 450 kfree(pstats); 451 } 452 453 static void mshv_debugfs_parent_partition_remove(void) 454 { 455 int idx; 456 457 for_each_online_cpu(idx) 458 parent_vp_debugfs_remove(hv_vp_index[idx], 459 parent_vp_stats[idx]); 460 461 partition_debugfs_remove(hv_current_partition_id, 462 parent_partition_stats); 463 kfree(parent_vp_stats); 464 parent_vp_stats = NULL; 465 parent_partition_stats = NULL; 466 } 467 468 static int __init parent_vp_debugfs_create(u32 vp_index, 469 struct dentry **vp_stats_ptr, 470 struct dentry *parent) 471 { 472 struct hv_stats_page **pstats; 473 int err; 474 475 pstats = kcalloc(NUM_STATS_AREAS, sizeof(struct hv_stats_page *), 476 GFP_KERNEL_ACCOUNT); 477 if (!pstats) 478 return -ENOMEM; 479 480 err = mshv_vp_stats_map(hv_current_partition_id, vp_index, pstats); 481 if (err) 482 goto cleanup; 483 484 err = vp_debugfs_create(hv_current_partition_id, vp_index, pstats, 485 vp_stats_ptr, parent); 486 if (err) 487 goto unmap_vp_stats; 488 489 return 0; 490 491 unmap_vp_stats: 492 mshv_vp_stats_unmap(hv_current_partition_id, vp_index, pstats); 493 cleanup: 494 kfree(pstats); 495 return err; 496 } 497 498 static int __init mshv_debugfs_parent_partition_create(void) 499 { 500 struct dentry *vp_dir; 501 int err, idx, i; 502 503 mshv_debugfs_partition = debugfs_create_dir("partition", 504 mshv_debugfs); 505 if (IS_ERR(mshv_debugfs_partition)) 506 return PTR_ERR(mshv_debugfs_partition); 507 508 err = partition_debugfs_create(hv_current_partition_id, 509 &vp_dir, 510 &parent_partition_stats, 511 mshv_debugfs_partition); 512 if (err) 513 goto remove_debugfs_partition; 514 515 parent_vp_stats = kcalloc(nr_cpu_ids, sizeof(*parent_vp_stats), 516 GFP_KERNEL); 517 if (!parent_vp_stats) { 518 err = -ENOMEM; 519 goto remove_debugfs_partition; 520 } 521 522 for_each_online_cpu(idx) { 523 err = parent_vp_debugfs_create(hv_vp_index[idx], 524 &parent_vp_stats[idx], 525 vp_dir); 526 if (err) 527 goto remove_debugfs_partition_vp; 528 } 529 530 return 0; 531 532 remove_debugfs_partition_vp: 533 for_each_online_cpu(i) { 534 if (i >= idx) 535 break; 536 parent_vp_debugfs_remove(i, parent_vp_stats[i]); 537 } 538 partition_debugfs_remove(hv_current_partition_id, 539 parent_partition_stats); 540 541 kfree(parent_vp_stats); 542 parent_vp_stats = NULL; 543 parent_partition_stats = NULL; 544 545 remove_debugfs_partition: 546 debugfs_remove_recursive(mshv_debugfs_partition); 547 mshv_debugfs_partition = NULL; 548 return err; 549 } 550 551 static int hv_stats_show(struct seq_file *m, void *v) 552 { 553 const struct hv_stats_page *stats = m->private; 554 int idx; 555 556 for (idx = 0; idx < ARRAY_SIZE(hv_hypervisor_counters); idx++) { 557 char *name = hv_hypervisor_counters[idx]; 558 559 if (!name) 560 continue; 561 seq_printf(m, "%-27s: %llu\n", name, stats->data[idx]); 562 } 563 564 return 0; 565 } 566 DEFINE_SHOW_ATTRIBUTE(hv_stats); 567 568 static void mshv_hv_stats_unmap(void) 569 { 570 union hv_stats_object_identity identity = { 571 .hv.stats_area_type = HV_STATS_AREA_SELF, 572 }; 573 int err; 574 575 err = hv_unmap_stats_page(HV_STATS_OBJECT_HYPERVISOR, NULL, &identity); 576 if (err) 577 pr_err("%s: failed to unmap hypervisor stats: %d\n", 578 __func__, err); 579 } 580 581 static void * __init mshv_hv_stats_map(void) 582 { 583 union hv_stats_object_identity identity = { 584 .hv.stats_area_type = HV_STATS_AREA_SELF, 585 }; 586 struct hv_stats_page *stats; 587 int err; 588 589 err = hv_map_stats_page(HV_STATS_OBJECT_HYPERVISOR, &identity, &stats); 590 if (err) { 591 pr_err("%s: failed to map hypervisor stats: %d\n", 592 __func__, err); 593 return ERR_PTR(err); 594 } 595 return stats; 596 } 597 598 static int __init mshv_debugfs_hv_stats_create(struct dentry *parent) 599 { 600 struct dentry *dentry; 601 u64 *stats; 602 int err; 603 604 stats = mshv_hv_stats_map(); 605 if (IS_ERR(stats)) 606 return PTR_ERR(stats); 607 608 dentry = debugfs_create_file("stats", 0400, parent, 609 stats, &hv_stats_fops); 610 if (IS_ERR(dentry)) { 611 err = PTR_ERR(dentry); 612 pr_err("%s: failed to create hypervisor stats dentry: %d\n", 613 __func__, err); 614 goto unmap_hv_stats; 615 } 616 617 mshv_lps_count = stats[HV_HYPERVISOR_COUNTER_LOGICAL_PROCESSORS]; 618 619 return 0; 620 621 unmap_hv_stats: 622 mshv_hv_stats_unmap(); 623 return err; 624 } 625 626 int mshv_debugfs_vp_create(struct mshv_vp *vp) 627 { 628 struct mshv_partition *p = vp->vp_partition; 629 630 if (!mshv_debugfs) 631 return 0; 632 633 return vp_debugfs_create(p->pt_id, vp->vp_index, 634 vp->vp_stats_pages, 635 &vp->vp_stats_dentry, 636 p->pt_vp_dentry); 637 } 638 639 void mshv_debugfs_vp_remove(struct mshv_vp *vp) 640 { 641 if (!mshv_debugfs) 642 return; 643 644 vp_debugfs_remove(vp->vp_stats_dentry); 645 } 646 647 int mshv_debugfs_partition_create(struct mshv_partition *partition) 648 { 649 int err; 650 651 if (!mshv_debugfs) 652 return 0; 653 654 err = partition_debugfs_create(partition->pt_id, 655 &partition->pt_vp_dentry, 656 &partition->pt_stats_dentry, 657 mshv_debugfs_partition); 658 if (err) 659 return err; 660 661 return 0; 662 } 663 664 void mshv_debugfs_partition_remove(struct mshv_partition *partition) 665 { 666 if (!mshv_debugfs) 667 return; 668 669 partition_debugfs_remove(partition->pt_id, 670 partition->pt_stats_dentry); 671 } 672 673 int __init mshv_debugfs_init(void) 674 { 675 int err; 676 677 mshv_debugfs = debugfs_create_dir("mshv", NULL); 678 if (IS_ERR(mshv_debugfs)) { 679 pr_err("%s: failed to create debugfs directory\n", __func__); 680 return PTR_ERR(mshv_debugfs); 681 } 682 683 if (hv_root_partition()) { 684 err = mshv_debugfs_hv_stats_create(mshv_debugfs); 685 if (err) 686 goto remove_mshv_dir; 687 688 err = mshv_debugfs_lp_create(mshv_debugfs); 689 if (err) 690 goto unmap_hv_stats; 691 } 692 693 err = mshv_debugfs_parent_partition_create(); 694 if (err) 695 goto unmap_lp_stats; 696 697 return 0; 698 699 unmap_lp_stats: 700 if (hv_root_partition()) { 701 mshv_debugfs_lp_remove(); 702 mshv_debugfs_lp = NULL; 703 } 704 unmap_hv_stats: 705 if (hv_root_partition()) 706 mshv_hv_stats_unmap(); 707 remove_mshv_dir: 708 debugfs_remove_recursive(mshv_debugfs); 709 mshv_debugfs = NULL; 710 return err; 711 } 712 713 void mshv_debugfs_exit(void) 714 { 715 mshv_debugfs_parent_partition_remove(); 716 717 if (hv_root_partition()) { 718 mshv_debugfs_lp_remove(); 719 mshv_debugfs_lp = NULL; 720 mshv_hv_stats_unmap(); 721 } 722 723 debugfs_remove_recursive(mshv_debugfs); 724 mshv_debugfs = NULL; 725 mshv_debugfs_partition = NULL; 726 } 727