From 90c95d505b08d4bb05601337ef35c53e8d0e4127 Mon Sep 17 00:00:00 2001 From: Chris Owen <59661130+WiseCrohn@users.noreply.github.com> Date: Wed, 11 Oct 2023 09:52:52 +0100 Subject: [PATCH] Add macro definition to schema, and option to protect against inclusion in assembler (#11) --- .vscode/launch.json | 35 ++++++ api/C/include/csi_hl_console.h | 110 +++++++++--------- spec-schema/parser/doc_gen.py | 54 +++++++++ spec-schema/parser/header_gen.py | 65 +++++++++-- .../parser/test_data/example-module4.yaml | 26 +++++ spec-schema/rvm-csi-module.schema.json | 50 ++++++++ 6 files changed, 276 insertions(+), 64 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1b24b9c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: doc generation", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/spec-schema/parser/csi_parser.py", + "args": ["--generate-docs", "${workspaceFolder}/api/rvm-csi-spec.yaml", "--doc-out-dir=${workspaceFolder}/auto-gen"], + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "Python: test doc generation", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/spec-schema/parser/csi_parser.py", + "args": ["--generate-docs", "${workspaceFolder}/spec-schema/parser/test_data/simple.rvm-csi.yaml", "--doc-out-dir=${workspaceFolder}/spec-schema/parser/adoc_output"], + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "Python: test header generation", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/spec-schema/parser/csi_parser.py", + "args": ["${workspaceFolder}/spec-schema/parser/test_data/simple.rvm-csi.yaml", "--out-dir=${workspaceFolder}/spec-schema/parser/output"], + "console": "integratedTerminal", + "justMyCode": true + }, + ] +} diff --git a/api/C/include/csi_hl_console.h b/api/C/include/csi_hl_console.h index f6fc53e..db62464 100644 --- a/api/C/include/csi_hl_console.h +++ b/api/C/include/csi_hl_console.h @@ -43,61 +43,6 @@ #include "csi_dl_bsp_uart.h" #include "csi_types.h" -/* - * Configure a circular buffer for use by csi_uprintf when CSI_UPRINTF_OUTPUT is - * defined as CSI_UPRINTF_CIRCBUFF. This function must be called once, before ever - * calling csi_uprintf in that case, and before ever calling csi_uprintf_circbuff. - * It does not need to be called if not using circular buffer output mode. - * - * @param buff: Pointer to circular buffer to use for console output. - * @param size_bytes: Circular buffer size in bytes. - * @return : Status of operation - */ -csi_status_t csi_set_uprintf_circbuff(void *buff, unsigned size_bytes); - -/* - * Configure csi_uprintf operation for use when CSI_UPRINTF_OUTPUT is defined as - * CSI_UPRINTF_UART. This function must be called once, before ever calling - * csi_uprintf in that case, and before ever calling csi_uprintf_uart. It does not - * need to be called if not using UART output mode. - * - * @param uart: Pointer to a UART object which must be initialized (through a call - * to csi_uart_init) prior to use. - * @return : Status of operation - */ -csi_status_t csi_set_uprintf_uart(csi_uart_t *uart); - -/* - * Semi-hosting version of csi_uprintf. This is not normally called directly by - * application writers. Rather, call csi_uprintf and define CSI_UPRINTF_OUTPUT to - * be CSI_UPRINTF_SEMIHOST. - * - * @param fmt: Formatted string matching that used by printf - * @return : Number of characters printed. A negative number indicates an error. - */ -int csi_uprintf_semihost(char const *fmt, ...) __attribute__((format(printf, 1, 2))); - -/* - * Circular buffering version of csi_uprintf. This is not normally called directly - * by application writers. Rather, call csi_uprintf and define CSI_UPRINTF_OUTPUT - * to be CSI_UPRINTF_CIRCBUFF. - * - * @param fmt: Formatted string matching that used by printf - * @return : Number of characters printed. A negative number indicates an error. - */ -int csi_uprintf_circbuff(char const *fmt, ...) __attribute__((format(printf, 1, 2))); - -/* - * UART version of csi_uprintf. This is not normally called directly by - * application writers. Rather, call csi_uprintf and define CSI_UPRINTF_OUTPUT to - * be CSI_UPRINTF_UART. - * - * @param fmt: Formatted string matching that used by printf - * @return : Number of characters printed. A negative number indicates an error. - */ -int csi_uprintf_uart(char const *fmt, ...) __attribute__((format(printf, 1, 2))); - - /* * Max number of characters that can be printed by a single csi_uprintf call. This * determines the amount of space that the function call will occupy on the stack @@ -189,4 +134,59 @@ int csi_uprintf_uart(char const *fmt, ...) __attribute__((format(printf, 1, 2))) #endif +/* + * Configure a circular buffer for use by csi_uprintf when CSI_UPRINTF_OUTPUT is + * defined as CSI_UPRINTF_CIRCBUFF. This function must be called once, before ever + * calling csi_uprintf in that case, and before ever calling csi_uprintf_circbuff. + * It does not need to be called if not using circular buffer output mode. + * + * @param buff: Pointer to circular buffer to use for console output. + * @param size_bytes: Circular buffer size in bytes. + * @return : Status of operation + */ +csi_status_t csi_set_uprintf_circbuff(void *buff, unsigned size_bytes); + +/* + * Configure csi_uprintf operation for use when CSI_UPRINTF_OUTPUT is defined as + * CSI_UPRINTF_UART. This function must be called once, before ever calling + * csi_uprintf in that case, and before ever calling csi_uprintf_uart. It does not + * need to be called if not using UART output mode. + * + * @param uart: Pointer to a UART object which must be initialized (through a call + * to csi_uart_init) prior to use. + * @return : Status of operation + */ +csi_status_t csi_set_uprintf_uart(csi_uart_t *uart); + +/* + * Semi-hosting version of csi_uprintf. This is not normally called directly by + * application writers. Rather, call csi_uprintf and define CSI_UPRINTF_OUTPUT to + * be CSI_UPRINTF_SEMIHOST. + * + * @param fmt: Formatted string matching that used by printf + * @return : Number of characters printed. A negative number indicates an error. + */ +int csi_uprintf_semihost(char const *fmt, ...) __attribute__((format(printf, 1, 2))); + +/* + * Circular buffering version of csi_uprintf. This is not normally called directly + * by application writers. Rather, call csi_uprintf and define CSI_UPRINTF_OUTPUT + * to be CSI_UPRINTF_CIRCBUFF. + * + * @param fmt: Formatted string matching that used by printf + * @return : Number of characters printed. A negative number indicates an error. + */ +int csi_uprintf_circbuff(char const *fmt, ...) __attribute__((format(printf, 1, 2))); + +/* + * UART version of csi_uprintf. This is not normally called directly by + * application writers. Rather, call csi_uprintf and define CSI_UPRINTF_OUTPUT to + * be CSI_UPRINTF_UART. + * + * @param fmt: Formatted string matching that used by printf + * @return : Number of characters printed. A negative number indicates an error. + */ +int csi_uprintf_uart(char const *fmt, ...) __attribute__((format(printf, 1, 2))); + + #endif /* CSI_HL_CONSOLE_H */ diff --git a/spec-schema/parser/doc_gen.py b/spec-schema/parser/doc_gen.py index 4d837fb..d88dc72 100644 --- a/spec-schema/parser/doc_gen.py +++ b/spec-schema/parser/doc_gen.py @@ -145,6 +145,53 @@ def format_adoc_function(function, linked_sections): return out_str +def format_adoc_macro(macro): + ''' Builds adoc level 3 & 4 section for supplied macro declaration. + Returns function adoc string. + ''' + out_str = heading_marker(3) + macro['name'] + '[[' + macro['name'] + ']]' "\n" + out_str += ''' +[source, c] +---- +''' + out_str += macro['code'] + out_str += "----\n\n" + + out_str += macro['description'] + "\n" + + if 'notes' in macro.keys(): + for note in macro['notes']: + out_str += note + "\n\n" + + out_str += heading_marker(4) + "Return\n" + + if 'c-return-value' in macro.keys(): + out_str += "`" + macro['c-return-value']['type'] + "` - " + \ + macro['c-return-value']['description'] + "\n\n" + + out_str += heading_marker(4) + "Parameters\n" + + if 'c-params' in macro.keys(): + for param in macro['c-params']: + + param_type = param['type'] + param_name = param['name'] + if param_type[-1] == '*': # pointer + param_type = param_type.rstrip('* ') + param_name = "*" + param_name + + out_str += param_type + " `" + param_name + "` - " + param['description'] + "\n\n" + + if 'notes' in param.keys(): + out_str += format_text_from_array(param['notes']) + out_str += "\n" + else: + out_str += "Macro takes no parameters\n\n" + + return out_str + + + def generate_c_module_adoc(module, out_dir, module_sub_dir, adoc_optimization, linked_sections): ''' Builds adoc file for a module. Inputs are the module definition and the output directory & sub directory for the @@ -190,6 +237,13 @@ def generate_c_module_adoc(module, out_dir, module_sub_dir, adoc_optimization, l out_str += "\n" out_str += "\n" + if 'macros' in module.keys(): + out_str += heading_marker(2) + "Macros\n" + for macro in module['macros']: + out_str += format_adoc_macro(macro) + out_str += "\n" + out_str += "\n" + if 'c-definitions' in module.keys(): out_str += heading_marker(2) + "Definitions\n" for fragment in module['c-definitions']: diff --git a/spec-schema/parser/header_gen.py b/spec-schema/parser/header_gen.py index 62c0252..2f05b3e 100644 --- a/spec-schema/parser/header_gen.py +++ b/spec-schema/parser/header_gen.py @@ -131,6 +131,37 @@ def format_c_function(function): return out_str +def format_c_macro(macro): + ''' Takes a macro object. + Returns a string containing the associated header file content. + ''' + + # Start the comment. + out_str = "/*\n " + + out_str += format_c_comment_lines(macro['description']) + out_str += "*\n " + if 'notes' in macro.keys(): + for note in macro['notes']: + out_str += format_c_comment_lines(note) + out_str += "*\n " + + if 'c-params' in macro.keys(): + for param in macro['c-params']: + out_str += format_c_comment_lines("@param " + param['name'] + ": " + param['description']) + if 'c-return-value' in macro.keys(): + out_str += format_c_comment_lines("@return " + ": " + macro['c-return-value']['description']) + + # Close comment + out_str += "*/\n" + + # Write out the macro code + out_str += macro['code'] + out_str += "\n" + + return out_str + + def generate_c(api_definition, module_definitions, out_dir): ''' Top level function which iterates through each of the modules in the api definition to build C header content and write it an appropriate file. @@ -173,7 +204,12 @@ def generate_c(api_definition, module_definitions, out_dir): for include_file in module['c-include-files']: out_str += format_c_include_file(include_file) out_str += "\n" - + + # Protection against inclusion in assembler code if required + if 'no-assembler' in module.keys(): + if module['no-assembler']: + out_str += "#ifndef __ASSEMBLER__\n\n" + # Add type declarations if 'c-type-declarations' in module.keys(): for type_declaration in module['c-type-declarations']: @@ -181,20 +217,31 @@ def generate_c(api_definition, module_definitions, out_dir): out_str += "\n" out_str += "\n" - # Add function declarations - if 'functions' in module.keys(): - for function in module['functions']: - out_str += format_c_function(function) - out_str += "\n" - out_str += "\n" - # Add code fragments if 'c-definitions' in module.keys(): for fragment in module['c-definitions']: out_str += "/*\n " + format_c_comment_lines(fragment['comment']) + "*/\n" out_str += fragment['fragment'] + '\n' out_str += "\n" - + + # Add macros + if 'macros' in module.keys(): + for macro in module['macros']: + out_str += format_c_macro(macro) + out_str += "\n" + + # Add function declarations + if 'functions' in module.keys(): + for function in module['functions']: + out_str += format_c_function(function) + out_str += "\n" + out_str += "\n" + + # Protection against inclusion in assembler code if required + if 'no-assembler' in module.keys(): + if module['no-assembler']: + out_str += "#endif // __ASSEMBLER__\n\n" + # Close guard against multiple inclusion out_str += "#endif /* " + def_file_name + " */ \n" diff --git a/spec-schema/parser/test_data/example-module4.yaml b/spec-schema/parser/test_data/example-module4.yaml index 1aefa3e..c19a7a8 100644 --- a/spec-schema/parser/test_data/example-module4.yaml +++ b/spec-schema/parser/test_data/example-module4.yaml @@ -2,6 +2,7 @@ module: name: Platform Discovery description: Functions for discovery of platform characteristics c-specific: false + no-assembler: true c-filename: csi_discovery.h c-type-declarations: - name: csi_timer_count_t @@ -13,3 +14,28 @@ module: c-return-value: description: clock frequency in Hz type: const unsigned + macros: + - name: csi_csr_swap + description: > + Read contents of a CSR to a temporary variable, write val into + the register, then return previous contents. + c-params: + - name: csr + description: CSR name as understood by assembler + type: string + - name: val + description: Value to write to CSR + type: int + c-return-value: + description: Previous CSR contents + type: int + code: | + #define csi_csr_swap(csr, val) \ + ({ \ + rv_csr_t __v = (unsigned long)(val); \ + __ASM volatile("csrrw %0, " STRINGIFY(csr) ", %1" \ + : "=r"(__v) \ + : "rK"(__v) \ + : "memory"); \ + __v; \ + }) diff --git a/spec-schema/rvm-csi-module.schema.json b/spec-schema/rvm-csi-module.schema.json index fa9e291..816010b 100644 --- a/spec-schema/rvm-csi-module.schema.json +++ b/spec-schema/rvm-csi-module.schema.json @@ -228,6 +228,46 @@ } } }, + "c-macro": { + "description": "C macro with arguments and potentially a return value (for a simple definition, use c-code-fragment instead)", + "type": "object", + "additionalProperties": false, + "required": ["name", "description", "code"], + "properties": { + "name": { + "description": "Macro name", + "type": "string" + }, + "description": { + "description": "Macro description", + "type": "string" + }, + "notes": { + "type": "array", + "items": { + "$ref": "#/definitions/note" + } + }, + "c-params": { + "description": "C function parameter list (affects documentation only)", + "type": "array", + "items": { + "$ref": "#/definitions/c-function-param" + } + }, + "c-return-value": { + "description": "C return type (affects documentation only)", + "type": "object", + "items": { + "$ref": "#/definitions/c-function-return-value" + } + }, + "code": { + "description": "Code fragment for the macro", + "type": "string" + } + } + }, "module": { "description": "The API may be divided into modules, each covering a different functional area. Modules may also equate to files. A module without functions is permissible, for example to create a top-level header file.", "type": "object", @@ -246,6 +286,10 @@ "description": "Set true if the module is unique to the C implementation", "type": "boolean" }, + "no-assembler": { + "description": "Set true for a C header that must not be included from assembler code (contents will be wrapped with #ifndef __ASSEMBLER__)", + "type": "boolean" + }, "c-filename": { "description": "C filename for this module", "type": "string" @@ -280,6 +324,12 @@ "$ref": "#/definitions/function" } }, + "macros": { + "type": "array", + "items": { + "$ref": "#/definitions/c-macro" + } + }, "c-definitions": { "type": "array", "items": {