729 lines · 16.6 KB · C
RawDownload
// 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 2static_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;

Lines 1–500 of 729.