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