1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * ACRN Hypervisor Service Module (HSM) |
4 | * |
5 | * Copyright (C) 2020 Intel Corporation. All rights reserved. |
6 | * |
7 | * Authors: |
8 | * Fengwei Yin <fengwei.yin@intel.com> |
9 | * Yakui Zhao <yakui.zhao@intel.com> |
10 | */ |
11 | |
12 | #include <linux/cpu.h> |
13 | #include <linux/io.h> |
14 | #include <linux/mm.h> |
15 | #include <linux/module.h> |
16 | #include <linux/slab.h> |
17 | |
18 | #include <asm/acrn.h> |
19 | #include <asm/hypervisor.h> |
20 | |
21 | #include "acrn_drv.h" |
22 | |
23 | /* |
24 | * When /dev/acrn_hsm is opened, a 'struct acrn_vm' object is created to |
25 | * represent a VM instance and continues to be associated with the opened file |
26 | * descriptor. All ioctl operations on this file descriptor will be targeted to |
27 | * the VM instance. Release of this file descriptor will destroy the object. |
28 | */ |
29 | static int acrn_dev_open(struct inode *inode, struct file *filp) |
30 | { |
31 | struct acrn_vm *vm; |
32 | |
33 | vm = kzalloc(size: sizeof(*vm), GFP_KERNEL); |
34 | if (!vm) |
35 | return -ENOMEM; |
36 | |
37 | vm->vmid = ACRN_INVALID_VMID; |
38 | filp->private_data = vm; |
39 | return 0; |
40 | } |
41 | |
42 | static int pmcmd_ioctl(u64 cmd, void __user *uptr) |
43 | { |
44 | struct acrn_pstate_data *px_data; |
45 | struct acrn_cstate_data *cx_data; |
46 | u64 *pm_info; |
47 | int ret = 0; |
48 | |
49 | switch (cmd & PMCMD_TYPE_MASK) { |
50 | case ACRN_PMCMD_GET_PX_CNT: |
51 | case ACRN_PMCMD_GET_CX_CNT: |
52 | pm_info = kmalloc(size: sizeof(u64), GFP_KERNEL); |
53 | if (!pm_info) |
54 | return -ENOMEM; |
55 | |
56 | ret = hcall_get_cpu_state(cmd, virt_to_phys(address: pm_info)); |
57 | if (ret < 0) { |
58 | kfree(objp: pm_info); |
59 | break; |
60 | } |
61 | |
62 | if (copy_to_user(to: uptr, from: pm_info, n: sizeof(u64))) |
63 | ret = -EFAULT; |
64 | kfree(objp: pm_info); |
65 | break; |
66 | case ACRN_PMCMD_GET_PX_DATA: |
67 | px_data = kmalloc(size: sizeof(*px_data), GFP_KERNEL); |
68 | if (!px_data) |
69 | return -ENOMEM; |
70 | |
71 | ret = hcall_get_cpu_state(cmd, virt_to_phys(address: px_data)); |
72 | if (ret < 0) { |
73 | kfree(objp: px_data); |
74 | break; |
75 | } |
76 | |
77 | if (copy_to_user(to: uptr, from: px_data, n: sizeof(*px_data))) |
78 | ret = -EFAULT; |
79 | kfree(objp: px_data); |
80 | break; |
81 | case ACRN_PMCMD_GET_CX_DATA: |
82 | cx_data = kmalloc(size: sizeof(*cx_data), GFP_KERNEL); |
83 | if (!cx_data) |
84 | return -ENOMEM; |
85 | |
86 | ret = hcall_get_cpu_state(cmd, virt_to_phys(address: cx_data)); |
87 | if (ret < 0) { |
88 | kfree(objp: cx_data); |
89 | break; |
90 | } |
91 | |
92 | if (copy_to_user(to: uptr, from: cx_data, n: sizeof(*cx_data))) |
93 | ret = -EFAULT; |
94 | kfree(objp: cx_data); |
95 | break; |
96 | default: |
97 | break; |
98 | } |
99 | |
100 | return ret; |
101 | } |
102 | |
103 | /* |
104 | * HSM relies on hypercall layer of the ACRN hypervisor to do the |
105 | * sanity check against the input parameters. |
106 | */ |
107 | static long acrn_dev_ioctl(struct file *filp, unsigned int cmd, |
108 | unsigned long ioctl_param) |
109 | { |
110 | struct acrn_vm *vm = filp->private_data; |
111 | struct acrn_vm_creation *vm_param; |
112 | struct acrn_vcpu_regs *cpu_regs; |
113 | struct acrn_ioreq_notify notify; |
114 | struct acrn_ptdev_irq *irq_info; |
115 | struct acrn_ioeventfd ioeventfd; |
116 | struct acrn_vm_memmap memmap; |
117 | struct acrn_mmiodev *mmiodev; |
118 | struct acrn_msi_entry *msi; |
119 | struct acrn_pcidev *pcidev; |
120 | struct acrn_irqfd irqfd; |
121 | struct acrn_vdev *vdev; |
122 | struct page *page; |
123 | u64 cstate_cmd; |
124 | int i, ret = 0; |
125 | |
126 | if (vm->vmid == ACRN_INVALID_VMID && cmd != ACRN_IOCTL_CREATE_VM) { |
127 | dev_dbg(acrn_dev.this_device, |
128 | "ioctl 0x%x: Invalid VM state!\n" , cmd); |
129 | return -EINVAL; |
130 | } |
131 | |
132 | switch (cmd) { |
133 | case ACRN_IOCTL_CREATE_VM: |
134 | vm_param = memdup_user((void __user *)ioctl_param, |
135 | sizeof(struct acrn_vm_creation)); |
136 | if (IS_ERR(ptr: vm_param)) |
137 | return PTR_ERR(ptr: vm_param); |
138 | |
139 | if ((vm_param->reserved0 | vm_param->reserved1) != 0) { |
140 | kfree(objp: vm_param); |
141 | return -EINVAL; |
142 | } |
143 | |
144 | vm = acrn_vm_create(vm, vm_param); |
145 | if (!vm) { |
146 | ret = -EINVAL; |
147 | kfree(objp: vm_param); |
148 | break; |
149 | } |
150 | |
151 | if (copy_to_user(to: (void __user *)ioctl_param, from: vm_param, |
152 | n: sizeof(struct acrn_vm_creation))) { |
153 | acrn_vm_destroy(vm); |
154 | ret = -EFAULT; |
155 | } |
156 | |
157 | kfree(objp: vm_param); |
158 | break; |
159 | case ACRN_IOCTL_START_VM: |
160 | ret = hcall_start_vm(vmid: vm->vmid); |
161 | if (ret < 0) |
162 | dev_dbg(acrn_dev.this_device, |
163 | "Failed to start VM %u!\n" , vm->vmid); |
164 | break; |
165 | case ACRN_IOCTL_PAUSE_VM: |
166 | ret = hcall_pause_vm(vmid: vm->vmid); |
167 | if (ret < 0) |
168 | dev_dbg(acrn_dev.this_device, |
169 | "Failed to pause VM %u!\n" , vm->vmid); |
170 | break; |
171 | case ACRN_IOCTL_RESET_VM: |
172 | ret = hcall_reset_vm(vmid: vm->vmid); |
173 | if (ret < 0) |
174 | dev_dbg(acrn_dev.this_device, |
175 | "Failed to restart VM %u!\n" , vm->vmid); |
176 | break; |
177 | case ACRN_IOCTL_DESTROY_VM: |
178 | ret = acrn_vm_destroy(vm); |
179 | break; |
180 | case ACRN_IOCTL_SET_VCPU_REGS: |
181 | cpu_regs = memdup_user((void __user *)ioctl_param, |
182 | sizeof(struct acrn_vcpu_regs)); |
183 | if (IS_ERR(ptr: cpu_regs)) |
184 | return PTR_ERR(ptr: cpu_regs); |
185 | |
186 | for (i = 0; i < ARRAY_SIZE(cpu_regs->reserved); i++) |
187 | if (cpu_regs->reserved[i]) { |
188 | kfree(objp: cpu_regs); |
189 | return -EINVAL; |
190 | } |
191 | |
192 | for (i = 0; i < ARRAY_SIZE(cpu_regs->vcpu_regs.reserved_32); i++) |
193 | if (cpu_regs->vcpu_regs.reserved_32[i]) { |
194 | kfree(objp: cpu_regs); |
195 | return -EINVAL; |
196 | } |
197 | |
198 | for (i = 0; i < ARRAY_SIZE(cpu_regs->vcpu_regs.reserved_64); i++) |
199 | if (cpu_regs->vcpu_regs.reserved_64[i]) { |
200 | kfree(objp: cpu_regs); |
201 | return -EINVAL; |
202 | } |
203 | |
204 | for (i = 0; i < ARRAY_SIZE(cpu_regs->vcpu_regs.gdt.reserved); i++) |
205 | if (cpu_regs->vcpu_regs.gdt.reserved[i] | |
206 | cpu_regs->vcpu_regs.idt.reserved[i]) { |
207 | kfree(objp: cpu_regs); |
208 | return -EINVAL; |
209 | } |
210 | |
211 | ret = hcall_set_vcpu_regs(vmid: vm->vmid, virt_to_phys(address: cpu_regs)); |
212 | if (ret < 0) |
213 | dev_dbg(acrn_dev.this_device, |
214 | "Failed to set regs state of VM%u!\n" , |
215 | vm->vmid); |
216 | kfree(objp: cpu_regs); |
217 | break; |
218 | case ACRN_IOCTL_SET_MEMSEG: |
219 | if (copy_from_user(to: &memmap, from: (void __user *)ioctl_param, |
220 | n: sizeof(memmap))) |
221 | return -EFAULT; |
222 | |
223 | ret = acrn_vm_memseg_map(vm, memmap: &memmap); |
224 | break; |
225 | case ACRN_IOCTL_UNSET_MEMSEG: |
226 | if (copy_from_user(to: &memmap, from: (void __user *)ioctl_param, |
227 | n: sizeof(memmap))) |
228 | return -EFAULT; |
229 | |
230 | ret = acrn_vm_memseg_unmap(vm, memmap: &memmap); |
231 | break; |
232 | case ACRN_IOCTL_ASSIGN_MMIODEV: |
233 | mmiodev = memdup_user((void __user *)ioctl_param, |
234 | sizeof(struct acrn_mmiodev)); |
235 | if (IS_ERR(ptr: mmiodev)) |
236 | return PTR_ERR(ptr: mmiodev); |
237 | |
238 | ret = hcall_assign_mmiodev(vmid: vm->vmid, virt_to_phys(address: mmiodev)); |
239 | if (ret < 0) |
240 | dev_dbg(acrn_dev.this_device, |
241 | "Failed to assign MMIO device!\n" ); |
242 | kfree(objp: mmiodev); |
243 | break; |
244 | case ACRN_IOCTL_DEASSIGN_MMIODEV: |
245 | mmiodev = memdup_user((void __user *)ioctl_param, |
246 | sizeof(struct acrn_mmiodev)); |
247 | if (IS_ERR(ptr: mmiodev)) |
248 | return PTR_ERR(ptr: mmiodev); |
249 | |
250 | ret = hcall_deassign_mmiodev(vmid: vm->vmid, virt_to_phys(address: mmiodev)); |
251 | if (ret < 0) |
252 | dev_dbg(acrn_dev.this_device, |
253 | "Failed to deassign MMIO device!\n" ); |
254 | kfree(objp: mmiodev); |
255 | break; |
256 | case ACRN_IOCTL_ASSIGN_PCIDEV: |
257 | pcidev = memdup_user((void __user *)ioctl_param, |
258 | sizeof(struct acrn_pcidev)); |
259 | if (IS_ERR(ptr: pcidev)) |
260 | return PTR_ERR(ptr: pcidev); |
261 | |
262 | ret = hcall_assign_pcidev(vmid: vm->vmid, virt_to_phys(address: pcidev)); |
263 | if (ret < 0) |
264 | dev_dbg(acrn_dev.this_device, |
265 | "Failed to assign pci device!\n" ); |
266 | kfree(objp: pcidev); |
267 | break; |
268 | case ACRN_IOCTL_DEASSIGN_PCIDEV: |
269 | pcidev = memdup_user((void __user *)ioctl_param, |
270 | sizeof(struct acrn_pcidev)); |
271 | if (IS_ERR(ptr: pcidev)) |
272 | return PTR_ERR(ptr: pcidev); |
273 | |
274 | ret = hcall_deassign_pcidev(vmid: vm->vmid, virt_to_phys(address: pcidev)); |
275 | if (ret < 0) |
276 | dev_dbg(acrn_dev.this_device, |
277 | "Failed to deassign pci device!\n" ); |
278 | kfree(objp: pcidev); |
279 | break; |
280 | case ACRN_IOCTL_CREATE_VDEV: |
281 | vdev = memdup_user((void __user *)ioctl_param, |
282 | sizeof(struct acrn_vdev)); |
283 | if (IS_ERR(ptr: vdev)) |
284 | return PTR_ERR(ptr: vdev); |
285 | |
286 | ret = hcall_create_vdev(vmid: vm->vmid, virt_to_phys(address: vdev)); |
287 | if (ret < 0) |
288 | dev_dbg(acrn_dev.this_device, |
289 | "Failed to create virtual device!\n" ); |
290 | kfree(objp: vdev); |
291 | break; |
292 | case ACRN_IOCTL_DESTROY_VDEV: |
293 | vdev = memdup_user((void __user *)ioctl_param, |
294 | sizeof(struct acrn_vdev)); |
295 | if (IS_ERR(ptr: vdev)) |
296 | return PTR_ERR(ptr: vdev); |
297 | ret = hcall_destroy_vdev(vmid: vm->vmid, virt_to_phys(address: vdev)); |
298 | if (ret < 0) |
299 | dev_dbg(acrn_dev.this_device, |
300 | "Failed to destroy virtual device!\n" ); |
301 | kfree(objp: vdev); |
302 | break; |
303 | case ACRN_IOCTL_SET_PTDEV_INTR: |
304 | irq_info = memdup_user((void __user *)ioctl_param, |
305 | sizeof(struct acrn_ptdev_irq)); |
306 | if (IS_ERR(ptr: irq_info)) |
307 | return PTR_ERR(ptr: irq_info); |
308 | |
309 | ret = hcall_set_ptdev_intr(vmid: vm->vmid, virt_to_phys(address: irq_info)); |
310 | if (ret < 0) |
311 | dev_dbg(acrn_dev.this_device, |
312 | "Failed to configure intr for ptdev!\n" ); |
313 | kfree(objp: irq_info); |
314 | break; |
315 | case ACRN_IOCTL_RESET_PTDEV_INTR: |
316 | irq_info = memdup_user((void __user *)ioctl_param, |
317 | sizeof(struct acrn_ptdev_irq)); |
318 | if (IS_ERR(ptr: irq_info)) |
319 | return PTR_ERR(ptr: irq_info); |
320 | |
321 | ret = hcall_reset_ptdev_intr(vmid: vm->vmid, virt_to_phys(address: irq_info)); |
322 | if (ret < 0) |
323 | dev_dbg(acrn_dev.this_device, |
324 | "Failed to reset intr for ptdev!\n" ); |
325 | kfree(objp: irq_info); |
326 | break; |
327 | case ACRN_IOCTL_SET_IRQLINE: |
328 | ret = hcall_set_irqline(vmid: vm->vmid, op: ioctl_param); |
329 | if (ret < 0) |
330 | dev_dbg(acrn_dev.this_device, |
331 | "Failed to set interrupt line!\n" ); |
332 | break; |
333 | case ACRN_IOCTL_INJECT_MSI: |
334 | msi = memdup_user((void __user *)ioctl_param, |
335 | sizeof(struct acrn_msi_entry)); |
336 | if (IS_ERR(ptr: msi)) |
337 | return PTR_ERR(ptr: msi); |
338 | |
339 | ret = hcall_inject_msi(vmid: vm->vmid, virt_to_phys(address: msi)); |
340 | if (ret < 0) |
341 | dev_dbg(acrn_dev.this_device, |
342 | "Failed to inject MSI!\n" ); |
343 | kfree(objp: msi); |
344 | break; |
345 | case ACRN_IOCTL_VM_INTR_MONITOR: |
346 | ret = pin_user_pages_fast(start: ioctl_param, nr_pages: 1, |
347 | gup_flags: FOLL_WRITE | FOLL_LONGTERM, pages: &page); |
348 | if (unlikely(ret != 1)) { |
349 | dev_dbg(acrn_dev.this_device, |
350 | "Failed to pin intr hdr buffer!\n" ); |
351 | return -EFAULT; |
352 | } |
353 | |
354 | ret = hcall_vm_intr_monitor(vmid: vm->vmid, page_to_phys(page)); |
355 | if (ret < 0) { |
356 | unpin_user_page(page); |
357 | dev_dbg(acrn_dev.this_device, |
358 | "Failed to monitor intr data!\n" ); |
359 | return ret; |
360 | } |
361 | if (vm->monitor_page) |
362 | unpin_user_page(page: vm->monitor_page); |
363 | vm->monitor_page = page; |
364 | break; |
365 | case ACRN_IOCTL_CREATE_IOREQ_CLIENT: |
366 | if (vm->default_client) |
367 | return -EEXIST; |
368 | if (!acrn_ioreq_client_create(vm, NULL, NULL, is_default: true, name: "acrndm" )) |
369 | ret = -EINVAL; |
370 | break; |
371 | case ACRN_IOCTL_DESTROY_IOREQ_CLIENT: |
372 | if (vm->default_client) |
373 | acrn_ioreq_client_destroy(client: vm->default_client); |
374 | break; |
375 | case ACRN_IOCTL_ATTACH_IOREQ_CLIENT: |
376 | if (vm->default_client) |
377 | ret = acrn_ioreq_client_wait(client: vm->default_client); |
378 | else |
379 | ret = -ENODEV; |
380 | break; |
381 | case ACRN_IOCTL_NOTIFY_REQUEST_FINISH: |
382 | if (copy_from_user(to: ¬ify, from: (void __user *)ioctl_param, |
383 | n: sizeof(struct acrn_ioreq_notify))) |
384 | return -EFAULT; |
385 | |
386 | if (notify.reserved != 0) |
387 | return -EINVAL; |
388 | |
389 | ret = acrn_ioreq_request_default_complete(vm, vcpu: notify.vcpu); |
390 | break; |
391 | case ACRN_IOCTL_CLEAR_VM_IOREQ: |
392 | acrn_ioreq_request_clear(vm); |
393 | break; |
394 | case ACRN_IOCTL_PM_GET_CPU_STATE: |
395 | if (copy_from_user(to: &cstate_cmd, from: (void __user *)ioctl_param, |
396 | n: sizeof(cstate_cmd))) |
397 | return -EFAULT; |
398 | |
399 | ret = pmcmd_ioctl(cmd: cstate_cmd, uptr: (void __user *)ioctl_param); |
400 | break; |
401 | case ACRN_IOCTL_IOEVENTFD: |
402 | if (copy_from_user(to: &ioeventfd, from: (void __user *)ioctl_param, |
403 | n: sizeof(ioeventfd))) |
404 | return -EFAULT; |
405 | |
406 | if (ioeventfd.reserved != 0) |
407 | return -EINVAL; |
408 | |
409 | ret = acrn_ioeventfd_config(vm, args: &ioeventfd); |
410 | break; |
411 | case ACRN_IOCTL_IRQFD: |
412 | if (copy_from_user(to: &irqfd, from: (void __user *)ioctl_param, |
413 | n: sizeof(irqfd))) |
414 | return -EFAULT; |
415 | ret = acrn_irqfd_config(vm, args: &irqfd); |
416 | break; |
417 | default: |
418 | dev_dbg(acrn_dev.this_device, "Unknown IOCTL 0x%x!\n" , cmd); |
419 | ret = -ENOTTY; |
420 | } |
421 | |
422 | return ret; |
423 | } |
424 | |
425 | static int acrn_dev_release(struct inode *inode, struct file *filp) |
426 | { |
427 | struct acrn_vm *vm = filp->private_data; |
428 | |
429 | acrn_vm_destroy(vm); |
430 | kfree(objp: vm); |
431 | return 0; |
432 | } |
433 | |
434 | static ssize_t remove_cpu_store(struct device *dev, |
435 | struct device_attribute *attr, |
436 | const char *buf, size_t count) |
437 | { |
438 | u64 cpu, lapicid; |
439 | int ret; |
440 | |
441 | if (kstrtoull(s: buf, base: 0, res: &cpu) < 0) |
442 | return -EINVAL; |
443 | |
444 | if (cpu >= num_possible_cpus() || cpu == 0 || !cpu_is_hotpluggable(cpu)) |
445 | return -EINVAL; |
446 | |
447 | if (cpu_online(cpu)) |
448 | remove_cpu(cpu); |
449 | |
450 | lapicid = cpu_data(cpu).topo.apicid; |
451 | dev_dbg(dev, "Try to remove cpu %lld with lapicid %lld\n" , cpu, lapicid); |
452 | ret = hcall_sos_remove_cpu(cpu: lapicid); |
453 | if (ret < 0) { |
454 | dev_err(dev, "Failed to remove cpu %lld!\n" , cpu); |
455 | goto fail_remove; |
456 | } |
457 | |
458 | return count; |
459 | |
460 | fail_remove: |
461 | add_cpu(cpu); |
462 | return ret; |
463 | } |
464 | static DEVICE_ATTR_WO(remove_cpu); |
465 | |
466 | static umode_t acrn_attr_visible(struct kobject *kobj, struct attribute *a, int n) |
467 | { |
468 | if (a == &dev_attr_remove_cpu.attr) |
469 | return IS_ENABLED(CONFIG_HOTPLUG_CPU) ? a->mode : 0; |
470 | |
471 | return a->mode; |
472 | } |
473 | |
474 | static struct attribute *acrn_attrs[] = { |
475 | &dev_attr_remove_cpu.attr, |
476 | NULL |
477 | }; |
478 | |
479 | static struct attribute_group acrn_attr_group = { |
480 | .attrs = acrn_attrs, |
481 | .is_visible = acrn_attr_visible, |
482 | }; |
483 | |
484 | static const struct attribute_group *acrn_attr_groups[] = { |
485 | &acrn_attr_group, |
486 | NULL |
487 | }; |
488 | |
489 | static const struct file_operations acrn_fops = { |
490 | .owner = THIS_MODULE, |
491 | .open = acrn_dev_open, |
492 | .release = acrn_dev_release, |
493 | .unlocked_ioctl = acrn_dev_ioctl, |
494 | }; |
495 | |
496 | struct miscdevice acrn_dev = { |
497 | .minor = MISC_DYNAMIC_MINOR, |
498 | .name = "acrn_hsm" , |
499 | .fops = &acrn_fops, |
500 | .groups = acrn_attr_groups, |
501 | }; |
502 | |
503 | static int __init hsm_init(void) |
504 | { |
505 | int ret; |
506 | |
507 | if (x86_hyper_type != X86_HYPER_ACRN) |
508 | return -ENODEV; |
509 | |
510 | if (!(cpuid_eax(ACRN_CPUID_FEATURES) & ACRN_FEATURE_PRIVILEGED_VM)) |
511 | return -EPERM; |
512 | |
513 | ret = misc_register(misc: &acrn_dev); |
514 | if (ret) { |
515 | pr_err("Create misc dev failed!\n" ); |
516 | return ret; |
517 | } |
518 | |
519 | ret = acrn_ioreq_intr_setup(); |
520 | if (ret) { |
521 | pr_err("Setup I/O request handler failed!\n" ); |
522 | misc_deregister(misc: &acrn_dev); |
523 | return ret; |
524 | } |
525 | return 0; |
526 | } |
527 | |
528 | static void __exit hsm_exit(void) |
529 | { |
530 | acrn_ioreq_intr_remove(); |
531 | misc_deregister(misc: &acrn_dev); |
532 | } |
533 | module_init(hsm_init); |
534 | module_exit(hsm_exit); |
535 | |
536 | MODULE_AUTHOR("Intel Corporation" ); |
537 | MODULE_LICENSE("GPL" ); |
538 | MODULE_DESCRIPTION("ACRN Hypervisor Service Module (HSM)" ); |
539 | |