diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2013-02-21 21:38:18 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-02-21 21:38:18 +0400 |
commit | 024e4ec1856d57bb78c06ec903d29dcf716f5f47 (patch) | |
tree | 72c89ea735f2aad2f63f64444ec056d7da8e06ff | |
parent | 850cb82b754c931c570c9cb7f10c9f2181bac617 (diff) | |
parent | fb0af3f2b1b613e5ea75426d454c7e5b1d1eef49 (diff) | |
download | linux-024e4ec1856d57bb78c06ec903d29dcf716f5f47.tar.xz |
Merge tag 'please-pull-pstore' of git://git.kernel.org/pub/scm/linux/kernel/git/aegl/linux
Pull pstore patches from Tony Luck:
"A few fixes to reduce places where pstore might hang a system in the
crash path. Plus a new mountpoint (/sys/fs/pstore ... makes more
sense then /dev/pstore)."
Fix up trivial conflict in drivers/firmware/efivars.c
* tag 'please-pull-pstore' of git://git.kernel.org/pub/scm/linux/kernel/git/aegl/linux:
pstore: Create a convenient mount point for pstore
efi_pstore: Introducing workqueue updating sysfs
efivars: Disable external interrupt while holding efivars->lock
efi_pstore: Avoid deadlock in non-blocking paths
pstore: Avoid deadlock in panic and emergency-restart path
-rw-r--r-- | Documentation/ABI/testing/pstore | 10 | ||||
-rw-r--r-- | drivers/firmware/efivars.c | 180 | ||||
-rw-r--r-- | fs/pstore/inode.c | 18 | ||||
-rw-r--r-- | fs/pstore/platform.c | 35 | ||||
-rw-r--r-- | include/linux/efi.h | 3 | ||||
-rw-r--r-- | include/linux/pstore.h | 6 |
6 files changed, 192 insertions, 60 deletions
diff --git a/Documentation/ABI/testing/pstore b/Documentation/ABI/testing/pstore index ff1df4e3b059..5fca9f5e10a3 100644 --- a/Documentation/ABI/testing/pstore +++ b/Documentation/ABI/testing/pstore @@ -1,4 +1,4 @@ -Where: /dev/pstore/... +Where: /sys/fs/pstore/... (or /dev/pstore/...) Date: March 2011 Kernel Version: 2.6.39 Contact: tony.luck@intel.com @@ -11,9 +11,9 @@ Description: Generic interface to platform dependent persistent storage. of the console log is captured, but other interesting data can also be saved. - # mount -t pstore -o kmsg_bytes=8000 - /dev/pstore + # mount -t pstore -o kmsg_bytes=8000 - /sys/fs/pstore - $ ls -l /dev/pstore + $ ls -l /sys/fs/pstore/ total 0 -r--r--r-- 1 root root 7896 Nov 30 15:38 dmesg-erst-1 @@ -27,9 +27,9 @@ Description: Generic interface to platform dependent persistent storage. the file will signal to the underlying persistent storage device that it can reclaim the space for later re-use. - $ rm /dev/pstore/dmesg-erst-1 + $ rm /sys/fs/pstore/dmesg-erst-1 - The expectation is that all files in /dev/pstore + The expectation is that all files in /sys/fs/pstore/ will be saved elsewhere and erased from persistent store soon after boot to free up space ready for the next catastrophe. diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index f5596db0cf58..fed08b661711 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -158,6 +158,13 @@ efivar_create_sysfs_entry(struct efivars *efivars, efi_char16_t *variable_name, efi_guid_t *vendor_guid); +/* + * Prototype for workqueue functions updating sysfs entry + */ + +static void efivar_update_sysfs_entries(struct work_struct *); +static DECLARE_WORK(efivar_work, efivar_update_sysfs_entries); + /* Return the number of unicode characters in data */ static unsigned long utf16_strnlen(efi_char16_t *s, size_t maxlength) @@ -405,10 +412,11 @@ static efi_status_t get_var_data(struct efivars *efivars, struct efi_variable *var) { efi_status_t status; + unsigned long flags; - spin_lock(&efivars->lock); + spin_lock_irqsave(&efivars->lock, flags); status = get_var_data_locked(efivars, var); - spin_unlock(&efivars->lock); + spin_unlock_irqrestore(&efivars->lock, flags); if (status != EFI_SUCCESS) { printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n", @@ -537,14 +545,14 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) return -EINVAL; } - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); status = efivars->ops->set_variable(new_var->VariableName, &new_var->VendorGuid, new_var->Attributes, new_var->DataSize, new_var->Data); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); if (status != EFI_SUCCESS) { printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", @@ -713,7 +721,7 @@ static ssize_t efivarfs_file_write(struct file *file, * amounts of memory. Pick a default size of 64K if * QueryVariableInfo() isn't supported by the firmware. */ - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); if (!efivars->ops->query_variable_info) status = EFI_UNSUPPORTED; @@ -723,7 +731,7 @@ static ssize_t efivarfs_file_write(struct file *file, &remaining_size, &max_size); } - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); if (status != EFI_SUCCESS) { if (status != EFI_UNSUPPORTED) @@ -754,7 +762,7 @@ static ssize_t efivarfs_file_write(struct file *file, * set_variable call, and removal of the variable from the efivars * list (in the case of an authenticated delete). */ - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); status = efivars->ops->set_variable(var->var.VariableName, &var->var.VendorGuid, @@ -762,7 +770,7 @@ static ssize_t efivarfs_file_write(struct file *file, data); if (status != EFI_SUCCESS) { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); kfree(data); return efi_status_to_err(status); @@ -783,21 +791,21 @@ static ssize_t efivarfs_file_write(struct file *file, NULL); if (status == EFI_BUFFER_TOO_SMALL) { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); mutex_lock(&inode->i_mutex); i_size_write(inode, newdatasize + sizeof(attributes)); mutex_unlock(&inode->i_mutex); } else if (status == EFI_NOT_FOUND) { list_del(&var->list); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); efivar_unregister(var); drop_nlink(inode); d_delete(file->f_dentry); dput(file->f_dentry); } else { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); pr_warn("efivarfs: inconsistent EFI variable implementation? " "status = %lx\n", status); } @@ -819,11 +827,11 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf, void *data; ssize_t size = 0; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); status = efivars->ops->get_variable(var->var.VariableName, &var->var.VendorGuid, &attributes, &datasize, NULL); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); if (status != EFI_BUFFER_TOO_SMALL) return efi_status_to_err(status); @@ -833,12 +841,12 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf, if (!data) return -ENOMEM; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); status = efivars->ops->get_variable(var->var.VariableName, &var->var.VendorGuid, &attributes, &datasize, (data + sizeof(attributes))); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); if (status != EFI_SUCCESS) { size = efi_status_to_err(status); @@ -966,9 +974,9 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry, goto out; kobject_uevent(&var->kobj, KOBJ_ADD); - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); list_add(&var->list, &efivars->list); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); d_instantiate(dentry, inode); dget(dentry); out: @@ -985,7 +993,7 @@ static int efivarfs_unlink(struct inode *dir, struct dentry *dentry) struct efivars *efivars = var->efivars; efi_status_t status; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); status = efivars->ops->set_variable(var->var.VariableName, &var->var.VendorGuid, @@ -993,14 +1001,14 @@ static int efivarfs_unlink(struct inode *dir, struct dentry *dentry) if (status == EFI_SUCCESS || status == EFI_NOT_FOUND) { list_del(&var->list); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); efivar_unregister(var); drop_nlink(dentry->d_inode); dput(dentry); return 0; } - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return -EINVAL; }; @@ -1066,13 +1074,13 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent) /* copied by the above to local storage in the dentry. */ kfree(name); - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); efivars->ops->get_variable(entry->var.VariableName, &entry->var.VendorGuid, &entry->var.Attributes, &size, NULL); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); mutex_lock(&inode->i_mutex); inode->i_private = entry; @@ -1123,7 +1131,7 @@ static int efi_pstore_open(struct pstore_info *psi) { struct efivars *efivars = psi->data; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); efivars->walk_entry = list_first_entry(&efivars->list, struct efivar_entry, list); return 0; @@ -1133,7 +1141,7 @@ static int efi_pstore_close(struct pstore_info *psi) { struct efivars *efivars = psi->data; - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return 0; } @@ -1209,8 +1217,18 @@ static int efi_pstore_write(enum pstore_type_id type, int i, ret = 0; u64 storage_space, remaining_space, max_variable_size; efi_status_t status = EFI_NOT_FOUND; - - spin_lock(&efivars->lock); + unsigned long flags; + + if (pstore_cannot_block_path(reason)) { + /* + * If the lock is taken by another cpu in non-blocking path, + * this driver returns without entering firmware to avoid + * hanging up. + */ + if (!spin_trylock_irqsave(&efivars->lock, flags)) + return -EBUSY; + } else + spin_lock_irqsave(&efivars->lock, flags); /* * Check if there is a space enough to log. @@ -1222,7 +1240,7 @@ static int efi_pstore_write(enum pstore_type_id type, &remaining_space, &max_variable_size); if (status || remaining_space < size + DUMP_NAME_LEN * 2) { - spin_unlock(&efivars->lock); + spin_unlock_irqrestore(&efivars->lock, flags); *id = part; return -ENOSPC; } @@ -1236,13 +1254,10 @@ static int efi_pstore_write(enum pstore_type_id type, efivars->ops->set_variable(efi_name, &vendor, PSTORE_EFI_ATTRIBUTES, size, psi->buf); - spin_unlock(&efivars->lock); + spin_unlock_irqrestore(&efivars->lock, flags); - if (size) - ret = efivar_create_sysfs_entry(efivars, - utf16_strsize(efi_name, - DUMP_NAME_LEN * 2), - efi_name, &vendor); + if (reason == KMSG_DUMP_OOPS) + schedule_work(&efivar_work); *id = part; return ret; @@ -1263,7 +1278,7 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count, sprintf(name, "dump-type%u-%u-%d-%lu", type, (unsigned int)id, count, time.tv_sec); - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); for (i = 0; i < DUMP_NAME_LEN; i++) efi_name[i] = name[i]; @@ -1307,7 +1322,7 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count, if (found) list_del(&found->list); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); if (found) efivar_unregister(found); @@ -1377,7 +1392,7 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, return -EINVAL; } - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); /* * Does this variable already exist? @@ -1395,7 +1410,7 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, } } if (found) { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return -EINVAL; } @@ -1409,10 +1424,10 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, if (status != EFI_SUCCESS) { printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", status); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return -EIO; } - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); /* Create the entry in sysfs. Locking is not required here */ status = efivar_create_sysfs_entry(efivars, @@ -1440,7 +1455,7 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj, if (!capable(CAP_SYS_ADMIN)) return -EACCES; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); /* * Does this variable already exist? @@ -1458,7 +1473,7 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj, } } if (!found) { - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return -EINVAL; } /* force the Attributes/DataSize to 0 to ensure deletion */ @@ -1474,18 +1489,87 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj, if (status != EFI_SUCCESS) { printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", status); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return -EIO; } list_del(&search_efivar->list); /* We need to release this lock before unregistering. */ - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); efivar_unregister(search_efivar); /* It's dead Jim.... */ return count; } +static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor) +{ + struct efivar_entry *entry, *n; + struct efivars *efivars = &__efivars; + unsigned long strsize1, strsize2; + bool found = false; + + strsize1 = utf16_strsize(variable_name, 1024); + list_for_each_entry_safe(entry, n, &efivars->list, list) { + strsize2 = utf16_strsize(entry->var.VariableName, 1024); + if (strsize1 == strsize2 && + !memcmp(variable_name, &(entry->var.VariableName), + strsize2) && + !efi_guidcmp(entry->var.VendorGuid, + *vendor)) { + found = true; + break; + } + } + return found; +} + +static void efivar_update_sysfs_entries(struct work_struct *work) +{ + struct efivars *efivars = &__efivars; + efi_guid_t vendor; + efi_char16_t *variable_name; + unsigned long variable_name_size = 1024; + efi_status_t status = EFI_NOT_FOUND; + bool found; + + /* Add new sysfs entries */ + while (1) { + variable_name = kzalloc(variable_name_size, GFP_KERNEL); + if (!variable_name) { + pr_err("efivars: Memory allocation failed.\n"); + return; + } + + spin_lock_irq(&efivars->lock); + found = false; + while (1) { + variable_name_size = 1024; + status = efivars->ops->get_next_variable( + &variable_name_size, + variable_name, + &vendor); + if (status != EFI_SUCCESS) { + break; + } else { + if (!variable_is_present(variable_name, + &vendor)) { + found = true; + break; + } + } + } + spin_unlock_irq(&efivars->lock); + + if (!found) { + kfree(variable_name); + break; + } else + efivar_create_sysfs_entry(efivars, + variable_name_size, + variable_name, &vendor); + } +} + /* * Let's not leave out systab information that snuck into * the efivars driver @@ -1594,9 +1678,9 @@ efivar_create_sysfs_entry(struct efivars *efivars, kfree(short_name); short_name = NULL; - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); list_add(&new_efivar->list, &efivars->list); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); return 0; } @@ -1665,9 +1749,9 @@ void unregister_efivars(struct efivars *efivars) struct efivar_entry *entry, *n; list_for_each_entry_safe(entry, n, &efivars->list, list) { - spin_lock(&efivars->lock); + spin_lock_irq(&efivars->lock); list_del(&entry->list); - spin_unlock(&efivars->lock); + spin_unlock_irq(&efivars->lock); efivar_unregister(entry); } if (efivars->new_var) @@ -1823,6 +1907,8 @@ err_put: static void __exit efivars_exit(void) { + cancel_work_sync(&efivar_work); + if (efi_enabled(EFI_RUNTIME_SERVICES)) { unregister_efivars(&__efivars); kobject_put(efi_kobj); diff --git a/fs/pstore/inode.c b/fs/pstore/inode.c index 67de74ca85f4..e4bcb2cf055a 100644 --- a/fs/pstore/inode.c +++ b/fs/pstore/inode.c @@ -418,9 +418,25 @@ static struct file_system_type pstore_fs_type = { .kill_sb = pstore_kill_sb, }; +static struct kobject *pstore_kobj; + static int __init init_pstore_fs(void) { - return register_filesystem(&pstore_fs_type); + int err = 0; + + /* Create a convenient mount point for people to access pstore */ + pstore_kobj = kobject_create_and_add("pstore", fs_kobj); + if (!pstore_kobj) { + err = -ENOMEM; + goto out; + } + + err = register_filesystem(&pstore_fs_type); + if (err < 0) + kobject_put(pstore_kobj); + +out: + return err; } module_init(init_pstore_fs) diff --git a/fs/pstore/platform.c b/fs/pstore/platform.c index 5ea2e77ff023..86d1038b5a12 100644 --- a/fs/pstore/platform.c +++ b/fs/pstore/platform.c @@ -96,6 +96,27 @@ static const char *get_reason_str(enum kmsg_dump_reason reason) } } +bool pstore_cannot_block_path(enum kmsg_dump_reason reason) +{ + /* + * In case of NMI path, pstore shouldn't be blocked + * regardless of reason. + */ + if (in_nmi()) + return true; + + switch (reason) { + /* In panic case, other cpus are stopped by smp_send_stop(). */ + case KMSG_DUMP_PANIC: + /* Emergency restart shouldn't be blocked by spin lock. */ + case KMSG_DUMP_EMERG: + return true; + default: + return false; + } +} +EXPORT_SYMBOL_GPL(pstore_cannot_block_path); + /* * callback from kmsg_dump. (s2,l2) has the most recently * written bytes, older bytes are in (s1,l1). Save as much @@ -114,10 +135,12 @@ static void pstore_dump(struct kmsg_dumper *dumper, why = get_reason_str(reason); - if (in_nmi()) { - is_locked = spin_trylock(&psinfo->buf_lock); - if (!is_locked) - pr_err("pstore dump routine blocked in NMI, may corrupt error record\n"); + if (pstore_cannot_block_path(reason)) { + is_locked = spin_trylock_irqsave(&psinfo->buf_lock, flags); + if (!is_locked) { + pr_err("pstore dump routine blocked in %s path, may corrupt error record\n" + , in_nmi() ? "NMI" : why); + } } else spin_lock_irqsave(&psinfo->buf_lock, flags); oopscount++; @@ -143,9 +166,9 @@ static void pstore_dump(struct kmsg_dumper *dumper, total += hsize + len; part++; } - if (in_nmi()) { + if (pstore_cannot_block_path(reason)) { if (is_locked) - spin_unlock(&psinfo->buf_lock); + spin_unlock_irqrestore(&psinfo->buf_lock, flags); } else spin_unlock_irqrestore(&psinfo->buf_lock, flags); } diff --git a/include/linux/efi.h b/include/linux/efi.h index 7a9498ab3c2d..9bf2f1fcae27 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -740,7 +740,8 @@ struct efivars { * 1) ->list - adds, removals, reads, writes * 2) ops.[gs]et_variable() calls. * It must not be held when creating sysfs entries or calling kmalloc. - * ops.get_next_variable() is only called from register_efivars(), + * ops.get_next_variable() is only called from register_efivars() + * or efivar_update_sysfs_entries(), * which is protected by the BKL, so that path is safe. */ spinlock_t lock; diff --git a/include/linux/pstore.h b/include/linux/pstore.h index 1788909d9a99..75d01760c911 100644 --- a/include/linux/pstore.h +++ b/include/linux/pstore.h @@ -68,12 +68,18 @@ struct pstore_info { #ifdef CONFIG_PSTORE extern int pstore_register(struct pstore_info *); +extern bool pstore_cannot_block_path(enum kmsg_dump_reason reason); #else static inline int pstore_register(struct pstore_info *psi) { return -ENODEV; } +static inline bool +pstore_cannot_block_path(enum kmsg_dump_reason reason) +{ + return false; +} #endif #endif /*_LINUX_PSTORE_H*/ |