]>
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> |
1da177e4 | 19 | |
4e8e56c6 | 20 | #include <asm/airq.h> |
da7c5af8 | 21 | #include <asm/isc.h> |
4e8e56c6 PO |
22 | |
23 | #include "cio.h" | |
1da177e4 | 24 | #include "cio_debug.h" |
f4eae94f | 25 | #include "ioasm.h" |
1da177e4 | 26 | |
f4eae94f MS |
27 | static DEFINE_SPINLOCK(airq_lists_lock); |
28 | static struct hlist_head airq_lists[MAX_ISC+1]; | |
1da177e4 | 29 | |
4e8e56c6 | 30 | /** |
f4eae94f MS |
31 | * register_adapter_interrupt() - register adapter interrupt handler |
32 | * @airq: pointer to adapter interrupt descriptor | |
4e8e56c6 | 33 | * |
f4eae94f | 34 | * Returns 0 on success, or -EINVAL. |
4e8e56c6 | 35 | */ |
f4eae94f | 36 | int register_adapter_interrupt(struct airq_struct *airq) |
1da177e4 | 37 | { |
f4eae94f MS |
38 | char dbf_txt[32]; |
39 | ||
40 | if (!airq->handler || airq->isc > MAX_ISC) | |
41 | return -EINVAL; | |
42 | if (!airq->lsi_ptr) { | |
43 | airq->lsi_ptr = kzalloc(1, GFP_KERNEL); | |
44 | if (!airq->lsi_ptr) | |
45 | return -ENOMEM; | |
46 | airq->flags |= AIRQ_PTR_ALLOCATED; | |
1da177e4 | 47 | } |
f4eae94f MS |
48 | if (!airq->lsi_mask) |
49 | airq->lsi_mask = 0xff; | |
50 | snprintf(dbf_txt, sizeof(dbf_txt), "rairq:%p", airq); | |
4e8e56c6 | 51 | CIO_TRACE_EVENT(4, dbf_txt); |
f4eae94f MS |
52 | isc_register(airq->isc); |
53 | spin_lock(&airq_lists_lock); | |
54 | hlist_add_head_rcu(&airq->list, &airq_lists[airq->isc]); | |
55 | spin_unlock(&airq_lists_lock); | |
56 | return 0; | |
1da177e4 | 57 | } |
f4eae94f | 58 | EXPORT_SYMBOL(register_adapter_interrupt); |
1da177e4 | 59 | |
4e8e56c6 | 60 | /** |
f4eae94f MS |
61 | * unregister_adapter_interrupt - unregister adapter interrupt handler |
62 | * @airq: pointer to adapter interrupt descriptor | |
4e8e56c6 | 63 | */ |
f4eae94f | 64 | void unregister_adapter_interrupt(struct airq_struct *airq) |
1da177e4 | 65 | { |
f4eae94f | 66 | char dbf_txt[32]; |
1da177e4 | 67 | |
f4eae94f MS |
68 | if (hlist_unhashed(&airq->list)) |
69 | return; | |
70 | snprintf(dbf_txt, sizeof(dbf_txt), "urairq:%p", airq); | |
4e8e56c6 | 71 | CIO_TRACE_EVENT(4, dbf_txt); |
f4eae94f MS |
72 | spin_lock(&airq_lists_lock); |
73 | hlist_del_rcu(&airq->list); | |
74 | spin_unlock(&airq_lists_lock); | |
75 | synchronize_rcu(); | |
76 | isc_unregister(airq->isc); | |
77 | if (airq->flags & AIRQ_PTR_ALLOCATED) { | |
78 | kfree(airq->lsi_ptr); | |
79 | airq->lsi_ptr = NULL; | |
80 | airq->flags &= ~AIRQ_PTR_ALLOCATED; | |
81 | } | |
1da177e4 | 82 | } |
f4eae94f | 83 | EXPORT_SYMBOL(unregister_adapter_interrupt); |
1da177e4 | 84 | |
1f44a225 | 85 | static irqreturn_t do_airq_interrupt(int irq, void *dummy) |
4e8e56c6 | 86 | { |
1f44a225 | 87 | struct tpi_info *tpi_info; |
f4eae94f MS |
88 | struct airq_struct *airq; |
89 | struct hlist_head *head; | |
90 | ||
fe0f4976 | 91 | set_cpu_flag(CIF_NOHZ_DELAY); |
1f44a225 | 92 | tpi_info = (struct tpi_info *) &get_irq_regs()->int_code; |
42248979 | 93 | trace_s390_cio_adapter_int(tpi_info); |
1f44a225 | 94 | head = &airq_lists[tpi_info->isc]; |
f4eae94f MS |
95 | rcu_read_lock(); |
96 | hlist_for_each_entry_rcu(airq, head, list) | |
97 | if ((*airq->lsi_ptr & airq->lsi_mask) != 0) | |
98 | airq->handler(airq); | |
99 | rcu_read_unlock(); | |
1f44a225 MS |
100 | |
101 | return IRQ_HANDLED; | |
102 | } | |
103 | ||
104 | static struct irqaction airq_interrupt = { | |
105 | .name = "AIO", | |
106 | .handler = do_airq_interrupt, | |
107 | }; | |
108 | ||
109 | void __init init_airq_interrupts(void) | |
110 | { | |
111 | irq_set_chip_and_handler(THIN_INTERRUPT, | |
112 | &dummy_irq_chip, handle_percpu_irq); | |
113 | setup_irq(THIN_INTERRUPT, &airq_interrupt); | |
4e8e56c6 | 114 | } |
a9a6f034 MS |
115 | |
116 | /** | |
117 | * airq_iv_create - create an interrupt vector | |
118 | * @bits: number of bits in the interrupt vector | |
119 | * @flags: allocation flags | |
120 | * | |
121 | * Returns a pointer to an interrupt vector structure | |
122 | */ | |
123 | struct airq_iv *airq_iv_create(unsigned long bits, unsigned long flags) | |
124 | { | |
125 | struct airq_iv *iv; | |
126 | unsigned long size; | |
127 | ||
128 | iv = kzalloc(sizeof(*iv), GFP_KERNEL); | |
129 | if (!iv) | |
130 | goto out; | |
131 | iv->bits = bits; | |
132 | size = BITS_TO_LONGS(bits) * sizeof(unsigned long); | |
133 | iv->vector = kzalloc(size, GFP_KERNEL); | |
134 | if (!iv->vector) | |
135 | goto out_free; | |
136 | if (flags & AIRQ_IV_ALLOC) { | |
137 | iv->avail = kmalloc(size, GFP_KERNEL); | |
138 | if (!iv->avail) | |
139 | goto out_free; | |
140 | memset(iv->avail, 0xff, size); | |
141 | iv->end = 0; | |
142 | } else | |
143 | iv->end = bits; | |
144 | if (flags & AIRQ_IV_BITLOCK) { | |
145 | iv->bitlock = kzalloc(size, GFP_KERNEL); | |
146 | if (!iv->bitlock) | |
147 | goto out_free; | |
148 | } | |
149 | if (flags & AIRQ_IV_PTR) { | |
150 | size = bits * sizeof(unsigned long); | |
151 | iv->ptr = kzalloc(size, GFP_KERNEL); | |
152 | if (!iv->ptr) | |
153 | goto out_free; | |
154 | } | |
155 | if (flags & AIRQ_IV_DATA) { | |
156 | size = bits * sizeof(unsigned int); | |
157 | iv->data = kzalloc(size, GFP_KERNEL); | |
158 | if (!iv->data) | |
159 | goto out_free; | |
160 | } | |
161 | spin_lock_init(&iv->lock); | |
162 | return iv; | |
163 | ||
164 | out_free: | |
165 | kfree(iv->ptr); | |
166 | kfree(iv->bitlock); | |
167 | kfree(iv->avail); | |
168 | kfree(iv->vector); | |
169 | kfree(iv); | |
170 | out: | |
171 | return NULL; | |
172 | } | |
173 | EXPORT_SYMBOL(airq_iv_create); | |
174 | ||
175 | /** | |
176 | * airq_iv_release - release an interrupt vector | |
177 | * @iv: pointer to interrupt vector structure | |
178 | */ | |
179 | void airq_iv_release(struct airq_iv *iv) | |
180 | { | |
181 | kfree(iv->data); | |
182 | kfree(iv->ptr); | |
183 | kfree(iv->bitlock); | |
184 | kfree(iv->vector); | |
185 | kfree(iv->avail); | |
186 | kfree(iv); | |
187 | } | |
188 | EXPORT_SYMBOL(airq_iv_release); | |
189 | ||
190 | /** | |
fe7c30a4 | 191 | * airq_iv_alloc - allocate irq bits from an interrupt vector |
a9a6f034 | 192 | * @iv: pointer to an interrupt vector structure |
fe7c30a4 | 193 | * @num: number of consecutive irq bits to allocate |
a9a6f034 | 194 | * |
fe7c30a4 MS |
195 | * Returns the bit number of the first irq in the allocated block of irqs, |
196 | * or -1UL if no bit is available or the AIRQ_IV_ALLOC flag has not been | |
197 | * specified | |
a9a6f034 | 198 | */ |
fe7c30a4 | 199 | unsigned long airq_iv_alloc(struct airq_iv *iv, unsigned long num) |
a9a6f034 | 200 | { |
0eb69a0c | 201 | unsigned long bit, i, flags; |
a9a6f034 | 202 | |
fe7c30a4 | 203 | if (!iv->avail || num == 0) |
a9a6f034 | 204 | return -1UL; |
0eb69a0c | 205 | spin_lock_irqsave(&iv->lock, flags); |
7d7c7b24 | 206 | bit = find_first_bit_inv(iv->avail, iv->bits); |
fe7c30a4 MS |
207 | while (bit + num <= iv->bits) { |
208 | for (i = 1; i < num; i++) | |
209 | if (!test_bit_inv(bit + i, iv->avail)) | |
210 | break; | |
211 | if (i >= num) { | |
212 | /* Found a suitable block of irqs */ | |
213 | for (i = 0; i < num; i++) | |
214 | clear_bit_inv(bit + i, iv->avail); | |
215 | if (bit + num >= iv->end) | |
216 | iv->end = bit + num + 1; | |
217 | break; | |
218 | } | |
219 | bit = find_next_bit_inv(iv->avail, iv->bits, bit + i + 1); | |
220 | } | |
221 | if (bit + num > iv->bits) | |
a9a6f034 | 222 | bit = -1UL; |
0eb69a0c | 223 | spin_unlock_irqrestore(&iv->lock, flags); |
a9a6f034 | 224 | return bit; |
a9a6f034 | 225 | } |
fe7c30a4 | 226 | EXPORT_SYMBOL(airq_iv_alloc); |
a9a6f034 MS |
227 | |
228 | /** | |
fe7c30a4 | 229 | * airq_iv_free - free irq bits of an interrupt vector |
a9a6f034 | 230 | * @iv: pointer to interrupt vector structure |
fe7c30a4 MS |
231 | * @bit: number of the first irq bit to free |
232 | * @num: number of consecutive irq bits to free | |
a9a6f034 | 233 | */ |
fe7c30a4 | 234 | void airq_iv_free(struct airq_iv *iv, unsigned long bit, unsigned long num) |
a9a6f034 | 235 | { |
0eb69a0c | 236 | unsigned long i, flags; |
fe7c30a4 MS |
237 | |
238 | if (!iv->avail || num == 0) | |
a9a6f034 | 239 | return; |
0eb69a0c | 240 | spin_lock_irqsave(&iv->lock, flags); |
fe7c30a4 MS |
241 | for (i = 0; i < num; i++) { |
242 | /* Clear (possibly left over) interrupt bit */ | |
243 | clear_bit_inv(bit + i, iv->vector); | |
244 | /* Make the bit positions available again */ | |
245 | set_bit_inv(bit + i, iv->avail); | |
246 | } | |
247 | if (bit + num >= iv->end) { | |
a9a6f034 | 248 | /* Find new end of bit-field */ |
fe7c30a4 MS |
249 | while (iv->end > 0 && !test_bit_inv(iv->end - 1, iv->avail)) |
250 | iv->end--; | |
a9a6f034 | 251 | } |
0eb69a0c | 252 | spin_unlock_irqrestore(&iv->lock, flags); |
a9a6f034 | 253 | } |
fe7c30a4 | 254 | EXPORT_SYMBOL(airq_iv_free); |
a9a6f034 MS |
255 | |
256 | /** | |
257 | * airq_iv_scan - scan interrupt vector for non-zero bits | |
258 | * @iv: pointer to interrupt vector structure | |
259 | * @start: bit number to start the search | |
260 | * @end: bit number to end the search | |
261 | * | |
262 | * Returns the bit number of the next non-zero interrupt bit, or | |
263 | * -1UL if the scan completed without finding any more any non-zero bits. | |
264 | */ | |
265 | unsigned long airq_iv_scan(struct airq_iv *iv, unsigned long start, | |
266 | unsigned long end) | |
267 | { | |
a9a6f034 MS |
268 | unsigned long bit; |
269 | ||
270 | /* Find non-zero bit starting from 'ivs->next'. */ | |
7d7c7b24 | 271 | bit = find_next_bit_inv(iv->vector, end, start); |
a9a6f034 MS |
272 | if (bit >= end) |
273 | return -1UL; | |
7d7c7b24 | 274 | clear_bit_inv(bit, iv->vector); |
a9a6f034 MS |
275 | return bit; |
276 | } | |
277 | EXPORT_SYMBOL(airq_iv_scan); |