xref: /linux/drivers/firmware/efi/efi-pstore.c (revision cdd30ebb1b9f36159d66f088b61aee264e649d7a)
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