14febfb8dSArd Biesheuvel // SPDX-License-Identifier: GPL-2.0+ 24febfb8dSArd Biesheuvel 304851772SMatt Fleming #include <linux/efi.h> 404851772SMatt Fleming #include <linux/module.h> 504851772SMatt Fleming #include <linux/pstore.h> 620b4fb48SLinus Torvalds #include <linux/slab.h> 7a614e192SMatt Fleming #include <linux/ucs2_string.h> 804851772SMatt Fleming 985974825SArd Biesheuvel MODULE_IMPORT_NS(EFIVAR); 1085974825SArd Biesheuvel 11efb74e4bSKees Cook #define DUMP_NAME_LEN 66 1204851772SMatt Fleming 13232f4eb6SArd Biesheuvel #define EFIVARS_DATA_SIZE_MAX 1024 14232f4eb6SArd Biesheuvel 1504851772SMatt Fleming static bool efivars_pstore_disable = 1604851772SMatt Fleming IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE); 1704851772SMatt Fleming 1804851772SMatt Fleming module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644); 1904851772SMatt Fleming 2004851772SMatt Fleming #define PSTORE_EFI_ATTRIBUTES \ 2104851772SMatt Fleming (EFI_VARIABLE_NON_VOLATILE | \ 2204851772SMatt Fleming EFI_VARIABLE_BOOTSERVICE_ACCESS | \ 2304851772SMatt Fleming EFI_VARIABLE_RUNTIME_ACCESS) 2404851772SMatt Fleming 2504851772SMatt Fleming static int efi_pstore_open(struct pstore_info *psi) 2604851772SMatt Fleming { 2785974825SArd Biesheuvel int err; 2885974825SArd Biesheuvel 2985974825SArd Biesheuvel err = efivar_lock(); 3085974825SArd Biesheuvel if (err) 3185974825SArd Biesheuvel return err; 3285974825SArd Biesheuvel 3385974825SArd Biesheuvel psi->data = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL); 3485974825SArd Biesheuvel if (!psi->data) 3585974825SArd Biesheuvel return -ENOMEM; 3685974825SArd Biesheuvel 3704851772SMatt Fleming return 0; 3804851772SMatt Fleming } 3904851772SMatt Fleming 4004851772SMatt Fleming static int efi_pstore_close(struct pstore_info *psi) 4104851772SMatt Fleming { 4285974825SArd Biesheuvel efivar_unlock(); 4385974825SArd Biesheuvel kfree(psi->data); 4404851772SMatt Fleming return 0; 4504851772SMatt Fleming } 4604851772SMatt Fleming 477aaa822eSKees Cook static inline u64 generic_id(u64 timestamp, unsigned int part, int count) 48fdeadb43SMadper Xie { 497aaa822eSKees Cook return (timestamp * 100 + part) * 1000 + count; 50fdeadb43SMadper Xie } 51fdeadb43SMadper Xie 5285974825SArd Biesheuvel static int efi_pstore_read_func(struct pstore_record *record, 5385974825SArd Biesheuvel efi_char16_t *varname) 5404851772SMatt Fleming { 5585974825SArd Biesheuvel unsigned long wlen, size = EFIVARS_DATA_SIZE_MAX; 56f8c62f34SAruna Balakrishnaiah char name[DUMP_NAME_LEN], data_type; 5785974825SArd Biesheuvel efi_status_t status; 5804851772SMatt Fleming int cnt; 5904851772SMatt Fleming unsigned int part; 607aaa822eSKees Cook u64 time; 6104851772SMatt Fleming 6285974825SArd Biesheuvel ucs2_as_utf8(name, varname, DUMP_NAME_LEN); 6304851772SMatt Fleming 647aaa822eSKees Cook if (sscanf(name, "dump-type%u-%u-%d-%llu-%c", 65125cc42bSKees Cook &record->type, &part, &cnt, &time, &data_type) == 5) { 66125cc42bSKees Cook record->id = generic_id(time, part, cnt); 67c10e8031SKees Cook record->part = part; 68125cc42bSKees Cook record->count = cnt; 69125cc42bSKees Cook record->time.tv_sec = time; 70125cc42bSKees Cook record->time.tv_nsec = 0; 71f8c62f34SAruna Balakrishnaiah if (data_type == 'C') 72125cc42bSKees Cook record->compressed = true; 73f8c62f34SAruna Balakrishnaiah else 74125cc42bSKees Cook record->compressed = false; 75125cc42bSKees Cook record->ecc_notice_size = 0; 767aaa822eSKees Cook } else if (sscanf(name, "dump-type%u-%u-%d-%llu", 77125cc42bSKees Cook &record->type, &part, &cnt, &time) == 4) { 78125cc42bSKees Cook record->id = generic_id(time, part, cnt); 79c10e8031SKees Cook record->part = part; 80125cc42bSKees Cook record->count = cnt; 81125cc42bSKees Cook record->time.tv_sec = time; 82125cc42bSKees Cook record->time.tv_nsec = 0; 83125cc42bSKees Cook record->compressed = false; 84125cc42bSKees Cook record->ecc_notice_size = 0; 857aaa822eSKees Cook } else if (sscanf(name, "dump-type%u-%u-%llu", 86125cc42bSKees Cook &record->type, &part, &time) == 3) { 8704851772SMatt Fleming /* 8804851772SMatt Fleming * Check if an old format, 8904851772SMatt Fleming * which doesn't support holding 9004851772SMatt Fleming * multiple logs, remains. 9104851772SMatt Fleming */ 92125cc42bSKees Cook record->id = generic_id(time, part, 0); 93c10e8031SKees Cook record->part = part; 94125cc42bSKees Cook record->count = 0; 95125cc42bSKees Cook record->time.tv_sec = time; 96125cc42bSKees Cook record->time.tv_nsec = 0; 97125cc42bSKees Cook record->compressed = false; 98125cc42bSKees Cook record->ecc_notice_size = 0; 9904851772SMatt Fleming } else 10004851772SMatt Fleming return 0; 10104851772SMatt Fleming 10285974825SArd Biesheuvel record->buf = kmalloc(size, GFP_KERNEL); 103125cc42bSKees Cook if (!record->buf) 104e0d59733SSeiji Aguchi return -ENOMEM; 105e0d59733SSeiji Aguchi 10685974825SArd Biesheuvel status = efivar_get_variable(varname, &LINUX_EFI_CRASH_GUID, NULL, 10785974825SArd Biesheuvel &size, record->buf); 10885974825SArd Biesheuvel if (status != EFI_SUCCESS) { 109125cc42bSKees Cook kfree(record->buf); 11085974825SArd Biesheuvel return -EIO; 111125cc42bSKees Cook } 11285974825SArd Biesheuvel 11385974825SArd Biesheuvel /* 11485974825SArd Biesheuvel * Store the name of the variable in the pstore_record priv field, so 11585974825SArd Biesheuvel * we can reuse it later if we need to delete the EFI variable from the 11685974825SArd Biesheuvel * variable store. 11785974825SArd Biesheuvel */ 11885974825SArd Biesheuvel wlen = (ucs2_strnlen(varname, DUMP_NAME_LEN) + 1) * sizeof(efi_char16_t); 11985974825SArd Biesheuvel record->priv = kmemdup(varname, wlen, GFP_KERNEL); 12085974825SArd Biesheuvel if (!record->priv) { 12185974825SArd Biesheuvel kfree(record->buf); 12285974825SArd Biesheuvel return -ENOMEM; 12385974825SArd Biesheuvel } 12485974825SArd Biesheuvel 125e0d59733SSeiji Aguchi return size; 12604851772SMatt Fleming } 12704851772SMatt Fleming 12885974825SArd Biesheuvel static ssize_t efi_pstore_read(struct pstore_record *record) 12985974825SArd Biesheuvel { 13085974825SArd Biesheuvel efi_char16_t *varname = record->psi->data; 13185974825SArd Biesheuvel efi_guid_t guid = LINUX_EFI_CRASH_GUID; 13285974825SArd Biesheuvel unsigned long varname_size; 13385974825SArd Biesheuvel efi_status_t status; 13485974825SArd Biesheuvel 13585974825SArd Biesheuvel for (;;) { 13685974825SArd Biesheuvel varname_size = EFIVARS_DATA_SIZE_MAX; 13785974825SArd Biesheuvel 13885974825SArd Biesheuvel /* 13985974825SArd Biesheuvel * If this is the first read() call in the pstore enumeration, 14085974825SArd Biesheuvel * varname will be the empty string, and the GetNextVariable() 14185974825SArd Biesheuvel * runtime service call will return the first EFI variable in 14285974825SArd Biesheuvel * its own enumeration order, ignoring the guid argument. 14385974825SArd Biesheuvel * 14485974825SArd Biesheuvel * Subsequent calls to GetNextVariable() must pass the name and 14585974825SArd Biesheuvel * guid values returned by the previous call, which is why we 14685974825SArd Biesheuvel * store varname in record->psi->data. Given that we only 14785974825SArd Biesheuvel * enumerate variables with the efi-pstore GUID, there is no 14885974825SArd Biesheuvel * need to record the guid return value. 14985974825SArd Biesheuvel */ 15085974825SArd Biesheuvel status = efivar_get_next_variable(&varname_size, varname, &guid); 15185974825SArd Biesheuvel if (status == EFI_NOT_FOUND) 15285974825SArd Biesheuvel return 0; 15385974825SArd Biesheuvel 15485974825SArd Biesheuvel if (status != EFI_SUCCESS) 15585974825SArd Biesheuvel return -EIO; 15685974825SArd Biesheuvel 15785974825SArd Biesheuvel /* skip variables that don't concern us */ 15885974825SArd Biesheuvel if (efi_guidcmp(guid, LINUX_EFI_CRASH_GUID)) 15985974825SArd Biesheuvel continue; 16085974825SArd Biesheuvel 16185974825SArd Biesheuvel return efi_pstore_read_func(record, varname); 16285974825SArd Biesheuvel } 16385974825SArd Biesheuvel } 16485974825SArd Biesheuvel 16576cc9580SKees Cook static int efi_pstore_write(struct pstore_record *record) 16604851772SMatt Fleming { 16704851772SMatt Fleming char name[DUMP_NAME_LEN]; 16804851772SMatt Fleming efi_char16_t efi_name[DUMP_NAME_LEN]; 16985974825SArd Biesheuvel efi_status_t status; 17085974825SArd Biesheuvel int i; 17104851772SMatt Fleming 172c10e8031SKees Cook record->id = generic_id(record->time.tv_sec, record->part, 173c10e8031SKees Cook record->count); 174c10e8031SKees Cook 175efb74e4bSKees Cook /* Since we copy the entire length of name, make sure it is wiped. */ 176efb74e4bSKees Cook memset(name, 0, sizeof(name)); 177efb74e4bSKees Cook 1787aaa822eSKees Cook snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lld-%c", 17976cc9580SKees Cook record->type, record->part, record->count, 1807aaa822eSKees Cook (long long)record->time.tv_sec, 1817aaa822eSKees Cook record->compressed ? 'C' : 'D'); 18204851772SMatt Fleming 18304851772SMatt Fleming for (i = 0; i < DUMP_NAME_LEN; i++) 18404851772SMatt Fleming efi_name[i] = name[i]; 18504851772SMatt Fleming 18685974825SArd Biesheuvel if (efivar_trylock()) 18785974825SArd Biesheuvel return -EBUSY; 18885974825SArd Biesheuvel status = efivar_set_variable_locked(efi_name, &LINUX_EFI_CRASH_GUID, 18985974825SArd Biesheuvel PSTORE_EFI_ATTRIBUTES, 19085974825SArd Biesheuvel record->size, record->psi->buf, 19185974825SArd Biesheuvel true); 19285974825SArd Biesheuvel efivar_unlock(); 19385974825SArd Biesheuvel return status == EFI_SUCCESS ? 0 : -EIO; 19404851772SMatt Fleming }; 19504851772SMatt Fleming 196efb74e4bSKees Cook static int efi_pstore_erase(struct pstore_record *record) 197efb74e4bSKees Cook { 19885974825SArd Biesheuvel efi_status_t status; 199efb74e4bSKees Cook 20085974825SArd Biesheuvel status = efivar_set_variable(record->priv, &LINUX_EFI_CRASH_GUID, 20185974825SArd Biesheuvel PSTORE_EFI_ATTRIBUTES, 0, NULL); 20204851772SMatt Fleming 20385974825SArd Biesheuvel if (status != EFI_SUCCESS && status != EFI_NOT_FOUND) 20485974825SArd Biesheuvel return -EIO; 20585974825SArd Biesheuvel return 0; 20604851772SMatt Fleming } 20704851772SMatt Fleming 20804851772SMatt Fleming static struct pstore_info efi_pstore_info = { 20904851772SMatt Fleming .owner = THIS_MODULE, 210*893c5f1dSGuilherme G. Piccoli .name = KBUILD_MODNAME, 211c950fd6fSNamhyung Kim .flags = PSTORE_FLAGS_DMESG, 21204851772SMatt Fleming .open = efi_pstore_open, 21304851772SMatt Fleming .close = efi_pstore_close, 21404851772SMatt Fleming .read = efi_pstore_read, 21504851772SMatt Fleming .write = efi_pstore_write, 21604851772SMatt Fleming .erase = efi_pstore_erase, 21704851772SMatt Fleming }; 21804851772SMatt Fleming 21904851772SMatt Fleming static __init int efivars_pstore_init(void) 22004851772SMatt Fleming { 22185974825SArd Biesheuvel if (!efivar_supports_writes()) 22204851772SMatt Fleming return 0; 22304851772SMatt Fleming 22404851772SMatt Fleming if (efivars_pstore_disable) 22504851772SMatt Fleming return 0; 22604851772SMatt Fleming 22704851772SMatt Fleming efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL); 22804851772SMatt Fleming if (!efi_pstore_info.buf) 22904851772SMatt Fleming return -ENOMEM; 23004851772SMatt Fleming 23104851772SMatt Fleming efi_pstore_info.bufsize = 1024; 23204851772SMatt Fleming 2330d838347SLenny Szubowicz if (pstore_register(&efi_pstore_info)) { 2340d838347SLenny Szubowicz kfree(efi_pstore_info.buf); 2350d838347SLenny Szubowicz efi_pstore_info.buf = NULL; 2360d838347SLenny Szubowicz efi_pstore_info.bufsize = 0; 2370d838347SLenny Szubowicz } 23804851772SMatt Fleming 23904851772SMatt Fleming return 0; 24004851772SMatt Fleming } 24104851772SMatt Fleming 24204851772SMatt Fleming static __exit void efivars_pstore_exit(void) 24304851772SMatt Fleming { 244cae73167SGeliang Tang if (!efi_pstore_info.bufsize) 245cae73167SGeliang Tang return; 246cae73167SGeliang Tang 247cae73167SGeliang Tang pstore_unregister(&efi_pstore_info); 248cae73167SGeliang Tang kfree(efi_pstore_info.buf); 249cae73167SGeliang Tang efi_pstore_info.buf = NULL; 250cae73167SGeliang Tang efi_pstore_info.bufsize = 0; 25104851772SMatt Fleming } 25204851772SMatt Fleming 25304851772SMatt Fleming module_init(efivars_pstore_init); 25404851772SMatt Fleming module_exit(efivars_pstore_exit); 25504851772SMatt Fleming 25604851772SMatt Fleming MODULE_DESCRIPTION("EFI variable backend for pstore"); 25704851772SMatt Fleming MODULE_LICENSE("GPL"); 2589ac4d5abSBen Hutchings MODULE_ALIAS("platform:efivars"); 259