Implementing Original Hypercalls
This is a tutorial of implementing new hypercalls.
System Setup
- CPU: x86_64
- Hypervisor: Linux KVM 5.8.13
- Guest: Linux 5.8.13
Step-by-step instructions
Add new hypercall entry in KVM
Register new hypercall number at the include/uapi/linux/kvm_para.h
.
#define KVM_HC_CLOCK_PAIRING 9
#define KVM_HC_SEND_IPI 10
#define KVM_HC_SCHED_YIELD 11
+#define KVM_HC_HELLO_HYPERCALL0 12
+#define KVM_HC_HELLO_HYPERCALL1 13
/*
* hypercalls use architecture specific
Hypercall occurs VMCALL that is one of the reason of VM Exit. The handler of VMCALL eventually calls kvm_emulate_hypercall()
in arch/x86/kvm/x86.c
. You can add hypercall by modifying the switch statement in it as follows.
kvm_sched_yield(vcpu->kvm, a0);
ret = 0;
break;
+ case KVM_HC_HELLO_HYPERCALL0:
+ trace_printk("Hello world!\n");
+ break;
+ case KVM_HC_HELLO_HYPERCALL1:
+ trace_printk("a0 = %d\n", a0);
+ break;
default:
ret = -KVM_ENOSYS;
break;
Implementing systemcall to call hypercall
A hypercall can be called using kvm_hypercall0()
and so on as follows. The maximum number of arguments is 4.
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <uapi/linux/kvm_para.h>
#include "my_syscalls.h"
SYSCALL_DEFINE0(hello_hypercall0)
{
kvm_hypercall0(KVM_HC_HELLO_HYPERCALL0);
return 0;
}
SYSCALL_DEFINE1(hello_hypercall1, int, arg1)
{
kvm_hypercall1(KVM_HC_HELLO_HYPERCALL1, arg1);
return 0;
}
You need to add a system call to call kvm_hypercallk()
since it cannot be called directly from userspace. The following patch adds a system call to call a simple hypercall.
diff --git a/Makefile b/Makefile
index 58283e912b4e..2f6191b70d84 100644
--- a/Makefile
+++ b/Makefile
@@ -1066,7 +1066,7 @@ export MODORDER := $(extmod-prefix)modules.order
export MODULES_NSDEPS := $(extmod-prefix)modules.nsdeps
ifeq ($(KBUILD_EXTMOD),)
-core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
+core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ my_syscalls/
vmlinux-dirs := $(patsubst %/,%,$(filter %/, \
$(core-y) $(core-m) $(drivers-y) $(drivers-m) \
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 78847b32e137..e8ce133372d2 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -360,6 +360,8 @@
437 common openat2 sys_openat2
438 common pidfd_getfd sys_pidfd_getfd
439 common faccessat2 sys_faccessat2
+440 common hello_hypercall0 sys_hello_hypercall0
+441 common hello_hypercall1 sys_hello_hypercall1
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index b951a87da987..65f480aed5ac 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1424,4 +1424,8 @@ long compat_ksys_semtimedop(int semid, struct sembuf __user *tsems,
unsigned int nsops,
const struct old_timespec32 __user *timeout);
+/* my_syscalls/my_syscalls.c */
+asmlinkage long sys_hello_hypercall0(void);
+asmlinkage long sys_hello_hypercall1(int arg1);
+
#endif
diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h
index 8b86609849b9..614cc87f38ec 100644
--- a/include/uapi/linux/kvm_para.h
+++ b/include/uapi/linux/kvm_para.h
@@ -29,6 +29,8 @@
#define KVM_HC_CLOCK_PAIRING 9
#define KVM_HC_SEND_IPI 10
#define KVM_HC_SCHED_YIELD 11
+#define KVM_HC_HELLO_HYPERCALL0 12
+#define KVM_HC_HELLO_HYPERCALL1 13
/*
* hypercalls use architecture specific
diff --git a/my_syscalls/Makefile b/my_syscalls/Makefile
new file mode 100644
index 000000000000..e7e211791186
--- /dev/null
+++ b/my_syscalls/Makefile
@@ -0,0 +1 @@
+obj-y:=my_syscalls.o
diff --git a/my_syscalls/my_syscalls.c b/my_syscalls/my_syscalls.c
new file mode 100644
index 000000000000..0989c604cbd4
--- /dev/null
+++ b/my_syscalls/my_syscalls.c
@@ -0,0 +1,15 @@
+#include <linux/kernel.h>
+#include <linux/syscalls.h>
+#include <uapi/linux/kvm_para.h>
+#include "my_syscalls.h"
+
+SYSCALL_DEFINE0(hello_hypercall0)
+{
+ kvm_hypercall0(KVM_HC_HELLO_HYPERCALL0);
+ return 0;
+}
+
+SYSCALL_DEFINE1(hello_hypercall1, int, arg1)
+{
+ kvm_hypercall1(KVM_HC_HELLO_HYPERCALL1, arg1);
+ return 0;
+}
diff --git a/my_syscalls/my_syscalls.h b/my_syscalls/my_syscalls.h
new file mode 100644
index 000000000000..04bf6fbd4efb
--- /dev/null
+++ b/my_syscalls/my_syscalls.h
@@ -0,0 +1,2 @@
+asmlinkage long hello_hypercall0(void);
+asmlinkage long hello_hypercall1(int arg1);
Test
Call hypercall from guest:
#include <stdio.h>
#include <sys/syscall.h>
#define HELLO_HYPERCALL0 440
#define HELLO_HYPERCALL1 441
int main(int argc, char **argv)
{
syscall(440);
syscall(441, 123);
return 0;
}
Check log on host:
user@host ~ % sudo cat /sys/kernel/tracing/trace
# tracer: nop
#
# entries-in-buffer/entries-written: 2/2 #P:6
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
CPU 1/KVM-1748 [000] .... 457.868063: kvm_emulate_hypercall: Hello world!
CPU 1/KVM-1748 [000] .... 457.868066: kvm_emulate_hypercall: a0 = 123