diff options
author | Jan Glauber <jang@linux.vnet.ibm.com> | 2011-07-24 04:48:27 -0400 |
---|---|---|
committer | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2011-07-24 04:48:22 -0400 |
commit | 89c9b66b104549a8698e412bf6f4140c1d0786fb (patch) | |
tree | d7c62f412b1e99b2ebb5d37e8f86539b7dc03d9d /arch/s390 | |
parent | 9e280f6693083baf1b7741c2b820be8d4109509e (diff) |
[S390] race safe external interrupt registration
The (un-)register_external_interrupt functions are not race safe if
more than one interrupt handler is added or deleted for an external
interrupt concurrently.
Make the registration / unregistration of external interrupts race safe
by using RCU and a spinlock. RCU is used to avoid a performance penalty
in the external interrupt handler, the register and unregister functions
are protected by the spinlock and are not performance critical.
call_rcu must be used since the SCLP driver uses the interface with
IRQs disabled. Also use the generic list implementation rather than
homebrewn list code.
Signed-off-by: Jan Glauber <jang@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'arch/s390')
-rw-r--r-- | arch/s390/kernel/irq.c | 83 |
1 files changed, 46 insertions, 37 deletions
diff --git a/arch/s390/kernel/irq.c b/arch/s390/kernel/irq.c index e3264f6a9720..1f4050d45f78 100644 --- a/arch/s390/kernel/irq.c +++ b/arch/s390/kernel/irq.c | |||
@@ -88,15 +88,6 @@ int show_interrupts(struct seq_file *p, void *v) | |||
88 | } | 88 | } |
89 | 89 | ||
90 | /* | 90 | /* |
91 | * For compatibilty only. S/390 specific setup of interrupts et al. is done | ||
92 | * much later in init_channel_subsystem(). | ||
93 | */ | ||
94 | void __init init_IRQ(void) | ||
95 | { | ||
96 | /* nothing... */ | ||
97 | } | ||
98 | |||
99 | /* | ||
100 | * Switch to the asynchronous interrupt stack for softirq execution. | 91 | * Switch to the asynchronous interrupt stack for softirq execution. |
101 | */ | 92 | */ |
102 | asmlinkage void do_softirq(void) | 93 | asmlinkage void do_softirq(void) |
@@ -144,28 +135,45 @@ void init_irq_proc(void) | |||
144 | #endif | 135 | #endif |
145 | 136 | ||
146 | /* | 137 | /* |
147 | * ext_int_hash[index] is the start of the list for all external interrupts | 138 | * ext_int_hash[index] is the list head for all external interrupts that hash |
148 | * that hash to this index. With the current set of external interrupts | 139 | * to this index. |
149 | * (0x1202 external call, 0x1004 cpu timer, 0x2401 hwc console, 0x4000 | ||
150 | * iucv and 0x2603 pfault) this is always the first element. | ||
151 | */ | 140 | */ |
141 | static struct list_head ext_int_hash[256]; | ||
152 | 142 | ||
153 | struct ext_int_info { | 143 | struct ext_int_info { |
154 | struct ext_int_info *next; | ||
155 | ext_int_handler_t handler; | 144 | ext_int_handler_t handler; |
156 | u16 code; | 145 | u16 code; |
146 | struct list_head entry; | ||
147 | struct rcu_head rcu; | ||
157 | }; | 148 | }; |
158 | 149 | ||
159 | static struct ext_int_info *ext_int_hash[256]; | 150 | /* ext_int_hash_lock protects the handler lists for external interrupts */ |
151 | DEFINE_SPINLOCK(ext_int_hash_lock); | ||
152 | |||
153 | static void __init init_external_interrupts(void) | ||
154 | { | ||
155 | int idx; | ||
156 | |||
157 | for (idx = 0; idx < ARRAY_SIZE(ext_int_hash); idx++) | ||
158 | INIT_LIST_HEAD(&ext_int_hash[idx]); | ||
159 | } | ||
160 | 160 | ||
161 | static inline int ext_hash(u16 code) | 161 | static inline int ext_hash(u16 code) |
162 | { | 162 | { |
163 | return (code + (code >> 9)) & 0xff; | 163 | return (code + (code >> 9)) & 0xff; |
164 | } | 164 | } |
165 | 165 | ||
166 | static void ext_int_hash_update(struct rcu_head *head) | ||
167 | { | ||
168 | struct ext_int_info *p = container_of(head, struct ext_int_info, rcu); | ||
169 | |||
170 | kfree(p); | ||
171 | } | ||
172 | |||
166 | int register_external_interrupt(u16 code, ext_int_handler_t handler) | 173 | int register_external_interrupt(u16 code, ext_int_handler_t handler) |
167 | { | 174 | { |
168 | struct ext_int_info *p; | 175 | struct ext_int_info *p; |
176 | unsigned long flags; | ||
169 | int index; | 177 | int index; |
170 | 178 | ||
171 | p = kmalloc(sizeof(*p), GFP_ATOMIC); | 179 | p = kmalloc(sizeof(*p), GFP_ATOMIC); |
@@ -174,33 +182,27 @@ int register_external_interrupt(u16 code, ext_int_handler_t handler) | |||
174 | p->code = code; | 182 | p->code = code; |
175 | p->handler = handler; | 183 | p->handler = handler; |
176 | index = ext_hash(code); | 184 | index = ext_hash(code); |
177 | p->next = ext_int_hash[index]; | 185 | |
178 | ext_int_hash[index] = p; | 186 | spin_lock_irqsave(&ext_int_hash_lock, flags); |
187 | list_add_rcu(&p->entry, &ext_int_hash[index]); | ||
188 | spin_unlock_irqrestore(&ext_int_hash_lock, flags); | ||
179 | return 0; | 189 | return 0; |
180 | } | 190 | } |
181 | EXPORT_SYMBOL(register_external_interrupt); | 191 | EXPORT_SYMBOL(register_external_interrupt); |
182 | 192 | ||
183 | int unregister_external_interrupt(u16 code, ext_int_handler_t handler) | 193 | int unregister_external_interrupt(u16 code, ext_int_handler_t handler) |
184 | { | 194 | { |
185 | struct ext_int_info *p, *q; | 195 | struct ext_int_info *p; |
186 | int index; | 196 | unsigned long flags; |
197 | int index = ext_hash(code); | ||
187 | 198 | ||
188 | index = ext_hash(code); | 199 | spin_lock_irqsave(&ext_int_hash_lock, flags); |
189 | q = NULL; | 200 | list_for_each_entry_rcu(p, &ext_int_hash[index], entry) |
190 | p = ext_int_hash[index]; | 201 | if (p->code == code && p->handler == handler) { |
191 | while (p) { | 202 | list_del_rcu(&p->entry); |
192 | if (p->code == code && p->handler == handler) | 203 | call_rcu(&p->rcu, ext_int_hash_update); |
193 | break; | 204 | } |
194 | q = p; | 205 | spin_unlock_irqrestore(&ext_int_hash_lock, flags); |
195 | p = p->next; | ||
196 | } | ||
197 | if (!p) | ||
198 | return -ENOENT; | ||
199 | if (q) | ||
200 | q->next = p->next; | ||
201 | else | ||
202 | ext_int_hash[index] = p->next; | ||
203 | kfree(p); | ||
204 | return 0; | 206 | return 0; |
205 | } | 207 | } |
206 | EXPORT_SYMBOL(unregister_external_interrupt); | 208 | EXPORT_SYMBOL(unregister_external_interrupt); |
@@ -224,15 +226,22 @@ void __irq_entry do_extint(struct pt_regs *regs, unsigned int ext_int_code, | |||
224 | kstat_cpu(smp_processor_id()).irqs[EXTERNAL_INTERRUPT]++; | 226 | kstat_cpu(smp_processor_id()).irqs[EXTERNAL_INTERRUPT]++; |
225 | if (code != 0x1004) | 227 | if (code != 0x1004) |
226 | __get_cpu_var(s390_idle).nohz_delay = 1; | 228 | __get_cpu_var(s390_idle).nohz_delay = 1; |
229 | |||
227 | index = ext_hash(code); | 230 | index = ext_hash(code); |
228 | for (p = ext_int_hash[index]; p; p = p->next) { | 231 | rcu_read_lock(); |
232 | list_for_each_entry_rcu(p, &ext_int_hash[index], entry) | ||
229 | if (likely(p->code == code)) | 233 | if (likely(p->code == code)) |
230 | p->handler(ext_int_code, param32, param64); | 234 | p->handler(ext_int_code, param32, param64); |
231 | } | 235 | rcu_read_unlock(); |
232 | irq_exit(); | 236 | irq_exit(); |
233 | set_irq_regs(old_regs); | 237 | set_irq_regs(old_regs); |
234 | } | 238 | } |
235 | 239 | ||
240 | void __init init_IRQ(void) | ||
241 | { | ||
242 | init_external_interrupts(); | ||
243 | } | ||
244 | |||
236 | static DEFINE_SPINLOCK(sc_irq_lock); | 245 | static DEFINE_SPINLOCK(sc_irq_lock); |
237 | static int sc_irq_refcount; | 246 | static int sc_irq_refcount; |
238 | 247 | ||