Introduce a debugfs interface to expose root and child partition stats
when running with mshv_root.

Create a debugfs directory "mshv" containing 'stats' files organized by
type and id. A stats file contains a number of counters depending on
its type. e.g. an excerpt from a VP stats file:

TotalRunTime                  : 1997602722
HypervisorRunTime             : 649671371
RemoteNodeRunTime             : 0
NormalizedRunTime             : 1997602721
IdealCpu                      : 0
HypercallsCount               : 1708169
HypercallsTime                : 111914774
PageInvalidationsCount        : 0
PageInvalidationsTime         : 0

On a root partition with some active child partitions, the entire
directory structure may look like:

mshv/
  stats             # hypervisor stats
  lp/               # logical processors
    0/              # LP id
      stats         # LP 0 stats
    1/
    2/
    3/
  partition/        # partition stats
    1/              # root partition id
      stats         # root partition stats
      vp/           # root virtual processors
        0/          # root VP id
          stats     # root VP 0 stats
        1/
        2/
        3/
    42/             # child partition id
      stats         # child partition stats
      vp/           # child VPs
        0/          # child VP id
          stats     # child VP 0 stats
        1/
    43/
    55/

On L1VH, some stats are not present as it does not own the hardware
like the root partition does:
- The hypervisor and lp stats are not present
- L1VH's partition directory is named "self" because it can't get its
  own id
- Some of L1VH's partition and VP stats fields are not populated, because
  it can't map its own HV_STATS_AREA_PARENT page.

Co-developed-by: Stanislav Kinsburskii <[email protected]>
Signed-off-by: Stanislav Kinsburskii <[email protected]>
Co-developed-by: Praveen K Paladugu <[email protected]>
Signed-off-by: Praveen K Paladugu <[email protected]>
Co-developed-by: Mukesh Rathor <[email protected]>
Signed-off-by: Mukesh Rathor <[email protected]>
Co-developed-by: Purna Pavan Chandra Aekkaladevi 
<[email protected]>
Signed-off-by: Purna Pavan Chandra Aekkaladevi 
<[email protected]>
Co-developed-by: Jinank Jain <[email protected]>
Signed-off-by: Jinank Jain <[email protected]>
Signed-off-by: Nuno Das Neves <[email protected]>
Reviewed-by: Stanislav Kinsburskii <[email protected]>
---
 drivers/hv/Makefile         |   1 +
 drivers/hv/mshv_debugfs.c   | 726 ++++++++++++++++++++++++++++++++++++
 drivers/hv/mshv_root.h      |  34 ++
 drivers/hv/mshv_root_main.c |  26 +-
 4 files changed, 785 insertions(+), 2 deletions(-)
 create mode 100644 drivers/hv/mshv_debugfs.c

diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index a49f93c2d245..2593711c3628 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -15,6 +15,7 @@ hv_vmbus-$(CONFIG_HYPERV_TESTING)     += hv_debugfs.o
 hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_utils_transport.o
 mshv_root-y := mshv_root_main.o mshv_synic.o mshv_eventfd.o mshv_irq.o \
               mshv_root_hv_call.o mshv_portid_table.o mshv_regions.o
+mshv_root-$(CONFIG_DEBUG_FS) += mshv_debugfs.o
 mshv_vtl-y := mshv_vtl_main.o
 
 # Code that must be built-in
