Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
1da177e4 | 2 | /* |
4e8e56c6 | 3 | * Support for adapter interruptions |
1da177e4 | 4 | * |
a53c8fab | 5 | * Copyright IBM Corp. 1999, 2007 |
4e8e56c6 PO |
6 | * Author(s): Ingo Adlung <adlung@de.ibm.com> |
7 | * Cornelia Huck <cornelia.huck@de.ibm.com> | |
8 | * Arnd Bergmann <arndb@de.ibm.com> | |
9 | * Peter Oberparleiter <peter.oberparleiter@de.ibm.com> | |
1da177e4 LT |
10 | */ |
11 | ||
12 | #include <linux/init.h> | |
f4eae94f MS |
13 | #include <linux/irq.h> |
14 | #include <linux/kernel_stat.h> | |
1da177e4 | 15 | #include <linux/module.h> |
f4eae94f MS |
16 | #include <linux/mutex.h> |
17 | #include <linux/rculist.h> | |
1da177e4 | 18 | #include <linux/slab.h> |
b50623e5 | 19 | #include <linux/dmapool.h> |
1da177e4 | 20 | |
4e8e56c6 | 21 | #include <asm/airq.h> |
da7c5af8 | 22 | #include <asm/isc.h> |
b50623e5 | 23 | #include <asm/cio.h> |
4e8e56c6 PO |
24 | |
25 | #include "cio.h" | |
1da177e4 | 26 | #include "cio_debug.h" |
f4eae94f | 27 | #include "ioasm.h" |
1da177e4 | 28 | |
f4eae94f MS |
29 | static DEFINE_SPINLOCK(airq_lists_lock); |
30 | static struct hlist_head airq_lists[MAX_ISC+1]; | |
1da177e4 | 31 | |
b50623e5 | 32 | static struct dma_pool *airq_iv_cache; |
414cbd1e | 33 | |
4e8e56c6 | 34 | /** |
f4eae94f MS |
35 | * register_adapter_interrupt() - register adapter interrupt handler |
36 | * @airq: pointer to adapter interrupt descriptor | |
4e8e56c6 | 37 | * |
f4eae94f | 38 | * Returns 0 on success, or -EINVAL. |
4e8e56c6 | 39 | */ |
f4eae94f | 40 | int register_adapter_interrupt(struct airq_struct *airq) |
1da177e4 | 41 | { |
f4eae94f MS |
42 | char dbf_txt[32]; |
43 | ||
44 | if (!airq->handler || airq->isc > MAX_ISC) | |
45 | return -EINVAL; | |
46 | if (!airq->lsi_ptr) { | |
9d792ef1 | 47 | airq->lsi_ptr = cio_dma_zalloc(1); |
f4eae94f MS |
48 | if (!airq->lsi_ptr) |
49 | return -ENOMEM; | |
50 | airq->flags |= AIRQ_PTR_ALLOCATED; | |
1da177e4 | 51 | } |
f4eae94f | 52 | snprintf(dbf_txt, sizeof(dbf_txt), "rairq:%p", airq); |
4e8e56c6 | 53 | CIO_TRACE_EVENT(4, dbf_txt); |
f4eae94f MS |
54 | isc_register(airq->isc); |
55 | spin_lock(&airq_lists_lock); | |
56 | hlist_add_head_rcu(&airq->list, &airq_lists[airq->isc]); | |
57 | spin_unlock(&airq_lists_lock); | |
58 | return 0; | |
1da177e4 | 59 | } |
f4eae94f | 60 | EXPORT_SYMBOL(register_adapter_interrupt); |
1da177e4 | 61 | |
4e8e56c6 | 62 | /** |
f4eae94f MS |
63 | * unregister_adapter_interrupt - unregister adapter interrupt handler |
64 | * @airq: pointer to adapter interrupt descriptor | |
4e8e56c6 | 65 | */ |
f4eae94f | 66 | void unregister_adapter_interrupt(struct airq_struct *airq) |
1da177e4 | 67 | { |
f4eae94f | 68 | char dbf_txt[32]; |
1da177e4 | 69 | |
f4eae94f MS |
70 | if (hlist_unhashed(&airq->list)) |
71 | return; | |
72 | snprintf(dbf_txt, sizeof(dbf_txt), "urairq:%p", airq); | |
4e8e56c6 | 73 | CIO_TRACE_EVENT(4, dbf_txt); |
f4eae94f MS |
74 | spin_lock(&airq_lists_lock); |
75 | hlist_del_rcu(&airq->list); | |
76 | spin_unlock(&airq_lists_lock); | |
77 | synchronize_rcu(); | |
78 | isc_unregister(airq->isc); | |
79 | if (airq->flags & AIRQ_PTR_ALLOCATED) { | |
9d792ef1 | 80 | cio_dma_free(airq->lsi_ptr, 1); |
f4eae94f MS |
81 | airq->lsi_ptr = NULL; |
82 | airq->flags &= ~AIRQ_PTR_ALLOCATED; | |
83 | } | |
1da177e4 | 84 | } |
f4eae94f | 85 | EXPORT_SYMBOL(unregister_adapter_interrupt); |
1da177e4 | 86 | |
1f44a225 | 87 | static irqreturn_t do_airq_interrupt(int irq, void *dummy) |
4e8e56c6 | 88 | { |
1f44a225 | 89 | struct tpi_info *tpi_info; |
f4eae94f MS |
90 | struct airq_struct *airq; |
91 | struct hlist_head *head; | |
92 | ||
34bbeed0 | 93 | tpi_info = &get_irq_regs()->tpi_info; |
42248979 | 94 | trace_s390_cio_adapter_int(tpi_info); |
1f44a225 | 95 | head = &airq_lists[tpi_info->isc]; |
f4eae94f MS |
96 | rcu_read_lock(); |
97 | hlist_for_each_entry_rcu(airq, head, list) | |
acf00b5e | 98 | if (*airq->lsi_ptr != 0) |
d2197485 | 99 | airq->handler(airq, tpi_info); |
f4eae94f | 100 | rcu_read_unlock(); |
1f44a225 MS |
101 | |
102 | return IRQ_HANDLED; | |
103 | } | |
104 | ||
1f44a225 MS |
105 | void __init init_airq_interrupts(void) |
106 | { | |
107 | irq_set_chip_and_handler(THIN_INTERRUPT, | |
108 | &dummy_irq_chip, handle_percpu_irq); | |
8719b6d2 | 109 | if (request_irq(THIN_INTERRUPT, do_airq_interrupt, 0, "AIO", NULL)) |
110 | panic("Failed to register AIO interrupt\n"); | |
4e8e56c6 | 111 | } |
a9a6f034 | 112 | |
b50623e5 HP |
113 | static inline unsigned long iv_size(unsigned long bits) |
114 | { | |
115 | return BITS_TO_LONGS(bits) * sizeof(unsigned long); | |
116 | } | |
117 | ||
a9a6f034 MS |
118 | /** |
119 | * airq_iv_create - create an interrupt vector | |
120 | * @bits: number of bits in the interrupt vector | |
121 | * @flags: allocation flags | |
932b6467 | 122 | * @vec: pointer to pinned guest memory if AIRQ_IV_GUESTVEC |
a9a6f034 MS |
123 | * |
124 | * Returns a pointer to an interrupt vector structure | |
125 | */ | |
932b6467 MR |
126 | struct airq_iv *airq_iv_create(unsigned long bits, unsigned long flags, |
127 | unsigned long *vec) | |
a9a6f034 MS |
128 | { |
129 | struct airq_iv *iv; | |
130 | unsigned long size; | |
131 | ||
132 | iv = kzalloc(sizeof(*iv), GFP_KERNEL); | |
133 | if (!iv) | |
134 | goto out; | |
135 | iv->bits = bits; | |
414cbd1e | 136 | iv->flags = flags; |
b50623e5 | 137 | size = iv_size(bits); |
414cbd1e SO |
138 | |
139 | if (flags & AIRQ_IV_CACHELINE) { | |
b50623e5 HP |
140 | if ((cache_line_size() * BITS_PER_BYTE) < bits |
141 | || !airq_iv_cache) | |
414cbd1e SO |
142 | goto out_free; |
143 | ||
b50623e5 HP |
144 | iv->vector = dma_pool_zalloc(airq_iv_cache, GFP_KERNEL, |
145 | &iv->vector_dma); | |
414cbd1e SO |
146 | if (!iv->vector) |
147 | goto out_free; | |
932b6467 MR |
148 | } else if (flags & AIRQ_IV_GUESTVEC) { |
149 | iv->vector = vec; | |
414cbd1e | 150 | } else { |
b50623e5 | 151 | iv->vector = cio_dma_zalloc(size); |
414cbd1e SO |
152 | if (!iv->vector) |
153 | goto out_free; | |
154 | } | |
a9a6f034 MS |
155 | if (flags & AIRQ_IV_ALLOC) { |
156 | iv->avail = kmalloc(size, GFP_KERNEL); | |
157 | if (!iv->avail) | |
158 | goto out_free; | |
159 | memset(iv->avail, 0xff, size); | |
160 | iv->end = 0; | |
161 | } else | |
162 | iv->end = bits; | |
163 | if (flags & AIRQ_IV_BITLOCK) { | |
164 | iv->bitlock = kzalloc(size, GFP_KERNEL); | |
165 | if (!iv->bitlock) | |
166 | goto out_free; | |
167 | } | |
168 | if (flags & AIRQ_IV_PTR) { | |
169 | size = bits * sizeof(unsigned long); | |
170 | iv->ptr = kzalloc(size, GFP_KERNEL); | |
171 | if (!iv->ptr) | |
172 | goto out_free; | |
173 | } | |
174 | if (flags & AIRQ_IV_DATA) { | |
175 | size = bits * sizeof(unsigned int); | |
176 | iv->data = kzalloc(size, GFP_KERNEL); | |
177 | if (!iv->data) | |
178 | goto out_free; | |
179 | } | |
180 | spin_lock_init(&iv->lock); | |
181 | return iv; | |
182 | ||
183 | out_free: | |
184 | kfree(iv->ptr); | |
185 | kfree(iv->bitlock); | |
186 | kfree(iv->avail); | |
b50623e5 HP |
187 | if (iv->flags & AIRQ_IV_CACHELINE && iv->vector) |
188 | dma_pool_free(airq_iv_cache, iv->vector, iv->vector_dma); | |
932b6467 | 189 | else if (!(iv->flags & AIRQ_IV_GUESTVEC)) |
b50623e5 | 190 | cio_dma_free(iv->vector, size); |
a9a6f034 MS |
191 | kfree(iv); |
192 | out: | |
193 | return NULL; | |
194 | } | |
195 | EXPORT_SYMBOL(airq_iv_create); | |
196 | ||
197 | /** | |
198 | * airq_iv_release - release an interrupt vector | |
199 | * @iv: pointer to interrupt vector structure | |
200 | */ | |
201 | void airq_iv_release(struct airq_iv *iv) | |
202 | { | |
203 | kfree(iv->data); | |
204 | kfree(iv->ptr); | |
205 | kfree(iv->bitlock); | |
414cbd1e | 206 | if (iv->flags & AIRQ_IV_CACHELINE) |
b50623e5 | 207 | dma_pool_free(airq_iv_cache, iv->vector, iv->vector_dma); |
932b6467 | 208 | else if (!(iv->flags & AIRQ_IV_GUESTVEC)) |
b50623e5 | 209 | cio_dma_free(iv->vector, iv_size(iv->bits)); |
a9a6f034 MS |
210 | kfree(iv->avail); |
211 | kfree(iv); | |
212 | } | |
213 | EXPORT_SYMBOL(airq_iv_release); | |
214 | ||
215 | /** | |
fe7c30a4 | 216 | * airq_iv_alloc - allocate irq bits from an interrupt vector |
a9a6f034 | 217 | * @iv: pointer to an interrupt vector structure |
fe7c30a4 | 218 | * @num: number of consecutive irq bits to allocate |
a9a6f034 | 219 | * |
fe7c30a4 MS |
220 | * Returns the bit number of the first irq in the allocated block of irqs, |
221 | * or -1UL if no bit is available or the AIRQ_IV_ALLOC flag has not been | |
222 | * specified | |
a9a6f034 | 223 | */ |
fe7c30a4 | 224 | unsigned long airq_iv_alloc(struct airq_iv *iv, unsigned long num) |
a9a6f034 | 225 | { |
0eb69a0c | 226 | unsigned long bit, i, flags; |
a9a6f034 | 227 | |
fe7c30a4 | 228 | if (!iv->avail || num == 0) |
a9a6f034 | 229 | return -1UL; |
0eb69a0c | 230 | spin_lock_irqsave(&iv->lock, flags); |
7d7c7b24 | 231 | bit = find_first_bit_inv(iv->avail, iv->bits); |
fe7c30a4 MS |
232 | while (bit + num <= iv->bits) { |
233 | for (i = 1; i < num; i++) | |
234 | if (!test_bit_inv(bit + i, iv->avail)) | |
235 | break; | |
236 | if (i >= num) { | |
237 | /* Found a suitable block of irqs */ | |
238 | for (i = 0; i < num; i++) | |
239 | clear_bit_inv(bit + i, iv->avail); | |
240 | if (bit + num >= iv->end) | |
241 | iv->end = bit + num + 1; | |
242 | break; | |
243 | } | |
244 | bit = find_next_bit_inv(iv->avail, iv->bits, bit + i + 1); | |
245 | } | |
246 | if (bit + num > iv->bits) | |
a9a6f034 | 247 | bit = -1UL; |
0eb69a0c | 248 | spin_unlock_irqrestore(&iv->lock, flags); |
a9a6f034 | 249 | return bit; |
a9a6f034 | 250 | } |
fe7c30a4 | 251 | EXPORT_SYMBOL(airq_iv_alloc); |
a9a6f034 MS |
252 | |
253 | /** | |
fe7c30a4 | 254 | * airq_iv_free - free irq bits of an interrupt vector |
a9a6f034 | 255 | * @iv: pointer to interrupt vector structure |
fe7c30a4 MS |
256 | * @bit: number of the first irq bit to free |
257 | * @num: number of consecutive irq bits to free | |
a9a6f034 | 258 | */ |
fe7c30a4 | 259 | void airq_iv_free(struct airq_iv *iv, unsigned long bit, unsigned long num) |
a9a6f034 | 260 | { |
0eb69a0c | 261 | unsigned long i, flags; |
fe7c30a4 MS |
262 | |
263 | if (!iv->avail || num == 0) | |
a9a6f034 | 264 | return; |
0eb69a0c | 265 | spin_lock_irqsave(&iv->lock, flags); |
fe7c30a4 MS |
266 | for (i = 0; i < num; i++) { |
267 | /* Clear (possibly left over) interrupt bit */ | |
268 | clear_bit_inv(bit + i, iv->vector); | |
269 | /* Make the bit positions available again */ | |
270 | set_bit_inv(bit + i, iv->avail); | |
271 | } | |
272 | if (bit + num >= iv->end) { | |
a9a6f034 | 273 | /* Find new end of bit-field */ |
fe7c30a4 MS |
274 | while (iv->end > 0 && !test_bit_inv(iv->end - 1, iv->avail)) |
275 | iv->end--; | |
a9a6f034 | 276 | } |
0eb69a0c | 277 | spin_unlock_irqrestore(&iv->lock, flags); |
a9a6f034 | 278 | } |
fe7c30a4 | 279 | EXPORT_SYMBOL(airq_iv_free); |
a9a6f034 MS |
280 | |
281 | /** | |
282 | * airq_iv_scan - scan interrupt vector for non-zero bits | |
283 | * @iv: pointer to interrupt vector structure | |
284 | * @start: bit number to start the search | |
285 | * @end: bit number to end the search | |
286 | * | |
287 | * Returns the bit number of the next non-zero interrupt bit, or | |
288 | * -1UL if the scan completed without finding any more any non-zero bits. | |
289 | */ | |
290 | unsigned long airq_iv_scan(struct airq_iv *iv, unsigned long start, | |
291 | unsigned long end) | |
292 | { | |
a9a6f034 MS |
293 | unsigned long bit; |
294 | ||
295 | /* Find non-zero bit starting from 'ivs->next'. */ | |
7d7c7b24 | 296 | bit = find_next_bit_inv(iv->vector, end, start); |
a9a6f034 MS |
297 | if (bit >= end) |
298 | return -1UL; | |
7d7c7b24 | 299 | clear_bit_inv(bit, iv->vector); |
a9a6f034 MS |
300 | return bit; |
301 | } | |
302 | EXPORT_SYMBOL(airq_iv_scan); | |
414cbd1e | 303 | |
b50623e5 | 304 | int __init airq_init(void) |
414cbd1e | 305 | { |
b50623e5 HP |
306 | airq_iv_cache = dma_pool_create("airq_iv_cache", cio_get_dma_css_dev(), |
307 | cache_line_size(), | |
308 | cache_line_size(), PAGE_SIZE); | |
414cbd1e SO |
309 | if (!airq_iv_cache) |
310 | return -ENOMEM; | |
311 | return 0; | |
312 | } |