diff options
Diffstat (limited to 'drivers/acpi/apei/erst.c')
-rw-r--r-- | drivers/acpi/apei/erst.c | 855 |
1 files changed, 855 insertions, 0 deletions
diff --git a/drivers/acpi/apei/erst.c b/drivers/acpi/apei/erst.c new file mode 100644 index 000000000000..2ebc39115507 --- /dev/null +++ b/drivers/acpi/apei/erst.c | |||
@@ -0,0 +1,855 @@ | |||
1 | /* | ||
2 | * APEI Error Record Serialization Table support | ||
3 | * | ||
4 | * ERST is a way provided by APEI to save and retrieve hardware error | ||
5 | * infomation to and from a persistent store. | ||
6 | * | ||
7 | * For more information about ERST, please refer to ACPI Specification | ||
8 | * version 4.0, section 17.4. | ||
9 | * | ||
10 | * Copyright 2010 Intel Corp. | ||
11 | * Author: Huang Ying <ying.huang@intel.com> | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License version | ||
15 | * 2 as published by the Free Software Foundation. | ||
16 | * | ||
17 | * This program is distributed in the hope that it will be useful, | ||
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | * GNU General Public License for more details. | ||
21 | * | ||
22 | * You should have received a copy of the GNU General Public License | ||
23 | * along with this program; if not, write to the Free Software | ||
24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
25 | */ | ||
26 | |||
27 | #include <linux/kernel.h> | ||
28 | #include <linux/module.h> | ||
29 | #include <linux/init.h> | ||
30 | #include <linux/delay.h> | ||
31 | #include <linux/io.h> | ||
32 | #include <linux/acpi.h> | ||
33 | #include <linux/uaccess.h> | ||
34 | #include <linux/cper.h> | ||
35 | #include <linux/nmi.h> | ||
36 | #include <acpi/apei.h> | ||
37 | |||
38 | #include "apei-internal.h" | ||
39 | |||
40 | #define ERST_PFX "ERST: " | ||
41 | |||
42 | /* ERST command status */ | ||
43 | #define ERST_STATUS_SUCCESS 0x0 | ||
44 | #define ERST_STATUS_NOT_ENOUGH_SPACE 0x1 | ||
45 | #define ERST_STATUS_HARDWARE_NOT_AVAILABLE 0x2 | ||
46 | #define ERST_STATUS_FAILED 0x3 | ||
47 | #define ERST_STATUS_RECORD_STORE_EMPTY 0x4 | ||
48 | #define ERST_STATUS_RECORD_NOT_FOUND 0x5 | ||
49 | |||
50 | #define ERST_TAB_ENTRY(tab) \ | ||
51 | ((struct acpi_whea_header *)((char *)(tab) + \ | ||
52 | sizeof(struct acpi_table_erst))) | ||
53 | |||
54 | #define SPIN_UNIT 100 /* 100ns */ | ||
55 | /* Firmware should respond within 1 miliseconds */ | ||
56 | #define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC) | ||
57 | #define FIRMWARE_MAX_STALL 50 /* 50us */ | ||
58 | |||
59 | int erst_disable; | ||
60 | EXPORT_SYMBOL_GPL(erst_disable); | ||
61 | |||
62 | static struct acpi_table_erst *erst_tab; | ||
63 | |||
64 | /* ERST Error Log Address Range atrributes */ | ||
65 | #define ERST_RANGE_RESERVED 0x0001 | ||
66 | #define ERST_RANGE_NVRAM 0x0002 | ||
67 | #define ERST_RANGE_SLOW 0x0004 | ||
68 | |||
69 | /* | ||
70 | * ERST Error Log Address Range, used as buffer for reading/writing | ||
71 | * error records. | ||
72 | */ | ||
73 | static struct erst_erange { | ||
74 | u64 base; | ||
75 | u64 size; | ||
76 | void __iomem *vaddr; | ||
77 | u32 attr; | ||
78 | } erst_erange; | ||
79 | |||
80 | /* | ||
81 | * Prevent ERST interpreter to run simultaneously, because the | ||
82 | * corresponding firmware implementation may not work properly when | ||
83 | * invoked simultaneously. | ||
84 | * | ||
85 | * It is used to provide exclusive accessing for ERST Error Log | ||
86 | * Address Range too. | ||
87 | */ | ||
88 | static DEFINE_SPINLOCK(erst_lock); | ||
89 | |||
90 | static inline int erst_errno(int command_status) | ||
91 | { | ||
92 | switch (command_status) { | ||
93 | case ERST_STATUS_SUCCESS: | ||
94 | return 0; | ||
95 | case ERST_STATUS_HARDWARE_NOT_AVAILABLE: | ||
96 | return -ENODEV; | ||
97 | case ERST_STATUS_NOT_ENOUGH_SPACE: | ||
98 | return -ENOSPC; | ||
99 | case ERST_STATUS_RECORD_STORE_EMPTY: | ||
100 | case ERST_STATUS_RECORD_NOT_FOUND: | ||
101 | return -ENOENT; | ||
102 | default: | ||
103 | return -EINVAL; | ||
104 | } | ||
105 | } | ||
106 | |||
107 | static int erst_timedout(u64 *t, u64 spin_unit) | ||
108 | { | ||
109 | if ((s64)*t < spin_unit) { | ||
110 | pr_warning(FW_WARN ERST_PFX | ||
111 | "Firmware does not respond in time\n"); | ||
112 | return 1; | ||
113 | } | ||
114 | *t -= spin_unit; | ||
115 | ndelay(spin_unit); | ||
116 | touch_nmi_watchdog(); | ||
117 | return 0; | ||
118 | } | ||
119 | |||
120 | static int erst_exec_load_var1(struct apei_exec_context *ctx, | ||
121 | struct acpi_whea_header *entry) | ||
122 | { | ||
123 | return __apei_exec_read_register(entry, &ctx->var1); | ||
124 | } | ||
125 | |||
126 | static int erst_exec_load_var2(struct apei_exec_context *ctx, | ||
127 | struct acpi_whea_header *entry) | ||
128 | { | ||
129 | return __apei_exec_read_register(entry, &ctx->var2); | ||
130 | } | ||
131 | |||
132 | static int erst_exec_store_var1(struct apei_exec_context *ctx, | ||
133 | struct acpi_whea_header *entry) | ||
134 | { | ||
135 | return __apei_exec_write_register(entry, ctx->var1); | ||
136 | } | ||
137 | |||
138 | static int erst_exec_add(struct apei_exec_context *ctx, | ||
139 | struct acpi_whea_header *entry) | ||
140 | { | ||
141 | ctx->var1 += ctx->var2; | ||
142 | return 0; | ||
143 | } | ||
144 | |||
145 | static int erst_exec_subtract(struct apei_exec_context *ctx, | ||
146 | struct acpi_whea_header *entry) | ||
147 | { | ||
148 | ctx->var1 -= ctx->var2; | ||
149 | return 0; | ||
150 | } | ||
151 | |||
152 | static int erst_exec_add_value(struct apei_exec_context *ctx, | ||
153 | struct acpi_whea_header *entry) | ||
154 | { | ||
155 | int rc; | ||
156 | u64 val; | ||
157 | |||
158 | rc = __apei_exec_read_register(entry, &val); | ||
159 | if (rc) | ||
160 | return rc; | ||
161 | val += ctx->value; | ||
162 | rc = __apei_exec_write_register(entry, val); | ||
163 | return rc; | ||
164 | } | ||
165 | |||
166 | static int erst_exec_subtract_value(struct apei_exec_context *ctx, | ||
167 | struct acpi_whea_header *entry) | ||
168 | { | ||
169 | int rc; | ||
170 | u64 val; | ||
171 | |||
172 | rc = __apei_exec_read_register(entry, &val); | ||
173 | if (rc) | ||
174 | return rc; | ||
175 | val -= ctx->value; | ||
176 | rc = __apei_exec_write_register(entry, val); | ||
177 | return rc; | ||
178 | } | ||
179 | |||
180 | static int erst_exec_stall(struct apei_exec_context *ctx, | ||
181 | struct acpi_whea_header *entry) | ||
182 | { | ||
183 | u64 stall_time; | ||
184 | |||
185 | if (ctx->value > FIRMWARE_MAX_STALL) { | ||
186 | if (!in_nmi()) | ||
187 | pr_warning(FW_WARN ERST_PFX | ||
188 | "Too long stall time for stall instruction: %llx.\n", | ||
189 | ctx->value); | ||
190 | stall_time = FIRMWARE_MAX_STALL; | ||
191 | } else | ||
192 | stall_time = ctx->value; | ||
193 | udelay(stall_time); | ||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | static int erst_exec_stall_while_true(struct apei_exec_context *ctx, | ||
198 | struct acpi_whea_header *entry) | ||
199 | { | ||
200 | int rc; | ||
201 | u64 val; | ||
202 | u64 timeout = FIRMWARE_TIMEOUT; | ||
203 | u64 stall_time; | ||
204 | |||
205 | if (ctx->var1 > FIRMWARE_MAX_STALL) { | ||
206 | if (!in_nmi()) | ||
207 | pr_warning(FW_WARN ERST_PFX | ||
208 | "Too long stall time for stall while true instruction: %llx.\n", | ||
209 | ctx->var1); | ||
210 | stall_time = FIRMWARE_MAX_STALL; | ||
211 | } else | ||
212 | stall_time = ctx->var1; | ||
213 | |||
214 | for (;;) { | ||
215 | rc = __apei_exec_read_register(entry, &val); | ||
216 | if (rc) | ||
217 | return rc; | ||
218 | if (val != ctx->value) | ||
219 | break; | ||
220 | if (erst_timedout(&timeout, stall_time * NSEC_PER_USEC)) | ||
221 | return -EIO; | ||
222 | } | ||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | static int erst_exec_skip_next_instruction_if_true( | ||
227 | struct apei_exec_context *ctx, | ||
228 | struct acpi_whea_header *entry) | ||
229 | { | ||
230 | int rc; | ||
231 | u64 val; | ||
232 | |||
233 | rc = __apei_exec_read_register(entry, &val); | ||
234 | if (rc) | ||
235 | return rc; | ||
236 | if (val == ctx->value) { | ||
237 | ctx->ip += 2; | ||
238 | return APEI_EXEC_SET_IP; | ||
239 | } | ||
240 | |||
241 | return 0; | ||
242 | } | ||
243 | |||
244 | static int erst_exec_goto(struct apei_exec_context *ctx, | ||
245 | struct acpi_whea_header *entry) | ||
246 | { | ||
247 | ctx->ip = ctx->value; | ||
248 | return APEI_EXEC_SET_IP; | ||
249 | } | ||
250 | |||
251 | static int erst_exec_set_src_address_base(struct apei_exec_context *ctx, | ||
252 | struct acpi_whea_header *entry) | ||
253 | { | ||
254 | return __apei_exec_read_register(entry, &ctx->src_base); | ||
255 | } | ||
256 | |||
257 | static int erst_exec_set_dst_address_base(struct apei_exec_context *ctx, | ||
258 | struct acpi_whea_header *entry) | ||
259 | { | ||
260 | return __apei_exec_read_register(entry, &ctx->dst_base); | ||
261 | } | ||
262 | |||
263 | static int erst_exec_move_data(struct apei_exec_context *ctx, | ||
264 | struct acpi_whea_header *entry) | ||
265 | { | ||
266 | int rc; | ||
267 | u64 offset; | ||
268 | |||
269 | rc = __apei_exec_read_register(entry, &offset); | ||
270 | if (rc) | ||
271 | return rc; | ||
272 | memmove((void *)ctx->dst_base + offset, | ||
273 | (void *)ctx->src_base + offset, | ||
274 | ctx->var2); | ||
275 | |||
276 | return 0; | ||
277 | } | ||
278 | |||
279 | static struct apei_exec_ins_type erst_ins_type[] = { | ||
280 | [ACPI_ERST_READ_REGISTER] = { | ||
281 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
282 | .run = apei_exec_read_register, | ||
283 | }, | ||
284 | [ACPI_ERST_READ_REGISTER_VALUE] = { | ||
285 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
286 | .run = apei_exec_read_register_value, | ||
287 | }, | ||
288 | [ACPI_ERST_WRITE_REGISTER] = { | ||
289 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
290 | .run = apei_exec_write_register, | ||
291 | }, | ||
292 | [ACPI_ERST_WRITE_REGISTER_VALUE] = { | ||
293 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
294 | .run = apei_exec_write_register_value, | ||
295 | }, | ||
296 | [ACPI_ERST_NOOP] = { | ||
297 | .flags = 0, | ||
298 | .run = apei_exec_noop, | ||
299 | }, | ||
300 | [ACPI_ERST_LOAD_VAR1] = { | ||
301 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
302 | .run = erst_exec_load_var1, | ||
303 | }, | ||
304 | [ACPI_ERST_LOAD_VAR2] = { | ||
305 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
306 | .run = erst_exec_load_var2, | ||
307 | }, | ||
308 | [ACPI_ERST_STORE_VAR1] = { | ||
309 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
310 | .run = erst_exec_store_var1, | ||
311 | }, | ||
312 | [ACPI_ERST_ADD] = { | ||
313 | .flags = 0, | ||
314 | .run = erst_exec_add, | ||
315 | }, | ||
316 | [ACPI_ERST_SUBTRACT] = { | ||
317 | .flags = 0, | ||
318 | .run = erst_exec_subtract, | ||
319 | }, | ||
320 | [ACPI_ERST_ADD_VALUE] = { | ||
321 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
322 | .run = erst_exec_add_value, | ||
323 | }, | ||
324 | [ACPI_ERST_SUBTRACT_VALUE] = { | ||
325 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
326 | .run = erst_exec_subtract_value, | ||
327 | }, | ||
328 | [ACPI_ERST_STALL] = { | ||
329 | .flags = 0, | ||
330 | .run = erst_exec_stall, | ||
331 | }, | ||
332 | [ACPI_ERST_STALL_WHILE_TRUE] = { | ||
333 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
334 | .run = erst_exec_stall_while_true, | ||
335 | }, | ||
336 | [ACPI_ERST_SKIP_NEXT_IF_TRUE] = { | ||
337 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
338 | .run = erst_exec_skip_next_instruction_if_true, | ||
339 | }, | ||
340 | [ACPI_ERST_GOTO] = { | ||
341 | .flags = 0, | ||
342 | .run = erst_exec_goto, | ||
343 | }, | ||
344 | [ACPI_ERST_SET_SRC_ADDRESS_BASE] = { | ||
345 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
346 | .run = erst_exec_set_src_address_base, | ||
347 | }, | ||
348 | [ACPI_ERST_SET_DST_ADDRESS_BASE] = { | ||
349 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
350 | .run = erst_exec_set_dst_address_base, | ||
351 | }, | ||
352 | [ACPI_ERST_MOVE_DATA] = { | ||
353 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
354 | .run = erst_exec_move_data, | ||
355 | }, | ||
356 | }; | ||
357 | |||
358 | static inline void erst_exec_ctx_init(struct apei_exec_context *ctx) | ||
359 | { | ||
360 | apei_exec_ctx_init(ctx, erst_ins_type, ARRAY_SIZE(erst_ins_type), | ||
361 | ERST_TAB_ENTRY(erst_tab), erst_tab->entries); | ||
362 | } | ||
363 | |||
364 | static int erst_get_erange(struct erst_erange *range) | ||
365 | { | ||
366 | struct apei_exec_context ctx; | ||
367 | int rc; | ||
368 | |||
369 | erst_exec_ctx_init(&ctx); | ||
370 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_RANGE); | ||
371 | if (rc) | ||
372 | return rc; | ||
373 | range->base = apei_exec_ctx_get_output(&ctx); | ||
374 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_LENGTH); | ||
375 | if (rc) | ||
376 | return rc; | ||
377 | range->size = apei_exec_ctx_get_output(&ctx); | ||
378 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_ATTRIBUTES); | ||
379 | if (rc) | ||
380 | return rc; | ||
381 | range->attr = apei_exec_ctx_get_output(&ctx); | ||
382 | |||
383 | return 0; | ||
384 | } | ||
385 | |||
386 | static ssize_t __erst_get_record_count(void) | ||
387 | { | ||
388 | struct apei_exec_context ctx; | ||
389 | int rc; | ||
390 | |||
391 | erst_exec_ctx_init(&ctx); | ||
392 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_COUNT); | ||
393 | if (rc) | ||
394 | return rc; | ||
395 | return apei_exec_ctx_get_output(&ctx); | ||
396 | } | ||
397 | |||
398 | ssize_t erst_get_record_count(void) | ||
399 | { | ||
400 | ssize_t count; | ||
401 | unsigned long flags; | ||
402 | |||
403 | if (erst_disable) | ||
404 | return -ENODEV; | ||
405 | |||
406 | spin_lock_irqsave(&erst_lock, flags); | ||
407 | count = __erst_get_record_count(); | ||
408 | spin_unlock_irqrestore(&erst_lock, flags); | ||
409 | |||
410 | return count; | ||
411 | } | ||
412 | EXPORT_SYMBOL_GPL(erst_get_record_count); | ||
413 | |||
414 | static int __erst_get_next_record_id(u64 *record_id) | ||
415 | { | ||
416 | struct apei_exec_context ctx; | ||
417 | int rc; | ||
418 | |||
419 | erst_exec_ctx_init(&ctx); | ||
420 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_ID); | ||
421 | if (rc) | ||
422 | return rc; | ||
423 | *record_id = apei_exec_ctx_get_output(&ctx); | ||
424 | |||
425 | return 0; | ||
426 | } | ||
427 | |||
428 | /* | ||
429 | * Get the record ID of an existing error record on the persistent | ||
430 | * storage. If there is no error record on the persistent storage, the | ||
431 | * returned record_id is APEI_ERST_INVALID_RECORD_ID. | ||
432 | */ | ||
433 | int erst_get_next_record_id(u64 *record_id) | ||
434 | { | ||
435 | int rc; | ||
436 | unsigned long flags; | ||
437 | |||
438 | if (erst_disable) | ||
439 | return -ENODEV; | ||
440 | |||
441 | spin_lock_irqsave(&erst_lock, flags); | ||
442 | rc = __erst_get_next_record_id(record_id); | ||
443 | spin_unlock_irqrestore(&erst_lock, flags); | ||
444 | |||
445 | return rc; | ||
446 | } | ||
447 | EXPORT_SYMBOL_GPL(erst_get_next_record_id); | ||
448 | |||
449 | static int __erst_write_to_storage(u64 offset) | ||
450 | { | ||
451 | struct apei_exec_context ctx; | ||
452 | u64 timeout = FIRMWARE_TIMEOUT; | ||
453 | u64 val; | ||
454 | int rc; | ||
455 | |||
456 | erst_exec_ctx_init(&ctx); | ||
457 | rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_WRITE); | ||
458 | if (rc) | ||
459 | return rc; | ||
460 | apei_exec_ctx_set_input(&ctx, offset); | ||
461 | rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET); | ||
462 | if (rc) | ||
463 | return rc; | ||
464 | rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); | ||
465 | if (rc) | ||
466 | return rc; | ||
467 | for (;;) { | ||
468 | rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); | ||
469 | if (rc) | ||
470 | return rc; | ||
471 | val = apei_exec_ctx_get_output(&ctx); | ||
472 | if (!val) | ||
473 | break; | ||
474 | if (erst_timedout(&timeout, SPIN_UNIT)) | ||
475 | return -EIO; | ||
476 | } | ||
477 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); | ||
478 | if (rc) | ||
479 | return rc; | ||
480 | val = apei_exec_ctx_get_output(&ctx); | ||
481 | rc = apei_exec_run(&ctx, ACPI_ERST_END); | ||
482 | if (rc) | ||
483 | return rc; | ||
484 | |||
485 | return erst_errno(val); | ||
486 | } | ||
487 | |||
488 | static int __erst_read_from_storage(u64 record_id, u64 offset) | ||
489 | { | ||
490 | struct apei_exec_context ctx; | ||
491 | u64 timeout = FIRMWARE_TIMEOUT; | ||
492 | u64 val; | ||
493 | int rc; | ||
494 | |||
495 | erst_exec_ctx_init(&ctx); | ||
496 | rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_READ); | ||
497 | if (rc) | ||
498 | return rc; | ||
499 | apei_exec_ctx_set_input(&ctx, offset); | ||
500 | rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET); | ||
501 | if (rc) | ||
502 | return rc; | ||
503 | apei_exec_ctx_set_input(&ctx, record_id); | ||
504 | rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID); | ||
505 | if (rc) | ||
506 | return rc; | ||
507 | rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); | ||
508 | if (rc) | ||
509 | return rc; | ||
510 | for (;;) { | ||
511 | rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); | ||
512 | if (rc) | ||
513 | return rc; | ||
514 | val = apei_exec_ctx_get_output(&ctx); | ||
515 | if (!val) | ||
516 | break; | ||
517 | if (erst_timedout(&timeout, SPIN_UNIT)) | ||
518 | return -EIO; | ||
519 | }; | ||
520 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); | ||
521 | if (rc) | ||
522 | return rc; | ||
523 | val = apei_exec_ctx_get_output(&ctx); | ||
524 | rc = apei_exec_run(&ctx, ACPI_ERST_END); | ||
525 | if (rc) | ||
526 | return rc; | ||
527 | |||
528 | return erst_errno(val); | ||
529 | } | ||
530 | |||
531 | static int __erst_clear_from_storage(u64 record_id) | ||
532 | { | ||
533 | struct apei_exec_context ctx; | ||
534 | u64 timeout = FIRMWARE_TIMEOUT; | ||
535 | u64 val; | ||
536 | int rc; | ||
537 | |||
538 | erst_exec_ctx_init(&ctx); | ||
539 | rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_CLEAR); | ||
540 | if (rc) | ||
541 | return rc; | ||
542 | apei_exec_ctx_set_input(&ctx, record_id); | ||
543 | rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID); | ||
544 | if (rc) | ||
545 | return rc; | ||
546 | rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); | ||
547 | if (rc) | ||
548 | return rc; | ||
549 | for (;;) { | ||
550 | rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); | ||
551 | if (rc) | ||
552 | return rc; | ||
553 | val = apei_exec_ctx_get_output(&ctx); | ||
554 | if (!val) | ||
555 | break; | ||
556 | if (erst_timedout(&timeout, SPIN_UNIT)) | ||
557 | return -EIO; | ||
558 | } | ||
559 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); | ||
560 | if (rc) | ||
561 | return rc; | ||
562 | val = apei_exec_ctx_get_output(&ctx); | ||
563 | rc = apei_exec_run(&ctx, ACPI_ERST_END); | ||
564 | if (rc) | ||
565 | return rc; | ||
566 | |||
567 | return erst_errno(val); | ||
568 | } | ||
569 | |||
570 | /* NVRAM ERST Error Log Address Range is not supported yet */ | ||
571 | static void pr_unimpl_nvram(void) | ||
572 | { | ||
573 | if (printk_ratelimit()) | ||
574 | pr_warning(ERST_PFX | ||
575 | "NVRAM ERST Log Address Range is not implemented yet\n"); | ||
576 | } | ||
577 | |||
578 | static int __erst_write_to_nvram(const struct cper_record_header *record) | ||
579 | { | ||
580 | /* do not print message, because printk is not safe for NMI */ | ||
581 | return -ENOSYS; | ||
582 | } | ||
583 | |||
584 | static int __erst_read_to_erange_from_nvram(u64 record_id, u64 *offset) | ||
585 | { | ||
586 | pr_unimpl_nvram(); | ||
587 | return -ENOSYS; | ||
588 | } | ||
589 | |||
590 | static int __erst_clear_from_nvram(u64 record_id) | ||
591 | { | ||
592 | pr_unimpl_nvram(); | ||
593 | return -ENOSYS; | ||
594 | } | ||
595 | |||
596 | int erst_write(const struct cper_record_header *record) | ||
597 | { | ||
598 | int rc; | ||
599 | unsigned long flags; | ||
600 | struct cper_record_header *rcd_erange; | ||
601 | |||
602 | if (erst_disable) | ||
603 | return -ENODEV; | ||
604 | |||
605 | if (memcmp(record->signature, CPER_SIG_RECORD, CPER_SIG_SIZE)) | ||
606 | return -EINVAL; | ||
607 | |||
608 | if (erst_erange.attr & ERST_RANGE_NVRAM) { | ||
609 | if (!spin_trylock_irqsave(&erst_lock, flags)) | ||
610 | return -EBUSY; | ||
611 | rc = __erst_write_to_nvram(record); | ||
612 | spin_unlock_irqrestore(&erst_lock, flags); | ||
613 | return rc; | ||
614 | } | ||
615 | |||
616 | if (record->record_length > erst_erange.size) | ||
617 | return -EINVAL; | ||
618 | |||
619 | if (!spin_trylock_irqsave(&erst_lock, flags)) | ||
620 | return -EBUSY; | ||
621 | memcpy(erst_erange.vaddr, record, record->record_length); | ||
622 | rcd_erange = erst_erange.vaddr; | ||
623 | /* signature for serialization system */ | ||
624 | memcpy(&rcd_erange->persistence_information, "ER", 2); | ||
625 | |||
626 | rc = __erst_write_to_storage(0); | ||
627 | spin_unlock_irqrestore(&erst_lock, flags); | ||
628 | |||
629 | return rc; | ||
630 | } | ||
631 | EXPORT_SYMBOL_GPL(erst_write); | ||
632 | |||
633 | static int __erst_read_to_erange(u64 record_id, u64 *offset) | ||
634 | { | ||
635 | int rc; | ||
636 | |||
637 | if (erst_erange.attr & ERST_RANGE_NVRAM) | ||
638 | return __erst_read_to_erange_from_nvram( | ||
639 | record_id, offset); | ||
640 | |||
641 | rc = __erst_read_from_storage(record_id, 0); | ||
642 | if (rc) | ||
643 | return rc; | ||
644 | *offset = 0; | ||
645 | |||
646 | return 0; | ||
647 | } | ||
648 | |||
649 | static ssize_t __erst_read(u64 record_id, struct cper_record_header *record, | ||
650 | size_t buflen) | ||
651 | { | ||
652 | int rc; | ||
653 | u64 offset, len = 0; | ||
654 | struct cper_record_header *rcd_tmp; | ||
655 | |||
656 | rc = __erst_read_to_erange(record_id, &offset); | ||
657 | if (rc) | ||
658 | return rc; | ||
659 | rcd_tmp = erst_erange.vaddr + offset; | ||
660 | len = rcd_tmp->record_length; | ||
661 | if (len <= buflen) | ||
662 | memcpy(record, rcd_tmp, len); | ||
663 | |||
664 | return len; | ||
665 | } | ||
666 | |||
667 | /* | ||
668 | * If return value > buflen, the buffer size is not big enough, | ||
669 | * else if return value < 0, something goes wrong, | ||
670 | * else everything is OK, and return value is record length | ||
671 | */ | ||
672 | ssize_t erst_read(u64 record_id, struct cper_record_header *record, | ||
673 | size_t buflen) | ||
674 | { | ||
675 | ssize_t len; | ||
676 | unsigned long flags; | ||
677 | |||
678 | if (erst_disable) | ||
679 | return -ENODEV; | ||
680 | |||
681 | spin_lock_irqsave(&erst_lock, flags); | ||
682 | len = __erst_read(record_id, record, buflen); | ||
683 | spin_unlock_irqrestore(&erst_lock, flags); | ||
684 | return len; | ||
685 | } | ||
686 | EXPORT_SYMBOL_GPL(erst_read); | ||
687 | |||
688 | /* | ||
689 | * If return value > buflen, the buffer size is not big enough, | ||
690 | * else if return value = 0, there is no more record to read, | ||
691 | * else if return value < 0, something goes wrong, | ||
692 | * else everything is OK, and return value is record length | ||
693 | */ | ||
694 | ssize_t erst_read_next(struct cper_record_header *record, size_t buflen) | ||
695 | { | ||
696 | int rc; | ||
697 | ssize_t len; | ||
698 | unsigned long flags; | ||
699 | u64 record_id; | ||
700 | |||
701 | if (erst_disable) | ||
702 | return -ENODEV; | ||
703 | |||
704 | spin_lock_irqsave(&erst_lock, flags); | ||
705 | rc = __erst_get_next_record_id(&record_id); | ||
706 | if (rc) { | ||
707 | spin_unlock_irqrestore(&erst_lock, flags); | ||
708 | return rc; | ||
709 | } | ||
710 | /* no more record */ | ||
711 | if (record_id == APEI_ERST_INVALID_RECORD_ID) { | ||
712 | spin_unlock_irqrestore(&erst_lock, flags); | ||
713 | return 0; | ||
714 | } | ||
715 | |||
716 | len = __erst_read(record_id, record, buflen); | ||
717 | spin_unlock_irqrestore(&erst_lock, flags); | ||
718 | |||
719 | return len; | ||
720 | } | ||
721 | EXPORT_SYMBOL_GPL(erst_read_next); | ||
722 | |||
723 | int erst_clear(u64 record_id) | ||
724 | { | ||
725 | int rc; | ||
726 | unsigned long flags; | ||
727 | |||
728 | if (erst_disable) | ||
729 | return -ENODEV; | ||
730 | |||
731 | spin_lock_irqsave(&erst_lock, flags); | ||
732 | if (erst_erange.attr & ERST_RANGE_NVRAM) | ||
733 | rc = __erst_clear_from_nvram(record_id); | ||
734 | else | ||
735 | rc = __erst_clear_from_storage(record_id); | ||
736 | spin_unlock_irqrestore(&erst_lock, flags); | ||
737 | |||
738 | return rc; | ||
739 | } | ||
740 | EXPORT_SYMBOL_GPL(erst_clear); | ||
741 | |||
742 | static int __init setup_erst_disable(char *str) | ||
743 | { | ||
744 | erst_disable = 1; | ||
745 | return 0; | ||
746 | } | ||
747 | |||
748 | __setup("erst_disable", setup_erst_disable); | ||
749 | |||
750 | static int erst_check_table(struct acpi_table_erst *erst_tab) | ||
751 | { | ||
752 | if (erst_tab->header_length != sizeof(struct acpi_table_erst)) | ||
753 | return -EINVAL; | ||
754 | if (erst_tab->header.length < sizeof(struct acpi_table_erst)) | ||
755 | return -EINVAL; | ||
756 | if (erst_tab->entries != | ||
757 | (erst_tab->header.length - sizeof(struct acpi_table_erst)) / | ||
758 | sizeof(struct acpi_erst_entry)) | ||
759 | return -EINVAL; | ||
760 | |||
761 | return 0; | ||
762 | } | ||
763 | |||
764 | static int __init erst_init(void) | ||
765 | { | ||
766 | int rc = 0; | ||
767 | acpi_status status; | ||
768 | struct apei_exec_context ctx; | ||
769 | struct apei_resources erst_resources; | ||
770 | struct resource *r; | ||
771 | |||
772 | if (acpi_disabled) | ||
773 | goto err; | ||
774 | |||
775 | if (erst_disable) { | ||
776 | pr_info(ERST_PFX | ||
777 | "Error Record Serialization Table (ERST) support is disabled.\n"); | ||
778 | goto err; | ||
779 | } | ||
780 | |||
781 | status = acpi_get_table(ACPI_SIG_ERST, 0, | ||
782 | (struct acpi_table_header **)&erst_tab); | ||
783 | if (status == AE_NOT_FOUND) { | ||
784 | pr_err(ERST_PFX "Table is not found!\n"); | ||
785 | goto err; | ||
786 | } else if (ACPI_FAILURE(status)) { | ||
787 | const char *msg = acpi_format_exception(status); | ||
788 | pr_err(ERST_PFX "Failed to get table, %s\n", msg); | ||
789 | rc = -EINVAL; | ||
790 | goto err; | ||
791 | } | ||
792 | |||
793 | rc = erst_check_table(erst_tab); | ||
794 | if (rc) { | ||
795 | pr_err(FW_BUG ERST_PFX "ERST table is invalid\n"); | ||
796 | goto err; | ||
797 | } | ||
798 | |||
799 | apei_resources_init(&erst_resources); | ||
800 | erst_exec_ctx_init(&ctx); | ||
801 | rc = apei_exec_collect_resources(&ctx, &erst_resources); | ||
802 | if (rc) | ||
803 | goto err_fini; | ||
804 | rc = apei_resources_request(&erst_resources, "APEI ERST"); | ||
805 | if (rc) | ||
806 | goto err_fini; | ||
807 | rc = apei_exec_pre_map_gars(&ctx); | ||
808 | if (rc) | ||
809 | goto err_release; | ||
810 | rc = erst_get_erange(&erst_erange); | ||
811 | if (rc) { | ||
812 | if (rc == -ENODEV) | ||
813 | pr_info(ERST_PFX | ||
814 | "The corresponding hardware device or firmware implementation " | ||
815 | "is not available.\n"); | ||
816 | else | ||
817 | pr_err(ERST_PFX | ||
818 | "Failed to get Error Log Address Range.\n"); | ||
819 | goto err_unmap_reg; | ||
820 | } | ||
821 | |||
822 | r = request_mem_region(erst_erange.base, erst_erange.size, "APEI ERST"); | ||
823 | if (!r) { | ||
824 | pr_err(ERST_PFX | ||
825 | "Can not request iomem region <0x%16llx-0x%16llx> for ERST.\n", | ||
826 | (unsigned long long)erst_erange.base, | ||
827 | (unsigned long long)erst_erange.base + erst_erange.size); | ||
828 | rc = -EIO; | ||
829 | goto err_unmap_reg; | ||
830 | } | ||
831 | rc = -ENOMEM; | ||
832 | erst_erange.vaddr = ioremap_cache(erst_erange.base, | ||
833 | erst_erange.size); | ||
834 | if (!erst_erange.vaddr) | ||
835 | goto err_release_erange; | ||
836 | |||
837 | pr_info(ERST_PFX | ||
838 | "Error Record Serialization Table (ERST) support is initialized.\n"); | ||
839 | |||
840 | return 0; | ||
841 | |||
842 | err_release_erange: | ||
843 | release_mem_region(erst_erange.base, erst_erange.size); | ||
844 | err_unmap_reg: | ||
845 | apei_exec_post_unmap_gars(&ctx); | ||
846 | err_release: | ||
847 | apei_resources_release(&erst_resources); | ||
848 | err_fini: | ||
849 | apei_resources_fini(&erst_resources); | ||
850 | err: | ||
851 | erst_disable = 1; | ||
852 | return rc; | ||
853 | } | ||
854 | |||
855 | device_initcall(erst_init); | ||