]> rtime.felk.cvut.cz Git - jailhouse.git/blob - hypervisor/mmio.c
a64093ca3e9dcaa9f1dce0c27dccbcb0e9d5c125
[jailhouse.git] / hypervisor / mmio.c
1 /*
2  * Jailhouse, a Linux-based partitioning hypervisor
3  *
4  * Copyright (c) Siemens AG, 2015
5  *
6  * Authors:
7  *  Jan Kiszka <jan.kiszka@siemens.com>
8  *
9  * This work is licensed under the terms of the GNU GPL, version 2.  See
10  * the COPYING file in the top-level directory.
11  */
12
13 #include <jailhouse/cell.h>
14 #include <jailhouse/mmio.h>
15 #include <jailhouse/paging.h>
16 #include <jailhouse/printk.h>
17 #include <asm/percpu.h>
18
19 /**
20  * Perform MMIO-specific initialization for a new cell.
21  * @param cell          Cell to be initialized.
22  *
23  * @return 0 on success, negative error code otherwise.
24  *
25  * @see mmio_cell_exit
26  */
27 int mmio_cell_init(struct cell *cell)
28 {
29         void *pages;
30
31         cell->max_mmio_regions = arch_mmio_count_regions(cell);
32
33         pages = page_alloc(&mem_pool,
34                            PAGES(cell->max_mmio_regions *
35                                  (sizeof(struct mmio_region_location) +
36                                   sizeof(struct mmio_region_handler))));
37         if (!pages)
38                 return -ENOMEM;
39
40         cell->mmio_locations = pages;
41         cell->mmio_handlers = pages +
42                 cell->max_mmio_regions * sizeof(struct mmio_region_location);
43
44         return 0;
45 }
46
47 static void copy_region(struct cell *cell, unsigned int src, unsigned dst)
48 {
49         /*
50          * Invalidate destination region by shrinking it to size 0. This has to
51          * be made visible to other CPUs via a memory barrier before
52          * manipulating other destination fields.
53          */
54         cell->mmio_locations[dst].size = 0;
55         memory_barrier();
56
57         cell->mmio_locations[dst].start = cell->mmio_locations[src].start;
58         cell->mmio_handlers[dst] = cell->mmio_handlers[src];
59         /* Ensure all fields are committed before activating the region. */
60         memory_barrier();
61
62         cell->mmio_locations[dst].size = cell->mmio_locations[src].size;
63 }
64
65 /**
66  * Register a MMIO region access handler for a cell.
67  * @param cell          Cell than can access the region.
68  * @param start         Region start address in cell address space.
69  * @param size          Region size.
70  * @param handler       Access handler.
71  * @param handler_arg   Opaque argument to pass to handler.
72  *
73  * @see mmio_region_unregister
74  */
75 void mmio_region_register(struct cell *cell, unsigned long start,
76                           unsigned long size, mmio_handler handler,
77                           void *handler_arg)
78 {
79         unsigned int index, n;
80
81         spin_lock(&cell->mmio_region_lock);
82
83         if (cell->num_mmio_regions >= cell->max_mmio_regions) {
84                 spin_unlock(&cell->mmio_region_lock);
85
86                 printk("WARNING: Overflow during MMIO region registration!\n");
87                 return;
88         }
89
90         for (index = 0; index < cell->num_mmio_regions; index++)
91                 if (cell->mmio_locations[index].start > start)
92                         break;
93
94         /*
95          * Set and commit a dummy region at the end if the list so that
96          * we can safely grow it.
97          */
98         cell->mmio_locations[cell->num_mmio_regions].start = -1;
99         cell->mmio_locations[cell->num_mmio_regions].size = 0;
100         memory_barrier();
101
102         /*
103          * Extend region list by one so that we can start moving entries.
104          * Commit this change via a barrier so that the current last element
105          * will remain visible when moving it up.
106          */
107         cell->num_mmio_regions++;
108         memory_barrier();
109
110         for (n = cell->num_mmio_regions - 1; n > index; n--)
111                 copy_region(cell, n - 1, n);
112
113         /* Invalidate the new region entry first (see also copy_region()). */
114         cell->mmio_locations[index].size = 0;
115         memory_barrier();
116
117         cell->mmio_locations[index].start = start;
118         cell->mmio_handlers[index].handler = handler;
119         cell->mmio_handlers[index].arg = handler_arg;
120         /* Ensure all fields are committed before activating the region. */
121         memory_barrier();
122
123         cell->mmio_locations[index].size = size;
124
125         spin_unlock(&cell->mmio_region_lock);
126 }
127
128 static int find_region(struct cell *cell, unsigned long address,
129                        unsigned int size)
130 {
131         unsigned int range_start = 0;
132         unsigned int range_size = cell->num_mmio_regions;
133         struct mmio_region_location region;
134         unsigned int index;
135
136         while (range_size > 0) {
137                 index = range_start + range_size / 2;
138                 region = cell->mmio_locations[index];
139
140                 if (address < region.start) {
141                         range_size = index - range_start;
142                 } else if (region.start + region.size < address + size) {
143                         range_size -= index + 1 - range_start;
144                         range_start = index + 1;
145                 } else {
146                         return index;
147                 }
148         }
149         return -1;
150 }
151
152 /**
153  * Unregister MMIO region from a cell.
154  * @param cell          Cell the region belongs to.
155  * @param start         Region start address as it was passed to
156  *                      mmio_region_register().
157  *
158  * @see mmio_region_register
159  */
160 void mmio_region_unregister(struct cell *cell, unsigned long start)
161 {
162         int index;
163
164         spin_lock(&cell->mmio_region_lock);
165
166         index = find_region(cell, start, 0);
167         if (index >= 0) {
168                 for (/* empty */; index < cell->num_mmio_regions; index++)
169                         copy_region(cell, index + 1, index);
170
171                 /*
172                  * Ensure the last region move is visible before shrinking the
173                  * list.
174                  */
175                 memory_barrier();
176
177                 cell->num_mmio_regions--;
178         }
179         spin_unlock(&cell->mmio_region_lock);
180 }
181
182 /**
183  * Dispatch MMIO access of a cell CPU.
184  * @param mmio          MMIO access description. @a mmio->value will receive the
185  *                      result of a successful read access. All @a mmio fields
186  *                      may have been modified on return.
187  *
188  * @return MMIO_HANDLED on success, MMIO_UNHANDLED if no region is registered
189  * for the access address and size, or MMIO_ERROR if an access error was
190  * detected.
191  *
192  * @see mmio_region_register
193  * @see mmio_region_unregister
194  */
195 enum mmio_result mmio_handle_access(struct mmio_access *mmio)
196 {
197         struct cell *cell = this_cell();
198         int index = find_region(cell, mmio->address, mmio->size);
199         mmio_handler handler;
200
201         if (index < 0)
202                 return MMIO_UNHANDLED;
203
204         handler = cell->mmio_handlers[index].handler;
205         mmio->address -= cell->mmio_locations[index].start;
206         return handler(cell->mmio_handlers[index].arg, mmio);
207 }
208
209 /**
210  * Perform MMIO-specific cleanup for a cell under destruction.
211  * @param cell          Cell to be destructed.
212  *
213  * @see mmio_cell_init
214  */
215 void mmio_cell_exit(struct cell *cell)
216 {
217         page_free(&mem_pool, cell->mmio_locations,
218                   PAGES(cell->max_mmio_regions *
219                         (sizeof(struct mmio_region_location) +
220                          sizeof(struct mmio_region_handler))));
221 }