| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724 |
- // 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 = kzalloc_objs(*mshv_lps_stats, mshv_lps_count,
- 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 = kzalloc_objs(struct hv_stats_page *, NUM_STATS_AREAS,
- 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 = kzalloc_objs(struct hv_stats_page *, NUM_STATS_AREAS,
- 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 = kzalloc_objs(*parent_vp_stats, nr_cpu_ids);
- 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;
- }
|