diff options
author | David S. Miller <davem@davemloft.net> | 2008-09-10 06:52:51 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2008-09-11 02:11:58 -0400 |
commit | e6e003720fd7123482f77dcec19e930d272937fe (patch) | |
tree | 4b08ce49ffc264b7c2e8ae9a7c4650e9f4eff63b /arch/sparc64/kernel/psycho_common.c | |
parent | 1c03a55cdf309d0939e881b313abbe7e9a67d95e (diff) |
sparc64: Commonize large portions of PSYCHO error handling.
The IOMMU and streaming cache error interrogation is moved here
as well as the PCI error interrupt handler.
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'arch/sparc64/kernel/psycho_common.c')
-rw-r--r-- | arch/sparc64/kernel/psycho_common.c | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/arch/sparc64/kernel/psycho_common.c b/arch/sparc64/kernel/psycho_common.c index 1b4d462513df..6b188dfeeb9d 100644 --- a/arch/sparc64/kernel/psycho_common.c +++ b/arch/sparc64/kernel/psycho_common.c | |||
@@ -3,15 +3,368 @@ | |||
3 | * Copyright (C) 2008 David S. Miller <davem@davemloft.net> | 3 | * Copyright (C) 2008 David S. Miller <davem@davemloft.net> |
4 | */ | 4 | */ |
5 | #include <linux/kernel.h> | 5 | #include <linux/kernel.h> |
6 | #include <linux/interrupt.h> | ||
6 | 7 | ||
7 | #include <asm/upa.h> | 8 | #include <asm/upa.h> |
8 | 9 | ||
9 | #include "pci_impl.h" | 10 | #include "pci_impl.h" |
11 | #include "iommu_common.h" | ||
10 | #include "psycho_common.h" | 12 | #include "psycho_common.h" |
11 | 13 | ||
14 | #define PSYCHO_STRBUF_CTRL_DENAB 0x0000000000000002UL | ||
15 | #define PSYCHO_STCERR_WRITE 0x0000000000000002UL | ||
16 | #define PSYCHO_STCERR_READ 0x0000000000000001UL | ||
17 | #define PSYCHO_STCTAG_PPN 0x0fffffff00000000UL | ||
18 | #define PSYCHO_STCTAG_VPN 0x00000000ffffe000UL | ||
19 | #define PSYCHO_STCTAG_VALID 0x0000000000000002UL | ||
20 | #define PSYCHO_STCTAG_WRITE 0x0000000000000001UL | ||
21 | #define PSYCHO_STCLINE_LINDX 0x0000000001e00000UL | ||
22 | #define PSYCHO_STCLINE_SPTR 0x00000000001f8000UL | ||
23 | #define PSYCHO_STCLINE_LADDR 0x0000000000007f00UL | ||
24 | #define PSYCHO_STCLINE_EPTR 0x00000000000000fcUL | ||
25 | #define PSYCHO_STCLINE_VALID 0x0000000000000002UL | ||
26 | #define PSYCHO_STCLINE_FOFN 0x0000000000000001UL | ||
27 | |||
28 | static DEFINE_SPINLOCK(stc_buf_lock); | ||
29 | static unsigned long stc_error_buf[128]; | ||
30 | static unsigned long stc_tag_buf[16]; | ||
31 | static unsigned long stc_line_buf[16]; | ||
32 | |||
33 | static void psycho_check_stc_error(struct pci_pbm_info *pbm) | ||
34 | { | ||
35 | unsigned long err_base, tag_base, line_base; | ||
36 | struct strbuf *strbuf = &pbm->stc; | ||
37 | u64 control; | ||
38 | int i; | ||
39 | |||
40 | if (!strbuf->strbuf_control) | ||
41 | return; | ||
42 | |||
43 | err_base = strbuf->strbuf_err_stat; | ||
44 | tag_base = strbuf->strbuf_tag_diag; | ||
45 | line_base = strbuf->strbuf_line_diag; | ||
46 | |||
47 | spin_lock(&stc_buf_lock); | ||
48 | |||
49 | /* This is __REALLY__ dangerous. When we put the streaming | ||
50 | * buffer into diagnostic mode to probe it's tags and error | ||
51 | * status, we _must_ clear all of the line tag valid bits | ||
52 | * before re-enabling the streaming buffer. If any dirty data | ||
53 | * lives in the STC when we do this, we will end up | ||
54 | * invalidating it before it has a chance to reach main | ||
55 | * memory. | ||
56 | */ | ||
57 | control = upa_readq(strbuf->strbuf_control); | ||
58 | upa_writeq(control | PSYCHO_STRBUF_CTRL_DENAB, strbuf->strbuf_control); | ||
59 | for (i = 0; i < 128; i++) { | ||
60 | u64 val; | ||
61 | |||
62 | val = upa_readq(err_base + (i * 8UL)); | ||
63 | upa_writeq(0UL, err_base + (i * 8UL)); | ||
64 | stc_error_buf[i] = val; | ||
65 | } | ||
66 | for (i = 0; i < 16; i++) { | ||
67 | stc_tag_buf[i] = upa_readq(tag_base + (i * 8UL)); | ||
68 | stc_line_buf[i] = upa_readq(line_base + (i * 8UL)); | ||
69 | upa_writeq(0UL, tag_base + (i * 8UL)); | ||
70 | upa_writeq(0UL, line_base + (i * 8UL)); | ||
71 | } | ||
72 | |||
73 | /* OK, state is logged, exit diagnostic mode. */ | ||
74 | upa_writeq(control, strbuf->strbuf_control); | ||
75 | |||
76 | for (i = 0; i < 16; i++) { | ||
77 | int j, saw_error, first, last; | ||
78 | |||
79 | saw_error = 0; | ||
80 | first = i * 8; | ||
81 | last = first + 8; | ||
82 | for (j = first; j < last; j++) { | ||
83 | u64 errval = stc_error_buf[j]; | ||
84 | if (errval != 0) { | ||
85 | saw_error++; | ||
86 | printk(KERN_ERR "%s: STC_ERR(%d)[wr(%d)" | ||
87 | "rd(%d)]\n", | ||
88 | pbm->name, | ||
89 | j, | ||
90 | (errval & PSYCHO_STCERR_WRITE) ? 1 : 0, | ||
91 | (errval & PSYCHO_STCERR_READ) ? 1 : 0); | ||
92 | } | ||
93 | } | ||
94 | if (saw_error != 0) { | ||
95 | u64 tagval = stc_tag_buf[i]; | ||
96 | u64 lineval = stc_line_buf[i]; | ||
97 | printk(KERN_ERR "%s: STC_TAG(%d)[PA(%016lx)VA(%08lx)" | ||
98 | "V(%d)W(%d)]\n", | ||
99 | pbm->name, | ||
100 | i, | ||
101 | ((tagval & PSYCHO_STCTAG_PPN) >> 19UL), | ||
102 | (tagval & PSYCHO_STCTAG_VPN), | ||
103 | ((tagval & PSYCHO_STCTAG_VALID) ? 1 : 0), | ||
104 | ((tagval & PSYCHO_STCTAG_WRITE) ? 1 : 0)); | ||
105 | printk(KERN_ERR "%s: STC_LINE(%d)[LIDX(%lx)SP(%lx)" | ||
106 | "LADDR(%lx)EP(%lx)V(%d)FOFN(%d)]\n", | ||
107 | pbm->name, | ||
108 | i, | ||
109 | ((lineval & PSYCHO_STCLINE_LINDX) >> 21UL), | ||
110 | ((lineval & PSYCHO_STCLINE_SPTR) >> 15UL), | ||
111 | ((lineval & PSYCHO_STCLINE_LADDR) >> 8UL), | ||
112 | ((lineval & PSYCHO_STCLINE_EPTR) >> 2UL), | ||
113 | ((lineval & PSYCHO_STCLINE_VALID) ? 1 : 0), | ||
114 | ((lineval & PSYCHO_STCLINE_FOFN) ? 1 : 0)); | ||
115 | } | ||
116 | } | ||
117 | |||
118 | spin_unlock(&stc_buf_lock); | ||
119 | } | ||
120 | |||
12 | #define PSYCHO_IOMMU_TAG 0xa580UL | 121 | #define PSYCHO_IOMMU_TAG 0xa580UL |
13 | #define PSYCHO_IOMMU_DATA 0xa600UL | 122 | #define PSYCHO_IOMMU_DATA 0xa600UL |
14 | 123 | ||
124 | static void psycho_record_iommu_tags_and_data(struct pci_pbm_info *pbm, | ||
125 | u64 *tag, u64 *data) | ||
126 | { | ||
127 | int i; | ||
128 | |||
129 | for (i = 0; i < 16; i++) { | ||
130 | unsigned long base = pbm->controller_regs; | ||
131 | unsigned long off = i * 8UL; | ||
132 | |||
133 | tag[i] = upa_readq(base + PSYCHO_IOMMU_TAG+off); | ||
134 | data[i] = upa_readq(base + PSYCHO_IOMMU_DATA+off); | ||
135 | |||
136 | /* Now clear out the entry. */ | ||
137 | upa_writeq(0, base + PSYCHO_IOMMU_TAG + off); | ||
138 | upa_writeq(0, base + PSYCHO_IOMMU_DATA + off); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | #define PSYCHO_IOMMU_TAG_ERRSTS (0x3UL << 23UL) | ||
143 | #define PSYCHO_IOMMU_TAG_ERR (0x1UL << 22UL) | ||
144 | #define PSYCHO_IOMMU_TAG_WRITE (0x1UL << 21UL) | ||
145 | #define PSYCHO_IOMMU_TAG_STREAM (0x1UL << 20UL) | ||
146 | #define PSYCHO_IOMMU_TAG_SIZE (0x1UL << 19UL) | ||
147 | #define PSYCHO_IOMMU_TAG_VPAGE 0x7ffffUL | ||
148 | #define PSYCHO_IOMMU_DATA_VALID (1UL << 30UL) | ||
149 | #define PSYCHO_IOMMU_DATA_CACHE (1UL << 28UL) | ||
150 | #define PSYCHO_IOMMU_DATA_PPAGE 0xfffffffUL | ||
151 | |||
152 | static void psycho_dump_iommu_tags_and_data(struct pci_pbm_info *pbm, | ||
153 | u64 *tag, u64 *data) | ||
154 | { | ||
155 | int i; | ||
156 | |||
157 | for (i = 0; i < 16; i++) { | ||
158 | u64 tag_val, data_val; | ||
159 | const char *type_str; | ||
160 | tag_val = tag[i]; | ||
161 | if (!(tag_val & PSYCHO_IOMMU_TAG_ERR)) | ||
162 | continue; | ||
163 | |||
164 | data_val = data[i]; | ||
165 | switch((tag_val & PSYCHO_IOMMU_TAG_ERRSTS) >> 23UL) { | ||
166 | case 0: | ||
167 | type_str = "Protection Error"; | ||
168 | break; | ||
169 | case 1: | ||
170 | type_str = "Invalid Error"; | ||
171 | break; | ||
172 | case 2: | ||
173 | type_str = "TimeOut Error"; | ||
174 | break; | ||
175 | case 3: | ||
176 | default: | ||
177 | type_str = "ECC Error"; | ||
178 | break; | ||
179 | } | ||
180 | |||
181 | printk(KERN_ERR "%s: IOMMU TAG(%d)[error(%s) wr(%d) " | ||
182 | "str(%d) sz(%dK) vpg(%08lx)]\n", | ||
183 | pbm->name, i, type_str, | ||
184 | ((tag_val & PSYCHO_IOMMU_TAG_WRITE) ? 1 : 0), | ||
185 | ((tag_val & PSYCHO_IOMMU_TAG_STREAM) ? 1 : 0), | ||
186 | ((tag_val & PSYCHO_IOMMU_TAG_SIZE) ? 64 : 8), | ||
187 | (tag_val & PSYCHO_IOMMU_TAG_VPAGE) << IOMMU_PAGE_SHIFT); | ||
188 | printk(KERN_ERR "%s: IOMMU DATA(%d)[valid(%d) cache(%d) " | ||
189 | "ppg(%016lx)]\n", | ||
190 | pbm->name, i, | ||
191 | ((data_val & PSYCHO_IOMMU_DATA_VALID) ? 1 : 0), | ||
192 | ((data_val & PSYCHO_IOMMU_DATA_CACHE) ? 1 : 0), | ||
193 | (data_val & PSYCHO_IOMMU_DATA_PPAGE)<<IOMMU_PAGE_SHIFT); | ||
194 | } | ||
195 | } | ||
196 | |||
197 | #define PSYCHO_IOMMU_CTRL_XLTESTAT 0x0000000006000000UL | ||
198 | #define PSYCHO_IOMMU_CTRL_XLTEERR 0x0000000001000000UL | ||
199 | |||
200 | void psycho_check_iommu_error(struct pci_pbm_info *pbm, | ||
201 | unsigned long afsr, | ||
202 | unsigned long afar, | ||
203 | enum psycho_error_type type) | ||
204 | { | ||
205 | u64 control, iommu_tag[16], iommu_data[16]; | ||
206 | struct iommu *iommu = pbm->iommu; | ||
207 | unsigned long flags; | ||
208 | |||
209 | spin_lock_irqsave(&iommu->lock, flags); | ||
210 | control = upa_readq(iommu->iommu_control); | ||
211 | if (control & PSYCHO_IOMMU_CTRL_XLTEERR) { | ||
212 | const char *type_str; | ||
213 | |||
214 | control &= ~PSYCHO_IOMMU_CTRL_XLTEERR; | ||
215 | upa_writeq(control, iommu->iommu_control); | ||
216 | |||
217 | switch ((control & PSYCHO_IOMMU_CTRL_XLTESTAT) >> 25UL) { | ||
218 | case 0: | ||
219 | type_str = "Protection Error"; | ||
220 | break; | ||
221 | case 1: | ||
222 | type_str = "Invalid Error"; | ||
223 | break; | ||
224 | case 2: | ||
225 | type_str = "TimeOut Error"; | ||
226 | break; | ||
227 | case 3: | ||
228 | default: | ||
229 | type_str = "ECC Error"; | ||
230 | break; | ||
231 | }; | ||
232 | printk(KERN_ERR "%s: IOMMU Error, type[%s]\n", | ||
233 | pbm->name, type_str); | ||
234 | |||
235 | /* It is very possible for another DVMA to occur while | ||
236 | * we do this probe, and corrupt the system further. | ||
237 | * But we are so screwed at this point that we are | ||
238 | * likely to crash hard anyways, so get as much | ||
239 | * diagnostic information to the console as we can. | ||
240 | */ | ||
241 | psycho_record_iommu_tags_and_data(pbm, iommu_tag, iommu_data); | ||
242 | psycho_dump_iommu_tags_and_data(pbm, iommu_tag, iommu_data); | ||
243 | } | ||
244 | psycho_check_stc_error(pbm); | ||
245 | spin_unlock_irqrestore(&iommu->lock, flags); | ||
246 | } | ||
247 | |||
248 | #define PSYCHO_PCICTRL_SBH_ERR 0x0000000800000000UL | ||
249 | #define PSYCHO_PCICTRL_SERR 0x0000000400000000UL | ||
250 | |||
251 | static irqreturn_t psycho_pcierr_intr_other(struct pci_pbm_info *pbm) | ||
252 | { | ||
253 | irqreturn_t ret = IRQ_NONE; | ||
254 | u64 csr, csr_error_bits; | ||
255 | u16 stat; | ||
256 | |||
257 | csr = upa_readq(pbm->pci_csr); | ||
258 | csr_error_bits = csr & (PSYCHO_PCICTRL_SBH_ERR | PSYCHO_PCICTRL_SERR); | ||
259 | if (csr_error_bits) { | ||
260 | /* Clear the errors. */ | ||
261 | upa_writeq(csr, pbm->pci_csr); | ||
262 | |||
263 | /* Log 'em. */ | ||
264 | if (csr_error_bits & PSYCHO_PCICTRL_SBH_ERR) | ||
265 | printk(KERN_ERR "%s: PCI streaming byte hole " | ||
266 | "error asserted.\n", pbm->name); | ||
267 | if (csr_error_bits & PSYCHO_PCICTRL_SERR) | ||
268 | printk(KERN_ERR "%s: PCI SERR signal asserted.\n", | ||
269 | pbm->name); | ||
270 | ret = IRQ_HANDLED; | ||
271 | } | ||
272 | pci_read_config_word(pbm->pci_bus->self, PCI_STATUS, &stat); | ||
273 | if (stat & (PCI_STATUS_PARITY | | ||
274 | PCI_STATUS_SIG_TARGET_ABORT | | ||
275 | PCI_STATUS_REC_TARGET_ABORT | | ||
276 | PCI_STATUS_REC_MASTER_ABORT | | ||
277 | PCI_STATUS_SIG_SYSTEM_ERROR)) { | ||
278 | printk(KERN_ERR "%s: PCI bus error, PCI_STATUS[%04x]\n", | ||
279 | pbm->name, stat); | ||
280 | pci_write_config_word(pbm->pci_bus->self, PCI_STATUS, 0xffff); | ||
281 | ret = IRQ_HANDLED; | ||
282 | } | ||
283 | return ret; | ||
284 | } | ||
285 | |||
286 | #define PSYCHO_PCIAFSR_PMA 0x8000000000000000UL | ||
287 | #define PSYCHO_PCIAFSR_PTA 0x4000000000000000UL | ||
288 | #define PSYCHO_PCIAFSR_PRTRY 0x2000000000000000UL | ||
289 | #define PSYCHO_PCIAFSR_PPERR 0x1000000000000000UL | ||
290 | #define PSYCHO_PCIAFSR_SMA 0x0800000000000000UL | ||
291 | #define PSYCHO_PCIAFSR_STA 0x0400000000000000UL | ||
292 | #define PSYCHO_PCIAFSR_SRTRY 0x0200000000000000UL | ||
293 | #define PSYCHO_PCIAFSR_SPERR 0x0100000000000000UL | ||
294 | #define PSYCHO_PCIAFSR_RESV1 0x00ff000000000000UL | ||
295 | #define PSYCHO_PCIAFSR_BMSK 0x0000ffff00000000UL | ||
296 | #define PSYCHO_PCIAFSR_BLK 0x0000000080000000UL | ||
297 | #define PSYCHO_PCIAFSR_RESV2 0x0000000040000000UL | ||
298 | #define PSYCHO_PCIAFSR_MID 0x000000003e000000UL | ||
299 | #define PSYCHO_PCIAFSR_RESV3 0x0000000001ffffffUL | ||
300 | |||
301 | irqreturn_t psycho_pcierr_intr(int irq, void *dev_id) | ||
302 | { | ||
303 | struct pci_pbm_info *pbm = dev_id; | ||
304 | u64 afsr, afar, error_bits; | ||
305 | int reported; | ||
306 | |||
307 | afsr = upa_readq(pbm->pci_afsr); | ||
308 | afar = upa_readq(pbm->pci_afar); | ||
309 | error_bits = afsr & | ||
310 | (PSYCHO_PCIAFSR_PMA | PSYCHO_PCIAFSR_PTA | | ||
311 | PSYCHO_PCIAFSR_PRTRY | PSYCHO_PCIAFSR_PPERR | | ||
312 | PSYCHO_PCIAFSR_SMA | PSYCHO_PCIAFSR_STA | | ||
313 | PSYCHO_PCIAFSR_SRTRY | PSYCHO_PCIAFSR_SPERR); | ||
314 | if (!error_bits) | ||
315 | return psycho_pcierr_intr_other(pbm); | ||
316 | upa_writeq(error_bits, pbm->pci_afsr); | ||
317 | printk(KERN_ERR "%s: PCI Error, primary error type[%s]\n", | ||
318 | pbm->name, | ||
319 | (((error_bits & PSYCHO_PCIAFSR_PMA) ? | ||
320 | "Master Abort" : | ||
321 | ((error_bits & PSYCHO_PCIAFSR_PTA) ? | ||
322 | "Target Abort" : | ||
323 | ((error_bits & PSYCHO_PCIAFSR_PRTRY) ? | ||
324 | "Excessive Retries" : | ||
325 | ((error_bits & PSYCHO_PCIAFSR_PPERR) ? | ||
326 | "Parity Error" : "???")))))); | ||
327 | printk(KERN_ERR "%s: bytemask[%04lx] UPA_MID[%02lx] was_block(%d)\n", | ||
328 | pbm->name, | ||
329 | (afsr & PSYCHO_PCIAFSR_BMSK) >> 32UL, | ||
330 | (afsr & PSYCHO_PCIAFSR_MID) >> 25UL, | ||
331 | (afsr & PSYCHO_PCIAFSR_BLK) ? 1 : 0); | ||
332 | printk(KERN_ERR "%s: PCI AFAR [%016lx]\n", pbm->name, afar); | ||
333 | printk(KERN_ERR "%s: PCI Secondary errors [", pbm->name); | ||
334 | reported = 0; | ||
335 | if (afsr & PSYCHO_PCIAFSR_SMA) { | ||
336 | reported++; | ||
337 | printk("(Master Abort)"); | ||
338 | } | ||
339 | if (afsr & PSYCHO_PCIAFSR_STA) { | ||
340 | reported++; | ||
341 | printk("(Target Abort)"); | ||
342 | } | ||
343 | if (afsr & PSYCHO_PCIAFSR_SRTRY) { | ||
344 | reported++; | ||
345 | printk("(Excessive Retries)"); | ||
346 | } | ||
347 | if (afsr & PSYCHO_PCIAFSR_SPERR) { | ||
348 | reported++; | ||
349 | printk("(Parity Error)"); | ||
350 | } | ||
351 | if (!reported) | ||
352 | printk("(none)"); | ||
353 | printk("]\n"); | ||
354 | |||
355 | if (error_bits & (PSYCHO_PCIAFSR_PTA | PSYCHO_PCIAFSR_STA)) { | ||
356 | psycho_check_iommu_error(pbm, afsr, afar, PCI_ERR); | ||
357 | pci_scan_for_target_abort(pbm, pbm->pci_bus); | ||
358 | } | ||
359 | if (error_bits & (PSYCHO_PCIAFSR_PMA | PSYCHO_PCIAFSR_SMA)) | ||
360 | pci_scan_for_master_abort(pbm, pbm->pci_bus); | ||
361 | |||
362 | if (error_bits & (PSYCHO_PCIAFSR_PPERR | PSYCHO_PCIAFSR_SPERR)) | ||
363 | pci_scan_for_parity_error(pbm, pbm->pci_bus); | ||
364 | |||
365 | return IRQ_HANDLED; | ||
366 | } | ||
367 | |||
15 | static void psycho_iommu_flush(struct pci_pbm_info *pbm) | 368 | static void psycho_iommu_flush(struct pci_pbm_info *pbm) |
16 | { | 369 | { |
17 | int i; | 370 | int i; |