Blame view

drivers/xen/events/events_fifo.c 11.2 KB
1fe565517   David Vrabel   xen/events: use t...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  /*
   * Xen event channels (FIFO-based ABI)
   *
   * Copyright (C) 2013 Citrix Systems R&D ltd.
   *
   * This source code is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License as
   * published by the Free Software Foundation; either version 2 of the
   * License, or (at your option) any later version.
   *
   * Or, when distributed separately from the Linux kernel or
   * incorporated into other software packages, subject to the following
   * license:
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this source file (the "Software"), to deal in the Software without
   * restriction, including without limitation the rights to use, copy, modify,
   * merge, publish, distribute, sublicense, and/or sell copies of the Software,
   * and to permit persons to whom the Software is furnished to do so, subject to
   * the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
   * IN THE SOFTWARE.
   */
  
  #define pr_fmt(fmt) "xen:" KBUILD_MODNAME ": " fmt
  
  #include <linux/linkage.h>
  #include <linux/interrupt.h>
  #include <linux/irq.h>
1fe565517   David Vrabel   xen/events: use t...
39
40
41
  #include <linux/smp.h>
  #include <linux/percpu.h>
  #include <linux/cpu.h>
234927540   Michael S. Tsirkin   xen/events: use v...
42
  #include <asm/barrier.h>
1fe565517   David Vrabel   xen/events: use t...
43
44
45
  #include <asm/sync_bitops.h>
  #include <asm/xen/hypercall.h>
  #include <asm/xen/hypervisor.h>
1fe565517   David Vrabel   xen/events: use t...
46
47
48
49
50
51
  
  #include <xen/xen.h>
  #include <xen/xen-ops.h>
  #include <xen/events.h>
  #include <xen/interface/xen.h>
  #include <xen/interface/event_channel.h>
a9fd60e26   Julien Grall   xen: Include xen/...
52
  #include <xen/page.h>
1fe565517   David Vrabel   xen/events: use t...
53
54
  
  #include "events_internal.h"
a001c9d95   Julien Grall   xen/events: fifo:...
55
  #define EVENT_WORDS_PER_PAGE (XEN_PAGE_SIZE / sizeof(event_word_t))
1fe565517   David Vrabel   xen/events: use t...
56
57
58
59
60
61
62
63
64
65
  #define MAX_EVENT_ARRAY_PAGES (EVTCHN_FIFO_NR_CHANNELS / EVENT_WORDS_PER_PAGE)
  
  struct evtchn_fifo_queue {
  	uint32_t head[EVTCHN_FIFO_MAX_QUEUES];
  };
  
  static DEFINE_PER_CPU(struct evtchn_fifo_control_block *, cpu_control_block);
  static DEFINE_PER_CPU(struct evtchn_fifo_queue, cpu_queue);
  static event_word_t *event_array[MAX_EVENT_ARRAY_PAGES] __read_mostly;
  static unsigned event_array_pages __read_mostly;
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
66
  /*
dcecb8fd9   David Vrabel   xen/events/fifo: ...
67
   * sync_set_bit() and friends must be unsigned long aligned.
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
68
   */
