162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#include <linux/efi.h> 462306a36Sopenharmony_ci#include <linux/module.h> 562306a36Sopenharmony_ci#include <linux/pstore.h> 662306a36Sopenharmony_ci#include <linux/slab.h> 762306a36Sopenharmony_ci#include <linux/ucs2_string.h> 862306a36Sopenharmony_ci 962306a36Sopenharmony_ciMODULE_IMPORT_NS(EFIVAR); 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#define DUMP_NAME_LEN 66 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistatic unsigned int record_size = 1024; 1462306a36Sopenharmony_cimodule_param(record_size, uint, 0444); 1562306a36Sopenharmony_ciMODULE_PARM_DESC(record_size, "size of each pstore UEFI var (in bytes, min/default=1024)"); 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic bool efivars_pstore_disable = 1862306a36Sopenharmony_ci IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE); 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cimodule_param_named(pstore_disable, efivars_pstore_disable, bool, 0644); 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define PSTORE_EFI_ATTRIBUTES \ 2362306a36Sopenharmony_ci (EFI_VARIABLE_NON_VOLATILE | \ 2462306a36Sopenharmony_ci EFI_VARIABLE_BOOTSERVICE_ACCESS | \ 2562306a36Sopenharmony_ci EFI_VARIABLE_RUNTIME_ACCESS) 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic int efi_pstore_open(struct pstore_info *psi) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci int err; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci err = efivar_lock(); 3262306a36Sopenharmony_ci if (err) 3362306a36Sopenharmony_ci return err; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci psi->data = kzalloc(record_size, GFP_KERNEL); 3662306a36Sopenharmony_ci if (!psi->data) 3762306a36Sopenharmony_ci return -ENOMEM; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci return 0; 4062306a36Sopenharmony_ci} 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic int efi_pstore_close(struct pstore_info *psi) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci efivar_unlock(); 4562306a36Sopenharmony_ci kfree(psi->data); 4662306a36Sopenharmony_ci return 0; 4762306a36Sopenharmony_ci} 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic inline u64 generic_id(u64 timestamp, unsigned int part, int count) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci return (timestamp * 100 + part) * 1000 + count; 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic int efi_pstore_read_func(struct pstore_record *record, 5562306a36Sopenharmony_ci efi_char16_t *varname) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci unsigned long wlen, size = record_size; 5862306a36Sopenharmony_ci char name[DUMP_NAME_LEN], data_type; 5962306a36Sopenharmony_ci efi_status_t status; 6062306a36Sopenharmony_ci int cnt; 6162306a36Sopenharmony_ci unsigned int part; 6262306a36Sopenharmony_ci u64 time; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci ucs2_as_utf8(name, varname, DUMP_NAME_LEN); 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci if (sscanf(name, "dump-type%u-%u-%d-%llu-%c", 6762306a36Sopenharmony_ci &record->type, &part, &cnt, &time, &data_type) == 5) { 6862306a36Sopenharmony_ci record->id = generic_id(time, part, cnt); 6962306a36Sopenharmony_ci record->part = part; 7062306a36Sopenharmony_ci record->count = cnt; 7162306a36Sopenharmony_ci record->time.tv_sec = time; 7262306a36Sopenharmony_ci record->time.tv_nsec = 0; 7362306a36Sopenharmony_ci if (data_type == 'C') 7462306a36Sopenharmony_ci record->compressed = true; 7562306a36Sopenharmony_ci else 7662306a36Sopenharmony_ci record->compressed = false; 7762306a36Sopenharmony_ci record->ecc_notice_size = 0; 7862306a36Sopenharmony_ci } else if (sscanf(name, "dump-type%u-%u-%d-%llu", 7962306a36Sopenharmony_ci &record->type, &part, &cnt, &time) == 4) { 8062306a36Sopenharmony_ci record->id = generic_id(time, part, cnt); 8162306a36Sopenharmony_ci record->part = part; 8262306a36Sopenharmony_ci record->count = cnt; 8362306a36Sopenharmony_ci record->time.tv_sec = time; 8462306a36Sopenharmony_ci record->time.tv_nsec = 0; 8562306a36Sopenharmony_ci record->compressed = false; 8662306a36Sopenharmony_ci record->ecc_notice_size = 0; 8762306a36Sopenharmony_ci } else if (sscanf(name, "dump-type%u-%u-%llu", 8862306a36Sopenharmony_ci &record->type, &part, &time) == 3) { 8962306a36Sopenharmony_ci /* 9062306a36Sopenharmony_ci * Check if an old format, 9162306a36Sopenharmony_ci * which doesn't support holding 9262306a36Sopenharmony_ci * multiple logs, remains. 9362306a36Sopenharmony_ci */ 9462306a36Sopenharmony_ci record->id = generic_id(time, part, 0); 9562306a36Sopenharmony_ci record->part = part; 9662306a36Sopenharmony_ci record->count = 0; 9762306a36Sopenharmony_ci record->time.tv_sec = time; 9862306a36Sopenharmony_ci record->time.tv_nsec = 0; 9962306a36Sopenharmony_ci record->compressed = false; 10062306a36Sopenharmony_ci record->ecc_notice_size = 0; 10162306a36Sopenharmony_ci } else 10262306a36Sopenharmony_ci return 0; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci record->buf = kmalloc(size, GFP_KERNEL); 10562306a36Sopenharmony_ci if (!record->buf) 10662306a36Sopenharmony_ci return -ENOMEM; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci status = efivar_get_variable(varname, &LINUX_EFI_CRASH_GUID, NULL, 10962306a36Sopenharmony_ci &size, record->buf); 11062306a36Sopenharmony_ci if (status != EFI_SUCCESS) { 11162306a36Sopenharmony_ci kfree(record->buf); 11262306a36Sopenharmony_ci return -EIO; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci /* 11662306a36Sopenharmony_ci * Store the name of the variable in the pstore_record priv field, so 11762306a36Sopenharmony_ci * we can reuse it later if we need to delete the EFI variable from the 11862306a36Sopenharmony_ci * variable store. 11962306a36Sopenharmony_ci */ 12062306a36Sopenharmony_ci wlen = (ucs2_strnlen(varname, DUMP_NAME_LEN) + 1) * sizeof(efi_char16_t); 12162306a36Sopenharmony_ci record->priv = kmemdup(varname, wlen, GFP_KERNEL); 12262306a36Sopenharmony_ci if (!record->priv) { 12362306a36Sopenharmony_ci kfree(record->buf); 12462306a36Sopenharmony_ci return -ENOMEM; 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci return size; 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic ssize_t efi_pstore_read(struct pstore_record *record) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci efi_char16_t *varname = record->psi->data; 13362306a36Sopenharmony_ci efi_guid_t guid = LINUX_EFI_CRASH_GUID; 13462306a36Sopenharmony_ci unsigned long varname_size; 13562306a36Sopenharmony_ci efi_status_t status; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci for (;;) { 13862306a36Sopenharmony_ci varname_size = 1024; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci /* 14162306a36Sopenharmony_ci * If this is the first read() call in the pstore enumeration, 14262306a36Sopenharmony_ci * varname will be the empty string, and the GetNextVariable() 14362306a36Sopenharmony_ci * runtime service call will return the first EFI variable in 14462306a36Sopenharmony_ci * its own enumeration order, ignoring the guid argument. 14562306a36Sopenharmony_ci * 14662306a36Sopenharmony_ci * Subsequent calls to GetNextVariable() must pass the name and 14762306a36Sopenharmony_ci * guid values returned by the previous call, which is why we 14862306a36Sopenharmony_ci * store varname in record->psi->data. Given that we only 14962306a36Sopenharmony_ci * enumerate variables with the efi-pstore GUID, there is no 15062306a36Sopenharmony_ci * need to record the guid return value. 15162306a36Sopenharmony_ci */ 15262306a36Sopenharmony_ci status = efivar_get_next_variable(&varname_size, varname, &guid); 15362306a36Sopenharmony_ci if (status == EFI_NOT_FOUND) 15462306a36Sopenharmony_ci return 0; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if (status != EFI_SUCCESS) 15762306a36Sopenharmony_ci return -EIO; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci /* skip variables that don't concern us */ 16062306a36Sopenharmony_ci if (efi_guidcmp(guid, LINUX_EFI_CRASH_GUID)) 16162306a36Sopenharmony_ci continue; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci return efi_pstore_read_func(record, varname); 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cistatic int efi_pstore_write(struct pstore_record *record) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci char name[DUMP_NAME_LEN]; 17062306a36Sopenharmony_ci efi_char16_t efi_name[DUMP_NAME_LEN]; 17162306a36Sopenharmony_ci efi_status_t status; 17262306a36Sopenharmony_ci int i; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci record->id = generic_id(record->time.tv_sec, record->part, 17562306a36Sopenharmony_ci record->count); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci /* Since we copy the entire length of name, make sure it is wiped. */ 17862306a36Sopenharmony_ci memset(name, 0, sizeof(name)); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lld-%c", 18162306a36Sopenharmony_ci record->type, record->part, record->count, 18262306a36Sopenharmony_ci (long long)record->time.tv_sec, 18362306a36Sopenharmony_ci record->compressed ? 'C' : 'D'); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci for (i = 0; i < DUMP_NAME_LEN; i++) 18662306a36Sopenharmony_ci efi_name[i] = name[i]; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci if (efivar_trylock()) 18962306a36Sopenharmony_ci return -EBUSY; 19062306a36Sopenharmony_ci status = efivar_set_variable_locked(efi_name, &LINUX_EFI_CRASH_GUID, 19162306a36Sopenharmony_ci PSTORE_EFI_ATTRIBUTES, 19262306a36Sopenharmony_ci record->size, record->psi->buf, 19362306a36Sopenharmony_ci true); 19462306a36Sopenharmony_ci efivar_unlock(); 19562306a36Sopenharmony_ci return status == EFI_SUCCESS ? 0 : -EIO; 19662306a36Sopenharmony_ci}; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic int efi_pstore_erase(struct pstore_record *record) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci efi_status_t status; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci status = efivar_set_variable(record->priv, &LINUX_EFI_CRASH_GUID, 20362306a36Sopenharmony_ci PSTORE_EFI_ATTRIBUTES, 0, NULL); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci if (status != EFI_SUCCESS && status != EFI_NOT_FOUND) 20662306a36Sopenharmony_ci return -EIO; 20762306a36Sopenharmony_ci return 0; 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_cistatic struct pstore_info efi_pstore_info = { 21162306a36Sopenharmony_ci .owner = THIS_MODULE, 21262306a36Sopenharmony_ci .name = KBUILD_MODNAME, 21362306a36Sopenharmony_ci .flags = PSTORE_FLAGS_DMESG, 21462306a36Sopenharmony_ci .open = efi_pstore_open, 21562306a36Sopenharmony_ci .close = efi_pstore_close, 21662306a36Sopenharmony_ci .read = efi_pstore_read, 21762306a36Sopenharmony_ci .write = efi_pstore_write, 21862306a36Sopenharmony_ci .erase = efi_pstore_erase, 21962306a36Sopenharmony_ci}; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic __init int efivars_pstore_init(void) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci if (!efivar_supports_writes()) 22462306a36Sopenharmony_ci return 0; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci if (efivars_pstore_disable) 22762306a36Sopenharmony_ci return 0; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci /* 23062306a36Sopenharmony_ci * Notice that 1024 is the minimum here to prevent issues with 23162306a36Sopenharmony_ci * decompression algorithms that were spotted during tests; 23262306a36Sopenharmony_ci * even in the case of not using compression, smaller values would 23362306a36Sopenharmony_ci * just pollute more the pstore FS with many small collected files. 23462306a36Sopenharmony_ci */ 23562306a36Sopenharmony_ci if (record_size < 1024) 23662306a36Sopenharmony_ci record_size = 1024; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci efi_pstore_info.buf = kmalloc(record_size, GFP_KERNEL); 23962306a36Sopenharmony_ci if (!efi_pstore_info.buf) 24062306a36Sopenharmony_ci return -ENOMEM; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci efi_pstore_info.bufsize = record_size; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci if (pstore_register(&efi_pstore_info)) { 24562306a36Sopenharmony_ci kfree(efi_pstore_info.buf); 24662306a36Sopenharmony_ci efi_pstore_info.buf = NULL; 24762306a36Sopenharmony_ci efi_pstore_info.bufsize = 0; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci return 0; 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cistatic __exit void efivars_pstore_exit(void) 25462306a36Sopenharmony_ci{ 25562306a36Sopenharmony_ci if (!efi_pstore_info.bufsize) 25662306a36Sopenharmony_ci return; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci pstore_unregister(&efi_pstore_info); 25962306a36Sopenharmony_ci kfree(efi_pstore_info.buf); 26062306a36Sopenharmony_ci efi_pstore_info.buf = NULL; 26162306a36Sopenharmony_ci efi_pstore_info.bufsize = 0; 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_cimodule_init(efivars_pstore_init); 26562306a36Sopenharmony_cimodule_exit(efivars_pstore_exit); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ciMODULE_DESCRIPTION("EFI variable backend for pstore"); 26862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 26962306a36Sopenharmony_ciMODULE_ALIAS("platform:efivars"); 270