| // SPDX-License-Identifier: GPL-2.0+ | |
| #include <linux/efi.h> | |
| #include <linux/module.h> | |
| #include <linux/pstore.h> | |
| #include <linux/slab.h> | |
| #include <linux/ucs2_string.h> | |
| MODULE_IMPORT_NS("EFIVAR"); | |
| #define DUMP_NAME_LEN 66 | |
| static unsigned int record_size = 1024; | |
| module_param(record_size, uint, 0444); | |
| MODULE_PARM_DESC(record_size, "size of each pstore UEFI var (in bytes, min/default=1024)"); | |
| #define PSTORE_EFI_ATTRIBUTES \ | |
| (EFI_VARIABLE_NON_VOLATILE | \ | |
| EFI_VARIABLE_BOOTSERVICE_ACCESS | \ | |
| EFI_VARIABLE_RUNTIME_ACCESS) | |
| static bool pstore_disable = IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE); | |
| static int efivars_pstore_init(void); | |
| static void efivars_pstore_exit(void); | |
| static int efi_pstore_disable_set(const char *val, const struct kernel_param *kp) | |
| { | |
| int err; | |
| bool old_pstore_disable = pstore_disable; | |
| err = param_set_bool(val, kp); | |
| if (err) | |
| return err; | |
| if (old_pstore_disable != pstore_disable) { | |
| if (pstore_disable) | |
| efivars_pstore_exit(); | |
| else | |
| efivars_pstore_init(); | |
| } | |
| return 0; | |
| } | |
| static const struct kernel_param_ops pstore_disable_ops = { | |
| .set = efi_pstore_disable_set, | |
| .get = param_get_bool, | |
| }; | |
| module_param_cb(pstore_disable, &pstore_disable_ops, &pstore_disable, 0644); | |
| __MODULE_PARM_TYPE(pstore_disable, "bool"); | |
| static int efi_pstore_open(struct pstore_info *psi) | |
| { | |
| int err; | |
| err = efivar_lock(); | |
| if (err) | |
| return err; | |
| psi->data = kzalloc(record_size, GFP_KERNEL); | |
| if (!psi->data) { | |
| efivar_unlock(); | |
| return -ENOMEM; | |
| } | |
| return 0; | |
| } | |
| static int efi_pstore_close(struct pstore_info *psi) | |
| { | |
| efivar_unlock(); | |
| kfree(psi->data); | |
| return 0; | |
| } | |
| static inline u64 generic_id(u64 timestamp, unsigned int part, int count) | |
| { | |
| return (timestamp * 100 + part) * 1000 + count; | |
| } | |
| static int efi_pstore_read_func(struct pstore_record *record, | |
| efi_char16_t *varname) | |
| { | |
| unsigned long wlen, size = record_size; | |
| char name[DUMP_NAME_LEN], data_type; | |
| efi_status_t status; | |
| int cnt; | |
| unsigned int part; | |
| u64 time; | |
| ucs2_as_utf8(name, varname, DUMP_NAME_LEN); | |
| if (sscanf(name, "dump-type%u-%u-%d-%llu-%c", | |
| &record->type, &part, &cnt, &time, &data_type) == 5) { | |
| record->id = generic_id(time, part, cnt); | |
| record->part = part; | |
| record->count = cnt; | |
| record->time.tv_sec = time; | |
| record->time.tv_nsec = 0; | |
| if (data_type == 'C') | |
| record->compressed = true; | |
| else | |
| record->compressed = false; | |
| record->ecc_notice_size = 0; | |
| } else if (sscanf(name, "dump-type%u-%u-%d-%llu", | |
| &record->type, &part, &cnt, &time) == 4) { | |
| record->id = generic_id(time, part, cnt); | |
| record->part = part; | |
| record->count = cnt; | |
| record->time.tv_sec = time; | |
| record->time.tv_nsec = 0; | |
| record->compressed = false; | |
| record->ecc_notice_size = 0; | |
| } else if (sscanf(name, "dump-type%u-%u-%llu", | |
| &record->type, &part, &time) == 3) { | |
| /* | |
| * Check if an old format, | |
| * which doesn't support holding | |
| * multiple logs, remains. | |
| */ | |
| record->id = generic_id(time, part, 0); | |
| record->part = part; | |
| record->count = 0; | |
| record->time.tv_sec = time; | |
| record->time.tv_nsec = 0; | |
| record->compressed = false; | |
| record->ecc_notice_size = 0; | |
| } else | |
| return 0; | |
| record->buf = kmalloc(size, GFP_KERNEL); | |
| if (!record->buf) | |
| return -ENOMEM; | |
| status = efivar_get_variable(varname, &LINUX_EFI_CRASH_GUID, NULL, | |
| &size, record->buf); | |
| if (status != EFI_SUCCESS) { | |
| kfree(record->buf); | |
| return efi_status_to_err(status); | |
| } | |
| /* | |
| * Store the name of the variable in the pstore_record priv field, so | |
| * we can reuse it later if we need to delete the EFI variable from the | |
| * variable store. | |
| */ | |
| wlen = (ucs2_strnlen(varname, DUMP_NAME_LEN) + 1) * sizeof(efi_char16_t); | |
| record->priv = kmemdup(varname, wlen, GFP_KERNEL); | |
| if (!record->priv) { | |
| kfree(record->buf); | |
| return -ENOMEM; | |
| } | |
| return size; | |
| } | |
| static ssize_t efi_pstore_read(struct pstore_record *record) | |
| { | |
| efi_char16_t *varname = record->psi->data; | |
| efi_guid_t guid = LINUX_EFI_CRASH_GUID; | |
| unsigned long varname_size; | |
| efi_status_t status; | |
| for (;;) { | |
| /* | |
| * A small set of old UEFI implementations reject sizes | |
| * above a certain threshold, the lowest seen in the wild | |
| * is 512. | |
| * | |
| * TODO: Commonize with the iteration implementation in | |
| * fs/efivarfs to keep all the quirks in one place. | |
| */ | |
| varname_size = 512; | |
| /* | |
| * If this is the first read() call in the pstore enumeration, | |
| * varname will be the empty string, and the GetNextVariable() | |
| * runtime service call will return the first EFI variable in | |
| * its own enumeration order, ignoring the guid argument. | |
| * | |
| * Subsequent calls to GetNextVariable() must pass the name and | |
| * guid values returned by the previous call, which is why we | |
| * store varname in record->psi->data. Given that we only | |
| * enumerate variables with the efi-pstore GUID, there is no | |
| * need to record the guid return value. | |
| */ | |
| status = efivar_get_next_variable(&varname_size, varname, &guid); | |
| if (status == EFI_NOT_FOUND) | |
| return 0; | |
| if (status != EFI_SUCCESS) | |
| return efi_status_to_err(status); | |
| /* skip variables that don't concern us */ | |
| if (efi_guidcmp(guid, LINUX_EFI_CRASH_GUID)) | |
| continue; | |
| return efi_pstore_read_func(record, varname); | |
| } | |
| } | |
| static int efi_pstore_write(struct pstore_record *record) | |
| { | |
| char name[DUMP_NAME_LEN]; | |
| efi_char16_t efi_name[DUMP_NAME_LEN]; | |
| efi_status_t status; | |
| int i; | |
| record->id = generic_id(record->time.tv_sec, record->part, | |
| record->count); | |
| /* Since we copy the entire length of name, make sure it is wiped. */ | |
| memset(name, 0, sizeof(name)); | |
| snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lld-%c", | |
| record->type, record->part, record->count, | |
| (long long)record->time.tv_sec, | |
| record->compressed ? 'C' : 'D'); | |
| for (i = 0; i < DUMP_NAME_LEN; i++) | |
| efi_name[i] = name[i]; | |
| if (efivar_trylock()) | |
| return -EBUSY; | |
| status = efivar_set_variable_locked(efi_name, &LINUX_EFI_CRASH_GUID, | |
| PSTORE_EFI_ATTRIBUTES, | |
| record->size, record->psi->buf, | |
| true); | |
| efivar_unlock(); | |
| return efi_status_to_err(status); | |
| }; | |
| static int efi_pstore_erase(struct pstore_record *record) | |
| { | |
| efi_status_t status; | |
| status = efivar_set_variable(record->priv, &LINUX_EFI_CRASH_GUID, | |
| PSTORE_EFI_ATTRIBUTES, 0, NULL); | |
| if (status != EFI_SUCCESS && status != EFI_NOT_FOUND) | |
| return efi_status_to_err(status); | |
| return 0; | |
| } | |
| static struct pstore_info efi_pstore_info = { | |
| .owner = THIS_MODULE, | |
| .name = KBUILD_MODNAME, | |
| .flags = PSTORE_FLAGS_DMESG, | |
| .open = efi_pstore_open, | |
| .close = efi_pstore_close, | |
| .read = efi_pstore_read, | |
| .write = efi_pstore_write, | |
| .erase = efi_pstore_erase, | |
| }; | |
| static int efivars_pstore_init(void) | |
| { | |
| if (!efivar_supports_writes()) | |
| return 0; | |
| if (pstore_disable) | |
| return 0; | |
| /* | |
| * Notice that 1024 is the minimum here to prevent issues with | |
| * decompression algorithms that were spotted during tests; | |
| * even in the case of not using compression, smaller values would | |
| * just pollute more the pstore FS with many small collected files. | |
| */ | |
| if (record_size < 1024) | |
| record_size = 1024; | |
| efi_pstore_info.buf = kmalloc(record_size, GFP_KERNEL); | |
| if (!efi_pstore_info.buf) | |
| return -ENOMEM; | |
| efi_pstore_info.bufsize = record_size; | |
| if (pstore_register(&efi_pstore_info)) { | |
| kfree(efi_pstore_info.buf); | |
| efi_pstore_info.buf = NULL; | |
| efi_pstore_info.bufsize = 0; | |
| } | |
| return 0; | |
| } | |
| static void efivars_pstore_exit(void) | |
| { | |
| if (!efi_pstore_info.bufsize) | |
| return; | |
| pstore_unregister(&efi_pstore_info); | |
| kfree(efi_pstore_info.buf); | |
| efi_pstore_info.buf = NULL; | |
| efi_pstore_info.bufsize = 0; | |
| } | |
| module_init(efivars_pstore_init); | |
| module_exit(efivars_pstore_exit); | |
| MODULE_DESCRIPTION("EFI variable backend for pstore"); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_ALIAS("platform:efivars"); |