mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 04:04:43 +01:00
mshv: Add debugfs to view hypervisor statistics
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 <skinsburskii@linux.microsoft.com>
Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
Co-developed-by: Praveen K Paladugu <prapal@linux.microsoft.com>
Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
Co-developed-by: Mukesh Rathor <mrathor@linux.microsoft.com>
Signed-off-by: Mukesh Rathor <mrathor@linux.microsoft.com>
Co-developed-by: Purna Pavan Chandra Aekkaladevi <paekkaladevi@linux.microsoft.com>
Signed-off-by: Purna Pavan Chandra Aekkaladevi <paekkaladevi@linux.microsoft.com>
Co-developed-by: Jinank Jain <jinankjain@microsoft.com>
Signed-off-by: Jinank Jain <jinankjain@microsoft.com>
Signed-off-by: Nuno Das Neves <nunodasneves@linux.microsoft.com>
Reviewed-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
Acked-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
Reviewed-by: Michael Kelley <mhklinux@outlook.com>
Signed-off-by: Wei Liu <wei.liu@kernel.org>
This commit is contained in:
parent
c23271b636
commit
ff225ba9ad
4 changed files with 785 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
726
drivers/hv/mshv_debugfs.c
Normal file
726
drivers/hv/mshv_debugfs.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1096,6 +1096,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.
|
||||
|
|
@ -1103,7 +1107,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++;
|
||||
|
|
@ -1111,6 +1115,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:
|
||||
|
|
@ -1553,10 +1559,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);
|
||||
|
||||
|
|
@ -1736,6 +1748,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);
|
||||
|
||||
|
|
@ -1769,6 +1782,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);
|
||||
|
||||
|
|
@ -2314,10 +2329,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);
|
||||
|
||||
|
|
@ -2325,6 +2344,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();
|
||||
|
|
@ -2341,6 +2362,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())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue