[project] Generate syscalls from new interface DSL

This change adds a new interface DSL for specifying objects (with
methods) and interfaces (that expose objects, and optionally have their
own methods).

Significant changes:

- Add the new scripts/definitions Python module to parse the DSL
- Add the new definitions directory containing DSL definition files
- Use cog to generate syscall-related code in kernel and libj6
- Unify ordering of pointer + length pairs in interfaces
This commit is contained in:
Justin C. Miller
2021-08-30 01:05:32 -07:00
parent 80f815c020
commit 186724e751
52 changed files with 4025 additions and 206 deletions

View File

@@ -0,0 +1,42 @@
start: import_statement* (object|interface)+
import_statement: "import" PATH
object: description? "object" name options? super? "{" uid method* "}"
interface: description? "interface" name options? "{" uid interface_param* "}"
?interface_param: expose | function
expose: "expose" type
uid: "uid" UID
super: ":" name
function: description? "function" name options? ("{" param* "}")?
method: description? "method" name options? ("{" param* "}")?
param: "param" name type options? description?
?type: PRIMITIVE | object_name
object_name: "object" name
id: NUMBER
name: IDENTIFIER
options: "[" IDENTIFIER+ "]"
description: COMMENT+
PRIMITIVE: INT_TYPE | "size" | "string" | "buffer" | "address"
INT_TYPE: /u?int(8|16|32|64)?/
NUMBER: /(0x)?[0-9a-fA-F]+/
UID: /[0-9a-fA-F]{16}/
COMMENT: /#.*/
PATH: /"[^"]*"/
%import common.LETTER
%import common.CNAME -> IDENTIFIER
%import common.WS
%ignore WS

View File

@@ -0,0 +1,3 @@
object channel : kobject {
uid 3ea38b96aa0e54c8
}

View File

@@ -0,0 +1,30 @@
# Channels are objects that enable synchronous IPC of
# arbitrary-sized messages.
object endpoint : kobject {
uid c5882f24a4c03b7e
method create [constructor]
# Send a message on a channel. Blocks until the message
# is received.
method send {
param tag uint64
param data buffer
}
# Receieve a message on a channel. Blocks until a message
# is available.
method receive {
param tag uint64 [out]
param data buffer [out]
}
# Send a message on a channel and then await a new message.
# Equivalent to calling send and then recieve, as a single
# operation.
method sendrecv {
param tag uint64 [inout]
param data buffer [inout]
}
}

View File

@@ -0,0 +1,25 @@
# The base type of all kernel-exposed objects
object kobject [virtual] {
uid 667f61fb2cd57bb4
# Get the internal kernel object id of an object
method koid {
param koid uint64 [out]
}
# Block the current thread waiting for an object to assert
# one of a set of signals
method wait {
param mask uint64 # Bitmap of which signals to wait for
param signals uint64 [out] # Returns the state of the signals
}
# Block the current thread waiting for an one of multiple
# objects to assert one of a set of signals
method wait_many [static] {
param handles object kobject [list] # The objects to wait on
param mask uint64 # Bitmap of which signals to wait for
param handle object kobject [out] # Returns the object that signalled
param signals uint64 [out] # Returns the state of the signals
}
}

View File

@@ -0,0 +1,33 @@
# Mailboxes are objects that enable asynchronous IPC via event notification.
# This is a second line of documentation
object mailbox {
uid 99934ad04ece1e07
# Create an unbound mailbox
method create [constructor]
method close [destructor]
method bind {
param index uint
param source object kobject
param event uint
}
method unbind {
param index uint
}
method notify {
param index uint
}
method wait {
param bitmap uint64 [out]
}
method ack {
param bitmap uint64
}
}

View File

@@ -0,0 +1,27 @@
import "objects/kobject.def"
# Processes are a collection of handles and a virtual memory
# space inside which threads are run.
object process : kobject {
uid 0c69ee0b7502ba31
# Create a new empty process
method create [constructor]
# Stop all threads and exit the given process
method kill [destructor]
# Stop all threads and exit the current process
method exit [static] {
param result int32 # The result to retrun to the parent process
}
# Start the given process running. Note that the entrypoint
# address must be specified in the address space of the new
# process.
method start {
param entrypoint address # The address of the main thread entrypoint
param handles object kobject [list] # A list of parent handles to send to the child process
}
}

View File

@@ -0,0 +1,29 @@
import "objects/endpoint.def"
import "objects/vma.def"
# The system object represents a handle to kernel functionality
# needed by drivers and other priviledged services
object system : kobject {
uid fa72506a2cf71a30
# Get a log line from the kernel log
method get_log {
param buffer buffer [out] # Buffer for the log message data structure
}
# Ask the kernel to send this process messages whenever
# the given IRQ fires
method bind_irq {
param dest object endpoint # Endpoint that will receive messages
param irq uint # IRQ number to bind
}
# Create a VMA and map an area of physical memory into it,
# also mapping that VMA into the current process
method map_phys {
param area object vma [out] # Receives a handle to the VMA created
param phys address # The physical address of the area
param size size # Size of the area, in pages
param flags uint32 # Flags to apply to the created VMA
}
}

View File

@@ -0,0 +1,18 @@
object thread : kobject {
uid 11f23e593d5761bd
method create [constructor] {
param entrypoint address
}
method kill [destructor]
method exit [static] {
param status int32
}
method pause [static]
method sleep [static] {
param until uint64
}
}

View File

@@ -0,0 +1,29 @@
import "objects/process.def"
object vma : kobject {
uid d6a12b63b3ed3937
method create [constructor] {
param size size
param flags uint32
}
method create_map [constructor] {
param size size
param address address
param flags uint32
}
method map {
param process object process
param address address
}
method unmap {
param process object process
}
method resize {
param size size [inout]
}
}

27
definitions/syscalls.def Normal file
View File

@@ -0,0 +1,27 @@
import "objects/system.def"
import "objects/kobject.def"
import "objects/process.def"
import "objects/thread.def"
import "objects/channel.def"
import "objects/endpoint.def"
import "objects/vma.def"
interface syscalls [syscall] {
uid 01d9b6a948961097
expose object system
expose object kobject
expose object process
expose object thread
expose object channel
expose object endpoint
expose object vma
# Simple no-op syscall for testing
function noop
# Write a message to the kernel log
function log {
param message string
}
}

View File

View File

