1 // SPDX-License-Identifier: GPL-2.0+
4 #include <linux/module.h>
5 #include <linux/pstore.h>
6 #include <linux/slab.h>
7 #include <linux/ucs2_string.h>
9 MODULE_IMPORT_NS(EFIVAR
);
11 #define DUMP_NAME_LEN 66
13 static unsigned int record_size
= 1024;
14 module_param(record_size
, uint
, 0444);
15 MODULE_PARM_DESC(record_size
, "size of each pstore UEFI var (in bytes, min/default=1024)");
17 static bool efivars_pstore_disable
=
18 IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE
);
20 module_param_named(pstore_disable
, efivars_pstore_disable
, bool, 0644);
22 #define PSTORE_EFI_ATTRIBUTES \
23 (EFI_VARIABLE_NON_VOLATILE | \
24 EFI_VARIABLE_BOOTSERVICE_ACCESS | \
25 EFI_VARIABLE_RUNTIME_ACCESS)
27 static int efi_pstore_open(struct pstore_info
*psi
)
35 psi
->data
= kzalloc(record_size
, GFP_KERNEL
);
42 static int efi_pstore_close(struct pstore_info
*psi
)
49 static inline u64
generic_id(u64 timestamp
, unsigned int part
, int count
)
51 return (timestamp
* 100 + part
) * 1000 + count
;
54 static int efi_pstore_read_func(struct pstore_record
*record
,
55 efi_char16_t
*varname
)
57 unsigned long wlen
, size
= record_size
;
58 char name
[DUMP_NAME_LEN
], data_type
;
64 ucs2_as_utf8(name
, varname
, DUMP_NAME_LEN
);
66 if (sscanf(name
, "dump-type%u-%u-%d-%llu-%c",
67 &record
->type
, &part
, &cnt
, &time
, &data_type
) == 5) {
68 record
->id
= generic_id(time
, part
, cnt
);
71 record
->time
.tv_sec
= time
;
72 record
->time
.tv_nsec
= 0;
74 record
->compressed
= true;
76 record
->compressed
= false;
77 record
->ecc_notice_size
= 0;
78 } else if (sscanf(name
, "dump-type%u-%u-%d-%llu",
79 &record
->type
, &part
, &cnt
, &time
) == 4) {
80 record
->id
= generic_id(time
, part
, cnt
);
83 record
->time
.tv_sec
= time
;
84 record
->time
.tv_nsec
= 0;
85 record
->compressed
= false;
86 record
->ecc_notice_size
= 0;
87 } else if (sscanf(name
, "dump-type%u-%u-%llu",
88 &record
->type
, &part
, &time
) == 3) {
90 * Check if an old format,
91 * which doesn't support holding
92 * multiple logs, remains.
94 record
->id
= generic_id(time
, part
, 0);
97 record
->time
.tv_sec
= time
;
98 record
->time
.tv_nsec
= 0;
99 record
->compressed
= false;
100 record
->ecc_notice_size
= 0;
104 record
->buf
= kmalloc(size
, GFP_KERNEL
);
108 status
= efivar_get_variable(varname
, &LINUX_EFI_CRASH_GUID
, NULL
,
110 if (status
!= EFI_SUCCESS
) {
116 * Store the name of the variable in the pstore_record priv field, so
117 * we can reuse it later if we need to delete the EFI variable from the
120 wlen
= (ucs2_strnlen(varname
, DUMP_NAME_LEN
) + 1) * sizeof(efi_char16_t
);
121 record
->priv
= kmemdup(varname
, wlen
, GFP_KERNEL
);
130 static ssize_t
efi_pstore_read(struct pstore_record
*record
)
132 efi_char16_t
*varname
= record
->psi
->data
;
133 efi_guid_t guid
= LINUX_EFI_CRASH_GUID
;
134 unsigned long varname_size
;
141 * If this is the first read() call in the pstore enumeration,
142 * varname will be the empty string, and the GetNextVariable()
143 * runtime service call will return the first EFI variable in
144 * its own enumeration order, ignoring the guid argument.
146 * Subsequent calls to GetNextVariable() must pass the name and
147 * guid values returned by the previous call, which is why we
148 * store varname in record->psi->data. Given that we only
149 * enumerate variables with the efi-pstore GUID, there is no
150 * need to record the guid return value.
152 status
= efivar_get_next_variable(&varname_size
, varname
, &guid
);
153 if (status
== EFI_NOT_FOUND
)
156 if (status
!= EFI_SUCCESS
)
159 /* skip variables that don't concern us */
160 if (efi_guidcmp(guid
, LINUX_EFI_CRASH_GUID
))
163 return efi_pstore_read_func(record
, varname
);
167 static int efi_pstore_write(struct pstore_record
*record
)
169 char name
[DUMP_NAME_LEN
];
170 efi_char16_t efi_name
[DUMP_NAME_LEN
];
174 record
->id
= generic_id(record
->time
.tv_sec
, record
->part
,
177 /* Since we copy the entire length of name, make sure it is wiped. */
178 memset(name
, 0, sizeof(name
));
180 snprintf(name
, sizeof(name
), "dump-type%u-%u-%d-%lld-%c",
181 record
->type
, record
->part
, record
->count
,
182 (long long)record
->time
.tv_sec
,
183 record
->compressed
? 'C' : 'D');
185 for (i
= 0; i
< DUMP_NAME_LEN
; i
++)
186 efi_name
[i
] = name
[i
];
188 if (efivar_trylock())
190 status
= efivar_set_variable_locked(efi_name
, &LINUX_EFI_CRASH_GUID
,
191 PSTORE_EFI_ATTRIBUTES
,
192 record
->size
, record
->psi
->buf
,
195 return status
== EFI_SUCCESS
? 0 : -EIO
;
198 static int efi_pstore_erase(struct pstore_record
*record
)
202 status
= efivar_set_variable(record
->priv
, &LINUX_EFI_CRASH_GUID
,
203 PSTORE_EFI_ATTRIBUTES
, 0, NULL
);
205 if (status
!= EFI_SUCCESS
&& status
!= EFI_NOT_FOUND
)
210 static struct pstore_info efi_pstore_info
= {
211 .owner
= THIS_MODULE
,
212 .name
= KBUILD_MODNAME
,
213 .flags
= PSTORE_FLAGS_DMESG
,
214 .open
= efi_pstore_open
,
215 .close
= efi_pstore_close
,
216 .read
= efi_pstore_read
,
217 .write
= efi_pstore_write
,
218 .erase
= efi_pstore_erase
,
221 static __init
int efivars_pstore_init(void)
223 if (!efivar_supports_writes())
226 if (efivars_pstore_disable
)
230 * Notice that 1024 is the minimum here to prevent issues with
231 * decompression algorithms that were spotted during tests;
232 * even in the case of not using compression, smaller values would
233 * just pollute more the pstore FS with many small collected files.
235 if (record_size
< 1024)
238 efi_pstore_info
.buf
= kmalloc(record_size
, GFP_KERNEL
);
239 if (!efi_pstore_info
.buf
)
242 efi_pstore_info
.bufsize
= record_size
;
244 if (pstore_register(&efi_pstore_info
)) {
245 kfree(efi_pstore_info
.buf
);
246 efi_pstore_info
.buf
= NULL
;
247 efi_pstore_info
.bufsize
= 0;
253 static __exit
void efivars_pstore_exit(void)
255 if (!efi_pstore_info
.bufsize
)
258 pstore_unregister(&efi_pstore_info
);
259 kfree(efi_pstore_info
.buf
);
260 efi_pstore_info
.buf
= NULL
;
261 efi_pstore_info
.bufsize
= 0;
264 module_init(efivars_pstore_init
);
265 module_exit(efivars_pstore_exit
);
267 MODULE_DESCRIPTION("EFI variable backend for pstore");
268 MODULE_LICENSE("GPL");
269 MODULE_ALIAS("platform:efivars");