ipc: Add ability to do variable length calls

This commit is contained in:
Jakob Bornecrantz 2023-11-02 00:56:45 +00:00
parent 6d990ba480
commit e576424995
2 changed files with 177 additions and 86 deletions

View file

@ -241,6 +241,18 @@ class Call:
for arg in self.out_args:
arg.dump()
def write_send_decl(self, f):
"""Write declaration of ipc_send_CALLNAME_locked."""
args = ["struct ipc_connection *ipc_c"]
args.extend(arg.get_func_argument_in() for arg in self.in_args)
write_decl(f, 'xrt_result_t', 'ipc_send_' + self.name + "_locked", args)
def write_receive_decl(self, f):
"""Write declaration of ipc_receive_CALLNAME_locked."""
args = ["struct ipc_connection *ipc_c"]
args.extend(arg.get_func_argument_out() for arg in self.out_args)
write_decl(f, 'xrt_result_t', 'ipc_receive_' + self.name + "_locked", args)
def write_call_decl(self, f):
"""Write declaration of ipc_call_CALLNAME."""
args = ["struct ipc_connection *ipc_c"]
@ -255,12 +267,19 @@ class Call:
def write_handler_decl(self, f):
"""Write declaration of ipc_handle_CALLNAME."""
args = ["volatile struct ipc_client_state *ics"]
# Always get in arguments.
args.extend(arg.get_func_argument_in() for arg in self.in_args)
args.extend(arg.get_func_argument_out() for arg in self.out_args)
# Handle sending reply in the function itself.
if not self.varlen:
args.extend(arg.get_func_argument_out() for arg in self.out_args)
if self.out_handles:
args.extend(self.out_handles.handler_arg_decls)
if self.in_handles:
args.extend(self.in_handles.const_arg_decls)
write_decl(f, 'xrt_result_t', 'ipc_handle_' + self.name, args)
@property
@ -276,6 +295,7 @@ class Call:
self.out_args = []
self.in_handles = None
self.out_handles = None
self.varlen = False
for key, val in data.items():
if key == 'id':
self.id = val
@ -287,10 +307,14 @@ class Call:
self.out_handles = HandleType(val)
elif key == 'in_handles':
self.in_handles = HandleType(val)
elif key == 'varlen':
self.varlen = val
else:
raise RuntimeError("Unrecognized key")
if not self.id:
self.id = "IPC_" + name.upper()
if self.varlen and (self.in_handles or self.out_handles):
raise Exception("Can not have handles with varlen functions")
class Proto:

View file

