| 1 | |
| 2 | |
| 3 | * Copyright (c) 2026, Microsoft Corporation.
|
| 4 | *
|
| 5 | * The /sys/kernel/debug/mshv directory contents.
|
| 6 | * Contains various statistics data, provided by the hypervisor.
|
| 7 | *
|
| 8 | * Authors: Microsoft Linux virtualization team
|
| 9 |
|
| 10 |
|
| 11 | #include <linux/debugfs.h>
|
| 12 | #include <linux/stringify.h>
|
| 13 | #include <asm/mshyperv.h>
|
| 14 | #include <linux/slab.h>
|
| 15 |
|
| 16 | #include "mshv.h"
|
| 17 | #include "mshv_root.h"
|
| 18 |
|
| 19 |
|
| 20 | #define MSHV_DEBUGFS_C
|
| 21 | #include "mshv_debugfs_counters.c"
|
| 22 |
|
| 23 | #define U32_BUF_SZ 11
|
| 24 | #define U64_BUF_SZ 21
|
| 25 |
|
| 26 | #define NUM_STATS_AREAS 2
|
| 27 | static_assert(HV_STATS_AREA_SELF == 0 && HV_STATS_AREA_PARENT == 1,
|
| 28 | "SELF and PARENT areas must be usable as indices into an array of size NUM_STATS_AREAS");
|
| 29 |
|
| 30 | #define HV_HYPERVISOR_COUNTER_LOGICAL_PROCESSORS 1
|
| 31 |
|
| 32 | static struct dentry *mshv_debugfs;
|
| 33 | static struct dentry *mshv_debugfs_partition;
|
| 34 | static struct dentry *mshv_debugfs_lp;
|
| 35 | static struct dentry **parent_vp_stats;
|
| 36 | static struct dentry *parent_partition_stats;
|
| 37 |
|
| 38 | static u64 mshv_lps_count;
|
| 39 | static struct hv_stats_page **mshv_lps_stats;
|
| 40 |
|
| 41 | static int lp_stats_show(struct seq_file *m, void *v)
|
| 42 | {
|
| 43 | const struct hv_stats_page *stats = m->private;
|
| 44 | int idx;
|
| 45 |
|
| 46 | for (idx = 0; idx < ARRAY_SIZE(hv_lp_counters); idx++) {
|
| 47 | char *name = hv_lp_counters[idx];
|
| 48 |
|
| 49 | if (!name)
|
| 50 | continue;
|
| 51 | seq_printf(m, "%-32s: %llu\n", name, stats->data[idx]);
|
| 52 | }
|
| 53 |
|
| 54 | return 0;
|
| 55 | }
|
| 56 | DEFINE_SHOW_ATTRIBUTE(lp_stats);
|
| 57 |
|
| 58 | static void mshv_lp_stats_unmap(u32 lp_index)
|
| 59 | {
|
| 60 | union hv_stats_object_identity identity = {
|
| 61 | .lp.lp_index = lp_index,
|
| 62 | .lp.stats_area_type = HV_STATS_AREA_SELF,
|
| 63 | };
|
| 64 | int err;
|
| 65 |
|
| 66 | err = hv_unmap_stats_page(HV_STATS_OBJECT_LOGICAL_PROCESSOR,
|
| 67 | mshv_lps_stats[lp_index], &identity);
|
| 68 | if (err)
|
| 69 | pr_err("%s: failed to unmap logical processor %u stats, err: %d\n",
|
| 70 | __func__, lp_index, err);
|
| 71 |
|
| 72 | mshv_lps_stats[lp_index] = NULL;
|
| 73 | }
|
| 74 |
|
| 75 | static struct hv_stats_page * __init mshv_lp_stats_map(u32 lp_index)
|
| 76 | {
|
| 77 | union hv_stats_object_identity identity = {
|
| 78 | .lp.lp_index = lp_index,
|
| 79 | .lp.stats_area_type = HV_STATS_AREA_SELF,
|
| 80 | };
|
| 81 | struct hv_stats_page *stats;
|
| 82 | int err;
|
| 83 |
|
| 84 | err = hv_map_stats_page(HV_STATS_OBJECT_LOGICAL_PROCESSOR, &identity,
|
| 85 | &stats);
|
| 86 | if (err) {
|
| 87 | pr_err("%s: failed to map logical processor %u stats, err: %d\n",
|
| 88 | __func__, lp_index, err);
|
| 89 | return ERR_PTR(err);
|
| 90 | }
|
| 91 | mshv_lps_stats[lp_index] = stats;
|
| 92 |
|
| 93 | return stats;
|
| 94 | }
|
| 95 |
|
| 96 | static struct hv_stats_page * __init lp_debugfs_stats_create(u32 lp_index,
|
| 97 | struct dentry *parent)
|
| 98 | {
|
| 99 | struct dentry *dentry;
|
| 100 | struct hv_stats_page *stats;
|
| 101 |
|
| 102 | stats = mshv_lp_stats_map(lp_index);
|
| 103 | if (IS_ERR(stats))
|
| 104 | return stats;
|
| 105 |
|
| 106 | dentry = debugfs_create_file("stats", 0400, parent,
|
| 107 | stats, &lp_stats_fops);
|
| 108 | if (IS_ERR(dentry)) {
|
| 109 | mshv_lp_stats_unmap(lp_index);
|
| 110 | return ERR_CAST(dentry);
|
| 111 | }
|
| 112 | return stats;
|
| 113 | }
|
| 114 |
|
| 115 | static int __init lp_debugfs_create(u32 lp_index, struct dentry *parent)
|
| 116 | {
|
| 117 | struct dentry *idx;
|
| 118 | char lp_idx_str[U32_BUF_SZ];
|
| 119 | struct hv_stats_page *stats;
|
| 120 | int err;
|
| 121 |
|
| 122 | sprintf(lp_idx_str, "%u", lp_index);
|
| 123 |
|
| 124 | idx = debugfs_create_dir(lp_idx_str, parent);
|
| 125 | if (IS_ERR(idx))
|
| 126 | return PTR_ERR(idx);
|
| 127 |
|
| 128 | stats = lp_debugfs_stats_create(lp_index, idx);
|
| 129 | if (IS_ERR(stats)) {
|
| 130 | err = PTR_ERR(stats);
|
| 131 | goto remove_debugfs_lp_idx;
|
| 132 | }
|
| 133 |
|
| 134 | return 0;
|
| 135 |
|
| 136 | remove_debugfs_lp_idx:
|
| 137 | debugfs_remove_recursive(idx);
|
| 138 | return err;
|
| 139 | }
|
| 140 |
|
| 141 | static void mshv_debugfs_lp_remove(void)
|
| 142 | {
|
| 143 | int lp_index;
|
| 144 |
|
| 145 | debugfs_remove_recursive(mshv_debugfs_lp);
|
| 146 |
|
| 147 | for (lp_index = 0; lp_index < mshv_lps_count; lp_index++)
|
| 148 | mshv_lp_stats_unmap(lp_index);
|
| 149 |
|
| 150 | kfree(mshv_lps_stats);
|
| 151 | mshv_lps_stats = NULL;
|
| 152 | }
|
| 153 |
|
| 154 | static int __init mshv_debugfs_lp_create(struct dentry *parent)
|
| 155 | {
|
| 156 | struct dentry *lp_dir;
|
| 157 | int err, lp_index;
|
| 158 |
|
| 159 | mshv_lps_stats = kzalloc_objs(*mshv_lps_stats, mshv_lps_count,
|
| 160 | GFP_KERNEL_ACCOUNT);
|
| 161 |
|
| 162 | if (!mshv_lps_stats)
|
| 163 | return -ENOMEM;
|
| 164 |
|
| 165 | lp_dir = debugfs_create_dir("lp", parent);
|
| 166 | if (IS_ERR(lp_dir)) {
|
| 167 | err = PTR_ERR(lp_dir);
|
| 168 | goto free_lp_stats;
|
| 169 | }
|
| 170 |
|
| 171 | for (lp_index = 0; lp_index < mshv_lps_count; lp_index++) {
|
| 172 | err = lp_debugfs_create(lp_index, lp_dir);
|
| 173 | if (err)
|
| 174 | goto remove_debugfs_lps;
|
| 175 | }
|
| 176 |
|
| 177 | mshv_debugfs_lp = lp_dir;
|
| 178 |
|
| 179 | return 0;
|
| 180 |
|
| 181 | remove_debugfs_lps:
|
| 182 | for (lp_index -= 1; lp_index >= 0; lp_index--)
|
| 183 | mshv_lp_stats_unmap(lp_index);
|
| 184 | debugfs_remove_recursive(lp_dir);
|
| 185 | free_lp_stats:
|
| 186 | kfree(mshv_lps_stats);
|
| 187 | mshv_lps_stats = NULL;
|
| 188 |
|
| 189 | return err;
|
| 190 | }
|
| 191 |
|
| 192 | static int vp_stats_show(struct seq_file *m, void *v)
|
| 193 | {
|
| 194 | const struct hv_stats_page **pstats = m->private;
|
| 195 | u64 parent_val, self_val;
|
| 196 | int idx;
|
| 197 |
|
| 198 | |
| 199 | * For VP and partition stats, there may be two stats areas mapped,
|
| 200 | * SELF and PARENT. These refer to the privilege level of the data in
|
| 201 | * each page. Some fields may be 0 in SELF and nonzero in PARENT, or
|
| 202 | * vice versa.
|
| 203 | *
|
| 204 | * Hence, prioritize printing from the PARENT page (more privileged
|
| 205 | * data), but use the value from the SELF page if the PARENT value is
|
| 206 | * 0.
|
| 207 |
|
| 208 |
|
| 209 | for (idx = 0; idx < ARRAY_SIZE(hv_vp_counters); idx++) {
|
| 210 | char *name = hv_vp_counters[idx];
|
| 211 |
|
| 212 | if (!name)
|
| 213 | continue;
|
| 214 |
|
| 215 | parent_val = pstats[HV_STATS_AREA_PARENT]->data[idx];
|
| 216 | self_val = pstats[HV_STATS_AREA_SELF]->data[idx];
|
| 217 | seq_printf(m, "%-43s: %llu\n", name,
|
| 218 | parent_val ? parent_val : self_val);
|
| 219 | }
|
| 220 |
|
| 221 | return 0;
|
| 222 | }
|
| 223 | DEFINE_SHOW_ATTRIBUTE(vp_stats);
|
| 224 |
|
| 225 | static void vp_debugfs_remove(struct dentry *vp_stats)
|
| 226 | {
|
| 227 | debugfs_remove_recursive(vp_stats->d_parent);
|
| 228 | }
|
| 229 |
|
| 230 | static int vp_debugfs_create(u64 partition_id, u32 vp_index,
|
| 231 | struct hv_stats_page **pstats,
|
| 232 | struct dentry **vp_stats_ptr,
|
| 233 | struct dentry *parent)
|
| 234 | {
|
| 235 | struct dentry *vp_idx_dir, *d;
|
| 236 | char vp_idx_str[U32_BUF_SZ];
|
| 237 | int err;
|
| 238 |
|
| 239 | sprintf(vp_idx_str, "%u", vp_index);
|
| 240 |
|
| 241 | vp_idx_dir = debugfs_create_dir(vp_idx_str, parent);
|
| 242 | if (IS_ERR(vp_idx_dir))
|
| 243 | return PTR_ERR(vp_idx_dir);
|
| 244 |
|
| 245 | d = debugfs_create_file("stats", 0400, vp_idx_dir,
|
| 246 | pstats, &vp_stats_fops);
|
| 247 | if (IS_ERR(d)) {
|
| 248 | err = PTR_ERR(d);
|
| 249 | goto remove_debugfs_vp_idx;
|
| 250 | }
|
| 251 |
|
| 252 | *vp_stats_ptr = d;
|
| 253 |
|
| 254 | return 0;
|
| 255 |
|
| 256 | remove_debugfs_vp_idx:
|
| 257 | debugfs_remove_recursive(vp_idx_dir);
|
| 258 | return err;
|
| 259 | }
|
| 260 |
|
| 261 | static int partition_stats_show(struct seq_file *m, void *v)
|
| 262 | {
|
| 263 | const struct hv_stats_page **pstats = m->private;
|
| 264 | u64 parent_val, self_val;
|
| 265 | int idx;
|
| 266 |
|
| 267 | for (idx = 0; idx < ARRAY_SIZE(hv_partition_counters); idx++) {
|
| 268 | char *name = hv_partition_counters[idx];
|
| 269 |
|
| 270 | if (!name)
|
| 271 | continue;
|
| 272 |
|
| 273 | parent_val = pstats[HV_STATS_AREA_PARENT]->data[idx];
|
| 274 | self_val = pstats[HV_STATS_AREA_SELF]->data[idx];
|
| 275 | seq_printf(m, "%-37s: %llu\n", name,
|
| 276 | parent_val ? parent_val : self_val);
|
| 277 | }
|
| 278 |
|
| 279 | return 0;
|
| 280 | }
|
| 281 | DEFINE_SHOW_ATTRIBUTE(partition_stats);
|
| 282 |
|
| 283 | static void mshv_partition_stats_unmap(u64 partition_id,
|
| 284 | struct hv_stats_page *stats_page,
|
| 285 | enum hv_stats_area_type stats_area_type)
|
| 286 | {
|
| 287 | union hv_stats_object_identity identity = {
|
| 288 | .partition.partition_id = partition_id,
|
| 289 | .partition.stats_area_type = stats_area_type,
|
| 290 | };
|
| 291 | int err;
|
| 292 |
|
| 293 | err = hv_unmap_stats_page(HV_STATS_OBJECT_PARTITION, stats_page,
|
| 294 | &identity);
|
| 295 | if (err)
|
| 296 | pr_err("%s: failed to unmap partition %lld %s stats, err: %d\n",
|
| 297 | __func__, partition_id,
|
| 298 | (stats_area_type == HV_STATS_AREA_SELF) ? "self" : "parent",
|
| 299 | err);
|
| 300 | }
|
| 301 |
|
| 302 | static struct hv_stats_page *mshv_partition_stats_map(u64 partition_id,
|
| 303 | enum hv_stats_area_type stats_area_type)
|
| 304 | {
|
| 305 | union hv_stats_object_identity identity = {
|
| 306 | .partition.partition_id = partition_id,
|
| 307 | .partition.stats_area_type = stats_area_type,
|
| 308 | };
|
| 309 | struct hv_stats_page *stats;
|
| 310 | int err;
|
| 311 |
|
| 312 | err = hv_map_stats_page(HV_STATS_OBJECT_PARTITION, &identity, &stats);
|
| 313 | if (err) {
|
| 314 | pr_err("%s: failed to map partition %lld %s stats, err: %d\n",
|
| 315 | __func__, partition_id,
|
| 316 | (stats_area_type == HV_STATS_AREA_SELF) ? "self" : "parent",
|
| 317 | err);
|
| 318 | return ERR_PTR(err);
|
| 319 | }
|
| 320 | return stats;
|
| 321 | }
|
| 322 |
|
| 323 | static int mshv_debugfs_partition_stats_create(u64 partition_id,
|
| 324 | struct dentry **partition_stats_ptr,
|
| 325 | struct dentry *parent)
|
| 326 | {
|
| 327 | struct dentry *dentry;
|
| 328 | struct hv_stats_page **pstats;
|
| 329 | int err;
|
| 330 |
|
| 331 | pstats = kzalloc_objs(struct hv_stats_page *, NUM_STATS_AREAS,
|
| 332 | GFP_KERNEL_ACCOUNT);
|
| 333 | if (!pstats)
|
| 334 | return -ENOMEM;
|
| 335 |
|
| 336 | pstats[HV_STATS_AREA_SELF] = mshv_partition_stats_map(partition_id,
|
| 337 | HV_STATS_AREA_SELF);
|
| 338 | if (IS_ERR(pstats[HV_STATS_AREA_SELF])) {
|
| 339 | err = PTR_ERR(pstats[HV_STATS_AREA_SELF]);
|
| 340 | goto cleanup;
|
| 341 | }
|
| 342 |
|
| 343 | |
| 344 | * L1VH partition cannot access its partition stats in parent area.
|
| 345 |
|
| 346 | if (is_l1vh_parent(partition_id)) {
|
| 347 | pstats[HV_STATS_AREA_PARENT] = pstats[HV_STATS_AREA_SELF];
|
| 348 | } else {
|
| 349 | pstats[HV_STATS_AREA_PARENT] = mshv_partition_stats_map(partition_id,
|
| 350 | HV_STATS_AREA_PARENT);
|
| 351 | if (IS_ERR(pstats[HV_STATS_AREA_PARENT])) {
|
| 352 | err = PTR_ERR(pstats[HV_STATS_AREA_PARENT]);
|
| 353 | goto unmap_self;
|
| 354 | }
|
| 355 | if (!pstats[HV_STATS_AREA_PARENT])
|
| 356 | pstats[HV_STATS_AREA_PARENT] = pstats[HV_STATS_AREA_SELF];
|
| 357 | }
|
| 358 |
|
| 359 | dentry = debugfs_create_file("stats", 0400, parent,
|
| 360 | pstats, &partition_stats_fops);
|
| 361 | if (IS_ERR(dentry)) {
|
| 362 | err = PTR_ERR(dentry);
|
| 363 | goto unmap_partition_stats;
|
| 364 | }
|
| 365 |
|
| 366 | *partition_stats_ptr = dentry;
|
| 367 | return 0;
|
| 368 |
|
| 369 | unmap_partition_stats:
|
| 370 | if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF])
|
| 371 | mshv_partition_stats_unmap(partition_id, pstats[HV_STATS_AREA_PARENT],
|
| 372 | HV_STATS_AREA_PARENT);
|
| 373 | unmap_self:
|
| 374 | mshv_partition_stats_unmap(partition_id, pstats[HV_STATS_AREA_SELF],
|
| 375 | HV_STATS_AREA_SELF);
|
| 376 | cleanup:
|
| 377 | kfree(pstats);
|
| 378 | return err;
|
| 379 | }
|
| 380 |
|
| 381 | static void partition_debugfs_remove(u64 partition_id, struct dentry *dentry)
|
| 382 | {
|
| 383 | struct hv_stats_page **pstats = NULL;
|
| 384 |
|
| 385 | pstats = dentry->d_inode->i_private;
|
| 386 |
|
| 387 | debugfs_remove_recursive(dentry->d_parent);
|
| 388 |
|
| 389 | if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF]) {
|
| 390 | mshv_partition_stats_unmap(partition_id,
|
| 391 | pstats[HV_STATS_AREA_PARENT],
|
| 392 | HV_STATS_AREA_PARENT);
|
| 393 | }
|
| 394 |
|
| 395 | mshv_partition_stats_unmap(partition_id,
|
| 396 | pstats[HV_STATS_AREA_SELF],
|
| 397 | HV_STATS_AREA_SELF);
|
| 398 |
|
| 399 | kfree(pstats);
|
| 400 | }
|
| 401 |
|
| 402 | static int partition_debugfs_create(u64 partition_id,
|
| 403 | struct dentry **vp_dir_ptr,
|
| 404 | struct dentry **partition_stats_ptr,
|
| 405 | struct dentry *parent)
|
| 406 | {
|
| 407 | char part_id_str[U64_BUF_SZ];
|
| 408 | struct dentry *part_id_dir, *vp_dir;
|
| 409 | int err;
|
| 410 |
|
| 411 | if (is_l1vh_parent(partition_id))
|
| 412 | sprintf(part_id_str, "self");
|
| 413 | else
|
| 414 | sprintf(part_id_str, "%llu", partition_id);
|
| 415 |
|
| 416 | part_id_dir = debugfs_create_dir(part_id_str, parent);
|
| 417 | if (IS_ERR(part_id_dir))
|
| 418 | return PTR_ERR(part_id_dir);
|
| 419 |
|
| 420 | vp_dir = debugfs_create_dir("vp", part_id_dir);
|
| 421 | if (IS_ERR(vp_dir)) {
|
| 422 | err = PTR_ERR(vp_dir);
|
| 423 | goto remove_debugfs_partition_id;
|
| 424 | }
|
| 425 |
|
| 426 | err = mshv_debugfs_partition_stats_create(partition_id,
|
| 427 | partition_stats_ptr,
|
| 428 | part_id_dir);
|
| 429 | if (err)
|
| 430 | goto remove_debugfs_partition_id;
|
| 431 |
|
| 432 | *vp_dir_ptr = vp_dir;
|
| 433 |
|
| 434 | return 0;
|
| 435 |
|
| 436 | remove_debugfs_partition_id:
|
| 437 | debugfs_remove_recursive(part_id_dir);
|
| 438 | return err;
|
| 439 | }
|
| 440 |
|
| 441 | static void parent_vp_debugfs_remove(u32 vp_index,
|
| 442 | struct dentry *vp_stats_ptr)
|
| 443 | {
|
| 444 | struct hv_stats_page **pstats;
|
| 445 |
|
| 446 | pstats = vp_stats_ptr->d_inode->i_private;
|
| 447 | vp_debugfs_remove(vp_stats_ptr);
|
| 448 | mshv_vp_stats_unmap(hv_current_partition_id, vp_index, pstats);
|
| 449 | kfree(pstats);
|
| 450 | }
|
| 451 |
|
| 452 | static void mshv_debugfs_parent_partition_remove(void)
|
| 453 | {
|
| 454 | int idx;
|
| 455 |
|
| 456 | for_each_online_cpu(idx)
|
| 457 | parent_vp_debugfs_remove(hv_vp_index[idx],
|
| 458 | parent_vp_stats[idx]);
|
| 459 |
|
| 460 | partition_debugfs_remove(hv_current_partition_id,
|
| 461 | parent_partition_stats);
|
| 462 | kfree(parent_vp_stats);
|
| 463 | parent_vp_stats = NULL;
|
| 464 | parent_partition_stats = NULL;
|
| 465 | }
|
| 466 |
|
| 467 | static int __init parent_vp_debugfs_create(u32 vp_index,
|
| 468 | struct dentry **vp_stats_ptr,
|
| 469 | struct dentry *parent)
|
| 470 | {
|
| 471 | struct hv_stats_page **pstats;
|
| 472 | int err;
|
| 473 |
|
| 474 | pstats = kzalloc_objs(struct hv_stats_page *, NUM_STATS_AREAS,
|
| 475 | GFP_KERNEL_ACCOUNT);
|
| 476 | if (!pstats)
|
| 477 | return -ENOMEM;
|
| 478 |
|
| 479 | err = mshv_vp_stats_map(hv_current_partition_id, vp_index, pstats);
|
| 480 | if (err)
|
| 481 | goto cleanup;
|
| 482 |
|
| 483 | err = vp_debugfs_create(hv_current_partition_id, vp_index, pstats,
|
| 484 | vp_stats_ptr, parent);
|
| 485 | if (err)
|
| 486 | goto unmap_vp_stats;
|
| 487 |
|
| 488 | return 0;
|
| 489 |
|
| 490 | unmap_vp_stats:
|
| 491 | mshv_vp_stats_unmap(hv_current_partition_id, vp_index, pstats);
|
| 492 | cleanup:
|
| 493 | kfree(pstats);
|
| 494 | return err;
|
| 495 | }
|
| 496 |
|
| 497 | static int __init mshv_debugfs_parent_partition_create(void)
|
| 498 | {
|
| 499 | struct dentry *vp_dir;
|
| 500 | int err, idx, i;
|
| 501 |
|
| 502 | mshv_debugfs_partition = debugfs_create_dir("partition",
|
| 503 | mshv_debugfs);
|
| 504 | if (IS_ERR(mshv_debugfs_partition))
|
| 505 | return PTR_ERR(mshv_debugfs_partition);
|
| 506 |
|
| 507 | err = partition_debugfs_create(hv_current_partition_id,
|
| 508 | &vp_dir,
|
| 509 | &parent_partition_stats,
|
| 510 | mshv_debugfs_partition);
|
| 511 | if (err)
|
| 512 | goto remove_debugfs_partition;
|
| 513 |
|
| 514 | parent_vp_stats = kzalloc_objs(*parent_vp_stats, nr_cpu_ids);
|
| 515 | if (!parent_vp_stats) {
|
| 516 | err = -ENOMEM;
|
| 517 | goto remove_debugfs_partition;
|
| 518 | }
|
| 519 |
|
| 520 | for_each_online_cpu(idx) {
|
| 521 | err = parent_vp_debugfs_create(hv_vp_index[idx],
|
| 522 | &parent_vp_stats[idx],
|
| 523 | vp_dir);
|
| 524 | if (err)
|
| 525 | goto remove_debugfs_partition_vp;
|
| 526 | }
|
| 527 |
|
| 528 | return 0;
|
| 529 |
|
| 530 | remove_debugfs_partition_vp:
|
| 531 | for_each_online_cpu(i) {
|
| 532 | if (i >= idx)
|
| 533 | break;
|
| 534 | parent_vp_debugfs_remove(i, parent_vp_stats[i]);
|
| 535 | }
|
| 536 | partition_debugfs_remove(hv_current_partition_id,
|
| 537 | parent_partition_stats);
|
| 538 |
|
| 539 | kfree(parent_vp_stats);
|
| 540 | parent_vp_stats = NULL;
|
| 541 | parent_partition_stats = NULL;
|
| 542 |
|
| 543 | remove_debugfs_partition:
|
| 544 | debugfs_remove_recursive(mshv_debugfs_partition);
|
| 545 | mshv_debugfs_partition = NULL;
|
| 546 | return err;
|
| 547 | }
|
| 548 |
|
| 549 | static int hv_stats_show(struct seq_file *m, void *v)
|
| 550 | {
|
| 551 | const struct hv_stats_page *stats = m->private;
|
| 552 | int idx;
|
| 553 |
|
| 554 | for (idx = 0; idx < ARRAY_SIZE(hv_hypervisor_counters); idx++) {
|
| 555 | char *name = hv_hypervisor_counters[idx];
|
| 556 |
|
| 557 | if (!name)
|
| 558 | continue;
|
| 559 | seq_printf(m, "%-27s: %llu\n", name, stats->data[idx]);
|
| 560 | }
|
| 561 |
|
| 562 | return 0;
|
| 563 | }
|
| 564 | DEFINE_SHOW_ATTRIBUTE(hv_stats);
|
| 565 |
|
| 566 | static void mshv_hv_stats_unmap(void)
|
| 567 | {
|
| 568 | union hv_stats_object_identity identity = {
|
| 569 | .hv.stats_area_type = HV_STATS_AREA_SELF,
|
| 570 | };
|
| 571 | int err;
|
| 572 |
|
| 573 | err = hv_unmap_stats_page(HV_STATS_OBJECT_HYPERVISOR, NULL, &identity);
|
| 574 | if (err)
|
| 575 | pr_err("%s: failed to unmap hypervisor stats: %d\n",
|
| 576 | __func__, err);
|
| 577 | }
|
| 578 |
|
| 579 | static void * __init mshv_hv_stats_map(void)
|
| 580 | {
|
| 581 | union hv_stats_object_identity identity = {
|
| 582 | .hv.stats_area_type = HV_STATS_AREA_SELF,
|
| 583 | };
|
| 584 | struct hv_stats_page *stats;
|
| 585 | int err;
|
| 586 |
|
| 587 | err = hv_map_stats_page(HV_STATS_OBJECT_HYPERVISOR, &identity, &stats);
|
| 588 | if (err) {
|
| 589 | pr_err("%s: failed to map hypervisor stats: %d\n",
|
| 590 | __func__, err);
|
| 591 | return ERR_PTR(err);
|
| 592 | }
|
| 593 | return stats;
|
| 594 | }
|
| 595 |
|
| 596 | static int __init mshv_debugfs_hv_stats_create(struct dentry *parent)
|
| 597 | {
|
| 598 | struct dentry *dentry;
|
| 599 | u64 *stats;
|
| 600 | int err;
|
| 601 |
|
| 602 | stats = mshv_hv_stats_map();
|
| 603 | if (IS_ERR(stats))
|
| 604 | return PTR_ERR(stats);
|
| 605 |
|
| 606 | dentry = debugfs_create_file("stats", 0400, parent,
|
| 607 | stats, &hv_stats_fops);
|
| 608 | if (IS_ERR(dentry)) {
|
| 609 | err = PTR_ERR(dentry);
|
| 610 | pr_err("%s: failed to create hypervisor stats dentry: %d\n",
|
| 611 | __func__, err);
|
| 612 | goto unmap_hv_stats;
|
| 613 | }
|
| 614 |
|
| 615 | mshv_lps_count = stats[HV_HYPERVISOR_COUNTER_LOGICAL_PROCESSORS];
|
| 616 |
|
| 617 | return 0;
|
| 618 |
|
| 619 | unmap_hv_stats:
|
| 620 | mshv_hv_stats_unmap();
|
| 621 | return err;
|
| 622 | }
|
| 623 |
|
| 624 | int mshv_debugfs_vp_create(struct mshv_vp *vp)
|
| 625 | {
|
| 626 | struct mshv_partition *p = vp->vp_partition;
|
| 627 |
|
| 628 | if (!mshv_debugfs)
|
| 629 | return 0;
|
| 630 |
|
| 631 | return vp_debugfs_create(p->pt_id, vp->vp_index,
|
| 632 | vp->vp_stats_pages,
|
| 633 | &vp->vp_stats_dentry,
|
| 634 | p->pt_vp_dentry);
|
| 635 | }
|
| 636 |
|
| 637 | void mshv_debugfs_vp_remove(struct mshv_vp *vp)
|
| 638 | {
|
| 639 | if (!mshv_debugfs)
|
| 640 | return;
|
| 641 |
|
| 642 | vp_debugfs_remove(vp->vp_stats_dentry);
|
| 643 | }
|
| 644 |
|
| 645 | int mshv_debugfs_partition_create(struct mshv_partition *partition)
|
| 646 | {
|
| 647 | int err;
|
| 648 |
|
| 649 | if (!mshv_debugfs)
|
| 650 | return 0;
|
| 651 |
|
| 652 | err = partition_debugfs_create(partition->pt_id,
|
| 653 | &partition->pt_vp_dentry,
|
| 654 | &partition->pt_stats_dentry,
|
| 655 | mshv_debugfs_partition);
|
| 656 | if (err)
|
| 657 | return err;
|
| 658 |
|
| 659 | return 0;
|
| 660 | }
|
| 661 |
|
| 662 | void mshv_debugfs_partition_remove(struct mshv_partition *partition)
|
| 663 | {
|
| 664 | if (!mshv_debugfs)
|
| 665 | return;
|
| 666 |
|
| 667 | partition_debugfs_remove(partition->pt_id,
|
| 668 | partition->pt_stats_dentry);
|
| 669 | }
|
| 670 |
|
| 671 | int __init mshv_debugfs_init(void)
|
| 672 | {
|
| 673 | int err;
|
| 674 |
|
| 675 | mshv_debugfs = debugfs_create_dir("mshv", NULL);
|
| 676 | if (IS_ERR(mshv_debugfs)) {
|
| 677 | err = PTR_ERR(mshv_debugfs);
|
| 678 | mshv_debugfs = NULL;
|
| 679 | pr_err("%s: failed to create debugfs directory\n", __func__);
|
| 680 | return err;
|
| 681 | }
|
| 682 |
|
| 683 | if (hv_root_partition()) {
|
| 684 | err = mshv_debugfs_hv_stats_create(mshv_debugfs);
|
| 685 | if (err)
|
| 686 | goto remove_mshv_dir;
|
| 687 |
|
| 688 | err = mshv_debugfs_lp_create(mshv_debugfs);
|
| 689 | if (err)
|
| 690 | goto unmap_hv_stats;
|
| 691 | }
|
| 692 |
|
| 693 | err = mshv_debugfs_parent_partition_create();
|
| 694 | if (err)
|
| 695 | goto unmap_lp_stats;
|
| 696 |
|
| 697 | return 0;
|
| 698 |
|
| 699 | unmap_lp_stats:
|
| 700 | if (hv_root_partition()) {
|
| 701 | mshv_debugfs_lp_remove();
|
| 702 | mshv_debugfs_lp = NULL;
|
| 703 | }
|
| 704 | unmap_hv_stats:
|
| 705 | if (hv_root_partition())
|
| 706 | mshv_hv_stats_unmap();
|
| 707 | remove_mshv_dir:
|
| 708 | debugfs_remove_recursive(mshv_debugfs);
|
| 709 | mshv_debugfs = NULL;
|
| 710 | return err;
|
| 711 | }
|
| 712 |
|
| 713 | void mshv_debugfs_exit(void)
|
| 714 | {
|
| 715 | if (!mshv_debugfs)
|
| 716 | return;
|
| 717 |
|
| 718 | mshv_debugfs_parent_partition_remove();
|
| 719 |
|
| 720 | if (hv_root_partition()) {
|
| 721 | mshv_debugfs_lp_remove();
|
| 722 | mshv_debugfs_lp = NULL;
|
| 723 | mshv_hv_stats_unmap();
|
| 724 | }
|
| 725 |
|
| 726 | debugfs_remove_recursive(mshv_debugfs);
|
| 727 | mshv_debugfs = NULL;
|
| 728 | mshv_debugfs_partition = NULL;
|
| 729 | }
|