From nobody Sat Feb 7 10:16:11 2026 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id E7811371065; Wed, 28 Jan 2026 18:11:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=13.77.154.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769623912; cv=none; b=KQAUnD19lOUg0qgPI67nhq+1hm514y1fR/HgUmnSU4Qni7vjPSGTNH9eLj61WSOlkvTw7KM1TB+ujUQu2LCQveP3N1qmEWTP/5iZ4ViI4jIUTjlqh1QstqYpvtdcUM6jSN1fq9Rxt7+B2SD5oYPc6Rtm+1aQ/AwO+pZF3KgBDYo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769623912; c=relaxed/simple; bh=z+4jYHc9rrfYSyc9V99ZLhGU6EsV2Bu8Lm/9X4RQpXg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=IN3gIcXU5BpgUoTY4aH8cRBxKHKzGaGVmxO+sEMbwwY/gtbfiMPb9vkON85z91QR4DeCCpaWKQL6x0ayXujQbfENuWcjsYpNqt01ijfC0Spy5A5asX4IU4A522z6rn/DWl8JJGuIjfigN2Uzh7CcxP+/eKjPr7Z2dXBjsiPGbxE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com; spf=pass smtp.mailfrom=linux.microsoft.com; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b=IBpuyK/y; arc=none smtp.client-ip=13.77.154.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b="IBpuyK/y" Received: by linux.microsoft.com (Postfix, from userid 1032) id 3D9D620B716E; Wed, 28 Jan 2026 10:11:49 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 3D9D620B716E DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1769623909; bh=XktaTb79CBAxA44Dt4ZFpyBuHnfZtfaw/0FSCkZZAqM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IBpuyK/ynLayLGGdvQqQHzvLdcvnJUjBOqIHiv4e4yztWwvrnHTvksFNEmTyDm1j7 r295eQH5Warn32noMWUJjRYxLw4U01WsexRJXvDGjxrM9qjtO4ypSe8JFXfvg21qpk +ZMhKdDseL6C7MJl9sB3xzXo1CoTlUEDq6WDe3c4= From: Nuno Das Neves To: linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org, mhklinux@outlook.com, skinsburskii@linux.microsoft.com Cc: kys@microsoft.com, haiyangz@microsoft.com, wei.liu@kernel.org, decui@microsoft.com, longli@microsoft.com, prapal@linux.microsoft.com, mrathor@linux.microsoft.com, paekkaladevi@linux.microsoft.com, Nuno Das Neves , Jinank Jain Subject: [PATCH v6 7/7] mshv: Add debugfs to view hypervisor statistics Date: Wed, 28 Jan 2026 10:11:46 -0800 Message-ID: <20260128181146.517708-8-nunodasneves@linux.microsoft.com> X-Mailer: git-send-email 2.43.7 In-Reply-To: <20260128181146.517708-1-nunodasneves@linux.microsoft.com> References: <20260128181146.517708-1-nunodasneves@linux.microsoft.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 Signed-off-by: Stanislav Kinsburskii Co-developed-by: Praveen K Paladugu Signed-off-by: Praveen K Paladugu Co-developed-by: Mukesh Rathor Signed-off-by: Mukesh Rathor Co-developed-by: Purna Pavan Chandra Aekkaladevi Signed-off-by: Purna Pavan Chandra Aekkaladevi Co-developed-by: Jinank Jain Signed-off-by: Jinank Jain Signed-off-by: Nuno Das Neves Reviewed-by: Stanislav Kinsburskii --- 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) +=3D hv_debugfs.o hv_utils-y :=3D hv_util.o hv_kvp.o hv_snapshot.o hv_utils_transport.o mshv_root-y :=3D 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) +=3D mshv_debugfs.o mshv_vtl-y :=3D mshv_vtl_main.o =20 # 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..ebf2549eb44d --- /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 +#include +#include +#include + +#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 =3D=3D 0 && HV_STATS_AREA_PARENT =3D=3D 1, + "SELF and PARENT areas must be usable as indices into an array of s= ize 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 =3D m->private; + int idx; + + for (idx =3D 0; idx < ARRAY_SIZE(hv_lp_counters); idx++) { + char *name =3D 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 =3D { + .lp.lp_index =3D lp_index, + .lp.stats_area_type =3D HV_STATS_AREA_SELF, + }; + int err; + + err =3D 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] =3D NULL; +} + +static struct hv_stats_page * __init mshv_lp_stats_map(u32 lp_index) +{ + union hv_stats_object_identity identity =3D { + .lp.lp_index =3D lp_index, + .lp.stats_area_type =3D HV_STATS_AREA_SELF, + }; + struct hv_stats_page *stats; + int err; + + err =3D 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] =3D 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 =3D mshv_lp_stats_map(lp_index); + if (IS_ERR(stats)) + return stats; + + dentry =3D 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 =3D debugfs_create_dir(lp_idx_str, parent); + if (IS_ERR(idx)) + return PTR_ERR(idx); + + stats =3D lp_debugfs_stats_create(lp_index, idx); + if (IS_ERR(stats)) { + err =3D 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 =3D 0; lp_index < mshv_lps_count; lp_index++) + mshv_lp_stats_unmap(lp_index); + + kfree(mshv_lps_stats); + mshv_lps_stats =3D NULL; +} + +static int __init mshv_debugfs_lp_create(struct dentry *parent) +{ + struct dentry *lp_dir; + int err, lp_index; + + mshv_lps_stats =3D kcalloc(mshv_lps_count, + sizeof(*mshv_lps_stats), + GFP_KERNEL_ACCOUNT); + + if (!mshv_lps_stats) + return -ENOMEM; + + lp_dir =3D debugfs_create_dir("lp", parent); + if (IS_ERR(lp_dir)) { + err =3D PTR_ERR(lp_dir); + goto free_lp_stats; + } + + for (lp_index =3D 0; lp_index < mshv_lps_count; lp_index++) { + err =3D lp_debugfs_create(lp_index, lp_dir); + if (err) + goto remove_debugfs_lps; + } + + mshv_debugfs_lp =3D lp_dir; + + return 0; + +remove_debugfs_lps: + for (lp_index -=3D 1; lp_index >=3D 0; lp_index--) + mshv_lp_stats_unmap(lp_index); + debugfs_remove_recursive(lp_dir); +free_lp_stats: + kfree(mshv_lps_stats); + mshv_lps_stats =3D NULL; + + return err; +} + +static int vp_stats_show(struct seq_file *m, void *v) +{ + const struct hv_stats_page **pstats =3D 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 =3D 0; idx < ARRAY_SIZE(hv_vp_counters); idx++) { + char *name =3D hv_vp_counters[idx]; + + if (!name) + continue; + + parent_val =3D pstats[HV_STATS_AREA_PARENT]->data[idx]; + self_val =3D 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 =3D debugfs_create_dir(vp_idx_str, parent); + if (IS_ERR(vp_idx_dir)) + return PTR_ERR(vp_idx_dir); + + d =3D debugfs_create_file("stats", 0400, vp_idx_dir, + pstats, &vp_stats_fops); + if (IS_ERR(d)) { + err =3D PTR_ERR(d); + goto remove_debugfs_vp_idx; + } + + *vp_stats_ptr =3D 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 =3D m->private; + u64 parent_val, self_val; + int idx; + + for (idx =3D 0; idx < ARRAY_SIZE(hv_partition_counters); idx++) { + char *name =3D hv_partition_counters[idx]; + + if (!name) + continue; + + parent_val =3D pstats[HV_STATS_AREA_PARENT]->data[idx]; + self_val =3D 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 =3D { + .partition.partition_id =3D partition_id, + .partition.stats_area_type =3D stats_area_type, + }; + int err; + + err =3D 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 =3D=3D 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 =3D { + .partition.partition_id =3D partition_id, + .partition.stats_area_type =3D stats_area_type, + }; + struct hv_stats_page *stats; + int err; + + err =3D 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 =3D=3D 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 =3D kcalloc(NUM_STATS_AREAS, sizeof(struct hv_stats_page *), + GFP_KERNEL_ACCOUNT); + if (!pstats) + return -ENOMEM; + + pstats[HV_STATS_AREA_SELF] =3D mshv_partition_stats_map(partition_id, + HV_STATS_AREA_SELF); + if (IS_ERR(pstats[HV_STATS_AREA_SELF])) { + err =3D 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] =3D pstats[HV_STATS_AREA_SELF]; + } else { + pstats[HV_STATS_AREA_PARENT] =3D mshv_partition_stats_map(partition_id, + HV_STATS_AREA_PARENT); + if (IS_ERR(pstats[HV_STATS_AREA_PARENT])) { + err =3D PTR_ERR(pstats[HV_STATS_AREA_PARENT]); + goto unmap_self; + } + if (!pstats[HV_STATS_AREA_PARENT]) + pstats[HV_STATS_AREA_PARENT] =3D pstats[HV_STATS_AREA_SELF]; + } + + dentry =3D debugfs_create_file("stats", 0400, parent, + pstats, &partition_stats_fops); + if (IS_ERR(dentry)) { + err =3D PTR_ERR(dentry); + goto unmap_partition_stats; + } + + *partition_stats_ptr =3D dentry; + return 0; + +unmap_partition_stats: + if (pstats[HV_STATS_AREA_PARENT] !=3D 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 *dent= ry) +{ + struct hv_stats_page **pstats =3D NULL; + + pstats =3D dentry->d_inode->i_private; + + debugfs_remove_recursive(dentry->d_parent); + + if (pstats[HV_STATS_AREA_PARENT] !=3D 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 =3D debugfs_create_dir(part_id_str, parent); + if (IS_ERR(part_id_dir)) + return PTR_ERR(part_id_dir); + + vp_dir =3D debugfs_create_dir("vp", part_id_dir); + if (IS_ERR(vp_dir)) { + err =3D PTR_ERR(vp_dir); + goto remove_debugfs_partition_id; + } + + err =3D mshv_debugfs_partition_stats_create(partition_id, + partition_stats_ptr, + part_id_dir); + if (err) + goto remove_debugfs_partition_id; + + *vp_dir_ptr =3D 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 =3D 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 =3D NULL; + parent_partition_stats =3D 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 =3D kcalloc(NUM_STATS_AREAS, sizeof(struct hv_stats_page *), + GFP_KERNEL_ACCOUNT); + if (!pstats) + return -ENOMEM; + + err =3D mshv_vp_stats_map(hv_current_partition_id, vp_index, pstats); + if (err) + goto cleanup; + + err =3D 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 =3D debugfs_create_dir("partition", + mshv_debugfs); + if (IS_ERR(mshv_debugfs_partition)) + return PTR_ERR(mshv_debugfs_partition); + + err =3D partition_debugfs_create(hv_current_partition_id, + &vp_dir, + &parent_partition_stats, + mshv_debugfs_partition); + if (err) + goto remove_debugfs_partition; + + parent_vp_stats =3D kcalloc(nr_cpu_ids, sizeof(*parent_vp_stats), + GFP_KERNEL); + if (!parent_vp_stats) { + err =3D -ENOMEM; + goto remove_debugfs_partition; + } + + for_each_online_cpu(idx) { + err =3D 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 >=3D 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 =3D NULL; + parent_partition_stats =3D NULL; + +remove_debugfs_partition: + debugfs_remove_recursive(mshv_debugfs_partition); + mshv_debugfs_partition =3D NULL; + return err; +} + +static int hv_stats_show(struct seq_file *m, void *v) +{ + const struct hv_stats_page *stats =3D m->private; + int idx; + + for (idx =3D 0; idx < ARRAY_SIZE(hv_hypervisor_counters); idx++) { + char *name =3D 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 =3D { + .hv.stats_area_type =3D HV_STATS_AREA_SELF, + }; + int err; + + err =3D 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 =3D { + .hv.stats_area_type =3D HV_STATS_AREA_SELF, + }; + struct hv_stats_page *stats; + int err; + + err =3D 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 =3D mshv_hv_stats_map(); + if (IS_ERR(stats)) + return PTR_ERR(stats); + + dentry =3D debugfs_create_file("stats", 0400, parent, + stats, &hv_stats_fops); + if (IS_ERR(dentry)) { + err =3D PTR_ERR(dentry); + pr_err("%s: failed to create hypervisor stats dentry: %d\n", + __func__, err); + goto unmap_hv_stats; + } + + mshv_lps_count =3D 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 =3D 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 =3D 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 =3D 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 =3D mshv_debugfs_hv_stats_create(mshv_debugfs); + if (err) + goto remove_mshv_dir; + + err =3D mshv_debugfs_lp_create(mshv_debugfs); + if (err) + goto unmap_hv_stats; + } + + err =3D 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 =3D NULL; + } +unmap_hv_stats: + if (hv_root_partition()) + mshv_hv_stats_unmap(); +remove_mshv_dir: + debugfs_remove_recursive(mshv_debugfs); + mshv_debugfs =3D NULL; + return err; +} + +void mshv_debugfs_exit(void) +{ + mshv_debugfs_parent_partition_remove(); + + if (hv_root_partition()) { + mshv_debugfs_lp_remove(); + mshv_debugfs_lp =3D NULL; + mshv_hv_stats_unmap(); + } + + debugfs_remove_recursive(mshv_debugfs); + mshv_debugfs =3D NULL; + mshv_debugfs_partition =3D 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 }; =20 #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 }; =20 #define pt_fmt(fmt) "p%llu: " fmt @@ -327,6 +334,33 @@ int hv_call_modify_spa_host_access(u64 partition_id, s= truct 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); =20 +#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 *par= tition) +{ + return 0; +} +static inline void mshv_debugfs_partition_remove(struct mshv_partition *pa= rtition) { } +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, =20 memcpy(vp->vp_stats_pages, stats_pages, sizeof(stats_pages)); =20 + ret =3D 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 =3D anon_inode_getfd("mshv_vp", &mshv_vp_fops, vp, O_RDWR | O_CLOEXEC); if (ret < 0) - goto put_partition; + goto remove_debugfs_vp; =20 /* 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, =20 return ret; =20 +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_partiti= on *partition) if (ret) goto withdraw_mem; =20 + ret =3D mshv_debugfs_partition_create(partition); + if (ret) + goto finalize_partition; + partition->pt_initialized =3D true; =20 return 0; =20 +finalize_partition: + hv_call_finalize_partition(partition->pt_id); withdraw_mem: hv_call_withdraw_memory(U64_MAX, NUMA_NO_NODE, partition->pt_id); =20 @@ -1735,6 +1747,7 @@ static void destroy_partition(struct mshv_partition *= partition) if (!vp) continue; =20 + mshv_debugfs_vp_remove(vp); mshv_vp_stats_unmap(partition->pt_id, vp->vp_index, vp->vp_stats_pages); =20 @@ -1768,6 +1781,8 @@ static void destroy_partition(struct mshv_partition *= partition) partition->pt_vp_array[i] =3D NULL; } =20 + mshv_debugfs_partition_remove(partition); + /* Deallocates and unmaps everything including vcpus, GPA mappings etc */ hv_call_finalize_partition(partition->pt_id); =20 @@ -2313,10 +2328,14 @@ static int __init mshv_parent_partition_init(void) =20 mshv_init_vmm_caps(dev); =20 - ret =3D mshv_irqfd_wq_init(); + ret =3D mshv_debugfs_init(); if (ret) goto exit_partition; =20 + ret =3D mshv_irqfd_wq_init(); + if (ret) + goto exit_debugfs; + spin_lock_init(&mshv_root.pt_ht_lock); hash_init(mshv_root.pt_htable); =20 @@ -2324,6 +2343,8 @@ static int __init mshv_parent_partition_init(void) =20 return 0; =20 +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()) --=20 2.34.1