diff --git a/MAINTAINERS b/MAINTAINERS index 74e58a4d035b..9e4862bf9961 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6325,7 +6325,7 @@ S: Maintained T: git git://git.infradead.org/users/cbou/linux-pstore.git F: fs/pstore/ F: include/linux/pstore* -F: drivers/firmware/efivars.c +F: drivers/firmware/efi/efi-pstore.c F: drivers/acpi/apei/erst.c PTP HARDWARE CLOCK SUPPORT diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 42c759a4d047..93876302fb2e 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -36,41 +36,6 @@ config FIRMWARE_MEMMAP See also Documentation/ABI/testing/sysfs-firmware-memmap. -config EFI_VARS - tristate "EFI Variable Support via sysfs" - depends on EFI - default n - help - If you say Y here, you are able to get EFI (Extensible Firmware - Interface) variable information via sysfs. You may read, - write, create, and destroy EFI variables through this interface. - - Note that using this driver in concert with efibootmgr requires - at least test release version 0.5.0-test3 or later, which is - available from Matt Domsch's website located at: - - - Subsequent efibootmgr releases may be found at: - - -config EFI_VARS_PSTORE - bool "Register efivars backend for pstore" - depends on EFI_VARS && PSTORE - default y - help - Say Y here to enable use efivars as a backend to pstore. This - will allow writing console messages, crash dumps, or anything - else supported by pstore to EFI variables. - -config EFI_VARS_PSTORE_DEFAULT_DISABLE - bool "Disable using efivars as a pstore backend by default" - depends on EFI_VARS_PSTORE - default n - help - Saying Y here will disable the use of efivars as a storage - backend for pstore by default. This setting can be overridden - using the efivars module's pstore_disable parameter. - config EFI_PCDP bool "Console device selection via EFI PCDP or HCDP table" depends on ACPI && EFI && IA64 @@ -164,5 +129,6 @@ config ISCSI_IBFT Otherwise, say N. source "drivers/firmware/google/Kconfig" +source "drivers/firmware/efi/Kconfig" endmenu diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 5a7e27399729..31bf68c93593 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o obj-$(CONFIG_GOOGLE_FIRMWARE) += google/ +obj-$(CONFIG_EFI) += efi/ diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig new file mode 100644 index 000000000000..b0fc7c79dfbb --- /dev/null +++ b/drivers/firmware/efi/Kconfig @@ -0,0 +1,39 @@ +menu "EFI (Extensible Firmware Interface) Support" + depends on EFI + +config EFI_VARS + tristate "EFI Variable Support via sysfs" + depends on EFI + default n + help + If you say Y here, you are able to get EFI (Extensible Firmware + Interface) variable information via sysfs. You may read, + write, create, and destroy EFI variables through this interface. + + Note that using this driver in concert with efibootmgr requires + at least test release version 0.5.0-test3 or later, which is + available from Matt Domsch's website located at: + + + Subsequent efibootmgr releases may be found at: + + +config EFI_VARS_PSTORE + tristate "Register efivars backend for pstore" + depends on EFI_VARS && PSTORE + default y + help + Say Y here to enable use efivars as a backend to pstore. This + will allow writing console messages, crash dumps, or anything + else supported by pstore to EFI variables. + +config EFI_VARS_PSTORE_DEFAULT_DISABLE + bool "Disable using efivars as a pstore backend by default" + depends on EFI_VARS_PSTORE + default n + help + Saying Y here will disable the use of efivars as a storage + backend for pstore by default. This setting can be overridden + using the efivars module's pstore_disable parameter. + +endmenu diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile new file mode 100644 index 000000000000..e03cd51525c2 --- /dev/null +++ b/drivers/firmware/efi/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for linux kernel +# +obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o diff --git a/drivers/firmware/efi/efi-pstore.c b/drivers/firmware/efi/efi-pstore.c new file mode 100644 index 000000000000..47ae712c9504 --- /dev/null +++ b/drivers/firmware/efi/efi-pstore.c @@ -0,0 +1,244 @@ +#include +#include +#include + +#define DUMP_NAME_LEN 52 + +static bool efivars_pstore_disable = + IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE); + +module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644); + +#define PSTORE_EFI_ATTRIBUTES \ + (EFI_VARIABLE_NON_VOLATILE | \ + EFI_VARIABLE_BOOTSERVICE_ACCESS | \ + EFI_VARIABLE_RUNTIME_ACCESS) + +static int efi_pstore_open(struct pstore_info *psi) +{ + efivar_entry_iter_begin(); + psi->data = NULL; + return 0; +} + +static int efi_pstore_close(struct pstore_info *psi) +{ + efivar_entry_iter_end(); + psi->data = NULL; + return 0; +} + +struct pstore_read_data { + u64 *id; + enum pstore_type_id *type; + int *count; + struct timespec *timespec; + char **buf; +}; + +static int efi_pstore_read_func(struct efivar_entry *entry, void *data) +{ + efi_guid_t vendor = LINUX_EFI_CRASH_GUID; + struct pstore_read_data *cb_data = data; + char name[DUMP_NAME_LEN]; + int i; + int cnt; + unsigned int part; + unsigned long time, size; + + if (efi_guidcmp(entry->var.VendorGuid, vendor)) + return 0; + + for (i = 0; i < DUMP_NAME_LEN; i++) + name[i] = entry->var.VariableName[i]; + + if (sscanf(name, "dump-type%u-%u-%d-%lu", + cb_data->type, &part, &cnt, &time) == 4) { + *cb_data->id = part; + *cb_data->count = cnt; + cb_data->timespec->tv_sec = time; + cb_data->timespec->tv_nsec = 0; + } else if (sscanf(name, "dump-type%u-%u-%lu", + cb_data->type, &part, &time) == 3) { + /* + * Check if an old format, + * which doesn't support holding + * multiple logs, remains. + */ + *cb_data->id = part; + *cb_data->count = 0; + cb_data->timespec->tv_sec = time; + cb_data->timespec->tv_nsec = 0; + } else + return 0; + + __efivar_entry_size(entry, &size); + *cb_data->buf = kmalloc(size, GFP_KERNEL); + if (*cb_data->buf == NULL) + return -ENOMEM; + memcpy(*cb_data->buf, entry->var.Data, size); + return size; +} + +static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, + int *count, struct timespec *timespec, + char **buf, struct pstore_info *psi) +{ + struct pstore_read_data data; + + data.id = id; + data.type = type; + data.count = count; + data.timespec = timespec; + data.buf = buf; + + return __efivar_entry_iter(efi_pstore_read_func, &efivar_sysfs_list, &data, + (struct efivar_entry **)&psi->data); +} + +static int efi_pstore_write(enum pstore_type_id type, + enum kmsg_dump_reason reason, u64 *id, + unsigned int part, int count, size_t size, + struct pstore_info *psi) +{ + char name[DUMP_NAME_LEN]; + efi_char16_t efi_name[DUMP_NAME_LEN]; + efi_guid_t vendor = LINUX_EFI_CRASH_GUID; + int i, ret = 0; + + sprintf(name, "dump-type%u-%u-%d-%lu", type, part, count, + get_seconds()); + + for (i = 0; i < DUMP_NAME_LEN; i++) + efi_name[i] = name[i]; + + efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES, + !pstore_cannot_block_path(reason), + size, psi->buf); + + if (reason == KMSG_DUMP_OOPS) + efivar_run_worker(); + + *id = part; + return ret; +}; + +struct pstore_erase_data { + u64 id; + enum pstore_type_id type; + int count; + struct timespec time; + efi_char16_t *name; +}; + +/* + * Clean up an entry with the same name + */ +static int efi_pstore_erase_func(struct efivar_entry *entry, void *data) +{ + struct pstore_erase_data *ed = data; + efi_guid_t vendor = LINUX_EFI_CRASH_GUID; + efi_char16_t efi_name_old[DUMP_NAME_LEN]; + efi_char16_t *efi_name = ed->name; + unsigned long utf16_len = utf16_strlen(ed->name); + char name_old[DUMP_NAME_LEN]; + int i; + + if (efi_guidcmp(entry->var.VendorGuid, vendor)) + return 0; + + if (utf16_strncmp(entry->var.VariableName, + efi_name, (size_t)utf16_len)) { + /* + * Check if an old format, which doesn't support + * holding multiple logs, remains. + */ + sprintf(name_old, "dump-type%u-%u-%lu", ed->type, + (unsigned int)ed->id, ed->time.tv_sec); + + for (i = 0; i < DUMP_NAME_LEN; i++) + efi_name_old[i] = name_old[i]; + + if (utf16_strncmp(entry->var.VariableName, efi_name_old, + utf16_strlen(efi_name_old))) + return 0; + } + + /* found */ + __efivar_entry_delete(entry); + return 1; +} + +static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count, + struct timespec time, struct pstore_info *psi) +{ + struct pstore_erase_data edata; + struct efivar_entry *entry; + char name[DUMP_NAME_LEN]; + efi_char16_t efi_name[DUMP_NAME_LEN]; + int found, i; + + sprintf(name, "dump-type%u-%u-%d-%lu", type, (unsigned int)id, count, + time.tv_sec); + + for (i = 0; i < DUMP_NAME_LEN; i++) + efi_name[i] = name[i]; + + edata.id = id; + edata.type = type; + edata.count = count; + edata.time = time; + edata.name = efi_name; + + efivar_entry_iter_begin(); + found = __efivar_entry_iter(efi_pstore_erase_func, &efivar_sysfs_list, &edata, &entry); + efivar_entry_iter_end(); + + if (found) + efivar_unregister(entry); + + return 0; +} + +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 __init int efivars_pstore_init(void) +{ + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + return 0; + + if (!efivars_kobject()) + return 0; + + if (efivars_pstore_disable) + return 0; + + efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL); + if (!efi_pstore_info.buf) + return -ENOMEM; + + efi_pstore_info.bufsize = 1024; + spin_lock_init(&efi_pstore_info.buf_lock); + + pstore_register(&efi_pstore_info); + + return 0; +} + +static __exit void efivars_pstore_exit(void) +{ +} + +module_init(efivars_pstore_init); +module_exit(efivars_pstore_exit); + +MODULE_DESCRIPTION("EFI variable backend for pstore"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 82fd145ead3b..5e7c3b1acde9 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -78,7 +78,6 @@ #include #include #include -#include #include #include @@ -95,15 +94,9 @@ MODULE_DESCRIPTION("sysfs interface to EFI Variables"); MODULE_LICENSE("GPL"); MODULE_VERSION(EFIVARS_VERSION); -#define DUMP_NAME_LEN 52 - static LIST_HEAD(efivarfs_list); -static LIST_HEAD(efivar_sysfs_list); - -static bool efivars_pstore_disable = - IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE); - -module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644); +LIST_HEAD(efivar_sysfs_list); +EXPORT_SYMBOL_GPL(efivar_sysfs_list); struct efivar_attribute { struct attribute attr; @@ -114,11 +107,6 @@ struct efivar_attribute { /* Private pointer to registered efivars */ static struct efivars *__efivars; -#define PSTORE_EFI_ATTRIBUTES \ - (EFI_VARIABLE_NON_VOLATILE | \ - EFI_VARIABLE_BOOTSERVICE_ACCESS | \ - EFI_VARIABLE_RUNTIME_ACCESS) - static struct kset *efivars_kset; static struct bin_attribute *efivars_new_var; @@ -148,34 +136,6 @@ static void efivar_update_sysfs_entries(struct work_struct *); static DECLARE_WORK(efivar_work, efivar_update_sysfs_entries); static bool efivar_wq_enabled = true; -/* - * 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 -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 bool validate_device_path(struct efi_variable *var, int match, u8 *buffer, unsigned long len) @@ -598,12 +558,6 @@ static struct kobj_type efivar_ktype = { .default_attrs = def_attrs, }; -static inline void -efivar_unregister(struct efivar_entry *var) -{ - kobject_put(&var->kobj); -} - static int efivarfs_file_open(struct inode *inode, struct file *file) { file->private_data = inode->i_private; @@ -1130,220 +1084,6 @@ static const struct inode_operations efivarfs_dir_inode_operations = { .create = efivarfs_create, }; -#ifdef CONFIG_EFI_VARS_PSTORE - -static int efi_pstore_open(struct pstore_info *psi) -{ - efivar_entry_iter_begin(); - psi->data = NULL; - return 0; -} - -static int efi_pstore_close(struct pstore_info *psi) -{ - efivar_entry_iter_end(); - psi->data = NULL; - return 0; -} - -struct pstore_read_data { - u64 *id; - enum pstore_type_id *type; - int *count; - struct timespec *timespec; - char **buf; -}; - -static int efi_pstore_read_func(struct efivar_entry *entry, void *data) -{ - efi_guid_t vendor = LINUX_EFI_CRASH_GUID; - struct pstore_read_data *cb_data = data; - char name[DUMP_NAME_LEN]; - int i; - int cnt; - unsigned int part; - unsigned long time, size; - - if (efi_guidcmp(entry->var.VendorGuid, vendor)) - return 0; - - for (i = 0; i < DUMP_NAME_LEN; i++) - name[i] = entry->var.VariableName[i]; - - if (sscanf(name, "dump-type%u-%u-%d-%lu", - cb_data->type, &part, &cnt, &time) == 4) { - *cb_data->id = part; - *cb_data->count = cnt; - cb_data->timespec->tv_sec = time; - cb_data->timespec->tv_nsec = 0; - } else if (sscanf(name, "dump-type%u-%u-%lu", - cb_data->type, &part, &time) == 3) { - /* - * Check if an old format, - * which doesn't support holding - * multiple logs, remains. - */ - *cb_data->id = part; - *cb_data->count = 0; - cb_data->timespec->tv_sec = time; - cb_data->timespec->tv_nsec = 0; - } else - return 0; - - __efivar_entry_size(entry, &size); - *cb_data->buf = kmalloc(size, GFP_KERNEL); - if (*cb_data->buf == NULL) - return -ENOMEM; - memcpy(*cb_data->buf, entry->var.Data, size); - return size; -} - -static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, - int *count, struct timespec *timespec, - char **buf, struct pstore_info *psi) -{ - struct pstore_read_data data; - - data.id = id; - data.type = type; - data.count = count; - data.timespec = timespec; - data.buf = buf; - - return __efivar_entry_iter(efi_pstore_read_func, &efivar_sysfs_list, &data, - (struct efivar_entry **)&psi->data); -} - -static int efi_pstore_write(enum pstore_type_id type, - enum kmsg_dump_reason reason, u64 *id, - unsigned int part, int count, size_t size, - struct pstore_info *psi) -{ - char name[DUMP_NAME_LEN]; - efi_char16_t efi_name[DUMP_NAME_LEN]; - efi_guid_t vendor = LINUX_EFI_CRASH_GUID; - int i, ret = 0; - - sprintf(name, "dump-type%u-%u-%d-%lu", type, part, count, - get_seconds()); - - for (i = 0; i < DUMP_NAME_LEN; i++) - efi_name[i] = name[i]; - - ret = efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES, - !pstore_cannot_block_path(reason), - size, psi->buf); - - if (reason == KMSG_DUMP_OOPS && efivar_wq_enabled) - schedule_work(&efivar_work); - - *id = part; - return ret; -}; - -struct pstore_erase_data { - u64 id; - enum pstore_type_id type; - int count; - struct timespec time; - efi_char16_t *name; -}; - -/* - * Clean up an entry with the same name - */ -static int efi_pstore_erase_func(struct efivar_entry *entry, void *data) -{ - struct pstore_erase_data *ed = data; - efi_guid_t vendor = LINUX_EFI_CRASH_GUID; - efi_char16_t efi_name_old[DUMP_NAME_LEN]; - efi_char16_t *efi_name = ed->name; - unsigned long utf16_len = utf16_strlen(ed->name); - char name_old[DUMP_NAME_LEN]; - int i; - - if (efi_guidcmp(entry->var.VendorGuid, vendor)) - return 0; - - if (utf16_strncmp(entry->var.VariableName, - efi_name, (size_t)utf16_len)) { - /* - * Check if an old format, which doesn't support - * holding multiple logs, remains. - */ - sprintf(name_old, "dump-type%u-%u-%lu", ed->type, - (unsigned int)ed->id, ed->time.tv_sec); - - for (i = 0; i < DUMP_NAME_LEN; i++) - efi_name_old[i] = name_old[i]; - - if (utf16_strncmp(entry->var.VariableName, efi_name_old, - utf16_strlen(efi_name_old))) - return 0; - } - - /* found */ - __efivar_entry_delete(entry); - return 1; -} - -static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count, - struct timespec time, struct pstore_info *psi) -{ - struct pstore_erase_data edata; - struct efivar_entry *entry; - char name[DUMP_NAME_LEN]; - efi_char16_t efi_name[DUMP_NAME_LEN]; - int found, i; - - sprintf(name, "dump-type%u-%u-%d-%lu", type, (unsigned int)id, count, - time.tv_sec); - - for (i = 0; i < DUMP_NAME_LEN; i++) - efi_name[i] = name[i]; - - edata.id = id; - edata.type = type; - edata.count = count; - edata.time = time; - edata.name = efi_name; - - efivar_entry_iter_begin(); - found = __efivar_entry_iter(efi_pstore_erase_func, &efivar_sysfs_list, &edata, &entry); - efivar_entry_iter_end(); - - if (found) - efivar_unregister(entry); - - return 0; -} - -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 void efivar_pstore_register(void) -{ - efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL); - if (efi_pstore_info.buf) { - efi_pstore_info.bufsize = 1024; - spin_lock_init(&efi_pstore_info.buf_lock); - pstore_register(&efi_pstore_info); - } -} -#else -static void efivar_pstore_register(void) -{ - return; -} -#endif - static ssize_t efivar_create(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) @@ -2396,6 +2136,16 @@ struct kobject *efivars_kobject(void) } EXPORT_SYMBOL_GPL(efivars_kobject); +/** + * efivar_run_worker - schedule the efivar worker thread + */ +void efivar_run_worker(void) +{ + if (efivar_wq_enabled) + schedule_work(&efivar_work); +} +EXPORT_SYMBOL_GPL(efivar_run_worker); + /** * efivars_register - register an efivars * @efivars: efivars to register @@ -2414,9 +2164,6 @@ int efivars_register(struct efivars *efivars, __efivars = efivars; - if (!efivars_pstore_disable) - efivar_pstore_register(); - register_filesystem(&efivarfs_type); return 0; diff --git a/include/linux/efi.h b/include/linux/efi.h index cd561b301e6a..8ff6ec1ac046 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -743,6 +743,34 @@ utf16_strlen(efi_char16_t *s) } #if defined(CONFIG_EFI_VARS) || defined(CONFIG_EFI_VARS_MODULE) +/* + * 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 +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--; + } +} + /* * EFI Variable support. * @@ -795,6 +823,14 @@ struct efivar_entry { struct kobject kobj; }; +extern struct list_head efivar_sysfs_list; + +static inline void +efivar_unregister(struct efivar_entry *var) +{ + kobject_put(&var->kobj); +} + int efivars_register(struct efivars *efivars, const struct efivar_operations *ops, struct kobject *kobject); @@ -836,6 +872,8 @@ struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid, bool efivar_validate(struct efi_variable *var, u8 *data, unsigned long len); +void efivar_run_worker(void); + int efivars_sysfs_init(void); #endif /* CONFIG_EFI_VARS */