@@ -0,0 +1,65 @@
class NotFound(Exception): pass
class Context:
def __init__(self, path, verbose=False):
if isinstance(path, str):
self.__paths = [path]
else:
self.__paths = path
self.__closed = set()
self.__verbose = verbose
self.__deps = {}
self.objects = dict()
self.interfaces = dict()
verbose = property(lambda self: self.__verbose)
def find(self, filename):
from os.path import exists, isabs, join
if exists(filename) or isabs(filename):
return filename
for path in self.__paths:
full = join(path, filename)
if exists(full):
return full
raise NotFound(filename)
def parse(self, filename):
pending = set()
pending.add(filename)
from .parser import Lark_StandAlone as Parser
from .transformer import DefTransformer
objects = {}
interfaces = {}
while pending:
name = pending.pop()
self.__closed.add(name)
path = self.find(name)
parser = Parser(transformer=DefTransformer(name))
imps, objs, ints = parser.parse(open(path, "r").read())
objects.update(objs)
interfaces.update(ints)
self.__deps[name] = imps
pending.update(imps.difference(self.__closed))
from .types import ObjectRef
ObjectRef.connect(objects)
self.objects.update(objects)
self.interfaces.update(interfaces)
def deps(self):
return {self.find(k): tuple(map(self.find, v)) for k, v in self.__deps.items()}

View File

@@ -0,0 +1,2 @@
class InvalidType(Exception): pass
class UnknownTypeError(Exception): pass

View File

@@ -0,0 +1,62 @@
def generate_template(template, outfile, **kwargs):
from hashlib import md5
from os import makedirs
from os.path import dirname, exists
content = template.render(**kwargs)
h = md5(content.encode('utf-8')).hexdigest()
if exists(outfile):
existing = open(outfile, 'r').read().encode('utf-8')
if md5(existing).hexdigest() == h:
return False
makedirs(dirname(outfile), exist_ok=True)
open(outfile, 'w').write(content)
return True
def generate(ctx, outdir, template_type):
from os.path import basename, join, split, splitext
from jinja2 import Environment, PackageLoader
for name, interface in ctx.interfaces.items():
base = "_".join(sorted(interface.options))
path = join("templates", base, template_type)
env = Environment(
loader = PackageLoader('definitions', package_path=path),
trim_blocks = True, lstrip_blocks = True)
env.filters
for template_name in env.list_templates():
template = env.get_template(template_name)
basepath, filename = split(template_name)
filename, ext = splitext(filename)
if filename == "_object_":
for obj in ctx.objects.values():
outfile = join(outdir, basepath, obj.name + ext)
wrote = generate_template(
template, outfile,
filename=obj.name + ext,
basepath=basepath,
object=obj,
interface=interface,
objects=ctx.objects)
if wrote and ctx.verbose:
print(f"Writing {outfile}")
else:
outfile = join(outdir, template_name)
wrote = generate_template(
template, outfile,
filename=basename(template_name),
interface=interface,
objects=ctx.objects)
if wrote and ctx.verbose:
print(f"Writing {outfile}")

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
%macro SYSCALL 2
global __syscall_%1
__syscall_%1:
push rbp
mov rbp, rsp
; args should already be in rdi, etc, but rcx will
; get stomped, so stash it in r10, which isn't a
; callee-saved register, but also isn't used in the
; function call ABI.
mov r10, rcx
mov rax, %2
syscall
; result is now already in rax, so just return
pop rbp
ret
%endmacro
{% for id, scope, method in interface.methods %}
SYSCALL {% if scope %}{{ scope.name }}_{% endif %}{{ method.name }} {{ id }}
{% endfor %}

View File

@@ -0,0 +1,41 @@
#pragma once
/// \file {{filename}}
{% if object.super %}
#include <j6/{{ object.super.name }}.hh>
{% endif %}
namespace j6 {
{% if object.desc %}
{{ object.desc|indent('/// ', first=True) }}
{% endif %}
class {{ object.name }}
{% if object.super %} : public {{ object.super.name }}
{% endif %}
{
public:
{% macro argument(type, name, first, options=False) -%}
{%- for ctype, suffix in type.c_names(options) -%}
{%- if not first or not loop.first %}, {% endif %}{{ ctype }} {{ name }}{{ suffix }}
{%- endfor -%}
{%- endmacro -%}
{% for method in object.methods %}
{% if method.desc %} /// {{ method.desc|indent(' /// ') }}{% endif %}
{% for param in method.params %}
{% if param.desc %} /// \arg {{ "%-10s"|format(param.name) }} {{ param.desc }}
{% endif %}
{% endfor %}
{%+ if method.static %}static {% endif %}j6_status_t {{ method.name }} (
{%- for param in method.params %}{{ argument(param.type, param.name, loop.first, options=param.options) }}{% endfor -%});
{% endfor %}
~{{ object.name }}();
private:
{{ object.name }}(j6_handle_t handle) : m_handle {handle} {}
j6_handle_t m_handle;
}
} // namespace j6

View File

