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
9*cdd30ebbSPeter Zijlstra 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
22a28655c3SGuilherme G. Piccoli static bool pstore_disable = IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE);
23a28655c3SGuilherme G. Piccoli
24a28655c3SGuilherme G. Piccoli static int efivars_pstore_init(void);
25a28655c3SGuilherme G. Piccoli static void efivars_pstore_exit(void);
26a28655c3SGuilherme G. Piccoli
efi_pstore_disable_set(const char * val,const struct kernel_param * kp)27a28655c3SGuilherme G. Piccoli static int efi_pstore_disable_set(const char *val, const struct kernel_param *kp)
28a28655c3SGuilherme G. Piccoli {
29a28655c3SGuilherme G. Piccoli int err;
30a28655c3SGuilherme G. Piccoli bool old_pstore_disable = pstore_disable;
31a28655c3SGuilherme G. Piccoli
32a28655c3SGuilherme G. Piccoli err = param_set_bool(val, kp);
33a28655c3SGuilherme G. Piccoli if (err)
34a28655c3SGuilherme G. Piccoli return err;
35a28655c3SGuilherme G. Piccoli
36a28655c3SGuilherme G. Piccoli if (old_pstore_disable != pstore_disable) {
37a28655c3SGuilherme G. Piccoli if (pstore_disable)
38a28655c3SGuilherme G. Piccoli efivars_pstore_exit();
39a28655c3SGuilherme G. Piccoli else
40a28655c3SGuilherme G. Piccoli efivars_pstore_init();
41a28655c3SGuilherme G. Piccoli }
42a28655c3SGuilherme G. Piccoli
43a28655c3SGuilherme G. Piccoli return 0;
44a28655c3SGuilherme G. Piccoli }
45a28655c3SGuilherme G. Piccoli
46a28655c3SGuilherme G. Piccoli static const struct kernel_param_ops pstore_disable_ops = {
47a28655c3SGuilherme G. Piccoli .set = efi_pstore_disable_set,
48a28655c3SGuilherme G. Piccoli .get = param_get_bool,
49a28655c3SGuilherme G. Piccoli };
50a28655c3SGuilherme G. Piccoli
51a28655c3SGuilherme G. Piccoli module_param_cb(pstore_disable, &pstore_disable_ops, &pstore_disable, 0644);
52a28655c3SGuilherme G. Piccoli __MODULE_PARM_TYPE(pstore_disable, "bool");
53a28655c3SGuilherme G. Piccoli
efi_pstore_open(struct pstore_info * psi)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
efi_pstore_close(struct pstore_info * psi)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
generic_id(u64 timestamp,unsigned int part,int count)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
efi_pstore_read_func(struct pstore_record * record,efi_char16_t * varname)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);
1397c23b186SGuilherme G. Piccoli return efi_status_to_err(status);
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
efi_pstore_read(struct pstore_record * record)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 (;;) {
16524427cdaSTim Schumacher /*
16624427cdaSTim Schumacher * A small set of old UEFI implementations reject sizes
16724427cdaSTim Schumacher * above a certain threshold, the lowest seen in the wild
16824427cdaSTim Schumacher * is 512.
16924427cdaSTim Schumacher *
17024427cdaSTim Schumacher * TODO: Commonize with the iteration implementation in
17124427cdaSTim Schumacher * fs/efivarfs to keep all the quirks in one place.
17224427cdaSTim Schumacher */
17324427cdaSTim Schumacher varname_size = 512;
17485974825SArd Biesheuvel
17585974825SArd Biesheuvel /*
17685974825SArd Biesheuvel * If this is the first read() call in the pstore enumeration,
17785974825SArd Biesheuvel * varname will be the empty string, and the GetNextVariable()
17885974825SArd Biesheuvel * runtime service call will return the first EFI variable in
17985974825SArd Biesheuvel * its own enumeration order, ignoring the guid argument.
18085974825SArd Biesheuvel *
18185974825SArd Biesheuvel * Subsequent calls to GetNextVariable() must pass the name and
18285974825SArd Biesheuvel * guid values returned by the previous call, which is why we
18385974825SArd Biesheuvel * store varname in record->psi->data. Given that we only
18485974825SArd Biesheuvel * enumerate variables with the efi-pstore GUID, there is no
18585974825SArd Biesheuvel * need to record the guid return value.
18685974825SArd Biesheuvel */
18785974825SArd Biesheuvel status = efivar_get_next_variable(&varname_size, varname, &guid);
18885974825SArd Biesheuvel if (status == EFI_NOT_FOUND)
18985974825SArd Biesheuvel return 0;
19085974825SArd Biesheuvel
19185974825SArd Biesheuvel if (status != EFI_SUCCESS)
1927c23b186SGuilherme G. Piccoli return efi_status_to_err(status);
19385974825SArd Biesheuvel
19485974825SArd Biesheuvel /* skip variables that don't concern us */
19585974825SArd Biesheuvel if (efi_guidcmp(guid, LINUX_EFI_CRASH_GUID))
19685974825SArd Biesheuvel continue;
19785974825SArd Biesheuvel
19885974825SArd Biesheuvel return efi_pstore_read_func(record, varname);
19985974825SArd Biesheuvel }
20085974825SArd Biesheuvel }
20185974825SArd Biesheuvel
efi_pstore_write(struct pstore_record * record)20276cc9580SKees Cook static int efi_pstore_write(struct pstore_record *record)
20304851772SMatt Fleming {
20404851772SMatt Fleming char name[DUMP_NAME_LEN];
20504851772SMatt Fleming efi_char16_t efi_name[DUMP_NAME_LEN];
20685974825SArd Biesheuvel efi_status_t status;
20785974825SArd Biesheuvel int i;
20804851772SMatt Fleming
209c10e8031SKees Cook record->id = generic_id(record->time.tv_sec, record->part,
210c10e8031SKees Cook record->count);
211c10e8031SKees Cook
212efb74e4bSKees Cook /* Since we copy the entire length of name, make sure it is wiped. */
213efb74e4bSKees Cook memset(name, 0, sizeof(name));
214efb74e4bSKees Cook
2157aaa822eSKees Cook snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lld-%c",
21676cc9580SKees Cook record->type, record->part, record->count,
2177aaa822eSKees Cook (long long)record->time.tv_sec,
2187aaa822eSKees Cook record->compressed ? 'C' : 'D');
21904851772SMatt Fleming
22004851772SMatt Fleming for (i = 0; i < DUMP_NAME_LEN; i++)
22104851772SMatt Fleming efi_name[i] = name[i];
22204851772SMatt Fleming
22385974825SArd Biesheuvel if (efivar_trylock())
22485974825SArd Biesheuvel return -EBUSY;
22585974825SArd Biesheuvel status = efivar_set_variable_locked(efi_name, &LINUX_EFI_CRASH_GUID,
22685974825SArd Biesheuvel PSTORE_EFI_ATTRIBUTES,
22785974825SArd Biesheuvel record->size, record->psi->buf,
22885974825SArd Biesheuvel true);
22985974825SArd Biesheuvel efivar_unlock();
2307c23b186SGuilherme G. Piccoli return efi_status_to_err(status);
23104851772SMatt Fleming };
23204851772SMatt Fleming
efi_pstore_erase(struct pstore_record * record)233efb74e4bSKees Cook static int efi_pstore_erase(struct pstore_record *record)
234efb74e4bSKees Cook {
23585974825SArd Biesheuvel efi_status_t status;
236efb74e4bSKees Cook
23785974825SArd Biesheuvel status = efivar_set_variable(record->priv, &LINUX_EFI_CRASH_GUID,
23885974825SArd Biesheuvel PSTORE_EFI_ATTRIBUTES, 0, NULL);
23904851772SMatt Fleming
24085974825SArd Biesheuvel if (status != EFI_SUCCESS && status != EFI_NOT_FOUND)
2417c23b186SGuilherme G. Piccoli return efi_status_to_err(status);
24285974825SArd Biesheuvel return 0;
24304851772SMatt Fleming }
24404851772SMatt Fleming
24504851772SMatt Fleming static struct pstore_info efi_pstore_info = {
24604851772SMatt Fleming .owner = THIS_MODULE,
247893c5f1dSGuilherme G. Piccoli .name = KBUILD_MODNAME,
248c950fd6fSNamhyung Kim .flags = PSTORE_FLAGS_DMESG,
24904851772SMatt Fleming .open = efi_pstore_open,
25004851772SMatt Fleming .close = efi_pstore_close,
25104851772SMatt Fleming .read = efi_pstore_read,
25204851772SMatt Fleming .write = efi_pstore_write,
25304851772SMatt Fleming .erase = efi_pstore_erase,
25404851772SMatt Fleming };
25504851772SMatt Fleming
efivars_pstore_init(void)256a28655c3SGuilherme G. Piccoli static int efivars_pstore_init(void)
25704851772SMatt Fleming {
25885974825SArd Biesheuvel if (!efivar_supports_writes())
25904851772SMatt Fleming return 0;
26004851772SMatt Fleming
261a28655c3SGuilherme G. Piccoli if (pstore_disable)
26204851772SMatt Fleming return 0;
26304851772SMatt Fleming
26436d5786aSGuilherme G. Piccoli /*
26536d5786aSGuilherme G. Piccoli * Notice that 1024 is the minimum here to prevent issues with
26636d5786aSGuilherme G. Piccoli * decompression algorithms that were spotted during tests;
26736d5786aSGuilherme G. Piccoli * even in the case of not using compression, smaller values would
26836d5786aSGuilherme G. Piccoli * just pollute more the pstore FS with many small collected files.
26936d5786aSGuilherme G. Piccoli */
27036d5786aSGuilherme G. Piccoli if (record_size < 1024)
27136d5786aSGuilherme G. Piccoli record_size = 1024;
27236d5786aSGuilherme G. Piccoli
27336d5786aSGuilherme G. Piccoli efi_pstore_info.buf = kmalloc(record_size, GFP_KERNEL);
27404851772SMatt Fleming if (!efi_pstore_info.buf)
27504851772SMatt Fleming return -ENOMEM;
27604851772SMatt Fleming
27736d5786aSGuilherme G. Piccoli efi_pstore_info.bufsize = record_size;
27804851772SMatt Fleming
2790d838347SLenny Szubowicz if (pstore_register(&efi_pstore_info)) {
2800d838347SLenny Szubowicz kfree(efi_pstore_info.buf);
2810d838347SLenny Szubowicz efi_pstore_info.buf = NULL;
2820d838347SLenny Szubowicz efi_pstore_info.bufsize = 0;
2830d838347SLenny Szubowicz }
28404851772SMatt Fleming
28504851772SMatt Fleming return 0;
28604851772SMatt Fleming }
28704851772SMatt Fleming
efivars_pstore_exit(void)288a28655c3SGuilherme G. Piccoli static void efivars_pstore_exit(void)
28904851772SMatt Fleming {
290cae73167SGeliang Tang if (!efi_pstore_info.bufsize)
291cae73167SGeliang Tang return;
292cae73167SGeliang Tang
293cae73167SGeliang Tang pstore_unregister(&efi_pstore_info);
294cae73167SGeliang Tang kfree(efi_pstore_info.buf);
295cae73167SGeliang Tang efi_pstore_info.buf = NULL;
296cae73167SGeliang Tang efi_pstore_info.bufsize = 0;
29704851772SMatt Fleming }
29804851772SMatt Fleming
29904851772SMatt Fleming module_init(efivars_pstore_init);
30004851772SMatt Fleming module_exit(efivars_pstore_exit);
30104851772SMatt Fleming
30204851772SMatt Fleming MODULE_DESCRIPTION("EFI variable backend for pstore");
30304851772SMatt Fleming MODULE_LICENSE("GPL");
3049ac4d5abSBen Hutchings MODULE_ALIAS("platform:efivars");
305