diff options
Diffstat (limited to 'arch/powerpc/platforms/powernv/opal.c')
-rw-r--r-- | arch/powerpc/platforms/powernv/opal.c | 100 |
1 files changed, 97 insertions, 3 deletions
diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c index 65499adaecff..d5f11d689d6c 100644 --- a/arch/powerpc/platforms/powernv/opal.c +++ b/arch/powerpc/platforms/powernv/opal.c | |||
@@ -21,6 +21,7 @@ | |||
21 | #include <linux/sched.h> | 21 | #include <linux/sched.h> |
22 | #include <linux/kobject.h> | 22 | #include <linux/kobject.h> |
23 | #include <linux/delay.h> | 23 | #include <linux/delay.h> |
24 | #include <linux/memblock.h> | ||
24 | #include <asm/opal.h> | 25 | #include <asm/opal.h> |
25 | #include <asm/firmware.h> | 26 | #include <asm/firmware.h> |
26 | #include <asm/mce.h> | 27 | #include <asm/mce.h> |
@@ -33,8 +34,18 @@ struct kobject *opal_kobj; | |||
33 | struct opal { | 34 | struct opal { |
34 | u64 base; | 35 | u64 base; |
35 | u64 entry; | 36 | u64 entry; |
37 | u64 size; | ||
36 | } opal; | 38 | } opal; |
37 | 39 | ||
40 | struct mcheck_recoverable_range { | ||
41 | u64 start_addr; | ||
42 | u64 end_addr; | ||
43 | u64 recover_addr; | ||
44 | }; | ||
45 | |||
46 | static struct mcheck_recoverable_range *mc_recoverable_range; | ||
47 | static int mc_recoverable_range_len; | ||
48 | |||
38 | static struct device_node *opal_node; | 49 | static struct device_node *opal_node; |
39 | static DEFINE_SPINLOCK(opal_write_lock); | 50 | static DEFINE_SPINLOCK(opal_write_lock); |
40 | extern u64 opal_mc_secondary_handler[]; | 51 | extern u64 opal_mc_secondary_handler[]; |
@@ -49,25 +60,29 @@ static atomic_t opal_notifier_hold = ATOMIC_INIT(0); | |||
49 | int __init early_init_dt_scan_opal(unsigned long node, | 60 | int __init early_init_dt_scan_opal(unsigned long node, |
50 | const char *uname, int depth, void *data) | 61 | const char *uname, int depth, void *data) |
51 | { | 62 | { |
52 | const void *basep, *entryp; | 63 | const void *basep, *entryp, *sizep; |
53 | unsigned long basesz, entrysz; | 64 | unsigned long basesz, entrysz, runtimesz; |
54 | 65 | ||
55 | if (depth != 1 || strcmp(uname, "ibm,opal") != 0) | 66 | if (depth != 1 || strcmp(uname, "ibm,opal") != 0) |
56 | return 0; | 67 | return 0; |
57 | 68 | ||
58 | basep = of_get_flat_dt_prop(node, "opal-base-address", &basesz); | 69 | basep = of_get_flat_dt_prop(node, "opal-base-address", &basesz); |
59 | entryp = of_get_flat_dt_prop(node, "opal-entry-address", &entrysz); | 70 | entryp = of_get_flat_dt_prop(node, "opal-entry-address", &entrysz); |
71 | sizep = of_get_flat_dt_prop(node, "opal-runtime-size", &runtimesz); | ||
60 | 72 | ||
61 | if (!basep || !entryp) | 73 | if (!basep || !entryp || !sizep) |
62 | return 1; | 74 | return 1; |
63 | 75 | ||
64 | opal.base = of_read_number(basep, basesz/4); | 76 | opal.base = of_read_number(basep, basesz/4); |
65 | opal.entry = of_read_number(entryp, entrysz/4); | 77 | opal.entry = of_read_number(entryp, entrysz/4); |
78 | opal.size = of_read_number(sizep, runtimesz/4); | ||
66 | 79 | ||
67 | pr_debug("OPAL Base = 0x%llx (basep=%p basesz=%ld)\n", | 80 | pr_debug("OPAL Base = 0x%llx (basep=%p basesz=%ld)\n", |
68 | opal.base, basep, basesz); | 81 | opal.base, basep, basesz); |
69 | pr_debug("OPAL Entry = 0x%llx (entryp=%p basesz=%ld)\n", | 82 | pr_debug("OPAL Entry = 0x%llx (entryp=%p basesz=%ld)\n", |
70 | opal.entry, entryp, entrysz); | 83 | opal.entry, entryp, entrysz); |
84 | pr_debug("OPAL Entry = 0x%llx (sizep=%p runtimesz=%ld)\n", | ||
85 | opal.size, sizep, runtimesz); | ||
71 | 86 | ||
72 | powerpc_firmware_features |= FW_FEATURE_OPAL; | 87 | powerpc_firmware_features |= FW_FEATURE_OPAL; |
73 | if (of_flat_dt_is_compatible(node, "ibm,opal-v3")) { | 88 | if (of_flat_dt_is_compatible(node, "ibm,opal-v3")) { |
@@ -84,6 +99,53 @@ int __init early_init_dt_scan_opal(unsigned long node, | |||
84 | return 1; | 99 | return 1; |
85 | } | 100 | } |
86 | 101 | ||
102 | int __init early_init_dt_scan_recoverable_ranges(unsigned long node, | ||
103 | const char *uname, int depth, void *data) | ||
104 | { | ||
105 | unsigned long i, size; | ||
106 | const __be32 *prop; | ||
107 | |||
108 | if (depth != 1 || strcmp(uname, "ibm,opal") != 0) | ||
109 | return 0; | ||
110 | |||
111 | prop = of_get_flat_dt_prop(node, "mcheck-recoverable-ranges", &size); | ||
112 | |||
113 | if (!prop) | ||
114 | return 1; | ||
115 | |||
116 | pr_debug("Found machine check recoverable ranges.\n"); | ||
117 | |||
118 | /* | ||
119 | * Allocate a buffer to hold the MC recoverable ranges. We would be | ||
120 | * accessing them in real mode, hence it needs to be within | ||
121 | * RMO region. | ||
122 | */ | ||
123 | mc_recoverable_range =__va(memblock_alloc_base(size, __alignof__(u64), | ||
124 | ppc64_rma_size)); | ||
125 | memset(mc_recoverable_range, 0, size); | ||
126 | |||
127 | /* | ||
128 | * Each recoverable address entry is an (start address,len, | ||
129 | * recover address) pair, * 2 cells each, totalling 4 cells per entry. | ||
130 | */ | ||
131 | for (i = 0; i < size / (sizeof(*prop) * 5); i++) { | ||
132 | mc_recoverable_range[i].start_addr = | ||
133 | of_read_number(prop + (i * 5) + 0, 2); | ||
134 | mc_recoverable_range[i].end_addr = | ||
135 | mc_recoverable_range[i].start_addr + | ||
136 | of_read_number(prop + (i * 5) + 2, 1); | ||
137 | mc_recoverable_range[i].recover_addr = | ||
138 | of_read_number(prop + (i * 5) + 3, 2); | ||
139 | |||
140 | pr_debug("Machine check recoverable range: %llx..%llx: %llx\n", | ||
141 | mc_recoverable_range[i].start_addr, | ||
142 | mc_recoverable_range[i].end_addr, | ||
143 | mc_recoverable_range[i].recover_addr); | ||
144 | } | ||
145 | mc_recoverable_range_len = i; | ||
146 | return 1; | ||
147 | } | ||
148 | |||
87 | static int __init opal_register_exception_handlers(void) | 149 | static int __init opal_register_exception_handlers(void) |
88 | { | 150 | { |
89 | #ifdef __BIG_ENDIAN__ | 151 | #ifdef __BIG_ENDIAN__ |
@@ -401,6 +463,38 @@ int opal_machine_check(struct pt_regs *regs) | |||
401 | return 0; | 463 | return 0; |
402 | } | 464 | } |
403 | 465 | ||
466 | static uint64_t find_recovery_address(uint64_t nip) | ||
467 | { | ||
468 | int i; | ||
469 | |||
470 | for (i = 0; i < mc_recoverable_range_len; i++) | ||
471 | if ((nip >= mc_recoverable_range[i].start_addr) && | ||
472 | (nip < mc_recoverable_range[i].end_addr)) | ||
473 | return mc_recoverable_range[i].recover_addr; | ||
474 | return 0; | ||
475 | } | ||
476 | |||
477 | bool opal_mce_check_early_recovery(struct pt_regs *regs) | ||
478 | { | ||
479 | uint64_t recover_addr = 0; | ||
480 | |||
481 | if (!opal.base || !opal.size) | ||
482 | goto out; | ||
483 | |||
484 | if ((regs->nip >= opal.base) && | ||
485 | (regs->nip <= (opal.base + opal.size))) | ||
486 | recover_addr = find_recovery_address(regs->nip); | ||
487 | |||
488 | /* | ||
489 | * Setup regs->nip to rfi into fixup address. | ||
490 | */ | ||
491 | if (recover_addr) | ||
492 | regs->nip = recover_addr; | ||
493 | |||
494 | out: | ||
495 | return !!recover_addr; | ||
496 | } | ||
497 | |||
404 | static irqreturn_t opal_interrupt(int irq, void *data) | 498 | static irqreturn_t opal_interrupt(int irq, void *data) |
405 | { | 499 | { |
406 | __be64 events; | 500 | __be64 events; |