@ -21,6 +21,116 @@ header = '''// Copyright 2020-2023, Collabora, Ltd.
'''
def write_send_definition(f, call):
"""Write a ipc_send_CALLNAME_locked function."""
call.write_send_decl(f)
f.write("\n{\n")
f.write("\tIPC_TRACE(ipc_c, \"Sending " + call.name + "\");\n\n")
write_msg_struct(f, call, '\t')
write_msg_send(f, 'xrt_result_t ret', indent="\t")
f.write("\n\treturn ret;\n}\n")
def write_receive_definition(f, call):
"""Write a ipc_receive_CALLNAME_locked function."""
call.write_receive_decl(f)
f.write("\n{\n")
f.write("\tIPC_TRACE(ipc_c, \"Receiving " + call.name + "\");\n\n")
write_reply_struct(f, call, '\t')
f.write("\n\t// Await the reply")
func = 'ipc_receive'
args = ['&ipc_c->imc', '&_reply', 'sizeof(_reply)']
write_invocation(f, 'xrt_result_t ret', func, args, indent="\t")
f.write(";")
write_result_handler(f, 'ret', None, indent="\t")
for arg in call.out_args:
f.write("\t*out_" + arg.name + " = _reply." + arg.name + ";\n")
f.write("\n\treturn _reply.result;\n}\n")
def write_call_definition(f, call):
"""Write a ipc_call_CALLNAME function."""
call.write_call_decl(f)
f.write("\n{\n")
f.write("\tIPC_TRACE(ipc_c, \"Calling " + call.name + "\");\n\n")
write_msg_struct(f, call, '\t')
write_reply_struct(f, call, '\t')
f.write("""
\t// Other threads must not read/write the fd while we wait for reply
\tos_mutex_lock(&ipc_c->mutex);
""")
cleanup = "os_mutex_unlock(&ipc_c->mutex);"
# Prepare initial sending
write_msg_send(f, 'xrt_result_t ret', indent="\t")
write_result_handler(f, 'ret', cleanup, indent="\t")
if call.in_handles:
f.write("\n\t// Send our handles separately\n")
f.write("\n\t// Wait for server sync")
# Must sync with the server so it's expecting the next message.
write_invocation(
f,
'ret',
'ipc_receive',
(
'&ipc_c->imc',
'&_sync',
'sizeof(_sync)'
),
indent="\t"
)
f.write(';')
write_result_handler(f, 'ret', cleanup, indent="\t")
# Must send these in a second message
# since the server doesn't know how many to expect.
f.write("\n\t// We need this message data as filler only\n")
f.write("\tstruct ipc_command_msg _handle_msg = {\n")
f.write("\t .cmd = " + str(call.id) + ",\n")
f.write("\t};\n")
write_invocation(
f,
'ret',
'ipc_send_handles_' + call.in_handles.stem,
(
'&ipc_c->imc',
"&_handle_msg",
"sizeof(_handle_msg)",
call.in_handles.arg_name,
call.in_handles.count_arg_name
),
indent="\t"
)
f.write(';')
write_result_handler(f, 'ret', cleanup, indent="\t")
f.write("\n\t// Await the reply")
func = 'ipc_receive'
args = ['&ipc_c->imc', '&_reply', 'sizeof(_reply)']
if call.out_handles:
func += '_handles_' + call.out_handles.stem
args.extend(call.out_handles.arg_names)
write_invocation(f, 'ret', func, args, indent="\t")
f.write(';')
write_result_handler(f, 'ret', cleanup, indent="\t")
for arg in call.out_args:
f.write("\t*out_" + arg.name + " = _reply." + arg.name + ";\n")
f.write("\n\t" + cleanup)
f.write("\n\treturn _reply.result;\n}\n")
def generate_h(file, p):
"""Generate protocol header.
@ -109,78 +219,12 @@ def generate_client_c(file, p):
# Loop over all of the calls.
for call in p.calls:
call.write_call_decl(f)
f.write("\n{\n")
if call.varlen:
write_send_definition(f, call)
write_receive_definition(f, call)
else:
write_call_definition(f, call)
f.write("\tIPC_TRACE(ipc_c, \"Calling " + call.name + "\");\n\n")
write_msg_struct(f, call, '\t')
write_reply_struct(f, call, '\t')
f.write("""
\t// Other threads must not read/write the fd while we wait for reply
\tos_mutex_lock(&ipc_c->mutex);
""")
cleanup = "os_mutex_unlock(&ipc_c->mutex);"
# Prepare initial sending
write_msg_send(f, 'xrt_result_t ret', indent="\t")
write_result_handler(f, 'ret', cleanup, indent="\t")
if call.in_handles:
f.write("\n\t// Send our handles separately\n")
f.write("\n\t// Wait for server sync")
# Must sync with the server so it's expecting the next message.
write_invocation(
f,
'ret',
'ipc_receive',
(
'&ipc_c->imc',
'&_sync',
'sizeof(_sync)'
),
indent="\t"
)
f.write(';')
write_result_handler(f, 'ret', cleanup, indent="\t")
# Must send these in a second message
# since the server doesn't know how many to expect.
f.write("\n\t// We need this message data as filler only\n")
f.write("\tstruct ipc_command_msg _handle_msg = {\n")
f.write("\t .cmd = " + str(call.id) + ",\n")
f.write("\t};\n")
write_invocation(
f,
'ret',
'ipc_send_handles_' + call.in_handles.stem,
(
'&ipc_c->imc',
"&_handle_msg",
"sizeof(_handle_msg)",
call.in_handles.arg_name,
call.in_handles.count_arg_name
),
indent="\t"
)
f.write(';')
write_result_handler(f, 'ret', cleanup, indent="\t")
f.write("\n\t// Await the reply")
func = 'ipc_receive'
args = ['&ipc_c->imc', '&_reply', 'sizeof(_reply)']
if call.out_handles:
func += '_handles_' + call.out_handles.stem
args.extend(call.out_handles.arg_names)
write_invocation(f, 'ret', func, args, indent="\t")
f.write(';')
write_result_handler(f, 'ret', cleanup, indent="\t")
for arg in call.out_args:
f.write("\t*out_" + arg.name + " = _reply." + arg.name + ";\n")
f.write("\n\t" + cleanup)
f.write("\n\treturn _reply.result;\n}\n")
f.close()
@ -203,7 +247,12 @@ def generate_client_h(file, p):
f.write("\n")
for call in p.calls:
call.write_call_decl(f)
if call.varlen:
call.write_send_decl(f)
f.write(";\n")
call.write_receive_decl(f)
else:
call.write_call_decl(f)
f.write(";\n")
write_cpp_header_guard_end(f)
@ -244,10 +293,14 @@ ipc_dispatch(volatile struct ipc_client_state *ics, ipc_command_t *ipc_command)
f.write(
"\t\tstruct ipc_{}_msg *msg = ".format(call.name))
f.write("(struct ipc_{}_msg *)ipc_command;\n".format(call.name))
if call.out_args:
if call.varlen:
f.write("\t\t// No return arguments")
elif call.out_args:
f.write("\t\tstruct ipc_%s_reply reply = {0};\n" % call.name)
else:
f.write("\t\tstruct ipc_result_reply reply = {0};\n")
if call.in_handles:
# We need to fetch these handles separately
f.write("\t\tstruct ipc_result_reply _sync = {XRT_SUCCESS};\n")
@ -305,11 +358,17 @@ ipc_dispatch(volatile struct ipc_client_state *ics, ipc_command_t *ipc_command)
# Write call to ipc_handle_CALLNAME
args = ["ics"]
# Always provide in arguments.
for arg in call.in_args:
args.append(("&msg->" + arg.name)
if arg.is_aggregate
else ("msg->" + arg.name))
args.extend("&reply." + arg.name for arg in call.out_args)
# No reply arguments on varlen.
if not call.varlen:
args.extend("&reply." + arg.name for arg in call.out_args)
if call.out_handles:
args.extend(("XRT_MAX_IPC_HANDLES",
call.out_handles.arg_name,
@ -318,23 +377,31 @@ ipc_dispatch(volatile struct ipc_client_state *ics, ipc_command_t *ipc_command)
if call.in_handles:
args.extend(("&in_%s[0]" % call.in_handles.arg_name,
"msg->"+call.in_handles.count_arg_name))
write_invocation(f, 'reply.result', 'ipc_handle_' +
# Should we put the return in the reply or return it?
return_target = 'reply.result'
if call.varlen:
return_target = 'xrt_result_t xret'
write_invocation(f, return_target, 'ipc_handle_' +
call.name, args, indent="\t\t")
f.write(";\n")
# TODO do we check reply.result and
# error out before replying if it's not success?
func = 'ipc_send'
args = ["(struct ipc_message_channel *)&ics->imc",
"&reply",
"sizeof(reply)"]
if call.out_handles:
func += '_handles_' + call.out_handles.stem
args.extend(call.out_handles.arg_names)
write_invocation(f, 'xrt_result_t ret', func, args, indent="\t\t")
f.write(";")
f.write("\n\t\treturn ret;\n")
if not call.varlen:
func = 'ipc_send'
args = ["(struct ipc_message_channel *)&ics->imc",
"&reply",
"sizeof(reply)"]
if call.out_handles:
func += '_handles_' + call.out_handles.stem
args.extend(call.out_handles.arg_names)
write_invocation(f, 'xrt_result_t xret', func, args, indent="\t\t")
f.write(";")
f.write("\n\t\treturn xret;\n")
f.write("\t}\n")
f.write('''\tdefault:
\t\tU_LOG_E("UNHANDLED IPC MESSAGE! %d", *ipc_command);