diff --git a/drivers/hv/mshv_debugfs.c b/drivers/hv/mshv_debugfs.c
new file mode 100644
index 000000000000..4553163e8665
--- /dev/null
+++ b/drivers/hv/mshv_debugfs.c
@@ -0,0 +1,726 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, Microsoft Corporation.
+ *
+ * The /sys/kernel/debug/mshv directory contents.
+ * Contains various statistics data, provided by the hypervisor.
+ *
+ * Authors: Microsoft Linux virtualization team
+ */
+
+#include <linux/debugfs.h>
+#include <linux/stringify.h>
+#include <asm/mshyperv.h>
+#include <linux/slab.h>
+
+#include "mshv.h"
+#include "mshv_root.h"
+
+/* Ensure this file is not used elsewhere by accident */
+#define MSHV_DEBUGFS_C
+#include "mshv_debugfs_counters.c"
+
+#define U32_BUF_SZ 11
+#define U64_BUF_SZ 21
+/* Only support SELF and PARENT areas */
+#define NUM_STATS_AREAS 2
+static_assert(HV_STATS_AREA_SELF == 0 && HV_STATS_AREA_PARENT == 1,
+             "SELF and PARENT areas must be usable as indices into an array of 
size NUM_STATS_AREAS");
+/* HV_HYPERVISOR_COUNTER */
+#define HV_HYPERVISOR_COUNTER_LOGICAL_PROCESSORS 1
+
+static struct dentry *mshv_debugfs;
+static struct dentry *mshv_debugfs_partition;
+static struct dentry *mshv_debugfs_lp;
+static struct dentry **parent_vp_stats;
+static struct dentry *parent_partition_stats;
+
+static u64 mshv_lps_count;
+static struct hv_stats_page **mshv_lps_stats;
+
+static int lp_stats_show(struct seq_file *m, void *v)
+{
+       const struct hv_stats_page *stats = m->private;
+       int idx;
+
+       for (idx = 0; idx < ARRAY_SIZE(hv_lp_counters); idx++) {
+               char *name = hv_lp_counters[idx];
+
+               if (!name)
+                       continue;
+               seq_printf(m, "%-32s: %llu\n", name, stats->data[idx]);
+       }
+
+       return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(lp_stats);
+
+static void mshv_lp_stats_unmap(u32 lp_index)
+{
+       union hv_stats_object_identity identity = {
+               .lp.lp_index = lp_index,
+               .lp.stats_area_type = HV_STATS_AREA_SELF,
+       };
+       int err;
+
+       err = hv_unmap_stats_page(HV_STATS_OBJECT_LOGICAL_PROCESSOR,
+                                 mshv_lps_stats[lp_index], &identity);
+       if (err)
+               pr_err("%s: failed to unmap logical processor %u stats, err: 
%d\n",
+                      __func__, lp_index, err);
+
+       mshv_lps_stats[lp_index] = NULL;
+}
+
+static struct hv_stats_page * __init mshv_lp_stats_map(u32 lp_index)
+{
+       union hv_stats_object_identity identity = {
+               .lp.lp_index = lp_index,
+               .lp.stats_area_type = HV_STATS_AREA_SELF,
+       };
+       struct hv_stats_page *stats;
+       int err;
+
+       err = hv_map_stats_page(HV_STATS_OBJECT_LOGICAL_PROCESSOR, &identity,
+                               &stats);
+       if (err) {
+               pr_err("%s: failed to map logical processor %u stats, err: 
%d\n",
+                      __func__, lp_index, err);
+               return ERR_PTR(err);
+       }
+       mshv_lps_stats[lp_index] = stats;
+
+       return stats;
+}
+
+static struct hv_stats_page * __init lp_debugfs_stats_create(u32 lp_index,
+                                                            struct dentry 
*parent)
+{
+       struct dentry *dentry;
+       struct hv_stats_page *stats;
+
+       stats = mshv_lp_stats_map(lp_index);
+       if (IS_ERR(stats))
+               return stats;
+
+       dentry = debugfs_create_file("stats", 0400, parent,
+                                    stats, &lp_stats_fops);
+       if (IS_ERR(dentry)) {
+               mshv_lp_stats_unmap(lp_index);
+               return ERR_CAST(dentry);
+       }
+       return stats;
+}
+
+static int __init lp_debugfs_create(u32 lp_index, struct dentry *parent)
+{
+       struct dentry *idx;
+       char lp_idx_str[U32_BUF_SZ];
+       struct hv_stats_page *stats;
+       int err;
+
+       sprintf(lp_idx_str, "%u", lp_index);
+
+       idx = debugfs_create_dir(lp_idx_str, parent);
+       if (IS_ERR(idx))
+               return PTR_ERR(idx);
+
+       stats = lp_debugfs_stats_create(lp_index, idx);
+       if (IS_ERR(stats)) {
+               err = PTR_ERR(stats);
+               goto remove_debugfs_lp_idx;
+       }
+
+       return 0;
+
+remove_debugfs_lp_idx:
+       debugfs_remove_recursive(idx);
+       return err;
+}
+
+static void mshv_debugfs_lp_remove(void)
+{
+       int lp_index;
+
+       debugfs_remove_recursive(mshv_debugfs_lp);
+
+       for (lp_index = 0; lp_index < mshv_lps_count; lp_index++)
+               mshv_lp_stats_unmap(lp_index);
+
+       kfree(mshv_lps_stats);
+       mshv_lps_stats = NULL;
+}
+
+static int __init mshv_debugfs_lp_create(struct dentry *parent)
+{
+       struct dentry *lp_dir;
+       int err, lp_index;
+
+       mshv_lps_stats = kcalloc(mshv_lps_count,
+                                sizeof(*mshv_lps_stats),
+                                GFP_KERNEL_ACCOUNT);
+
+       if (!mshv_lps_stats)
+               return -ENOMEM;
+
+       lp_dir = debugfs_create_dir("lp", parent);
+       if (IS_ERR(lp_dir)) {
+               err = PTR_ERR(lp_dir);
+               goto free_lp_stats;
+       }
+
+       for (lp_index = 0; lp_index < mshv_lps_count; lp_index++) {
+               err = lp_debugfs_create(lp_index, lp_dir);
+               if (err)
+                       goto remove_debugfs_lps;
+       }
+
+       mshv_debugfs_lp = lp_dir;
+
+       return 0;
+
+remove_debugfs_lps:
+       for (lp_index -= 1; lp_index >= 0; lp_index--)
+               mshv_lp_stats_unmap(lp_index);
+       debugfs_remove_recursive(lp_dir);
+free_lp_stats:
+       kfree(mshv_lps_stats);
+       mshv_lps_stats = NULL;
+
+       return err;
+}
+
+static int vp_stats_show(struct seq_file *m, void *v)
+{
+       const struct hv_stats_page **pstats = m->private;
+       u64 parent_val, self_val;
+       int idx;
+
+       /*
+        * For VP and partition stats, there may be two stats areas mapped,
+        * SELF and PARENT. These refer to the privilege level of the data in
+        * each page. Some fields may be 0 in SELF and nonzero in PARENT, or
+        * vice versa.
+        *
+        * Hence, prioritize printing from the PARENT page (more privileged
+        * data), but use the value from the SELF page if the PARENT value is
+        * 0.
+        */
+
+       for (idx = 0; idx < ARRAY_SIZE(hv_vp_counters); idx++) {
+               char *name = hv_vp_counters[idx];
+
+               if (!name)
+                       continue;
+
+               parent_val = pstats[HV_STATS_AREA_PARENT]->data[idx];
+               self_val = pstats[HV_STATS_AREA_SELF]->data[idx];
+               seq_printf(m, "%-43s: %llu\n", name,
+                          parent_val ? parent_val : self_val);
+       }
+
+       return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(vp_stats);
+
+static void vp_debugfs_remove(struct dentry *vp_stats)
+{
+       debugfs_remove_recursive(vp_stats->d_parent);
+}
+
+static int vp_debugfs_create(u64 partition_id, u32 vp_index,
+                            struct hv_stats_page **pstats,
+                            struct dentry **vp_stats_ptr,
+                            struct dentry *parent)
+{
+       struct dentry *vp_idx_dir, *d;
+       char vp_idx_str[U32_BUF_SZ];
+       int err;
+
+       sprintf(vp_idx_str, "%u", vp_index);
+
+       vp_idx_dir = debugfs_create_dir(vp_idx_str, parent);
+       if (IS_ERR(vp_idx_dir))
+               return PTR_ERR(vp_idx_dir);
+
+       d = debugfs_create_file("stats", 0400, vp_idx_dir,
+                                    pstats, &vp_stats_fops);
+       if (IS_ERR(d)) {
+               err = PTR_ERR(d);
+               goto remove_debugfs_vp_idx;
+       }
+
+       *vp_stats_ptr = d;
+
+       return 0;
+
+remove_debugfs_vp_idx:
+       debugfs_remove_recursive(vp_idx_dir);
+       return err;
+}
+
+static int partition_stats_show(struct seq_file *m, void *v)
+{
+       const struct hv_stats_page **pstats = m->private;
+       u64 parent_val, self_val;
+       int idx;
+
+       for (idx = 0; idx < ARRAY_SIZE(hv_partition_counters); idx++) {
+               char *name = hv_partition_counters[idx];
+
+               if (!name)
+                       continue;
+
+               parent_val = pstats[HV_STATS_AREA_PARENT]->data[idx];
+               self_val = pstats[HV_STATS_AREA_SELF]->data[idx];
+               seq_printf(m, "%-37s: %llu\n", name,
+                          parent_val ? parent_val : self_val);
+       }
+
+       return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(partition_stats);
+
+static void mshv_partition_stats_unmap(u64 partition_id,
+                                      struct hv_stats_page *stats_page,
+                                      enum hv_stats_area_type stats_area_type)
+{
+       union hv_stats_object_identity identity = {
+               .partition.partition_id = partition_id,
+               .partition.stats_area_type = stats_area_type,
+       };
+       int err;
+
+       err = hv_unmap_stats_page(HV_STATS_OBJECT_PARTITION, stats_page,
+                                 &identity);
+       if (err)
+               pr_err("%s: failed to unmap partition %lld %s stats, err: %d\n",
+                      __func__, partition_id,
+                      (stats_area_type == HV_STATS_AREA_SELF) ? "self" : 
"parent",
+                      err);
+}
+
+static struct hv_stats_page *mshv_partition_stats_map(u64 partition_id,
+                                                     enum hv_stats_area_type 
stats_area_type)
+{
+       union hv_stats_object_identity identity = {
+               .partition.partition_id = partition_id,
+               .partition.stats_area_type = stats_area_type,
+       };
+       struct hv_stats_page *stats;
+       int err;
+
+       err = hv_map_stats_page(HV_STATS_OBJECT_PARTITION, &identity, &stats);
+       if (err) {
+               pr_err("%s: failed to map partition %lld %s stats, err: %d\n",
+                      __func__, partition_id,
+                      (stats_area_type == HV_STATS_AREA_SELF) ? "self" : 
"parent",
+                      err);
+               return ERR_PTR(err);
+       }
+       return stats;
+}
+
+static int mshv_debugfs_partition_stats_create(u64 partition_id,
+                                           struct dentry **partition_stats_ptr,
+                                           struct dentry *parent)
+{
+       struct dentry *dentry;
+       struct hv_stats_page **pstats;
+       int err;
+
+       pstats = kcalloc(NUM_STATS_AREAS, sizeof(struct hv_stats_page *),
+                        GFP_KERNEL_ACCOUNT);
+       if (!pstats)
+               return -ENOMEM;
+
+       pstats[HV_STATS_AREA_SELF] = mshv_partition_stats_map(partition_id,
+                                                             
HV_STATS_AREA_SELF);
+       if (IS_ERR(pstats[HV_STATS_AREA_SELF])) {
+               err = PTR_ERR(pstats[HV_STATS_AREA_SELF]);
+               goto cleanup;
+       }
+
+       /*
+        * L1VH partition cannot access its partition stats in parent area.
+        */
+       if (is_l1vh_parent(partition_id)) {
+               pstats[HV_STATS_AREA_PARENT] = pstats[HV_STATS_AREA_SELF];
+       } else {
+               pstats[HV_STATS_AREA_PARENT] = 
mshv_partition_stats_map(partition_id,
+                                                                       
HV_STATS_AREA_PARENT);
+               if (IS_ERR(pstats[HV_STATS_AREA_PARENT])) {
+                       err = PTR_ERR(pstats[HV_STATS_AREA_PARENT]);
+                       goto unmap_self;
+               }
+               if (!pstats[HV_STATS_AREA_PARENT])
+                       pstats[HV_STATS_AREA_PARENT] = 
pstats[HV_STATS_AREA_SELF];
+       }
+
+       dentry = debugfs_create_file("stats", 0400, parent,
+                                    pstats, &partition_stats_fops);
+       if (IS_ERR(dentry)) {
+               err = PTR_ERR(dentry);
+               goto unmap_partition_stats;
+       }
+
+       *partition_stats_ptr = dentry;
+       return 0;
+
+unmap_partition_stats:
+       if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF])
+               mshv_partition_stats_unmap(partition_id, 
pstats[HV_STATS_AREA_PARENT],
+                                          HV_STATS_AREA_PARENT);
+unmap_self:
+       mshv_partition_stats_unmap(partition_id, pstats[HV_STATS_AREA_SELF],
+                                  HV_STATS_AREA_SELF);
+cleanup:
+       kfree(pstats);
+       return err;
+}
+
+static void partition_debugfs_remove(u64 partition_id, struct dentry *dentry)
+{
+       struct hv_stats_page **pstats = NULL;
+
+       pstats = dentry->d_inode->i_private;
+
+       debugfs_remove_recursive(dentry->d_parent);
+
+       if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF]) {
+               mshv_partition_stats_unmap(partition_id,
+                                          pstats[HV_STATS_AREA_PARENT],
+                                          HV_STATS_AREA_PARENT);
+       }
+
+       mshv_partition_stats_unmap(partition_id,
+                                  pstats[HV_STATS_AREA_SELF],
+                                  HV_STATS_AREA_SELF);
+
+       kfree(pstats);
+}
+
+static int partition_debugfs_create(u64 partition_id,
+                                   struct dentry **vp_dir_ptr,
+                                   struct dentry **partition_stats_ptr,
+                                   struct dentry *parent)
+{
+       char part_id_str[U64_BUF_SZ];
+       struct dentry *part_id_dir, *vp_dir;
+       int err;
+
+       if (is_l1vh_parent(partition_id))
+               sprintf(part_id_str, "self");
+       else
+               sprintf(part_id_str, "%llu", partition_id);
+
+       part_id_dir = debugfs_create_dir(part_id_str, parent);
+       if (IS_ERR(part_id_dir))
+               return PTR_ERR(part_id_dir);
+
+       vp_dir = debugfs_create_dir("vp", part_id_dir);
+       if (IS_ERR(vp_dir)) {
+               err = PTR_ERR(vp_dir);
+               goto remove_debugfs_partition_id;
+       }
+
+       err = mshv_debugfs_partition_stats_create(partition_id,
+                                                 partition_stats_ptr,
+                                                 part_id_dir);
+       if (err)
+               goto remove_debugfs_partition_id;
+
+       *vp_dir_ptr = vp_dir;
+
+       return 0;
+
+remove_debugfs_partition_id:
+       debugfs_remove_recursive(part_id_dir);
+       return err;
+}
+
+static void parent_vp_debugfs_remove(u32 vp_index,
+                                    struct dentry *vp_stats_ptr)
+{
+       struct hv_stats_page **pstats;
+
+       pstats = vp_stats_ptr->d_inode->i_private;
+       vp_debugfs_remove(vp_stats_ptr);
+       mshv_vp_stats_unmap(hv_current_partition_id, vp_index, pstats);
+       kfree(pstats);
+}
+
+static void mshv_debugfs_parent_partition_remove(void)
+{
+       int idx;
+
+       for_each_online_cpu(idx)
+               parent_vp_debugfs_remove(hv_vp_index[idx],
+                                        parent_vp_stats[idx]);
+
+       partition_debugfs_remove(hv_current_partition_id,
+                                parent_partition_stats);
+       kfree(parent_vp_stats);
+       parent_vp_stats = NULL;
+       parent_partition_stats = NULL;
+}
+
+static int __init parent_vp_debugfs_create(u32 vp_index,
+                                          struct dentry **vp_stats_ptr,
+                                          struct dentry *parent)
+{
+       struct hv_stats_page **pstats;
+       int err;
+
+       pstats = kcalloc(NUM_STATS_AREAS, sizeof(struct hv_stats_page *),
+                        GFP_KERNEL_ACCOUNT);
+       if (!pstats)
+               return -ENOMEM;
+
+       err = mshv_vp_stats_map(hv_current_partition_id, vp_index, pstats);
+       if (err)
+               goto cleanup;
+
+       err = vp_debugfs_create(hv_current_partition_id, vp_index, pstats,
+                               vp_stats_ptr, parent);
+       if (err)
+               goto unmap_vp_stats;
+
+       return 0;
+
+unmap_vp_stats:
+       mshv_vp_stats_unmap(hv_current_partition_id, vp_index, pstats);
+cleanup:
+       kfree(pstats);
+       return err;
+}
+
+static int __init mshv_debugfs_parent_partition_create(void)
+{
+       struct dentry *vp_dir;
+       int err, idx, i;
+
+       mshv_debugfs_partition = debugfs_create_dir("partition",
+                                                    mshv_debugfs);
+       if (IS_ERR(mshv_debugfs_partition))
+               return PTR_ERR(mshv_debugfs_partition);
+
+       err = partition_debugfs_create(hv_current_partition_id,
+                                      &vp_dir,
+                                      &parent_partition_stats,
+                                      mshv_debugfs_partition);
+       if (err)
+               goto remove_debugfs_partition;
+
+       parent_vp_stats = kcalloc(nr_cpu_ids, sizeof(*parent_vp_stats),
+                                 GFP_KERNEL);
+       if (!parent_vp_stats) {
+               err = -ENOMEM;
+               goto remove_debugfs_partition;
+       }
+
+       for_each_online_cpu(idx) {
+               err = parent_vp_debugfs_create(hv_vp_index[idx],
+                                              &parent_vp_stats[idx],
+                                              vp_dir);
+               if (err)
+                       goto remove_debugfs_partition_vp;
+       }
+
+       return 0;
+
+remove_debugfs_partition_vp:
+       for_each_online_cpu(i) {
+               if (i >= idx)
+                       break;
+               parent_vp_debugfs_remove(i, parent_vp_stats[i]);
+       }
+       partition_debugfs_remove(hv_current_partition_id,
+                                parent_partition_stats);
+
+       kfree(parent_vp_stats);
+       parent_vp_stats = NULL;
+       parent_partition_stats = NULL;
+
+remove_debugfs_partition:
+       debugfs_remove_recursive(mshv_debugfs_partition);
+       mshv_debugfs_partition = NULL;
+       return err;
+}
+
+static int hv_stats_show(struct seq_file *m, void *v)
+{
+       const struct hv_stats_page *stats = m->private;
+       int idx;
+
+       for (idx = 0; idx < ARRAY_SIZE(hv_hypervisor_counters); idx++) {
+               char *name = hv_hypervisor_counters[idx];
+
+               if (!name)
+                       continue;
+               seq_printf(m, "%-27s: %llu\n", name, stats->data[idx]);
+       }
+
+       return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(hv_stats);
+
+static void mshv_hv_stats_unmap(void)
+{
+       union hv_stats_object_identity identity = {
+               .hv.stats_area_type = HV_STATS_AREA_SELF,
+       };
+       int err;
+
+       err = hv_unmap_stats_page(HV_STATS_OBJECT_HYPERVISOR, NULL, &identity);
+       if (err)
+               pr_err("%s: failed to unmap hypervisor stats: %d\n",
+                      __func__, err);
+}
+
+static void * __init mshv_hv_stats_map(void)
+{
+       union hv_stats_object_identity identity = {
+               .hv.stats_area_type = HV_STATS_AREA_SELF,
+       };
+       struct hv_stats_page *stats;
+       int err;
+
+       err = hv_map_stats_page(HV_STATS_OBJECT_HYPERVISOR, &identity, &stats);
+       if (err) {
+               pr_err("%s: failed to map hypervisor stats: %d\n",
+                      __func__, err);
+               return ERR_PTR(err);
+       }
+       return stats;
+}
+
+static int __init mshv_debugfs_hv_stats_create(struct dentry *parent)
+{
+       struct dentry *dentry;
+       u64 *stats;
+       int err;
+
+       stats = mshv_hv_stats_map();
+       if (IS_ERR(stats))
+               return PTR_ERR(stats);
+
+       dentry = debugfs_create_file("stats", 0400, parent,
+                                    stats, &hv_stats_fops);
+       if (IS_ERR(dentry)) {
+               err = PTR_ERR(dentry);
+               pr_err("%s: failed to create hypervisor stats dentry: %d\n",
+                      __func__, err);
+               goto unmap_hv_stats;
+       }
+
+       mshv_lps_count = stats[HV_HYPERVISOR_COUNTER_LOGICAL_PROCESSORS];
+
+       return 0;
+
+unmap_hv_stats:
+       mshv_hv_stats_unmap();
+       return err;
+}
+
+int mshv_debugfs_vp_create(struct mshv_vp *vp)
+{
+       struct mshv_partition *p = vp->vp_partition;
+
+       if (!mshv_debugfs)
+               return 0;
+
+       return vp_debugfs_create(p->pt_id, vp->vp_index,
+                                vp->vp_stats_pages,
+                                &vp->vp_stats_dentry,
+                                p->pt_vp_dentry);
+}
+
+void mshv_debugfs_vp_remove(struct mshv_vp *vp)
+{
+       if (!mshv_debugfs)
+               return;
+
+       vp_debugfs_remove(vp->vp_stats_dentry);
+}
+
+int mshv_debugfs_partition_create(struct mshv_partition *partition)
+{
+       int err;
+
+       if (!mshv_debugfs)
+               return 0;
+
+       err = partition_debugfs_create(partition->pt_id,
+                                      &partition->pt_vp_dentry,
+                                      &partition->pt_stats_dentry,
+                                      mshv_debugfs_partition);
+       if (err)
+               return err;
+
+       return 0;
+}
+
+void mshv_debugfs_partition_remove(struct mshv_partition *partition)
+{
+       if (!mshv_debugfs)
+               return;
+
+       partition_debugfs_remove(partition->pt_id,
+                                partition->pt_stats_dentry);
+}
+
+int __init mshv_debugfs_init(void)
+{
+       int err;
+
+       mshv_debugfs = debugfs_create_dir("mshv", NULL);
+       if (IS_ERR(mshv_debugfs)) {
+               pr_err("%s: failed to create debugfs directory\n", __func__);
+               return PTR_ERR(mshv_debugfs);
+       }
+
+       if (hv_root_partition()) {
+               err = mshv_debugfs_hv_stats_create(mshv_debugfs);
+               if (err)
+                       goto remove_mshv_dir;
+
+               err = mshv_debugfs_lp_create(mshv_debugfs);
+               if (err)
+                       goto unmap_hv_stats;
+       }
+
+       err = mshv_debugfs_parent_partition_create();
+       if (err)
+               goto unmap_lp_stats;
+
+       return 0;
+
+unmap_lp_stats:
+       if (hv_root_partition()) {
+               mshv_debugfs_lp_remove();
+               mshv_debugfs_lp = NULL;
+       }
+unmap_hv_stats:
+       if (hv_root_partition())
+               mshv_hv_stats_unmap();
+remove_mshv_dir:
+       debugfs_remove_recursive(mshv_debugfs);
+       mshv_debugfs = NULL;
+       return err;
+}
+
+void mshv_debugfs_exit(void)
+{
+       mshv_debugfs_parent_partition_remove();
+
+       if (hv_root_partition()) {
+               mshv_debugfs_lp_remove();
+               mshv_debugfs_lp = NULL;
+               mshv_hv_stats_unmap();
+       }
+
+       debugfs_remove_recursive(mshv_debugfs);
+       mshv_debugfs = NULL;
+       mshv_debugfs_partition = NULL;
+}
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index e4912b0618fa..7332d9af8373 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -52,6 +52,9 @@ struct mshv_vp {
                unsigned int kicked_by_hv;
                wait_queue_head_t vp_suspend_queue;
        } run;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+       struct dentry *vp_stats_dentry;
+#endif
 };
 
 #define vp_fmt(fmt) "p%lluvp%u: " fmt
