1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | #include <linux/efi.h> |
4 | #include <linux/module.h> |
5 | #include <linux/pstore.h> |
6 | #include <linux/slab.h> |
7 | #include <linux/ucs2_string.h> |
8 | |
9 | MODULE_IMPORT_NS(EFIVAR); |
10 | |
11 | #define DUMP_NAME_LEN 66 |
12 | |
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)" ); |
16 | |
17 | #define PSTORE_EFI_ATTRIBUTES \ |
18 | (EFI_VARIABLE_NON_VOLATILE | \ |
19 | EFI_VARIABLE_BOOTSERVICE_ACCESS | \ |
20 | EFI_VARIABLE_RUNTIME_ACCESS) |
21 | |
22 | static bool pstore_disable = IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE); |
23 | |
24 | static int efivars_pstore_init(void); |
25 | static void efivars_pstore_exit(void); |
26 | |
27 | static int efi_pstore_disable_set(const char *val, const struct kernel_param *kp) |
28 | { |
29 | int err; |
30 | bool old_pstore_disable = pstore_disable; |
31 | |
32 | err = param_set_bool(val, kp); |
33 | if (err) |
34 | return err; |
35 | |
36 | if (old_pstore_disable != pstore_disable) { |
37 | if (pstore_disable) |
38 | efivars_pstore_exit(); |
39 | else |
40 | efivars_pstore_init(); |
41 | } |
42 | |
43 | return 0; |
44 | } |
45 | |
46 | static const struct kernel_param_ops pstore_disable_ops = { |
47 | .set = efi_pstore_disable_set, |
48 | .get = param_get_bool, |
49 | }; |
50 | |
51 | module_param_cb(pstore_disable, &pstore_disable_ops, &pstore_disable, 0644); |
52 | __MODULE_PARM_TYPE(pstore_disable, "bool" ); |
53 | |
54 | static int efi_pstore_open(struct pstore_info *psi) |
55 | { |
56 | int err; |
57 | |
58 | err = efivar_lock(); |
59 | if (err) |
60 | return err; |
61 | |
62 | psi->data = kzalloc(size: record_size, GFP_KERNEL); |
63 | if (!psi->data) |
64 | return -ENOMEM; |
65 | |
66 | return 0; |
67 | } |
68 | |
69 | static int efi_pstore_close(struct pstore_info *psi) |
70 | { |
71 | efivar_unlock(); |
72 | kfree(objp: psi->data); |
73 | return 0; |
74 | } |
75 | |
76 | static inline u64 generic_id(u64 timestamp, unsigned int part, int count) |
77 | { |
78 | return (timestamp * 100 + part) * 1000 + count; |
79 | } |
80 | |
81 | static int efi_pstore_read_func(struct pstore_record *record, |
82 | efi_char16_t *varname) |
83 | { |
84 | unsigned long wlen, size = record_size; |
85 | char name[DUMP_NAME_LEN], data_type; |
86 | efi_status_t status; |
87 | int cnt; |
88 | unsigned int part; |
89 | u64 time; |
90 | |
91 | ucs2_as_utf8(dest: name, src: varname, DUMP_NAME_LEN); |
92 | |
93 | if (sscanf(name, "dump-type%u-%u-%d-%llu-%c" , |
94 | &record->type, &part, &cnt, &time, &data_type) == 5) { |
95 | record->id = generic_id(timestamp: time, part, count: cnt); |
96 | record->part = part; |
97 | record->count = cnt; |
98 | record->time.tv_sec = time; |
99 | record->time.tv_nsec = 0; |
100 | if (data_type == 'C') |
101 | record->compressed = true; |
102 | else |
103 | record->compressed = false; |
104 | record->ecc_notice_size = 0; |
105 | } else if (sscanf(name, "dump-type%u-%u-%d-%llu" , |
106 | &record->type, &part, &cnt, &time) == 4) { |
107 | record->id = generic_id(timestamp: time, part, count: cnt); |
108 | record->part = part; |
109 | record->count = cnt; |
110 | record->time.tv_sec = time; |
111 | record->time.tv_nsec = 0; |
112 | record->compressed = false; |
113 | record->ecc_notice_size = 0; |
114 | } else if (sscanf(name, "dump-type%u-%u-%llu" , |
115 | &record->type, &part, &time) == 3) { |
116 | /* |
117 | * Check if an old format, |
118 | * which doesn't support holding |
119 | * multiple logs, remains. |
120 | */ |
121 | record->id = generic_id(timestamp: time, part, count: 0); |
122 | record->part = part; |
123 | record->count = 0; |
124 | record->time.tv_sec = time; |
125 | record->time.tv_nsec = 0; |
126 | record->compressed = false; |
127 | record->ecc_notice_size = 0; |
128 | } else |
129 | return 0; |
130 | |
131 | record->buf = kmalloc(size, GFP_KERNEL); |
132 | if (!record->buf) |
133 | return -ENOMEM; |
134 | |
135 | status = efivar_get_variable(name: varname, vendor: &LINUX_EFI_CRASH_GUID, NULL, |
136 | size: &size, data: record->buf); |
137 | if (status != EFI_SUCCESS) { |
138 | kfree(objp: record->buf); |
139 | return -EIO; |
140 | } |
141 | |
142 | /* |
143 | * Store the name of the variable in the pstore_record priv field, so |
144 | * we can reuse it later if we need to delete the EFI variable from the |
145 | * variable store. |
146 | */ |
147 | wlen = (ucs2_strnlen(s: varname, DUMP_NAME_LEN) + 1) * sizeof(efi_char16_t); |
148 | record->priv = kmemdup(p: varname, size: wlen, GFP_KERNEL); |
149 | if (!record->priv) { |
150 | kfree(objp: record->buf); |
151 | return -ENOMEM; |
152 | } |
153 | |
154 | return size; |
155 | } |
156 | |
157 | static ssize_t efi_pstore_read(struct pstore_record *record) |
158 | { |
159 | efi_char16_t *varname = record->psi->data; |
160 | efi_guid_t guid = LINUX_EFI_CRASH_GUID; |
161 | unsigned long varname_size; |
162 | efi_status_t status; |
163 | |
164 | for (;;) { |
165 | varname_size = 1024; |
166 | |
167 | /* |
168 | * If this is the first read() call in the pstore enumeration, |
169 | * varname will be the empty string, and the GetNextVariable() |
170 | * runtime service call will return the first EFI variable in |
171 | * its own enumeration order, ignoring the guid argument. |
172 | * |
173 | * Subsequent calls to GetNextVariable() must pass the name and |
174 | * guid values returned by the previous call, which is why we |
175 | * store varname in record->psi->data. Given that we only |
176 | * enumerate variables with the efi-pstore GUID, there is no |
177 | * need to record the guid return value. |
178 | */ |
179 | status = efivar_get_next_variable(name_size: &varname_size, name: varname, vendor: &guid); |
180 | if (status == EFI_NOT_FOUND) |
181 | return 0; |
182 | |
183 | if (status != EFI_SUCCESS) |
184 | return -EIO; |
185 | |
186 | /* skip variables that don't concern us */ |
187 | if (efi_guidcmp(left: guid, LINUX_EFI_CRASH_GUID)) |
188 | continue; |
189 | |
190 | return efi_pstore_read_func(record, varname); |
191 | } |
192 | } |
193 | |
194 | static int efi_pstore_write(struct pstore_record *record) |
195 | { |
196 | char name[DUMP_NAME_LEN]; |
197 | efi_char16_t efi_name[DUMP_NAME_LEN]; |
198 | efi_status_t status; |
199 | int i; |
200 | |
201 | record->id = generic_id(timestamp: record->time.tv_sec, part: record->part, |
202 | count: record->count); |
203 | |
204 | /* Since we copy the entire length of name, make sure it is wiped. */ |
205 | memset(name, 0, sizeof(name)); |
206 | |
207 | snprintf(buf: name, size: sizeof(name), fmt: "dump-type%u-%u-%d-%lld-%c" , |
208 | record->type, record->part, record->count, |
209 | (long long)record->time.tv_sec, |
210 | record->compressed ? 'C' : 'D'); |
211 | |
212 | for (i = 0; i < DUMP_NAME_LEN; i++) |
213 | efi_name[i] = name[i]; |
214 | |
215 | if (efivar_trylock()) |
216 | return -EBUSY; |
217 | status = efivar_set_variable_locked(name: efi_name, vendor: &LINUX_EFI_CRASH_GUID, |
218 | PSTORE_EFI_ATTRIBUTES, |
219 | data_size: record->size, data: record->psi->buf, |
220 | nonblocking: true); |
221 | efivar_unlock(); |
222 | return status == EFI_SUCCESS ? 0 : -EIO; |
223 | }; |
224 | |
225 | static int efi_pstore_erase(struct pstore_record *record) |
226 | { |
227 | efi_status_t status; |
228 | |
229 | status = efivar_set_variable(name: record->priv, vendor: &LINUX_EFI_CRASH_GUID, |
230 | PSTORE_EFI_ATTRIBUTES, data_size: 0, NULL); |
231 | |
232 | if (status != EFI_SUCCESS && status != EFI_NOT_FOUND) |
233 | return -EIO; |
234 | return 0; |
235 | } |
236 | |
237 | static struct pstore_info efi_pstore_info = { |
238 | .owner = THIS_MODULE, |
239 | .name = KBUILD_MODNAME, |
240 | .flags = PSTORE_FLAGS_DMESG, |
241 | .open = efi_pstore_open, |
242 | .close = efi_pstore_close, |
243 | .read = efi_pstore_read, |
244 | .write = efi_pstore_write, |
245 | .erase = efi_pstore_erase, |
246 | }; |
247 | |
248 | static int efivars_pstore_init(void) |
249 | { |
250 | if (!efivar_supports_writes()) |
251 | return 0; |
252 | |
253 | if (pstore_disable) |
254 | return 0; |
255 | |
256 | /* |
257 | * Notice that 1024 is the minimum here to prevent issues with |
258 | * decompression algorithms that were spotted during tests; |
259 | * even in the case of not using compression, smaller values would |
260 | * just pollute more the pstore FS with many small collected files. |
261 | */ |
262 | if (record_size < 1024) |
263 | record_size = 1024; |
264 | |
265 | efi_pstore_info.buf = kmalloc(size: record_size, GFP_KERNEL); |
266 | if (!efi_pstore_info.buf) |
267 | return -ENOMEM; |
268 | |
269 | efi_pstore_info.bufsize = record_size; |
270 | |
271 | if (pstore_register(&efi_pstore_info)) { |
272 | kfree(objp: efi_pstore_info.buf); |
273 | efi_pstore_info.buf = NULL; |
274 | efi_pstore_info.bufsize = 0; |
275 | } |
276 | |
277 | return 0; |
278 | } |
279 | |
280 | static void efivars_pstore_exit(void) |
281 | { |
282 | if (!efi_pstore_info.bufsize) |
283 | return; |
284 | |
285 | pstore_unregister(&efi_pstore_info); |
286 | kfree(objp: efi_pstore_info.buf); |
287 | efi_pstore_info.buf = NULL; |
288 | efi_pstore_info.bufsize = 0; |
289 | } |
290 | |
291 | module_init(efivars_pstore_init); |
292 | module_exit(efivars_pstore_exit); |
293 | |
294 | MODULE_DESCRIPTION("EFI variable backend for pstore" ); |
295 | MODULE_LICENSE("GPL" ); |
296 | MODULE_ALIAS("platform:efivars" ); |
297 | |