diff options
-rw-r--r-- | drivers/xen/events/Makefile | 1 | ||||
-rw-r--r-- | drivers/xen/events/events_base.c | 14 | ||||
-rw-r--r-- | drivers/xen/events/events_fifo.c | 426 | ||||
-rw-r--r-- | drivers/xen/events/events_internal.h | 8 |
4 files changed, 448 insertions, 1 deletions
diff --git a/drivers/xen/events/Makefile b/drivers/xen/events/Makefile index 08179fe04612..62be55cd981d 100644 --- a/drivers/xen/events/Makefile +++ b/drivers/xen/events/Makefile | |||
@@ -2,3 +2,4 @@ obj-y += events.o | |||
2 | 2 | ||
3 | events-y += events_base.o | 3 | events-y += events_base.o |
4 | events-y += events_2l.o | 4 | events-y += events_2l.o |
5 | events-y += events_fifo.o | ||
diff --git a/drivers/xen/events/events_base.c b/drivers/xen/events/events_base.c index e9001fef4ffd..1d16185e82b2 100644 --- a/drivers/xen/events/events_base.c +++ b/drivers/xen/events/events_base.c | |||
@@ -1563,6 +1563,7 @@ void xen_irq_resume(void) | |||
1563 | 1563 | ||
1564 | /* New event-channel space is not 'live' yet. */ | 1564 | /* New event-channel space is not 'live' yet. */ |
1565 | xen_evtchn_mask_all(); | 1565 | xen_evtchn_mask_all(); |
1566 | xen_evtchn_resume(); | ||
1566 | 1567 | ||
1567 | /* No IRQ <-> event-channel mappings. */ | 1568 | /* No IRQ <-> event-channel mappings. */ |
1568 | list_for_each_entry(info, &xen_irq_list_head, list) | 1569 | list_for_each_entry(info, &xen_irq_list_head, list) |
@@ -1659,9 +1660,20 @@ void xen_callback_vector(void) | |||
1659 | void xen_callback_vector(void) {} | 1660 | void xen_callback_vector(void) {} |
1660 | #endif | 1661 | #endif |
1661 | 1662 | ||
1663 | #undef MODULE_PARAM_PREFIX | ||
1664 | #define MODULE_PARAM_PREFIX "xen." | ||
1665 | |||
1666 | static bool fifo_events = true; | ||
1667 | module_param(fifo_events, bool, 0); | ||
1668 | |||
1662 | void __init xen_init_IRQ(void) | 1669 | void __init xen_init_IRQ(void) |
1663 | { | 1670 | { |
1664 | xen_evtchn_2l_init(); | 1671 | int ret = -EINVAL; |
1672 | |||
1673 | if (fifo_events) | ||
1674 | ret = xen_evtchn_fifo_init(); | ||
1675 | if (ret < 0) | ||
1676 | xen_evtchn_2l_init(); | ||
1665 | 1677 | ||
1666 | evtchn_to_irq = kcalloc(EVTCHN_ROW(xen_evtchn_max_channels()), | 1678 | evtchn_to_irq = kcalloc(EVTCHN_ROW(xen_evtchn_max_channels()), |
1667 | sizeof(*evtchn_to_irq), GFP_KERNEL); | 1679 | sizeof(*evtchn_to_irq), GFP_KERNEL); |
diff --git a/drivers/xen/events/events_fifo.c b/drivers/xen/events/events_fifo.c new file mode 100644 index 000000000000..e2bf9571f7fe --- /dev/null +++ b/drivers/xen/events/events_fifo.c | |||
@@ -0,0 +1,426 @@ | |||
1 | /* | ||
2 | * Xen event channels (FIFO-based ABI) | ||
3 | * | ||
4 | * Copyright (C) 2013 Citrix Systems R&D ltd. | ||
5 | * | ||
6 | * This source code is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License as | ||
8 | * published by the Free Software Foundation; either version 2 of the | ||
9 | * License, or (at your option) any later version. | ||
10 | * | ||
11 | * Or, when distributed separately from the Linux kernel or | ||
12 | * incorporated into other software packages, subject to the following | ||
13 | * license: | ||
14 | * | ||
15 | * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
16 | * of this source file (the "Software"), to deal in the Software without | ||
17 | * restriction, including without limitation the rights to use, copy, modify, | ||
18 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, | ||
19 | * and to permit persons to whom the Software is furnished to do so, subject to | ||
20 | * the following conditions: | ||
21 | * | ||
22 | * The above copyright notice and this permission notice shall be included in | ||
23 | * all copies or substantial portions of the Software. | ||
24 | * | ||
25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
26 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
27 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
28 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
29 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
30 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | ||
31 | * IN THE SOFTWARE. | ||
32 | */ | ||
33 | |||
34 | #define pr_fmt(fmt) "xen:" KBUILD_MODNAME ": " fmt | ||
35 | |||
36 | #include <linux/linkage.h> | ||
37 | #include <linux/interrupt.h> | ||
38 | #include <linux/irq.h> | ||
39 | #include <linux/module.h> | ||
40 | #include <linux/smp.h> | ||
41 | #include <linux/percpu.h> | ||
42 | #include <linux/cpu.h> | ||
43 | |||
44 | #include <asm/sync_bitops.h> | ||
45 | #include <asm/xen/hypercall.h> | ||
46 | #include <asm/xen/hypervisor.h> | ||
47 | #include <asm/xen/page.h> | ||
48 | |||
49 | #include <xen/xen.h> | ||
50 | #include <xen/xen-ops.h> | ||
51 | #include <xen/events.h> | ||
52 | #include <xen/interface/xen.h> | ||
53 | #include <xen/interface/event_channel.h> | ||
54 | |||
55 | #include "events_internal.h" | ||
56 | |||
57 | #define EVENT_WORDS_PER_PAGE (PAGE_SIZE / sizeof(event_word_t)) | ||
58 | #define MAX_EVENT_ARRAY_PAGES (EVTCHN_FIFO_NR_CHANNELS / EVENT_WORDS_PER_PAGE) | ||
59 | |||
60 | struct evtchn_fifo_queue { | ||
61 | uint32_t head[EVTCHN_FIFO_MAX_QUEUES]; | ||
62 | }; | ||
63 | |||
64 | static DEFINE_PER_CPU(struct evtchn_fifo_control_block *, cpu_control_block); | ||
65 | static DEFINE_PER_CPU(struct evtchn_fifo_queue, cpu_queue); | ||
66 | static event_word_t *event_array[MAX_EVENT_ARRAY_PAGES] __read_mostly; | ||
67 | static unsigned event_array_pages __read_mostly; | ||
68 | |||
69 | #define BM(w) ((unsigned long *)(w)) | ||
70 | |||
71 | static inline event_word_t *event_word_from_port(unsigned port) | ||
72 | { | ||
73 | unsigned i = port / EVENT_WORDS_PER_PAGE; | ||
74 | |||
75 | return event_array[i] + port % EVENT_WORDS_PER_PAGE; | ||
76 | } | ||
77 | |||
78 | static unsigned evtchn_fifo_max_channels(void) | ||
79 | { | ||
80 | return EVTCHN_FIFO_NR_CHANNELS; | ||
81 | } | ||
82 | |||
83 | static unsigned evtchn_fifo_nr_channels(void) | ||
84 | { | ||
85 | return event_array_pages * EVENT_WORDS_PER_PAGE; | ||
86 | } | ||
87 | |||
88 | static void free_unused_array_pages(void) | ||
89 | { | ||
90 | unsigned i; | ||
91 | |||
92 | for (i = event_array_pages; i < MAX_EVENT_ARRAY_PAGES; i++) { | ||
93 | if (!event_array[i]) | ||
94 | break; | ||
95 | free_page((unsigned long)event_array[i]); | ||
96 | event_array[i] = NULL; | ||
97 | } | ||
98 | } | ||
99 | |||
100 | static void init_array_page(event_word_t *array_page) | ||
101 | { | ||
102 | unsigned i; | ||
103 | |||
104 | for (i = 0; i < EVENT_WORDS_PER_PAGE; i++) | ||
105 | array_page[i] = 1 << EVTCHN_FIFO_MASKED; | ||
106 | } | ||
107 | |||
108 | static int evtchn_fifo_setup(struct irq_info *info) | ||
109 | { | ||
110 | unsigned port = info->evtchn; | ||
111 | unsigned new_array_pages; | ||
112 | int ret = -ENOMEM; | ||
113 | |||
114 | new_array_pages = port / EVENT_WORDS_PER_PAGE + 1; | ||
115 | |||
116 | if (new_array_pages > MAX_EVENT_ARRAY_PAGES) | ||
117 | return -EINVAL; | ||
118 | |||
119 | while (event_array_pages < new_array_pages) { | ||
120 | void *array_page; | ||
121 | struct evtchn_expand_array expand_array; | ||
122 | |||
123 | /* Might already have a page if we've resumed. */ | ||
124 | array_page = event_array[event_array_pages]; | ||
125 | if (!array_page) { | ||
126 | array_page = (void *)__get_free_page(GFP_KERNEL); | ||
127 | if (array_page == NULL) | ||
128 | goto error; | ||
129 | event_array[event_array_pages] = array_page; | ||
130 | } | ||
131 | |||
132 | /* Mask all events in this page before adding it. */ | ||
133 | init_array_page(array_page); | ||
134 | |||
135 | expand_array.array_gfn = virt_to_mfn(array_page); | ||
136 | |||
137 | ret = HYPERVISOR_event_channel_op(EVTCHNOP_expand_array, &expand_array); | ||
138 | if (ret < 0) | ||
139 | goto error; | ||
140 | |||
141 | event_array_pages++; | ||
142 | } | ||
143 | return 0; | ||
144 | |||
145 | error: | ||
146 | if (event_array_pages == 0) | ||
147 | panic("xen: unable to expand event array with initial page (%d)\n", ret); | ||
148 | else | ||
149 | pr_err("unable to expand event array (%d)\n", ret); | ||
150 | free_unused_array_pages(); | ||
151 | return ret; | ||
152 | } | ||
153 | |||
154 | static void evtchn_fifo_bind_to_cpu(struct irq_info *info, unsigned cpu) | ||
155 | { | ||
156 | /* no-op */ | ||
157 | } | ||
158 | |||
159 | static void evtchn_fifo_clear_pending(unsigned port) | ||
160 | { | ||
161 | event_word_t *word = event_word_from_port(port); | ||
162 | sync_clear_bit(EVTCHN_FIFO_PENDING, BM(word)); | ||
163 | } | ||
164 | |||
165 | static void evtchn_fifo_set_pending(unsigned port) | ||
166 | { | ||
167 | event_word_t *word = event_word_from_port(port); | ||
168 | sync_set_bit(EVTCHN_FIFO_PENDING, BM(word)); | ||
169 | } | ||
170 | |||
171 | static bool evtchn_fifo_is_pending(unsigned port) | ||
172 | { | ||
173 | event_word_t *word = event_word_from_port(port); | ||
174 | return sync_test_bit(EVTCHN_FIFO_PENDING, BM(word)); | ||
175 | } | ||
176 | |||
177 | static bool evtchn_fifo_test_and_set_mask(unsigned port) | ||
178 | { | ||
179 | event_word_t *word = event_word_from_port(port); | ||
180 | return sync_test_and_set_bit(EVTCHN_FIFO_MASKED, BM(word)); | ||
181 | } | ||
182 | |||
183 | static void evtchn_fifo_mask(unsigned port) | ||
184 | { | ||
185 | event_word_t *word = event_word_from_port(port); | ||
186 | sync_set_bit(EVTCHN_FIFO_MASKED, BM(word)); | ||
187 | } | ||
188 | |||
189 | /* | ||
190 | * Clear MASKED, spinning if BUSY is set. | ||
191 | */ | ||
192 | static void clear_masked(volatile event_word_t *word) | ||
193 | { | ||
194 | event_word_t new, old, w; | ||
195 | |||
196 | w = *word; | ||
197 | |||
198 | do { | ||
199 | old = w & ~(1 << EVTCHN_FIFO_BUSY); | ||
200 | new = old & ~(1 << EVTCHN_FIFO_MASKED); | ||
201 | w = sync_cmpxchg(word, old, new); | ||
202 | } while (w != old); | ||
203 | } | ||
204 | |||
205 | static void evtchn_fifo_unmask(unsigned port) | ||
206 | { | ||
207 | event_word_t *word = event_word_from_port(port); | ||
208 | |||
209 | BUG_ON(!irqs_disabled()); | ||
210 | |||
211 | clear_masked(word); | ||
212 | if (sync_test_bit(EVTCHN_FIFO_PENDING, BM(word))) { | ||
213 | struct evtchn_unmask unmask = { .port = port }; | ||
214 | (void)HYPERVISOR_event_channel_op(EVTCHNOP_unmask, &unmask); | ||
215 | } | ||
216 | } | ||
217 | |||
218 | static uint32_t clear_linked(volatile event_word_t *word) | ||
219 | { | ||
220 | event_word_t new, old, w; | ||
221 | |||
222 | w = *word; | ||
223 | |||
224 | do { | ||
225 | old = w; | ||
226 | new = (w & ~((1 << EVTCHN_FIFO_LINKED) | ||
227 | | EVTCHN_FIFO_LINK_MASK)); | ||
228 | } while ((w = sync_cmpxchg(word, old, new)) != old); | ||
229 | |||
230 | return w & EVTCHN_FIFO_LINK_MASK; | ||
231 | } | ||
232 | |||
233 | static void handle_irq_for_port(unsigned port) | ||
234 | { | ||
235 | int irq; | ||
236 | struct irq_desc *desc; | ||
237 | |||
238 | irq = get_evtchn_to_irq(port); | ||
239 | if (irq != -1) { | ||
240 | desc = irq_to_desc(irq); | ||
241 | if (desc) | ||
242 | generic_handle_irq_desc(irq, desc); | ||
243 | } | ||
244 | } | ||
245 | |||
246 | static void consume_one_event(unsigned cpu, | ||
247 | struct evtchn_fifo_control_block *control_block, | ||
248 | unsigned priority, uint32_t *ready) | ||
249 | { | ||
250 | struct evtchn_fifo_queue *q = &per_cpu(cpu_queue, cpu); | ||
251 | uint32_t head; | ||
252 | unsigned port; | ||
253 | event_word_t *word; | ||
254 | |||
255 | head = q->head[priority]; | ||
256 | |||
257 | /* | ||
258 | * Reached the tail last time? Read the new HEAD from the | ||
259 | * control block. | ||
260 | */ | ||
261 | if (head == 0) { | ||
262 | rmb(); /* Ensure word is up-to-date before reading head. */ | ||
263 | head = control_block->head[priority]; | ||
264 | } | ||
265 | |||
266 | port = head; | ||
267 | word = event_word_from_port(port); | ||
268 | head = clear_linked(word); | ||
269 | |||
270 | /* | ||
271 | * If the link is non-zero, there are more events in the | ||
272 | * queue, otherwise the queue is empty. | ||
273 | * | ||
274 | * If the queue is empty, clear this priority from our local | ||
275 | * copy of the ready word. | ||
276 | */ | ||
277 | if (head == 0) | ||
278 | clear_bit(priority, BM(ready)); | ||
279 | |||
280 | if (sync_test_bit(EVTCHN_FIFO_PENDING, BM(word)) | ||
281 | && !sync_test_bit(EVTCHN_FIFO_MASKED, BM(word))) | ||
282 | handle_irq_for_port(port); | ||
283 | |||
284 | q->head[priority] = head; | ||
285 | } | ||
286 | |||
287 | static void evtchn_fifo_handle_events(unsigned cpu) | ||
288 | { | ||
289 | struct evtchn_fifo_control_block *control_block; | ||
290 | uint32_t ready; | ||
291 | unsigned q; | ||
292 | |||
293 | control_block = per_cpu(cpu_control_block, cpu); | ||
294 | |||
295 | ready = xchg(&control_block->ready, 0); | ||
296 | |||
297 | while (ready) { | ||
298 | q = find_first_bit(BM(&ready), EVTCHN_FIFO_MAX_QUEUES); | ||
299 | consume_one_event(cpu, control_block, q, &ready); | ||
300 | ready |= xchg(&control_block->ready, 0); | ||
301 | } | ||
302 | } | ||
303 | |||
304 | static void evtchn_fifo_resume(void) | ||
305 | { | ||
306 | unsigned cpu; | ||
307 | |||
308 | for_each_possible_cpu(cpu) { | ||
309 | void *control_block = per_cpu(cpu_control_block, cpu); | ||
310 | struct evtchn_init_control init_control; | ||
311 | int ret; | ||
312 | |||
313 | if (!control_block) | ||
314 | continue; | ||
315 | |||
316 | /* | ||
317 | * If this CPU is offline, take the opportunity to | ||
318 | * free the control block while it is not being | ||
319 | * used. | ||
320 | */ | ||
321 | if (!cpu_online(cpu)) { | ||
322 | free_page((unsigned long)control_block); | ||
323 | per_cpu(cpu_control_block, cpu) = NULL; | ||
324 | continue; | ||
325 | } | ||
326 | |||
327 | init_control.control_gfn = virt_to_mfn(control_block); | ||
328 | init_control.offset = 0; | ||
329 | init_control.vcpu = cpu; | ||
330 | |||
331 | ret = HYPERVISOR_event_channel_op(EVTCHNOP_init_control, | ||
332 | &init_control); | ||
333 | if (ret < 0) | ||
334 | BUG(); | ||
335 | } | ||
336 | |||
337 | /* | ||
338 | * The event array starts out as empty again and is extended | ||
339 | * as normal when events are bound. The existing pages will | ||
340 | * be reused. | ||
341 | */ | ||
342 | event_array_pages = 0; | ||
343 | } | ||
344 | |||
345 | static const struct evtchn_ops evtchn_ops_fifo = { | ||
346 | .max_channels = evtchn_fifo_max_channels, | ||
347 | .nr_channels = evtchn_fifo_nr_channels, | ||
348 | .setup = evtchn_fifo_setup, | ||
349 | .bind_to_cpu = evtchn_fifo_bind_to_cpu, | ||
350 | .clear_pending = evtchn_fifo_clear_pending, | ||
351 | .set_pending = evtchn_fifo_set_pending, | ||
352 | .is_pending = evtchn_fifo_is_pending, | ||
353 | .test_and_set_mask = evtchn_fifo_test_and_set_mask, | ||
354 | .mask = evtchn_fifo_mask, | ||
355 | .unmask = evtchn_fifo_unmask, | ||
356 | .handle_events = evtchn_fifo_handle_events, | ||
357 | .resume = evtchn_fifo_resume, | ||
358 | }; | ||
359 | |||
360 | static int __cpuinit evtchn_fifo_init_control_block(unsigned cpu) | ||
361 | { | ||
362 | struct page *control_block = NULL; | ||
363 | struct evtchn_init_control init_control; | ||
364 | int ret = -ENOMEM; | ||
365 | |||
366 | control_block = alloc_page(GFP_KERNEL|__GFP_ZERO); | ||
367 | if (control_block == NULL) | ||
368 | goto error; | ||
369 | |||
370 | init_control.control_gfn = virt_to_mfn(page_address(control_block)); | ||
371 | init_control.offset = 0; | ||
372 | init_control.vcpu = cpu; | ||
373 | |||
374 | ret = HYPERVISOR_event_channel_op(EVTCHNOP_init_control, &init_control); | ||
375 | if (ret < 0) | ||
376 | goto error; | ||
377 | |||
378 | per_cpu(cpu_control_block, cpu) = page_address(control_block); | ||
379 | |||
380 | return 0; | ||
381 | |||
382 | error: | ||
383 | __free_page(control_block); | ||
384 | return ret; | ||
385 | } | ||
386 | |||
387 | static int __cpuinit evtchn_fifo_cpu_notification(struct notifier_block *self, | ||
388 | unsigned long action, | ||
389 | void *hcpu) | ||
390 | { | ||
391 | int cpu = (long)hcpu; | ||
392 | int ret = 0; | ||
393 | |||
394 | switch (action) { | ||
395 | case CPU_UP_PREPARE: | ||
396 | if (!per_cpu(cpu_control_block, cpu)) | ||
397 | ret = evtchn_fifo_init_control_block(cpu); | ||
398 | break; | ||
399 | default: | ||
400 | break; | ||
401 | } | ||
402 | return ret < 0 ? NOTIFY_BAD : NOTIFY_OK; | ||
403 | } | ||
404 | |||
405 | static struct notifier_block evtchn_fifo_cpu_notifier __cpuinitdata = { | ||
406 | .notifier_call = evtchn_fifo_cpu_notification, | ||
407 | }; | ||
408 | |||
409 | int __init xen_evtchn_fifo_init(void) | ||
410 | { | ||
411 | int cpu = get_cpu(); | ||
412 | int ret; | ||
413 | |||
414 | ret = evtchn_fifo_init_control_block(cpu); | ||
415 | if (ret < 0) | ||
416 | goto out; | ||
417 | |||
418 | pr_info("Using FIFO-based ABI\n"); | ||
419 | |||
420 | evtchn_ops = &evtchn_ops_fifo; | ||
421 | |||
422 | register_cpu_notifier(&evtchn_fifo_cpu_notifier); | ||
423 | out: | ||
424 | put_cpu(); | ||
425 | return ret; | ||
426 | } | ||
diff --git a/drivers/xen/events/events_internal.h b/drivers/xen/events/events_internal.h index 2862e1cccf1c..677f41a0fff9 100644 --- a/drivers/xen/events/events_internal.h +++ b/drivers/xen/events/events_internal.h | |||
@@ -69,6 +69,7 @@ struct evtchn_ops { | |||
69 | void (*unmask)(unsigned port); | 69 | void (*unmask)(unsigned port); |
70 | 70 | ||
71 | void (*handle_events)(unsigned cpu); | 71 | void (*handle_events)(unsigned cpu); |
72 | void (*resume)(void); | ||
72 | }; | 73 | }; |
73 | 74 | ||
74 | extern const struct evtchn_ops *evtchn_ops; | 75 | extern const struct evtchn_ops *evtchn_ops; |
@@ -137,6 +138,13 @@ static inline void xen_evtchn_handle_events(unsigned cpu) | |||
137 | return evtchn_ops->handle_events(cpu); | 138 | return evtchn_ops->handle_events(cpu); |
138 | } | 139 | } |
139 | 140 | ||
141 | static inline void xen_evtchn_resume(void) | ||
142 | { | ||
143 | if (evtchn_ops->resume) | ||
144 | evtchn_ops->resume(); | ||
145 | } | ||
146 | |||
140 | void xen_evtchn_2l_init(void); | 147 | void xen_evtchn_2l_init(void); |
148 | int xen_evtchn_fifo_init(void); | ||
141 | 149 | ||
142 | #endif /* #ifndef __EVENTS_INTERNAL_H__ */ | 150 | #endif /* #ifndef __EVENTS_INTERNAL_H__ */ |