/* * Copyright (C) 2012 Google, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include "../../../kernel/trace/trace.h" struct persistent_trace_record { unsigned long ip; unsigned long parent_ip; }; #define REC_SIZE sizeof(struct persistent_trace_record) static struct persistent_ram_zone *persistent_trace; static int persistent_trace_enabled; static struct trace_array *persistent_trace_array; static struct ftrace_ops trace_ops; static int persistent_tracer_init(struct trace_array *tr) { persistent_trace_array = tr; tr->cpu = get_cpu(); put_cpu(); tracing_start_cmdline_record(); persistent_trace_enabled = 0; smp_wmb(); register_ftrace_function(&trace_ops); smp_wmb(); persistent_trace_enabled = 1; return 0; } static void persistent_trace_reset(struct trace_array *tr) { persistent_trace_enabled = 0; smp_wmb(); unregister_ftrace_function(&trace_ops); tracing_stop_cmdline_record(); } static void persistent_trace_start(struct trace_array *tr) { tracing_reset_online_cpus(tr); } static void persistent_trace_call(unsigned long ip, unsigned long parent_ip) { struct trace_array *tr = persistent_trace_array; struct trace_array_cpu *data; long disabled; struct persistent_trace_record rec; unsigned long flags; int cpu; smp_rmb(); if (unlikely(!persistent_trace_enabled)) return; if (unlikely(oops_in_progress)) return; /* * Need to use raw, since this must be called before the * recursive protection is performed. */ local_irq_save(flags); cpu = raw_smp_processor_id(); data = tr->data[cpu]; disabled = atomic_inc_return(&data->disabled); if (likely(disabled == 1)) { rec.ip = ip; rec.parent_ip = parent_ip; rec.ip |= cpu; persistent_ram_write(persistent_trace, &rec, sizeof(rec)); } atomic_dec(&data->disabled); local_irq_restore(flags); } static struct ftrace_ops trace_ops __read_mostly = { .func = persistent_trace_call, .flags = FTRACE_OPS_FL_GLOBAL, }; static struct tracer persistent_tracer __read_mostly = { .name = "persistent", .init = persistent_tracer_init, .reset = persistent_trace_reset, .start = persistent_trace_start, .wait_pipe = poll_wait_pipe, }; struct persistent_trace_seq_data { const void *ptr; size_t off; size_t size; }; void *persistent_trace_seq_start(struct seq_file *s, loff_t *pos) { struct persistent_trace_seq_data *data; data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return NULL; data->ptr = persistent_ram_old(persistent_trace); data->size = persistent_ram_old_size(persistent_trace); data->off = data->size % REC_SIZE; data->off += *pos * REC_SIZE; if (data->off + REC_SIZE > data->size) { kfree(data); return NULL; } return data; } void persistent_trace_seq_stop(struct seq_file *s, void *v) { kfree(v); } void *persistent_trace_seq_next(struct seq_file *s, void *v, loff_t *pos) { struct persistent_trace_seq_data *data = v; data->off += REC_SIZE; if (data->off + REC_SIZE > data->size) return NULL; (*pos)++; return data; } int persistent_trace_seq_show(struct seq_file *s, void *v) { struct persistent_trace_seq_data *data = v; struct persistent_trace_record *rec; rec = (struct persistent_trace_record *)(data->ptr + data->off); seq_printf(s, "%ld %08lx %08lx %pf <- %pF\n", rec->ip & 3, rec->ip, rec->parent_ip, (void *)rec->ip, (void *)rec->parent_ip); return 0; } static const struct seq_operations persistent_trace_seq_ops = { .start = persistent_trace_seq_start, .next = persistent_trace_seq_next, .stop = persistent_trace_seq_stop, .show = persistent_trace_seq_show, }; static int persistent_trace_old_open(struct inode *inode, struct file *file) { return seq_open(file, &persistent_trace_seq_ops); } static const struct file_operations persistent_trace_old_fops = { .open = persistent_trace_old_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static int __devinit persistent_trace_probe(struct platform_device *pdev) { struct dentry *d; int ret; persistent_trace = persistent_ram_init_ringbuffer(&pdev->dev, false); if (IS_ERR(persistent_trace)) { pr_err("persistent_trace: failed to init ringbuffer: %ld\n", PTR_ERR(persistent_trace)); return PTR_ERR(persistent_trace); } ret = register_tracer(&persistent_tracer); if (ret) pr_err("persistent_trace: failed to register tracer"); if (persistent_ram_old_size(persistent_trace) > 0) { d = debugfs_create_file("persistent_trace", S_IRUGO, NULL, NULL, &persistent_trace_old_fops); if (IS_ERR_OR_NULL(d)) pr_err("persistent_trace: failed to create old file\n"); } return 0; } static struct platform_driver persistent_trace_driver = { .probe = persistent_trace_probe, .driver = { .name = "persistent_trace", }, }; static int __init persistent_trace_init(void) { return platform_driver_register(&persistent_trace_driver); } core_initcall(persistent_trace_init);