Skip to content

System Calls

Andrea Barisani edited this page Oct 29, 2024 · 13 revisions

GoTEE system calls

To provide a meaningful interface between the Trusted Applet (PL0 - user mode) and the Trusted OS (PL1 - system mode), system calls must be implemented.

A system call API defines a convention between the two execution contexts to pass requests, responses and related data using shared memory and/or registers.

On ARM, system calls are issued through a supervisor call, a dedicated exception to request privileged operations.

The GoTEE API implements system calls in the syscall package with two types of operations:

  • basic Go runtime functions needed for bare metal execution (see TamaGo internals)
  • a generic JSON-RPC interface for arbitrary Trusted OS <> Trusted Applet communication

It is important to emphasize that any freestanding user mode executable can implement this system call API, as it is not exclusive to TamaGo unikernels.

Example: Print()

The Print syscall allows user mode to print a string to standard output.

This is necessary as user mode does not typically have direct access to mapped peripherals such as the serial console, therefore this is a privileged operation.

The function is implemented as follows:

// func Print(c byte)
TEXT ·Print(SB),$0-1
	MOVW	$const_SYS_WRITE, R0
	MOVB	c+0(FP), R1

	SWI	$0

	RET

The system call constant is placed in register r0 while the single byte character to be printed is placed in r1, after this is done the supervisor call (SWI) is issued.

The Trusted OS can then inspect the registers and take action as done here:

	switch num := ctx.A0; num {
...
	case syscall.SYS_WRITE:
		print(string(ctx.A1()))

In TamaGo unikernels the printk function must be defined to allow any write to standard output, therefore a user mode TamaGo unikernel simply needs to map the Print syscall to the printk function to have all Go fmt.Print, log.Print and similar functions working as expected.

The applet package does this automatically here:

//go:linkname printk runtime.printk
func printk(c byte) {
	syscall.Print(c)
}

The same approach is used for getting random data or CPU ticks (e.g. timers), all of this is automatically taken care of when importing the applet package.

Example: RPC

To allow easy creation of arbitrary Trusted OS <> Trusted Applet interfaces GoTEE simply uses the standard Go jsonrpc package, used over a connection object that relays requests/responses over the Write and Read system calls rather than conventional TCP/IP networking.

The use of jsonrpc, opposed to plain rpc, is to make easier non-TamaGo Trusted Applet integration (e.g. C, Rust) of the RPC API.

The choice of Go RPC interface allows convenient registration of Trusted OS methods for Trusted Applet use, as the conventional rpc interface still applies.

As an example a structure and its methods such as the following:

type RPC struct{}

func (r *RPC) Echo(in string, out *string) error {
	*out = in
	return nil
}

Can be simply registered for Trusted Applet use:

ta.Server.Register(&RPC{})

Which can then invoke it as follows:

var res string
syscall.Call("RPC.Echo", "hello", &res)

This sample run is taken from the GoTEE example with some additional debugging to show the JSON-RPC payloads:

PL0 requests echo via RPC: hello
PL1 JSON-RPC req: {"method":"RPC.Echo","params":["hello"],"id":0}
PL1 JSON-RPC res: {"id":0,"result":"hello","error":null}
PL0 received echo via RPC: hello

Next

Main OS execution