The open source OpenXR runtime
at prediction-2 528 lines 16 kB view raw
1#!/usr/bin/env python3 2# Copyright 2020-2023, Collabora, Ltd. 3# Copyright 2025, NVIDIA CORPORATION. 4# SPDX-License-Identifier: BSL-1.0 5"""Generate code from a JSON file describing the IPC protocol.""" 6 7import argparse 8import os.path 9from string import Template 10 11from ipcproto.common import (Proto, write_decl, write_invocation, 12 write_result_handler, write_cpp_header_guard_start, 13 write_cpp_header_guard_end, write_msg_struct, 14 write_reply_struct, write_msg_send) 15 16header = '''// Copyright 2020-2023, Collabora, Ltd. 17// SPDX-License-Identifier: BSL-1.0 18/*! 19 * @file 20 * @brief {brief}. 21 * @author Jakob Bornecrantz <jakob@collabora.com> 22 * @ingroup ipc{suffix} 23 */ 24''' 25 26 27def write_send_definition(f, call): 28 """Write a ipc_send_CALLNAME_locked function.""" 29 call.write_send_decl(f) 30 f.write("\n{\n") 31 f.write("\tIPC_TRACE(ipc_c, \"Sending " + call.name + "\");\n\n") 32 33 write_msg_struct(f, call, '\t') 34 35 write_msg_send(f, 'xrt_result_t ret', indent="\t") 36 37 f.write("\n\treturn ret;\n}\n") 38 39 40def write_receive_definition(f, call): 41 """Write a ipc_receive_CALLNAME_locked function.""" 42 call.write_receive_decl(f) 43 f.write("\n{\n") 44 f.write("\tIPC_TRACE(ipc_c, \"Receiving " + call.name + "\");\n\n") 45 46 write_reply_struct(f, call, '\t') 47 48 f.write("\n\t// Await the reply") 49 func = 'ipc_receive' 50 args = ['&ipc_c->imc', '&_reply', 'sizeof(_reply)'] 51 write_invocation(f, 'xrt_result_t ret', func, args, indent="\t") 52 f.write(";") 53 write_result_handler(f, 'ret', None, indent="\t") 54 55 for arg in call.out_args: 56 f.write("\t*out_" + arg.name + " = _reply." + arg.name + ";\n") 57 58 f.write("\n\treturn _reply.result;\n}\n") 59 60 61def write_call_definition(f, call): 62 """Write a ipc_call_CALLNAME function.""" 63 call.write_call_decl(f) 64 f.write("\n{\n") 65 66 f.write("\tIPC_TRACE(ipc_c, \"Calling " + call.name + "\");\n\n") 67 68 write_msg_struct(f, call, '\t') 69 write_reply_struct(f, call, '\t') 70 71 f.write(""" 72\t// Other threads must not read/write the fd while we wait for reply 73\tos_mutex_lock(&ipc_c->mutex); 74""") 75 cleanup = "os_mutex_unlock(&ipc_c->mutex);" 76 77 # Prepare initial sending 78 write_msg_send(f, 'xrt_result_t ret', indent="\t") 79 write_result_handler(f, 'ret', cleanup, indent="\t") 80 81 if call.in_handles: 82 f.write("\n\t// Send our handles separately\n") 83 f.write("\n\t// Wait for server sync") 84 # Must sync with the server so it's expecting the next message. 85 write_invocation( 86 f, 87 'ret', 88 'ipc_receive', 89 ( 90 '&ipc_c->imc', 91 '&_sync', 92 'sizeof(_sync)' 93 ), 94 indent="\t" 95 ) 96 f.write(';') 97 write_result_handler(f, 'ret', cleanup, indent="\t") 98 99 # Must send these in a second message 100 # since the server doesn't know how many to expect. 101 f.write("\n\t// We need this message data as filler only\n") 102 f.write("\tstruct ipc_command_msg _handle_msg = {\n") 103 f.write("\t .cmd = " + str(call.id) + ",\n") 104 f.write("\t};\n") 105 write_invocation( 106 f, 107 'ret', 108 'ipc_send_handles_' + call.in_handles.stem, 109 ( 110 '&ipc_c->imc', 111 "&_handle_msg", 112 "sizeof(_handle_msg)", 113 call.in_handles.arg_name, 114 call.in_handles.count_arg_name 115 ), 116 indent="\t" 117 ) 118 f.write(';') 119 write_result_handler(f, 'ret', cleanup, indent="\t") 120 121 f.write("\n\t// Await the reply") 122 func = 'ipc_receive' 123 args = ['&ipc_c->imc', '&_reply', 'sizeof(_reply)'] 124 if call.out_handles: 125 func += '_handles_' + call.out_handles.stem 126 args.extend(call.out_handles.arg_names) 127 write_invocation(f, 'ret', func, args, indent="\t") 128 f.write(';') 129 write_result_handler(f, 'ret', cleanup, indent="\t") 130 131 for arg in call.out_args: 132 f.write("\t*out_" + arg.name + " = _reply." + arg.name + ";\n") 133 f.write("\n\t" + cleanup) 134 f.write("\n\treturn _reply.result;\n}\n") 135 136 137def generate_h(file, p): 138 """Generate protocol header. 139 140 Defines command enum, utility functions, and command and reply structures. 141 """ 142 # Get the directory where this script is located 143 script_dir = os.path.dirname(os.path.abspath(__file__)) 144 template_file = os.path.join(script_dir, 'ipc_protocol_generated.h.template') 145 146 # Goes directly into the template file. 147 ipc_commands = '\n'.join([f'\t{call.id},' for call in p.calls]) 148 149 # Goes directly into the template file. 150 ipc_cmd_cases = '\n'.join([f'\tcase {call.id}: return "{call.id}";' for call in p.calls]) 151 152 # Build message and reply structs. 153 ipc_msg_structs_list = [] 154 for call in p.calls: 155 # Should we emit a msg struct. 156 if call.needs_msg_struct: 157 struct_lines = [f'struct ipc_{call.name}_msg'] 158 struct_lines.append('{') 159 struct_lines.append('\tenum ipc_command cmd;') 160 for arg in call.in_args: 161 struct_lines.append('\t' + arg.get_struct_field() + ';') 162 if call.in_handles: 163 struct_lines.append('\t%s %s;' % (call.in_handles.count_arg_type, 164 call.in_handles.count_arg_name)) 165 struct_lines.append('};') 166 167 # Each entry contains a struct complete struct declaration. 168 ipc_msg_structs_list.append('\n'.join(struct_lines)) 169 170 # Should we emit a reply struct. 171 if call.out_args: 172 struct_lines = [f'struct ipc_{call.name}_reply'] 173 struct_lines.append('{') 174 struct_lines.append('\txrt_result_t result;') 175 for arg in call.out_args: 176 struct_lines.append('\t' + arg.get_struct_field() + ';') 177 struct_lines.append('};') 178 179 # Each entry contains a struct complete struct declaration. 180 ipc_msg_structs_list.append('\n'.join(struct_lines)) 181 182 # What finally goes into the template file. 183 # The struct declarations doesn't end on a newline, 184 # so insert two for each declaration. 185 ipc_msg_structs = '\n\n'.join(ipc_msg_structs_list) 186 187 # Read the template file. 188 with open(template_file, 'r') as f: 189 template = Template(f.read()) 190 191 # Substitute values into the template. 192 filled = template.substitute( 193 ipc_commands=ipc_commands, 194 ipc_cmd_cases=ipc_cmd_cases, 195 ipc_msg_structs=ipc_msg_structs 196 ) 197 198 # Write the generated header file. 199 with open(file, 'w') as f: 200 f.write(filled) 201 202 203def generate_client_c(file, p): 204 """Generate IPC client proxy source.""" 205 f = open(file, "w") 206 f.write(header.format(brief='Generated IPC client code', suffix='_client')) 207 f.write(''' 208#include "client/ipc_client.h" 209#include "ipc_protocol_generated.h" 210 211 212\n''') 213 214 # Loop over all of the calls. 215 for call in p.calls: 216 if call.varlen: 217 write_send_definition(f, call) 218 write_receive_definition(f, call) 219 else: 220 write_call_definition(f, call) 221 222 f.close() 223 224 225def generate_client_h(file, p): 226 """Generate IPC client header. 227 228 Contains prototypes for generated IPC proxy call functions. 229 """ 230 f = open(file, "w") 231 f.write(header.format(brief='Generated IPC client code', suffix='_client')) 232 f.write(''' 233#pragma once 234 235#include "shared/ipc_protocol.h" 236#include "ipc_protocol_generated.h" 237#include "client/ipc_client.h" 238 239''') 240 write_cpp_header_guard_start(f) 241 f.write("\n") 242 243 for call in p.calls: 244 if call.varlen: 245 call.write_send_decl(f) 246 f.write(";\n") 247 call.write_receive_decl(f) 248 else: 249 call.write_call_decl(f) 250 f.write(";\n") 251 252 write_cpp_header_guard_end(f) 253 f.close() 254 255 256def generate_server_c(file, p): 257 """Generate IPC server stub/dispatch source.""" 258 f = open(file, "w") 259 f.write(header.format(brief='Generated IPC server code', suffix='_server')) 260 f.write(''' 261#include "xrt/xrt_limits.h" 262 263#include "shared/ipc_protocol.h" 264#include "shared/ipc_utils.h" 265 266#include "server/ipc_server.h" 267 268#include "ipc_server_generated.h" 269 270''') 271 272 f.write(''' 273xrt_result_t 274ipc_dispatch(volatile struct ipc_client_state *ics, ipc_command_t *ipc_command) 275{ 276\tswitch (*ipc_command) { 277''') 278 279 for call in p.calls: 280 f.write("\tcase " + call.id + ": {\n") 281 282 f.write("\t\tIPC_TRACE(ics->server, \"Dispatching " + call.name + 283 "\");\n\n") 284 285 if call.needs_msg_struct: 286 f.write( 287 "\t\tstruct ipc_{}_msg *msg = ".format(call.name)) 288 f.write("(struct ipc_{}_msg *)ipc_command;\n".format(call.name)) 289 290 if call.varlen: 291 f.write("\t\t// No return arguments") 292 elif call.out_args: 293 f.write("\t\tstruct ipc_%s_reply reply = {0};\n" % call.name) 294 else: 295 f.write("\t\tstruct ipc_result_reply reply = {0};\n") 296 297 if call.in_handles: 298 # We need to fetch these handles separately 299 f.write("\t\tstruct ipc_result_reply _sync = {XRT_SUCCESS};\n") 300 f.write("\t\t%s in_%s[XRT_MAX_IPC_HANDLES] = {0};\n" % ( 301 call.in_handles.typename, call.in_handles.arg_name)) 302 f.write("\t\tstruct ipc_command_msg _handle_msg = {0};\n") 303 if call.out_handles: 304 f.write("\t\t%s %s[XRT_MAX_IPC_HANDLES] = {0};\n" % ( 305 call.out_handles.typename, call.out_handles.arg_name)) 306 f.write("\t\t%s %s = {0};\n" % ( 307 call.out_handles.count_arg_type, 308 call.out_handles.count_arg_name)) 309 f.write("\n") 310 311 if call.in_handles: 312 # Validate the number of handles. 313 f.write("\t\tif (msg->%s > XRT_MAX_IPC_HANDLES) {\n" % (call.in_handles.count_arg_name)) 314 f.write("\t\t\treturn XRT_ERROR_IPC_FAILURE;\n") 315 f.write("\t\t}\n") 316 317 # Let the client know we are ready to receive the handles. 318 write_invocation( 319 f, 320 'xrt_result_t sync_result', 321 'ipc_send', 322 ( 323 "(struct ipc_message_channel *)&ics->imc", 324 "&_sync", 325 "sizeof(_sync)" 326 ), 327 indent="\t\t" 328 ) 329 f.write(";") 330 write_result_handler(f, "sync_result", 331 indent="\t\t") 332 write_invocation( 333 f, 334 'xrt_result_t receive_handle_result', 335 'ipc_receive_handles_' + call.in_handles.stem, 336 ( 337 "(struct ipc_message_channel *)&ics->imc", 338 "&_handle_msg", 339 "sizeof(_handle_msg)", 340 "in_" + call.in_handles.arg_name, 341 "msg->"+call.in_handles.count_arg_name 342 ), 343 indent="\t\t" 344 ) 345 f.write(";") 346 write_result_handler(f, "receive_handle_result", 347 indent="\t\t") 348 f.write("\t\tif (_handle_msg.cmd != %s) {\n" % str(call.id)) 349 f.write("\t\t\treturn XRT_ERROR_IPC_FAILURE;\n") 350 f.write("\t\t}\n") 351 352 # Write call to ipc_handle_CALLNAME 353 args = ["ics"] 354 355 # Always provide in arguments. 356 for arg in call.in_args: 357 args.append(("&msg->" + arg.name) 358 if arg.is_aggregate 359 else ("msg->" + arg.name)) 360 361 # No reply arguments on varlen. 362 if not call.varlen: 363 args.extend("&reply." + arg.name for arg in call.out_args) 364 365 if call.out_handles: 366 args.extend(("XRT_MAX_IPC_HANDLES", 367 call.out_handles.arg_name, 368 "&" + call.out_handles.count_arg_name)) 369 370 if call.in_handles: 371 args.extend(("&in_%s[0]" % call.in_handles.arg_name, 372 "msg->"+call.in_handles.count_arg_name)) 373 374 # Should we put the return in the reply or return it? 375 return_target = 'reply.result' 376 if call.varlen: 377 return_target = 'xrt_result_t xret' 378 379 write_invocation(f, return_target, 'ipc_handle_' + 380 call.name, args, indent="\t\t") 381 f.write(";\n") 382 383 # TODO do we check reply.result and 384 # error out before replying if it's not success? 385 386 if not call.varlen: 387 func = 'ipc_send' 388 args = ["(struct ipc_message_channel *)&ics->imc", 389 "&reply", 390 "sizeof(reply)"] 391 if call.out_handles: 392 func += '_handles_' + call.out_handles.stem 393 args.extend(call.out_handles.arg_names) 394 write_invocation(f, 'xrt_result_t xret', func, args, indent="\t\t") 395 f.write(";") 396 397 f.write("\n\t\treturn xret;\n") 398 f.write("\t}\n") 399 f.write('''\tdefault: 400\t\tU_LOG_E("UNHANDLED IPC MESSAGE! %d", *ipc_command); 401\t\treturn XRT_ERROR_IPC_FAILURE; 402\t} 403} 404 405''') 406 407 f.write(''' 408size_t 409ipc_command_size(const enum ipc_command cmd) 410{ 411\tswitch (cmd) { 412''') 413 414 for call in p.calls: 415 if call.needs_msg_struct: 416 f.write("\tcase " + call.id + ": return sizeof(struct ipc_{}_msg);\n".format(call.name)) 417 else: 418 f.write("\tcase " + call.id + ": return sizeof(enum ipc_command);\n") 419 420 f.write('''\tdefault: 421\t\tU_LOG_E("UNHANDLED IPC COMMAND! %d", cmd); 422\t\treturn 0; 423\t} 424} 425 426''') 427 428 f.close() 429 430 431def generate_server_header(file, p): 432 """Generate IPC server header. 433 434 Declares handler prototypes to implement, 435 as well as the prototype for the generated dispatch function. 436 """ 437 f = open(file, "w") 438 f.write(header.format(brief='Generated IPC server code', suffix='_server')) 439 f.write(''' 440#pragma once 441 442#include "shared/ipc_protocol.h" 443#include "ipc_protocol_generated.h" 444#include "server/ipc_server.h" 445 446 447''') 448 449 write_cpp_header_guard_start(f) 450 f.write("\n") 451 452 # Those decls are constant, but we must write them here 453 # because they depends on a generated enum. 454 write_decl( 455 f, 456 "xrt_result_t", 457 "ipc_dispatch", 458 [ 459 "volatile struct ipc_client_state *ics", 460 "ipc_command_t *ipc_command" 461 ] 462 ) 463 f.write(";\n") 464 465 write_decl( 466 f, 467 "size_t", 468 "ipc_command_size", 469 [ 470 "const enum ipc_command cmd" 471 ] 472 ) 473 f.write(";\n") 474 475 for call in p.calls: 476 call.write_handler_decl(f) 477 f.write(";\n") 478 479 write_cpp_header_guard_end(f) 480 f.close() 481 482def generate_struct_names(file, p): 483 """Generate list of structures names. 484 485 Lists the structures used in the IPC protocol, this can be 486 used for tools such as pahole. 487 """ 488 f = open(file, "w") 489 f.write("ipc_shared_memory\n") 490 types = set() 491 for call in p.calls: 492 for i in call.in_args + call.out_args: 493 if i.is_aggregate: 494 types.add(i.typename.split(" ")[-1]) 495 for t in sorted(types): 496 f.write(t) 497 f.write("\n") 498 f.close() 499 500def main(): 501 """Handle command line and generate a file.""" 502 parser = argparse.ArgumentParser(description='Protocol generator.') 503 parser.add_argument( 504 'proto', help='Protocol file to use') 505 parser.add_argument( 506 'output', type=str, nargs='+', 507 help='Output file, uses the name to choose output type') 508 args = parser.parse_args() 509 510 p = Proto.load_and_parse(args.proto) 511 512 for output in args.output: 513 if output.endswith("ipc_protocol_generated.h"): 514 generate_h(output, p) 515 if output.endswith("ipc_client_generated.c"): 516 generate_client_c(output, p) 517 if output.endswith("ipc_client_generated.h"): 518 generate_client_h(output, p) 519 if output.endswith("ipc_server_generated.c"): 520 generate_server_c(output, p) 521 if output.endswith("ipc_server_generated.h"): 522 generate_server_header(output, p) 523 if output.endswith("structs.txt"): 524 generate_struct_names(output, p) 525 526 527if __name__ == "__main__": 528 main()