]>
Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
57852a85 MK |
2 | /* |
3 | * Copyright (C) 2006 Mike Kravetz IBM Corporation | |
4 | * | |
5 | * Hypervisor Call Instrumentation | |
57852a85 MK |
6 | */ |
7 | ||
8 | #include <linux/kernel.h> | |
9 | #include <linux/percpu.h> | |
10 | #include <linux/debugfs.h> | |
11 | #include <linux/seq_file.h> | |
12 | #include <linux/cpumask.h> | |
13 | #include <asm/hvcall.h> | |
14 | #include <asm/firmware.h> | |
15 | #include <asm/cputable.h> | |
c8cd093a | 16 | #include <asm/trace.h> |
8e83e905 | 17 | #include <asm/machdep.h> |
57852a85 | 18 | |
017614a5 TH |
19 | /* For hcall instrumentation. One structure per-hcall, per-CPU */ |
20 | struct hcall_stats { | |
21 | unsigned long num_calls; /* number of calls (on this CPU) */ | |
22 | unsigned long tb_total; /* total wall time (mftb) of calls. */ | |
23 | unsigned long purr_total; /* total cpu time (PURR) of calls. */ | |
24 | unsigned long tb_start; | |
25 | unsigned long purr_start; | |
26 | }; | |
27 | #define HCALL_STAT_ARRAY_SIZE ((MAX_HCALL_OPCODE >> 2) + 1) | |
28 | ||
57852a85 MK |
29 | DEFINE_PER_CPU(struct hcall_stats[HCALL_STAT_ARRAY_SIZE], hcall_stats); |
30 | ||
31 | /* | |
32 | * Routines for displaying the statistics in debugfs | |
33 | */ | |
34 | static void *hc_start(struct seq_file *m, loff_t *pos) | |
35 | { | |
dc40127c | 36 | if ((int)*pos < (HCALL_STAT_ARRAY_SIZE-1)) |
57852a85 MK |
37 | return (void *)(unsigned long)(*pos + 1); |
38 | ||
39 | return NULL; | |
40 | } | |
41 | ||
42 | static void *hc_next(struct seq_file *m, void *p, loff_t * pos) | |
43 | { | |
44 | ++*pos; | |
45 | ||
46 | return hc_start(m, pos); | |
47 | } | |
48 | ||
49 | static void hc_stop(struct seq_file *m, void *p) | |
50 | { | |
51 | } | |
52 | ||
53 | static int hc_show(struct seq_file *m, void *p) | |
54 | { | |
55 | unsigned long h_num = (unsigned long)p; | |
6d2ad1e3 | 56 | struct hcall_stats *hs = m->private; |
57852a85 MK |
57 | |
58 | if (hs[h_num].num_calls) { | |
dc40127c | 59 | if (cpu_has_feature(CPU_FTR_PURR)) |
57852a85 MK |
60 | seq_printf(m, "%lu %lu %lu %lu\n", h_num<<2, |
61 | hs[h_num].num_calls, | |
62 | hs[h_num].tb_total, | |
63 | hs[h_num].purr_total); | |
64 | else | |
65 | seq_printf(m, "%lu %lu %lu\n", h_num<<2, | |
66 | hs[h_num].num_calls, | |
67 | hs[h_num].tb_total); | |
68 | } | |
69 | ||
70 | return 0; | |
71 | } | |
72 | ||
88e9d34c | 73 | static const struct seq_operations hcall_inst_seq_ops = { |
57852a85 MK |
74 | .start = hc_start, |
75 | .next = hc_next, | |
76 | .stop = hc_stop, | |
77 | .show = hc_show | |
78 | }; | |
79 | ||
80 | static int hcall_inst_seq_open(struct inode *inode, struct file *file) | |
81 | { | |
82 | int rc; | |
83 | struct seq_file *seq; | |
84 | ||
85 | rc = seq_open(file, &hcall_inst_seq_ops); | |
86 | seq = file->private_data; | |
496ad9aa | 87 | seq->private = file_inode(file)->i_private; |
57852a85 MK |
88 | |
89 | return rc; | |
90 | } | |
91 | ||
5dfe4c96 | 92 | static const struct file_operations hcall_inst_seq_fops = { |
57852a85 MK |
93 | .open = hcall_inst_seq_open, |
94 | .read = seq_read, | |
95 | .llseek = seq_lseek, | |
96 | .release = seq_release, | |
97 | }; | |
98 | ||
99 | #define HCALL_ROOT_DIR "hcall_inst" | |
100 | #define CPU_NAME_BUF_SIZE 32 | |
101 | ||
c8cd093a | 102 | |
969ea5c5 | 103 | static void probe_hcall_entry(void *ignored, unsigned long opcode, unsigned long *args) |
c8cd093a AB |
104 | { |
105 | struct hcall_stats *h; | |
106 | ||
107 | if (opcode > MAX_HCALL_OPCODE) | |
108 | return; | |
109 | ||
69111bac | 110 | h = this_cpu_ptr(&hcall_stats[opcode / 4]); |
c8cd093a AB |
111 | h->tb_start = mftb(); |
112 | h->purr_start = mfspr(SPRN_PURR); | |
113 | } | |
114 | ||
8f2133cc | 115 | static void probe_hcall_exit(void *ignored, unsigned long opcode, long retval, |
6f26353c | 116 | unsigned long *retbuf) |
c8cd093a AB |
117 | { |
118 | struct hcall_stats *h; | |
119 | ||
120 | if (opcode > MAX_HCALL_OPCODE) | |
121 | return; | |
122 | ||
69111bac | 123 | h = this_cpu_ptr(&hcall_stats[opcode / 4]); |
c8cd093a | 124 | h->num_calls++; |
25ef231d WS |
125 | h->tb_total += mftb() - h->tb_start; |
126 | h->purr_total += mfspr(SPRN_PURR) - h->purr_start; | |
c8cd093a AB |
127 | } |
128 | ||
57852a85 MK |
129 | static int __init hcall_inst_init(void) |
130 | { | |
131 | struct dentry *hcall_root; | |
132 | struct dentry *hcall_file; | |
133 | char cpu_name_buf[CPU_NAME_BUF_SIZE]; | |
134 | int cpu; | |
135 | ||
136 | if (!firmware_has_feature(FW_FEATURE_LPAR)) | |
137 | return 0; | |
138 | ||
969ea5c5 | 139 | if (register_trace_hcall_entry(probe_hcall_entry, NULL)) |
c8cd093a AB |
140 | return -EINVAL; |
141 | ||
969ea5c5 SR |
142 | if (register_trace_hcall_exit(probe_hcall_exit, NULL)) { |
143 | unregister_trace_hcall_entry(probe_hcall_entry, NULL); | |
c8cd093a AB |
144 | return -EINVAL; |
145 | } | |
146 | ||
57852a85 MK |
147 | hcall_root = debugfs_create_dir(HCALL_ROOT_DIR, NULL); |
148 | if (!hcall_root) | |
149 | return -ENOMEM; | |
150 | ||
151 | for_each_possible_cpu(cpu) { | |
152 | snprintf(cpu_name_buf, CPU_NAME_BUF_SIZE, "cpu%d", cpu); | |
57ad583f | 153 | hcall_file = debugfs_create_file(cpu_name_buf, 0444, |
57852a85 MK |
154 | hcall_root, |
155 | per_cpu(hcall_stats, cpu), | |
156 | &hcall_inst_seq_fops); | |
157 | if (!hcall_file) | |
158 | return -ENOMEM; | |
159 | } | |
160 | ||
161 | return 0; | |
162 | } | |
8e83e905 | 163 | machine_device_initcall(pseries, hcall_inst_init); |