Merge branch 'master' of master.kernel.org:/pub/scm/linux/kernel/git/davem/net-2.6
[pandora-kernel.git] / arch / powerpc / platforms / pseries / nvram.c
index bc3c7f2..7e828ba 100644 (file)
 #include <asm/prom.h>
 #include <asm/machdep.h>
 
+/* Max bytes to read/write in one go */
+#define NVRW_CNT 0x20
+
 static unsigned int nvram_size;
 static int nvram_fetch, nvram_store;
 static char nvram_buf[NVRW_CNT];       /* assume this is in the first 4GB */
 static DEFINE_SPINLOCK(nvram_lock);
 
+static long nvram_error_log_index = -1;
+static long nvram_error_log_size = 0;
+
+struct err_log_info {
+       int error_type;
+       unsigned int seq_num;
+};
+#define NVRAM_MAX_REQ          2079
+#define NVRAM_MIN_REQ          1055
+
+#define NVRAM_LOG_PART_NAME    "ibm,rtas-log"
 
 static ssize_t pSeries_nvram_read(char *buf, size_t count, loff_t *index)
 {
@@ -119,6 +133,197 @@ static ssize_t pSeries_nvram_get_size(void)
        return nvram_size ? nvram_size : -ENODEV;
 }
 
+
+/* nvram_write_error_log
+ *
+ * We need to buffer the error logs into nvram to ensure that we have
+ * the failure information to decode.  If we have a severe error there
+ * is no way to guarantee that the OS or the machine is in a state to
+ * get back to user land and write the error to disk.  For example if
+ * the SCSI device driver causes a Machine Check by writing to a bad
+ * IO address, there is no way of guaranteeing that the device driver
+ * is in any state that is would also be able to write the error data
+ * captured to disk, thus we buffer it in NVRAM for analysis on the
+ * next boot.
+ *
+ * In NVRAM the partition containing the error log buffer will looks like:
+ * Header (in bytes):
+ * +-----------+----------+--------+------------+------------------+
+ * | signature | checksum | length | name       | data             |
+ * |0          |1         |2      3|4         15|16        length-1|
+ * +-----------+----------+--------+------------+------------------+
+ *
+ * The 'data' section would look like (in bytes):
+ * +--------------+------------+-----------------------------------+
+ * | event_logged | sequence # | error log                         |
+ * |0            3|4          7|8            nvram_error_log_size-1|
+ * +--------------+------------+-----------------------------------+
+ *
+ * event_logged: 0 if event has not been logged to syslog, 1 if it has
+ * sequence #: The unique sequence # for each event. (until it wraps)
+ * error log: The error log from event_scan
+ */
+int nvram_write_error_log(char * buff, int length,
+                          unsigned int err_type, unsigned int error_log_cnt)
+{
+       int rc;
+       loff_t tmp_index;
+       struct err_log_info info;
+       
+       if (nvram_error_log_index == -1) {
+               return -ESPIPE;
+       }
+
+       if (length > nvram_error_log_size) {
+               length = nvram_error_log_size;
+       }
+
+       info.error_type = err_type;
+       info.seq_num = error_log_cnt;
+
+       tmp_index = nvram_error_log_index;
+
+       rc = ppc_md.nvram_write((char *)&info, sizeof(struct err_log_info), &tmp_index);
+       if (rc <= 0) {
+               printk(KERN_ERR "nvram_write_error_log: Failed nvram_write (%d)\n", rc);
+               return rc;
+       }
+
+       rc = ppc_md.nvram_write(buff, length, &tmp_index);
+       if (rc <= 0) {
+               printk(KERN_ERR "nvram_write_error_log: Failed nvram_write (%d)\n", rc);
+               return rc;
+       }
+       
+       return 0;
+}
+
+/* nvram_read_error_log
+ *
+ * Reads nvram for error log for at most 'length'
+ */
+int nvram_read_error_log(char * buff, int length,
+                         unsigned int * err_type, unsigned int * error_log_cnt)
+{
+       int rc;
+       loff_t tmp_index;
+       struct err_log_info info;
+       
+       if (nvram_error_log_index == -1)
+               return -1;
+
+       if (length > nvram_error_log_size)
+               length = nvram_error_log_size;
+
+       tmp_index = nvram_error_log_index;
+
+       rc = ppc_md.nvram_read((char *)&info, sizeof(struct err_log_info), &tmp_index);
+       if (rc <= 0) {
+               printk(KERN_ERR "nvram_read_error_log: Failed nvram_read (%d)\n", rc);
+               return rc;
+       }
+
+       rc = ppc_md.nvram_read(buff, length, &tmp_index);
+       if (rc <= 0) {
+               printk(KERN_ERR "nvram_read_error_log: Failed nvram_read (%d)\n", rc);
+               return rc;
+       }
+
+       *error_log_cnt = info.seq_num;
+       *err_type = info.error_type;
+
+       return 0;
+}
+
+/* This doesn't actually zero anything, but it sets the event_logged
+ * word to tell that this event is safely in syslog.
+ */
+int nvram_clear_error_log(void)
+{
+       loff_t tmp_index;
+       int clear_word = ERR_FLAG_ALREADY_LOGGED;
+       int rc;
+
+       if (nvram_error_log_index == -1)
+               return -1;
+
+       tmp_index = nvram_error_log_index;
+       
+       rc = ppc_md.nvram_write((char *)&clear_word, sizeof(int), &tmp_index);
+       if (rc <= 0) {
+               printk(KERN_ERR "nvram_clear_error_log: Failed nvram_write (%d)\n", rc);
+               return rc;
+       }
+
+       return 0;
+}
+
+/* pseries_nvram_init_log_partition
+ *
+ * This will setup the partition we need for buffering the
+ * error logs and cleanup partitions if needed.
+ *
+ * The general strategy is the following:
+ * 1.) If there is log partition large enough then use it.
+ * 2.) If there is none large enough, search
+ * for a free partition that is large enough.
+ * 3.) If there is not a free partition large enough remove 
+ * _all_ OS partitions and consolidate the space.
+ * 4.) Will first try getting a chunk that will satisfy the maximum
+ * error log size (NVRAM_MAX_REQ).
+ * 5.) If the max chunk cannot be allocated then try finding a chunk
+ * that will satisfy the minum needed (NVRAM_MIN_REQ).
+ */
+static int __init pseries_nvram_init_log_partition(void)
+{
+       loff_t p;
+       int size;
+
+       /* Scan nvram for partitions */
+       nvram_scan_partitions();
+
+       /* Lookg for ours */
+       p = nvram_find_partition(NVRAM_LOG_PART_NAME, NVRAM_SIG_OS, &size);
+
+       /* Found one but too small, remove it */
+       if (p && size < NVRAM_MIN_REQ) {
+               pr_info("nvram: Found too small "NVRAM_LOG_PART_NAME" partition"
+                       ",removing it...");
+               nvram_remove_partition(NVRAM_LOG_PART_NAME, NVRAM_SIG_OS);
+               p = 0;
+       }
+
+       /* Create one if we didn't find */
+       if (!p) {
+               p = nvram_create_partition(NVRAM_LOG_PART_NAME, NVRAM_SIG_OS,
+                                          NVRAM_MAX_REQ, NVRAM_MIN_REQ);
+               /* No room for it, try to get rid of any OS partition
+                * and try again
+                */
+               if (p == -ENOSPC) {
+                       pr_info("nvram: No room to create "NVRAM_LOG_PART_NAME
+                               " partition, deleting all OS partitions...");
+                       nvram_remove_partition(NULL, NVRAM_SIG_OS);
+                       p = nvram_create_partition(NVRAM_LOG_PART_NAME,
+                                                  NVRAM_SIG_OS, NVRAM_MAX_REQ,
+                                                  NVRAM_MIN_REQ);
+               }
+       }
+
+       if (p <= 0) {
+               pr_err("nvram: Failed to find or create "NVRAM_LOG_PART_NAME
+                      " partition, err %d\n", (int)p);
+               return 0;
+       }
+
+       nvram_error_log_index = p;
+       nvram_error_log_size = nvram_get_partition_size(p) -
+               sizeof(struct err_log_info);
+       
+       return 0;
+}
+machine_arch_initcall(pseries, pseries_nvram_init_log_partition);
+
 int __init pSeries_nvram_init(void)
 {
        struct device_node *nvram;