forked from Imagelibrary/seL4
Introduced a new config flag to enable userError format strings to be written to the IPC buffer. Another config bool has been introduced to toggle printing the error out and this can also be set at runtime. Signed-off-by: Saer Debel <saer.debel@data61.csiro.au>
1095 lines
37 KiB
Python
1095 lines
37 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
|
|
#
|
|
# SPDX-License-Identifier: BSD-2-Clause
|
|
#
|
|
|
|
#
|
|
# seL4 System Call Stub Generator
|
|
# ===============================
|
|
#
|
|
# 2009 David Greenaway
|
|
#
|
|
# This script generates system call stubs based on an XML specification of the
|
|
# objects that the kernel exports (and the methods those objects export).
|
|
#
|
|
# Previously, Magpie (an IDL compiler) was used to generate these stubs. As
|
|
# Magpie development progressed, support for a fixed ABI (i.e., the ABI
|
|
# implemented by the seL4 kernel) was lost, and support for generating
|
|
# alignment-safe code (required by platforms such as ARM) was also removed.
|
|
#
|
|
# This script is a stop-gap until these features can be restored in Magpie
|
|
# once again.
|
|
#
|
|
# The script has certain limitations:
|
|
#
|
|
# * It must be told the size of all types. This includes complex types
|
|
# such as structures.
|
|
#
|
|
# We generate code that will cause compilation to fail if we get any
|
|
# object's size wrong, which should help mitigate the number of bugs caused
|
|
# because of this script becoming out of date compared to the source files.
|
|
#
|
|
# * The script has only been tested on the actual seL4 API XML description.
|
|
#
|
|
# No stress testing has taken place; there may be bugs if new and wonderful
|
|
# XML method descriptions are added.
|
|
#
|
|
|
|
import operator
|
|
import itertools
|
|
import xml.dom.minidom
|
|
from argparse import ArgumentParser
|
|
import sys
|
|
from functools import reduce
|
|
|
|
# Number of bits in a standard word
|
|
WORD_SIZE_BITS_ARCH = {
|
|
"aarch32": 32,
|
|
"ia32": 32,
|
|
"aarch64": 64,
|
|
"ia64": 64,
|
|
"x86_64": 64,
|
|
"arm_hyp": 32,
|
|
"riscv32": 32,
|
|
"riscv64": 64,
|
|
}
|
|
|
|
MESSAGE_REGISTERS_FOR_ARCH = {
|
|
"aarch32": 4,
|
|
"aarch64": 4,
|
|
"ia32": 2,
|
|
"ia32-mcs": 1,
|
|
"x86_64": 4,
|
|
"arm_hyp": 4,
|
|
"riscv32": 4,
|
|
"riscv64": 4,
|
|
}
|
|
|
|
WORD_CONST_SUFFIX_BITS = {
|
|
32: "ul",
|
|
64: "ull",
|
|
}
|
|
|
|
# Maximum number of words that will be in a message.
|
|
MAX_MESSAGE_LENGTH = 64
|
|
|
|
# Headers to include
|
|
INCLUDES = [
|
|
'autoconf.h', 'sel4/types.h'
|
|
]
|
|
|
|
TYPES = {
|
|
8: "seL4_Uint8",
|
|
16: "seL4_Uint16",
|
|
32: "seL4_Uint32",
|
|
64: "seL4_Uint64"
|
|
}
|
|
|
|
|
|
class Type(object):
|
|
"""
|
|
This class represents a C type (such as an 'int', structure or
|
|
pointer.
|
|
"""
|
|
|
|
def __init__(self, name, size_bits, wordsize, double_word=False, native_size_bits=None):
|
|
"""
|
|
Define a new type, named 'name' that is 'size_bits' bits
|
|
long.
|
|
"""
|
|
|
|
self.name = name
|
|
self.size_bits = size_bits
|
|
self.wordsize = wordsize
|
|
self.double_word = double_word
|
|
|
|
#
|
|
# Store the number of bits C will use for this type
|
|
# in its native unpacked form.
|
|
#
|
|
# Required for 'bool', for example, which only uses 1
|
|
# bit when packed, but 32 bits when unpacked.
|
|
#
|
|
if native_size_bits:
|
|
self.native_size_bits = native_size_bits
|
|
else:
|
|
self.native_size_bits = size_bits
|
|
|
|
def pass_by_reference(self):
|
|
return self.size_bits > self.wordsize and not self.double_word
|
|
|
|
def render_parameter_name(self, name):
|
|
"""
|
|
Return a string of C code that would be used in a function
|
|
parameter declaration.
|
|
"""
|
|
return "%s %s" % (self.name, name)
|
|
|
|
def pointer(self):
|
|
"""
|
|
Return a new Type class representing a pointer to this
|
|
object.
|
|
"""
|
|
return PointerType(self, self.wordsize)
|
|
|
|
def c_expression(self, var_name, word_num=0):
|
|
"""
|
|
Return code for a C expression that gets word 'word_num'
|
|
of this type.
|
|
"""
|
|
assert word_num == 0
|
|
return "%s" % var_name
|
|
|
|
def double_word_expression(self, var_name, word_num, word_size):
|
|
|
|
assert word_num == 0 or word_num == 1
|
|
|
|
if word_num == 0:
|
|
return "({0}) {1}".format(TYPES[self.size_bits], var_name)
|
|
elif word_num == 1:
|
|
return "({0}) ({1} >> {2})".format(TYPES[self.size_bits], var_name,
|
|
word_size)
|
|
|
|
|
|
class PointerType(Type):
|
|
"""
|
|
A pointer to a standard type.
|
|
"""
|
|
|
|
def __init__(self, base_type, wordsize):
|
|
Type.__init__(self, base_type.name, wordsize, wordsize)
|
|
self.base_type = base_type
|
|
|
|
def render_parameter_name(self, name):
|
|
return "%s *%s" % (self.name, name)
|
|
|
|
def c_expression(self, var_name, word_num=0):
|
|
assert word_num == 0
|
|
return "*%s" % var_name
|
|
|
|
def pointer(self):
|
|
raise NotImplementedError()
|
|
|
|
|
|
class CapType(Type):
|
|
"""
|
|
A type that is just a typedef of seL4_CPtr.
|
|
"""
|
|
|
|
def __init__(self, name, wordsize):
|
|
Type.__init__(self, name, wordsize, wordsize)
|
|
|
|
|
|
class StructType(Type):
|
|
"""
|
|
A C 'struct' definition.
|
|
"""
|
|
|
|
def __init__(self, name, size_bits, wordsize):
|
|
Type.__init__(self, name, size_bits, wordsize)
|
|
|
|
def c_expression(self, var_name, word_num, member_name):
|
|
assert word_num < self.size_bits / self.wordsize
|
|
|
|
# Multiword structure.
|
|
assert self.pass_by_reference()
|
|
return "%s->%s" % (var_name, member_name[word_num])
|
|
|
|
|
|
class BitFieldType(Type):
|
|
"""
|
|
A special C 'struct' generated by the bitfield generator
|
|
"""
|
|
|
|
def __init__(self, name, size_bits, wordsize):
|
|
Type.__init__(self, name, size_bits, wordsize)
|
|
|
|
def c_expression(self, var_name, word_num=0):
|
|
|
|
return "%s.words[%d]" % (var_name, word_num)
|
|
|
|
|
|
class Parameter(object):
|
|
def __init__(self, name, type):
|
|
self.name = name
|
|
self.type = type
|
|
|
|
|
|
class Api(object):
|
|
def __init__(self, node):
|
|
self.name = node.getAttribute("name")
|
|
self.label_prefix = node.getAttribute("label_prefix") or ""
|
|
|
|
#
|
|
# Types
|
|
#
|
|
|
|
|
|
def init_data_types(wordsize):
|
|
types = [
|
|
# Simple Types
|
|
Type("int", 32, wordsize),
|
|
Type("long", wordsize, wordsize),
|
|
|
|
Type("seL4_Uint8", 8, wordsize),
|
|
Type("seL4_Uint16", 16, wordsize),
|
|
Type("seL4_Uint32", 32, wordsize),
|
|
Type("seL4_Uint64", 64, wordsize, double_word=(wordsize == 32)),
|
|
Type("seL4_Time", 64, wordsize, double_word=(wordsize == 32)),
|
|
Type("seL4_Word", wordsize, wordsize),
|
|
Type("seL4_Bool", 1, wordsize, native_size_bits=8),
|
|
|
|
# seL4 Structures
|
|
BitFieldType("seL4_CapRights_t", wordsize, wordsize),
|
|
|
|
# Object types
|
|
CapType("seL4_CPtr", wordsize),
|
|
CapType("seL4_CNode", wordsize),
|
|
CapType("seL4_IRQHandler", wordsize),
|
|
CapType("seL4_IRQControl", wordsize),
|
|
CapType("seL4_TCB", wordsize),
|
|
CapType("seL4_Untyped", wordsize),
|
|
CapType("seL4_DomainSet", wordsize),
|
|
CapType("seL4_SchedContext", wordsize),
|
|
CapType("seL4_SchedControl", wordsize),
|
|
]
|
|
|
|
return types
|
|
|
|
|
|
def init_arch_types(wordsize):
|
|
arch_types = {
|
|
"aarch32": [
|
|
Type("seL4_ARM_VMAttributes", wordsize, wordsize),
|
|
CapType("seL4_ARM_Page", wordsize),
|
|
CapType("seL4_ARM_PageTable", wordsize),
|
|
CapType("seL4_ARM_PageDirectory", wordsize),
|
|
CapType("seL4_ARM_ASIDControl", wordsize),
|
|
CapType("seL4_ARM_ASIDPool", wordsize),
|
|
CapType("seL4_ARM_VCPU", wordsize),
|
|
CapType("seL4_ARM_IOSpace", wordsize),
|
|
CapType("seL4_ARM_IOPageTable", wordsize),
|
|
StructType("seL4_UserContext", wordsize * 18, wordsize),
|
|
],
|
|
|
|
"aarch64": [
|
|
Type("seL4_ARM_VMAttributes", wordsize, wordsize),
|
|
CapType("seL4_ARM_Page", wordsize),
|
|
CapType("seL4_ARM_PageTable", wordsize),
|
|
CapType("seL4_ARM_PageDirectory", wordsize),
|
|
CapType("seL4_ARM_PageUpperDirectory", wordsize),
|
|
CapType("seL4_ARM_PageGlobalDirectory", wordsize),
|
|
CapType("seL4_ARM_VSpace", wordsize),
|
|
CapType("seL4_ARM_ASIDControl", wordsize),
|
|
CapType("seL4_ARM_ASIDPool", wordsize),
|
|
CapType("seL4_ARM_VCPU", wordsize),
|
|
CapType("seL4_ARM_IOSpace", wordsize),
|
|
CapType("seL4_ARM_IOPageTable", wordsize),
|
|
StructType("seL4_UserContext", wordsize * 36, wordsize),
|
|
],
|
|
|
|
"arm_hyp": [
|
|
Type("seL4_ARM_VMAttributes", wordsize, wordsize),
|
|
CapType("seL4_ARM_Page", wordsize),
|
|
CapType("seL4_ARM_PageTable", wordsize),
|
|
CapType("seL4_ARM_PageDirectory", wordsize),
|
|
CapType("seL4_ARM_ASIDControl", wordsize),
|
|
CapType("seL4_ARM_ASIDPool", wordsize),
|
|
CapType("seL4_ARM_VCPU", wordsize),
|
|
CapType("seL4_ARM_IOSpace", wordsize),
|
|
CapType("seL4_ARM_IOPageTable", wordsize),
|
|
StructType("seL4_UserContext", wordsize * 18, wordsize),
|
|
],
|
|
|
|
"ia32": [
|
|
Type("seL4_X86_VMAttributes", wordsize, wordsize),
|
|
CapType("seL4_X86_IOPort", wordsize),
|
|
CapType("seL4_X86_IOPortControl", wordsize),
|
|
CapType("seL4_X86_ASIDControl", wordsize),
|
|
CapType("seL4_X86_ASIDPool", wordsize),
|
|
CapType("seL4_X86_IOSpace", wordsize),
|
|
CapType("seL4_X86_Page", wordsize),
|
|
CapType("seL4_X86_PageDirectory", wordsize),
|
|
CapType("seL4_X86_PageTable", wordsize),
|
|
CapType("seL4_X86_IOPageTable", wordsize),
|
|
CapType("seL4_X86_VCPU", wordsize),
|
|
CapType("seL4_X86_EPTPML4", wordsize),
|
|
CapType("seL4_X86_EPTPDPT", wordsize),
|
|
CapType("seL4_X86_EPTPD", wordsize),
|
|
CapType("seL4_X86_EPTPT", wordsize),
|
|
StructType("seL4_VCPUContext", wordsize * 7, wordsize),
|
|
StructType("seL4_UserContext", wordsize * 12, wordsize),
|
|
],
|
|
|
|
"x86_64": [
|
|
Type("seL4_X86_VMAttributes", wordsize, wordsize),
|
|
CapType("seL4_X86_IOPort", wordsize),
|
|
CapType("seL4_X86_IOPortControl", wordsize),
|
|
CapType("seL4_X86_ASIDControl", wordsize),
|
|
CapType("seL4_X86_ASIDPool", wordsize),
|
|
CapType("seL4_X86_IOSpace", wordsize),
|
|
CapType("seL4_X86_Page", wordsize),
|
|
CapType("seL4_X64_PML4", wordsize),
|
|
CapType("seL4_X86_PDPT", wordsize),
|
|
CapType("seL4_X86_PageDirectory", wordsize),
|
|
CapType("seL4_X86_PageTable", wordsize),
|
|
CapType("seL4_X86_IOPageTable", wordsize),
|
|
CapType("seL4_X86_VCPU", wordsize),
|
|
CapType("seL4_X86_EPTPML4", wordsize),
|
|
CapType("seL4_X86_EPTPDPT", wordsize),
|
|
CapType("seL4_X86_EPTPD", wordsize),
|
|
CapType("seL4_X86_EPTPT", wordsize),
|
|
StructType("seL4_VCPUContext", wordsize * 7, wordsize),
|
|
StructType("seL4_UserContext", wordsize * 20, wordsize),
|
|
],
|
|
"riscv32": [
|
|
Type("seL4_RISCV_VMAttributes", wordsize, wordsize),
|
|
CapType("seL4_RISCV_Page", wordsize),
|
|
CapType("seL4_RISCV_PageTable", wordsize),
|
|
CapType("seL4_RISCV_ASIDControl", wordsize),
|
|
CapType("seL4_RISCV_ASIDPool", wordsize),
|
|
StructType("seL4_UserContext", wordsize * 32, wordsize),
|
|
],
|
|
"riscv64": [
|
|
Type("seL4_RISCV_VMAttributes", wordsize, wordsize),
|
|
CapType("seL4_RISCV_Page", wordsize),
|
|
CapType("seL4_RISCV_PageTable", wordsize),
|
|
CapType("seL4_RISCV_ASIDControl", wordsize),
|
|
CapType("seL4_RISCV_ASIDPool", wordsize),
|
|
StructType("seL4_UserContext", wordsize * 32, wordsize),
|
|
]
|
|
}
|
|
|
|
return arch_types
|
|
|
|
# Retrieve a member list for a given struct type
|
|
|
|
|
|
def struct_members(typ, structs):
|
|
members = [member for struct_name, member in structs if struct_name == typ.name]
|
|
assert len(members) == 1
|
|
return members[0]
|
|
|
|
# Keep increasing the given number 'x' until 'x % a == 0'.
|
|
|
|
|
|
def align_up(x, a):
|
|
if x % a == 0:
|
|
return x
|
|
return x + a - (x % a)
|
|
|
|
|
|
def get_parameter_positions(parameters, wordsize):
|
|
"""
|
|
Determine where each parameter should be packed in the generated message.
|
|
We generate a list of:
|
|
|
|
(param_name, param_type, first_bit, num_bits)
|
|
|
|
tuples.
|
|
|
|
We guarantee that either (num_words == 1) or (bit_offset == 0).
|
|
"""
|
|
bits_used = 0
|
|
results = []
|
|
|
|
for param in parameters:
|
|
# How big are we?
|
|
type_size = param.type.size_bits
|
|
|
|
# We need everything to be a power of two, or word sized.
|
|
assert ((type_size & (type_size - 1)) == 0) or (type_size % wordsize == 0)
|
|
|
|
# Align up to our own size, or the next word. (Whichever is smaller)
|
|
bits_used = align_up(bits_used, min(type_size, wordsize))
|
|
|
|
# Place ourself.
|
|
results.append((param, bits_used, type_size))
|
|
bits_used += type_size
|
|
|
|
return results
|
|
|
|
|
|
def generate_param_list(input_params, output_params):
|
|
# Generate parameters
|
|
params = []
|
|
for param in input_params:
|
|
if not param.type.pass_by_reference():
|
|
params.append(param.type.render_parameter_name(param.name))
|
|
else:
|
|
params.append(param.type.pointer().render_parameter_name(param.name))
|
|
for param in output_params:
|
|
if param.type.pass_by_reference():
|
|
params.append(param.type.pointer().render_parameter_name(param.name))
|
|
|
|
return ", ".join(params)
|
|
|
|
|
|
def generate_marshal_expressions(params, num_mrs, structs, wordsize):
|
|
"""
|
|
Generate marshalling expressions for the given set of inputs.
|
|
|
|
We return a list of expressions; one expression per word required
|
|
to marshal all the inputs.
|
|
"""
|
|
|
|
def generate_param_code(param, first_bit, num_bits, word_array, wordsize):
|
|
"""
|
|
Generate code to marshal the given parameter into the correct
|
|
location in the message.
|
|
|
|
'word_array' is an array of the final contents of the message.
|
|
word_array[k] contains what should be placed in the k'th message
|
|
register, and is an array of expressions that will (eventually)
|
|
be bitwise-or'ed into it.
|
|
"""
|
|
|
|
target_word = first_bit // wordsize
|
|
target_offset = first_bit % wordsize
|
|
|
|
# double word type
|
|
if param.type.double_word:
|
|
word_array[target_word].append(
|
|
param.type.double_word_expression(param.name, 0, wordsize))
|
|
word_array[target_word +
|
|
1].append(param.type.double_word_expression(param.name, 1, wordsize))
|
|
return
|
|
|
|
# Single full word?
|
|
if num_bits == wordsize:
|
|
assert target_offset == 0
|
|
expr = param.type.c_expression(param.name)
|
|
word_array[target_word].append(expr)
|
|
return
|
|
|
|
# Part of a word?
|
|
if num_bits < wordsize:
|
|
expr = param.type.c_expression(param.name)
|
|
expr = "(%s & %#x%s)" % (expr, (1 << num_bits) - 1,
|
|
WORD_CONST_SUFFIX_BITS[wordsize])
|
|
if target_offset:
|
|
expr = "(%s << %d)" % (expr, target_offset)
|
|
word_array[target_word].append(expr)
|
|
return
|
|
|
|
# Multiword array
|
|
assert target_offset == 0
|
|
num_words = num_bits // wordsize
|
|
for i in range(num_words):
|
|
expr = param.type.c_expression(param.name, i, struct_members(param.type, structs))
|
|
word_array[target_word + i].append(expr)
|
|
|
|
# Get their marshalling positions
|
|
positions = get_parameter_positions(params, wordsize)
|
|
|
|
# Generate marshal code.
|
|
words = [[] for _ in range(num_mrs, MAX_MESSAGE_LENGTH)]
|
|
for (param, first_bit, num_bits) in positions:
|
|
generate_param_code(param, first_bit, num_bits, words, wordsize)
|
|
|
|
# Return list of expressions.
|
|
return [" | ".join(x) for x in words if len(x) > 0]
|
|
|
|
|
|
def generate_unmarshal_expressions(params, wordsize):
|
|
"""
|
|
Generate unmarshalling expressions for the given set of outputs.
|
|
|
|
We return a list of list of expressions; one list per variable, containing
|
|
expressions for the words in it that must be unmarshalled. The expressions
|
|
will have tokens of the form:
|
|
"%(w0)s"
|
|
in them, indicating a read from a word in the message.
|
|
"""
|
|
|
|
def unmarshal_single_param(first_bit, num_bits, wordsize):
|
|
"""
|
|
Unmarshal a single parameter.
|
|
"""
|
|
first_word = first_bit // wordsize
|
|
bit_offset = first_bit % wordsize
|
|
|
|
# Multiword type?
|
|
if num_bits > wordsize:
|
|
result = []
|
|
for x in range(num_bits // wordsize):
|
|
result.append("%%(w%d)s" % (x + first_word))
|
|
return result
|
|
|
|
# Otherwise, bit packed.
|
|
if num_bits == wordsize:
|
|
return ["%%(w%d)s" % first_word]
|
|
elif bit_offset == 0:
|
|
return ["(%%(w%d)s & %#x)" % (
|
|
first_word, (1 << num_bits) - 1)]
|
|
else:
|
|
return ["(%%(w%d)s >> %d) & %#x" % (
|
|
first_word, bit_offset, (1 << num_bits) - 1)]
|
|
|
|
# Get their marshalling positions
|
|
positions = get_parameter_positions(params, wordsize)
|
|
|
|
# Generate the unmarshal code.
|
|
results = []
|
|
for (param, first_bit, num_bits) in positions:
|
|
results.append((param, unmarshal_single_param(first_bit, num_bits, wordsize)))
|
|
return results
|
|
|
|
|
|
def is_result_struct_required(output_params):
|
|
return len([x for x in output_params if not x.type.pass_by_reference()]) != 0
|
|
|
|
|
|
def generate_result_struct(interface_name, method_name, output_params):
|
|
"""
|
|
Generate a structure definition to be returned by the system call stubs to
|
|
the user.
|
|
|
|
We have a few constraints:
|
|
|
|
* We always need an 'error' output parameter, even though it won't
|
|
appear in the list 'output_params' given to us.
|
|
|
|
* Output parameters may be marked as 'pass_by_reference', indicating
|
|
that we only ever see pointers to the item.
|
|
|
|
If no structure is needed (i.e., we just return an error code), we return
|
|
'None'.
|
|
"""
|
|
|
|
# Do we actually need a structure?
|
|
if not is_result_struct_required(output_params):
|
|
return None
|
|
|
|
#
|
|
# Generate the structure:
|
|
#
|
|
# struct seL4_CNode_Copy {
|
|
# int error;
|
|
# seL4_Word foo;
|
|
# };
|
|
# typedef struct seL4_CNode_Copy seL4_CNode_Copy_t;
|
|
#
|
|
result = []
|
|
result.append("struct %s_%s {" % (interface_name, method_name))
|
|
result.append("\tint error;")
|
|
for i in output_params:
|
|
if not i.type.pass_by_reference():
|
|
result.append("\t%s;" % i.type.render_parameter_name(i.name))
|
|
result.append("};")
|
|
result.append("typedef struct %s_%s %s_%s_t;" % (
|
|
(interface_name, method_name, interface_name, method_name)))
|
|
result.append("")
|
|
|
|
return "\n".join(result)
|
|
|
|
|
|
def generate_stub(arch, wordsize, interface_name, method_name, method_id, input_params, output_params, structs, use_only_ipc_buffer, comment, mcs):
|
|
result = []
|
|
|
|
if use_only_ipc_buffer:
|
|
num_mrs = 0
|
|
else:
|
|
if mcs and "%s-mcs" % arch in MESSAGE_REGISTERS_FOR_ARCH:
|
|
num_mrs = MESSAGE_REGISTERS_FOR_ARCH["%s-mcs" % arch]
|
|
else:
|
|
num_mrs = MESSAGE_REGISTERS_FOR_ARCH[arch]
|
|
|
|
# Split out cap parameters and standard parameters
|
|
standard_params = []
|
|
cap_params = []
|
|
for x in input_params:
|
|
if isinstance(x.type, CapType):
|
|
cap_params.append(x)
|
|
else:
|
|
standard_params.append(x)
|
|
|
|
# Determine if we are returning a structure, or just the error code.
|
|
returning_struct = False
|
|
results_structure = generate_result_struct(interface_name, method_name, output_params)
|
|
if results_structure:
|
|
return_type = "%s_%s_t" % (interface_name, method_name)
|
|
returning_struct = True
|
|
else:
|
|
return_type = "seL4_Error"
|
|
|
|
#
|
|
# Print doxygen comment.
|
|
#
|
|
result.append(comment)
|
|
|
|
#
|
|
# Print function header.
|
|
#
|
|
# static inline int
|
|
# seL4_Untyped_Retype(...)
|
|
# {
|
|
#
|
|
result.append("LIBSEL4_INLINE %s" % return_type)
|
|
result.append("%s_%s(%s)" % (interface_name, method_name,
|
|
generate_param_list(input_params, output_params)))
|
|
result.append("{")
|
|
|
|
#
|
|
# Get a list of expressions for our caps and inputs.
|
|
#
|
|
input_expressions = generate_marshal_expressions(standard_params, num_mrs,
|
|
structs, wordsize)
|
|
cap_expressions = [x.name for x in cap_params]
|
|
service_cap = cap_expressions[0]
|
|
cap_expressions = cap_expressions[1:]
|
|
|
|
#
|
|
# Compute how many words the inputs and output will require.
|
|
#
|
|
input_param_words = len(input_expressions)
|
|
output_param_words = sum([p.type.size_bits for p in output_params]) / wordsize
|
|
|
|
#
|
|
# Setup variables we will need.
|
|
#
|
|
result.append("\t%s result;" % return_type)
|
|
result.append("\tseL4_MessageInfo_t tag = seL4_MessageInfo_new(%s, 0, %d, %d);" %
|
|
(method_id, len(cap_expressions), len(input_expressions)))
|
|
result.append("\tseL4_MessageInfo_t output_tag;")
|
|
for i in range(num_mrs):
|
|
result.append("\tseL4_Word mr%d;" % i)
|
|
result.append("")
|
|
|
|
#
|
|
# Copy capabilities.
|
|
#
|
|
# /* Setup input capabilities. */
|
|
# seL4_SetCap(i, cap);
|
|
#
|
|
if len(cap_expressions) > 0:
|
|
result.append("\t/* Setup input capabilities. */")
|
|
for i in range(len(cap_expressions)):
|
|
result.append("\tseL4_SetCap(%d, %s);" % (i, cap_expressions[i]))
|
|
result.append("")
|
|
|
|
#
|
|
# Copy in the inputs.
|
|
#
|
|
# /* Marshal input parameters. */
|
|
# seL4_SetMR(i, v);
|
|
# ...
|
|
#
|
|
if max(num_mrs, len(input_expressions)) > 0:
|
|
result.append("\t/* Marshal and initialise parameters. */")
|
|
# Initialise in-register parameters
|
|
for i in range(num_mrs):
|
|
if i < len(input_expressions):
|
|
result.append("\tmr%d = %s;" % (i, input_expressions[i]))
|
|
else:
|
|
result.append("\tmr%d = 0;" % i)
|
|
# Initialise buffered parameters
|
|
for i in range(num_mrs, len(input_expressions)):
|
|
result.append("\tseL4_SetMR(%d, %s);" % (i, input_expressions[i]))
|
|
result.append("")
|
|
|
|
#
|
|
# Generate the call.
|
|
#
|
|
if use_only_ipc_buffer:
|
|
result.append("\t/* Perform the call. */")
|
|
result.append("\toutput_tag = seL4_Call(%s, tag);" % service_cap)
|
|
else:
|
|
result.append("\t/* Perform the call, passing in-register arguments directly. */")
|
|
result.append("\toutput_tag = seL4_CallWithMRs(%s, tag," % (service_cap))
|
|
result.append("\t\t%s);" % ', '.join(
|
|
("&mr%d" % i) for i in range(num_mrs)))
|
|
|
|
#
|
|
# Prepare the result.
|
|
#
|
|
label = "result.error" if returning_struct else "result"
|
|
cast = " (%s)" % return_type if not returning_struct else ""
|
|
result.append("\t%s =%s seL4_MessageInfo_get_label(output_tag);" % (label, cast))
|
|
result.append("")
|
|
|
|
if not use_only_ipc_buffer:
|
|
result.append("\t/* Unmarshal registers into IPC buffer on error. */")
|
|
result.append("\tif (%s != seL4_NoError) {" % label)
|
|
for i in range(num_mrs):
|
|
result.append("\t\tseL4_SetMR(%d, mr%d);" % (i, i))
|
|
result.append("#ifdef CONFIG_KERNEL_INVOCATION_REPORT_ERROR_IPC")
|
|
result.append("\t\tif (seL4_CanPrintError()) {")
|
|
result.append("\t\t\tseL4_DebugPutString(seL4_GetDebugError());")
|
|
result.append("\t\t}")
|
|
result.append("#endif")
|
|
if returning_struct:
|
|
result.append("\t\treturn result;")
|
|
result.append("\t}")
|
|
result.append("")
|
|
|
|
#
|
|
# Generate unmarshalling code.
|
|
#
|
|
if len(output_params) > 0:
|
|
result.append("\t/* Unmarshal result. */")
|
|
source_words = {}
|
|
for i in range(MAX_MESSAGE_LENGTH):
|
|
if i < num_mrs:
|
|
source_words["w%d" % i] = "mr%d" % i
|
|
else:
|
|
source_words["w%d" % i] = "seL4_GetMR(%d)" % i
|
|
unmashalled_params = generate_unmarshal_expressions(output_params, wordsize)
|
|
for (param, words) in unmashalled_params:
|
|
if param.type.pass_by_reference():
|
|
members = struct_members(param.type, structs)
|
|
for i in range(len(words)):
|
|
result.append("\t%s->%s = %s;" %
|
|
(param.name, members[i], words[i] % source_words))
|
|
else:
|
|
if param.type.double_word:
|
|
result.append("\tresult.%s = ((%s)%s + ((%s)%s << 32));" %
|
|
(param.name, TYPES[64], words[0] % source_words,
|
|
TYPES[64], words[1] % source_words))
|
|
else:
|
|
for word in words:
|
|
result.append("\tresult.%s = %s;" % (param.name, word % source_words))
|
|
|
|
#
|
|
# }
|
|
#
|
|
result.append("\treturn result;")
|
|
result.append("}")
|
|
|
|
return "\n".join(result) + "\n"
|
|
|
|
|
|
def get_xml_element_contents(element):
|
|
"""
|
|
Converts the contents of an xml element into a string, with all
|
|
child xml nodes unchanged.
|
|
"""
|
|
return "".join([c.toxml() for c in element.childNodes])
|
|
|
|
|
|
def get_xml_element_content_with_xmlonly(element):
|
|
"""
|
|
Converts the contents of an xml element into a string, wrapping
|
|
all child xml nodes in doxygen @xmlonly/@endxmlonly keywords.
|
|
"""
|
|
|
|
result = []
|
|
prev_element = False
|
|
for node in element.childNodes:
|
|
if node.nodeType == xml.dom.Node.TEXT_NODE:
|
|
if prev_element:
|
|
# text node following element node
|
|
result.append(" @endxmlonly ")
|
|
prev_element = False
|
|
else:
|
|
if not prev_element:
|
|
# element node following text node
|
|
result.append(" @xmlonly ")
|
|
prev_element = True
|
|
|
|
result.append(node.toxml())
|
|
|
|
return "".join(result)
|
|
|
|
|
|
def normalise_text(text):
|
|
"""
|
|
Removes leading and trailing whitespace from each line of text.
|
|
Removes leading and trailing blank lines from text.
|
|
"""
|
|
stripped = text.strip()
|
|
stripped_lines = [line.strip() for line in text.split("\n")]
|
|
# remove leading and trailing empty lines
|
|
stripped_head = list(itertools.dropwhile(lambda s: not s, stripped_lines))
|
|
stripped_tail = itertools.dropwhile(lambda s: not s, reversed(stripped_head))
|
|
return "\n".join(reversed(list(stripped_tail)))
|
|
|
|
|
|
def parse_xml_file(input_file, valid_types):
|
|
"""
|
|
Parse an XML file containing method definitions.
|
|
"""
|
|
|
|
# Create a dictionary of type name to type.
|
|
type_names = {}
|
|
for i in valid_types:
|
|
type_names[i.name] = i
|
|
|
|
# Parse the XML to generate method structures.
|
|
methods = []
|
|
structs = []
|
|
doc = xml.dom.minidom.parse(input_file)
|
|
|
|
api = Api(doc.getElementsByTagName("api")[0])
|
|
|
|
for struct in doc.getElementsByTagName("struct"):
|
|
_struct_members = []
|
|
struct_name = struct.getAttribute("name")
|
|
for members in struct.getElementsByTagName("member"):
|
|
member_name = members.getAttribute("name")
|
|
_struct_members.append(member_name)
|
|
structs.append((struct_name, _struct_members))
|
|
|
|
for interface in doc.getElementsByTagName("interface"):
|
|
interface_name = interface.getAttribute("name")
|
|
interface_manual_name = interface.getAttribute("manual_name") or interface_name
|
|
|
|
interface_cap_description = interface.getAttribute("cap_description")
|
|
|
|
for method in interface.getElementsByTagName("method"):
|
|
method_name = method.getAttribute("name")
|
|
method_id = method.getAttribute("id")
|
|
method_condition = method.getAttribute("condition")
|
|
method_manual_name = method.getAttribute("manual_name") or method_name
|
|
method_manual_label = method.getAttribute("manual_label")
|
|
|
|
if not method_manual_label:
|
|
# If no manual label is specified, infer one from the interface and method
|
|
# names by combining the interface name and method name.
|
|
method_manual_label = ("%s_%s" % (interface_manual_name, method_manual_name)) \
|
|
.lower() \
|
|
.replace(" ", "_") \
|
|
.replace("/", "")
|
|
|
|
# Prefix the label with an api-wide label prefix
|
|
method_manual_label = "%s%s" % (api.label_prefix, method_manual_label)
|
|
|
|
comment_lines = ["@xmlonly <manual name=\"%s\" label=\"%s\"/> @endxmlonly" %
|
|
(method_manual_name, method_manual_label)]
|
|
|
|
method_brief = method.getElementsByTagName("brief")
|
|
if method_brief:
|
|
method_brief_text = get_xml_element_contents(method_brief[0])
|
|
normalised_method_brief_text = normalise_text(method_brief_text)
|
|
comment_lines.append("@brief @xmlonly %s @endxmlonly" %
|
|
normalised_method_brief_text)
|
|
|
|
method_description = method.getElementsByTagName("description")
|
|
if method_description:
|
|
method_description_text = get_xml_element_contents(method_description[0])
|
|
normalised_method_description_text = normalise_text(method_description_text)
|
|
comment_lines.append("\n@xmlonly\n%s\n@endxmlonly\n" %
|
|
normalised_method_description_text)
|
|
|
|
#
|
|
# Get parameters.
|
|
#
|
|
# We always have an implicit cap parameter.
|
|
#
|
|
input_params = [Parameter("_service", type_names[interface_name])]
|
|
|
|
cap_description = interface_cap_description
|
|
cap_param = method.getElementsByTagName("cap_param")
|
|
if cap_param:
|
|
append_description = cap_param[0].getAttribute("append_description")
|
|
if append_description:
|
|
cap_description += append_description
|
|
|
|
comment_lines.append("@param[in] _service %s" % cap_description)
|
|
output_params = []
|
|
for param in method.getElementsByTagName("param"):
|
|
param_name = param.getAttribute("name")
|
|
param_type = type_names.get(param.getAttribute("type"))
|
|
if not param_type:
|
|
raise Exception("Unknown type '%s'." % (param.getAttribute("type")))
|
|
param_dir = param.getAttribute("dir")
|
|
assert (param_dir == "in") or (param_dir == "out")
|
|
if param_dir == "in":
|
|
input_params.append(Parameter(param_name, param_type))
|
|
else:
|
|
output_params.append(Parameter(param_name, param_type))
|
|
|
|
if param_dir == "in" or param_type.pass_by_reference():
|
|
param_description = param.getAttribute("description")
|
|
if not param_description:
|
|
param_description_element = param.getElementsByTagName("description")
|
|
if param_description_element:
|
|
param_description_text = get_xml_element_content_with_xmlonly(
|
|
param_description_element[0])
|
|
param_description = normalise_text(param_description_text)
|
|
|
|
comment_lines.append("@param[%s] %s %s " %
|
|
(param_dir, param_name, param_description))
|
|
|
|
method_return_description = method.getElementsByTagName("return")
|
|
if method_return_description:
|
|
comment_lines.append("@return @xmlonly %s @endxmlonly" %
|
|
get_xml_element_contents(method_return_description[0]))
|
|
else:
|
|
# no return documentation given - default to something sane
|
|
if is_result_struct_required(output_params):
|
|
comment_lines.append("@return @xmlonly @endxmlonly")
|
|
else:
|
|
comment_lines.append("@return @xmlonly <errorenumdesc/> @endxmlonly")
|
|
|
|
# split each line on newlines
|
|
comment_lines = reduce(operator.add, [l.split("\n") for l in comment_lines], [])
|
|
|
|
# place the comment text in a c comment
|
|
comment = "\n".join(["/**"] + [" * %s" % l for l in comment_lines] + [" */"])
|
|
|
|
methods.append((interface_name, method_name, method_id, input_params,
|
|
output_params, method_condition, comment))
|
|
|
|
return (methods, structs, api)
|
|
|
|
|
|
def generate_stub_file(arch, wordsize, input_files, output_file, use_only_ipc_buffer, mcs):
|
|
"""
|
|
Generate a header file containing system call stubs for seL4.
|
|
"""
|
|
result = []
|
|
|
|
# Ensure architecture looks sane.
|
|
if arch not in WORD_SIZE_BITS_ARCH.keys():
|
|
raise Exception("Invalid architecture.")
|
|
|
|
data_types = init_data_types(wordsize)
|
|
arch_types = init_arch_types(wordsize)
|
|
|
|
# Parse XML
|
|
methods = []
|
|
structs = []
|
|
for infile in input_files:
|
|
method, struct, _ = parse_xml_file(infile, data_types + arch_types[arch])
|
|
methods += method
|
|
structs += struct
|
|
|
|
# Print header.
|
|
result.append("""
|
|
/*
|
|
* Automatically generated system call stubs.
|
|
*/
|
|
|
|
#ifndef __LIBSEL4_SEL4_CLIENT_H
|
|
#define __LIBSEL4_SEL4_CLIENT_H
|
|
""")
|
|
|
|
# Emit the includes
|
|
result.append('\n'.join(['#include <%s>' % include for include in INCLUDES]))
|
|
|
|
#
|
|
# Emit code to ensure that all of our type sizes are consistent with
|
|
# the compiler's.
|
|
#
|
|
result.append("""
|
|
/*
|
|
* The following code generates a compile-time error if the system call
|
|
* stub generator has an incorrect understanding of how large a type is.
|
|
*
|
|
* If you receive a compile-time error here, you will need to adjust
|
|
* the type information in the stub generator.
|
|
*/
|
|
#define assert_size_correct(type, expected_bytes) \\
|
|
typedef unsigned long __type_##type##_size_incorrect[ \\
|
|
(sizeof(type) == expected_bytes) ? 1 : -1]
|
|
""")
|
|
for x in data_types + arch_types[arch]:
|
|
result.append("assert_size_correct(%s, %d);" % (x.name, x.native_size_bits / 8))
|
|
result.append("")
|
|
|
|
#
|
|
# Generate structures needed to return results back to the user.
|
|
#
|
|
# We can not use pass-by-reference (except for really large objects), as
|
|
# the verification framework does not support them.
|
|
#
|
|
result.append("/*")
|
|
result.append(" * Return types for generated methods.")
|
|
result.append(" */")
|
|
for (interface_name, method_name, _, _, output_params, _, _) in methods:
|
|
results_structure = generate_result_struct(interface_name, method_name, output_params)
|
|
if results_structure:
|
|
result.append(results_structure)
|
|
|
|
#
|
|
# Generate the actual stub code.
|
|
#
|
|
result.append("/*")
|
|
result.append(" * Generated stubs.")
|
|
result.append(" */")
|
|
for (interface_name, method_name, method_id, inputs, outputs, condition, comment) in methods:
|
|
if condition != "":
|
|
result.append("#if %s" % condition)
|
|
result.append(generate_stub(arch, wordsize, interface_name, method_name,
|
|
method_id, inputs, outputs, structs, use_only_ipc_buffer, comment, mcs))
|
|
if condition != "":
|
|
result.append("#endif")
|
|
|
|
# Print footer.
|
|
result.append("#endif /* __LIBSEL4_SEL4_CLIENT_H */")
|
|
result.append("")
|
|
|
|
# Write the output
|
|
output = open(output_file, "w")
|
|
output.write("\n".join(result))
|
|
output.close()
|
|
|
|
|
|
def process_args():
|
|
usage_str = """
|
|
%(prog)s [OPTIONS] [FILES] """
|
|
epilog_str = """
|
|
|
|
"""
|
|
parser = ArgumentParser(description='seL4 System Call Stub Generator.',
|
|
usage=usage_str,
|
|
epilog=epilog_str)
|
|
parser.add_argument("-o", "--output", dest="output", default="/dev/stdout",
|
|
help="Output file to write stub to. (default: %(default)s).")
|
|
parser.add_argument("-b", "--buffer", dest="buffer", action="store_true", default=False,
|
|
help="Use IPC buffer exclusively, i.e. do not pass syscall arguments by registers. (default: %(default)s)")
|
|
parser.add_argument("-a", "--arch", dest="arch", required=True, choices=WORD_SIZE_BITS_ARCH,
|
|
help="Architecture to generate stubs for.")
|
|
parser.add_argument("--mcs", dest="mcs", action="store_true",
|
|
help="Generate MCS api.")
|
|
|
|
wsizegroup = parser.add_mutually_exclusive_group()
|
|
wsizegroup.add_argument("-w", "--word-size", dest="wsize",
|
|
help="Word size(in bits), for the platform.")
|
|
wsizegroup.add_argument("-c", "--cfile", dest="cfile",
|
|
help="Config file for Kbuild, used to get Word size.")
|
|
|
|
parser.add_argument("files", metavar="FILES", nargs="+",
|
|
help="Input XML files.")
|
|
|
|
return parser
|
|
|
|
|
|
def main():
|
|
|
|
parser = process_args()
|
|
args = parser.parse_args()
|
|
|
|
if not (args.wsize or args.cfile):
|
|
parser.error("Require either -w/--word-size or -c/--cfile argument.")
|
|
sys.exit(2)
|
|
|
|
# Get word size
|
|
wordsize = -1
|
|
|
|
if args.cfile:
|
|
try:
|
|
with open(args.cfile) as conffile:
|
|
for line in conffile:
|
|
if line.startswith('CONFIG_WORD_SIZE'):
|
|
wordsize = int(line.split('=')[1].strip())
|
|
except IndexError:
|
|
print("Invalid word size in configuration file.")
|
|
sys.exit(2)
|
|
else:
|
|
wordsize = int(args.wsize)
|
|
|
|
if wordsize == -1:
|
|
print("Invalid word size.")
|
|
sys.exit(2)
|
|
|
|
# Generate the stubs.
|
|
generate_stub_file(args.arch, wordsize, args.files, args.output, args.buffer, args.mcs)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|