blob: 597c3fb91286fd0384a00657a215a095641cbcc7 [file] [log] [blame]
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
// Parses syscalls.abigen of Fuchsia's zircon kernel and produces Go
// assembly for calling the system calls.
//
// Regenerate syscall/zx with:
//
// go run mkfuchsia.go -stubs > syscalls_fuchsia.go
// go run mkfuchsia.go -goarch=amd64 > syscalls_fuchsia_amd64.s
// go run mkfuchsia.go -goarch=arm64 > syscalls_fuchsia_arm64.s
// gofmt -w *.go
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"unicode/utf8"
)
var (
stubs = flag.Bool("stubs", false, "print only Go function stubs")
goarch = flag.String("goarch", "amd64", "arch to print asm for")
fuchsiaRoot = flag.String("fuchsia_root", filepath.Join(os.Getenv("HOME"), "fuchsia"), "path to fuchsia root")
)
func main() {
flag.Parse()
sdkArch := ""
switch *goarch {
case "amd64":
sdkArch = "x86_64"
case "arm64":
sdkArch = "aarch64"
default:
log.Fatalf("GOARCH=%s not supported", *goarch)
}
_ = sdkArch
syscallsFile := filepath.Join(*fuchsiaRoot, "/zircon/system/public/zircon/syscalls.abigen")
if args := flag.Args(); len(args) != 0 {
syscallsFile = args[0]
}
b, err := ioutil.ReadFile(syscallsFile)
if err != nil {
log.Fatal(err)
}
p := &parser{
buf: b,
line: 1,
name: syscallsFile,
}
syscalls, err := p.parse()
if err != nil {
log.Fatal(err)
}
buf := new(bytes.Buffer)
if *stubs {
fmt.Fprint(buf, stubsHeader[1:])
for _, call := range syscalls {
fmt.Fprintf(buf, "//go:cgo_import_dynamic vdso_zx_%s zx_%s \"libzircon.so\"\n", call.name, call.name)
}
fmt.Fprint(buf, "\n")
for _, call := range syscalls {
fmt.Fprintf(buf, "//go:linkname vdso_zx_%s vdso_zx_%s\n", call.name, call.name)
}
fmt.Fprint(buf, "\nvar (\n")
for _, call := range syscalls {
fmt.Fprintf(buf, "\tvdso_zx_%s uintptr\n", call.name)
}
fmt.Fprint(buf, ")\n\n")
for _, call := range syscalls {
fmt.Fprint(buf, "//go:noescape\n")
fmt.Fprint(buf, "//go:nosplit\n")
printStub(buf, call)
fmt.Fprint(buf, "\n")
}
} else {
fmt.Fprint(buf, asmHeader[1:])
for _, call := range syscalls {
fmt.Fprint(buf, "// ")
printStub(buf, call)
printAsm(buf, call)
fmt.Fprint(buf, "\n")
}
}
buf.WriteTo(os.Stdout)
}
const stubsHeader = `
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Zircon system calls for the Fuchsia OS.
// Generated by mkfuchsia.go, do not edit.
package zx
import "unsafe"
`
const asmHeader = `
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Zircon system calls for the Fuchsia OS.
// Generated by mkfuchsia.go, do not edit.
#include "textflag.h"
`
func printStub(buf *bytes.Buffer, call zirconSyscallDef) {
fmt.Fprintf(buf, "func %s(", call.sysname())
for i, arg := range call.args {
if arg.atype == (zirconType{}) {
continue
}
if i > 0 {
fmt.Fprint(buf, ", ")
}
fmt.Fprintf(buf, "%s ", arg.aname)
printGoType(buf, arg.atype)
}
fmt.Fprint(buf, ")")
if call.ret != (zirconType{}) {
fmt.Fprint(buf, " ")
printGoType(buf, call.ret)
}
fmt.Fprint(buf, "\n")
}
// amd64RegArgs is the amd64 registers in function argument calling convention order
var amd64RegArgs = []string{"DI", "SI", "DX", "CX", "R8", "R9", "R12", "R13"}
// arm64RegArgs is the arm64 registers in function argument calling convention order
var arm64RegArgs = []string{"R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7"}
// blockingSyscalls is a map of known syscalls which may block.
var blockingSyscalls = map[string]bool{
"port_wait": true,
"object_wait_one": true,
"object_wait_many": true,
}
const ZIRCON_SYSCALL_MAGIC = 0x00ff00ff00000000
func printAsm(buf *bytes.Buffer, call zirconSyscallDef) {
// The summed size of all arguments
argSize := 0
for _, arg := range call.args {
sz := typeSize(arg.atype)
if *goarch == "arm64" && sz == 1 {
sz = 8
}
for argSize%sz != 0 {
// Add padding until the 'argSize' is aligned to the type we are adding
argSize++
}
argSize += sz
}
if argSize%8 == 4 {
// Force the return argument on the stack to be 8-byte aligned, not 4-byte aligned
argSize += 4
}
retSize := typeSize(call.ret)
var frameSize int
var regArgs []string
var callIns, retReg, suffix4, suffix8 string
switch *goarch {
case "amd64":
regArgs = amd64RegArgs
callIns = "CALL"
retReg = "AX"
suffix8 = "Q"
suffix4 = "L"
switch len(call.args) {
case 7:
frameSize = 16 + 8
case 8:
frameSize = 16 + 2*8
}
case "arm64":
regArgs = arm64RegArgs
callIns = "BL"
retReg = "R0"
suffix8 = "D"
suffix4 = "W"
default:
panic(fmt.Sprintf("goarch=%s not supported", *goarch))
}
fmt.Fprintf(buf, "TEXT ·%s(SB),NOSPLIT,$%d-%d\n", call.sysname(), frameSize, argSize+retSize)
if _, ok := blockingSyscalls[call.name]; ok {
fmt.Fprintf(buf, "\tCALL runtime·entersyscall(SB)\n")
}
off := 0
for i, arg := range call.args {
name := arg.aname
suffix := suffix8
t := arg.atype
if typeSize(t) == 4 {
suffix = suffix4
}
sz := typeSize(t)
if *goarch == "arm64" && sz == 1 {
sz = 8
}
for off%sz != 0 {
// Add padding until the offset is aligned to the type we are accessing
off++
}
fmt.Fprintf(buf, "\tMOV%s %s+%d(FP), %s\n", suffix, name, off, regArgs[i])
off += sz
}
switch *goarch {
case "amd64":
if len(call.args) >= 7 {
fmt.Fprintf(buf, "\tMOVQ SP, BP // BP is preserved across vsdo call by the x86-64 ABI\n")
fmt.Fprintf(buf, "\tANDQ $~15, SP // stack alignment for x86-64 ABI\n")
if len(call.args) == 8 {
fmt.Fprintf(buf, "\tPUSHQ R13\n")
}
fmt.Fprintf(buf, "\tPUSHQ R12\n")
}
fmt.Fprintf(buf, "\tMOVQ vdso_zx_%s(SB), AX\n", call.name)
fmt.Fprintf(buf, "\tCALL AX\n")
if len(call.args) >= 7 {
fmt.Fprintf(buf, "\tPOPQ R12\n")
if len(call.args) == 8 {
fmt.Fprintf(buf, "\tPOPQ R13\n")
}
fmt.Fprintf(buf, "\tMOVQ BP, SP\n")
}
case "arm64":
fmt.Fprintf(buf, "\tBL vdso_zx_%s(SB)\n", call.name)
}
if retSize := typeSize(call.ret); retSize > 0 {
suffix := suffix8
if retSize == 4 {
suffix = suffix4
}
fmt.Fprintf(buf, "\tMOV%s %s, ret+%d(FP)\n", suffix, retReg, argSize)
}
if _, ok := blockingSyscalls[call.name]; ok {
fmt.Fprintf(buf, "\t%s runtime·exitsyscall(SB)\n", callIns)
}
fmt.Fprintf(buf, "\tRET\n")
}
func typeSize(t zirconType) int {
regSize := 8
if *goarch == "arm" {
regSize = 4
}
if t.isArray {
return regSize
}
switch t.named {
case zx_function_pointer:
return regSize
case zx_void:
return 0
case zx_any:
return 0
case zx_bool:
return 1
case zx_int, zx_uint, zx_uintptr_t, zx_size_t:
return regSize
case zx_char, zx_uint8_t:
return 1
case zx_int16_t, zx_uint16_t:
return 2
case zx_int32_t, zx_uint32_t:
return 4
case zx_int64_t, zx_uint64_t:
return 8
case zx_status_t:
return 4
case zx_time_t:
return 8
case zx_duration_t:
return 8
case zx_koid_t:
return 8
case zx_handle_t:
return 4
case zx_signals_t:
return 4
case zx_rights_t:
return 4
case zx_vaddr_t, zx_paddr_t:
return regSize
case zx_process_info_t:
panic("cannot pass zx_process_info_t")
case zx_exception_status_t, zx_exception_behaviour_t:
return 4
default:
panic(fmt.Sprintf("UNKNOWN(%s)", t.named))
}
}
func printGoType(buf *bytes.Buffer, t zirconType) {
if t.isArray {
if t.named == zx_any {
fmt.Fprint(buf, "unsafe.Pointer")
return
}
fmt.Fprint(buf, "*")
}
switch t.named {
case zx_void:
fmt.Fprint(buf, "BAD(void)")
case zx_any:
fmt.Fprint(buf, "BAD(any)")
case zx_bool:
fmt.Fprint(buf, "bool")
case zx_uint:
fmt.Fprint(buf, "uint")
case zx_int:
fmt.Fprint(buf, "int")
case zx_char, zx_uint8_t:
fmt.Fprint(buf, "uint8")
case zx_int16_t:
fmt.Fprint(buf, "int16")
case zx_uint16_t:
fmt.Fprint(buf, "uint16")
case zx_int32_t:
fmt.Fprint(buf, "int32")
case zx_uint32_t:
fmt.Fprint(buf, "uint32")
case zx_int64_t:
fmt.Fprint(buf, "int64")
case zx_uint64_t:
fmt.Fprint(buf, "uint64")
case zx_uintptr_t:
fmt.Fprint(buf, "uintptr")
case zx_status_t:
fmt.Fprint(buf, "Status")
case zx_time_t:
fmt.Fprint(buf, "Time")
case zx_duration_t:
fmt.Fprint(buf, "Duration")
case zx_koid_t:
fmt.Fprint(buf, "Koid")
case zx_handle_t:
fmt.Fprint(buf, "Handle")
case zx_signals_t:
fmt.Fprint(buf, "Signals")
case zx_wait_item_t:
fmt.Fprint(buf, "WaitItem")
case zx_rights_t:
fmt.Fprint(buf, "Rights")
case zx_vaddr_t:
fmt.Fprint(buf, "Vaddr")
case zx_paddr_t:
fmt.Fprint(buf, "Paddr")
case zx_size_t:
fmt.Fprint(buf, "uint")
case zx_process_info_t:
fmt.Fprint(buf, "ProcessInfo")
case zx_exception_status_t:
fmt.Fprint(buf, "ExceptionStatus")
case zx_exception_behaviour_t:
fmt.Fprint(buf, "ExceptionBehaviour")
case zx_rrec_t:
fmt.Fprint(buf, "Rrec")
case zx_channel_call_args_t:
fmt.Fprint(buf, "ChannelCallArgs")
case zx_fifo_state_t:
fmt.Fprint(buf, "FIFOState")
default:
panic(fmt.Sprintf("UNKNOWN type: %s", t.named))
}
}
func (p *parser) parse() (syscalls []zirconSyscallDef, err error) {
if len(p.buf) == 0 {
return syscalls, nil
}
p.nextRune()
for {
t, lit := p.nextToken()
if lit == "syscall" {
call, err := p.parseSyscall()
if err != nil {
return nil, err
}
if strings.Contains(call.name, "_test") {
continue
}
// The PCI syscalls are DDK-only and temporary, so exclude generation of
// helpers for them
if strings.HasPrefix(call.name, "pci_") {
continue
}
// The hypervisor syscalls have complex parameter and return types. Skip
// generation for these until we need them.
if strings.HasPrefix(call.name, "guest_") || strings.HasPrefix(call.name, "vcpu_") {
continue
}
syscalls = append(syscalls, call)
continue
}
if t == tokenEOF {
return syscalls, nil
}
if t == tokenBad {
return nil, p.errorf("bad token: %s", lit)
}
return nil, p.errorf("unexpected token: %q (literal %q)", t, lit)
}
}
func (p *parser) expect(want token) {
t, lit := p.nextToken()
if t != want {
p.panicf("expected %q but got %q (literal %q)", want, t, lit)
}
}
func (p *parser) parseAttributes() (attr []string, err error) {
for {
p.skipWhitespace()
if p.r == '(' {
return
}
attr = append(attr, p.nextIdent())
p.skipWhitespace()
if p.r == ',' {
p.nextRune()
}
}
}
func (p *parser) parseArg() (arg zirconArg, err error) {
if p.r == ')' {
return
}
arg.aname = p.nextIdent()
p.expect(':')
atype, err := p.parseType()
if err != nil {
return
}
arg.atype = atype
p.skipWhitespace()
if isLetter(p.r) {
arg.constraint = p.nextIdent()
p.skipWhitespace()
}
return
}
func (p *parser) parseSyscall() (call zirconSyscallDef, err error) {
call.name = p.nextIdent()
attr, err := p.parseAttributes()
if err != nil {
return zirconSyscallDef{}, err
}
for _, a := range attr {
if a == "vdsocall" {
call.vdsocall = true
}
}
call.attr = attr
p.expect('(')
for {
p.skipWhitespace()
if p.r == ')' {
p.nextRune()
break
}
arg, err := p.parseArg()
if err != nil {
return zirconSyscallDef{}, err
}
if arg.aname == "" {
break
}
call.args = append(call.args, arg)
if p.r == ',' {
p.nextRune()
}
}
p.skipWhitespace()
if p.r != ';' {
kw := p.nextIdent()
if kw != "returns" {
p.panicf("expected \"returns\" but got \"%s\"", kw)
}
p.expect('(')
first := true
for {
p.skipWhitespace()
if p.r == ')' {
p.nextRune()
break
}
if first {
first = false
rtype, err := p.parseType()
if err != nil {
return zirconSyscallDef{}, err
}
call.ret = rtype
} else {
arg, err := p.parseArg()
if err != nil {
return zirconSyscallDef{}, err
}
// Extra return types are returned as pointers.
arg.atype.isArray = true
arg.atype.arraySize = "1"
call.args = append(call.args, arg)
}
if p.r == ',' {
p.nextRune()
}
}
}
p.skipWhitespace()
p.expect(';')
return call, nil
}
func (p *parser) parseType() (t zirconType, err error) {
ident := p.nextIdent()
n, ok := zirconTypeNamedStr[ident]
if !ok {
return t, p.errorf("unknown type name: %s", ident)
}
t.named = n
p.skipWhitespace()
if p.r == '[' {
p.nextRune()
t.isArray = true
t.arraySize = p.nextIdent()
p.skipWhitespace();
for p.r == '*' {
p.nextRune()
t.arraySize += " * " + p.nextIdent();
}
p.expect(']')
}
return t, nil
}
type parser struct {
buf []byte
off int
line, col int
name string
r rune
}
type token rune
const (
tokenEOF token = -1
tokenBad token = -2
tokenIdent token = -3
tokenNumber token = -4
)
func (t token) String() string {
switch t {
case tokenEOF:
return "token(EOF)"
case tokenBad:
return "token(BAD)"
case tokenIdent:
return "token(Ident)"
case tokenNumber:
return "token(Number)"
default:
return "token(" + string(t) + ")"
}
}
func (p *parser) nextToken() (t token, lit string) {
p.skipWhitespace()
if isLetter(p.r) {
lit = p.nextIdent()
return tokenIdent, lit
}
if isNumber(p.r) {
lit = p.nextNumber()
return tokenNumber, lit
}
r := p.r
p.nextRune()
switch r {
case -1:
return tokenEOF, ""
case '(', ')', '*', ',', ';', ':', '[', ']':
return token(r), ""
case '#':
p.skipComment()
return p.nextToken()
}
return tokenBad, fmt.Sprintf("%q", r)
}
func (p *parser) nextIdent() string {
p.skipWhitespace()
if !isLetter(p.r) && !isNumber(p.r) {
p.panicf("expected ident but got %q", p.r)
}
off := p.off - 1
for isLetter(p.r) || isNumber(p.r) {
p.nextRune()
}
r := string(p.buf[off : p.off-1])
if r == "type" {
// "type" is a reserved word in Go.
return "typ"
}
return r
}
func (p *parser) nextNumber() string {
p.skipWhitespace()
if !isNumber(p.r) {
p.panicf("expected number but got %q", p.r)
}
off := p.off - 1
for isNumber(p.r) {
p.nextRune()
}
return string(p.buf[off : p.off-1])
}
func (p *parser) skipWhitespace() {
for p.r == ' ' || p.r == '\t' || p.r == '\n' {
p.nextRune()
}
}
func (p *parser) skipComment() {
// skip until end of line
p.nextRune()
for p.r != '\n' && p.r >= 0 {
p.nextRune()
}
}
func (p *parser) nextRune() {
if p.off >= len(p.buf) {
p.r = -1
return
}
if p.r == '\n' {
p.line++
p.col = 0
} else {
p.col += utf8.RuneLen(p.r)
}
r, w := utf8.DecodeRune(p.buf[p.off:])
if r == utf8.RuneError && w == 1 {
p.r = -1
p.panicf("input is not UTF-8")
return
}
p.r = r
p.off += w
}
func (p *parser) panicf(format string, args ...interface{}) {
panic(parsePanic{err: p.errorf(format, args...)})
}
func (p *parser) errorf(format string, args ...interface{}) error {
return fmt.Errorf("%s:%d:%d: %s", p.name, p.line, p.col, fmt.Sprintf(format, args...))
}
func isLetter(r rune) bool { return 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '_' }
func isNumber(r rune) bool { return '0' <= r && r <= '9' }
type parsePanic struct {
err error
}
func (pp parsePanic) String() string { return pp.err.Error() }
type zirconArg struct {
aname string
atype zirconType
constraint string
}
type zirconSyscallDef struct {
name string
ret zirconType
attr []string
args []zirconArg
vdsocall bool
}
func (call zirconSyscallDef) sysname() string {
if call.exported() {
return "Sys_" + call.name
}
return "sys_" + call.name
}
func (call zirconSyscallDef) exported() bool {
switch call.name {
case "handle_close",
"handle_duplicate",
"object_set_profile",
"object_wait_many",
"channel_create",
"channel_read",
"channel_read_etc",
"channel_write",
"pci_get_bar",
"pci_map_interrupt",
"pci_query_irq_mode",
"pci_set_irq_mode",
"pci_init",
"pci_add_subtract_to_range",
"socket_create",
"socket_read",
"socket_write",
"socket_accept",
"socket_share",
"system_get_dcache_line_size",
"system_get_features",
"vmar_allocate",
"vmar_destroy",
"vmar_map",
"vmar_unmap",
"vmar_protect",
"vmo_read",
"vmo_write",
"vmo_get_size",
"vmo_set_size",
"vmo_create",
"vmo_op_range",
"cprng_draw_new":
return false
default:
return true
}
}
type zirconType struct {
named zirconTypeNamed
isArray bool
arraySize string
}
type zirconTypeNamed int
const (
zx_void zirconTypeNamed = iota
zx_any
zx_bool
zx_uint
zx_int
zx_char
zx_uint8_t
zx_int16_t
zx_uint16_t
zx_int32_t
zx_uint32_t
zx_int64_t
zx_uint64_t
zx_uintptr_t
zx_size_t
zx_status_t
zx_time_t
zx_duration_t
zx_koid_t
zx_handle_t
zx_signals_t
zx_signals_state_t
zx_wait_item_t
zx_rights_t
zx_vaddr_t
zx_paddr_t
zx_process_info_t
zx_exception_status_t
zx_exception_behaviour_t
zx_function_pointer
zx_rrec_t
zx_channel_call_args_t
zx_fifo_state_t
)
var zirconTypeNamedStr = map[string]zirconTypeNamed{
"any": zx_any,
"void": zx_void,
"bool": zx_bool,
"unsigned int": zx_uint,
"int": zx_int,
"char": zx_char,
"uint8_t": zx_uint8_t,
"int16_t": zx_int16_t,
"int32_t": zx_int32_t,
"int64_t": zx_int64_t,
"uint16_t": zx_uint16_t,
"uint32_t": zx_uint32_t,
"uint64_t": zx_uint64_t,
"uintptr_t": zx_uintptr_t,
"size_t": zx_size_t,
"zx_status_t": zx_status_t, // int32_t
"zx_time_t": zx_time_t, // uint64_t monotonic time
"zx_duration_t": zx_duration_t, // uint64_t nanoseconds
"zx_koid_t": zx_koid_t,
"zx_handle_t": zx_handle_t, // int32_t
"zx_futex_t": zx_int,
"zx_signals_t": zx_signals_t,
"zx_signals_state_t": zx_signals_state_t,
"zx_wait_item_t": zx_wait_item_t,
"zx_rights_t": zx_rights_t,
"zx_vaddr_t": zx_vaddr_t,
"zx_paddr_t": zx_paddr_t,
"zx_process_info_t": zx_process_info_t,
"zx_exception_status_t": zx_exception_status_t,
"zx_exception_behaviour_t": zx_exception_behaviour_t,
"zx_function_pointer": zx_function_pointer,
"zx_rrec_t": zx_rrec_t,
"zx_channel_call_args_t": zx_channel_call_args_t,
"zx_fifo_state_t": zx_fifo_state_t,
// TODO(smklein): Remove the following temporary types. They could be blacklisted, but this is
// easier.
"zx_cache_policy_t": zx_int,
"zx_port_packet_t": zx_int,
"zx_pci_init_arg_t": zx_int,
"zx_pci_irq_mode_t": zx_int,
"zx_pci_resource_t": zx_int,
"zx_pcie_device_info_t": zx_int,
"zx_pcie_get_nth_info_t": zx_int,
"zx_vcpu_create_args_t": zx_int,
"zx_system_powerctl_arg_t": zx_int,
"zx_handle_info_t": zx_int,
"zx_profile_info_t": zx_int,
"zx_pci_bar_t": zx_int,
}