@@ -0,0 +1,25 @@
#pragma once
#include <j6/types.h>
#ifdef __cplusplus
extern "C" {
#endif
{% macro argument(type, name, first, options=False) -%}
{%- for ctype, suffix in type.c_names(options) -%}
{%- if not first or not loop.first %}, {% endif %}{{ ctype }} {{ name }}{{ suffix }}
{%- endfor -%}
{%- endmacro %}
{% for id, scope, method in interface.methods %}
j6_status_t __syscall_{% if scope %}{{ scope.name }}_{% endif %}{{ method.name }} (
{%- if not method.static -%}{{ argument(scope.reftype, "self", True) }}{% endif -%}
{%- set first = method.static -%}
{%- for param in method.params %}{{ argument(param.type, param.name, first, options=param.options) }}{% set first = False %}{% endfor -%}
);
{% endfor %}
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,142 @@
from .parser import Transformer, v_args
def get_opts(args):
from .types import Description, Options, Type, UID
kinds = {
Description: "desc",
Options: "opts",
UID: "uid",
Type: "typename",
}
result = dict()
outargs = []
for a in args:
for kind, name in kinds.items():
if isinstance(a, kind):
result[name] = a
break
else:
outargs.append(a)
return result, outargs
class DefTransformer(Transformer):
def __init__(self, filename):
self.filename = filename
def start(self, args):
from .types import Import, Interface, Object
imports = set()
objects = dict()
interfaces = dict()
for o in args:
if isinstance(o, Object):
objects[o.name] = o
elif isinstance(o, Interface):
interfaces[o.name] = o
elif isinstance(o, Import):
imports.add(o)
return imports, objects, interfaces
@v_args(inline=True)
def import_statement(self, path):
from .types import Import
return Import(path)
def object(self, args):
from .types import Object
specials, args = get_opts(args)
name, args = args[0], args[1:]
return Object(name, children=args, **specials)
def interface(self, args):
from .types import Interface
specials, args = get_opts(args)
name, args = args[0], args[1:]
return Interface(name, children=args, **specials)
def method(self, args):
from .types import Method
specials, args = get_opts(args)
name, args = args[0], args[1:]
return Method(name, children=args, **specials)
def function(self, args):
from .types import Function
specials, args = get_opts(args)
name, args = args[0], args[1:]
return Function(name, children=args, **specials)
def param(self, args):
from .types import Param
specials, args = get_opts(args)
name = args[0]
return Param(name, **specials)
@v_args(inline=True)
def expose(self, s):
from .types import Expose
return Expose(s)
@v_args(inline=True)
def uid(self, s):
return s
@v_args(inline=True)
def name(self, s):
return s
@v_args(inline=True)
def type(self, s):
return s
@v_args(inline=True)
def super(self, s):
from .types import ObjectRef
return ObjectRef(s, self.filename)
def options(self, args):
from .types import Options
return Options([str(s) for s in args])
def description(self, s):
from .types import Description
return Description("\n".join(s))
@v_args(inline=True)
def object_name(self, n):
from .types import ObjectRef
return ObjectRef(n, self.filename)
def PRIMITIVE(self, s):
from .types import get_primitive
return get_primitive(s)
def UID(self, s):
from .types import UID
return UID(int(s, base=16))
def INT_TYPE(self, s):
return s
def NUMBER(self, s):
if s.startswith("0x"):
return int(s,16)
return int(s)
def COMMENT(self, s):
return s[2:].strip()
def IDENTIFIER(self, s):
return str(s)
def PATH(self, s):
return str(s[1:-1])

View File

@@ -0,0 +1,23 @@
def _indent(x):
from textwrap import indent
return indent(str(x), ' ')
class Description(str): pass
class Import(str): pass
class Options(set):
def __str__(self):
if not self: return ""
return "[{}]".format(" ".join(self))
class UID(int):
def __str__(self):
return f"{self:016x}"
from .object import Object
from .interface import Interface, Expose
from .function import Function, Method, Param
from .type import Type
from .primitive import get_primitive
from .objref import ObjectRef

View File

@@ -0,0 +1,49 @@
from . import _indent
from . import Options
def _hasopt(opt):
def test(self):
return opt in self.options
return test
class Function:
typename = "function"
def __init__(self, name, opts=Options(), desc="", children=tuple()):
self.name = name
self.options = opts
self.desc = desc
self.params = [c for c in children if isinstance(c, Param)]
self.id = -1
def __str__(self):
parts = ["{} {}".format(self.typename, self.name)]
if self.desc:
parts.append(_indent(self.desc))
if self.options:
parts.append(f" Options: {self.options}")
parts.extend(map(_indent, self.params))
return "\n".join(parts)
static = property(lambda x: True)
constructor = property(lambda x: False)
class Method(Function):
typename = "method"
static = property(_hasopt("static"))
constructor = property(_hasopt("constructor"))
class Param:
def __init__(self, name, typename, opts=Options(), desc=""):
self.name = name
self.type = typename
self.options = opts
self.desc = desc
def __str__(self):
return "param {} {} {} {}".format(
self.name, repr(self.type), self.options, self.desc or "")

View File

@@ -0,0 +1,43 @@
from . import _indent
from . import Options
class Expose(object):
def __init__(self, type):
self.type = type
def __repr__(self):
return f'expose {repr(self.type)}'
class Interface:
def __init__(self, name, uid, opts=Options(), desc="", children=tuple()):
from .function import Function
self.name = name
self.uid = uid
self.options = opts
self.desc = desc
self.functions = [c for c in children if isinstance(c, Function)]
self.exposes = [e.type for e in children if isinstance(e, Expose)]
def __str__(self):
parts = [f"interface {self.name}: {self.uid}"]
if self.desc:
parts.append(_indent(self.desc))
if self.options:
parts.append(f" Options: {self.options}")
parts.extend(map(_indent, self.exposes))
parts.extend(map(_indent, self.functions))
return "\n".join(parts)
def __methods(self):
mm = [(i, None, self.functions[i]) for i in range(len(self.functions))]
base = len(mm)
for o in [e.object for e in self.exposes]:
mm.extend([(base + i, o, o.methods[i]) for i in range(len(o.methods))])
base += len(o.methods)
return mm
methods = property(__methods)

View File

@@ -0,0 +1,25 @@
from . import _indent
from . import Options
class Object:
def __init__(self, name, uid, typename=None, opts=Options(), desc="", children=tuple()):
self.name = name
self.uid = uid
self.options = opts
self.desc = desc
self.super = typename
self.methods = children
from . import ObjectRef
self.__ref = ObjectRef(name)
def __str__(self):
parts = [f"object {self.name}: {self.uid}"]
if self.desc:
parts.append(_indent(self.desc))
if self.options:
parts.append(f" Options: {self.options}")
parts.extend(map(_indent, self.methods))
return "\n".join(parts)
reftype = property(lambda self: self.__ref)

View File

@@ -0,0 +1,44 @@
from .type import Type
class ObjectRef(Type):
all_refs = {}
def __init__(self, name, filename=None):
super().__init__(name)
self.__c_type = "j6_handle_t"
self.__object = None
ObjectRef.all_refs[self] = filename
def __repr__(self):
return f'ObjectRef({self.name})'
object = property(lambda self: self.__object)
def c_names(self, options):
one = self.__c_type
out = bool({"out", "inout"}.intersection(options))
if "list" in options:
two = "size_t"
if out:
one = f"const {one} *"
two += " *"
else:
one = f"{one} *"
return ((one, ""), (two, "_count"))
else:
if out:
one += " *"
return ((one, ""),)
def cxx_names(self, options):
return self.c_names(options)
@classmethod
def connect(cls, objects):
for ref, filename in cls.all_refs.items():
ref.__object = objects.get(ref.name)
if ref.__object is None:
from ..errors import UnknownTypeError
raise UnknownTypeError(ref.name, filename)

View File

@@ -0,0 +1,70 @@
from .type import Type
class Primitive(Type):
def __init__(self, name, c_type):
super().__init__(name)
self.c_type = c_type
def __repr__(self):
return f'Primitive({self.name})'
def c_names(self, options=None):
one = self.c_type
if options and "out" in options or "inout" in options:
one += " *"
return ((one, ""),)
def cxx_names(self, options):
return self.c_names(options)
class PrimitiveRef(Primitive):
def __init__(self, name, c_type, counted=False):
super().__init__(name, c_type)
self.__counted = counted
def c_names(self, options=None):
one = f"{self.c_type} *"
two = "size_t"
if options and "out" in options or "inout" in options:
two += " *"
else:
one = "const " + one
if self.__counted:
return ((one, ""), (two, "_len"))
else:
return ((one, ""),)
def cxx_names(self, options):
return self.c_names(options)
_primitives = {
"string": PrimitiveRef("string", "char"),
"buffer": PrimitiveRef("buffer", "void", counted=True),
"int": Primitive("int", "int"),
"uint": Primitive("uint", "unsigned"),
"size": Primitive("size", "size_t"),
"address": Primitive("address", "uintptr_t"),
"int8": Primitive("int8", "int8_t"),
"uint8": Primitive("uint8", "uint8_t"),
"int16": Primitive("int16", "int16_t"),
"uint16": Primitive("uint16", "uint16_t"),
"int32": Primitive("int32", "int32_t"),
"uint32": Primitive("uint32", "uint32_t"),
"int64": Primitive("int64", "int64_t"),
"uint64": Primitive("uint64", "uint64_t"),
}
def get_primitive(name):
p = _primitives.get(name)
if not p:
from ..errors import InvalidType
raise InvalidType(name)
return p

View File

@@ -0,0 +1,12 @@
class Type:
def __init__(self, name):
self.__name = name
name = property(lambda self: self.__name)
def c_names(self, options):
raise NotImplemented("Call to base Type.c_names")
def cxx_names(self, options):
raise NotImplemented("Call to base Type.c_names")

11
scripts/idgen Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python3
def hashid(s):
from hashlib import shake_128 as sh
return sh(s.encode('utf-8')).hexdigest(8)
import sys
for arg in sys.argv[1:]:
id = hashid(arg)
print(f"{arg}: {id}")

View File

@@ -14,7 +14,6 @@ kernel = module("kernel",
"clock.cpp",
"console.cpp",
"cpprt.cpp",
"cpu.cpp",
"debug.cpp",
"debug.s",
"device_manager.cpp",
@@ -27,7 +26,6 @@ kernel = module("kernel",
"interrupts.s",
"io.cpp",
"log.cpp",
"main.cpp",
"memory_bootstrap.cpp",
"msr.cpp",
"objects/channel.cpp",
@@ -42,8 +40,6 @@ kernel = module("kernel",
"pci.cpp",
"scheduler.cpp",
"serial.cpp",
"syscall.cpp",
"syscall.s",
"syscalls/channel.cpp",
"syscalls/endpoint.cpp",
"syscalls/object.cpp",
@@ -56,4 +52,15 @@ kernel = module("kernel",
"vm_space.cpp",
])
from glob import glob
definitions = glob('definitions/**/*.def', recursive=True)
sysinc = kernel.add_input("syscalls.inc.cog", deps=definitions)
kernel.add_input("syscall.s", deps=[sysinc])
sysh = kernel.add_input("syscall.h.cog", deps=definitions)
sysc = kernel.add_input("syscall.cpp.cog", deps=definitions + [sysh])
kernel.add_input("main.cpp", deps=[sysh])
kernel.add_input("cpu.cpp", deps=[sysh])
kernel.variables['ldflags'] = ["${ldflags}", "-T", "${source_root}/src/kernel/kernel.ld"]

View File

@@ -28,10 +28,10 @@ endpoint::close()
}
j6_status_t
endpoint::send(j6_tag_t tag, size_t len, void *data)
endpoint::send(j6_tag_t tag, const void *data, size_t data_len)
{
thread_data sender = { &thread::current(), data };
sender.len = len;
sender.len = data_len;
sender.tag = tag;
if (!check_signal(j6_signal_endpoint_can_send)) {
@@ -55,11 +55,11 @@ endpoint::send(j6_tag_t tag, size_t len, void *data)
}
j6_status_t
endpoint::receive(j6_tag_t *tag, size_t *len, void *data)
endpoint::receive(j6_tag_t *tag, void *data, size_t *data_len)
{
thread_data receiver = { &thread::current(), data };
receiver.tag_p = tag;
receiver.len_p = len;
receiver.len_p = data_len;
if (!check_signal(j6_signal_endpoint_can_recv)) {
assert_signal(j6_signal_endpoint_can_send);
@@ -120,7 +120,7 @@ endpoint::do_message_copy(const endpoint::thread_data &sender, endpoint::thread_
if (sender.len) {
vm_space &source = sender.th->parent().space();
vm_space &dest = receiver.th->parent().space();
vm_space::copy(source, dest, sender.data, receiver.data, sender.len);
vm_space::copy(source, dest, sender.data, receiver.buffer, sender.len);
}
*receiver.len_p = sender.len;

View File

@@ -27,10 +27,10 @@ public:
/// Send a message to a thread waiting to receive on this endpoint. If no threads
/// are currently trying to receive, block the current thread.
/// \arg tag The application-specified message tag
/// \arg len The size in bytes of the message
/// \arg data The message data
/// \arg len The size in bytes of the message
/// \returns j6_status_ok on success
j6_status_t send(j6_tag_t tag, size_t len, void *data);
j6_status_t send(j6_tag_t tag, const void *data, size_t data_len);
/// Receive a message from a thread waiting to send on this endpoint. If no threads
/// are currently trying to send, block the current thread.
@@ -38,7 +38,7 @@ public:
/// \arg len [in] The size in bytes of the buffer [out] Number of bytes in the message
/// \arg data Buffer for copying message data into
/// \returns j6_status_ok on success
j6_status_t receive(j6_tag_t *tag, size_t *len, void *data);
j6_status_t receive(j6_tag_t *tag, void *data, size_t *data_len);
/// Give the listener on the endpoint a message that a bound IRQ has been signalled
/// \arg irq The IRQ that caused this signal
@@ -48,7 +48,10 @@ private:
struct thread_data
{
thread *th;
void *data;
union {
const void *data;
void *buffer;
};
union {
j6_tag_t *tag_p;
j6_tag_t tag;

View File

@@ -1,52 +0,0 @@
#include <stddef.h>
#include "kutil/memory.h"
#include "console.h"
#include "debug.h"
#include "log.h"
#include "syscall.h"
extern "C" {
void syscall_invalid(uint64_t call);
}
uintptr_t syscall_registry[256] __attribute__((section(".syscall_registry")));
const char * syscall_names[256] __attribute__((section(".syscall_registry")));
static constexpr size_t num_syscalls = sizeof(syscall_registry) / sizeof(syscall_registry[0]);
void
syscall_invalid(uint64_t call)
{
console *cons = console::get();
cons->set_color(9);
cons->printf("\nReceived unknown syscall: %02x\n", call);
cons->printf(" Known syscalls:\n");
cons->printf(" invalid %016lx\n", syscall_invalid);
for (unsigned i = 0; i < num_syscalls; ++i) {
const char *name = syscall_names[i];
uintptr_t handler = syscall_registry[i];
if (name)
cons->printf(" %02x %10s %016lx\n", i, name, handler);
}
cons->set_color();
_halt();
}
void
syscall_initialize()
{
kutil::memset(&syscall_registry, 0, sizeof(syscall_registry));
kutil::memset(&syscall_names, 0, sizeof(syscall_names));
#define SYSCALL(id, name, result, ...) \
syscall_registry[id] = reinterpret_cast<uintptr_t>(syscalls::name); \
syscall_names[id] = #name; \
log::debug(logs::syscall, "Enabling syscall 0x%02x as " #name , id);
#include "j6/tables/syscalls.inc"
#undef SYSCALL
}

View File

@@ -0,0 +1,56 @@
// vim: ft=cpp
#include <stddef.h>
#include "kutil/memory.h"
#include "console.h"
#include "debug.h"
#include "log.h"
#include "syscall.h"
extern "C" {
void syscall_invalid(uint64_t call);
}
/*[[[cog code generation
from definitions.context import Context
ctx = Context(definitions_path)
ctx.parse("syscalls.def")
syscalls = ctx.interfaces['syscalls']
cog.outl(f"constexpr size_t num_syscalls = {len(syscalls.methods)};")
]]]*/
/// [[[end]]]
uintptr_t syscall_registry[num_syscalls] __attribute__((section(".syscall_registry")));
void
syscall_invalid(uint64_t call)
{
console *cons = console::get();
cons->set_color(9);
cons->printf("\nReceived unknown syscall: %02x\n", call);
cons->set_color();
_halt();
}
void
syscall_initialize()
{
kutil::memset(&syscall_registry, 0, sizeof(syscall_registry));
/*[[[cog code generation
for id, scope, method in syscalls.methods:
if scope:
name = f"{scope.name}_{method.name}"
else:
name = method.name
cog.outl(f"syscall_registry[{id}] = reinterpret_cast<uintptr_t>(syscalls::{name});")
cog.outl(f"""log::debug(logs::syscall, "Enabling syscall {id:x} as {name}");""")
cog.outl("")
]]]*/
//[[[end]]]
}

View File

@@ -1,23 +0,0 @@
#pragma once
#include <stdint.h>
#include "j6/types.h"
struct cpu_state;
enum class syscall : uint64_t
{
#define SYSCALL(id, name, ...) name = id,
#include "j6/tables/syscalls.inc"
#undef SYSCALL
};
void syscall_initialize();
extern "C" void syscall_enable();
namespace syscalls
{
#define SYSCALL(id, name, ...) j6_status_t name (__VA_ARGS__);
#include "j6/tables/syscalls.inc"
#undef SYSCALL
}

60
src/kernel/syscall.h.cog Normal file
View File

@@ -0,0 +1,60 @@
#pragma once
// vim: ft=cpp
#include <stdint.h>
#include "j6/types.h"
struct cpu_state;
/*[[[cog code generation
from definitions.context import Context
ctx = Context(definitions_path)
ctx.parse("syscalls.def")
syscalls = ctx.interfaces["syscalls"]
]]]*/
/// [[[end]]]
enum class syscall : uint64_t
{
/*[[[cog code generation
for id, scope, method in syscalls.methods:
if scope:
name = f"{scope.name}_{method.name}"
else:
name = method.name
cog.outl(f"{name:20} = {id},")
]]]*/
//[[[end]]]
};
void syscall_initialize();
extern "C" void syscall_enable();
namespace syscalls
{
/*[[[cog code generation
for id, scope, method in syscalls.methods:
if scope:
name = f"{scope.name}_{method.name}"
else:
name = method.name
args = []
if method.constructor:
args.append("j6_handle_t *handle")
elif not method.static:
args.append("j6_handle_t handle")
for param in method.params:
for type, suffix in param.type.c_names(param.options):
args.append(f"{type} {param.name}{suffix}")
cog.outl(f"""j6_status_t {name} ({", ".join(args)});""")
]]]*/
//[[[end]]]
}

View File

@@ -1,4 +1,5 @@
%include "tasking.inc"
%include "syscalls.inc"
; SYSCALL/SYSRET control MSRs
MSR_STAR equ 0xc0000081
@@ -51,7 +52,9 @@ syscall_handler_prelude:
inc qword [rel __counter_syscall_enter]
and rax, 0xff ; Only 256 possible syscall values
cmp rax, NUM_SYSCALLS
jge .bad_syscall
lea r11, [rel syscall_registry]
mov r11, [r11 + rax * 8]
cmp r11, 0

View File

@@ -0,0 +1,11 @@
; vim: ft=asm
; [[[cog code generation
; from definitions.context import Context
;
; ctx = Context(definitions_path)
; ctx.parse("syscalls.def")
; syscalls = ctx.interfaces['syscalls']
; cog.outl(f"NUM_SYSCALLS equ {len(syscalls.methods)}")
; ]]]
; [[[end]]]

View File

@@ -15,7 +15,7 @@ endpoint_create(j6_handle_t *handle)
}
j6_status_t
endpoint_send(j6_handle_t handle, j6_tag_t tag, size_t len, void *data)
endpoint_send(j6_handle_t handle, uint64_t tag, const void * data, size_t data_len)
{
if (tag & j6_tag_system_flag)
return j6_err_invalid_arg;
@@ -23,28 +23,28 @@ endpoint_send(j6_handle_t handle, j6_tag_t tag, size_t len, void *data)
endpoint *e = get_handle<endpoint>(handle);
if (!e) return j6_err_invalid_arg;
return e->send(tag, len, data);
return e->send(tag, data, data_len);
}
j6_status_t
endpoint_receive(j6_handle_t handle, j6_tag_t *tag, size_t *len, void *data)
endpoint_receive(j6_handle_t handle, uint64_t * tag, void * data, size_t * data_len)
{
if (!tag || !len || (*len && !data))
if (!tag || !data_len || !data)
return j6_err_invalid_arg;
endpoint *e = get_handle<endpoint>(handle);
if (!e) return j6_err_invalid_arg;
j6_tag_t out_tag = j6_tag_invalid;
size_t out_len = *len;
j6_status_t s = e->receive(&out_tag, &out_len, data);
size_t out_len = *data_len;
j6_status_t s = e->receive(&out_tag, data, &out_len);
*tag = out_tag;
*len = out_len;
*data_len = out_len;
return s;
}
j6_status_t
endpoint_sendrecv(j6_handle_t handle, j6_tag_t *tag, size_t *len, void *data)
endpoint_sendrecv(j6_handle_t handle, uint64_t * tag, void * data, size_t * data_len)
{
if (!tag || (*tag & j6_tag_system_flag))
return j6_err_invalid_arg;
@@ -52,15 +52,15 @@ endpoint_sendrecv(j6_handle_t handle, j6_tag_t *tag, size_t *len, void *data)
endpoint *e = get_handle<endpoint>(handle);
if (!e) return j6_err_invalid_arg;
j6_status_t status = e->send(*tag, *len, data);
j6_status_t status = e->send(*tag, data, *data_len);
if (status != j6_status_ok)
return status;
j6_tag_t out_tag = j6_tag_invalid;
size_t out_len = *len;
j6_status_t s = e->receive(&out_tag, &out_len, data);
size_t out_len = *data_len;
j6_status_t s = e->receive(&out_tag, data, &out_len);
*tag = out_tag;
*len = out_len;
*data_len = out_len;
return s;
}

View File

@@ -9,7 +9,7 @@
namespace syscalls {
j6_status_t
object_koid(j6_handle_t handle, j6_koid_t *koid)
kobject_koid(j6_handle_t handle, j6_koid_t *koid)
{
if (koid == nullptr)
return j6_err_invalid_arg;
@@ -23,7 +23,7 @@ object_koid(j6_handle_t handle, j6_koid_t *koid)
}
j6_status_t
object_wait(j6_handle_t handle, j6_signal_t mask, j6_signal_t *sigs)
kobject_wait(j6_handle_t handle, j6_signal_t mask, j6_signal_t *sigs)
{
kobject *obj = get_handle<kobject>(handle);
if (!obj)
@@ -47,11 +47,11 @@ object_wait(j6_handle_t handle, j6_signal_t mask, j6_signal_t *sigs)
}
j6_status_t
object_wait_many(j6_handle_t *handles, uint32_t count, j6_signal_t mask, j6_handle_t *handle, j6_signal_t *sigs)
kobject_wait_many(j6_handle_t * handles, size_t handles_count, uint64_t mask, j6_handle_t * handle, uint64_t * signals)
{
kutil::vector<kobject*> objects {count};
kutil::vector<kobject*> objects {uint32_t(handles_count)};
for (unsigned i = 0; i < count; ++i) {
for (unsigned i = 0; i < handles_count; ++i) {
j6_handle_t h = handles[i];
if (h == j6_handle_invalid)
continue;
@@ -62,7 +62,7 @@ object_wait_many(j6_handle_t *handles, uint32_t count, j6_signal_t mask, j6_hand
j6_signal_t current = obj->signals();
if ((current & mask) != 0) {
*sigs = current;
*signals = current;
*handle = h;
return j6_status_ok;
}
@@ -81,9 +81,9 @@ object_wait_many(j6_handle_t *handles, uint32_t count, j6_signal_t mask, j6_hand
return result;
*handle = j6_handle_invalid;
*sigs = th.get_wait_data();
*signals = th.get_wait_data();
j6_koid_t koid = th.get_wait_object();
for (unsigned i = 0; i < count; ++i) {
for (unsigned i = 0; i < handles_count; ++i) {
if (koid == objects[i]->koid())
*handle = handles[i];
else
@@ -96,7 +96,7 @@ object_wait_many(j6_handle_t *handles, uint32_t count, j6_signal_t mask, j6_hand
}
j6_status_t
object_signal(j6_handle_t handle, j6_signal_t signals)
kobject_signal(j6_handle_t handle, j6_signal_t signals)
{
if ((signals & j6_signal_user_mask) != signals)
return j6_err_invalid_arg;
@@ -110,7 +110,7 @@ object_signal(j6_handle_t handle, j6_signal_t signals)
}
j6_status_t
object_close(j6_handle_t handle)
kobject_close(j6_handle_t handle)
{
kobject *obj = get_handle<kobject>(handle);
if (!obj)

View File

@@ -16,14 +16,14 @@ process_create(j6_handle_t *handle)
}
j6_status_t
process_start(j6_handle_t handle, uintptr_t entrypoint, j6_handle_t *handles, size_t handle_count)
process_start(j6_handle_t handle, uintptr_t entrypoint, j6_handle_t * handles, size_t handles_count)
{
process &p = process::current();
process *c = get_handle<process>(handle);
if (handle_count && !handles)
if (handles_count && !handles)
return j6_err_invalid_arg;
for (size_t i = 0; i < handle_count; ++i) {
for (size_t i = 0; i < handles_count; ++i) {
kobject *o = p.lookup_handle(handles[i]);
if (o) c->add_handle(o);
}

View File

@@ -15,7 +15,7 @@ extern log::logger &g_logger;
namespace syscalls {
j6_status_t
system_log(const char *message)
log(const char *message)
{
if (message == nullptr)
return j6_err_invalid_arg;
@@ -26,7 +26,7 @@ system_log(const char *message)
}
j6_status_t
system_noop()
noop()
{
thread &th = thread::current();
log::debug(logs::syscall, "Thread %llx called noop syscall.", th.koid());
@@ -61,18 +61,18 @@ system_bind_irq(j6_handle_t sys, j6_handle_t endp, unsigned irq)
}
j6_status_t
system_map_phys(j6_handle_t sys, j6_handle_t *vma_handle, uintptr_t phys_addr, size_t size, uint32_t flags)
system_map_phys(j6_handle_t handle, j6_handle_t * area, uintptr_t phys, size_t size, uint32_t flags)
{
// TODO: check capabilities on sys handle
if (!vma_handle) return j6_err_invalid_arg;
if (!area) return j6_err_invalid_arg;
// TODO: check to see if frames are already used? How would that collide with
// the bootloader's allocated pages already being marked used?
if (!(flags & vm_flags::mmio))
frame_allocator::get().used(phys_addr, memory::page_count(size));
frame_allocator::get().used(phys, memory::page_count(size));
vm_flags vmf = (static_cast<vm_flags>(flags) & vm_flags::driver_mask);
construct_handle<vm_area_fixed>(vma_handle, phys_addr, size, vmf);
construct_handle<vm_area_fixed>(area, phys, size, vmf);
return j6_status_ok;
}

View File

@@ -4,17 +4,18 @@
#include "log.h"
#include "objects/process.h"
#include "objects/thread.h"
#include "syscalls/helpers.h"
namespace syscalls {
j6_status_t
thread_create(void *rip, j6_handle_t *handle)
thread_create(j6_handle_t *handle, uintptr_t entrypoint)
{
thread &parent = thread::current();
process &p = parent.parent();
thread *child = p.create_thread();
child->add_thunk_user(reinterpret_cast<uintptr_t>(rip));
child->add_thunk_user(entrypoint);
*handle = child->self_handle();
child->clear_state(thread::state::loading);
child->set_state(thread::state::ready);
@@ -36,6 +37,18 @@ thread_exit(int32_t status)
return j6_err_unexpected;
}
j6_status_t
thread_kill(j6_handle_t handle)
{
thread *th = get_handle<thread>(handle);
if (!th)
return j6_err_invalid_arg;
log::debug(logs::task, "Killing thread %llx", th->koid());
th->exit(-1);
return j6_status_ok;
}
j6_status_t
thread_pause()
{

View File

@@ -286,7 +286,7 @@ vm_space::handle_fault(uintptr_t addr, fault_type fault)
}
size_t
vm_space::copy(vm_space &source, vm_space &dest, void *from, void *to, size_t length)
vm_space::copy(vm_space &source, vm_space &dest, const void *from, void *to, size_t length)
{
uintptr_t ifrom = reinterpret_cast<uintptr_t>(from);
uintptr_t ito = reinterpret_cast<uintptr_t>(to);

View File

@@ -103,7 +103,7 @@ public:
/// \arg to Pointer to the destination in the dest address space
/// \arg length Amount of data to copy, in bytes
/// \returnd The number of bytes copied
static size_t copy(vm_space &source, vm_space &dest, void *from, void *to, size_t length);
static size_t copy(vm_space &source, vm_space &dest, const void *from, void *to, size_t length);
private:
friend class vm_area;

View File

@@ -1,15 +0,0 @@
#pragma once
#include <j6/types.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SYSCALL(n, name, ...) j6_status_t j6_ ## name (__VA_ARGS__);
#include "j6/tables/syscalls.inc"
#undef SYSCALL
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,39 @@
#pragma once
// vim: ft=cpp
#include <j6/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/*[[[cog code generation
from definitions.context import Context
ctx = Context(definitions_path)
ctx.parse("syscalls.def")
syscalls = ctx.interfaces["syscalls"]
for id, scope, method in syscalls.methods:
if scope:
name = f"{scope.name}_{method.name}"
else:
name = method.name
args = []
if method.constructor:
args.append("j6_handle_t *handle")
elif not method.static:
args.append("j6_handle_t handle")
for param in method.params:
for type, suffix in param.type.c_names(param.options):
args.append(f"{type} {param.name}{suffix}")
cog.outl(f"""j6_status_t j6_{name} ({", ".join(args)});""")
]]]*/
/// [[[end]]]
#ifdef __cplusplus
}
#endif

View File

@@ -1,9 +1,14 @@
# vim: ft=python
module("j6",
j6 = module("j6",
kind = "lib",
includes = [ "include" ],
sources = [
"init.cpp",
"syscalls.s",
])
from glob import glob
definitions = glob('definitions/**/*.def', recursive=True)
j6.add_input("include/j6/syscalls.h.cog", deps=definitions)
j6.add_input("syscalls.s.cog", deps=definitions)

View File

@@ -1,28 +0,0 @@
%macro SYSCALL 2
global j6_%1
j6_%1:
push rbp
mov rbp, rsp
; args should already be in rdi, etc, but rcx will
; get stomped, so stash it in r10, which isn't a
; callee-saved register, but also isn't used in the
; function call ABI.
mov r10, rcx
mov rax, %2
syscall
; result is now already in rax, so just return
pop rbp
ret
%endmacro
%define SYSCALL(n, name) SYSCALL name, n
%define SYSCALL(n, name, a) SYSCALL name, n
%define SYSCALL(n, name, a, b) SYSCALL name, n
%define SYSCALL(n, name, a, b, c) SYSCALL name, n
%define SYSCALL(n, name, a, b, c, d) SYSCALL name, n
%define SYSCALL(n, name, a, b, c, d, e) SYSCALL name, n
%include "j6/tables/syscalls.inc"

View File

@@ -0,0 +1,37 @@
; vim: ft=asm
%macro define_syscall 2
global j6_%1
j6_%1:
push rbp
mov rbp, rsp
; args should already be in rdi, etc, but rcx will
; get stomped, so stash it in r10, which isn't a
; callee-saved register, but also isn't used in the
; function call ABI.
mov r10, rcx
mov rax, %2
syscall
; result is now already in rax, so just return
pop rbp
ret
%endmacro
; [[[cog code generation
; from definitions.context import Context
;
; ctx = Context(definitions_path)
; ctx.parse("syscalls.def")
; syscalls = ctx.interfaces['syscalls']
;
; for id, scope, method in syscalls.methods:
; if scope:
; name = f"{scope.name}_{method.name}"
; else:
; name = method.name
; cog.outl(f"define_syscall {name:20}, {id}")
; ]]]
; [[[end]]]

View File

@@ -33,7 +33,7 @@ struct entry
int
main(int argc, const char **argv)
{
j6_system_log("fb driver starting");
j6_log("fb driver starting");
size_t initc = 0;
j6_init_value *initv = nullptr;
@@ -48,7 +48,7 @@ main(int argc, const char **argv)
}
if (!fb || fb->addr == 0) {
j6_system_log("fb driver didn't find a framebuffer, exiting");
j6_log("fb driver didn't find a framebuffer, exiting");
return 1;
}
@@ -109,7 +109,7 @@ main(int argc, const char **argv)
buffer_size = size;
continue;
} else if (s != j6_status_ok) {
j6_system_log("fb driver got error from get_log, quitting");
j6_log("fb driver got error from get_log, quitting");
return s;
}
@@ -134,7 +134,7 @@ main(int argc, const char **argv)
}
}
j6_system_log("fb driver done, exiting");
j6_log("fb driver done, exiting");
return 0;
}

View File

@@ -20,7 +20,7 @@ j6_handle_t handle_system = 2; // boot protocol is that init gets the system as
int
main(int argc, const char **argv)
{
j6_system_log("srv.init starting");
j6_log("srv.init starting");
modules mods = modules::load_modules(_arg_modules_phys, handle_system, handle_self);
@@ -28,7 +28,7 @@ main(int argc, const char **argv)
for (auto &mod : mods.of_type(module_type::program)) {
auto &prog = static_cast<const module_program&>(mod);
sprintf(message, " program module '%s' at %lx", prog.filename, prog.base_address);
j6_system_log(message);
j6_log(message);
}
return 0;

View File

@@ -70,7 +70,7 @@ modules::load_modules(uintptr_t address, j6_handle_t system, j6_handle_t self)
char message[100];
sprintf(message, "srv.init found %d modules from page at 0x%lx", page->count, address);
j6_system_log(message);
j6_log(message);
if (!first)
first = page->modules;

View File

@@ -20,32 +20,32 @@ extern "C" {
void
thread_proc()
{
j6_system_log("sub thread starting");
j6_log("sub thread starting");
char buffer[512];
size_t len = sizeof(buffer);
j6_tag_t tag = 0;
j6_status_t result = j6_endpoint_receive(endp, &tag, &len, (void*)buffer);
j6_status_t result = j6_endpoint_receive(endp, &tag, (void*)buffer, &len);
if (result != j6_status_ok)
j6_thread_exit(result);
j6_system_log("sub thread received message");
j6_log("sub thread received message");
for (int i = 0; i < len; ++i)
if (buffer[i] >= 'A' && buffer[i] <= 'Z')
buffer[i] += 0x20;
tag++;
result = j6_endpoint_send(endp, tag, len, (void*)buffer);
result = j6_endpoint_send(endp, tag, (void*)buffer, len);
if (result != j6_status_ok)
j6_thread_exit(result);
j6_system_log("sub thread sent message");
j6_log("sub thread sent message");
for (int i = 1; i < 5; ++i)
j6_thread_sleep(i*10);
j6_system_log("sub thread exiting");
j6_log("sub thread exiting");
j6_thread_exit(0);
}
@@ -55,10 +55,10 @@ main(int argc, const char **argv)
j6_handle_t children[2] = {j6_handle_invalid, j6_handle_invalid};
j6_signal_t out = 0;
j6_system_log("main thread starting");
j6_log("main thread starting");
for (int i = 0; i < argc; ++i)
j6_system_log(argv[i]);
j6_log(argv[i]);
void *base = malloc(0x1000);
if (!base)
@@ -68,57 +68,57 @@ main(int argc, const char **argv)
for (int i = 0; i < 3; ++i)
vma_ptr[i*100] = uint64_t(i);
j6_system_log("main thread wrote to memory area");
j6_log("main thread wrote to memory area");
j6_status_t result = j6_endpoint_create(&endp);
if (result != j6_status_ok)
return result;
j6_system_log("main thread created endpoint");
j6_log("main thread created endpoint");
result = j6_thread_create(reinterpret_cast<void*>(&thread_proc), &children[1]);
result = j6_thread_create(&children[1], reinterpret_cast<uintptr_t>(&thread_proc));
if (result != j6_status_ok)
return result;
j6_system_log("main thread created sub thread");
j6_log("main thread created sub thread");
char message[] = "MAIN THREAD SUCCESSFULLY CALLED SENDRECV IF THIS IS LOWERCASE";
size_t size = sizeof(message);
j6_tag_t tag = 16;
result = j6_endpoint_sendrecv(endp, &tag, &size, (void*)message);
result = j6_endpoint_sendrecv(endp, &tag, (void*)message, &size);
if (result != j6_status_ok)
return result;
if (tag != 17)
j6_system_log("GOT WRONG TAG FROM SENDRECV");
j6_log("GOT WRONG TAG FROM SENDRECV");
result = j6_system_bind_irq(__handle_sys, endp, 3);
if (result != j6_status_ok)
return result;
j6_system_log(message);
j6_log(message);
j6_system_log("main thread creating a new process");
j6_log("main thread creating a new process");
result = j6_process_create(&children[0]);
if (result != j6_status_ok)
return result;
j6_system_log("main thread waiting on children");
j6_log("main thread waiting on children");
j6_handle_t outhandle;
result = j6_object_wait_many(children, 2, -1ull, &outhandle, &out);
result = j6_kobject_wait_many(children, 2, -1ull, &outhandle, &out);
if (result != j6_status_ok)
return result;
if (outhandle == children[1]) {
j6_system_log(" ok from child thread");
j6_log(" ok from child thread");
} else if (outhandle == children[0]) {
j6_system_log(" ok from child process");
j6_log(" ok from child process");
} else {
j6_system_log(" ... got unknown handle");
j6_log(" ... got unknown handle");
}
j6_system_log("main testing irqs");
j6_log("main testing irqs");
serial_port com2(COM2);
@@ -137,16 +137,10 @@ main(int argc, const char **argv)
return result;
if (j6_tag_is_irq(tag))
j6_system_log("main thread got irq!");
j6_log("main thread got irq!");
}
j6_system_log("main thread closing endpoint");
result = j6_object_close(endp);
if (result != j6_status_ok)
return result;
j6_system_log("main thread done, exiting");
j6_log("main thread done, exiting");
return 0;
}