powerpc/nvram: Improve partition removal
[pandora-kernel.git] / arch / powerpc / kernel / nvram_64.c
index 9cf197f..6dd2700 100644 (file)
 
 #undef DEBUG_NVRAM
 
+#define NVRAM_HEADER_LEN       sizeof(struct nvram_header)
+#define NVRAM_BLOCK_LEN                NVRAM_HEADER_LEN
+#define NVRAM_MAX_REQ          2079
+#define NVRAM_MIN_REQ          1055
+
+/* If change this size, then change the size of NVNAME_LEN */
+struct nvram_header {
+       unsigned char signature;
+       unsigned char checksum;
+       unsigned short length;
+       char name[12];
+};
+
+struct nvram_partition {
+       struct list_head partition;
+       struct nvram_header header;
+       unsigned int index;
+};
+
 static struct nvram_partition * nvram_part;
 static long nvram_error_log_index = -1;
 static long nvram_error_log_size = 0;
@@ -228,95 +247,113 @@ static unsigned char __init nvram_checksum(struct nvram_header *p)
        return c_sum;
 }
 
-static int __init nvram_remove_os_partition(void)
+/**
+ * nvram_remove_partition - Remove one or more partitions in nvram
+ * @name: name of the partition to remove, or NULL for a
+ *        signature only match
+ * @sig: signature of the partition(s) to remove
+ */
+
+static int __init nvram_remove_partition(const char *name, int sig)
 {
-       struct list_head *i;
-       struct list_head *j;
-       struct nvram_partition * part;
-       struct nvram_partition * cur_part;
+       struct nvram_partition *part, *prev, *tmp;
        int rc;
 
-       list_for_each(i, &nvram_part->partition) {
-               part = list_entry(i, struct nvram_partition, partition);
-               if (part->header.signature != NVRAM_SIG_OS)
+       list_for_each_entry(part, &nvram_part->partition, partition) {
+               if (part->header.signature != sig)
                        continue;
-               
-               /* Make os partition a free partition */
+               if (name && strncmp(name, part->header.name, 12))
+                       continue;
+
+               /* Make partition a free partition */
                part->header.signature = NVRAM_SIG_FREE;
                sprintf(part->header.name, "wwwwwwwwwwww");
                part->header.checksum = nvram_checksum(&part->header);
-
-               /* Merge contiguous free partitions backwards */
-               list_for_each_prev(j, &part->partition) {
-                       cur_part = list_entry(j, struct nvram_partition, partition);
-                       if (cur_part == nvram_part || cur_part->header.signature != NVRAM_SIG_FREE) {
-                               break;
-                       }
-                       
-                       part->header.length += cur_part->header.length;
-                       part->header.checksum = nvram_checksum(&part->header);
-                       part->index = cur_part->index;
-
-                       list_del(&cur_part->partition);
-                       kfree(cur_part);
-                       j = &part->partition; /* fixup our loop */
-               }
-               
-               /* Merge contiguous free partitions forwards */
-               list_for_each(j, &part->partition) {
-                       cur_part = list_entry(j, struct nvram_partition, partition);
-                       if (cur_part == nvram_part || cur_part->header.signature != NVRAM_SIG_FREE) {
-                               break;
-                       }
-
-                       part->header.length += cur_part->header.length;
-                       part->header.checksum = nvram_checksum(&part->header);
-
-                       list_del(&cur_part->partition);
-                       kfree(cur_part);
-                       j = &part->partition; /* fixup our loop */
-               }
-               
                rc = nvram_write_header(part);
                if (rc <= 0) {
-                       printk(KERN_ERR "nvram_remove_os_partition: nvram_write failed (%d)\n", rc);
+                       printk(KERN_ERR "nvram_remove_partition: nvram_write failed (%d)\n", rc);
                        return rc;
                }
+       }
 
+       /* Merge contiguous ones */
+       prev = NULL;
+       list_for_each_entry_safe(part, tmp, &nvram_part->partition, partition) {
+               if (part->header.signature != NVRAM_SIG_FREE) {
+                       prev = NULL;
+                       continue;
+               }
+               if (prev) {
+                       prev->header.length += part->header.length;
+                       prev->header.checksum = nvram_checksum(&part->header);
+                       rc = nvram_write_header(part);
+                       if (rc <= 0) {
+                               printk(KERN_ERR "nvram_remove_partition: nvram_write failed (%d)\n", rc);
+                               return rc;
+                       }
+                       list_del(&part->partition);
+                       kfree(part);
+               } else
+                       prev = part;
        }
        
        return 0;
 }
 
-/* nvram_create_os_partition
+/**
+ * nvram_create_partition - Create a partition in nvram
+ * @name: name of the partition to create
+ * @sig: signature of the partition to create
+ * @req_size: size of data to allocate in bytes
+ * @min_size: minimum acceptable size (0 means req_size)
  *
- * Create a OS linux partition to buffer error logs.
- * Will create a partition starting at the first free
- * space found if space has enough room.
+ * Returns a negative error code or a positive nvram index
+ * of the beginning of the data area of the newly created
+ * partition. If you provided a min_size smaller than req_size
+ * you need to query for the actual size yourself after the
+ * call using nvram_partition_get_size().
  */
-static int __init nvram_create_os_partition(void)
+static loff_t __init nvram_create_partition(const char *name, int sig,
+                                           int req_size, int min_size)
 {
        struct nvram_partition *part;
        struct nvram_partition *new_part;
        struct nvram_partition *free_part = NULL;
-       int seq_init[2] = { 0, 0 };
+       static char nv_init_vals[16];
        loff_t tmp_index;
        long size = 0;
        int rc;
-       
+
+       /* Convert sizes from bytes to blocks */
+       req_size = _ALIGN_UP(req_size, NVRAM_BLOCK_LEN) / NVRAM_BLOCK_LEN;
+       min_size = _ALIGN_UP(min_size, NVRAM_BLOCK_LEN) / NVRAM_BLOCK_LEN;
+
+       /* If no minimum size specified, make it the same as the
+        * requested size
+        */
+       if (min_size == 0)
+               min_size = req_size;
+       if (min_size > req_size)
+               return -EINVAL;
+
+       /* Now add one block to each for the header */
+       req_size += 1;
+       min_size += 1;
+
        /* Find a free partition that will give us the maximum needed size 
           If can't find one that will give us the minimum size needed */
        list_for_each_entry(part, &nvram_part->partition, partition) {
                if (part->header.signature != NVRAM_SIG_FREE)
                        continue;
 
-               if (part->header.length >= NVRAM_MAX_REQ) {
-                       size = NVRAM_MAX_REQ;
+               if (part->header.length >= req_size) {
+                       size = req_size;
                        free_part = part;
                        break;
                }
-               if (!size && part->header.length >= NVRAM_MIN_REQ) {
-                       size = NVRAM_MIN_REQ;
+               if (part->header.length > size &&
+                   part->header.length >= min_size) {
+                       size = part->header.length;
                        free_part = part;
                }
        }
@@ -326,58 +363,69 @@ static int __init nvram_create_os_partition(void)
        /* Create our OS partition */
        new_part = kmalloc(sizeof(*new_part), GFP_KERNEL);
        if (!new_part) {
-               printk(KERN_ERR "nvram_create_os_partition: kmalloc failed\n");
+               pr_err("nvram_create_os_partition: kmalloc failed\n");
                return -ENOMEM;
        }
 
        new_part->index = free_part->index;
-       new_part->header.signature = NVRAM_SIG_OS;
+       new_part->header.signature = sig;
        new_part->header.length = size;
-       strcpy(new_part->header.name, "ppc64,linux");
+       strncpy(new_part->header.name, name, 12);
        new_part->header.checksum = nvram_checksum(&new_part->header);
 
        rc = nvram_write_header(new_part);
        if (rc <= 0) {
-               printk(KERN_ERR "nvram_create_os_partition: nvram_write_header "
-                               "failed (%d)\n", rc);
-               return rc;
-       }
-
-       /* make sure and initialize to zero the sequence number and the error
-          type logged */
-       tmp_index = new_part->index + NVRAM_HEADER_LEN;
-       rc = ppc_md.nvram_write((char *)&seq_init, sizeof(seq_init), &tmp_index);
-       if (rc <= 0) {
-               printk(KERN_ERR "nvram_create_os_partition: nvram_write "
+               pr_err("nvram_create_os_partition: nvram_write_header "
                       "failed (%d)\n", rc);
                return rc;
        }
-       
-       nvram_error_log_index = new_part->index + NVRAM_HEADER_LEN;
-       nvram_error_log_size = ((part->header.length - 1) *
-                               NVRAM_BLOCK_LEN) - sizeof(struct err_log_info);
-       
        list_add_tail(&new_part->partition, &free_part->partition);
 
-       if (free_part->header.length <= size) {
+       /* Adjust or remove the partition we stole the space from */
+       if (free_part->header.length > size) {
+               free_part->index += size * NVRAM_BLOCK_LEN;
+               free_part->header.length -= size;
+               free_part->header.checksum = nvram_checksum(&free_part->header);
+               rc = nvram_write_header(free_part);
+               if (rc <= 0) {
+                       pr_err("nvram_create_os_partition: nvram_write_header "
+                              "failed (%d)\n", rc);
+                       return rc;
+               }
+       } else {
                list_del(&free_part->partition);
                kfree(free_part);
-               return 0;
        } 
 
-       /* Adjust the partition we stole the space from */
-       free_part->index += size * NVRAM_BLOCK_LEN;
-       free_part->header.length -= size;
-       free_part->header.checksum = nvram_checksum(&free_part->header);
-       
-       rc = nvram_write_header(free_part);
-       if (rc <= 0) {
-               printk(KERN_ERR "nvram_create_os_partition: nvram_write_header "
-                      "failed (%d)\n", rc);
-               return rc;
+       /* Clear the new partition */
+       for (tmp_index = new_part->index + NVRAM_HEADER_LEN;
+            tmp_index <  ((size - 1) * NVRAM_BLOCK_LEN);
+            tmp_index += NVRAM_BLOCK_LEN) {
+               rc = ppc_md.nvram_write(nv_init_vals, NVRAM_BLOCK_LEN, &tmp_index);
+               if (rc <= 0) {
+                       pr_err("nvram_create_partition: nvram_write failed (%d)\n", rc);
+                       return rc;
+               }
        }
+       
+       return new_part->index + NVRAM_HEADER_LEN;
+}
 
-       return 0;
+/**
+ * nvram_get_partition_size - Get the data size of an nvram partition
+ * @data_index: This is the offset of the start of the data of
+ *              the partition. The same value that is returned by
+ *              nvram_create_partition().
+ */
+static int nvram_get_partition_size(loff_t data_index)
+{
+       struct nvram_partition *part;
+       
+       list_for_each_entry(part, &nvram_part->partition, partition) {
+               if (part->index + NVRAM_HEADER_LEN == data_index)
+                       return (part->header.length - 1) * NVRAM_BLOCK_LEN;
+       }
+       return -1;
 }
 
 
@@ -422,39 +470,40 @@ static int __init nvram_setup_partition(void)
                if (strcmp(part->header.name, "ppc64,linux"))
                        continue;
 
-               if (part->header.length >= NVRAM_MIN_REQ) {
+               if ((part->header.length - 1) * NVRAM_BLOCK_LEN >= NVRAM_MIN_REQ) {
                        /* found our partition */
                        nvram_error_log_index = part->index + NVRAM_HEADER_LEN;
                        nvram_error_log_size = ((part->header.length - 1) *
                                                NVRAM_BLOCK_LEN) - sizeof(struct err_log_info);
                        return 0;
                }
+
+               /* Found one but it's too small, remove it */
+               nvram_remove_partition("ppc64,linux", NVRAM_SIG_OS);
        }
        
        /* try creating a partition with the free space we have */
-       rc = nvram_create_os_partition();
-       if (!rc) {
-               return 0;
-       }
-               
-       /* need to free up some space */
-       rc = nvram_remove_os_partition();
-       if (rc) {
-               return rc;
-       }
+       rc = nvram_create_partition("ppc64,linux", NVRAM_SIG_OS,
+                                      NVRAM_MAX_REQ, NVRAM_MIN_REQ);
+       if (rc < 0) {
+               /* need to free up some space, remove any "OS" partition */
+               nvram_remove_partition(NULL, NVRAM_SIG_OS);
        
-       /* create a partition in this new space */
-       rc = nvram_create_os_partition();
-       if (rc) {
-               printk(KERN_ERR "nvram_create_os_partition: Could not find a "
-                      "NVRAM partition large enough\n");
-               return rc;
+               /* Try again */
+               rc = nvram_create_partition("ppc64,linux", NVRAM_SIG_OS,
+                                           NVRAM_MAX_REQ, NVRAM_MIN_REQ);
+               if (rc < 0) {
+                       pr_err("nvram_create_partition: Could not find"
+                              " enough space in NVRAM for partition\n");
+                       return rc;
+               }
        }
        
+       nvram_error_log_index = rc;     
+       nvram_error_log_size = nvram_get_partition_size(rc) - sizeof(struct err_log_info);      
        return 0;
 }
 
-
 static int __init nvram_scan_partitions(void)
 {
        loff_t cur_index = 0;
@@ -528,6 +577,8 @@ static int __init nvram_init(void)
        int error;
        int rc;
        
+       BUILD_BUG_ON(NVRAM_BLOCK_LEN != 16);
+
        if (ppc_md.nvram_size == NULL || ppc_md.nvram_size() <= 0)
                return  -ENODEV;