@@ -136,6 +139,10 @@ struct mshv_partition {
        u64 isolation_type;
        bool import_completed;
        bool pt_initialized;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+       struct dentry *pt_stats_dentry;
+       struct dentry *pt_vp_dentry;
+#endif
 };
 
 #define pt_fmt(fmt) "p%llu: " fmt
@@ -327,6 +334,33 @@ int hv_call_modify_spa_host_access(u64 partition_id, 
struct page **pages,
 int hv_call_get_partition_property_ex(u64 partition_id, u64 property_code, u64 
arg,
                                      void *property_value, size_t 
property_value_sz);
 
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+int __init mshv_debugfs_init(void);
+void mshv_debugfs_exit(void);
+
+int mshv_debugfs_partition_create(struct mshv_partition *partition);
+void mshv_debugfs_partition_remove(struct mshv_partition *partition);
+int mshv_debugfs_vp_create(struct mshv_vp *vp);
+void mshv_debugfs_vp_remove(struct mshv_vp *vp);
+#else
+static inline int __init mshv_debugfs_init(void)
+{
+       return 0;
+}
+static inline void mshv_debugfs_exit(void) { }
+
+static inline int mshv_debugfs_partition_create(struct mshv_partition 
*partition)
+{
+       return 0;
+}
+static inline void mshv_debugfs_partition_remove(struct mshv_partition 
*partition) { }
+static inline int mshv_debugfs_vp_create(struct mshv_vp *vp)
+{
+       return 0;
+}
+static inline void mshv_debugfs_vp_remove(struct mshv_vp *vp) { }
+#endif
+
 extern struct mshv_root mshv_root;
 extern enum hv_scheduler_type hv_scheduler_type;
 extern u8 * __percpu *hv_synic_eventring_tail;
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 414d9cee5252..3a43e41e16a1 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -1095,6 +1095,10 @@ mshv_partition_ioctl_create_vp(struct mshv_partition 
*partition,
 
        memcpy(vp->vp_stats_pages, stats_pages, sizeof(stats_pages));
 
+       ret = mshv_debugfs_vp_create(vp);
+       if (ret)
+               goto put_partition;
+
        /*
         * Keep anon_inode_getfd last: it installs fd in the file struct and
         * thus makes the state accessible in user space.
@@ -1102,7 +1106,7 @@ mshv_partition_ioctl_create_vp(struct mshv_partition 
*partition,
        ret = anon_inode_getfd("mshv_vp", &mshv_vp_fops, vp,
                               O_RDWR | O_CLOEXEC);
        if (ret < 0)
-               goto put_partition;
+               goto remove_debugfs_vp;
 
        /* already exclusive with the partition mutex for all ioctls */
        partition->pt_vp_count++;
@@ -1110,6 +1114,8 @@ mshv_partition_ioctl_create_vp(struct mshv_partition 
*partition,
 
        return ret;
 
+remove_debugfs_vp:
+       mshv_debugfs_vp_remove(vp);
 put_partition:
        mshv_partition_put(partition);
 free_vp:
@@ -1552,10 +1558,16 @@ mshv_partition_ioctl_initialize(struct mshv_partition 
*partition)
        if (ret)
                goto withdraw_mem;
 
+       ret = mshv_debugfs_partition_create(partition);
+       if (ret)
+               goto finalize_partition;
+
        partition->pt_initialized = true;
 
        return 0;
 
+finalize_partition:
+       hv_call_finalize_partition(partition->pt_id);
 withdraw_mem:
        hv_call_withdraw_memory(U64_MAX, NUMA_NO_NODE, partition->pt_id);
 
@@ -1735,6 +1747,7 @@ static void destroy_partition(struct mshv_partition 
*partition)
                        if (!vp)
                                continue;
 
+                       mshv_debugfs_vp_remove(vp);
                        mshv_vp_stats_unmap(partition->pt_id, vp->vp_index,
                                            vp->vp_stats_pages);
 
@@ -1768,6 +1781,8 @@ static void destroy_partition(struct mshv_partition 
*partition)
                        partition->pt_vp_array[i] = NULL;
                }
 
+               mshv_debugfs_partition_remove(partition);
+
                /* Deallocates and unmaps everything including vcpus, GPA 
mappings etc */
                hv_call_finalize_partition(partition->pt_id);
 
@@ -2313,10 +2328,14 @@ static int __init mshv_parent_partition_init(void)
 
        mshv_init_vmm_caps(dev);
 
-       ret = mshv_irqfd_wq_init();
+       ret = mshv_debugfs_init();
        if (ret)
                goto exit_partition;
 
+       ret = mshv_irqfd_wq_init();
+       if (ret)
+               goto exit_debugfs;
+
        spin_lock_init(&mshv_root.pt_ht_lock);
        hash_init(mshv_root.pt_htable);
 
@@ -2324,6 +2343,8 @@ static int __init mshv_parent_partition_init(void)
 
        return 0;
 
+exit_debugfs:
+       mshv_debugfs_exit();
 exit_partition:
        if (hv_root_partition())
                mshv_root_partition_exit();
@@ -2340,6 +2361,7 @@ static void __exit mshv_parent_partition_exit(void)
 {
        hv_setup_mshv_handler(NULL);
        mshv_port_table_fini();
+       mshv_debugfs_exit();
        misc_deregister(&mshv_dev);
        mshv_irqfd_wq_cleanup();
        if (hv_root_partition())
-- 
2.34.1


Reply via email to