Index: kernel/sched.c =================================================================== --- kernel/sched.c (.../tags/starting_base_kernel) (revision 95) +++ kernel/sched.c (.../trunk/kernel) (revision 95) @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -1392,6 +1393,8 @@ rq->prev_mm = oldmm; } + LOGSWITCH(prev,next); + /* Here we just switch the register state and the stack. */ switch_to(prev, next, prev); Index: include/linux/logdev.h =================================================================== --- include/linux/logdev.h (.../tags/starting_base_kernel) (revision 0) +++ include/linux/logdev.h (.../trunk/kernel) (revision 95) @@ -0,0 +1,148 @@ +/* + * Logdevice - A device used to record debuging information in the kernel. + * It uses a large memory ring buffer consisting of individual pages + * to keep down on hogging large sections. A user may then read the device + * to get debugging information out of it. Or if configured, this can + * be dumped to the network on a system crash. + * + * Copyright - 2005 - Steven Rostedt, Kihon Technologies, (rostedt at kihontech dot com) + */ +#ifndef _LOG_DEV_H +#define _LOG_DEV_H + +#define LOGDEV_CUSTOM 0x1afb +#define LOGDEV_SWITCH_ID 0x2afc +#define LOGDEV_PKT_ID 0x42aa +#define LOGDEV_PRINT 0x4adb +#define LOGDEV_PRINT_TIME 0x4ade + +struct logdev_switch_struct { + int id; + int size; + int cpu; + struct timeval tv; + short pid_prev; + short pid_next; + short prev_len; + short next_len; + char prev_comm[0]; + char next_comm[0]; +}; + +struct logdev_pkt { + int id; + int size; + int cpu; + struct timeval tv; + short protocol; + short dir; + char packet[0]; +}; + +struct logdev_print { + int id; + int size; + char str[0]; +}; + +struct logdev_print_time { + int id; + int size; + struct timeval tv; + char str[0]; +}; + +#ifdef __KERNEL__ +#include +#include +#include +#include + +/* + * I also use this with Ingo Molnar's RT kernel. + */ +#ifndef CONFIG_PREEMPT_RT +#undef raw_spinlock_t +#undef RAW_SPIN_LOCK_UNLOCKED +#define raw_spinlock_t spinlock_t +#define RAW_SPIN_LOCK_UNLOCKED SPIN_LOCK_UNLOCKED +#endif + +#ifdef CONFIG_LOGDEV_HOOKS +extern int (*logdev_print_hook)(const char *fmt, ...); +extern int (*logdev_print_time_hook)(const char *fmt, ...); +extern void (*logdev_record_switch_hook)(struct task_struct *a,struct task_struct *b); +extern int (*logdev_dev_hook)(int id, int size, const void *data); +extern int (*logdev_write_hook)(const char *data, int size); +extern int (*logdev_read_hook)(char *buf, int size); +extern void (*logdev_pkt_hook)(struct sk_buff *skb, int dir); +extern raw_spinlock_t logdev_add_hook_lock; +extern void (*logdev_dump_hook)(void); +extern void (*logdev_dumpnet_hook)(void); + +extern atomic_t logdev_level; +extern atomic_t logdev_switch; +extern atomic_t logdev_print_enabled; + +#define logdev_switch_on() atomic_inc(&logdev_switch) +#define logdev_switch_off() atomic_dec(&logdev_switch) + +/* + * Only exists when compiled in. + */ +#ifdef CONFIG_LOGDEV +void logdev_gettimeofday(struct timeval *tv); +#endif + +#ifndef CONFIG_LOGDEV +/* + * dumpnet is expected to be called on a bug, so we don't + * need to lock it. + */ +#define LOGDUMP() do { if (logdev_dump_hook) logdev_dump_hook(); } while(0) +#define LOGDUMPNET() do { if (logdev_dumpnet_hook) logdev_dumpnet_hook(); } while(0) + +#define LOGDEV(x,y...) do { \ + spin_lock(&logdev_add_hook_lock); \ + if (logdev_##x##_hook) \ + logdev_##x##_hook(y); \ + spin_unlock(&logdev_add_hook_lock); \ +} while (0) +#else + +void logdev_record_switch(struct task_struct *prev, struct task_struct *next); +void logdev_pkt(struct sk_buff *skb, int direction); +int logdev_print(const char *str, ...) + __attribute__ ((format (printf, 1, 2))); +int logdev_print_time(const char *str, ...) + __attribute__ ((format (printf, 1, 2))); +int logdev_record(int id, int size, const void *data); +int logdev_record_write(const char *data, int size); +int logdev_record_read(char *data, int size); +void logdev_dumpnet(void); +void logdev_dump(void); + +#define LOGDUMP() logdev_dump() +#define LOGDUMPNET() logdev_dumpnet() +#define LOGDEV(x,y...) logdev_##x(y) + +#endif +#define LOGPRINT(x...) do { if (atomic_read(&logdev_print_enabled)) LOGDEV(print,x); } while(0) +#define LOGTPRINT(x...) do { if (atomic_read(&logdev_print_enabled)) LOGDEV(print_time,x); } while(0) +#define LOGSWITCH(prev,next) LOGDEV(record_switch,prev,next) +#define LOGPKT(skb,direction) LOGDEV(pkt,skb,direction) + +#define lprint(x...) do { if (atomic_read(&logdev_switch)) { LOGPRINT(x); } } while(0) + +#else /* !LOGDEV_HOOKS */ +#define LOGDUMP() do {} while(0) +#define LOGDEV(x,y...) do {} while(0) +#define LOGSWITCH(prev,next) do {} while(0) +#define LOGPKT(skb,direction) do {} while(0) +#define logdev_switch_on() do {} while(0) +#define logdev_switch_off() do {} while(0) +#endif /* LOGDEV_HOOKS */ + +#endif + +#endif Index: lib/Kconfig.debug =================================================================== --- lib/Kconfig.debug (.../tags/starting_base_kernel) (revision 95) +++ lib/Kconfig.debug (.../trunk/kernel) (revision 95) @@ -107,6 +107,50 @@ If you're truly short on disk space or don't expect to report any bugs back to the UML developers, say N, otherwise say Y. +config LOGDEV_HOOKS + bool "Enable hooks for logdev device" + default n + help + If you plan on using the logdev device, you need to include this + into your kernel. This will add the hooks necessary for the logdev + device to be loaded. + + +config LOGDEV + tristate "Enable logdev device" + depends on LOGDEV_HOOKS + default m + help + The logdev device stores data into the kernel that can be retrieved + later through a misc device (major 10). The minor number is + dynamic and is posted through /proc/logdev_minor. Utilities + to open and read the device can be found at + http://www.kihontech.com/logdev + + This device allows for tracing lots of information in the kernel + when simply printk is too expensive. When the logdev is initialized, + it allocates a default of 1 meg of memory (in page size units). This + allows for saving data in a ring buffer without the need to allocate. + + +config LOGDEV_PAGES + int "Number of pages to allocate for logdev device" + depends on LOGDEV + default 256 + help + The Logdev device allocates a number of pages for the sole + purpose of logging data. This is the number of pages that + the Logdev device should allocate upon loading / initializing. + +config LOGDEV_PRINT_ENABLED + bool "Default Logdev prints should be enabled on startup" + depends on LOGDEV_HOOKS + default y + help + Enable this if you expect the LOGPRINT macros to be enabled + as soon as the logdev device is loaded. Otherwise you must + enable it with /proc/logdev_print + if !X86_64 config FRAME_POINTER bool "Compile the kernel with frame pointers" Index: drivers/char/logdev.c =================================================================== --- drivers/char/logdev.c (.../tags/starting_base_kernel) (revision 0) +++ drivers/char/logdev.c (.../trunk/kernel) (revision 95) @@ -0,0 +1,1191 @@ +/* + * logdev.c + * + * Copyright (C) 2004 Steven Rostedt + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License (not later!) + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +static char *logdev_version = "0.1.0"; + +#ifdef CONFIG_DEVFS_FS +static devfs_handle_t logdev_devfs; +#endif + +static int pages = CONFIG_LOGDEV_PAGES; + +module_param(pages, int, 0644); +MODULE_PARM_DESC(pages, " number of pages to allocate for the logdev ring buffer"); +MODULE_LICENSE("GPL"); + +#define LOGDEV_NAME "logdev" +#define LOGDEV_PROC_ENTRY "logdev" +#define LOGDEV_PROC_SW "logdev_switch" +#define LOGDEV_PROC_LEVEL "logdev_level" +#define LOGDEV_PROC_PRINT "logdev_print" +#define LOGDEV_PROC_MINOR "logdev_minor" + +/* + * doesn't really need to be atomic, but helps that we don't need to + * write another proc interface function. + */ +static atomic_t logdev_minor = ATOMIC_INIT(0); +static struct proc_dir_entry *logdev_proc_entry; +static struct proc_dir_entry *logdev_proc_sw; +static struct proc_dir_entry *logdev_proc_print; +static struct proc_dir_entry *logdev_proc_level; +static struct proc_dir_entry *logdev_proc_minor; + + +/* + * The testing markers are used to cut and paste into a userland program for + * easy algorithm debugging. + */ + +/********************* Start testing code here ***********************/ + +struct logdev_entry { + unsigned int head; + unsigned int tail; + char *dat; +}; + +static struct logdev_dev { + struct logdev_entry *entry; + int size; + int len; + int start; + int end; + int corrupted; + wait_queue_head_t wait; + raw_spinlock_t lock; +} logdev_dev; + +static int logdev_copy_to_dev(struct logdev_dev *dev, const void *dat, + int size); + +#define logdev_gettimeofday do_gettimeofday + +/* + * logdev_record_switch is used to track context switches. + * + * If the logdev_switch is not set, then this doesn't record. Thus allowing + * you to just record the context switches that are needed to record. + * Just use logdev_switch_on and logdev_switch_off to turn on this function. + */ +void logdev_record_switch(struct task_struct *prev, struct task_struct *next) +{ + struct logdev_switch_struct rs; + struct logdev_dev *dev; + + if (!atomic_read(&logdev_switch)) + return; + + + dev = &logdev_dev; + + rs.id = LOGDEV_SWITCH_ID; + logdev_gettimeofday(&rs.tv); + rs.cpu = smp_processor_id(); + rs.pid_prev = prev->pid; + rs.pid_next = next->pid; + rs.prev_len = strlen(prev->comm); + rs.next_len = strlen(next->comm); + rs.size = sizeof(rs) + rs.prev_len + rs.next_len; + + spin_lock(&dev->lock); + logdev_copy_to_dev(dev,&rs,sizeof(rs)); + logdev_copy_to_dev(dev,prev->comm,rs.prev_len); + logdev_copy_to_dev(dev,next->comm,rs.next_len); + spin_unlock(&dev->lock); + +} + +#ifdef __KERNEL__ /* we debug in userland but not this function */ +#include +#include +/* + * This routine may be placed in the network code if you want to see + * what packets are traveling through. You may use the direction to + * indicate if they are coming or going. + * direction = 0 : saddr ==> daddr + * 1 : saddr <== daddr + */ +void logdev_pkt(struct sk_buff *skb, int direction) +{ + struct logdev_pkt rs; + struct logdev_dev *dev; + struct tcphdr *th; + struct iphdr *iph; + unsigned long flags; + + dev = &logdev_dev; + + local_irq_save(flags); + + rs.id = LOGDEV_PKT_ID; + rs.size = sizeof(rs); + + logdev_gettimeofday(&rs.tv); + + iph = skb->nh.iph; + + rs.protocol = skb->protocol; + rs.dir = direction; + rs.cpu = smp_processor_id(); + + if ((skb->protocol != __constant_htons(ETH_P_IP)) || + (skb->pkt_type != PACKET_HOST) || + (iph->protocol != IPPROTO_TCP)) { + /* Just copy the first 20 bytes of the packet */ + + rs.size = sizeof(rs) + 20; + + spin_lock(&dev->lock); + logdev_copy_to_dev(dev,&rs,sizeof(rs)); + logdev_copy_to_dev(dev,iph,20); + spin_unlock(&dev->lock); + + } else { + int iplen; + int tcplen; + + iplen = iph->ihl<<2; + th = (struct tcphdr*)((void*)(skb->nh.iph)+(iplen)); + tcplen = th->doff<<2; + + rs.size = sizeof(rs) + iplen + tcplen; + + spin_lock(&dev->lock); + logdev_copy_to_dev(dev,&rs,sizeof(rs)); + logdev_copy_to_dev(dev,iph,iplen+tcplen); + spin_unlock(&dev->lock); + } + + + local_irq_restore(flags); +} +#endif /* endif __KERNEL__ */ + + +#define ENTRY_SIZE(e) ((((e)->tail - (e)->head)) & (PAGE_SIZE-1)) +#define ENTRY_FREE(e) ((PAGE_SIZE-1) - ENTRY_SIZE(e)) +#define ENTRY_ADD(e,x) ((e) = ((e) + x) & (PAGE_SIZE-1)) +#define ENTRY_INC(e) ENTRY_ADD(e,1) +#define ENTRY_MAX (PAGE_SIZE-1) + +/* + * User buffer is used to get data from userland, and this can sleep when + * copying. + */ +static DECLARE_RWSEM(user_sem); +static char user_buffer[PAGE_SIZE]; + +/* + * Kernel buffer is used inside the kernel, and MUST NOT SLEEP. So + * we use a raw spinlock to synchronize things. This lock must be taken first + * before the logdev lock can be taken (unless of course you don't need to + * take this lock at all). + */ +static raw_spinlock_t kern_buffer_lock = RAW_SPIN_LOCK_UNLOCKED; +static char kern_buffer[PAGE_SIZE]; + +/* + * logdev_print acts like printk but it writes to the logdev device instead + * of a console. + */ +int logdev_print(const char *str, ...) +{ + va_list va; + int len; + struct logdev_print rs; + struct logdev_dev *dev; + unsigned long flags; + + dev = &logdev_dev; + + spin_lock(&kern_buffer_lock); + + va_start(va,str); + len = vsnprintf(kern_buffer,PAGE_SIZE,str,va); + va_end(va); + + if (len > PAGE_SIZE) { + kern_buffer[PAGE_SIZE-1] = 0; + len = PAGE_SIZE; + } + + rs.id = LOGDEV_PRINT; + rs.size = sizeof(rs) + len; + + spin_lock_irqsave(&dev->lock,flags); + logdev_copy_to_dev(dev, &rs, sizeof(rs)); + logdev_copy_to_dev(dev, kern_buffer, len); + spin_unlock_irqrestore(&dev->lock,flags); + + spin_unlock(&kern_buffer_lock); + + return len; +} + +/* + * logdev_print_time is the same as logdev_print but it attaches a timestamp to it. + * saves on doing it yourself. + */ +int logdev_print_time(const char *str, ...) +{ + va_list va; + int len; + struct logdev_print_time rs; + struct logdev_dev *dev; + unsigned long flags; + + dev = &logdev_dev; + + spin_lock(&kern_buffer_lock); + + va_start(va,str); + len = vsnprintf(kern_buffer,PAGE_SIZE,str,va); + va_end(va); + + if (len > PAGE_SIZE) { + kern_buffer[PAGE_SIZE-1] = 0; + len = PAGE_SIZE; + } + + rs.id = LOGDEV_PRINT_TIME; + rs.size = sizeof(rs) + len; + logdev_gettimeofday(&rs.tv); + + spin_lock_irqsave(&dev->lock,flags); + logdev_copy_to_dev(dev, &rs, sizeof(rs)); + logdev_copy_to_dev(dev, kern_buffer, len); + spin_unlock_irqrestore(&dev->lock,flags); + + spin_unlock(&kern_buffer_lock); + + return len; +} + +int logdev_copy_from_dev(struct logdev_dev *dev, char *buf, int size) +{ + struct logdev_entry *entry = &dev->entry[dev->start]; + int ret = 0; + + if (size < 0) { + printk("logdev_copy_from_dev: size < 0 ???\n"); + return -1; + } + if (size > dev->size) + size = dev->size; + + while (size && dev->size) { + int copy = size; + int used; + + if (!ENTRY_SIZE(entry)) { + dev->start = (dev->start + 1) % dev->len; + entry = &dev->entry[dev->start]; + } + + if (copy > (used=ENTRY_SIZE(entry))) + copy = used; + if (entry->head+copy > PAGE_SIZE) + copy = PAGE_SIZE - entry->head; + memcpy(buf,entry->dat+entry->head,copy); + ENTRY_ADD(entry->head,copy); + buf += copy; + dev->size -= copy; + size -= copy; + ret += copy; + + } + + return ret; +} + +static void move_start_to_next_entry(struct logdev_dev *dev) +{ + struct logdev_entry *entry = &dev->entry[dev->start]; + int start = dev->start; + int size; + struct header { + int id; + int size; + } hdr; + + if (dev->corrupted) { + /* Don't trust headers, just skip to the next entry */ + dev->size -= ENTRY_SIZE(entry); + entry->head = entry->tail = 0; + dev->start = (dev->start+1) % dev->len; + return; + } + + while (start == dev->start) { + logdev_copy_from_dev(dev,(char*)&hdr,sizeof(hdr)); + entry = &dev->entry[dev->start]; + switch (hdr.id) { + case LOGDEV_CUSTOM: + case LOGDEV_SWITCH_ID: + case LOGDEV_PKT_ID: + case LOGDEV_PRINT: + case LOGDEV_PRINT_TIME: + break; + default: + dev->corrupted = 1; + } + if (dev->corrupted) { + if (start == dev->start) + move_start_to_next_entry(dev); + return; + } + + size = sizeof(hdr); + while (size < hdr.size) { + int count = hdr.size - size; + if (count > ENTRY_SIZE(entry)) + count = ENTRY_SIZE(entry); + dev->size -= count; + size += count; + ENTRY_ADD(entry->head,count); + if (ENTRY_SIZE(entry) == 0) { + dev->start = (dev->start + 1) % dev->len; + entry->head = entry->tail = 0; + entry = &dev->entry[dev->start]; + } + } + } +} + +static int logdev_copy_to_dev(struct logdev_dev *dev, const void *dat, + int size) +{ + struct logdev_entry *entry = &dev->entry[dev->end]; + const char *buf = dat; + int ret = 0; + + while (size) { + int copy; + int free; + if (ENTRY_SIZE(entry) == ENTRY_MAX) { + dev->end = (dev->end+1) % dev->len; + entry = &dev->entry[dev->end]; + /* if we wrapped, then clear out this entire + * buffer. + */ + if (dev->end == dev->start) { + move_start_to_next_entry(dev); + } + } + copy = size; + if (copy > (free=ENTRY_FREE(entry))) + copy = free; + if (entry->tail+copy > PAGE_SIZE) + copy = PAGE_SIZE - entry->tail; + memcpy(entry->dat+entry->tail,buf,copy); + ENTRY_ADD(entry->tail,copy); + buf += copy; + size -= copy; + ret += copy; + dev->size += copy; + + } +#if 0 + /* wake up those waiting for data */ + if (waitqueue_active(&dev->wait)) + wake_up_interruptible(&dev->wait); +#endif + + return ret; + +} + + +/* + * If you feel like recording your own data, you can use logdev_record. + * just pass your own id, size and data. The size is the size of + * the data being passed and not the size actually being written to the device. + * That is already calculated. + * + * It is recommended to use LOGDEV_CUSTOM as the id so that you don't corrupt + * the ring buffer (corrupt is as strong word since it still works, just not so + * automatic). And you should have you data have some sort of tag to figure out + * what it is later. + */ +int logdev_record(int id, int size, const void *data) +{ + struct logdev_dev *dev; + int ret; + int rsize = size + sizeof(rsize) + sizeof(id); + unsigned long flags; + + dev = &logdev_dev; + + spin_lock_irqsave(&dev->lock,flags); + logdev_copy_to_dev(dev, &id, sizeof(id)); + logdev_copy_to_dev(dev, &rsize, sizeof(rsize)); + ret = logdev_copy_to_dev(dev, data, size); + spin_unlock_irqrestore(&dev->lock,flags); + + return ret; +} + +/* + * If you just want to write into the buffer using your own methods, then this + * is perfectly fine. Just pass in your data and the size of the data being + * passed in. You can read it out later with logdev_record_read. But you wont + * have the benefits of keeping integrity when the buffer overflows. + */ +int logdev_record_write(const char *data, int size) +{ + struct logdev_dev *dev; + int ret; + unsigned long flags; + + dev = &logdev_dev; + + spin_lock_irqsave(&dev->lock,flags); + ret = logdev_copy_to_dev(dev, data, size); + spin_unlock_irqrestore(&dev->lock,flags); + + return ret; +} + +/* + * logdev_record_read reads some data from the logdev device no matter what + * it was. + */ +int logdev_record_read(char *data, int size) +{ + struct logdev_dev *dev; + int ret; + unsigned long flags; + + dev = &logdev_dev; + + spin_lock_irqsave(&dev->lock,flags); + ret = logdev_copy_from_dev(dev, data, size); + spin_unlock_irqrestore(&dev->lock,flags); + + return ret; +} + +/************************ Stop testing code here ******************************/ + +ssize_t logdev_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) +{ + struct logdev_dev *dev = (struct logdev_dev*)filp->private_data; + unsigned long flags; + + if (count > PAGE_SIZE) + count = PAGE_SIZE; + + down_write(&user_sem); + + if (copy_from_user(user_buffer,buf,count)) { + count = -EFAULT; + goto out; + } + + spin_lock_irqsave(&dev->lock,flags); + count = logdev_copy_to_dev(dev,user_buffer,count); + spin_unlock_irqrestore(&dev->lock,flags); + + /* wake up those waiting for data */ + if (waitqueue_active(&dev->wait)) + wake_up_interruptible(&dev->wait); + +out: + up_write(&user_sem); + return count; +} + +ssize_t logdev_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) +{ + struct logdev_dev *dev = (struct logdev_dev*)filp->private_data; + unsigned long flags; + + down_write(&user_sem); + spin_lock_irqsave(&dev->lock,flags); + + if (!dev->size) { + + /* TBD - FIXME */ +#if 1 + + count = 0; + goto out; +#endif + + if (filp->f_flags & O_NONBLOCK) { + count = -EAGAIN; + goto out; + } + + do { + DECLARE_WAITQUEUE(wait,current); + current->state = TASK_INTERRUPTIBLE; + add_wait_queue(&dev->wait,&wait); + spin_unlock_irqrestore(&dev->lock,flags); + schedule(); + spin_lock_irqsave(&dev->lock,flags); + remove_wait_queue(&dev->wait,&wait); + if (dev->size) + break; + if (signal_pending(current)) { + count = -ERESTARTSYS; + goto out; + } + } while(1); + } + + if (count > PAGE_SIZE) + count = PAGE_SIZE; + + + count = logdev_copy_from_dev(dev,user_buffer,count); + + out: + /* We can't be corrupted if we have no data */ + if (!dev->size) + dev->corrupted = 0; + spin_unlock_irqrestore(&dev->lock,flags); + + if (count > 0) { + /* Well if we fail here, we just lost the data read :-( */ + if (copy_to_user(buf,user_buffer,count)) + count = -EFAULT; + } + up_write(&user_sem); + + return count; +} + + +static int logdev_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + return -ENOTTY; +} + +static int logdev_close(struct inode *inode, struct file *filp) +{ +#if 0 + struct logdev_dev *dev = (struct logdev_dev*)filp->private_data; +#endif + return 0; +} + + +static int logdev_open (struct inode *inode, struct file *filp) +{ + struct logdev_dev *dev; + + dev = &logdev_dev; + + filp->private_data = dev; + + return 0; +} + +static struct file_operations logdev_fops = { + .read = logdev_read, + .write = logdev_write, + .ioctl = logdev_ioctl, + .open = logdev_open, + .release = logdev_close, + .llseek = no_llseek, +}; + +/* + * The following UDP sending is based off of netconsole. + */ +static char config[256]; +module_param_string(logdevnet, config, 256, 0); +MODULE_PARM_DESC(logdevnet, " logdevnet=[src-port]@[src-ip]/[dev],[tgt-port]@/[tgt-macaddr]\n"); + +static struct netpoll np = { + .name = "logdevnet", + .dev_name = "eth0", + .local_port = 6665, + .remote_port = 6666, + .remote_mac = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, +}; +static int configured = 0; + +#define MAX_PRINT_CHUNK 1000 + +static void write_msg(const char *msg, unsigned int len) +{ + int frag, left; + unsigned long flags; + + if (!np.dev) + return; + + local_irq_save(flags); +#ifdef CONFIG_PREEMPT_RT + /* + * A bit hairy. Netconsole uses mutexes (indirectly) and + * thus must have interrupts enabled: + */ + if (0) local_irq_enable(); +#endif + + for(left = len; left; ) { + frag = min(left, MAX_PRINT_CHUNK); + netpoll_send_udp(&np, msg, frag); + msg += frag; + left -= frag; + } + + local_irq_restore(flags); +} + +static int option_setup(char *opt) +{ + configured = !netpoll_parse_options(&np, opt); + return 0; +} + +__setup("logdevnet=", option_setup); + +static void __logdev_dump(int net) +{ + int r; + int i; + int count; + char *msg; + struct logdev_dev *dev; + struct header { + int id; + int size; + } hdr; + static int started = 0; + int corrupt = 0; + + if (started) + return; + + if (!configured) + return; + + dev = &logdev_dev; + + if (dev->corrupted) { + msg = ">>>>> Warning: Logdev corrupted <<<<<\n"; + i = strlen(msg); + if (net) + write_msg(msg,i); + else + printk(msg); + } + + while ((r = logdev_copy_from_dev(dev,(char *)&hdr,sizeof(hdr))) == sizeof(hdr)) { + switch (hdr.id) { + case LOGDEV_PRINT: + for (i=r; i < hdr.size; i += r) { + count = hdr.size - i; + if (count > PAGE_SIZE-1) + count = PAGE_SIZE-1; + r = logdev_copy_from_dev(dev,user_buffer,count); + if (r < 0) + break; + user_buffer[count] = 0; + if (net) + write_msg(user_buffer,count); + else + printk(user_buffer); + } + break; + case LOGDEV_SWITCH_ID: + { + struct logdev_switch_struct rs; + memcpy(&rs,&hdr,sizeof(hdr)); + + msg = ">>>> IN LOGDEV SWITCH <<<<\n"; + i = strlen(msg); + if (net) + write_msg(msg,i); + else + printk(msg); + + logdev_copy_from_dev(dev,((char*)&rs)+sizeof(hdr),sizeof(rs) - sizeof(hdr)); + i = snprintf(user_buffer,100,"%d:%06d ", + (int)rs.tv.tv_sec,(int)rs.tv.tv_usec); + logdev_copy_from_dev(dev,user_buffer+i,rs.prev_len); + i += rs.prev_len; + + i += snprintf(user_buffer+i,100,":%d -->> ",rs.pid_prev); + logdev_copy_from_dev(dev,user_buffer+i,rs.next_len); + i += rs.next_len; + i += snprintf(user_buffer+i,100,":%d\n",rs.pid_next); + + if (net) + write_msg(user_buffer,i); + else + printk(user_buffer); + + break; + } + + case LOGDEV_CUSTOM: + case LOGDEV_PKT_ID: + case LOGDEV_PRINT_TIME: + switch (hdr.id) { + case LOGDEV_CUSTOM: + msg = "skipping! LOGDEV_CUSTOM\n"; + break; + case LOGDEV_PKT_ID: + msg = "skipping! LOGDEV_PKT_ID\n"; + break; + case LOGDEV_PRINT_TIME: + msg = "skipping! LOGDEV_PRINT_TIME\n"; + break; + default: + msg = "skipping! ????\n"; + break; + } + i = strlen(msg); + if (net) + write_msg(msg,i); + else + printk(msg); + for (i = r; i < hdr.size; i+= r) { + count = hdr.size - i; + if (count > PAGE_SIZE) + count = PAGE_SIZE; + r = logdev_copy_from_dev(dev,user_buffer,count); + } + break; + + default: + if (!corrupt) { + corrupt = 1; + i = snprintf(user_buffer,100,">>>>> Unknown logdev header, may be " + "corrupted from this point on\n"); + if (net) + write_msg(user_buffer,i); + else + printk(user_buffer); + } + + break; + } + } + msg = ">>>>> done <<<<<\n"; + i = strlen(msg); + if (net) + write_msg(msg,i); + else + printk(msg); + started = 0; +} + +void logdev_dumpnet(void) +{ + __logdev_dump(1); +} + +void logdev_dump(void) +{ + __logdev_dump(0); +} + +int __init logdevnet_init(void) +{ + if(strlen(config)) + option_setup(config); + /* + * Don't need to check the return code on this, + * if it fails then we just don't use the network. + */ + if (configured) { + if (netpoll_setup(&np)) { + printk("logdev: warning, netpoll failed to setup\n"); + } + } + + return 0; +} + +#if 1 +static int atoi(const char *p) +{ + int n = 0; + while (*p && '0' <= *p && *p <= '9') { + n *= 10; + n += *p - '0'; + p++; + } + return (n); +} + +static int proc_var_read(char *buffer, char **start, off_t offset, int count, + int *eof, void *data) +{ + int var; + int len; + + var = atomic_read((atomic_t*)data); + + len = sprintf(buffer,"%d\n",var); + + if (offset >= len) { + *start = buffer; + *eof = 1; + return 0; + } + *start = buffer + offset; + if ((len -= offset) > count) + return count; + *eof = 1; + return len; +} + +static int proc_var_write(struct file * file, const char * buffer, + unsigned long count, void *data) +{ + atomic_t *var; + int val; + char buf[10]; + + var = (atomic_t*)data; + + if (count > 9) + count = 9; + + if(copy_from_user(&buf, buffer, count)) + return -EFAULT; + + buf[count] = 0; + + val = atoi(buf); + + atomic_set(var,val); + + file->f_pos += count; + + return count; + +} +#endif + +static void *s_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct logdev_dev *dev = m->private; + int i = (int)*pos; + + (*pos)++; + + if (i >= dev->len) + return NULL; + + return &dev->entry[i]; +} + +static void *s_start(struct seq_file *m, loff_t *pos) + __acquires(logdev_dev.lock) +{ + void *p = NULL; + loff_t l = 0; + + /* + * A little strong? Perhaps, but we know that this is bad right + * from the start. Anyway this is for debugging purposes only, + * so it's OK, as well as the big latency we get by turning off + * intrerrupts. But we also never know who will be locking + * this. + */ + if (irqs_disabled()) + BUG(); + + local_irq_disable(); + spin_lock(&logdev_dev.lock); + for (p = (void *)1; p && l < *pos; p = s_next(m,p,&l)) + ; + + return p; +} + +static void s_stop(struct seq_file *m, void *p) + __releases(logdev_dev.lock) +{ + spin_unlock(&logdev_dev.lock); + local_irq_enable(); +} + +static int s_show(struct seq_file *m, void *v) +{ + int i = (int)(v); + struct logdev_entry *entry = v; + struct logdev_dev *dev; + + dev = &logdev_dev; + + if (i == 1) { + seq_printf(m,"Logdev:\n"); + seq_printf(m,"\tlen:\t%d\n",dev->len); + seq_printf(m,"\tsize:\t%d\n",dev->size); + seq_printf(m,"\tstart:\t%d\n",dev->start); + seq_printf(m,"\tend:\t%d\n",dev->end); + seq_printf(m,"\tcorrupted:%d\n",dev->corrupted); + seq_printf(m,"\n\tEntries:\n"); + + } else { + i = (int)((char*)entry - (char*)dev->entry) / sizeof(struct logdev_entry); + + seq_printf(m,"\t %d:\t%8u : %8u\tsize: %lu\n", i, + entry->head, entry->tail, + ENTRY_SIZE(entry)); + } + + + return 0; +} + +static struct seq_operations logdev_proc_op = { + .start = s_start, + .next = s_next, + .stop = s_stop, + .show = s_show, +}; + +static int logdev_proc_open (struct inode *inode, struct file *file) +{ + int ret; + + ret = seq_open(file, &logdev_proc_op); + if (!ret) { + struct seq_file *m = file->private_data; + m->private = &logdev_dev; + } + + return ret; +} + + +static struct file_operations logdev_proc_operations = { + .open = logdev_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + + +static void logdev_proc_setup(void) +{ + struct proc_dir_entry *entry; + + entry = create_proc_entry(LOGDEV_PROC_ENTRY, + S_IFREG | S_IRUGO, NULL); + if (!entry) + return; + + logdev_proc_entry = entry; + entry->proc_fops = &logdev_proc_operations; + + entry = create_proc_entry(LOGDEV_PROC_SW, + S_IFREG | S_IRUGO | S_IWUGO, + NULL); + if (entry) { + entry->read_proc = proc_var_read; + entry->write_proc = proc_var_write; + entry->data = &logdev_switch; + logdev_proc_sw = entry; + } + entry = create_proc_entry(LOGDEV_PROC_LEVEL, + S_IFREG | S_IRUGO | S_IWUGO, + NULL); + if (entry) { + entry->read_proc = proc_var_read; + entry->write_proc = proc_var_write; + entry->data = &logdev_level; + logdev_proc_level = entry; + } + + entry = create_proc_entry(LOGDEV_PROC_PRINT, + S_IFREG | S_IRUGO | S_IWUGO, + NULL); + if (entry) { + entry->read_proc = proc_var_read; + entry->write_proc = proc_var_write; + entry->data = &logdev_print_enabled; + logdev_proc_print = entry; + } + + entry = create_proc_entry(LOGDEV_PROC_MINOR, + S_IFREG | S_IRUGO, + NULL); + if (entry) { + entry->read_proc = proc_var_read; + entry->data = &logdev_minor; + logdev_proc_minor = entry; + } + +} + + +static struct miscdevice logdev_misc_dev = +{ + .minor = MISC_DYNAMIC_MINOR, + .name = "logdev", + .fops = &logdev_fops +}; + +void __exit logdev_cleanup(void) +{ + int i; + +#ifdef MODULE + spin_lock(&logdev_dev.lock); + logdev_record_switch_hook = NULL; + logdev_pkt_hook = NULL; + logdev_print_hook = NULL; + logdev_print_time_hook = NULL; + logdev_dev_hook = NULL; + logdev_write_hook = NULL; + logdev_read_hook = NULL; + spin_unlock(&logdev_dev.lock); +#endif + + if (logdev_proc_entry) + remove_proc_entry(LOGDEV_PROC_ENTRY,0); + if (logdev_proc_sw) + remove_proc_entry(LOGDEV_PROC_SW,0); + if (logdev_proc_level) + remove_proc_entry(LOGDEV_PROC_LEVEL,0); + if (logdev_proc_minor) + remove_proc_entry(LOGDEV_PROC_MINOR,0); + if (logdev_proc_print) + remove_proc_entry(LOGDEV_PROC_PRINT,0); + + if (logdev_dev.entry) { + struct logdev_entry *entry; + for (i=0, entry=logdev_dev.entry; idat) { + free_page((unsigned long)entry->dat); + entry->dat = NULL; + } + logdev_dev.len = 0; + kfree(logdev_dev.entry); + logdev_dev.entry = NULL; + } + + misc_deregister(&logdev_misc_dev); + +} + +int __init logdev_init(void) +{ + int res = 0; + int i; + struct logdev_entry *entry; + + printk("Logdevice: copyright Steven Rostedt, Kihon Technologies Inc." + " (Version %s)\n", + logdev_version); + + res = misc_register(&logdev_misc_dev); + if (res) + goto out; + atomic_set(&logdev_minor,logdev_misc_dev.minor); + + logdev_dev.len = pages; + + res = -ENOMEM; + logdev_dev.entry = kmalloc(sizeof(struct logdev_entry)*logdev_dev.len,GFP_KERNEL); + if (!logdev_dev.entry) + goto fail; + + memset(logdev_dev.entry,0,sizeof(*logdev_dev.entry)*logdev_dev.len); + + for (i=0,entry=logdev_dev.entry; idat = (void*)__get_free_pages(GFP_KERNEL,0); + if (!entry->dat) + goto fail; + } + + logdev_proc_setup(); + + init_waitqueue_head(&logdev_dev.wait); + spin_lock_init(&logdev_dev.lock); + + res = 0; + + +#ifdef MODULE + /* Make hooks last! */ + spin_lock(&logdev_dev.lock); + logdev_record_switch_hook = logdev_record_switch; + logdev_pkt_hook = logdev_pkt; + logdev_print_hook = logdev_print; + logdev_print_time_hook = logdev_print_time; + logdev_dev_hook = logdev_record; + logdev_write_hook = logdev_record_write; + logdev_read_hook = logdev_record_read; + logdev_dumpnet_hook = logdev_dumpnet; + logdev_dump_hook = logdev_dump; + spin_unlock(&logdev_dev.lock); + + logdevnet_init(); +#endif + +out: + return res; + +fail: + logdev_cleanup(); + goto out; +} + +#ifndef MODULE +EXPORT_SYMBOL(logdev_record_switch); +EXPORT_SYMBOL(logdev_pkt); +EXPORT_SYMBOL(logdev_print); +EXPORT_SYMBOL(logdev_print_time); +EXPORT_SYMBOL(logdev_record); +EXPORT_SYMBOL(logdev_record_write); +EXPORT_SYMBOL(logdev_record_read); +EXPORT_SYMBOL(logdev_dumpnet); +EXPORT_SYMBOL(logdev_dump); + +late_initcall(logdevnet_init); +#endif + +module_init(logdev_init); +module_exit(logdev_cleanup); Index: drivers/char/logdev_hooks.c =================================================================== --- drivers/char/logdev_hooks.c (.../tags/starting_base_kernel) (revision 0) +++ drivers/char/logdev_hooks.c (.../trunk/kernel) (revision 95) @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include + +/* + * Only need to add this is LOGDEV is defined as a module + */ +#ifndef CONFIG_LOGDEV +int (*logdev_print_hook)(const char *fmt, ...); +int (*logdev_print_time_hook)(const char *fmt, ...); +void (*logdev_record_switch_hook)(struct task_struct *a,struct task_struct *b); +int (*logdev_dev_hook)(int id, int size, const void *data); +int (*logdev_write_hook)(const char *data, int size); +int (*logdev_read_hook)(char *buf, int size); +void (*logdev_pkt_hook)(struct sk_buff *skb, int dir); +void (*logdev_dump_hook)(void); +void (*logdev_dumpnet_hook)(void); + +EXPORT_SYMBOL(logdev_print_hook); +EXPORT_SYMBOL(logdev_print_time_hook); +EXPORT_SYMBOL(logdev_record_switch_hook); +EXPORT_SYMBOL(logdev_dev_hook); +EXPORT_SYMBOL(logdev_write_hook); +EXPORT_SYMBOL(logdev_read_hook); +EXPORT_SYMBOL(logdev_pkt_hook); +EXPORT_SYMBOL(logdev_dumpnet_hook); +EXPORT_SYMBOL(logdev_dump_hook); +EXPORT_SYMBOL(logdev_add_hook_lock); + +void logdev_add_hook(void **hook, void *func) +{ + spin_lock(&logdev_add_hook_lock); + *hook = func; + spin_unlock(&logdev_add_hook_lock); +} + +void logdev_remove_hook(void **hook) +{ + spin_lock(&logdev_add_hook_lock); + *hook = NULL; + spin_unlock(&logdev_add_hook_lock); +} +#endif + +atomic_t logdev_level = ATOMIC_INIT(0); +atomic_t logdev_switch = ATOMIC_INIT(0); +#ifdef CONFIG_LOGDEV_PRINT_ENABLED +atomic_t logdev_print_enabled = ATOMIC_INIT(1); +#else +atomic_t logdev_print_enabled = ATOMIC_INIT(0); +#endif + +raw_spinlock_t logdev_add_hook_lock = RAW_SPIN_LOCK_UNLOCKED; + +EXPORT_SYMBOL(logdev_switch); +EXPORT_SYMBOL(logdev_level); +EXPORT_SYMBOL(logdev_print_enabled); Index: drivers/char/Makefile =================================================================== --- drivers/char/Makefile (.../tags/starting_base_kernel) (revision 95) +++ drivers/char/Makefile (.../trunk/kernel) (revision 95) @@ -89,6 +89,9 @@ obj-$(CONFIG_PCMCIA) += pcmcia/ obj-$(CONFIG_IPMI_HANDLER) += ipmi/ +obj-$(CONFIG_LOGDEV_HOOKS) += logdev_hooks.o +obj-$(CONFIG_LOGDEV) += logdev.o + obj-$(CONFIG_HANGCHECK_TIMER) += hangcheck-timer.o # Files generated that shall be removed upon make clean