Commit | Line | Data |
---|---|---|
06d1838d SL |
1 | /* |
2 | * This file is subject to the terms and conditions of the GNU General Public | |
3 | * License. See the file "COPYING" in the main directory of this archive | |
4 | * for more details. | |
5 | * | |
6 | * KVM/MIPS: Interrupt delivery | |
7 | * | |
8 | * Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved. | |
9 | * Authors: Sanjay Lal <sanjayl@kymasys.com> | |
10 | */ | |
11 | ||
12 | #include <linux/errno.h> | |
13 | #include <linux/err.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/vmalloc.h> | |
16 | #include <linux/fs.h> | |
17 | #include <linux/bootmem.h> | |
18 | #include <asm/page.h> | |
19 | #include <asm/cacheflush.h> | |
20 | ||
21 | #include <linux/kvm_host.h> | |
22 | ||
23 | #include "kvm_mips_int.h" | |
24 | ||
25 | void kvm_mips_queue_irq(struct kvm_vcpu *vcpu, uint32_t priority) | |
26 | { | |
27 | set_bit(priority, &vcpu->arch.pending_exceptions); | |
28 | } | |
29 | ||
30 | void kvm_mips_dequeue_irq(struct kvm_vcpu *vcpu, uint32_t priority) | |
31 | { | |
32 | clear_bit(priority, &vcpu->arch.pending_exceptions); | |
33 | } | |
34 | ||
35 | void kvm_mips_queue_timer_int_cb(struct kvm_vcpu *vcpu) | |
36 | { | |
37 | /* Cause bits to reflect the pending timer interrupt, | |
38 | * the EXC code will be set when we are actually | |
39 | * delivering the interrupt: | |
40 | */ | |
41 | kvm_set_c0_guest_cause(vcpu->arch.cop0, (C_IRQ5 | C_TI)); | |
42 | ||
43 | /* Queue up an INT exception for the core */ | |
44 | kvm_mips_queue_irq(vcpu, MIPS_EXC_INT_TIMER); | |
45 | ||
46 | } | |
47 | ||
48 | void kvm_mips_dequeue_timer_int_cb(struct kvm_vcpu *vcpu) | |
49 | { | |
50 | kvm_clear_c0_guest_cause(vcpu->arch.cop0, (C_IRQ5 | C_TI)); | |
51 | kvm_mips_dequeue_irq(vcpu, MIPS_EXC_INT_TIMER); | |
52 | } | |
53 | ||
54 | void | |
55 | kvm_mips_queue_io_int_cb(struct kvm_vcpu *vcpu, struct kvm_mips_interrupt *irq) | |
56 | { | |
57 | int intr = (int)irq->irq; | |
58 | ||
59 | /* Cause bits to reflect the pending IO interrupt, | |
60 | * the EXC code will be set when we are actually | |
61 | * delivering the interrupt: | |
62 | */ | |
63 | switch (intr) { | |
64 | case 2: | |
65 | kvm_set_c0_guest_cause(vcpu->arch.cop0, (C_IRQ0)); | |
66 | /* Queue up an INT exception for the core */ | |
67 | kvm_mips_queue_irq(vcpu, MIPS_EXC_INT_IO); | |
68 | break; | |
69 | ||
70 | case 3: | |
71 | kvm_set_c0_guest_cause(vcpu->arch.cop0, (C_IRQ1)); | |
72 | kvm_mips_queue_irq(vcpu, MIPS_EXC_INT_IPI_1); | |
73 | break; | |
74 | ||
75 | case 4: | |
76 | kvm_set_c0_guest_cause(vcpu->arch.cop0, (C_IRQ2)); | |
77 | kvm_mips_queue_irq(vcpu, MIPS_EXC_INT_IPI_2); | |
78 | break; | |
79 | ||
80 | default: | |
81 | break; | |
82 | } | |
83 | ||
84 | } | |
85 | ||
86 | void | |
87 | kvm_mips_dequeue_io_int_cb(struct kvm_vcpu *vcpu, | |
88 | struct kvm_mips_interrupt *irq) | |
89 | { | |
90 | int intr = (int)irq->irq; | |
91 | switch (intr) { | |
92 | case -2: | |
93 | kvm_clear_c0_guest_cause(vcpu->arch.cop0, (C_IRQ0)); | |
94 | kvm_mips_dequeue_irq(vcpu, MIPS_EXC_INT_IO); | |
95 | break; | |
96 | ||
97 | case -3: | |
98 | kvm_clear_c0_guest_cause(vcpu->arch.cop0, (C_IRQ1)); | |
99 | kvm_mips_dequeue_irq(vcpu, MIPS_EXC_INT_IPI_1); | |
100 | break; | |
101 | ||
102 | case -4: | |
103 | kvm_clear_c0_guest_cause(vcpu->arch.cop0, (C_IRQ2)); | |
104 | kvm_mips_dequeue_irq(vcpu, MIPS_EXC_INT_IPI_2); | |
105 | break; | |
106 | ||
107 | default: | |
108 | break; | |
109 | } | |
110 | ||
111 | } | |
112 | ||
113 | /* Deliver the interrupt of the corresponding priority, if possible. */ | |
114 | int | |
115 | kvm_mips_irq_deliver_cb(struct kvm_vcpu *vcpu, unsigned int priority, | |
116 | uint32_t cause) | |
117 | { | |
118 | int allowed = 0; | |
119 | uint32_t exccode; | |
120 | ||
121 | struct kvm_vcpu_arch *arch = &vcpu->arch; | |
122 | struct mips_coproc *cop0 = vcpu->arch.cop0; | |
123 | ||
124 | switch (priority) { | |
125 | case MIPS_EXC_INT_TIMER: | |
126 | if ((kvm_read_c0_guest_status(cop0) & ST0_IE) | |
127 | && (!(kvm_read_c0_guest_status(cop0) & (ST0_EXL | ST0_ERL))) | |
128 | && (kvm_read_c0_guest_status(cop0) & IE_IRQ5)) { | |
129 | allowed = 1; | |
130 | exccode = T_INT; | |
131 | } | |
132 | break; | |
133 | ||
134 | case MIPS_EXC_INT_IO: | |
135 | if ((kvm_read_c0_guest_status(cop0) & ST0_IE) | |
136 | && (!(kvm_read_c0_guest_status(cop0) & (ST0_EXL | ST0_ERL))) | |
137 | && (kvm_read_c0_guest_status(cop0) & IE_IRQ0)) { | |
138 | allowed = 1; | |
139 | exccode = T_INT; | |
140 | } | |
141 | break; | |
142 | ||
143 | case MIPS_EXC_INT_IPI_1: | |
144 | if ((kvm_read_c0_guest_status(cop0) & ST0_IE) | |
145 | && (!(kvm_read_c0_guest_status(cop0) & (ST0_EXL | ST0_ERL))) | |
146 | && (kvm_read_c0_guest_status(cop0) & IE_IRQ1)) { | |
147 | allowed = 1; | |
148 | exccode = T_INT; | |
149 | } | |
150 | break; | |
151 | ||
152 | case MIPS_EXC_INT_IPI_2: | |
153 | if ((kvm_read_c0_guest_status(cop0) & ST0_IE) | |
154 | && (!(kvm_read_c0_guest_status(cop0) & (ST0_EXL | ST0_ERL))) | |
155 | && (kvm_read_c0_guest_status(cop0) & IE_IRQ2)) { | |
156 | allowed = 1; | |
157 | exccode = T_INT; | |
158 | } | |
159 | break; | |
160 | ||
161 | default: | |
162 | break; | |
163 | } | |
164 | ||
165 | /* Are we allowed to deliver the interrupt ??? */ | |
166 | if (allowed) { | |
167 | ||
168 | if ((kvm_read_c0_guest_status(cop0) & ST0_EXL) == 0) { | |
169 | /* save old pc */ | |
170 | kvm_write_c0_guest_epc(cop0, arch->pc); | |
171 | kvm_set_c0_guest_status(cop0, ST0_EXL); | |
172 | ||
173 | if (cause & CAUSEF_BD) | |
174 | kvm_set_c0_guest_cause(cop0, CAUSEF_BD); | |
175 | else | |
176 | kvm_clear_c0_guest_cause(cop0, CAUSEF_BD); | |
177 | ||
178 | kvm_debug("Delivering INT @ pc %#lx\n", arch->pc); | |
179 | ||
180 | } else | |
181 | kvm_err("Trying to deliver interrupt when EXL is already set\n"); | |
182 | ||
183 | kvm_change_c0_guest_cause(cop0, CAUSEF_EXCCODE, | |
184 | (exccode << CAUSEB_EXCCODE)); | |
185 | ||
186 | /* XXXSL Set PC to the interrupt exception entry point */ | |
187 | if (kvm_read_c0_guest_cause(cop0) & CAUSEF_IV) | |
188 | arch->pc = KVM_GUEST_KSEG0 + 0x200; | |
189 | else | |
190 | arch->pc = KVM_GUEST_KSEG0 + 0x180; | |
191 | ||
192 | clear_bit(priority, &vcpu->arch.pending_exceptions); | |
193 | } | |
194 | ||
195 | return allowed; | |
196 | } | |
197 | ||
198 | int | |
199 | kvm_mips_irq_clear_cb(struct kvm_vcpu *vcpu, unsigned int priority, | |
200 | uint32_t cause) | |
201 | { | |
202 | return 1; | |
203 | } | |
204 | ||
205 | void kvm_mips_deliver_interrupts(struct kvm_vcpu *vcpu, uint32_t cause) | |
206 | { | |
207 | unsigned long *pending = &vcpu->arch.pending_exceptions; | |
208 | unsigned long *pending_clr = &vcpu->arch.pending_exceptions_clr; | |
209 | unsigned int priority; | |
210 | ||
211 | if (!(*pending) && !(*pending_clr)) | |
212 | return; | |
213 | ||
214 | priority = __ffs(*pending_clr); | |
215 | while (priority <= MIPS_EXC_MAX) { | |
216 | if (kvm_mips_callbacks->irq_clear(vcpu, priority, cause)) { | |
217 | if (!KVM_MIPS_IRQ_CLEAR_ALL_AT_ONCE) | |
218 | break; | |
219 | } | |
220 | ||
221 | priority = find_next_bit(pending_clr, | |
222 | BITS_PER_BYTE * sizeof(*pending_clr), | |
223 | priority + 1); | |
224 | } | |
225 | ||
226 | priority = __ffs(*pending); | |
227 | while (priority <= MIPS_EXC_MAX) { | |
228 | if (kvm_mips_callbacks->irq_deliver(vcpu, priority, cause)) { | |
229 | if (!KVM_MIPS_IRQ_DELIVER_ALL_AT_ONCE) | |
230 | break; | |
231 | } | |
232 | ||
233 | priority = find_next_bit(pending, | |
234 | BITS_PER_BYTE * sizeof(*pending), | |
235 | priority + 1); | |
236 | } | |
237 | ||
238 | } | |
239 | ||
240 | int kvm_mips_pending_timer(struct kvm_vcpu *vcpu) | |
241 | { | |
242 | return test_bit(MIPS_EXC_INT_TIMER, &vcpu->arch.pending_exceptions); | |
243 | } |