diff options
author | Jeremy Kerr <jk@ozlabs.org> | 2009-03-11 13:55:52 -0400 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2009-03-23 22:47:28 -0400 |
commit | fc59a3fc8eed3a2c811e64ec65015d7eb1459ace (patch) | |
tree | b2ba8d071b92e85ba74ca59c402e0c63d4be1b95 /arch | |
parent | 098e8957afd86323db04cbb8deea3bd158f9cc71 (diff) |
powerpc: Add virtual processor dispatch trace log
pseries SPLPAR machines are able to retrieve a log of dispatch and
preempt events from the hypervisor. With this information, we can
see when and why each dispatch & preempt is occuring.
This change adds a set of debugfs files allowing userspace to read this
dispatch log.
Based on initial patches from Nishanth Aravamudan <nacc@us.ibm.com>.
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/powerpc/platforms/pseries/Kconfig | 10 | ||||
-rw-r--r-- | arch/powerpc/platforms/pseries/Makefile | 1 | ||||
-rw-r--r-- | arch/powerpc/platforms/pseries/dtl.c | 274 | ||||
-rw-r--r-- | arch/powerpc/platforms/pseries/plpar_wrappers.h | 10 |
4 files changed, 295 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/pseries/Kconfig b/arch/powerpc/platforms/pseries/Kconfig index c0e6ec240f46..f0e6f28427bd 100644 --- a/arch/powerpc/platforms/pseries/Kconfig +++ b/arch/powerpc/platforms/pseries/Kconfig | |||
@@ -68,3 +68,13 @@ config CMM | |||
68 | makes sense for a system running in an LPAR where the unused pages | 68 | makes sense for a system running in an LPAR where the unused pages |
69 | will be reused for other LPARs. The interface allows firmware to | 69 | will be reused for other LPARs. The interface allows firmware to |
70 | balance memory across many LPARs. | 70 | balance memory across many LPARs. |
71 | |||
72 | config DTL | ||
73 | bool "Dispatch Trace Log" | ||
74 | depends on PPC_SPLPAR && DEBUG_FS | ||
75 | help | ||
76 | SPLPAR machines can log hypervisor preempt & dispatch events to a | ||
77 | kernel buffer. Saying Y here will enable logging these events, | ||
78 | which are accessible through a debugfs file. | ||
79 | |||
80 | Say N if you are unsure. | ||
diff --git a/arch/powerpc/platforms/pseries/Makefile b/arch/powerpc/platforms/pseries/Makefile index 0ce691df595f..790c0b872d4f 100644 --- a/arch/powerpc/platforms/pseries/Makefile +++ b/arch/powerpc/platforms/pseries/Makefile | |||
@@ -25,3 +25,4 @@ obj-$(CONFIG_HVCS) += hvcserver.o | |||
25 | obj-$(CONFIG_HCALL_STATS) += hvCall_inst.o | 25 | obj-$(CONFIG_HCALL_STATS) += hvCall_inst.o |
26 | obj-$(CONFIG_PHYP_DUMP) += phyp_dump.o | 26 | obj-$(CONFIG_PHYP_DUMP) += phyp_dump.o |
27 | obj-$(CONFIG_CMM) += cmm.o | 27 | obj-$(CONFIG_CMM) += cmm.o |
28 | obj-$(CONFIG_DTL) += dtl.o | ||
diff --git a/arch/powerpc/platforms/pseries/dtl.c b/arch/powerpc/platforms/pseries/dtl.c new file mode 100644 index 000000000000..dc9b0f81e60f --- /dev/null +++ b/arch/powerpc/platforms/pseries/dtl.c | |||
@@ -0,0 +1,274 @@ | |||
1 | /* | ||
2 | * Virtual Processor Dispatch Trace Log | ||
3 | * | ||
4 | * (C) Copyright IBM Corporation 2009 | ||
5 | * | ||
6 | * Author: Jeremy Kerr <jk@ozlabs.org> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2, or (at your option) | ||
11 | * any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
21 | */ | ||
22 | |||
23 | #include <linux/init.h> | ||
24 | #include <linux/debugfs.h> | ||
25 | #include <asm/smp.h> | ||
26 | #include <asm/system.h> | ||
27 | #include <asm/uaccess.h> | ||
28 | |||
29 | #include "plpar_wrappers.h" | ||
30 | |||
31 | /* | ||
32 | * Layout of entries in the hypervisor's DTL buffer. Although we don't | ||
33 | * actually access the internals of an entry (we only need to know the size), | ||
34 | * we might as well define it here for reference. | ||
35 | */ | ||
36 | struct dtl_entry { | ||
37 | u8 dispatch_reason; | ||
38 | u8 preempt_reason; | ||
39 | u16 processor_id; | ||
40 | u32 enqueue_to_dispatch_time; | ||
41 | u32 ready_to_enqueue_time; | ||
42 | u32 waiting_to_ready_time; | ||
43 | u64 timebase; | ||
44 | u64 fault_addr; | ||
45 | u64 srr0; | ||
46 | u64 srr1; | ||
47 | }; | ||
48 | |||
49 | struct dtl { | ||
50 | struct dtl_entry *buf; | ||
51 | struct dentry *file; | ||
52 | int cpu; | ||
53 | int buf_entries; | ||
54 | u64 last_idx; | ||
55 | }; | ||
56 | static DEFINE_PER_CPU(struct dtl, dtl); | ||
57 | |||
58 | /* | ||
59 | * Dispatch trace log event mask: | ||
60 | * 0x7: 0x1: voluntary virtual processor waits | ||
61 | * 0x2: time-slice preempts | ||
62 | * 0x4: virtual partition memory page faults | ||
63 | */ | ||
64 | static u8 dtl_event_mask = 0x7; | ||
65 | |||
66 | |||
67 | /* | ||
68 | * Size of per-cpu log buffers. Default is just under 16 pages worth. | ||
69 | */ | ||
70 | static int dtl_buf_entries = (16 * 85); | ||
71 | |||
72 | |||
73 | static int dtl_enable(struct dtl *dtl) | ||
74 | { | ||
75 | unsigned long addr; | ||
76 | int ret, hwcpu; | ||
77 | |||
78 | /* only allow one reader */ | ||
79 | if (dtl->buf) | ||
80 | return -EBUSY; | ||
81 | |||
82 | /* we need to store the original allocation size for use during read */ | ||
83 | dtl->buf_entries = dtl_buf_entries; | ||
84 | |||
85 | dtl->buf = kmalloc_node(dtl->buf_entries * sizeof(struct dtl_entry), | ||
86 | GFP_KERNEL, cpu_to_node(dtl->cpu)); | ||
87 | if (!dtl->buf) { | ||
88 | printk(KERN_WARNING "%s: buffer alloc failed for cpu %d\n", | ||
89 | __func__, dtl->cpu); | ||
90 | return -ENOMEM; | ||
91 | } | ||
92 | |||
93 | /* Register our dtl buffer with the hypervisor. The HV expects the | ||
94 | * buffer size to be passed in the second word of the buffer */ | ||
95 | ((u32 *)dtl->buf)[1] = dtl->buf_entries * sizeof(struct dtl_entry); | ||
96 | |||
97 | hwcpu = get_hard_smp_processor_id(dtl->cpu); | ||
98 | addr = __pa(dtl->buf); | ||
99 | ret = register_dtl(hwcpu, addr); | ||
100 | if (ret) { | ||
101 | printk(KERN_WARNING "%s: DTL registration for cpu %d (hw %d) " | ||
102 | "failed with %d\n", __func__, dtl->cpu, hwcpu, ret); | ||
103 | kfree(dtl->buf); | ||
104 | return -EIO; | ||
105 | } | ||
106 | |||
107 | /* set our initial buffer indices */ | ||
108 | dtl->last_idx = lppaca[dtl->cpu].dtl_idx = 0; | ||
109 | |||
110 | /* enable event logging */ | ||
111 | lppaca[dtl->cpu].dtl_enable_mask = dtl_event_mask; | ||
112 | |||
113 | return 0; | ||
114 | } | ||
115 | |||
116 | static void dtl_disable(struct dtl *dtl) | ||
117 | { | ||
118 | int hwcpu = get_hard_smp_processor_id(dtl->cpu); | ||
119 | |||
120 | lppaca[dtl->cpu].dtl_enable_mask = 0x0; | ||
121 | |||
122 | unregister_dtl(hwcpu, __pa(dtl->buf)); | ||
123 | |||
124 | kfree(dtl->buf); | ||
125 | dtl->buf = NULL; | ||
126 | dtl->buf_entries = 0; | ||
127 | } | ||
128 | |||
129 | /* file interface */ | ||
130 | |||
131 | static int dtl_file_open(struct inode *inode, struct file *filp) | ||
132 | { | ||
133 | struct dtl *dtl = inode->i_private; | ||
134 | int rc; | ||
135 | |||
136 | rc = dtl_enable(dtl); | ||
137 | if (rc) | ||
138 | return rc; | ||
139 | |||
140 | filp->private_data = dtl; | ||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | static int dtl_file_release(struct inode *inode, struct file *filp) | ||
145 | { | ||
146 | struct dtl *dtl = inode->i_private; | ||
147 | dtl_disable(dtl); | ||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | static ssize_t dtl_file_read(struct file *filp, char __user *buf, size_t len, | ||
152 | loff_t *pos) | ||
153 | { | ||
154 | int rc, cur_idx, last_idx, n_read, n_req, read_size; | ||
155 | struct dtl *dtl; | ||
156 | |||
157 | if ((len % sizeof(struct dtl_entry)) != 0) | ||
158 | return -EINVAL; | ||
159 | |||
160 | dtl = filp->private_data; | ||
161 | |||
162 | /* requested number of entries to read */ | ||
163 | n_req = len / sizeof(struct dtl_entry); | ||
164 | |||
165 | /* actual number of entries read */ | ||
166 | n_read = 0; | ||
167 | |||
168 | cur_idx = lppaca[dtl->cpu].dtl_idx; | ||
169 | last_idx = dtl->last_idx; | ||
170 | |||
171 | if (cur_idx - last_idx > dtl->buf_entries) { | ||
172 | pr_debug("%s: hv buffer overflow for cpu %d, samples lost\n", | ||
173 | __func__, dtl->cpu); | ||
174 | } | ||
175 | |||
176 | cur_idx %= dtl->buf_entries; | ||
177 | last_idx %= dtl->buf_entries; | ||
178 | |||
179 | /* read the tail of the buffer if we've wrapped */ | ||
180 | if (last_idx > cur_idx) { | ||
181 | read_size = min(n_req, dtl->buf_entries - last_idx); | ||
182 | |||
183 | rc = copy_to_user(buf, &dtl->buf[last_idx], | ||
184 | read_size * sizeof(struct dtl_entry)); | ||
185 | if (rc) | ||
186 | return -EFAULT; | ||
187 | |||
188 | last_idx = 0; | ||
189 | n_req -= read_size; | ||
190 | n_read += read_size; | ||
191 | buf += read_size * sizeof(struct dtl_entry); | ||
192 | } | ||
193 | |||
194 | /* .. and now the head */ | ||
195 | read_size = min(n_req, cur_idx - last_idx); | ||
196 | rc = copy_to_user(buf, &dtl->buf[last_idx], | ||
197 | read_size * sizeof(struct dtl_entry)); | ||
198 | if (rc) | ||
199 | return -EFAULT; | ||
200 | |||
201 | n_read += read_size; | ||
202 | dtl->last_idx += n_read; | ||
203 | |||
204 | return n_read * sizeof(struct dtl_entry); | ||
205 | } | ||
206 | |||
207 | static struct file_operations dtl_fops = { | ||
208 | .open = dtl_file_open, | ||
209 | .release = dtl_file_release, | ||
210 | .read = dtl_file_read, | ||
211 | .llseek = no_llseek, | ||
212 | }; | ||
213 | |||
214 | static struct dentry *dtl_dir; | ||
215 | |||
216 | static int dtl_setup_file(struct dtl *dtl) | ||
217 | { | ||
218 | char name[10]; | ||
219 | |||
220 | sprintf(name, "cpu-%d", dtl->cpu); | ||
221 | |||
222 | dtl->file = debugfs_create_file(name, 0400, dtl_dir, dtl, &dtl_fops); | ||
223 | if (!dtl->file) | ||
224 | return -ENOMEM; | ||
225 | |||
226 | return 0; | ||
227 | } | ||
228 | |||
229 | static int dtl_init(void) | ||
230 | { | ||
231 | struct dentry *event_mask_file, *buf_entries_file; | ||
232 | int rc, i; | ||
233 | |||
234 | if (!firmware_has_feature(FW_FEATURE_SPLPAR)) | ||
235 | return -ENODEV; | ||
236 | |||
237 | /* set up common debugfs structure */ | ||
238 | |||
239 | rc = -ENOMEM; | ||
240 | dtl_dir = debugfs_create_dir("dtl", powerpc_debugfs_root); | ||
241 | if (!dtl_dir) { | ||
242 | printk(KERN_WARNING "%s: can't create dtl root dir\n", | ||
243 | __func__); | ||
244 | goto err; | ||
245 | } | ||
246 | |||
247 | event_mask_file = debugfs_create_x8("dtl_event_mask", 0600, | ||
248 | dtl_dir, &dtl_event_mask); | ||
249 | buf_entries_file = debugfs_create_u32("dtl_buf_entries", 0600, | ||
250 | dtl_dir, &dtl_buf_entries); | ||
251 | |||
252 | if (!event_mask_file || !buf_entries_file) { | ||
253 | printk(KERN_WARNING "%s: can't create dtl files\n", __func__); | ||
254 | goto err_remove_dir; | ||
255 | } | ||
256 | |||
257 | /* set up the per-cpu log structures */ | ||
258 | for_each_possible_cpu(i) { | ||
259 | struct dtl *dtl = &per_cpu(dtl, i); | ||
260 | dtl->cpu = i; | ||
261 | |||
262 | rc = dtl_setup_file(dtl); | ||
263 | if (rc) | ||
264 | goto err_remove_dir; | ||
265 | } | ||
266 | |||
267 | return 0; | ||
268 | |||
269 | err_remove_dir: | ||
270 | debugfs_remove_recursive(dtl_dir); | ||
271 | err: | ||
272 | return rc; | ||
273 | } | ||
274 | arch_initcall(dtl_init); | ||
diff --git a/arch/powerpc/platforms/pseries/plpar_wrappers.h b/arch/powerpc/platforms/pseries/plpar_wrappers.h index d967c1893ab5..a24a6b2333b2 100644 --- a/arch/powerpc/platforms/pseries/plpar_wrappers.h +++ b/arch/powerpc/platforms/pseries/plpar_wrappers.h | |||
@@ -43,6 +43,16 @@ static inline long register_slb_shadow(unsigned long cpu, unsigned long vpa) | |||
43 | return vpa_call(0x3, cpu, vpa); | 43 | return vpa_call(0x3, cpu, vpa); |
44 | } | 44 | } |
45 | 45 | ||
46 | static inline long unregister_dtl(unsigned long cpu, unsigned long vpa) | ||
47 | { | ||
48 | return vpa_call(0x6, cpu, vpa); | ||
49 | } | ||
50 | |||
51 | static inline long register_dtl(unsigned long cpu, unsigned long vpa) | ||
52 | { | ||
53 | return vpa_call(0x2, cpu, vpa); | ||
54 | } | ||
55 | |||
46 | static inline long plpar_page_set_loaned(unsigned long vpa) | 56 | static inline long plpar_page_set_loaned(unsigned long vpa) |
47 | { | 57 | { |
48 | unsigned long cmo_page_sz = cmo_get_page_size(); | 58 | unsigned long cmo_page_sz = cmo_get_page_size(); |