diff options
Diffstat (limited to 'arch/arm/kvm/vgic.c')
-rw-r--r-- | arch/arm/kvm/vgic.c | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/arch/arm/kvm/vgic.c b/arch/arm/kvm/vgic.c new file mode 100644 index 000000000000..c400661409ab --- /dev/null +++ b/arch/arm/kvm/vgic.c | |||
@@ -0,0 +1,153 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2012 ARM Ltd. | ||
3 | * Author: Marc Zyngier <marc.zyngier@arm.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the Free Software | ||
16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
17 | */ | ||
18 | |||
19 | #include <linux/kvm.h> | ||
20 | #include <linux/kvm_host.h> | ||
21 | #include <linux/interrupt.h> | ||
22 | #include <linux/io.h> | ||
23 | #include <asm/kvm_emulate.h> | ||
24 | |||
25 | #define ACCESS_READ_VALUE (1 << 0) | ||
26 | #define ACCESS_READ_RAZ (0 << 0) | ||
27 | #define ACCESS_READ_MASK(x) ((x) & (1 << 0)) | ||
28 | #define ACCESS_WRITE_IGNORED (0 << 1) | ||
29 | #define ACCESS_WRITE_SETBIT (1 << 1) | ||
30 | #define ACCESS_WRITE_CLEARBIT (2 << 1) | ||
31 | #define ACCESS_WRITE_VALUE (3 << 1) | ||
32 | #define ACCESS_WRITE_MASK(x) ((x) & (3 << 1)) | ||
33 | |||
34 | static u32 mmio_data_read(struct kvm_exit_mmio *mmio, u32 mask) | ||
35 | { | ||
36 | return *((u32 *)mmio->data) & mask; | ||
37 | } | ||
38 | |||
39 | static void mmio_data_write(struct kvm_exit_mmio *mmio, u32 mask, u32 value) | ||
40 | { | ||
41 | *((u32 *)mmio->data) = value & mask; | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * vgic_reg_access - access vgic register | ||
46 | * @mmio: pointer to the data describing the mmio access | ||
47 | * @reg: pointer to the virtual backing of vgic distributor data | ||
48 | * @offset: least significant 2 bits used for word offset | ||
49 | * @mode: ACCESS_ mode (see defines above) | ||
50 | * | ||
51 | * Helper to make vgic register access easier using one of the access | ||
52 | * modes defined for vgic register access | ||
53 | * (read,raz,write-ignored,setbit,clearbit,write) | ||
54 | */ | ||
55 | static void vgic_reg_access(struct kvm_exit_mmio *mmio, u32 *reg, | ||
56 | phys_addr_t offset, int mode) | ||
57 | { | ||
58 | int word_offset = (offset & 3) * 8; | ||
59 | u32 mask = (1UL << (mmio->len * 8)) - 1; | ||
60 | u32 regval; | ||
61 | |||
62 | /* | ||
63 | * Any alignment fault should have been delivered to the guest | ||
64 | * directly (ARM ARM B3.12.7 "Prioritization of aborts"). | ||
65 | */ | ||
66 | |||
67 | if (reg) { | ||
68 | regval = *reg; | ||
69 | } else { | ||
70 | BUG_ON(mode != (ACCESS_READ_RAZ | ACCESS_WRITE_IGNORED)); | ||
71 | regval = 0; | ||
72 | } | ||
73 | |||
74 | if (mmio->is_write) { | ||
75 | u32 data = mmio_data_read(mmio, mask) << word_offset; | ||
76 | switch (ACCESS_WRITE_MASK(mode)) { | ||
77 | case ACCESS_WRITE_IGNORED: | ||
78 | return; | ||
79 | |||
80 | case ACCESS_WRITE_SETBIT: | ||
81 | regval |= data; | ||
82 | break; | ||
83 | |||
84 | case ACCESS_WRITE_CLEARBIT: | ||
85 | regval &= ~data; | ||
86 | break; | ||
87 | |||
88 | case ACCESS_WRITE_VALUE: | ||
89 | regval = (regval & ~(mask << word_offset)) | data; | ||
90 | break; | ||
91 | } | ||
92 | *reg = regval; | ||
93 | } else { | ||
94 | switch (ACCESS_READ_MASK(mode)) { | ||
95 | case ACCESS_READ_RAZ: | ||
96 | regval = 0; | ||
97 | /* fall through */ | ||
98 | |||
99 | case ACCESS_READ_VALUE: | ||
100 | mmio_data_write(mmio, mask, regval >> word_offset); | ||
101 | } | ||
102 | } | ||
103 | } | ||
104 | |||
105 | /* | ||
106 | * I would have liked to use the kvm_bus_io_*() API instead, but it | ||
107 | * cannot cope with banked registers (only the VM pointer is passed | ||
108 | * around, and we need the vcpu). One of these days, someone please | ||
109 | * fix it! | ||
110 | */ | ||
111 | struct mmio_range { | ||
112 | phys_addr_t base; | ||
113 | unsigned long len; | ||
114 | bool (*handle_mmio)(struct kvm_vcpu *vcpu, struct kvm_exit_mmio *mmio, | ||
115 | phys_addr_t offset); | ||
116 | }; | ||
117 | |||
118 | static const struct mmio_range vgic_ranges[] = { | ||
119 | {} | ||
120 | }; | ||
121 | |||
122 | static const | ||
123 | struct mmio_range *find_matching_range(const struct mmio_range *ranges, | ||
124 | struct kvm_exit_mmio *mmio, | ||
125 | phys_addr_t base) | ||
126 | { | ||
127 | const struct mmio_range *r = ranges; | ||
128 | phys_addr_t addr = mmio->phys_addr - base; | ||
129 | |||
130 | while (r->len) { | ||
131 | if (addr >= r->base && | ||
132 | (addr + mmio->len) <= (r->base + r->len)) | ||
133 | return r; | ||
134 | r++; | ||
135 | } | ||
136 | |||
137 | return NULL; | ||
138 | } | ||
139 | |||
140 | /** | ||
141 | * vgic_handle_mmio - handle an in-kernel MMIO access | ||
142 | * @vcpu: pointer to the vcpu performing the access | ||
143 | * @run: pointer to the kvm_run structure | ||
144 | * @mmio: pointer to the data describing the access | ||
145 | * | ||
146 | * returns true if the MMIO access has been performed in kernel space, | ||
147 | * and false if it needs to be emulated in user space. | ||
148 | */ | ||
149 | bool vgic_handle_mmio(struct kvm_vcpu *vcpu, struct kvm_run *run, | ||
150 | struct kvm_exit_mmio *mmio) | ||
151 | { | ||
152 | return KVM_EXIT_MMIO; | ||
153 | } | ||