efi: Add support for using efivars as a pstore backend
Matthew Garrett [Thu, 21 Jul 2011 20:57:56 +0000 (16:57 -0400)]
EFI provides an area of nonvolatile storage managed by the firmware. We
can use this as a pstore backend to maintain copies of oopses, aiding
diagnosis.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
Signed-off-by: Tony Luck <tony.luck@intel.com>

drivers/firmware/efivars.c
include/linux/efi.h

index 5f29aaf..2bbb226 100644 (file)
@@ -78,6 +78,7 @@
 #include <linux/kobject.h>
 #include <linux/device.h>
 #include <linux/slab.h>
+#include <linux/pstore.h>
 
 #include <asm/uaccess.h>
 
@@ -89,6 +90,8 @@ MODULE_DESCRIPTION("sysfs interface to EFI Variables");
 MODULE_LICENSE("GPL");
 MODULE_VERSION(EFIVARS_VERSION);
 
+#define DUMP_NAME_LEN 52
+
 /*
  * The maximum size of VariableName + Data = 1024
  * Therefore, it's reasonable to save that much
@@ -161,18 +164,28 @@ utf8_strsize(efi_char16_t *data, unsigned long maxlength)
 }
 
 static efi_status_t
-get_var_data(struct efivars *efivars, struct efi_variable *var)
+get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
 {
        efi_status_t status;
 
-       spin_lock(&efivars->lock);
        var->DataSize = 1024;
        status = efivars->ops->get_variable(var->VariableName,
                                            &var->VendorGuid,
                                            &var->Attributes,
                                            &var->DataSize,
                                            var->Data);
+       return status;
+}
+
+static efi_status_t
+get_var_data(struct efivars *efivars, struct efi_variable *var)
+{
+       efi_status_t status;
+
+       spin_lock(&efivars->lock);
+       status = get_var_data_locked(efivars, var);
        spin_unlock(&efivars->lock);
+
        if (status != EFI_SUCCESS) {
                printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n",
                        status);
@@ -387,12 +400,176 @@ static struct kobj_type efivar_ktype = {
        .default_attrs = def_attrs,
 };
 
+static struct pstore_info efi_pstore_info;
+
 static inline void
 efivar_unregister(struct efivar_entry *var)
 {
        kobject_put(&var->kobj);
 }
 
+#ifdef CONFIG_PSTORE
+
+static int efi_pstore_open(struct pstore_info *psi)
+{
+       struct efivars *efivars = psi->data;
+
+       spin_lock(&efivars->lock);
+       efivars->walk_entry = list_first_entry(&efivars->list,
+                                              struct efivar_entry, list);
+       return 0;
+}
+
+static int efi_pstore_close(struct pstore_info *psi)
+{
+       struct efivars *efivars = psi->data;
+
+       spin_unlock(&efivars->lock);
+       return 0;
+}
+
+static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
+                              struct timespec *timespec, struct pstore_info *psi)
+{
+       efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
+       struct efivars *efivars = psi->data;
+       char name[DUMP_NAME_LEN];
+       int i;
+       unsigned int part, size;
+       unsigned long time;
+
+       while (&efivars->walk_entry->list != &efivars->list) {
+               if (!efi_guidcmp(efivars->walk_entry->var.VendorGuid,
+                                vendor)) {
+                       for (i = 0; i < DUMP_NAME_LEN; i++) {
+                               name[i] = efivars->walk_entry->var.VariableName[i];
+                       }
+                       if (sscanf(name, "dump-type%u-%u-%lu", type, &part, &time) == 3) {
+                               *id = part;
+                               timespec->tv_sec = time;
+                               timespec->tv_nsec = 0;
+                               get_var_data_locked(efivars, &efivars->walk_entry->var);
+                               size = efivars->walk_entry->var.DataSize;
+                               memcpy(psi->buf, efivars->walk_entry->var.Data, size);
+                               efivars->walk_entry = list_entry(efivars->walk_entry->list.next,
+                                                  struct efivar_entry, list);
+                               return size;
+                       }
+               }
+               efivars->walk_entry = list_entry(efivars->walk_entry->list.next,
+                                                struct efivar_entry, list);
+       }
+       return 0;
+}
+
+static u64 efi_pstore_write(enum pstore_type_id type, unsigned int part,
+                           size_t size, struct pstore_info *psi)
+{
+       char name[DUMP_NAME_LEN];
+       char stub_name[DUMP_NAME_LEN];
+       efi_char16_t efi_name[DUMP_NAME_LEN];
+       efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
+       struct efivars *efivars = psi->data;
+       struct efivar_entry *entry, *found = NULL;
+       int i;
+
+       sprintf(stub_name, "dump-type%u-%u-", type, part);
+       sprintf(name, "%s%lu", stub_name, get_seconds());
+
+       spin_lock(&efivars->lock);
+
+       for (i = 0; i < DUMP_NAME_LEN; i++)
+               efi_name[i] = stub_name[i];
+
+       /*
+        * Clean up any entries with the same name
+        */
+
+       list_for_each_entry(entry, &efivars->list, list) {
+               get_var_data_locked(efivars, &entry->var);
+
+               for (i = 0; i < DUMP_NAME_LEN; i++) {
+                       if (efi_name[i] == 0) {
+                               found = entry;
+                               efivars->ops->set_variable(entry->var.VariableName,
+                                                          &entry->var.VendorGuid,
+                                                          EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
+                                                          0, NULL);
+                               break;
+                       } else if (efi_name[i] != entry->var.VariableName[i]) {
+                               break;
+                       }
+               }
+       }
+
+       if (found)
+               list_del(&found->list);
+
+       for (i = 0; i < DUMP_NAME_LEN; i++)
+               efi_name[i] = name[i];
+
+       efivars->ops->set_variable(efi_name, &vendor,
+                                  EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
+                                  size, psi->buf);
+
+       spin_unlock(&efivars->lock);
+
+       if (found)
+               efivar_unregister(found);
+
+       if (size)
+               efivar_create_sysfs_entry(efivars, utf8_strsize(efi_name, DUMP_NAME_LEN * 2),
+                                         efi_name, &vendor);
+
+       return part;
+};
+
+static int efi_pstore_erase(enum pstore_type_id type, u64 id,
+                           struct pstore_info *psi)
+{
+       efi_pstore_write(type, id, 0, psi);
+
+       return 0;
+}
+#else
+static int efi_pstore_open(struct pstore_info *psi)
+{
+       return 0;
+}
+
+static int efi_pstore_close(struct pstore_info *psi)
+{
+       return 0;
+}
+
+static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
+                              struct timespec *time, struct pstore_info *psi)
+{
+       return -1;
+}
+
+static u64 efi_pstore_write(enum pstore_type_id type, int part, size_t size,
+                           struct pstore_info *psi)
+{
+       return 0;
+}
+
+static int efi_pstore_erase(enum pstore_type_id type, u64 id,
+                           struct pstore_info *psi)
+{
+       return 0;
+}
+#endif
+
+static struct pstore_info efi_pstore_info = {
+       .owner          = THIS_MODULE,
+       .name           = "efi",
+       .open           = efi_pstore_open,
+       .close          = efi_pstore_close,
+       .read           = efi_pstore_read,
+       .write          = efi_pstore_write,
+       .erase          = efi_pstore_erase,
+};
 
 static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
                             struct bin_attribute *bin_attr,
