]>
Commit | Line | Data |
---|---|---|
b47be87f DS |
1 | /* -*- linux-c -*- |
2 | * | |
42b8c995 DS |
3 | * This transport version uses relayfs on top of a debugfs file. This |
4 | * code started as a proposed relayfs interface called 'utt'. It has | |
5 | * been modified and simplified for systemtap. | |
b47be87f | 6 | * |
ef36f781 | 7 | * Changes Copyright (C) 2009-2014 Red Hat Inc. |
b47be87f | 8 | * |
42b8c995 DS |
9 | * Original utt code by: |
10 | * Copyright (C) 2006 Jens Axboe <axboe@suse.de> | |
11 | * Moved to utt.c by Tom Zanussi, 2006 | |
b47be87f DS |
12 | * |
13 | * This program is free software; you can redistribute it and/or modify | |
14 | * it under the terms of the GNU General Public License version 2 as | |
15 | * published by the Free Software Foundation. | |
16 | * | |
17 | * This program is distributed in the hope that it will be useful, | |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | * GNU General Public License for more details. | |
21 | * | |
22 | * You should have received a copy of the GNU General Public License | |
e8daaf60 | 23 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
b47be87f DS |
24 | * |
25 | */ | |
4a8c28f9 | 26 | #include <linux/kernel.h> |
b47be87f DS |
27 | #include <linux/module.h> |
28 | #include <linux/percpu.h> | |
29 | #include <linux/init.h> | |
4a8c28f9 | 30 | #include <linux/debugfs.h> |
b47be87f DS |
31 | #include <linux/mm.h> |
32 | #include <linux/relay.h> | |
33 | #include <linux/timer.h> | |
85716315 | 34 | #include "../uidgid_compatibility.h" |
4a8c28f9 | 35 | |
b47be87f DS |
36 | #ifndef STP_RELAY_TIMER_INTERVAL |
37 | /* Wakeup timer interval in jiffies (default 10 ms) */ | |
38 | #define STP_RELAY_TIMER_INTERVAL ((HZ + 99) / 100) | |
4a8c28f9 | 39 | #endif |
4a8c28f9 | 40 | |
43041178 DS |
41 | /* Note: if struct _stp_relay_data_type changes, staplog.c might need |
42 | * to be changed. */ | |
b47be87f | 43 | struct _stp_relay_data_type { |
b47be87f | 44 | struct rchan *rchan; |
f2b610b6 | 45 | atomic_t /* enum _stp_transport_state */ transport_state; |
9b1d74f9 | 46 | #ifdef _STP_USE_DROPPED_FILE |
b47be87f DS |
47 | struct dentry *dropped_file; |
48 | atomic_t dropped; | |
9b1d74f9 | 49 | #endif |
b47be87f DS |
50 | atomic_t wakeup; |
51 | struct timer_list timer; | |
52 | int overwrite_flag; | |
53 | }; | |
54 | struct _stp_relay_data_type _stp_relay_data; | |
4a8c28f9 | 55 | |
8af0ed12 PP |
56 | /* relay_file_operations is const, so .owner is obviously not set there. |
57 | * Below struct, filled in _stp_transport_data_fs_init(), fixes it. */ | |
58 | static struct file_operations relay_file_operations_w_owner; | |
59 | ||
b47be87f DS |
60 | /* |
61 | * __stp_relay_switch_subbuf - switch to a new sub-buffer | |
62 | * | |
63 | * Most of this function is deadcopy of relay_switch_subbuf. | |
64 | */ | |
65 | static size_t __stp_relay_switch_subbuf(struct rchan_buf *buf, size_t length) | |
4a8c28f9 | 66 | { |
b47be87f DS |
67 | char *old, *new; |
68 | size_t old_subbuf, new_subbuf; | |
4a8c28f9 | 69 | |
b47be87f DS |
70 | if (unlikely(buf == NULL)) |
71 | return 0; | |
4a8c28f9 | 72 | |
b47be87f DS |
73 | if (unlikely(length > buf->chan->subbuf_size)) |
74 | goto toobig; | |
75 | ||
76 | if (buf->offset != buf->chan->subbuf_size + 1) { | |
77 | buf->prev_padding = buf->chan->subbuf_size - buf->offset; | |
78 | old_subbuf = buf->subbufs_produced % buf->chan->n_subbufs; | |
79 | buf->padding[old_subbuf] = buf->prev_padding; | |
80 | buf->subbufs_produced++; | |
81 | buf->dentry->d_inode->i_size += buf->chan->subbuf_size - | |
82 | buf->padding[old_subbuf]; | |
83 | smp_mb(); | |
84 | if (waitqueue_active(&buf->read_wait)) | |
85 | /* | |
86 | * Calling wake_up_interruptible() and __mod_timer() | |
87 | * from here will deadlock if we happen to be logging | |
88 | * from the scheduler and timer (trying to re-grab | |
89 | * rq->lock/timer->base->lock), so just set a flag. | |
90 | */ | |
91 | atomic_set(&_stp_relay_data.wakeup, 1); | |
92 | } | |
4a8c28f9 | 93 | |
b47be87f DS |
94 | old = buf->data; |
95 | new_subbuf = buf->subbufs_produced % buf->chan->n_subbufs; | |
96 | new = (char*)buf->start + new_subbuf * buf->chan->subbuf_size; | |
97 | buf->offset = 0; | |
98 | if (!buf->chan->cb->subbuf_start(buf, new, old, buf->prev_padding)) { | |
99 | buf->offset = buf->chan->subbuf_size + 1; | |
100 | return 0; | |
4a8c28f9 | 101 | } |
b47be87f DS |
102 | buf->data = new; |
103 | buf->padding[new_subbuf] = 0; | |
104 | ||
105 | if (unlikely(length + buf->offset > buf->chan->subbuf_size)) | |
106 | goto toobig; | |
107 | ||
108 | return length; | |
4a8c28f9 | 109 | |
b47be87f DS |
110 | toobig: |
111 | buf->chan->last_toobig = length; | |
112 | return 0; | |
4a8c28f9 DS |
113 | } |
114 | ||
b47be87f | 115 | static void __stp_relay_wakeup_readers(struct rchan_buf *buf) |
4a8c28f9 | 116 | { |
b47be87f DS |
117 | if (buf && waitqueue_active(&buf->read_wait) && |
118 | buf->subbufs_produced != buf->subbufs_consumed) | |
119 | wake_up_interruptible(&buf->read_wait); | |
120 | } | |
4a8c28f9 | 121 | |
b47be87f DS |
122 | static void __stp_relay_wakeup_timer(unsigned long val) |
123 | { | |
4a8c28f9 | 124 | #ifdef STP_BULKMODE |
b47be87f | 125 | int i; |
4a8c28f9 | 126 | #endif |
4a8c28f9 | 127 | |
b47be87f DS |
128 | if (atomic_read(&_stp_relay_data.wakeup)) { |
129 | atomic_set(&_stp_relay_data.wakeup, 0); | |
4a8c28f9 | 130 | #ifdef STP_BULKMODE |
b47be87f DS |
131 | for_each_possible_cpu(i) |
132 | __stp_relay_wakeup_readers(_stp_relay_data.rchan->buf[i]); | |
4a8c28f9 | 133 | #else |
b47be87f | 134 | __stp_relay_wakeup_readers(_stp_relay_data.rchan->buf[0]); |
4a8c28f9 | 135 | #endif |
b47be87f | 136 | } |
4a8c28f9 | 137 | |
f2b610b6 FCE |
138 | if (atomic_read(&_stp_relay_data.transport_state) == STP_TRANSPORT_RUNNING) |
139 | mod_timer(&_stp_relay_data.timer, jiffies + STP_RELAY_TIMER_INTERVAL); | |
140 | else | |
141 | dbug_trans(0, "relay_v2 wakeup timer expiry\n"); | |
4a8c28f9 DS |
142 | } |
143 | ||
b47be87f | 144 | static void __stp_relay_timer_init(void) |
4a8c28f9 | 145 | { |
b47be87f DS |
146 | atomic_set(&_stp_relay_data.wakeup, 0); |
147 | init_timer(&_stp_relay_data.timer); | |
148 | _stp_relay_data.timer.expires = jiffies + STP_RELAY_TIMER_INTERVAL; | |
149 | _stp_relay_data.timer.function = __stp_relay_wakeup_timer; | |
150 | _stp_relay_data.timer.data = 0; | |
151 | add_timer(&_stp_relay_data.timer); | |
152 | smp_mb(); | |
4a8c28f9 DS |
153 | } |
154 | ||
7b1be319 DS |
155 | static enum _stp_transport_state _stp_transport_get_state(void) |
156 | { | |
f2b610b6 | 157 | return atomic_read (&_stp_relay_data.transport_state); |
7b1be319 DS |
158 | } |
159 | ||
24a5e9a6 | 160 | static void _stp_transport_data_fs_overwrite(int overwrite) |
4a8c28f9 | 161 | { |
b47be87f DS |
162 | _stp_relay_data.overwrite_flag = overwrite; |
163 | } | |
4a8c28f9 | 164 | |
9b1d74f9 | 165 | #ifdef _STP_USE_DROPPED_FILE |
b47be87f | 166 | static int __stp_relay_dropped_open(struct inode *inode, struct file *filp) |
4a8c28f9 | 167 | { |
4a8c28f9 DS |
168 | return 0; |
169 | } | |
170 | ||
b47be87f DS |
171 | static ssize_t __stp_relay_dropped_read(struct file *filp, char __user *buffer, |
172 | size_t count, loff_t *ppos) | |
173 | { | |
174 | char buf[16]; | |
4a8c28f9 | 175 | |
b47be87f DS |
176 | snprintf(buf, sizeof(buf), "%u\n", |
177 | atomic_read(&_stp_relay_data.dropped)); | |
178 | ||
179 | return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf)); | |
180 | } | |
181 | ||
182 | static struct file_operations __stp_relay_dropped_fops = { | |
183 | .owner = THIS_MODULE, | |
184 | .open = __stp_relay_dropped_open, | |
185 | .read = __stp_relay_dropped_read, | |
186 | }; | |
9b1d74f9 | 187 | #endif |
4a8c28f9 DS |
188 | |
189 | /* | |
b47be87f DS |
190 | * Keep track of how many times we encountered a full subbuffer, to aid |
191 | * the user space app in telling how many lost events there were. | |
4a8c28f9 | 192 | */ |
b47be87f DS |
193 | static int __stp_relay_subbuf_start_callback(struct rchan_buf *buf, |
194 | void *subbuf, void *prev_subbuf, | |
195 | size_t prev_padding) | |
4a8c28f9 | 196 | { |
b47be87f DS |
197 | if (_stp_relay_data.overwrite_flag || !relay_buf_full(buf)) |
198 | return 1; | |
4a8c28f9 | 199 | |
9b1d74f9 | 200 | #ifdef _STP_USE_DROPPED_FILE |
b47be87f | 201 | atomic_inc(&_stp_relay_data.dropped); |
9b1d74f9 | 202 | #endif |
b47be87f | 203 | return 0; |
4a8c28f9 | 204 | } |
4a8c28f9 | 205 | |
b47be87f | 206 | static int __stp_relay_remove_buf_file_callback(struct dentry *dentry) |
4a8c28f9 | 207 | { |
b47be87f DS |
208 | debugfs_remove(dentry); |
209 | return 0; | |
210 | } | |
4a8c28f9 | 211 | |
b47be87f DS |
212 | static struct dentry * |
213 | __stp_relay_create_buf_file_callback(const char *filename, | |
214 | struct dentry *parent, | |
56111fdc FCE |
215 | #ifdef STAPCONF_RELAY_UMODE_T |
216 | umode_t mode, | |
217 | #else | |
b47be87f | 218 | int mode, |
56111fdc | 219 | #endif |
b47be87f DS |
220 | struct rchan_buf *buf, |
221 | int *is_global) | |
222 | { | |
223 | struct dentry *file = debugfs_create_file(filename, mode, parent, buf, | |
8af0ed12 | 224 | &relay_file_operations_w_owner); |
d05b7a1c DS |
225 | /* |
226 | * Here's what 'is_global' does (from linux/relay.h): | |
227 | * | |
228 | * Setting the is_global outparam to a non-zero value will | |
229 | * cause relay_open() to create a single global buffer rather | |
230 | * than the default set of per-cpu buffers. | |
231 | */ | |
232 | if (is_global) { | |
233 | #ifdef STP_BULKMODE | |
234 | *is_global = 0; | |
235 | #else | |
236 | *is_global = 1; | |
237 | #endif | |
238 | } | |
239 | ||
e57421f4 DS |
240 | if (IS_ERR(file)) { |
241 | file = NULL; | |
242 | } | |
243 | else if (file) { | |
85716315 AF |
244 | file->d_inode->i_uid = KUIDT_INIT(_stp_uid); |
245 | file->d_inode->i_gid = KGIDT_INIT(_stp_gid); | |
4a8c28f9 | 246 | } |
b47be87f | 247 | return file; |
4a8c28f9 DS |
248 | } |
249 | ||
b47be87f DS |
250 | static struct rchan_callbacks __stp_relay_callbacks = { |
251 | .subbuf_start = __stp_relay_subbuf_start_callback, | |
252 | .create_buf_file = __stp_relay_create_buf_file_callback, | |
253 | .remove_buf_file = __stp_relay_remove_buf_file_callback, | |
254 | }; | |
4a8c28f9 | 255 | |
01adca5b DS |
256 | static void _stp_transport_data_fs_start(void) |
257 | { | |
f2b610b6 FCE |
258 | if (atomic_read (&_stp_relay_data.transport_state) == STP_TRANSPORT_INITIALIZED) { |
259 | atomic_set (&_stp_relay_data.transport_state, STP_TRANSPORT_RUNNING); | |
01adca5b DS |
260 | /* We're initialized. Now start the timer. */ |
261 | __stp_relay_timer_init(); | |
01adca5b DS |
262 | } |
263 | } | |
264 | ||
265 | static void _stp_transport_data_fs_stop(void) | |
4a8c28f9 | 266 | { |
f2b610b6 FCE |
267 | if (atomic_read (&_stp_relay_data.transport_state) == STP_TRANSPORT_RUNNING) { |
268 | atomic_set (&_stp_relay_data.transport_state, STP_TRANSPORT_STOPPED); | |
b47be87f | 269 | del_timer_sync(&_stp_relay_data.timer); |
01adca5b | 270 | dbug_trans(0, "flushing...\n"); |
01adca5b DS |
271 | if (_stp_relay_data.rchan) |
272 | relay_flush(_stp_relay_data.rchan); | |
273 | } | |
274 | } | |
b47be87f | 275 | |
01adca5b DS |
276 | static void _stp_transport_data_fs_close(void) |
277 | { | |
278 | _stp_transport_data_fs_stop(); | |
9b1d74f9 | 279 | #ifdef _STP_USE_DROPPED_FILE |
b47be87f DS |
280 | if (_stp_relay_data.dropped_file) |
281 | debugfs_remove(_stp_relay_data.dropped_file); | |
9b1d74f9 | 282 | #endif |
b47be87f | 283 | if (_stp_relay_data.rchan) { |
b47be87f | 284 | relay_close(_stp_relay_data.rchan); |
01adca5b | 285 | _stp_relay_data.rchan = NULL; |
b47be87f | 286 | } |
4a8c28f9 DS |
287 | } |
288 | ||
4a8c28f9 DS |
289 | static int _stp_transport_data_fs_init(void) |
290 | { | |
291 | int rc; | |
292 | u64 npages; | |
293 | struct sysinfo si; | |
294 | ||
f2b610b6 | 295 | atomic_set(&_stp_relay_data.transport_state, STP_TRANSPORT_STOPPED); |
b47be87f | 296 | _stp_relay_data.overwrite_flag = 0; |
9b1d74f9 DS |
297 | _stp_relay_data.rchan = NULL; |
298 | ||
299 | #ifdef _STP_USE_DROPPED_FILE | |
b47be87f DS |
300 | atomic_set(&_stp_relay_data.dropped, 0); |
301 | _stp_relay_data.dropped_file = NULL; | |
b47be87f DS |
302 | |
303 | /* Create "dropped" file. */ | |
304 | _stp_relay_data.dropped_file | |
c714669f | 305 | = debugfs_create_file("dropped", 0400, _stp_get_module_dir(), |
b47be87f DS |
306 | NULL, &__stp_relay_dropped_fops); |
307 | if (!_stp_relay_data.dropped_file) { | |
308 | rc = -EIO; | |
309 | goto err; | |
310 | } | |
e57421f4 DS |
311 | else if (IS_ERR(_stp_relay_data.dropped_file)) { |
312 | rc = PTR_ERR(_stp_relay_data.dropped_file); | |
313 | _stp_relay_data.dropped_file = NULL; | |
314 | goto err; | |
315 | } | |
316 | ||
85716315 AF |
317 | _stp_relay_data.dropped_file->d_inode->i_uid = KUIDT_INIT(_stp_uid); |
318 | _stp_relay_data.dropped_file->d_inode->i_gid = KGIDT_INIT(_stp_gid); | |
9b1d74f9 | 319 | #endif |
4a8c28f9 | 320 | |
b47be87f | 321 | /* Create "trace" file. */ |
4a8c28f9 DS |
322 | npages = _stp_subbuf_size * _stp_nsubbufs; |
323 | #ifdef STP_BULKMODE | |
66abcb3c | 324 | npages *= num_online_cpus(); |
4a8c28f9 DS |
325 | #endif |
326 | npages >>= PAGE_SHIFT; | |
327 | si_meminfo(&si); | |
328 | #define MB(i) (unsigned long)((i) >> (20 - PAGE_SHIFT)) | |
329 | if (npages > (si.freeram + si.bufferram)) { | |
330 | errk("Not enough free+buffered memory(%luMB) for log buffer(%luMB)\n", | |
331 | MB(si.freeram + si.bufferram), | |
332 | MB(npages)); | |
333 | rc = -ENOMEM; | |
334 | goto err; | |
335 | } | |
336 | else if (npages > si.freeram) { | |
337 | /* exceeds freeram, but below freeram+bufferram */ | |
338 | printk(KERN_WARNING | |
339 | "log buffer size exceeds free memory(%luMB)\n", | |
340 | MB(si.freeram)); | |
341 | } | |
8af0ed12 PP |
342 | relay_file_operations_w_owner = relay_file_operations; |
343 | relay_file_operations_w_owner.owner = THIS_MODULE; | |
4a8c28f9 | 344 | #if (RELAYFS_CHANNEL_VERSION >= 7) |
b47be87f DS |
345 | _stp_relay_data.rchan = relay_open("trace", _stp_get_module_dir(), |
346 | _stp_subbuf_size, _stp_nsubbufs, | |
347 | &__stp_relay_callbacks, NULL); | |
4a8c28f9 | 348 | #else /* (RELAYFS_CHANNEL_VERSION < 7) */ |
b47be87f DS |
349 | _stp_relay_data.rchan = relay_open("trace", _stp_get_module_dir(), |
350 | _stp_subbuf_size, _stp_nsubbufs, | |
351 | &__stp_relay_callbacks); | |
4a8c28f9 | 352 | #endif /* (RELAYFS_CHANNEL_VERSION < 7) */ |
b47be87f | 353 | if (!_stp_relay_data.rchan) { |
4a8c28f9 DS |
354 | rc = -ENOENT; |
355 | goto err; | |
356 | } | |
e04e95a4 FCE |
357 | /* Increment _stp_allocated_memory and _stp_allocated_net_memory to account for buffers |
358 | allocated by relay_open. */ | |
359 | { | |
360 | u64 relay_mem; | |
361 | relay_mem = _stp_subbuf_size * _stp_nsubbufs; | |
362 | #ifdef STP_BULKMODE | |
363 | relay_mem *= num_online_cpus(); | |
364 | #endif | |
365 | _stp_allocated_net_memory += relay_mem; | |
366 | _stp_allocated_memory += relay_mem; | |
367 | } | |
368 | ||
4a8c28f9 | 369 | dbug_trans(1, "returning 0...\n"); |
f2b610b6 | 370 | atomic_set (&_stp_relay_data.transport_state, STP_TRANSPORT_INITIALIZED); |
b47be87f | 371 | |
4a8c28f9 DS |
372 | return 0; |
373 | ||
374 | err: | |
b47be87f | 375 | _stp_transport_data_fs_close(); |
4a8c28f9 DS |
376 | return rc; |
377 | } | |
378 | ||
b47be87f DS |
379 | |
380 | /** | |
42b8c995 DS |
381 | * _stp_data_write_reserve - try to reserve size_request bytes |
382 | * @size_request: number of bytes to attempt to reserve | |
383 | * @entry: entry is returned here | |
b47be87f | 384 | * |
42b8c995 DS |
385 | * Returns number of bytes reserved, 0 if full. On return, entry |
386 | * will point to allocated opaque pointer. Use | |
387 | * _stp_data_entry_data() to get pointer to copy data into. | |
b47be87f | 388 | * |
42b8c995 DS |
389 | * (For this code's purposes, entry is filled in with the actual |
390 | * data pointer, but the caller doesn't know that.) | |
b47be87f DS |
391 | */ |
392 | static size_t | |
393 | _stp_data_write_reserve(size_t size_request, void **entry) | |
394 | { | |
395 | struct rchan_buf *buf; | |
396 | ||
397 | if (entry == NULL) | |
398 | return -EINVAL; | |
399 | ||
400 | buf = _stp_relay_data.rchan->buf[smp_processor_id()]; | |
401 | if (unlikely(buf->offset + size_request > buf->chan->subbuf_size)) { | |
402 | size_request = __stp_relay_switch_subbuf(buf, size_request); | |
403 | if (!size_request) | |
404 | return 0; | |
405 | } | |
406 | *entry = (char*)buf->data + buf->offset; | |
407 | buf->offset += size_request; | |
408 | ||
409 | return size_request; | |
410 | } | |
411 | ||
412 | static unsigned char *_stp_data_entry_data(void *entry) | |
4a8c28f9 | 413 | { |
b47be87f DS |
414 | /* Nothing to do here. */ |
415 | return entry; | |
4a8c28f9 DS |
416 | } |
417 | ||
b47be87f DS |
418 | static int _stp_data_write_commit(void *entry) |
419 | { | |
420 | /* Nothing to do here. */ | |
421 | return 0; | |
422 | } |