/* * c 2001 PPC 64 Team, IBM Corp * * 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; either version * 2 of the License, or (at your option) any later version. * * scan-log-data driver for PPC64 Todd Inglett <tinglett@vnet.ibm.com> * * When ppc64 hardware fails the service processor dumps internal state * of the system. After a reboot the operating system can access a dump * of this data using this driver. A dump exists if the device-tree * /chosen/ibm,scan-log-data property exists. * * This driver exports /proc/ppc64/scan-log-dump which can be read. * The driver supports only sequential reads. * * The driver looks at a write to the driver for the single word "reset". * If given, the driver will reset the scanlog so the platform can free it. */ #include <linux/module.h> #include <linux/types.h> #include <linux/errno.h> #include <linux/proc_fs.h> #include <linux/init.h> #include <asm/uaccess.h> #include <asm/rtas.h> #include <asm/prom.h> #define MODULE_VERS "1.0" #define MODULE_NAME "scanlog" /* Status returns from ibm,scan-log-dump */ #define SCANLOG_COMPLETE 0 #define SCANLOG_HWERROR -1 #define SCANLOG_CONTINUE 1 #define DEBUG(A...) do { if (scanlog_debug) printk(KERN_ERR "scanlog: " A); } while (0) static int scanlog_debug; static unsigned int ibm_scan_log_dump; /* RTAS token */ static struct proc_dir_entry *proc_ppc64_scan_log_dump; /* The proc file */ static ssize_t scanlog_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct inode * inode = file->f_dentry->d_inode; struct proc_dir_entry *dp; unsigned int *data; int status; unsigned long len, off; unsigned int wait_time; dp = PDE(inode); data = (unsigned int *)dp->data; if (!data) { printk(KERN_ERR "scanlog: read failed no data\n"); return -EIO; } if (count > RTAS_DATA_BUF_SIZE) count = RTAS_DATA_BUF_SIZE; if (count < 1024) { /* This is the min supported by this RTAS call. Rather * than do all the buffering we insist the user code handle * larger reads. As long as cp works... :) */ printk(KERN_ERR "scanlog: cannot perform a small read (%ld)\n", count); return -EINVAL; } if (!access_ok(VERIFY_WRITE, buf, count)) return -EFAULT; for (;;) { wait_time = HZ/2; /* default wait if no data */ spin_lock(&rtas_data_buf_lock); memcpy(rtas_data_buf, data, RTAS_DATA_BUF_SIZE); status = rtas_call(ibm_scan_log_dump, 2, 1, NULL, (u32) __pa(rtas_data_buf), (u32) count); memcpy(data, rtas_data_buf, RTAS_DATA_BUF_SIZE); spin_unlock(&rtas_data_buf_lock); DEBUG("status=%d, data[0]=%x, data[1]=%x, data[2]=%x\n", status, data[0], data[1], data[2]); switch (status) { case SCANLOG_COMPLETE: DEBUG("hit eof\n"); return 0; case SCANLOG_HWERROR: DEBUG("hardware error reading scan log data\n"); return -EIO; case SCANLOG_CONTINUE: /* We may or may not have data yet */ len = data[1]; off = data[2]; if (len > 0) { if (copy_to_user(buf, ((char *)data)+off, len)) return -EFAULT; return len; } /* Break to sleep default time */ break; default: if (status > 9900 && status <= 9905) { /* No data. RTAS is hinting at a delay required * between 1-100000 milliseconds */ int ms = 1; for (; status > 9900; status--) ms = ms * 10; /* Use microseconds for reasonable accuracy */ ms *= 1000; wait_time = ms / (1000000/HZ); /* round down is fine */ /* Fall through to sleep */ } else { printk(KERN_ERR "scanlog: unknown error from rtas: %d\n", status); return -EIO; } } /* Apparently no data yet. Wait and try again. */ set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(wait_time); } /*NOTREACHED*/ } static ssize_t scanlog_write(struct file * file, const char __user * buf, size_t count, loff_t *ppos) { char stkbuf[20]; int status; if (count > 19) count = 19; if (copy_from_user (stkbuf, buf, count)) { return -EFAULT; } stkbuf[count] = 0; if (buf) { if (strncmp(stkbuf, "reset", 5) == 0) { DEBUG("reset scanlog\n"); status = rtas_call(ibm_scan_log_dump, 2, 1, NULL, 0, 0); DEBUG("rtas returns %d\n", status); } else if (strncmp(stkbuf, "debugon", 7) == 0) { printk(KERN_ERR "scanlog: debug on\n"); scanlog_debug = 1; } else if (strncmp(stkbuf, "debugoff", 8) == 0) { printk(KERN_ERR "scanlog: debug off\n"); scanlog_debug = 0; } } return count; } static int scanlog_open(struct inode * inode, struct file * file) { struct proc_dir_entry *dp = PDE(inode); unsigned int *data = (unsigned int *)dp->data; if (!data) { printk(KERN_ERR "scanlog: open failed no data\n"); return -EIO; } if (data[0] != 0) { /* This imperfect test stops a second copy of the * data (or a reset while data is being copied) */ return -EBUSY; } data[0] = 0; /* re-init so we restart the scan */ return 0; } static int scanlog_release(struct inode * inode, struct file * file) { struct proc_dir_entry *dp = PDE(inode); unsigned int *data = (unsigned int *)dp->data; if (!data) { printk(KERN_ERR "scanlog: release failed no data\n"); return -EIO; } data[0] = 0; return 0; } struct file_operations scanlog_fops = { .owner = THIS_MODULE, .read = scanlog_read, .write = scanlog_write, .open = scanlog_open, .release = scanlog_release, }; int __init scanlog_init(void) { struct proc_dir_entry *ent; ibm_scan_log_dump = rtas_token("ibm,scan-log-dump"); if (ibm_scan_log_dump == RTAS_UNKNOWN_SERVICE) { printk(KERN_ERR "scan-log-dump not implemented on this system\n"); return -EIO; } ent = create_proc_entry("ppc64/rtas/scan-log-dump", S_IRUSR, NULL); if (ent) { ent->proc_fops = &scanlog_fops; /* Ideally we could allocate a buffer < 4G */ ent->data = kmalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL); if (!ent->data) { printk(KERN_ERR "Failed to allocate a buffer\n"); remove_proc_entry("scan-log-dump", ent->parent); return -ENOMEM; } ((unsigned int *)ent->data)[0] = 0; } else { printk(KERN_ERR "Failed to create ppc64/scan-log-dump proc entry\n"); return -EIO; } proc_ppc64_scan_log_dump = ent; return 0; } void __exit scanlog_cleanup(void) { if (proc_ppc64_scan_log_dump) { if (proc_ppc64_scan_log_dump->data) kfree(proc_ppc64_scan_log_dump->data); remove_proc_entry("scan-log-dump", proc_ppc64_scan_log_dump->parent); } } module_init(scanlog_init); module_exit(scanlog_cleanup); MODULE_LICENSE("GPL");