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