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 1336d5786aSGuilherme G. Piccoli static unsigned int record_size = 1024; 1436d5786aSGuilherme G. Piccoli module_param(record_size, uint, 0444); 1536d5786aSGuilherme G. Piccoli MODULE_PARM_DESC(record_size, "size of each pstore UEFI var (in bytes, min/default=1024)"); 16232f4eb6SArd Biesheuvel 1704851772SMatt Fleming #define PSTORE_EFI_ATTRIBUTES \ 1804851772SMatt Fleming (EFI_VARIABLE_NON_VOLATILE | \ 1904851772SMatt Fleming EFI_VARIABLE_BOOTSERVICE_ACCESS | \ 2004851772SMatt Fleming EFI_VARIABLE_RUNTIME_ACCESS) 2104851772SMatt Fleming 22*a28655c3SGuilherme G. Piccoli static bool pstore_disable = IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE); 23*a28655c3SGuilherme G. Piccoli 24*a28655c3SGuilherme G. Piccoli static int efivars_pstore_init(void); 25*a28655c3SGuilherme G. Piccoli static void efivars_pstore_exit(void); 26*a28655c3SGuilherme G. Piccoli 27*a28655c3SGuilherme G. Piccoli static int efi_pstore_disable_set(const char *val, const struct kernel_param *kp) 28*a28655c3SGuilherme G. Piccoli { 29*a28655c3SGuilherme G. Piccoli int err; 30*a28655c3SGuilherme G. Piccoli bool old_pstore_disable = pstore_disable; 31*a28655c3SGuilherme G. Piccoli 32*a28655c3SGuilherme G. Piccoli err = param_set_bool(val, kp); 33*a28655c3SGuilherme G. Piccoli if (err) 34*a28655c3SGuilherme G. Piccoli return err; 35*a28655c3SGuilherme G. Piccoli 36*a28655c3SGuilherme G. Piccoli if (old_pstore_disable != pstore_disable) { 37*a28655c3SGuilherme G. Piccoli if (pstore_disable) 38*a28655c3SGuilherme G. Piccoli efivars_pstore_exit(); 39*a28655c3SGuilherme G. Piccoli else 40*a28655c3SGuilherme G. Piccoli efivars_pstore_init(); 41*a28655c3SGuilherme G. Piccoli } 42*a28655c3SGuilherme G. Piccoli 43*a28655c3SGuilherme G. Piccoli return 0; 44*a28655c3SGuilherme G. Piccoli } 45*a28655c3SGuilherme G. Piccoli 46*a28655c3SGuilherme G. Piccoli static const struct kernel_param_ops pstore_disable_ops = { 47*a28655c3SGuilherme G. Piccoli .set = efi_pstore_disable_set, 48*a28655c3SGuilherme G. Piccoli .get = param_get_bool, 49*a28655c3SGuilherme G. Piccoli }; 50*a28655c3SGuilherme G. Piccoli 51*a28655c3SGuilherme G. Piccoli module_param_cb(pstore_disable, &pstore_disable_ops, &pstore_disable, 0644); 52*a28655c3SGuilherme G. Piccoli __MODULE_PARM_TYPE(pstore_disable, "bool"); 53*a28655c3SGuilherme G. Piccoli 5404851772SMatt Fleming static int efi_pstore_open(struct pstore_info *psi) 5504851772SMatt Fleming { 5685974825SArd Biesheuvel int err; 5785974825SArd Biesheuvel 5885974825SArd Biesheuvel err = efivar_lock(); 5985974825SArd Biesheuvel if (err) 6085974825SArd Biesheuvel return err; 6185974825SArd Biesheuvel 6236d5786aSGuilherme G. Piccoli psi->data = kzalloc(record_size, GFP_KERNEL); 6385974825SArd Biesheuvel if (!psi->data) 6485974825SArd Biesheuvel return -ENOMEM; 6585974825SArd Biesheuvel 6604851772SMatt Fleming return 0; 6704851772SMatt Fleming } 6804851772SMatt Fleming 6904851772SMatt Fleming static int efi_pstore_close(struct pstore_info *psi) 7004851772SMatt Fleming { 7185974825SArd Biesheuvel efivar_unlock(); 7285974825SArd Biesheuvel kfree(psi->data); 7304851772SMatt Fleming return 0; 7404851772SMatt Fleming } 7504851772SMatt Fleming 767aaa822eSKees Cook static inline u64 generic_id(u64 timestamp, unsigned int part, int count) 77fdeadb43SMadper Xie { 787aaa822eSKees Cook return (timestamp * 100 + part) * 1000 + count; 79fdeadb43SMadper Xie } 80fdeadb43SMadper Xie 8185974825SArd Biesheuvel static int efi_pstore_read_func(struct pstore_record *record, 8285974825SArd Biesheuvel efi_char16_t *varname) 8304851772SMatt Fleming { 8436d5786aSGuilherme G. Piccoli unsigned long wlen, size = record_size; 85f8c62f34SAruna Balakrishnaiah char name[DUMP_NAME_LEN], data_type; 8685974825SArd Biesheuvel efi_status_t status; 8704851772SMatt Fleming int cnt; 8804851772SMatt Fleming unsigned int part; 897aaa822eSKees Cook u64 time; 9004851772SMatt Fleming 9185974825SArd Biesheuvel ucs2_as_utf8(name, varname, DUMP_NAME_LEN); 9204851772SMatt Fleming 937aaa822eSKees Cook if (sscanf(name, "dump-type%u-%u-%d-%llu-%c", 94125cc42bSKees Cook &record->type, &part, &cnt, &time, &data_type) == 5) { 95125cc42bSKees Cook record->id = generic_id(time, part, cnt); 96c10e8031SKees Cook record->part = part; 97125cc42bSKees Cook record->count = cnt; 98125cc42bSKees Cook record->time.tv_sec = time; 99125cc42bSKees Cook record->time.tv_nsec = 0; 100f8c62f34SAruna Balakrishnaiah if (data_type == 'C') 101125cc42bSKees Cook record->compressed = true; 102f8c62f34SAruna Balakrishnaiah else 103125cc42bSKees Cook record->compressed = false; 104125cc42bSKees Cook record->ecc_notice_size = 0; 1057aaa822eSKees Cook } else if (sscanf(name, "dump-type%u-%u-%d-%llu", 106125cc42bSKees Cook &record->type, &part, &cnt, &time) == 4) { 107125cc42bSKees Cook record->id = generic_id(time, part, cnt); 108c10e8031SKees Cook record->part = part; 109125cc42bSKees Cook record->count = cnt; 110125cc42bSKees Cook record->time.tv_sec = time; 111125cc42bSKees Cook record->time.tv_nsec = 0; 112125cc42bSKees Cook record->compressed = false; 113125cc42bSKees Cook record->ecc_notice_size = 0; 1147aaa822eSKees Cook } else if (sscanf(name, "dump-type%u-%u-%llu", 115125cc42bSKees Cook &record->type, &part, &time) == 3) { 11604851772SMatt Fleming /* 11704851772SMatt Fleming * Check if an old format, 11804851772SMatt Fleming * which doesn't support holding 11904851772SMatt Fleming * multiple logs, remains. 12004851772SMatt Fleming */ 121125cc42bSKees Cook record->id = generic_id(time, part, 0); 122c10e8031SKees Cook record->part = part; 123125cc42bSKees Cook record->count = 0; 124125cc42bSKees Cook record->time.tv_sec = time; 125125cc42bSKees Cook record->time.tv_nsec = 0; 126125cc42bSKees Cook record->compressed = false; 127125cc42bSKees Cook record->ecc_notice_size = 0; 12804851772SMatt Fleming } else 12904851772SMatt Fleming return 0; 13004851772SMatt Fleming 13185974825SArd Biesheuvel record->buf = kmalloc(size, GFP_KERNEL); 132125cc42bSKees Cook if (!record->buf) 133e0d59733SSeiji Aguchi return -ENOMEM; 134e0d59733SSeiji Aguchi 13585974825SArd Biesheuvel status = efivar_get_variable(varname, &LINUX_EFI_CRASH_GUID, NULL, 13685974825SArd Biesheuvel &size, record->buf); 13785974825SArd Biesheuvel if (status != EFI_SUCCESS) { 138125cc42bSKees Cook kfree(record->buf); 13985974825SArd Biesheuvel return -EIO; 140125cc42bSKees Cook } 14185974825SArd Biesheuvel 14285974825SArd Biesheuvel /* 14385974825SArd Biesheuvel * Store the name of the variable in the pstore_record priv field, so 14485974825SArd Biesheuvel * we can reuse it later if we need to delete the EFI variable from the 14585974825SArd Biesheuvel * variable store. 14685974825SArd Biesheuvel */ 14785974825SArd Biesheuvel wlen = (ucs2_strnlen(varname, DUMP_NAME_LEN) + 1) * sizeof(efi_char16_t); 14885974825SArd Biesheuvel record->priv = kmemdup(varname, wlen, GFP_KERNEL); 14985974825SArd Biesheuvel if (!record->priv) { 15085974825SArd Biesheuvel kfree(record->buf); 15185974825SArd Biesheuvel return -ENOMEM; 15285974825SArd Biesheuvel } 15385974825SArd Biesheuvel 154e0d59733SSeiji Aguchi return size; 15504851772SMatt Fleming } 15604851772SMatt Fleming 15785974825SArd Biesheuvel static ssize_t efi_pstore_read(struct pstore_record *record) 15885974825SArd Biesheuvel { 15985974825SArd Biesheuvel efi_char16_t *varname = record->psi->data; 16085974825SArd Biesheuvel efi_guid_t guid = LINUX_EFI_CRASH_GUID; 16185974825SArd Biesheuvel unsigned long varname_size; 16285974825SArd Biesheuvel efi_status_t status; 16385974825SArd Biesheuvel 16485974825SArd Biesheuvel for (;;) { 16536d5786aSGuilherme G. Piccoli varname_size = 1024; 16685974825SArd Biesheuvel 16785974825SArd Biesheuvel /* 16885974825SArd Biesheuvel * If this is the first read() call in the pstore enumeration, 16985974825SArd Biesheuvel * varname will be the empty string, and the GetNextVariable() 17085974825SArd Biesheuvel * runtime service call will return the first EFI variable in 17185974825SArd Biesheuvel * its own enumeration order, ignoring the guid argument. 17285974825SArd Biesheuvel * 17385974825SArd Biesheuvel * Subsequent calls to GetNextVariable() must pass the name and 17485974825SArd Biesheuvel * guid values returned by the previous call, which is why we 17585974825SArd Biesheuvel * store varname in record->psi->data. Given that we only 17685974825SArd Biesheuvel * enumerate variables with the efi-pstore GUID, there is no 17785974825SArd Biesheuvel * need to record the guid return value. 17885974825SArd Biesheuvel */ 17985974825SArd Biesheuvel status = efivar_get_next_variable(&varname_size, varname, &guid); 18085974825SArd Biesheuvel if (status == EFI_NOT_FOUND) 18185974825SArd Biesheuvel return 0; 18285974825SArd Biesheuvel 18385974825SArd Biesheuvel if (status != EFI_SUCCESS) 18485974825SArd Biesheuvel return -EIO; 18585974825SArd Biesheuvel 18685974825SArd Biesheuvel /* skip variables that don't concern us */ 18785974825SArd Biesheuvel if (efi_guidcmp(guid, LINUX_EFI_CRASH_GUID)) 18885974825SArd Biesheuvel continue; 18985974825SArd Biesheuvel 19085974825SArd Biesheuvel return efi_pstore_read_func(record, varname); 19185974825SArd Biesheuvel } 19285974825SArd Biesheuvel } 19385974825SArd Biesheuvel 19476cc9580SKees Cook static int efi_pstore_write(struct pstore_record *record) 19504851772SMatt Fleming { 19604851772SMatt Fleming char name[DUMP_NAME_LEN]; 19704851772SMatt Fleming efi_char16_t efi_name[DUMP_NAME_LEN]; 19885974825SArd Biesheuvel efi_status_t status; 19985974825SArd Biesheuvel int i; 20004851772SMatt Fleming 201c10e8031SKees Cook record->id = generic_id(record->time.tv_sec, record->part, 202c10e8031SKees Cook record->count); 203c10e8031SKees Cook 204efb74e4bSKees Cook /* Since we copy the entire length of name, make sure it is wiped. */ 205efb74e4bSKees Cook memset(name, 0, sizeof(name)); 206efb74e4bSKees Cook 2077aaa822eSKees Cook snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lld-%c", 20876cc9580SKees Cook record->type, record->part, record->count, 2097aaa822eSKees Cook (long long)record->time.tv_sec, 2107aaa822eSKees Cook record->compressed ? 'C' : 'D'); 21104851772SMatt Fleming 21204851772SMatt Fleming for (i = 0; i < DUMP_NAME_LEN; i++) 21304851772SMatt Fleming efi_name[i] = name[i]; 21404851772SMatt Fleming 21585974825SArd Biesheuvel if (efivar_trylock()) 21685974825SArd Biesheuvel return -EBUSY; 21785974825SArd Biesheuvel status = efivar_set_variable_locked(efi_name, &LINUX_EFI_CRASH_GUID, 21885974825SArd Biesheuvel PSTORE_EFI_ATTRIBUTES, 21985974825SArd Biesheuvel record->size, record->psi->buf, 22085974825SArd Biesheuvel true); 22185974825SArd Biesheuvel efivar_unlock(); 22285974825SArd Biesheuvel return status == EFI_SUCCESS ? 0 : -EIO; 22304851772SMatt Fleming }; 22404851772SMatt Fleming 225efb74e4bSKees Cook static int efi_pstore_erase(struct pstore_record *record) 226efb74e4bSKees Cook { 22785974825SArd Biesheuvel efi_status_t status; 228efb74e4bSKees Cook 22985974825SArd Biesheuvel status = efivar_set_variable(record->priv, &LINUX_EFI_CRASH_GUID, 23085974825SArd Biesheuvel PSTORE_EFI_ATTRIBUTES, 0, NULL); 23104851772SMatt Fleming 23285974825SArd Biesheuvel if (status != EFI_SUCCESS && status != EFI_NOT_FOUND) 23385974825SArd Biesheuvel return -EIO; 23485974825SArd Biesheuvel return 0; 23504851772SMatt Fleming } 23604851772SMatt Fleming 23704851772SMatt Fleming static struct pstore_info efi_pstore_info = { 23804851772SMatt Fleming .owner = THIS_MODULE, 239893c5f1dSGuilherme G. Piccoli .name = KBUILD_MODNAME, 240c950fd6fSNamhyung Kim .flags = PSTORE_FLAGS_DMESG, 24104851772SMatt Fleming .open = efi_pstore_open, 24204851772SMatt Fleming .close = efi_pstore_close, 24304851772SMatt Fleming .read = efi_pstore_read, 24404851772SMatt Fleming .write = efi_pstore_write, 24504851772SMatt Fleming .erase = efi_pstore_erase, 24604851772SMatt Fleming }; 24704851772SMatt Fleming 248*a28655c3SGuilherme G. Piccoli static int efivars_pstore_init(void) 24904851772SMatt Fleming { 25085974825SArd Biesheuvel if (!efivar_supports_writes()) 25104851772SMatt Fleming return 0; 25204851772SMatt Fleming 253*a28655c3SGuilherme G. Piccoli if (pstore_disable) 25404851772SMatt Fleming return 0; 25504851772SMatt Fleming 25636d5786aSGuilherme G. Piccoli /* 25736d5786aSGuilherme G. Piccoli * Notice that 1024 is the minimum here to prevent issues with 25836d5786aSGuilherme G. Piccoli * decompression algorithms that were spotted during tests; 25936d5786aSGuilherme G. Piccoli * even in the case of not using compression, smaller values would 26036d5786aSGuilherme G. Piccoli * just pollute more the pstore FS with many small collected files. 26136d5786aSGuilherme G. Piccoli */ 26236d5786aSGuilherme G. Piccoli if (record_size < 1024) 26336d5786aSGuilherme G. Piccoli record_size = 1024; 26436d5786aSGuilherme G. Piccoli 26536d5786aSGuilherme G. Piccoli efi_pstore_info.buf = kmalloc(record_size, GFP_KERNEL); 26604851772SMatt Fleming if (!efi_pstore_info.buf) 26704851772SMatt Fleming return -ENOMEM; 26804851772SMatt Fleming 26936d5786aSGuilherme G. Piccoli efi_pstore_info.bufsize = record_size; 27004851772SMatt Fleming 2710d838347SLenny Szubowicz if (pstore_register(&efi_pstore_info)) { 2720d838347SLenny Szubowicz kfree(efi_pstore_info.buf); 2730d838347SLenny Szubowicz efi_pstore_info.buf = NULL; 2740d838347SLenny Szubowicz efi_pstore_info.bufsize = 0; 2750d838347SLenny Szubowicz } 27604851772SMatt Fleming 27704851772SMatt Fleming return 0; 27804851772SMatt Fleming } 27904851772SMatt Fleming 280*a28655c3SGuilherme G. Piccoli static void efivars_pstore_exit(void) 28104851772SMatt Fleming { 282cae73167SGeliang Tang if (!efi_pstore_info.bufsize) 283cae73167SGeliang Tang return; 284cae73167SGeliang Tang 285cae73167SGeliang Tang pstore_unregister(&efi_pstore_info); 286cae73167SGeliang Tang kfree(efi_pstore_info.buf); 287cae73167SGeliang Tang efi_pstore_info.buf = NULL; 288cae73167SGeliang Tang efi_pstore_info.bufsize = 0; 28904851772SMatt Fleming } 29004851772SMatt Fleming 29104851772SMatt Fleming module_init(efivars_pstore_init); 29204851772SMatt Fleming module_exit(efivars_pstore_exit); 29304851772SMatt Fleming 29404851772SMatt Fleming MODULE_DESCRIPTION("EFI variable backend for pstore"); 29504851772SMatt Fleming MODULE_LICENSE("GPL"); 2969ac4d5abSBen Hutchings MODULE_ALIAS("platform:efivars"); 297