Commit | Line | Data |
---|---|---|
457c8996 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
1da177e4 LT |
2 | /* |
3 | * linux/kernel/profile.c | |
4 | * Simple profiling. Manages a direct-mapped profile hit count buffer, | |
5 | * with configurable resolution, support for restricting the cpus on | |
6 | * which profiling is done, and switching between cpu time and | |
7 | * schedule() calls via kernel command line parameters passed at boot. | |
8 | * | |
9 | * Scheduler profiling support, Arjan van de Ven and Ingo Molnar, | |
10 | * Red Hat, July 2004 | |
11 | * Consolidation of architecture support code for profiling, | |
6d49e352 | 12 | * Nadia Yvette Chambers, Oracle, July 2004 |
1da177e4 | 13 | * Amortized hit count accounting via per-cpu open-addressed hashtables |
6d49e352 NYC |
14 | * to resolve timer interrupt livelocks, Nadia Yvette Chambers, |
15 | * Oracle, 2004 | |
1da177e4 LT |
16 | */ |
17 | ||
9984de1a | 18 | #include <linux/export.h> |
1da177e4 | 19 | #include <linux/profile.h> |
57c8a661 | 20 | #include <linux/memblock.h> |
1da177e4 LT |
21 | #include <linux/notifier.h> |
22 | #include <linux/mm.h> | |
23 | #include <linux/cpumask.h> | |
24 | #include <linux/cpu.h> | |
1da177e4 | 25 | #include <linux/highmem.h> |
97d1f15b | 26 | #include <linux/mutex.h> |
22b8ce94 DH |
27 | #include <linux/slab.h> |
28 | #include <linux/vmalloc.h> | |
3905f9ad IM |
29 | #include <linux/sched/stat.h> |
30 | ||
1da177e4 | 31 | #include <asm/sections.h> |
7d12e780 | 32 | #include <asm/irq_regs.h> |
e8edc6e0 | 33 | #include <asm/ptrace.h> |
1da177e4 LT |
34 | |
35 | struct profile_hit { | |
36 | u32 pc, hits; | |
37 | }; | |
38 | #define PROFILE_GRPSHIFT 3 | |
39 | #define PROFILE_GRPSZ (1 << PROFILE_GRPSHIFT) | |
40 | #define NR_PROFILE_HIT (PAGE_SIZE/sizeof(struct profile_hit)) | |
41 | #define NR_PROFILE_GRP (NR_PROFILE_HIT/PROFILE_GRPSZ) | |
42 | ||
1da177e4 | 43 | static atomic_t *prof_buffer; |
2d186afd PS |
44 | static unsigned long prof_len; |
45 | static unsigned short int prof_shift; | |
07031e14 | 46 | |
ece8a684 | 47 | int prof_on __read_mostly; |
07031e14 IM |
48 | EXPORT_SYMBOL_GPL(prof_on); |
49 | ||
22b8ce94 | 50 | int profile_setup(char *str) |
1da177e4 | 51 | { |
f3da64d1 | 52 | static const char schedstr[] = "schedule"; |
f3da64d1 | 53 | static const char kvmstr[] = "kvm"; |
35783ccb | 54 | const char *select = NULL; |
1da177e4 LT |
55 | int par; |
56 | ||
b88f5538 | 57 | if (!strncmp(str, schedstr, strlen(schedstr))) { |
1da177e4 | 58 | prof_on = SCHED_PROFILING; |
35783ccb | 59 | select = schedstr; |
07031e14 IM |
60 | } else if (!strncmp(str, kvmstr, strlen(kvmstr))) { |
61 | prof_on = KVM_PROFILING; | |
35783ccb | 62 | select = kvmstr; |
dfaa9c94 | 63 | } else if (get_option(&str, &par)) { |
2d186afd | 64 | prof_shift = clamp(par, 0, BITS_PER_LONG - 1); |
1da177e4 | 65 | prof_on = CPU_PROFILING; |
2d186afd | 66 | pr_info("kernel profiling enabled (shift: %u)\n", |
1da177e4 LT |
67 | prof_shift); |
68 | } | |
35783ccb | 69 | |
70 | if (select) { | |
71 | if (str[strlen(select)] == ',') | |
72 | str += strlen(select) + 1; | |
73 | if (get_option(&str, &par)) | |
74 | prof_shift = clamp(par, 0, BITS_PER_LONG - 1); | |
75 | pr_info("kernel %s profiling enabled (shift: %u)\n", | |
76 | select, prof_shift); | |
77 | } | |
78 | ||
1da177e4 LT |
79 | return 1; |
80 | } | |
81 | __setup("profile=", profile_setup); | |
82 | ||
83 | ||
ce05fcc3 | 84 | int __ref profile_init(void) |
1da177e4 | 85 | { |
22b8ce94 | 86 | int buffer_bytes; |
1ad82fd5 | 87 | if (!prof_on) |
22b8ce94 | 88 | return 0; |
1ad82fd5 | 89 | |
1da177e4 LT |
90 | /* only text is profiled */ |
91 | prof_len = (_etext - _stext) >> prof_shift; | |
0fe6ee8f CZ |
92 | |
93 | if (!prof_len) { | |
94 | pr_warn("profiling shift: %u too large\n", prof_shift); | |
95 | prof_on = 0; | |
96 | return -EINVAL; | |
97 | } | |
98 | ||
22b8ce94 | 99 | buffer_bytes = prof_len*sizeof(atomic_t); |
22b8ce94 | 100 | |
b62f495d | 101 | prof_buffer = kzalloc(buffer_bytes, GFP_KERNEL|__GFP_NOWARN); |
22b8ce94 DH |
102 | if (prof_buffer) |
103 | return 0; | |
104 | ||
b62f495d MG |
105 | prof_buffer = alloc_pages_exact(buffer_bytes, |
106 | GFP_KERNEL|__GFP_ZERO|__GFP_NOWARN); | |
22b8ce94 DH |
107 | if (prof_buffer) |
108 | return 0; | |
109 | ||
559fa6e7 JJ |
110 | prof_buffer = vzalloc(buffer_bytes); |
111 | if (prof_buffer) | |
22b8ce94 DH |
112 | return 0; |
113 | ||
114 | return -ENOMEM; | |
1da177e4 LT |
115 | } |
116 | ||
6f7bd76f | 117 | static void do_profile_hits(int type, void *__pc, unsigned int nr_hits) |
1da177e4 LT |
118 | { |
119 | unsigned long pc; | |
1da177e4 | 120 | pc = ((unsigned long)__pc - (unsigned long)_stext) >> prof_shift; |
2accfdb7 LT |
121 | if (pc < prof_len) |
122 | atomic_add(nr_hits, &prof_buffer[pc]); | |
1da177e4 | 123 | } |
6f7bd76f RM |
124 | |
125 | void profile_hits(int type, void *__pc, unsigned int nr_hits) | |
126 | { | |
127 | if (prof_on != type || !prof_buffer) | |
128 | return; | |
129 | do_profile_hits(type, __pc, nr_hits); | |
130 | } | |
bbe1a59b AM |
131 | EXPORT_SYMBOL_GPL(profile_hits); |
132 | ||
7d12e780 | 133 | void profile_tick(int type) |
1da177e4 | 134 | { |
7d12e780 DH |
135 | struct pt_regs *regs = get_irq_regs(); |
136 | ||
7c51f7bb TH |
137 | /* This is the old kernel-only legacy profiling */ |
138 | if (!user_mode(regs)) | |
1da177e4 LT |
139 | profile_hit(type, (void *)profile_pc(regs)); |
140 | } | |
141 | ||
142 | #ifdef CONFIG_PROC_FS | |
143 | #include <linux/proc_fs.h> | |
583a22e7 | 144 | #include <linux/seq_file.h> |
7c0f6ba6 | 145 | #include <linux/uaccess.h> |
1da177e4 | 146 | |
1da177e4 LT |
147 | /* |
148 | * This function accesses profiling information. The returned data is | |
149 | * binary: the sampling step and the actual contents of the profile | |
150 | * buffer. Use of the program readprofile is recommended in order to | |
151 | * get meaningful info out of these data. | |
152 | */ | |
153 | static ssize_t | |
154 | read_profile(struct file *file, char __user *buf, size_t count, loff_t *ppos) | |
155 | { | |
156 | unsigned long p = *ppos; | |
157 | ssize_t read; | |
1ad82fd5 | 158 | char *pnt; |
2d186afd | 159 | unsigned long sample_step = 1UL << prof_shift; |
1da177e4 | 160 | |
1da177e4 LT |
161 | if (p >= (prof_len+1)*sizeof(unsigned int)) |
162 | return 0; | |
163 | if (count > (prof_len+1)*sizeof(unsigned int) - p) | |
164 | count = (prof_len+1)*sizeof(unsigned int) - p; | |
165 | read = 0; | |
166 | ||
167 | while (p < sizeof(unsigned int) && count > 0) { | |
1ad82fd5 | 168 | if (put_user(*((char *)(&sample_step)+p), buf)) |
064b022c | 169 | return -EFAULT; |
1da177e4 LT |
170 | buf++; p++; count--; read++; |
171 | } | |
172 | pnt = (char *)prof_buffer + p - sizeof(atomic_t); | |
1ad82fd5 | 173 | if (copy_to_user(buf, (void *)pnt, count)) |
1da177e4 LT |
174 | return -EFAULT; |
175 | read += count; | |
176 | *ppos += read; | |
177 | return read; | |
178 | } | |
179 | ||
787dbea1 BD |
180 | /* default is to not implement this call */ |
181 | int __weak setup_profiling_timer(unsigned mult) | |
182 | { | |
183 | return -EINVAL; | |
184 | } | |
185 | ||
1da177e4 LT |
186 | /* |
187 | * Writing to /proc/profile resets the counters | |
188 | * | |
189 | * Writing a 'profiling multiplier' value into it also re-sets the profiling | |
190 | * interrupt frequency, on architectures that support this. | |
191 | */ | |
192 | static ssize_t write_profile(struct file *file, const char __user *buf, | |
193 | size_t count, loff_t *ppos) | |
194 | { | |
195 | #ifdef CONFIG_SMP | |
1da177e4 LT |
196 | if (count == sizeof(int)) { |
197 | unsigned int multiplier; | |
198 | ||
199 | if (copy_from_user(&multiplier, buf, sizeof(int))) | |
200 | return -EFAULT; | |
201 | ||
202 | if (setup_profiling_timer(multiplier)) | |
203 | return -EINVAL; | |
204 | } | |
205 | #endif | |
1da177e4 LT |
206 | memset(prof_buffer, 0, prof_len * sizeof(atomic_t)); |
207 | return count; | |
208 | } | |
209 | ||
97a32539 AD |
210 | static const struct proc_ops profile_proc_ops = { |
211 | .proc_read = read_profile, | |
212 | .proc_write = write_profile, | |
213 | .proc_lseek = default_llseek, | |
1da177e4 LT |
214 | }; |
215 | ||
e722d8da | 216 | int __ref create_proc_profile(void) |
1da177e4 | 217 | { |
e722d8da | 218 | struct proc_dir_entry *entry; |
c270a817 | 219 | int err = 0; |
1da177e4 LT |
220 | |
221 | if (!prof_on) | |
222 | return 0; | |
c33fff0a | 223 | entry = proc_create("profile", S_IWUSR | S_IRUGO, |
97a32539 | 224 | NULL, &profile_proc_ops); |
7c51f7bb TH |
225 | if (entry) |
226 | proc_set_size(entry, (1 + prof_len) * sizeof(atomic_t)); | |
c270a817 | 227 | return err; |
1da177e4 | 228 | } |
c96d6660 | 229 | subsys_initcall(create_proc_profile); |
1da177e4 | 230 | #endif /* CONFIG_PROC_FS */ |