dcecb8fd9   David Vrabel   xen/events/fifo: ...
69
  #if BITS_PER_LONG > 32
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
70
71
72
73
74
75
  
  #define BM(w) (unsigned long *)((unsigned long)w & ~0x7UL)
  #define EVTCHN_FIFO_BIT(b, w) \
      (((unsigned long)w & 0x4UL) ? (EVTCHN_FIFO_ ##b + 32) : EVTCHN_FIFO_ ##b)
  
  #else
1fe565517   David Vrabel   xen/events: use t...
76
  #define BM(w) ((unsigned long *)(w))
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
77
78
79
  #define EVTCHN_FIFO_BIT(b, w) EVTCHN_FIFO_ ##b
  
  #endif
1fe565517   David Vrabel   xen/events: use t...
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
  
  static inline event_word_t *event_word_from_port(unsigned port)
  {
  	unsigned i = port / EVENT_WORDS_PER_PAGE;
  
  	return event_array[i] + port % EVENT_WORDS_PER_PAGE;
  }
  
  static unsigned evtchn_fifo_max_channels(void)
  {
  	return EVTCHN_FIFO_NR_CHANNELS;
  }
  
  static unsigned evtchn_fifo_nr_channels(void)
  {
  	return event_array_pages * EVENT_WORDS_PER_PAGE;
  }
c12784c3d   David Vrabel   xen/events/fifo: ...
97
98
99
100
101
102
103
104
105
106
107
  static int init_control_block(int cpu,
                                struct evtchn_fifo_control_block *control_block)
  {
  	struct evtchn_fifo_queue *q = &per_cpu(cpu_queue, cpu);
  	struct evtchn_init_control init_control;
  	unsigned int i;
  
  	/* Reset the control block and the local HEADs. */
  	clear_page(control_block);
  	for (i = 0; i < EVTCHN_FIFO_MAX_QUEUES; i++)
  		q->head[i] = 0;
0df4f266b   Julien Grall   xen: Use correctl...
108
  	init_control.control_gfn = virt_to_gfn(control_block);
c12784c3d   David Vrabel   xen/events/fifo: ...
109
  	init_control.offset      = 0;
be78da1cf   Vitaly Kuznetsov   xen/events: fifo:...
110
  	init_control.vcpu        = xen_vcpu_nr(cpu);
c12784c3d   David Vrabel   xen/events/fifo: ...
111
112
113
  
  	return HYPERVISOR_event_channel_op(EVTCHNOP_init_control, &init_control);
  }
1fe565517   David Vrabel   xen/events: use t...
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
  static void free_unused_array_pages(void)
  {
  	unsigned i;
  
  	for (i = event_array_pages; i < MAX_EVENT_ARRAY_PAGES; i++) {
  		if (!event_array[i])
  			break;
  		free_page((unsigned long)event_array[i]);
  		event_array[i] = NULL;
  	}
  }
  
  static void init_array_page(event_word_t *array_page)
  {
  	unsigned i;
  
  	for (i = 0; i < EVENT_WORDS_PER_PAGE; i++)
  		array_page[i] = 1 << EVTCHN_FIFO_MASKED;
  }
  
  static int evtchn_fifo_setup(struct irq_info *info)
  {
  	unsigned port = info->evtchn;
  	unsigned new_array_pages;
be1403b9e   Wei Yongjun   xen/evtchn_fifo: ...
138
  	int ret;
1fe565517   David Vrabel   xen/events: use t...
139
140
141
142
143
144
145
146
147
148
149
150
151
152
  
  	new_array_pages = port / EVENT_WORDS_PER_PAGE + 1;
  
  	if (new_array_pages > MAX_EVENT_ARRAY_PAGES)
  		return -EINVAL;
  
  	while (event_array_pages < new_array_pages) {
  		void *array_page;
  		struct evtchn_expand_array expand_array;
  
  		/* Might already have a page if we've resumed. */
  		array_page = event_array[event_array_pages];
  		if (!array_page) {
  			array_page = (void *)__get_free_page(GFP_KERNEL);
be1403b9e   Wei Yongjun   xen/evtchn_fifo: ...
153
154
  			if (array_page == NULL) {
  				ret = -ENOMEM;
1fe565517   David Vrabel   xen/events: use t...
155
  				goto error;
be1403b9e   Wei Yongjun   xen/evtchn_fifo: ...
156
  			}
1fe565517   David Vrabel   xen/events: use t...
157
158
159
160
161
  			event_array[event_array_pages] = array_page;
  		}
  
  		/* Mask all events in this page before adding it. */
  		init_array_page(array_page);
0df4f266b   Julien Grall   xen: Use correctl...
162
  		expand_array.array_gfn = virt_to_gfn(array_page);
1fe565517   David Vrabel   xen/events: use t...
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
  
  		ret = HYPERVISOR_event_channel_op(EVTCHNOP_expand_array, &expand_array);
  		if (ret < 0)
  			goto error;
  
  		event_array_pages++;
  	}
  	return 0;
  
    error:
  	if (event_array_pages == 0)
  		panic("xen: unable to expand event array with initial page (%d)
  ", ret);
  	else
  		pr_err("unable to expand event array (%d)
  ", ret);
  	free_unused_array_pages();
  	return ret;
  }
  
  static void evtchn_fifo_bind_to_cpu(struct irq_info *info, unsigned cpu)
  {
  	/* no-op */
  }
  
  static void evtchn_fifo_clear_pending(unsigned port)
  {
  	event_word_t *word = event_word_from_port(port);
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
191
  	sync_clear_bit(EVTCHN_FIFO_BIT(PENDING, word), BM(word));
1fe565517   David Vrabel   xen/events: use t...
192
193
194
195
196
  }
  
  static void evtchn_fifo_set_pending(unsigned port)
  {
  	event_word_t *word = event_word_from_port(port);
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
197
  	sync_set_bit(EVTCHN_FIFO_BIT(PENDING, word), BM(word));
1fe565517   David Vrabel   xen/events: use t...
198
199
200
201
202
  }
  
  static bool evtchn_fifo_is_pending(unsigned port)
  {
  	event_word_t *word = event_word_from_port(port);
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
203
  	return sync_test_bit(EVTCHN_FIFO_BIT(PENDING, word), BM(word));
1fe565517   David Vrabel   xen/events: use t...
204
205
206
207
208
  }
  
  static bool evtchn_fifo_test_and_set_mask(unsigned port)
  {
  	event_word_t *word = event_word_from_port(port);
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
209
  	return sync_test_and_set_bit(EVTCHN_FIFO_BIT(MASKED, word), BM(word));
1fe565517   David Vrabel   xen/events: use t...
210
211
212
213
214
  }
  
  static void evtchn_fifo_mask(unsigned port)
  {
  	event_word_t *word = event_word_from_port(port);
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
215
  	sync_set_bit(EVTCHN_FIFO_BIT(MASKED, word), BM(word));
1fe565517   David Vrabel   xen/events: use t...
216
  }
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
217
218
219
220
221
  static bool evtchn_fifo_is_masked(unsigned port)
  {
  	event_word_t *word = event_word_from_port(port);
  	return sync_test_bit(EVTCHN_FIFO_BIT(MASKED, word), BM(word));
  }
1fe565517   David Vrabel   xen/events: use t...
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
  /*
   * Clear MASKED, spinning if BUSY is set.
   */
  static void clear_masked(volatile event_word_t *word)
  {
  	event_word_t new, old, w;
  
  	w = *word;
  
  	do {
  		old = w & ~(1 << EVTCHN_FIFO_BUSY);
  		new = old & ~(1 << EVTCHN_FIFO_MASKED);
  		w = sync_cmpxchg(word, old, new);
  	} while (w != old);
  }
  
  static void evtchn_fifo_unmask(unsigned port)
  {
  	event_word_t *word = event_word_from_port(port);
  
  	BUG_ON(!irqs_disabled());
  
  	clear_masked(word);
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
245
  	if (evtchn_fifo_is_pending(port)) {
1fe565517   David Vrabel   xen/events: use t...
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
  		struct evtchn_unmask unmask = { .port = port };
  		(void)HYPERVISOR_event_channel_op(EVTCHNOP_unmask, &unmask);
  	}
  }
  
  static uint32_t clear_linked(volatile event_word_t *word)
  {
  	event_word_t new, old, w;
  
  	w = *word;
  
  	do {
  		old = w;
  		new = (w & ~((1 << EVTCHN_FIFO_LINKED)
  			     | EVTCHN_FIFO_LINK_MASK));
  	} while ((w = sync_cmpxchg(word, old, new)) != old);
  
  	return w & EVTCHN_FIFO_LINK_MASK;
  }
  
  static void handle_irq_for_port(unsigned port)
  {
  	int irq;
1fe565517   David Vrabel   xen/events: use t...
269
270
  
  	irq = get_evtchn_to_irq(port);
589d03e93   Thomas Gleixner   xen: Use the prop...
271
272
  	if (irq != -1)
  		generic_handle_irq(irq);
1fe565517   David Vrabel   xen/events: use t...
273
274
275
276
  }
  
  static void consume_one_event(unsigned cpu,
  			      struct evtchn_fifo_control_block *control_block,
3de88d622   Ross Lagerwall   xen/events/fifo: ...
277
278
  			      unsigned priority, unsigned long *ready,
  			      bool drop)
1fe565517   David Vrabel   xen/events: use t...
279
280
281
282
283
284
285
286
287
288
289
290
291
  {
  	struct evtchn_fifo_queue *q = &per_cpu(cpu_queue, cpu);
  	uint32_t head;
  	unsigned port;
  	event_word_t *word;
  
  	head = q->head[priority];
  
  	/*
  	 * Reached the tail last time?  Read the new HEAD from the
  	 * control block.
  	 */
  	if (head == 0) {
234927540   Michael S. Tsirkin   xen/events: use v...
292
  		virt_rmb(); /* Ensure word is up-to-date before reading head. */
1fe565517   David Vrabel   xen/events: use t...
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
  		head = control_block->head[priority];
  	}
  
  	port = head;
  	word = event_word_from_port(port);
  	head = clear_linked(word);
  
  	/*
  	 * If the link is non-zero, there are more events in the
  	 * queue, otherwise the queue is empty.
  	 *
  	 * If the queue is empty, clear this priority from our local
  	 * copy of the ready word.
  	 */
  	if (head == 0)
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
308
  		clear_bit(priority, ready);
1fe565517   David Vrabel   xen/events: use t...
309

3de88d622   Ross Lagerwall   xen/events/fifo: ...
310
311
312
313
314
315
316
  	if (evtchn_fifo_is_pending(port) && !evtchn_fifo_is_masked(port)) {
  		if (unlikely(drop))
  			pr_warn("Dropping pending event for port %u
  ", port);
  		else
  			handle_irq_for_port(port);
  	}
1fe565517   David Vrabel   xen/events: use t...
317
318
319
  
  	q->head[priority] = head;
  }
3de88d622   Ross Lagerwall   xen/events/fifo: ...
320
  static void __evtchn_fifo_handle_events(unsigned cpu, bool drop)
1fe565517   David Vrabel   xen/events: use t...
321
322
  {
  	struct evtchn_fifo_control_block *control_block;
05a812ac4   Vladimir Murzin   xen/events/fifo: ...
323
  	unsigned long ready;
1fe565517   David Vrabel   xen/events: use t...
324
325
326
327
328
329
330
  	unsigned q;
  
  	control_block = per_cpu(cpu_control_block, cpu);
  
  	ready = xchg(&control_block->ready, 0);
  
  	while (ready) {
e4a743124   Frediano Ziglio   xen/events/fifo: ...
331
  		q = find_first_bit(&ready, EVTCHN_FIFO_MAX_QUEUES);
3de88d622   Ross Lagerwall   xen/events/fifo: ...
332
  		consume_one_event(cpu, control_block, q, &ready, drop);
1fe565517   David Vrabel   xen/events: use t...
333
334
335
  		ready |= xchg(&control_block->ready, 0);
  	}
  }
3de88d622   Ross Lagerwall   xen/events/fifo: ...
336
337
338
339
  static void evtchn_fifo_handle_events(unsigned cpu)
  {
  	__evtchn_fifo_handle_events(cpu, false);
  }
1fe565517   David Vrabel   xen/events: use t...
340
341
342
343
344
345
  static void evtchn_fifo_resume(void)
  {
  	unsigned cpu;
  
  	for_each_possible_cpu(cpu) {
  		void *control_block = per_cpu(cpu_control_block, cpu);
1fe565517   David Vrabel   xen/events: use t...
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
  		int ret;
  
  		if (!control_block)
  			continue;
  
  		/*
  		 * If this CPU is offline, take the opportunity to
  		 * free the control block while it is not being
  		 * used.
  		 */
  		if (!cpu_online(cpu)) {
  			free_page((unsigned long)control_block);
  			per_cpu(cpu_control_block, cpu) = NULL;
  			continue;
  		}
c12784c3d   David Vrabel   xen/events/fifo: ...
361
  		ret = init_control_block(cpu, control_block);
1fe565517   David Vrabel   xen/events: use t...
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
  		if (ret < 0)
  			BUG();
  	}
  
  	/*
  	 * The event array starts out as empty again and is extended
  	 * as normal when events are bound.  The existing pages will
  	 * be reused.
  	 */
  	event_array_pages = 0;
  }
  
  static const struct evtchn_ops evtchn_ops_fifo = {
  	.max_channels      = evtchn_fifo_max_channels,
  	.nr_channels       = evtchn_fifo_nr_channels,
  	.setup             = evtchn_fifo_setup,
  	.bind_to_cpu       = evtchn_fifo_bind_to_cpu,
  	.clear_pending     = evtchn_fifo_clear_pending,
  	.set_pending       = evtchn_fifo_set_pending,
  	.is_pending        = evtchn_fifo_is_pending,
  	.test_and_set_mask = evtchn_fifo_test_and_set_mask,
  	.mask              = evtchn_fifo_mask,
  	.unmask            = evtchn_fifo_unmask,
  	.handle_events     = evtchn_fifo_handle_events,
  	.resume            = evtchn_fifo_resume,
  };
c12784c3d   David Vrabel   xen/events/fifo: ...
388
  static int evtchn_fifo_alloc_control_block(unsigned cpu)
1fe565517   David Vrabel   xen/events: use t...
389
  {
c12784c3d   David Vrabel   xen/events/fifo: ...
390
  	void *control_block = NULL;
1fe565517   David Vrabel   xen/events: use t...
391
  	int ret = -ENOMEM;
c12784c3d   David Vrabel   xen/events/fifo: ...
392
  	control_block = (void *)__get_free_page(GFP_KERNEL);
1fe565517   David Vrabel   xen/events: use t...
393
394
  	if (control_block == NULL)
  		goto error;
c12784c3d   David Vrabel   xen/events/fifo: ...
395
  	ret = init_control_block(cpu, control_block);
1fe565517   David Vrabel   xen/events: use t...
396
397
  	if (ret < 0)
  		goto error;
c12784c3d   David Vrabel   xen/events/fifo: ...
398
  	per_cpu(cpu_control_block, cpu) = control_block;
1fe565517   David Vrabel   xen/events: use t...
399
400
401
402
  
  	return 0;
  
    error:
c12784c3d   David Vrabel   xen/events/fifo: ...
403
  	free_page((unsigned long)control_block);
1fe565517   David Vrabel   xen/events: use t...
404
405
  	return ret;
  }
c8761e201   Sebastian Andrzej Siewior   xen/events: Conve...
406
  static int xen_evtchn_cpu_prepare(unsigned int cpu)
1fe565517   David Vrabel   xen/events: use t...
407
  {
c8761e201   Sebastian Andrzej Siewior   xen/events: Conve...
408
409
410
  	if (!per_cpu(cpu_control_block, cpu))
  		return evtchn_fifo_alloc_control_block(cpu);
  	return 0;
1fe565517   David Vrabel   xen/events: use t...
411
  }
c8761e201   Sebastian Andrzej Siewior   xen/events: Conve...
412
413
414
415
416
  static int xen_evtchn_cpu_dead(unsigned int cpu)
  {
  	__evtchn_fifo_handle_events(cpu, true);
  	return 0;
  }
1fe565517   David Vrabel   xen/events: use t...
417
418
419
420
421
  
  int __init xen_evtchn_fifo_init(void)
  {
  	int cpu = get_cpu();
  	int ret;
c12784c3d   David Vrabel   xen/events/fifo: ...
422
  	ret = evtchn_fifo_alloc_control_block(cpu);
1fe565517   David Vrabel   xen/events: use t...
423
424
425
426
427
428
429
  	if (ret < 0)
  		goto out;
  
  	pr_info("Using FIFO-based ABI
  ");
  
  	evtchn_ops = &evtchn_ops_fifo;
c8761e201   Sebastian Andrzej Siewior   xen/events: Conve...
430
431
432
  	cpuhp_setup_state_nocalls(CPUHP_XEN_EVTCHN_PREPARE,
  				  "CPUHP_XEN_EVTCHN_PREPARE",
  				  xen_evtchn_cpu_prepare, xen_evtchn_cpu_dead);
1fe565517   David Vrabel   xen/events: use t...
433
434
435
436
  out:
  	put_cpu();
  	return ret;
  }