Commit | Line | Data |
---|---|---|
1da177e4 | 1 | /* |
4e8e56c6 | 2 | * Support for adapter interruptions |
1da177e4 | 3 | * |
a53c8fab | 4 | * Copyright IBM Corp. 1999, 2007 |
4e8e56c6 PO |
5 | * Author(s): Ingo Adlung <adlung@de.ibm.com> |
6 | * Cornelia Huck <cornelia.huck@de.ibm.com> | |
7 | * Arnd Bergmann <arndb@de.ibm.com> | |
8 | * Peter Oberparleiter <peter.oberparleiter@de.ibm.com> | |
1da177e4 LT |
9 | */ |
10 | ||
11 | #include <linux/init.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/slab.h> | |
14 | #include <linux/rcupdate.h> | |
15 | ||
4e8e56c6 | 16 | #include <asm/airq.h> |
da7c5af8 | 17 | #include <asm/isc.h> |
4e8e56c6 PO |
18 | |
19 | #include "cio.h" | |
1da177e4 | 20 | #include "cio_debug.h" |
1da177e4 | 21 | |
4e8e56c6 PO |
22 | #define NR_AIRQS 32 |
23 | #define NR_AIRQS_PER_WORD sizeof(unsigned long) | |
24 | #define NR_AIRQ_WORDS (NR_AIRQS / NR_AIRQS_PER_WORD) | |
1da177e4 | 25 | |
4e8e56c6 PO |
26 | union indicator_t { |
27 | unsigned long word[NR_AIRQ_WORDS]; | |
28 | unsigned char byte[NR_AIRQS]; | |
29 | } __attribute__((packed)); | |
1da177e4 | 30 | |
4e8e56c6 PO |
31 | struct airq_t { |
32 | adapter_int_handler_t handler; | |
33 | void *drv_data; | |
34 | }; | |
1da177e4 | 35 | |
a1f64073 SO |
36 | static union indicator_t indicators[MAX_ISC+1]; |
37 | static struct airq_t *airqs[MAX_ISC+1][NR_AIRQS]; | |
1da177e4 | 38 | |
da7c5af8 | 39 | static int register_airq(struct airq_t *airq, u8 isc) |
4e8e56c6 PO |
40 | { |
41 | int i; | |
1da177e4 | 42 | |
4e8e56c6 | 43 | for (i = 0; i < NR_AIRQS; i++) |
da7c5af8 | 44 | if (!cmpxchg(&airqs[isc][i], NULL, airq)) |
4e8e56c6 PO |
45 | return i; |
46 | return -ENOMEM; | |
1da177e4 LT |
47 | } |
48 | ||
4e8e56c6 PO |
49 | /** |
50 | * s390_register_adapter_interrupt() - register adapter interrupt handler | |
51 | * @handler: adapter handler to be registered | |
52 | * @drv_data: driver data passed with each call to the handler | |
da7c5af8 | 53 | * @isc: isc for which the handler should be called |
4e8e56c6 PO |
54 | * |
55 | * Returns: | |
56 | * Pointer to the indicator to be used on success | |
57 | * ERR_PTR() if registration failed | |
58 | */ | |
59 | void *s390_register_adapter_interrupt(adapter_int_handler_t handler, | |
da7c5af8 | 60 | void *drv_data, u8 isc) |
1da177e4 | 61 | { |
4e8e56c6 PO |
62 | struct airq_t *airq; |
63 | char dbf_txt[16]; | |
1da177e4 | 64 | int ret; |
1da177e4 | 65 | |
da7c5af8 CH |
66 | if (isc > MAX_ISC) |
67 | return ERR_PTR(-EINVAL); | |
4e8e56c6 PO |
68 | airq = kmalloc(sizeof(struct airq_t), GFP_KERNEL); |
69 | if (!airq) { | |
70 | ret = -ENOMEM; | |
71 | goto out; | |
1da177e4 | 72 | } |
4e8e56c6 PO |
73 | airq->handler = handler; |
74 | airq->drv_data = drv_data; | |
da7c5af8 CH |
75 | |
76 | ret = register_airq(airq, isc); | |
4e8e56c6 PO |
77 | out: |
78 | snprintf(dbf_txt, sizeof(dbf_txt), "rairq:%d", ret); | |
79 | CIO_TRACE_EVENT(4, dbf_txt); | |
da7c5af8 CH |
80 | if (ret < 0) { |
81 | kfree(airq); | |
4e8e56c6 | 82 | return ERR_PTR(ret); |
da7c5af8 CH |
83 | } else |
84 | return &indicators[isc].byte[ret]; | |
1da177e4 | 85 | } |
4e8e56c6 | 86 | EXPORT_SYMBOL(s390_register_adapter_interrupt); |
1da177e4 | 87 | |
4e8e56c6 PO |
88 | /** |
89 | * s390_unregister_adapter_interrupt - unregister adapter interrupt handler | |
90 | * @ind: indicator for which the handler is to be unregistered | |
da7c5af8 | 91 | * @isc: interruption subclass |
4e8e56c6 | 92 | */ |
da7c5af8 | 93 | void s390_unregister_adapter_interrupt(void *ind, u8 isc) |
1da177e4 | 94 | { |
4e8e56c6 PO |
95 | struct airq_t *airq; |
96 | char dbf_txt[16]; | |
97 | int i; | |
1da177e4 | 98 | |
da7c5af8 | 99 | i = (int) ((addr_t) ind) - ((addr_t) &indicators[isc].byte[0]); |
4e8e56c6 PO |
100 | snprintf(dbf_txt, sizeof(dbf_txt), "urairq:%d", i); |
101 | CIO_TRACE_EVENT(4, dbf_txt); | |
da7c5af8 CH |
102 | indicators[isc].byte[i] = 0; |
103 | airq = xchg(&airqs[isc][i], NULL); | |
4e8e56c6 PO |
104 | /* |
105 | * Allow interrupts to complete. This will ensure that the airq handle | |
106 | * is no longer referenced by any interrupt handler. | |
107 | */ | |
108 | synchronize_sched(); | |
109 | kfree(airq); | |
1da177e4 | 110 | } |
4e8e56c6 PO |
111 | EXPORT_SYMBOL(s390_unregister_adapter_interrupt); |
112 | ||
113 | #define INDICATOR_MASK (0xffUL << ((NR_AIRQS_PER_WORD - 1) * 8)) | |
1da177e4 | 114 | |
da7c5af8 | 115 | void do_adapter_IO(u8 isc) |
4e8e56c6 PO |
116 | { |
117 | int w; | |
118 | int i; | |
119 | unsigned long word; | |
120 | struct airq_t *airq; | |
121 | ||
122 | /* | |
123 | * Access indicator array in word-sized chunks to minimize storage | |
124 | * fetch operations. | |
125 | */ | |
126 | for (w = 0; w < NR_AIRQ_WORDS; w++) { | |
da7c5af8 | 127 | word = indicators[isc].word[w]; |
4e8e56c6 PO |
128 | i = w * NR_AIRQS_PER_WORD; |
129 | /* | |
130 | * Check bytes within word for active indicators. | |
131 | */ | |
132 | while (word) { | |
133 | if (word & INDICATOR_MASK) { | |
da7c5af8 | 134 | airq = airqs[isc][i]; |
87fa5af8 HC |
135 | /* Make sure gcc reads from airqs only once. */ |
136 | barrier(); | |
4e8e56c6 | 137 | if (likely(airq)) |
da7c5af8 | 138 | airq->handler(&indicators[isc].byte[i], |
4e8e56c6 PO |
139 | airq->drv_data); |
140 | else | |
141 | /* | |
142 | * Reset ill-behaved indicator. | |
143 | */ | |
da7c5af8 | 144 | indicators[isc].byte[i] = 0; |
4e8e56c6 PO |
145 | } |
146 | word <<= 8; | |
147 | i++; | |
148 | } | |
149 | } | |
150 | } |