Max Filippov | 3863c58 | 2016-11-16 15:02:29 -0800 | [diff] [blame] | 1 | /* |
| 2 | * S32C1I selftest. |
| 3 | * |
| 4 | * This file is subject to the terms and conditions of the GNU General Public |
| 5 | * License. See the file "COPYING" in the main directory of this archive |
| 6 | * for more details. |
| 7 | * |
| 8 | * Copyright (C) 2016 Cadence Design Systems Inc. |
| 9 | */ |
| 10 | |
| 11 | #include <linux/init.h> |
| 12 | #include <linux/kernel.h> |
| 13 | |
| 14 | #include <asm/traps.h> |
| 15 | |
| 16 | #if XCHAL_HAVE_S32C1I |
| 17 | |
| 18 | static int __initdata rcw_word, rcw_probe_pc, rcw_exc; |
| 19 | |
| 20 | /* |
| 21 | * Basic atomic compare-and-swap, that records PC of S32C1I for probing. |
| 22 | * |
| 23 | * If *v == cmp, set *v = set. Return previous *v. |
| 24 | */ |
| 25 | static inline int probed_compare_swap(int *v, int cmp, int set) |
| 26 | { |
| 27 | int tmp; |
| 28 | |
| 29 | __asm__ __volatile__( |
| 30 | " movi %1, 1f\n" |
| 31 | " s32i %1, %4, 0\n" |
| 32 | " wsr %2, scompare1\n" |
| 33 | "1: s32c1i %0, %3, 0\n" |
| 34 | : "=a" (set), "=&a" (tmp) |
| 35 | : "a" (cmp), "a" (v), "a" (&rcw_probe_pc), "0" (set) |
| 36 | : "memory" |
| 37 | ); |
| 38 | return set; |
| 39 | } |
| 40 | |
| 41 | /* Handle probed exception */ |
| 42 | |
| 43 | static void __init do_probed_exception(struct pt_regs *regs, |
| 44 | unsigned long exccause) |
| 45 | { |
| 46 | if (regs->pc == rcw_probe_pc) { /* exception on s32c1i ? */ |
| 47 | regs->pc += 3; /* skip the s32c1i instruction */ |
| 48 | rcw_exc = exccause; |
| 49 | } else { |
| 50 | do_unhandled(regs, exccause); |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | /* Simple test of S32C1I (soc bringup assist) */ |
| 55 | |
| 56 | static int __init check_s32c1i(void) |
| 57 | { |
| 58 | int n, cause1, cause2; |
| 59 | void *handbus, *handdata, *handaddr; /* temporarily saved handlers */ |
| 60 | |
| 61 | rcw_probe_pc = 0; |
| 62 | handbus = trap_set_handler(EXCCAUSE_LOAD_STORE_ERROR, |
| 63 | do_probed_exception); |
| 64 | handdata = trap_set_handler(EXCCAUSE_LOAD_STORE_DATA_ERROR, |
| 65 | do_probed_exception); |
| 66 | handaddr = trap_set_handler(EXCCAUSE_LOAD_STORE_ADDR_ERROR, |
| 67 | do_probed_exception); |
| 68 | |
| 69 | /* First try an S32C1I that does not store: */ |
| 70 | rcw_exc = 0; |
| 71 | rcw_word = 1; |
| 72 | n = probed_compare_swap(&rcw_word, 0, 2); |
| 73 | cause1 = rcw_exc; |
| 74 | |
| 75 | /* took exception? */ |
| 76 | if (cause1 != 0) { |
| 77 | /* unclean exception? */ |
| 78 | if (n != 2 || rcw_word != 1) |
| 79 | panic("S32C1I exception error"); |
| 80 | } else if (rcw_word != 1 || n != 1) { |
| 81 | panic("S32C1I compare error"); |
| 82 | } |
| 83 | |
| 84 | /* Then an S32C1I that stores: */ |
| 85 | rcw_exc = 0; |
| 86 | rcw_word = 0x1234567; |
| 87 | n = probed_compare_swap(&rcw_word, 0x1234567, 0xabcde); |
| 88 | cause2 = rcw_exc; |
| 89 | |
| 90 | if (cause2 != 0) { |
| 91 | /* unclean exception? */ |
| 92 | if (n != 0xabcde || rcw_word != 0x1234567) |
| 93 | panic("S32C1I exception error (b)"); |
| 94 | } else if (rcw_word != 0xabcde || n != 0x1234567) { |
| 95 | panic("S32C1I store error"); |
| 96 | } |
| 97 | |
| 98 | /* Verify consistency of exceptions: */ |
| 99 | if (cause1 || cause2) { |
| 100 | pr_warn("S32C1I took exception %d, %d\n", cause1, cause2); |
| 101 | /* If emulation of S32C1I upon bus error gets implemented, |
| 102 | * we can get rid of this panic for single core (not SMP) |
| 103 | */ |
| 104 | panic("S32C1I exceptions not currently supported"); |
| 105 | } |
| 106 | if (cause1 != cause2) |
| 107 | panic("inconsistent S32C1I exceptions"); |
| 108 | |
| 109 | trap_set_handler(EXCCAUSE_LOAD_STORE_ERROR, handbus); |
| 110 | trap_set_handler(EXCCAUSE_LOAD_STORE_DATA_ERROR, handdata); |
| 111 | trap_set_handler(EXCCAUSE_LOAD_STORE_ADDR_ERROR, handaddr); |
| 112 | return 0; |
| 113 | } |
| 114 | |
| 115 | #else /* XCHAL_HAVE_S32C1I */ |
| 116 | |
| 117 | /* This condition should not occur with a commercially deployed processor. |
| 118 | * Display reminder for early engr test or demo chips / FPGA bitstreams |
| 119 | */ |
| 120 | static int __init check_s32c1i(void) |
| 121 | { |
| 122 | pr_warn("Processor configuration lacks atomic compare-and-swap support!\n"); |
| 123 | return 0; |
| 124 | } |
| 125 | |
| 126 | #endif /* XCHAL_HAVE_S32C1I */ |
| 127 | |
| 128 | early_initcall(check_s32c1i); |