New Syscall

Adding a new system call

The following instruction shows how to add new syscall the the hypervisor API to extend it’s functionality and provide guest with additional kernel-side services.

As an example we will use l4_console call, which is meant to print a text on a kernel console. It’s especially useful for guest that don’t have an access to any real serial port, or during debugging of very early stages of guest boot process, where serial console output is not yet available.

To make locating the mentioned code that requires modifications easy, parts of the uniform diff patch outputs will be presented. Please note that the code is subject of continuous development so the outputs presented here may not exact correspond to future versions of Codezero.

Good Philosophy: Keep the API small

Although it may seem appealing to introduce new API call each time new functionality is to be added, it’s good practice to carefully examine if there’s really a need to add anything to hypervisor API.

The hypervisor code is meant to be minimal and it’s API to be small and generic. Smaller trusted code base means greater code re-use, less bugs and greater performance.

Any non-essential functionality should most likely be implemented as a part of guest, not kernel. In many cases it’s better to add new flags to existing API calls, where new function logically fits existing one.

Syscall API

The first step is to decide on the API function signature and writing it in the API header file include/api/l4/arch/arm/syscalls.h, like the following:

--- a/include/api/l4/arch/arm/syscalls.h
+++ b/include/api/l4/arch/arm/syscalls.h
@@ -37,6 +37,11 @@ typedef int (*__l4_ipc_t)(l4id_t to, l4id_t from, l4_u32 flags);
 extern __l4_ipc_t __l4_ipc;
 int l4_ipc(l4id_t to, l4id_t from, l4_u32 flags);

+typedef int (*__l4_console_t)(void* buf, l4_u32 len);
+extern __l4_console_t __l4_console;
+int l4_console(void* buf, l4_u32 len);
+
+
 typedef int (*__l4_capability_control_t)(unsigned int req, unsigned int flags, void *buf);
 extern __l4_capability_control_t __l4_capability_control;
 int l4_capability_control(unsigned int req, unsigned int flags, void *buf);

The first line defines the function pointer type that we will use in other places.

The pointers to all API call locations are passed on runtime in the struct l4_kip. Each new syscall must be added there, too.

--- a/include/api/l4/api/kip.h
+++ b/include/api/l4/api/kip.h
@@ -64,6 +64,7 @@ struct l4_kip {
        l4_u32 getid;
        l4_u32 mutex_control;
        l4_u32 cache_control;
+       l4_u32 console;

        l4_u32 arch_syscall0;
        l4_u32 arch_syscall1;

Syscall implementation

The address passed in struct l4_kip is an address to a arm_system_calls jump-table entry, which is implemented in src/arch/arm/syscall.S file. New API call must have an entry added there, too.

--- a/src/arch/arm/syscall.S
+++ b/src/arch/arm/syscall.S
@@ -47,5 +47,7 @@ BEGIN_PROC(arm_system_calls)
        mov     pc, lr
        swi     0x14            @ cache_control
        mov     pc, lr
+       swi     0x14            @ console
+       mov     pc, lr

Entries in arm_system_calls are numbered and corresponding indexes are stored placed in include/l4/api/syscall.h file.

--- a/include/l4/api/syscall.h
+++ b/include/l4/api/syscall.h
@@ -28,6 +28,7 @@
 #define sys_time_no                            12
 #define sys_mutex_control_no                   13
 #define sys_cache_control_no                   14
+#define sys_console_no                         15

 #define        sys_ipc_offset                          0x0
 #define sys_context_switch_offset              0x8
@@ -44,10 +45,11 @@
 #define sys_time_offset                                0x60
 #define sys_mutex_control_offset               0x68
 #define sys_cache_control_offset               0x70
-#define syscalls_end_offset                    sys_cache_control_offset
+#define sys_console_offset                     0x78
+#define syscalls_end_offset                    sys_console_offset
 #define SYSCALLS_TOTAL                         ((syscalls_end_offset >> syscall_offset_shift) + 1)

-#if SYSCALLS_TOTAL != sys_cache_control_no + 1
+#if SYSCALLS_TOTAL != sys_console_no + 1
 #error "SYSCALLS_TOTAL invalid"
 #endif

@@ -71,4 +73,5 @@ int sys_mutex_control(unsigned long mutex_address, int mutex_op);
 int sys_cache_control(unsigned long start, unsigned long end,
                      unsigned int flags);

+int sys_console(void* buf, unsigned long len);
 #endif /* __SYSCALL_H__ */

As we mentioned, the syscall address in a jump-table must be placed in kip passed to each guest. The offset in the jump-table is used to determine which call has been triggered. All this is implemented in the src/glue/arm/systable.c file.

--- a/src/glue/arm/systable.c
+++ b/src/glue/arm/systable.c
@@ -39,6 +39,7 @@ void kip_init_syscalls(void)
        kip.time = ARM_SYSCALL_PAGE + sys_time_offset;
        kip.mutex_control = ARM_SYSCALL_PAGE + sys_mutex_control_offset;
        kip.cache_control = ARM_SYSCALL_PAGE + sys_cache_control_offset;
+       kip.console = ARM_SYSCALL_PAGE + sys_console_offset;
 }

 /* Jump table for all system calls. */