@@ -763,6 +940,16 @@ int register_efivars(struct efivars *efivars,
        if (error)
                unregister_efivars(efivars);
 
+       efivars->efi_pstore_info = efi_pstore_info;
+
+       efivars->efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
+       if (efivars->efi_pstore_info.buf) {
+               efivars->efi_pstore_info.bufsize = 1024;
+               efivars->efi_pstore_info.data = efivars;
+               mutex_init(&efivars->efi_pstore_info.buf_mutex);
+               pstore_register(&efivars->efi_pstore_info);
+       }
+
 out:
        kfree(variable_name);
 
index e376270..c1f5107 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/rtc.h>
 #include <linux/ioport.h>
 #include <linux/pfn.h>
+#include <linux/pstore.h>
 
 #include <asm/page.h>
 #include <asm/system.h>
@@ -211,6 +212,9 @@ typedef efi_status_t efi_set_virtual_address_map_t (unsigned long memory_map_siz
 #define UV_SYSTEM_TABLE_GUID \
     EFI_GUID(  0x3b13a7d4, 0x633e, 0x11dd, 0x93, 0xec, 0xda, 0x25, 0x56, 0xd8, 0x95, 0x93 )
 
+#define LINUX_EFI_CRASH_GUID \
+    EFI_GUID(  0xcfc8fc79, 0xbe2e, 0x4ddc, 0x97, 0xf0, 0x9f, 0x98, 0xbf, 0xe2, 0x98, 0xa0 )
+
 typedef struct {
        efi_guid_t guid;
        unsigned long table;
@@ -426,6 +430,8 @@ struct efivars {
        struct kset *kset;
        struct bin_attribute *new_var, *del_var;
        const struct efivar_operations *ops;
+       struct efivar_entry *walk_entry;
+       struct pstore_info efi_pstore_info;
 };
 
 int register_efivars(struct efivars *efivars,