/* * sched_trace.c -- record scheduling events to a byte stream. */ #include #include #include #include #include #include #include #include #include #include /* Allocate a buffer of about 32k per CPU */ #define LITMUS_TRACE_BUF_PAGES 8 #define LITMUS_TRACE_BUF_SIZE (PAGE_SIZE * LITMUS_TRACE_BUF_PAGES * NR_CPUS) /* Max length of one read from the buffer */ #define MAX_READ_LEN (64 * 1024) /* Max length for one write --- from kernel --- to the buffer */ #define MSG_SIZE 255 /* * Major number for the tracing char device. * the major numbes are from the unassigned/local use block */ #define LOG_MAJOR 251 /* Inner ring buffer structure */ typedef struct { rwlock_t del_lock; /* the buffer */ struct kfifo *kfifo; } ring_buffer_t; /* Main buffer structure */ typedef struct { ring_buffer_t buf; atomic_t reader_cnt; struct semaphore reader_mutex; } trace_buffer_t; /* * Inner buffer management functions */ void rb_init(ring_buffer_t* buf) { rwlock_init(&buf->del_lock); buf->kfifo = NULL; } int rb_alloc_buf(ring_buffer_t* buf, unsigned int size) { unsigned long flags; write_lock_irqsave(&buf->del_lock, flags); buf->kfifo = kfifo_alloc(size, GFP_ATOMIC, NULL); write_unlock_irqrestore(&buf->del_lock, flags); if(IS_ERR(buf->kfifo)) { printk(KERN_ERR "kfifo_alloc failed\n"); return PTR_ERR(buf->kfifo); } return 0; } int rb_free_buf(ring_buffer_t* buf) { unsigned long flags; write_lock_irqsave(&buf->del_lock, flags); BUG_ON(!buf->kfifo); kfifo_free(buf->kfifo); buf->kfifo = NULL; write_unlock_irqrestore(&buf->del_lock, flags); return 0; } /* * Assumption: concurrent writes are serialized externally * * Will only succeed if there is enough space for all len bytes. */ int rb_put(ring_buffer_t* buf, char* mem, size_t len) { unsigned long flags; int error = 0; read_lock_irqsave(&buf->del_lock, flags); if (!buf->kfifo) { error = -ENODEV; goto out; } if((__kfifo_put(buf->kfifo, mem, len)) < len) { error = -ENOMEM; goto out; } out: read_unlock_irqrestore(&buf->del_lock, flags); return error; } /* Assumption: concurrent reads are serialized externally */ int rb_get(ring_buffer_t* buf, char* mem, size_t len) { unsigned long flags; int error = 0; read_lock_irqsave(&buf->del_lock, flags); if (!buf->kfifo) { error = -ENODEV; goto out; } error = __kfifo_get(buf->kfifo, (unsigned char*)mem, len); out: read_unlock_irqrestore(&buf->del_lock, flags); return error; } /* * Device Driver management */ static spinlock_t log_buffer_lock = SPIN_LOCK_UNLOCKED; static trace_buffer_t log_buffer; static void init_log_buffer(void) { rb_init(&log_buffer.buf); atomic_set(&log_buffer.reader_cnt,0); init_MUTEX(&log_buffer.reader_mutex); } static DEFINE_PER_CPU(char[MSG_SIZE], fmt_buffer); /* * sched_trace_log_message - Write to the trace buffer (log_buffer) * * This is the only function accessing the log_buffer from inside the * kernel for writing. * Concurrent access to sched_trace_log_message must be serialized using * log_buffer_lock * The maximum length of a formatted message is 255 */ void sched_trace_log_message(const char* fmt, ...) { unsigned long flags; va_list args; size_t len; char* buf; va_start(args, fmt); local_irq_save(flags); /* format message */ buf = __get_cpu_var(fmt_buffer); len = vscnprintf(buf, MSG_SIZE, fmt, args); spin_lock(&log_buffer_lock); /* Don't copy the trailing null byte, we don't want null bytes * in a text file. */ rb_put(&log_buffer.buf, buf, len); spin_unlock(&log_buffer_lock); local_irq_restore(flags); va_end(args); } /* * log_read - Read the trace buffer * * This function is called as a file operation from userspace. * Readers can sleep. Access is serialized through reader_mutex */ static ssize_t log_read(struct file *filp, char __user *to, size_t len, loff_t *f_pos) { /* we ignore f_pos, this is strictly sequential */ ssize_t error = -EINVAL; char* mem; trace_buffer_t *tbuf = filp->private_data; if (down_interruptible(&tbuf->reader_mutex)) { error = -ERESTARTSYS; goto out; } if (len > MAX_READ_LEN) len = MAX_READ_LEN; mem = kmalloc(len, GFP_KERNEL); if (!mem) { error = -ENOMEM; goto out_unlock; } error = rb_get(&tbuf->buf, mem, len); while (!error) { set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(110); if (signal_pending(current)) error = -ERESTARTSYS; else error = rb_get(&tbuf->buf, mem, len); } if (error > 0 && copy_to_user(to, mem, error)) error = -EFAULT; kfree(mem); out_unlock: up(&tbuf->reader_mutex); out: return error; } /* * Enable redirection of printk() messages to the trace buffer. * Defined in kernel/printk.c */ extern int trace_override; extern int trace_recurse; /* * log_open - open the global log message ring buffer. */ static int log_open(struct inode *in, struct file *filp) { int error = -EINVAL; trace_buffer_t* tbuf; tbuf = &log_buffer; if (down_interruptible(&tbuf->reader_mutex)) { error = -ERESTARTSYS; goto out; } /* first open must allocate buffers */ if (atomic_inc_return(&tbuf->reader_cnt) == 1) { if ((error = rb_alloc_buf(&tbuf->buf, LITMUS_TRACE_BUF_SIZE))) { atomic_dec(&tbuf->reader_cnt); goto out_unlock; } } error = 0; filp->private_data = tbuf; printk(KERN_DEBUG "sched_trace kfifo at 0x%p with buffer starting at: 0x%p\n", tbuf->buf.kfifo, &((tbuf->buf.kfifo)->buffer)); /* override printk() */ trace_override++; out_unlock: up(&tbuf->reader_mutex); out: return error; } static int log_release(struct inode *in, struct file *filp) { int error = -EINVAL; trace_buffer_t* tbuf = filp->private_data; BUG_ON(!filp->private_data); if (down_interruptible(&tbuf->reader_mutex)) { error = -ERESTARTSYS; goto out; } /* last release must deallocate buffers */ if (atomic_dec_return(&tbuf->reader_cnt) == 0) { error = rb_free_buf(&tbuf->buf); } /* release printk() overriding */ trace_override--; printk(KERN_DEBUG "sched_trace kfifo released\n"); up(&tbuf->reader_mutex); out: return error; } /* * log_fops - The file operations for accessing the global LITMUS log message * buffer. * * Except for opening the device file it uses the same operations as trace_fops. */ struct file_operations log_fops = { .owner = THIS_MODULE, .open = log_open, .release = log_release, .read = log_read, }; /* * Device registration */ static int __init register_buffer_dev(const char* name, struct file_operations* fops, int major, int count) { dev_t trace_dev; struct cdev *cdev; int error = 0; trace_dev = MKDEV(major, 0); error = register_chrdev_region(trace_dev, count, name); if (error) { printk(KERN_WARNING "sched trace: " "Could not register major/minor number %d\n", major); return error; } cdev = cdev_alloc(); if (!cdev) { printk(KERN_WARNING "sched trace: " "Could not get a cdev for %s.\n", name); return -ENOMEM; } cdev->owner = THIS_MODULE; cdev->ops = fops; error = cdev_add(cdev, trace_dev, count); if (error) { printk(KERN_WARNING "sched trace: " "add_cdev failed for %s.\n", name); return -ENOMEM; } return error; } #ifdef CONFIG_MAGIC_SYSRQ void dump_trace_buffer(int max) { char line[80]; int len; int count = 0; /* potentially, but very unlikely race... */ trace_recurse = 1; while ((max == 0 || count++ < max) && (len = rb_get(&log_buffer.buf, line, sizeof(line) - 1)) > 0) { line[len] = '\0'; printk("%s", line); } trace_recurse = 0; } static void sysrq_dump_trace_buffer(int key, struct tty_struct *tty) { dump_trace_buffer(100); } static struct sysrq_key_op sysrq_dump_trace_buffer_op = { .handler = sysrq_dump_trace_buffer, .help_msg = "dump-trace-buffer(Y)", .action_msg = "writing content of TRACE() buffer", }; #endif static int __init init_sched_trace(void) { printk("Initializing TRACE() device\n"); init_log_buffer(); #ifdef CONFIG_MAGIC_SYSRQ /* offer some debugging help */ if (!register_sysrq_key('y', &sysrq_dump_trace_buffer_op)) printk("Registered dump-trace-buffer(Y) magic sysrq.\n"); else printk("Could not register dump-trace-buffer(Y) magic sysrq.\n"); #endif return register_buffer_dev("litmus_log", &log_fops, LOG_MAJOR, 1); } module_init(init_sched_trace);