From 5ee9c198a4208d7760275d48e4c4f6c89dcd2ef0 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Thu, 21 Jul 2011 16:57:56 -0400 Subject: efi: Add support for using efivars as a pstore backend 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 Signed-off-by: Tony Luck --- drivers/firmware/efivars.c | 191 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 2 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 5f29aafd446..2bbb22670d2 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -78,6 +78,7 @@ #include #include #include +#include #include @@ -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); -- cgit v1.2.3-70-g09d2 From a2940908391f3cee72e38769b30e829b22742b5b Mon Sep 17 00:00:00 2001 From: Mike Waychison Date: Thu, 21 Jul 2011 16:57:57 -0400 Subject: efivars: String functions Fix the string functions in the efivars driver to be called utf16_* instead of utf8_* as the encoding is utf16, not utf8. As well, rename utf16_strlen to utf16_strnlen as it takes a maxlength argument and the name should be consistent with the standard C function names. utf16_strlen is still provided for convenience in a subsequent patch. Signed-off-by: Mike Waychison Signed-off-by: Tony Luck --- drivers/firmware/efivars.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 2bbb22670d2..4202a317046 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -144,23 +144,29 @@ efivar_create_sysfs_entry(struct efivars *efivars, /* Return the number of unicode characters in data */ static unsigned long -utf8_strlen(efi_char16_t *data, unsigned long maxlength) +utf16_strnlen(efi_char16_t *s, size_t maxlength) { unsigned long length = 0; - while (*data++ != 0 && length < maxlength) + while (*s++ != 0 && length < maxlength) length++; return length; } +static unsigned long +utf16_strlen(efi_char16_t *s) +{ + return utf16_strnlen(s, ~0UL); +} + /* * Return the number of bytes is the length of this string * Note: this is NOT the same as the number of unicode characters */ static inline unsigned long -utf8_strsize(efi_char16_t *data, unsigned long maxlength) +utf16_strsize(efi_char16_t *data, unsigned long maxlength) { - return utf8_strlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t); + return utf16_strnlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t); } static efi_status_t @@ -518,7 +524,9 @@ static u64 efi_pstore_write(enum pstore_type_id type, unsigned int part, efivar_unregister(found); if (size) - efivar_create_sysfs_entry(efivars, utf8_strsize(efi_name, DUMP_NAME_LEN * 2), + efivar_create_sysfs_entry(efivars, + utf16_strsize(efi_name, + DUMP_NAME_LEN * 2), efi_name, &vendor); return part; @@ -591,8 +599,8 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, * Does this variable already exist? */ list_for_each_entry_safe(search_efivar, n, &efivars->list, list) { - strsize1 = utf8_strsize(search_efivar->var.VariableName, 1024); - strsize2 = utf8_strsize(new_var->VariableName, 1024); + strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024); + strsize2 = utf16_strsize(new_var->VariableName, 1024); if (strsize1 == strsize2 && !memcmp(&(search_efivar->var.VariableName), new_var->VariableName, strsize1) && @@ -624,8 +632,8 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, /* Create the entry in sysfs. Locking is not required here */ status = efivar_create_sysfs_entry(efivars, - utf8_strsize(new_var->VariableName, - 1024), + utf16_strsize(new_var->VariableName, + 1024), new_var->VariableName, &new_var->VendorGuid); if (status) { @@ -654,8 +662,8 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj, * Does this variable already exist? */ list_for_each_entry_safe(search_efivar, n, &efivars->list, list) { - strsize1 = utf8_strsize(search_efivar->var.VariableName, 1024); - strsize2 = utf8_strsize(del_var->VariableName, 1024); + strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024); + strsize2 = utf16_strsize(del_var->VariableName, 1024); if (strsize1 == strsize2 && !memcmp(&(search_efivar->var.VariableName), del_var->VariableName, strsize1) && -- cgit v1.2.3-70-g09d2 From 828aa1f00ec3508a4d813bd60d210de82929ac97 Mon Sep 17 00:00:00 2001 From: Mike Waychison Date: Thu, 21 Jul 2011 16:57:58 -0400 Subject: efivars: introduce utf16_strncmp Introduce utf16_strncmp which is used in the next patch. Semantics should be the same as the strncmp C function. Signed-off-by: Mike Waychison Signed-off-by: Tony Luck --- drivers/firmware/efivars.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 4202a317046..15b9a01b6c6 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -169,6 +169,24 @@ utf16_strsize(efi_char16_t *data, unsigned long maxlength) return utf16_strnlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t); } +static inline int +utf16_strncmp(const efi_char16_t *a, const efi_char16_t *b, size_t len) +{ + while (1) { + if (len == 0) + return 0; + if (*a < *b) + return -1; + if (*a > *b) + return 1; + if (*a == 0) /* implies *b == 0 */ + return 0; + a++; + b++; + len--; + } +} + static efi_status_t get_var_data_locked(struct efivars *efivars, struct efi_variable *var) { -- cgit v1.2.3-70-g09d2 From c475594d838c5c872e734f693a700df8c01b39d4 Mon Sep 17 00:00:00 2001 From: Mike Waychison Date: Thu, 21 Jul 2011 16:57:59 -0400 Subject: efivars: Use string functions in pstore_write Instead of open-coding the string operations for comparing the prefix of the variable names, use the provided utf16_* string functions. This patch also changes the calls to efi.set_variable to efivars->ops->set_variable so that the right function gets called in the case of gsmi (which doesn't have a valid efi structure). As well, make sure that we only consider variables with the right vendor string. Signed-off-by: Mike Waychison Signed-off-by: Tony Luck --- drivers/firmware/efivars.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 15b9a01b6c6..563492e4d5c 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -512,18 +512,20 @@ static u64 efi_pstore_write(enum pstore_type_id type, unsigned int part, 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 (efi_guidcmp(entry->var.VendorGuid, vendor)) + continue; + if (utf16_strncmp(entry->var.VariableName, efi_name, + utf16_strlen(efi_name))) + continue; + /* Needs to be a prefix */ + if (entry->var.VariableName[utf16_strlen(efi_name)] == 0) + continue; + + /* found */ + 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); } if (found) -- cgit v1.2.3-70-g09d2 From 7644c16c7e7431fa398e834109dbb76dc1b51617 Mon Sep 17 00:00:00 2001 From: Mike Waychison Date: Thu, 21 Jul 2011 16:58:00 -0400 Subject: efivars: Introduce PSTORE_EFI_ATTRIBUTES Consolidate the attributes listed for pstore operations in one place, PSTORE_EFI_ATTRIBUTES. Signed-off-by: Mike Waychison Signed-off-by: Tony Luck --- drivers/firmware/efivars.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 563492e4d5c..eacb05e6cfb 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -122,6 +122,10 @@ struct efivar_attribute { ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count); }; +#define PSTORE_EFI_ATTRIBUTES \ + (EFI_VARIABLE_NON_VOLATILE | \ + EFI_VARIABLE_BOOTSERVICE_ACCESS | \ + EFI_VARIABLE_RUNTIME_ACCESS) #define EFIVAR_ATTR(_name, _mode, _show, _store) \ struct efivar_attribute efivar_attr_##_name = { \ @@ -523,8 +527,9 @@ static u64 efi_pstore_write(enum pstore_type_id type, unsigned int part, /* found */ found = entry; - efivars->ops->set_variable(entry->var.VariableName, &entry->var.VendorGuid, - EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, + efivars->ops->set_variable(entry->var.VariableName, + &entry->var.VendorGuid, + PSTORE_EFI_ATTRIBUTES, 0, NULL); } @@ -534,8 +539,7 @@ static u64 efi_pstore_write(enum pstore_type_id type, unsigned int part, 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, + efivars->ops->set_variable(efi_name, &vendor, PSTORE_EFI_ATTRIBUTES, size, psi->buf); spin_unlock(&efivars->lock); -- cgit v1.2.3-70-g09d2