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