blob: 973bd97d9338c9d9043c64ad830ce96870dec239 [file] [log] [blame]
Wedson Almeida Filho2f62b422018-06-19 06:44:32 +01001#include <linux/hrtimer.h>
2#include <linux/init.h>
3#include <linux/kernel.h>
4#include <linux/kthread.h>
5#include <linux/module.h>
6#include <linux/sched/task.h>
7#include <linux/slab.h>
8
9struct hf_vcpu {
10 spinlock_t lock;
11 long vm_index;
12 long vcpu_index;
13 struct task_struct *task;
14 struct hrtimer timer;
15 bool pending_irq;
16};
17
18struct hf_vm {
19 long vcpu_count;
20 struct hf_vcpu *vcpu;
21};
22
23long hf_hvc(size_t arg0, size_t arg1, size_t arg2, size_t arg3);
24
25static struct hf_vm *hf_vms;
26static long hf_vm_count;
27
28
29/* TODO: Define constants below according to spec. Include shared header. */
30#define HF_VCPU_RUN 0xff00
31#define HF_VM_GET_COUNT 0xff01
32#define HF_VCPU_GET_COUNT 0xff02
33
34/**
35 * Wakes up the thread associated with the vcpu that owns the given timer. This
36 * is called when the timer the thread is waiting on expires.
37 */
38static enum hrtimer_restart hf_vcpu_timer_expired(struct hrtimer *timer)
39{
40 struct hf_vcpu *vcpu = container_of(timer, struct hf_vcpu, timer);
41 wake_up_process(vcpu->task);
42 return HRTIMER_NORESTART;
43}
44
45/**
46 * This is the main loop of each vcpu.
47 */
48static int hf_vcpu_thread(void *data)
49{
50 struct hf_vcpu *vcpu = data;
51 long ret;
52
53 hrtimer_init(&vcpu->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
54 vcpu->timer.function = &hf_vcpu_timer_expired;
55
56 while (!kthread_should_stop()) {
57 unsigned long flags;
58 size_t irqs;
59
60 set_current_state(TASK_RUNNING);
61
62 /* Determine if we must interrupt the vcpu. */
63 spin_lock_irqsave(&vcpu->lock, flags);
64 irqs = vcpu->pending_irq ? 1 : 0;
65 vcpu->pending_irq = false;
66 spin_unlock_irqrestore(&vcpu->lock, flags);
67
68 /* Call into hafnium to run vcpu. */
69 ret = hf_hvc(HF_VCPU_RUN, vcpu->vm_index, vcpu->vcpu_index,
70 irqs);
71
72 /* A negative return value indicates that this vcpu needs to
73 * sleep for the given number of nanoseconds.
74 */
75 if (ret < 0) {
76 set_current_state(TASK_INTERRUPTIBLE);
77 if (kthread_should_stop())
78 break;
79 hrtimer_start(&vcpu->timer, -ret, HRTIMER_MODE_REL);
80 schedule();
81 hrtimer_cancel(&vcpu->timer);
82 continue;
83 }
84
85 /* TODO: Use constants below. */
86 switch ((u8)ret) {
87 case 0x0: /* Yield (forcibly or voluntarily). */
88 break;
89
90 case 0x01: /* WFI. */
91 set_current_state(TASK_INTERRUPTIBLE);
92 if (kthread_should_stop())
93 break;
94 schedule();
95 break;
96
97 case 0x02: /* Wake up another vcpu. */
98 {
99 struct hf_vm *vm = hf_vms + vcpu->vm_index;
100 long target = ret >> 8;
101 if (target < vm->vcpu_count)
102 wake_up_process(vm->vcpu[target].task);
103 }
104 break;
105 }
106 }
107
108 set_current_state(TASK_RUNNING);
109
110 return 0;
111}
112
113/**
114 * Frees all resources, including threads, associated with the hafnium driver.
115 */
116static void hf_free_resources(long vm_count)
117{
118 long i, j;
119
120 /*
121 * First stop all worker threads. We need to do this before freeing
122 * resources because workers may reference each other, so it is only
123 * safe to free resources after they have all stopped.
124 */
125 for (i = 0; i < vm_count; i++) {
126 struct hf_vm *vm = hf_vms + i;
127 for (j = 0; j < vm->vcpu_count; j++)
128 kthread_stop(vm->vcpu[j].task);
129 }
130
131 /* Free resources. */
132 for (i = 0; i < vm_count; i++) {
133 struct hf_vm *vm = hf_vms + i;
134 for (j = 0; j < vm->vcpu_count; j++)
135 put_task_struct(vm->vcpu[j].task);
136 kfree(vm->vcpu);
137 }
138
139 kfree(hf_vms);
140}
141
142static ssize_t hf_interrupt_store(struct kobject *kobj,
143 struct kobj_attribute *attr, const char *buf,
144 size_t count)
145{
146 struct hf_vcpu *vcpu;
147 unsigned long flags;
148 struct task_struct *task;
149
150 /* TODO: Parse input to determine which vcpu to interrupt. */
151 pr_info("Interrupting the first vcpu of the first vm\n");
152 /* TODO: Check bounds. */
153
154 vcpu = hf_vms[0].vcpu + 0;
155
156 spin_lock_irqsave(&vcpu->lock, flags);
157 vcpu->pending_irq = true;
158 /* TODO: Do we need to increment the task's ref count here? */
159 task = vcpu->task;
160 spin_unlock_irqrestore(&vcpu->lock, flags);
161
162 /* Wake up the task. If it's already running, kick it out. */
163 /* TODO: There's a race here: the kick may happen right before we go
164 * to the hypervisor. */
165 if (wake_up_process(task) == 0)
166 kick_process(task);
167
168 return count;
169}
170
171static struct kobject *hf_sysfs_obj = NULL;
172static struct kobj_attribute interrupt_attr =
173 __ATTR(interrupt, 0200, NULL, hf_interrupt_store);
174
175/**
176 * Initializes the hafnium driver by creating a thread for each vCPU of each
177 * virtual machine.
178 */
179static int __init hf_init(void)
180{
181 long ret;
182 long i, j;
183
184 /* Get the number of VMs and allocate storage for them. */
185 ret = hf_hvc(HF_VM_GET_COUNT, 0, 0, 0);
186 if (ret < 0) {
187 pr_err("Unable to retrieve number of VMs: %ld\n", ret);
188 return ret;
189 }
190
191 hf_vm_count = ret;
192 hf_vms = kmalloc(sizeof(struct hf_vm) * hf_vm_count, GFP_KERNEL);
193 if (!hf_vms)
194 return -ENOMEM;
195
196 /* Initialize each VM. */
197 for (i = 0; i < hf_vm_count; i++) {
198 struct hf_vm *vm = hf_vms + i;
199
200 ret = hf_hvc(HF_VCPU_GET_COUNT, i, 0, 0);
201 if (ret < 0) {
202 pr_err("HF_VCPU_GET_COUNT failed for vm=%ld: %ld", i,
203 ret);
204 hf_free_resources(i);
205 return ret;
206 }
207
208 vm->vcpu_count = ret;
209 vm->vcpu = kmalloc(sizeof(struct hf_vcpu) * vm->vcpu_count,
210 GFP_KERNEL);
211 if (!vm->vcpu) {
212 pr_err("No memory for %ld vcpus for vm %ld",
213 vm->vcpu_count, i);
214 hf_free_resources(i);
215 return -ENOMEM;
216 }
217
218 /* Create a kernel thread for each vcpu. */
219 for (j = 0; j < vm->vcpu_count; j++) {
220 struct hf_vcpu *vcpu = vm->vcpu + j;
221 vcpu->task = kthread_create(hf_vcpu_thread, vcpu,
222 "vcpu_thread_%ld_%ld",
223 i, j);
224 if (IS_ERR(vcpu->task)) {
225 pr_err("Error creating task (vm=%ld,vcpu=%ld)"
226 ": %ld\n", i, j, PTR_ERR(vcpu->task));
227 vm->vcpu_count = j;
228 hf_free_resources(i + 1);
229 return PTR_ERR(vcpu->task);
230 }
231
232 get_task_struct(vcpu->task);
233 spin_lock_init(&vcpu->lock);
234 vcpu->vm_index = i;
235 vcpu->vcpu_index = j;
236 vcpu->pending_irq = false;
237 }
238 }
239
240 /* Start running threads now that all is initialized. */
241 for (i = 0; i < hf_vm_count; i++) {
242 struct hf_vm *vm = hf_vms + i;
243 for (j = 0; j < vm->vcpu_count; j++)
244 wake_up_process(vm->vcpu[j].task);
245 }
246
247 /* Dump vm/vcpu count info. */
248 pr_info("Hafnium successfully loaded with %ld VMs:\n", hf_vm_count);
249 for (i = 0; i < hf_vm_count; i++)
250 pr_info("\tVM %ld: %ld vCPUS\n", i, hf_vms[i].vcpu_count);
251
252 /* Create the sysfs interface to interrupt vcpus. */
253 hf_sysfs_obj = kobject_create_and_add("hafnium", kernel_kobj);
254 if (!hf_sysfs_obj) {
255 pr_err("Unable to create sysfs object");
256 } else {
257 ret = sysfs_create_file(hf_sysfs_obj, &interrupt_attr.attr);
258 if (ret)
259 pr_err("Unable to create 'interrupt' sysfs file");
260 }
261
262 return 0;
263}
264
265/**
266 * Frees up all resources used by the hafnium driver in preparation for
267 * unloading it.
268 */
269static void __exit hf_exit(void)
270{
271 if (hf_sysfs_obj)
272 kobject_put(hf_sysfs_obj);
273
274 pr_info("Preparing to unload hafnium\n");
275 hf_free_resources(hf_vm_count);
276 pr_info("Hafnium ready to unload\n");
277}
278
279MODULE_LICENSE("GPL");
280
281module_init(hf_init);
282module_exit(hf_exit);