Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 | 2 | /* |
bb9b813b RB |
3 | * |
4 | * Copyright (C) 2001, 2002, 2003 Broadcom Corporation | |
5 | * Copyright (C) 2007 Ralf Baechle <ralf@linux-mips.org> | |
6 | * Copyright (C) 2007 MIPS Technologies, Inc. | |
7 | * written by Ralf Baechle <ralf@linux-mips.org> | |
1da177e4 LT |
8 | */ |
9 | ||
bb9b813b | 10 | #undef DEBUG |
1da177e4 | 11 | |
bb9b813b | 12 | #include <linux/device.h> |
1da177e4 LT |
13 | #include <linux/module.h> |
14 | #include <linux/kernel.h> | |
15 | #include <linux/types.h> | |
16 | #include <linux/init.h> | |
17 | #include <linux/interrupt.h> | |
0156915c | 18 | #include <linux/sched.h> |
1da177e4 LT |
19 | #include <linux/vmalloc.h> |
20 | #include <linux/fs.h> | |
21 | #include <linux/errno.h> | |
db89a48c | 22 | #include <linux/wait.h> |
1da177e4 LT |
23 | #include <asm/io.h> |
24 | #include <asm/sibyte/sb1250.h> | |
d619f38f MM |
25 | |
26 | #if defined(CONFIG_SIBYTE_BCM1x55) || defined(CONFIG_SIBYTE_BCM1x80) | |
27 | #include <asm/sibyte/bcm1480_regs.h> | |
28 | #include <asm/sibyte/bcm1480_scd.h> | |
29 | #include <asm/sibyte/bcm1480_int.h> | |
30 | #elif defined(CONFIG_SIBYTE_SB1250) || defined(CONFIG_SIBYTE_BCM112X) | |
1da177e4 LT |
31 | #include <asm/sibyte/sb1250_regs.h> |
32 | #include <asm/sibyte/sb1250_scd.h> | |
33 | #include <asm/sibyte/sb1250_int.h> | |
d619f38f | 34 | #else |
0b1974de | 35 | #error invalid SiByte UART configuration |
d619f38f MM |
36 | #endif |
37 | ||
38 | #if defined(CONFIG_SIBYTE_BCM1x55) || defined(CONFIG_SIBYTE_BCM1x80) | |
39 | #undef K_INT_TRACE_FREEZE | |
40 | #define K_INT_TRACE_FREEZE K_BCM1480_INT_TRACE_FREEZE | |
41 | #undef K_INT_PERF_CNT | |
42 | #define K_INT_PERF_CNT K_BCM1480_INT_PERF_CNT | |
43 | #endif | |
44 | ||
7c0f6ba6 | 45 | #include <linux/uaccess.h> |
1da177e4 | 46 | |
bb9b813b RB |
47 | #define SBPROF_TB_MAJOR 240 |
48 | ||
49 | typedef u64 tb_sample_t[6*256]; | |
50 | ||
51 | enum open_status { | |
52 | SB_CLOSED, | |
53 | SB_OPENING, | |
54 | SB_OPEN | |
55 | }; | |
56 | ||
57 | struct sbprof_tb { | |
58 | wait_queue_head_t tb_sync; | |
59 | wait_queue_head_t tb_read; | |
60 | struct mutex lock; | |
61 | enum open_status open; | |
62 | tb_sample_t *sbprof_tbbuf; | |
63 | int next_tb_sample; | |
64 | ||
65 | volatile int tb_enable; | |
66 | volatile int tb_armed; | |
67 | ||
68 | }; | |
1da177e4 LT |
69 | |
70 | static struct sbprof_tb sbp; | |
71 | ||
bb9b813b RB |
72 | #define MAX_SAMPLE_BYTES (24*1024*1024) |
73 | #define MAX_TBSAMPLE_BYTES (12*1024*1024) | |
74 | ||
75 | #define MAX_SAMPLES (MAX_SAMPLE_BYTES/sizeof(u_int32_t)) | |
76 | #define TB_SAMPLE_SIZE (sizeof(tb_sample_t)) | |
77 | #define MAX_TB_SAMPLES (MAX_TBSAMPLE_BYTES/TB_SAMPLE_SIZE) | |
78 | ||
79 | /* ioctls */ | |
80 | #define SBPROF_ZBSTART _IOW('s', 0, int) | |
81 | #define SBPROF_ZBSTOP _IOW('s', 1, int) | |
82 | #define SBPROF_ZBWAITFULL _IOW('s', 2, int) | |
83 | ||
84 | /* | |
85 | * Routines for using 40-bit SCD cycle counter | |
86 | * | |
87 | * Client responsible for either handling interrupts or making sure | |
88 | * the cycles counter never saturates, e.g., by doing | |
89 | * zclk_timer_init(0) at least every 2^40 - 1 ZCLKs. | |
90 | */ | |
91 | ||
92 | /* | |
93 | * Configures SCD counter 0 to count ZCLKs starting from val; | |
94 | * Configures SCD counters1,2,3 to count nothing. | |
95 | * Must not be called while gathering ZBbus profiles. | |
96 | */ | |
97 | ||
98 | #define zclk_timer_init(val) \ | |
99 | __asm__ __volatile__ (".set push;" \ | |
100 | ".set mips64;" \ | |
101 | "la $8, 0xb00204c0;" /* SCD perf_cnt_cfg */ \ | |
102 | "sd %0, 0x10($8);" /* write val to counter0 */ \ | |
103 | "sd %1, 0($8);" /* config counter0 for zclks*/ \ | |
104 | ".set pop" \ | |
105 | : /* no outputs */ \ | |
106 | /* enable, counter0 */ \ | |
107 | : /* inputs */ "r"(val), "r" ((1ULL << 33) | 1ULL) \ | |
108 | : /* modifies */ "$8" ) | |
109 | ||
110 | ||
111 | /* Reads SCD counter 0 and puts result in value | |
112 | unsigned long long val; */ | |
113 | #define zclk_get(val) \ | |
114 | __asm__ __volatile__ (".set push;" \ | |
115 | ".set mips64;" \ | |
116 | "la $8, 0xb00204c0;" /* SCD perf_cnt_cfg */ \ | |
117 | "ld %0, 0x10($8);" /* write val to counter0 */ \ | |
118 | ".set pop" \ | |
119 | : /* outputs */ "=r"(val) \ | |
120 | : /* inputs */ \ | |
121 | : /* modifies */ "$8" ) | |
122 | ||
d619f38f | 123 | #define DEVNAME "sb_tbprof" |
bb9b813b | 124 | |
1da177e4 LT |
125 | #define TB_FULL (sbp.next_tb_sample == MAX_TB_SAMPLES) |
126 | ||
bb9b813b | 127 | /* |
1da177e4 LT |
128 | * Support for ZBbus sampling using the trace buffer |
129 | * | |
130 | * We use the SCD performance counter interrupt, caused by a Zclk counter | |
131 | * overflow, to trigger the start of tracing. | |
132 | * | |
133 | * We set the trace buffer to sample everything and freeze on | |
134 | * overflow. | |
135 | * | |
136 | * We map the interrupt for trace_buffer_freeze to handle it on CPU 0. | |
d619f38f | 137 | * |
bb9b813b | 138 | */ |
1da177e4 | 139 | |
bb9b813b | 140 | static u64 tb_period; |
1da177e4 LT |
141 | |
142 | static void arm_tb(void) | |
143 | { | |
70342287 | 144 | u64 scdperfcnt; |
bb9b813b RB |
145 | u64 next = (1ULL << 40) - tb_period; |
146 | u64 tb_options = M_SCD_TRACE_CFG_FREEZE_FULL; | |
147 | ||
148 | /* | |
d619f38f MM |
149 | * Generate an SCD_PERFCNT interrupt in TB_PERIOD Zclks to |
150 | * trigger start of trace. XXX vary sampling period | |
bb9b813b | 151 | */ |
65bda1a9 MR |
152 | __raw_writeq(0, IOADDR(A_SCD_PERF_CNT_1)); |
153 | scdperfcnt = __raw_readq(IOADDR(A_SCD_PERF_CNT_CFG)); | |
bb9b813b RB |
154 | |
155 | /* | |
d619f38f MM |
156 | * Unfortunately, in Pass 2 we must clear all counters to knock down |
157 | * a previous interrupt request. This means that bus profiling | |
158 | * requires ALL of the SCD perf counters. | |
bb9b813b | 159 | */ |
d619f38f MM |
160 | #if defined(CONFIG_SIBYTE_BCM1x55) || defined(CONFIG_SIBYTE_BCM1x80) |
161 | __raw_writeq((scdperfcnt & ~M_SPC_CFG_SRC1) | | |
162 | /* keep counters 0,2,3,4,5,6,7 as is */ | |
163 | V_SPC_CFG_SRC1(1), /* counter 1 counts cycles */ | |
164 | IOADDR(A_BCM1480_SCD_PERF_CNT_CFG0)); | |
165 | __raw_writeq( | |
166 | M_SPC_CFG_ENABLE | /* enable counting */ | |
167 | M_SPC_CFG_CLEAR | /* clear all counters */ | |
168 | V_SPC_CFG_SRC1(1), /* counter 1 counts cycles */ | |
169 | IOADDR(A_BCM1480_SCD_PERF_CNT_CFG1)); | |
170 | #else | |
65bda1a9 | 171 | __raw_writeq((scdperfcnt & ~M_SPC_CFG_SRC1) | |
bb9b813b RB |
172 | /* keep counters 0,2,3 as is */ |
173 | M_SPC_CFG_ENABLE | /* enable counting */ | |
174 | M_SPC_CFG_CLEAR | /* clear all counters */ | |
175 | V_SPC_CFG_SRC1(1), /* counter 1 counts cycles */ | |
65bda1a9 | 176 | IOADDR(A_SCD_PERF_CNT_CFG)); |
d619f38f | 177 | #endif |
65bda1a9 | 178 | __raw_writeq(next, IOADDR(A_SCD_PERF_CNT_1)); |
1da177e4 | 179 | /* Reset the trace buffer */ |
65bda1a9 | 180 | __raw_writeq(M_SCD_TRACE_CFG_RESET, IOADDR(A_SCD_TRACE_CFG)); |
1da177e4 LT |
181 | #if 0 && defined(M_SCD_TRACE_CFG_FORCECNT) |
182 | /* XXXKW may want to expose control to the data-collector */ | |
183 | tb_options |= M_SCD_TRACE_CFG_FORCECNT; | |
184 | #endif | |
65bda1a9 | 185 | __raw_writeq(tb_options, IOADDR(A_SCD_TRACE_CFG)); |
1da177e4 LT |
186 | sbp.tb_armed = 1; |
187 | } | |
188 | ||
36d98e79 | 189 | static irqreturn_t sbprof_tb_intr(int irq, void *dev_id) |
1da177e4 LT |
190 | { |
191 | int i; | |
bb9b813b RB |
192 | |
193 | pr_debug(DEVNAME ": tb_intr\n"); | |
194 | ||
1da177e4 LT |
195 | if (sbp.next_tb_sample < MAX_TB_SAMPLES) { |
196 | /* XXX should use XKPHYS to make writes bypass L2 */ | |
bb9b813b | 197 | u64 *p = sbp.sbprof_tbbuf[sbp.next_tb_sample++]; |
1da177e4 | 198 | /* Read out trace */ |
65bda1a9 MR |
199 | __raw_writeq(M_SCD_TRACE_CFG_START_READ, |
200 | IOADDR(A_SCD_TRACE_CFG)); | |
1da177e4 LT |
201 | __asm__ __volatile__ ("sync" : : : "memory"); |
202 | /* Loop runs backwards because bundles are read out in reverse order */ | |
203 | for (i = 256 * 6; i > 0; i -= 6) { | |
bb9b813b RB |
204 | /* Subscripts decrease to put bundle in the order */ |
205 | /* t0 lo, t0 hi, t1 lo, t1 hi, t2 lo, t2 hi */ | |
65bda1a9 | 206 | p[i - 1] = __raw_readq(IOADDR(A_SCD_TRACE_READ)); |
d619f38f | 207 | /* read t2 hi */ |
65bda1a9 | 208 | p[i - 2] = __raw_readq(IOADDR(A_SCD_TRACE_READ)); |
d619f38f | 209 | /* read t2 lo */ |
65bda1a9 | 210 | p[i - 3] = __raw_readq(IOADDR(A_SCD_TRACE_READ)); |
d619f38f | 211 | /* read t1 hi */ |
65bda1a9 | 212 | p[i - 4] = __raw_readq(IOADDR(A_SCD_TRACE_READ)); |
d619f38f | 213 | /* read t1 lo */ |
65bda1a9 | 214 | p[i - 5] = __raw_readq(IOADDR(A_SCD_TRACE_READ)); |
d619f38f | 215 | /* read t0 hi */ |
65bda1a9 | 216 | p[i - 6] = __raw_readq(IOADDR(A_SCD_TRACE_READ)); |
d619f38f | 217 | /* read t0 lo */ |
1da177e4 LT |
218 | } |
219 | if (!sbp.tb_enable) { | |
bb9b813b | 220 | pr_debug(DEVNAME ": tb_intr shutdown\n"); |
65bda1a9 MR |
221 | __raw_writeq(M_SCD_TRACE_CFG_RESET, |
222 | IOADDR(A_SCD_TRACE_CFG)); | |
1da177e4 | 223 | sbp.tb_armed = 0; |
d619f38f | 224 | wake_up_interruptible(&sbp.tb_sync); |
1da177e4 | 225 | } else { |
d619f38f MM |
226 | /* knock down current interrupt and get another one later */ |
227 | arm_tb(); | |
1da177e4 LT |
228 | } |
229 | } else { | |
230 | /* No more trace buffer samples */ | |
bb9b813b | 231 | pr_debug(DEVNAME ": tb_intr full\n"); |
65bda1a9 | 232 | __raw_writeq(M_SCD_TRACE_CFG_RESET, IOADDR(A_SCD_TRACE_CFG)); |
1da177e4 | 233 | sbp.tb_armed = 0; |
d619f38f MM |
234 | if (!sbp.tb_enable) |
235 | wake_up_interruptible(&sbp.tb_sync); | |
236 | wake_up_interruptible(&sbp.tb_read); | |
1da177e4 LT |
237 | } |
238 | return IRQ_HANDLED; | |
239 | } | |
240 | ||
36d98e79 | 241 | static irqreturn_t sbprof_pc_intr(int irq, void *dev_id) |
1da177e4 LT |
242 | { |
243 | printk(DEVNAME ": unexpected pc_intr"); | |
244 | return IRQ_NONE; | |
245 | } | |
246 | ||
bb9b813b RB |
247 | /* |
248 | * Requires: Already called zclk_timer_init with a value that won't | |
70342287 RB |
249 | * saturate 40 bits. No subsequent use of SCD performance counters |
250 | * or trace buffer. | |
bb9b813b RB |
251 | */ |
252 | ||
253 | static int sbprof_zbprof_start(struct file *filp) | |
1da177e4 | 254 | { |
bb9b813b RB |
255 | u64 scdperfcnt; |
256 | int err; | |
1da177e4 | 257 | |
bb9b813b | 258 | if (xchg(&sbp.tb_enable, 1)) |
1da177e4 LT |
259 | return -EBUSY; |
260 | ||
bb9b813b | 261 | pr_debug(DEVNAME ": starting\n"); |
1da177e4 | 262 | |
1da177e4 LT |
263 | sbp.next_tb_sample = 0; |
264 | filp->f_pos = 0; | |
265 | ||
49a89efb RB |
266 | err = request_irq(K_INT_TRACE_FREEZE, sbprof_tb_intr, 0, |
267 | DEVNAME " trace freeze", &sbp); | |
bb9b813b | 268 | if (err) |
1da177e4 | 269 | return -EBUSY; |
bb9b813b | 270 | |
1da177e4 | 271 | /* Make sure there isn't a perf-cnt interrupt waiting */ |
65bda1a9 | 272 | scdperfcnt = __raw_readq(IOADDR(A_SCD_PERF_CNT_CFG)); |
1da177e4 | 273 | /* Disable and clear counters, override SRC_1 */ |
65bda1a9 MR |
274 | __raw_writeq((scdperfcnt & ~(M_SPC_CFG_SRC1 | M_SPC_CFG_ENABLE)) | |
275 | M_SPC_CFG_ENABLE | M_SPC_CFG_CLEAR | V_SPC_CFG_SRC1(1), | |
276 | IOADDR(A_SCD_PERF_CNT_CFG)); | |
1da177e4 | 277 | |
bb9b813b | 278 | /* |
d619f38f | 279 | * We grab this interrupt to prevent others from trying to use |
70342287 RB |
280 | * it, even though we don't want to service the interrupts |
281 | * (they only feed into the trace-on-interrupt mechanism) | |
bb9b813b | 282 | */ |
d619f38f MM |
283 | if (request_irq(K_INT_PERF_CNT, sbprof_pc_intr, 0, DEVNAME " scd perfcnt", &sbp)) { |
284 | free_irq(K_INT_TRACE_FREEZE, &sbp); | |
285 | return -EBUSY; | |
286 | } | |
bb9b813b RB |
287 | |
288 | /* | |
d619f38f | 289 | * I need the core to mask these, but the interrupt mapper to |
70342287 | 290 | * pass them through. I am exploiting my knowledge that |
d619f38f | 291 | * cp0_status masks out IP[5]. krw |
bb9b813b | 292 | */ |
d619f38f MM |
293 | #if defined(CONFIG_SIBYTE_BCM1x55) || defined(CONFIG_SIBYTE_BCM1x80) |
294 | __raw_writeq(K_BCM1480_INT_MAP_I3, | |
295 | IOADDR(A_BCM1480_IMR_REGISTER(0, R_BCM1480_IMR_INTERRUPT_MAP_BASE_L) + | |
296 | ((K_BCM1480_INT_PERF_CNT & 0x3f) << 3))); | |
297 | #else | |
65bda1a9 MR |
298 | __raw_writeq(K_INT_MAP_I3, |
299 | IOADDR(A_IMR_REGISTER(0, R_IMR_INTERRUPT_MAP_BASE) + | |
300 | (K_INT_PERF_CNT << 3))); | |
d619f38f | 301 | #endif |
1da177e4 LT |
302 | |
303 | /* Initialize address traps */ | |
65bda1a9 MR |
304 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_UP_0)); |
305 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_UP_1)); | |
306 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_UP_2)); | |
307 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_UP_3)); | |
1da177e4 | 308 | |
65bda1a9 MR |
309 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_DOWN_0)); |
310 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_DOWN_1)); | |
311 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_DOWN_2)); | |
312 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_DOWN_3)); | |
1da177e4 | 313 | |
65bda1a9 MR |
314 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_CFG_0)); |
315 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_CFG_1)); | |
316 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_CFG_2)); | |
317 | __raw_writeq(0, IOADDR(A_ADDR_TRAP_CFG_3)); | |
1da177e4 LT |
318 | |
319 | /* Initialize Trace Event 0-7 */ | |
70342287 | 320 | /* when interrupt */ |
65bda1a9 MR |
321 | __raw_writeq(M_SCD_TREVT_INTERRUPT, IOADDR(A_SCD_TRACE_EVENT_0)); |
322 | __raw_writeq(0, IOADDR(A_SCD_TRACE_EVENT_1)); | |
323 | __raw_writeq(0, IOADDR(A_SCD_TRACE_EVENT_2)); | |
324 | __raw_writeq(0, IOADDR(A_SCD_TRACE_EVENT_3)); | |
325 | __raw_writeq(0, IOADDR(A_SCD_TRACE_EVENT_4)); | |
326 | __raw_writeq(0, IOADDR(A_SCD_TRACE_EVENT_5)); | |
327 | __raw_writeq(0, IOADDR(A_SCD_TRACE_EVENT_6)); | |
328 | __raw_writeq(0, IOADDR(A_SCD_TRACE_EVENT_7)); | |
1da177e4 LT |
329 | |
330 | /* Initialize Trace Sequence 0-7 */ | |
bb9b813b | 331 | /* Start on event 0 (interrupt) */ |
65bda1a9 MR |
332 | __raw_writeq(V_SCD_TRSEQ_FUNC_START | 0x0fff, |
333 | IOADDR(A_SCD_TRACE_SEQUENCE_0)); | |
bb9b813b | 334 | /* dsamp when d used | asamp when a used */ |
65bda1a9 MR |
335 | __raw_writeq(M_SCD_TRSEQ_ASAMPLE | M_SCD_TRSEQ_DSAMPLE | |
336 | K_SCD_TRSEQ_TRIGGER_ALL, | |
337 | IOADDR(A_SCD_TRACE_SEQUENCE_1)); | |
338 | __raw_writeq(0, IOADDR(A_SCD_TRACE_SEQUENCE_2)); | |
339 | __raw_writeq(0, IOADDR(A_SCD_TRACE_SEQUENCE_3)); | |
340 | __raw_writeq(0, IOADDR(A_SCD_TRACE_SEQUENCE_4)); | |
341 | __raw_writeq(0, IOADDR(A_SCD_TRACE_SEQUENCE_5)); | |
342 | __raw_writeq(0, IOADDR(A_SCD_TRACE_SEQUENCE_6)); | |
343 | __raw_writeq(0, IOADDR(A_SCD_TRACE_SEQUENCE_7)); | |
1da177e4 LT |
344 | |
345 | /* Now indicate the PERF_CNT interrupt as a trace-relevant interrupt */ | |
d619f38f MM |
346 | #if defined(CONFIG_SIBYTE_BCM1x55) || defined(CONFIG_SIBYTE_BCM1x80) |
347 | __raw_writeq(1ULL << (K_BCM1480_INT_PERF_CNT & 0x3f), | |
348 | IOADDR(A_BCM1480_IMR_REGISTER(0, R_BCM1480_IMR_INTERRUPT_TRACE_L))); | |
349 | #else | |
65bda1a9 MR |
350 | __raw_writeq(1ULL << K_INT_PERF_CNT, |
351 | IOADDR(A_IMR_REGISTER(0, R_IMR_INTERRUPT_TRACE))); | |
d619f38f | 352 | #endif |
1da177e4 LT |
353 | arm_tb(); |
354 | ||
bb9b813b | 355 | pr_debug(DEVNAME ": done starting\n"); |
1da177e4 LT |
356 | |
357 | return 0; | |
358 | } | |
359 | ||
bb9b813b | 360 | static int sbprof_zbprof_stop(void) |
1da177e4 | 361 | { |
d619f38f | 362 | int err = 0; |
bb9b813b RB |
363 | |
364 | pr_debug(DEVNAME ": stopping\n"); | |
1da177e4 LT |
365 | |
366 | if (sbp.tb_enable) { | |
bb9b813b RB |
367 | /* |
368 | * XXXKW there is a window here where the intr handler may run, | |
369 | * see the disable, and do the wake_up before this sleep | |
370 | * happens. | |
371 | */ | |
372 | pr_debug(DEVNAME ": wait for disarm\n"); | |
373 | err = wait_event_interruptible(sbp.tb_sync, !sbp.tb_armed); | |
374 | pr_debug(DEVNAME ": disarm complete, stat %d\n", err); | |
375 | ||
376 | if (err) | |
377 | return err; | |
378 | ||
1da177e4 | 379 | sbp.tb_enable = 0; |
1da177e4 LT |
380 | free_irq(K_INT_TRACE_FREEZE, &sbp); |
381 | free_irq(K_INT_PERF_CNT, &sbp); | |
382 | } | |
383 | ||
bb9b813b | 384 | pr_debug(DEVNAME ": done stopping\n"); |
1da177e4 | 385 | |
d619f38f | 386 | return err; |
1da177e4 LT |
387 | } |
388 | ||
389 | static int sbprof_tb_open(struct inode *inode, struct file *filp) | |
390 | { | |
391 | int minor; | |
392 | ||
393 | minor = iminor(inode); | |
36ac829e RB |
394 | if (minor != 0) |
395 | return -ENODEV; | |
bb9b813b | 396 | |
36ac829e RB |
397 | if (xchg(&sbp.open, SB_OPENING) != SB_CLOSED) |
398 | return -EBUSY; | |
1da177e4 LT |
399 | |
400 | memset(&sbp, 0, sizeof(struct sbprof_tb)); | |
a1181caa | 401 | sbp.sbprof_tbbuf = vzalloc(MAX_TBSAMPLE_BYTES); |
7558da94 | 402 | if (!sbp.sbprof_tbbuf) { |
36ac829e RB |
403 | sbp.open = SB_CLOSED; |
404 | wmb(); | |
405 | return -ENOMEM; | |
7558da94 | 406 | } |
36ac829e | 407 | |
1da177e4 LT |
408 | init_waitqueue_head(&sbp.tb_sync); |
409 | init_waitqueue_head(&sbp.tb_read); | |
bb9b813b RB |
410 | mutex_init(&sbp.lock); |
411 | ||
412 | sbp.open = SB_OPEN; | |
36ac829e | 413 | wmb(); |
1da177e4 | 414 | |
36ac829e | 415 | return 0; |
1da177e4 LT |
416 | } |
417 | ||
418 | static int sbprof_tb_release(struct inode *inode, struct file *filp) | |
419 | { | |
d619f38f | 420 | int minor; |
1da177e4 | 421 | |
d619f38f | 422 | minor = iminor(inode); |
36ac829e | 423 | if (minor != 0 || sbp.open != SB_CLOSED) |
1da177e4 | 424 | return -ENODEV; |
1da177e4 | 425 | |
bb9b813b RB |
426 | mutex_lock(&sbp.lock); |
427 | ||
428 | if (sbp.tb_armed || sbp.tb_enable) | |
1da177e4 | 429 | sbprof_zbprof_stop(); |
1da177e4 LT |
430 | |
431 | vfree(sbp.sbprof_tbbuf); | |
36ac829e RB |
432 | sbp.open = SB_CLOSED; |
433 | wmb(); | |
1da177e4 | 434 | |
bb9b813b RB |
435 | mutex_unlock(&sbp.lock); |
436 | ||
1da177e4 LT |
437 | return 0; |
438 | } | |
439 | ||
440 | static ssize_t sbprof_tb_read(struct file *filp, char *buf, | |
441 | size_t size, loff_t *offp) | |
442 | { | |
443 | int cur_sample, sample_off, cur_count, sample_left; | |
bb9b813b | 444 | char *src; |
d619f38f MM |
445 | int count = 0; |
446 | char *dest = buf; | |
447 | long cur_off = *offp; | |
bb9b813b | 448 | |
96d4f267 | 449 | if (!access_ok(buf, size)) |
bb9b813b RB |
450 | return -EFAULT; |
451 | ||
452 | mutex_lock(&sbp.lock); | |
1da177e4 LT |
453 | |
454 | count = 0; | |
455 | cur_sample = cur_off / TB_SAMPLE_SIZE; | |
456 | sample_off = cur_off % TB_SAMPLE_SIZE; | |
457 | sample_left = TB_SAMPLE_SIZE - sample_off; | |
bb9b813b | 458 | |
1da177e4 | 459 | while (size && (cur_sample < sbp.next_tb_sample)) { |
bb9b813b RB |
460 | int err; |
461 | ||
1da177e4 LT |
462 | cur_count = size < sample_left ? size : sample_left; |
463 | src = (char *)(((long)sbp.sbprof_tbbuf[cur_sample])+sample_off); | |
bb9b813b RB |
464 | err = __copy_to_user(dest, src, cur_count); |
465 | if (err) { | |
466 | *offp = cur_off + cur_count - err; | |
467 | mutex_unlock(&sbp.lock); | |
468 | return err; | |
469 | } | |
bb9b813b | 470 | pr_debug(DEVNAME ": read from sample %d, %d bytes\n", |
70342287 | 471 | cur_sample, cur_count); |
1da177e4 LT |
472 | size -= cur_count; |
473 | sample_left -= cur_count; | |
474 | if (!sample_left) { | |
475 | cur_sample++; | |
476 | sample_off = 0; | |
477 | sample_left = TB_SAMPLE_SIZE; | |
478 | } else { | |
479 | sample_off += cur_count; | |
480 | } | |
481 | cur_off += cur_count; | |
482 | dest += cur_count; | |
483 | count += cur_count; | |
484 | } | |
485 | *offp = cur_off; | |
bb9b813b | 486 | mutex_unlock(&sbp.lock); |
1da177e4 LT |
487 | |
488 | return count; | |
489 | } | |
490 | ||
d619f38f MM |
491 | static long sbprof_tb_ioctl(struct file *filp, |
492 | unsigned int command, | |
493 | unsigned long arg) | |
1da177e4 | 494 | { |
d619f38f | 495 | int err = 0; |
1da177e4 LT |
496 | |
497 | switch (command) { | |
498 | case SBPROF_ZBSTART: | |
bb9b813b | 499 | mutex_lock(&sbp.lock); |
d619f38f | 500 | err = sbprof_zbprof_start(filp); |
bb9b813b | 501 | mutex_unlock(&sbp.lock); |
1da177e4 | 502 | break; |
bb9b813b | 503 | |
1da177e4 | 504 | case SBPROF_ZBSTOP: |
bb9b813b | 505 | mutex_lock(&sbp.lock); |
d619f38f | 506 | err = sbprof_zbprof_stop(); |
bb9b813b | 507 | mutex_unlock(&sbp.lock); |
1da177e4 | 508 | break; |
bb9b813b | 509 | |
d619f38f MM |
510 | case SBPROF_ZBWAITFULL: { |
511 | err = wait_event_interruptible(sbp.tb_read, TB_FULL); | |
512 | if (err) | |
bb9b813b RB |
513 | break; |
514 | ||
d619f38f | 515 | err = put_user(TB_FULL, (int *) arg); |
bb9b813b | 516 | break; |
d619f38f | 517 | } |
bb9b813b | 518 | |
1da177e4 | 519 | default: |
d619f38f | 520 | err = -EINVAL; |
1da177e4 LT |
521 | break; |
522 | } | |
523 | ||
d619f38f | 524 | return err; |
1da177e4 LT |
525 | } |
526 | ||
5dfe4c96 | 527 | static const struct file_operations sbprof_tb_fops = { |
1da177e4 LT |
528 | .owner = THIS_MODULE, |
529 | .open = sbprof_tb_open, | |
530 | .release = sbprof_tb_release, | |
531 | .read = sbprof_tb_read, | |
70342287 | 532 | .unlocked_ioctl = sbprof_tb_ioctl, |
b288f135 | 533 | .compat_ioctl = sbprof_tb_ioctl, |
1da177e4 | 534 | .mmap = NULL, |
6038f373 | 535 | .llseek = default_llseek, |
1da177e4 LT |
536 | }; |
537 | ||
bb9b813b RB |
538 | static struct class *tb_class; |
539 | static struct device *tb_dev; | |
540 | ||
1da177e4 LT |
541 | static int __init sbprof_tb_init(void) |
542 | { | |
bb9b813b RB |
543 | struct device *dev; |
544 | struct class *tbc; | |
545 | int err; | |
546 | ||
1da177e4 LT |
547 | if (register_chrdev(SBPROF_TB_MAJOR, DEVNAME, &sbprof_tb_fops)) { |
548 | printk(KERN_WARNING DEVNAME ": initialization failed (dev %d)\n", | |
549 | SBPROF_TB_MAJOR); | |
550 | return -EIO; | |
551 | } | |
bb9b813b RB |
552 | |
553 | tbc = class_create(THIS_MODULE, "sb_tracebuffer"); | |
554 | if (IS_ERR(tbc)) { | |
555 | err = PTR_ERR(tbc); | |
556 | goto out_chrdev; | |
557 | } | |
558 | ||
559 | tb_class = tbc; | |
560 | ||
a9b12619 | 561 | dev = device_create(tbc, NULL, MKDEV(SBPROF_TB_MAJOR, 0), NULL, "tb"); |
bb9b813b RB |
562 | if (IS_ERR(dev)) { |
563 | err = PTR_ERR(dev); | |
564 | goto out_class; | |
565 | } | |
566 | tb_dev = dev; | |
567 | ||
36ac829e RB |
568 | sbp.open = SB_CLOSED; |
569 | wmb(); | |
1da177e4 | 570 | tb_period = zbbus_mhz * 10000LL; |
d619f38f MM |
571 | pr_info(DEVNAME ": initialized - tb_period = %lld\n", |
572 | (long long) tb_period); | |
1da177e4 | 573 | return 0; |
bb9b813b RB |
574 | |
575 | out_class: | |
576 | class_destroy(tb_class); | |
577 | out_chrdev: | |
578 | unregister_chrdev(SBPROF_TB_MAJOR, DEVNAME); | |
579 | ||
580 | return err; | |
1da177e4 LT |
581 | } |
582 | ||
583 | static void __exit sbprof_tb_cleanup(void) | |
584 | { | |
bb9b813b | 585 | device_destroy(tb_class, MKDEV(SBPROF_TB_MAJOR, 0)); |
1da177e4 | 586 | unregister_chrdev(SBPROF_TB_MAJOR, DEVNAME); |
bb9b813b | 587 | class_destroy(tb_class); |
1da177e4 LT |
588 | } |
589 | ||
590 | module_init(sbprof_tb_init); | |
591 | module_exit(sbprof_tb_cleanup); | |
bb9b813b RB |
592 | |
593 | MODULE_ALIAS_CHARDEV_MAJOR(SBPROF_TB_MAJOR); | |
594 | MODULE_AUTHOR("Ralf Baechle <ralf@linux-mips.org>"); | |
595 | MODULE_LICENSE("GPL"); |