@@ -141,6 +142,13 @@ int arch_sys_cache_control(cpu_context_t *regs)
                                 (unsigned int)regs->regs[2]);
 }

+int arch_sys_console(cpu_context_t *regs)
+{
+       return sys_console((void*)regs->regs[0],
+                                (unsigned long)regs->regs[1]);
+}
+
+
 /*
  * Initialises the system call jump table, for kernel to use.
  * Also maps the system call page into userspace.
@@ -162,6 +170,7 @@ void syscall_init()
        syscall_table[sys_time_no]                      = (syscall_fn_t)arch_sys_time;
        syscall_table[sys_mutex_control_no]             = (syscall_fn_t)arch_sys_mutex_control;
        syscall_table[sys_cache_control_no]             = (syscall_fn_t)arch_sys_cache_control;
+       syscall_table[sys_console_no]           = (syscall_fn_t)arch_sys_console;

        add_boot_mapping(virt_to_phys(&__syscall_page_start),
                         ARM_SYSCALL_PAGE, PAGE_SIZE, MAP_USR_RX);

The final step is to provide the syscall implementation. Check the src/api/ directory for a reference and src/api/console.c for this particular syscall (l4_console). Also, remember to add any new files to Kbuild files so the building system will take them into account at next build.

Adding userspace side syscall calling code.

Each Codezero guest is for convenience linked with libl4 library, providing common code for accessing hypervisor API.

The actual code that implements jumping to kip-provided jump-table entry must be added:

--- a/libs/libl4/src/arch/arm/syscalls.S
+++ b/libs/libl4/src/arch/arm/syscalls.S
@@ -237,3 +237,17 @@ BEGIN_PROC(l4_cache_control)
        ldr     pc, [r12]
        ldmfd   sp!, {pc}       @ Restore original lr and return.
 END_PROC(l4_cache_control)
+
+/*
+ * System call that prints on hypervisor console.
+ *
+ * @r0 = buffer pointer,
+ * @r1 = buffer len,
+ */
+BEGIN_PROC(l4_console)
+       stmfd   sp!, {lr}
+       ldr     r12, =__l4_console
+       mov     lr, pc
+       ldr     pc, [r12]
+       ldmfd   sp!, {pc}       @ Restore original lr and return.
+END_PROC(l4_console)

The above code uses the address stored in __l4_console variable, which is copied from kip in libs/libl4/src/init.c like this:

--- a/libs/libl4/src/init.c
+++ b/libs/libl4/src/init.c
@@ -24,6 +24,7 @@ __l4_capability_control_t __l4_capability_control = 0;
 __l4_time_t __l4_time = 0;
 __l4_mutex_control_t __l4_mutex_control = 0;
 __l4_cache_control_t __l4_cache_control = 0;
+__l4_console_t __l4_console = 0;

 struct l4_kip *kip;

@@ -61,5 +62,6 @@ void __l4_init(void)
        __l4_time =             (__l4_time_t)kip->time;
        __l4_mutex_control =    (__l4_mutex_control_t)kip->mutex_control;
        __l4_cache_control =    (__l4_cache_control_t)kip->cache_control;
+       __l4_console =  (__l4_console_t)kip->console;
 }

Comments are closed.