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