diff --git a/.gitignore b/.gitignore index 58aa680a..d1433703 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,11 @@ # IDE settings /.vscode +# Local OS files +.DS_Store +# Python cache +__pycache__/ +*.py[cod] # Generated documentation _build build diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..0cf4d916 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +PSA_API_TOOL ?= tools + +# The location of psa-api-tool must be specified +ifeq ($(wildcard $(PSA_API_TOOL)/make),) + $(error The 'PSA_API_TOOL' variable is not set, or does not point to a suitable installation of psa-api-tool) +endif + +include $(PSA_API_TOOL)/make diff --git a/README.md b/README.md index d7d58fb9..34222e1a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ This GitHub repository contains: * Specification source files * Reference copies of the PSA Certified API header files * Examples of usage and implementation of the PSA Certified APIs +* Build tooling for rendering the specifications * Discussions of updates to the specifications * Proposed changes to the specifications @@ -77,6 +78,32 @@ Crypto Driver Interface | [1.0 Alpha-1][crypto-driver-specs] | [doc/crypto-drive Reference header files for each minor version of each API are provided in the [headers/](headers) folder. +## Building the specifications + +This repository includes the documentation build tooling in [tools/](tools). The top-level `Makefile` uses that local tool copy by default, so a normal build does not require a separate checkout of the build tools. + +The core HTML build path requires Python, Sphinx, and `make`. PDF output also requires a LaTeX toolchain with `pdflatex`. Regenerating figures can require additional tools, depending on the figure source format, including Graphviz, `wavedrompy`, PlantUML, Java, and `rsvg-convert`. + +Build one specification from the repository root with: + +```sh +make doc/crypto/html +make doc/crypto/pdf +make doc/crypto/headers +make doc/crypto/api-diff +``` + +Replace `doc/crypto` with another specification directory, such as `doc/attestation`, `doc/storage`, `doc/fwu`, `doc/status-code`, or `doc/crypto-driver`. + +Build one output format for every specification with: + +```sh +make html +make pdf +``` + +Generated output is written under [build/](build). The build guide in [tools/docs/using-psa-api-tool.md](tools/docs/using-psa-api-tool.md) describes the available targets, dependencies, and validation flow. The editing reference in [tools/docs/psa-api-tool-notes.md](tools/docs/psa-api-tool-notes.md) describes the custom directives, roles, and source conventions used by the specifications. + ## Test Suite Test suites are available to validate compliance of API implementations against the specifications for Crypto, Attestation, and Secure Storage APIs, from: diff --git a/design/rfc-01-fwu-suit/fetch-sequence.puml b/design/rfc-01-fwu-suit/fetch-sequence.puml index 565f9cc6..2c5953ee 100644 --- a/design/rfc-01-fwu-suit/fetch-sequence.puml +++ b/design/rfc-01-fwu-suit/fetch-sequence.puml @@ -5,7 +5,7 @@ ' SUIT update using the FWU API -!include atg-spec.pumh +!include psa-spec.pumh box Network participant "Update server" as server diff --git a/design/rfc-01-fwu-suit/installer-sequence.puml b/design/rfc-01-fwu-suit/installer-sequence.puml index 00fc60c6..8b87af9f 100644 --- a/design/rfc-01-fwu-suit/installer-sequence.puml +++ b/design/rfc-01-fwu-suit/installer-sequence.puml @@ -3,7 +3,7 @@ @startuml -!include atg-spec.pumh +!include psa-spec.pumh ' Complex SUIT installation using the FWU API diff --git a/design/rfc-01-fwu-suit/no-reboot-sequence.puml b/design/rfc-01-fwu-suit/no-reboot-sequence.puml index 2ed3154b..419a9239 100644 --- a/design/rfc-01-fwu-suit/no-reboot-sequence.puml +++ b/design/rfc-01-fwu-suit/no-reboot-sequence.puml @@ -3,7 +3,7 @@ @startuml -!include atg-spec.pumh +!include psa-spec.pumh ' Complex SUIT installation using the FWU API, no boot diff --git a/design/rfc-01-fwu-suit/suit-install.puml b/design/rfc-01-fwu-suit/suit-install.puml index b658fc02..3cbb7985 100644 --- a/design/rfc-01-fwu-suit/suit-install.puml +++ b/design/rfc-01-fwu-suit/suit-install.puml @@ -3,7 +3,7 @@ @startuml -!include atg-spec.pumh +!include psa-spec.pumh ' title SUIT update : advanced installers diff --git a/design/rfc-01-fwu-suit/suit-update.puml b/design/rfc-01-fwu-suit/suit-update.puml index 02fd2f99..cdf8e642 100644 --- a/design/rfc-01-fwu-suit/suit-update.puml +++ b/design/rfc-01-fwu-suit/suit-update.puml @@ -3,7 +3,7 @@ @startuml -!include atg-spec.pumh +!include psa-spec.pumh ' title SUIT update : high-level flow diff --git a/doc/attestation/conf.py b/doc/attestation/conf.py index b7455619..78a83c72 100644 --- a/doc/attestation/conf.py +++ b/doc/attestation/conf.py @@ -68,14 +68,12 @@ 'page_break': 'chapter' } -# absolute or relative path to the psa_spec material from this file -# atg_sphinx_spec_dir = '../atg-sphinx-spec' - -# Set up and run the atg-sphinx-spec configuration +# Set up and run the psa-api-tool configuration import os -atg_sphinx_spec_dir = os.environ.get('ATG_SPHINX_SPEC') or atg_sphinx_spec_dir -exec(compile(open(os.path.join(atg_sphinx_spec_dir,'atg-sphinx-conf.py'), +psa_api_tool_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..', 'tools')) +psa_api_tool_path = os.environ.get('PSA_API_TOOL') or psa_api_tool_path +exec(compile(open(os.path.join(psa_api_tool_path,'psa-api-conf.py'), encoding='utf-8').read(), - 'atg-sphinx-conf.py', 'exec')) + 'psa-api-conf.py', 'exec')) diff --git a/doc/attestation/pyproject.toml b/doc/attestation/pyproject.toml new file mode 100644 index 00000000..52de3a0e --- /dev/null +++ b/doc/attestation/pyproject.toml @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +[tool.esbonio] diff --git a/doc/crypto-driver/conf.py b/doc/crypto-driver/conf.py index 5edc7445..c04bdab2 100644 --- a/doc/crypto-driver/conf.py +++ b/doc/crypto-driver/conf.py @@ -84,14 +84,12 @@ #'page_break': 'chapter' } -# absolute or relative path to the psa_spec material from this file -# atg_sphinx_spec_dir = '../atg-sphinx-spec' - -# Set up and run the atg-sphinx-spec configuration +# Set up and run the psa-api-tool configuration import os -atg_sphinx_spec_dir = os.environ.get('ATG_SPHINX_SPEC') or atg_sphinx_spec_dir -exec(compile(open(os.path.join(atg_sphinx_spec_dir,'atg-sphinx-conf.py'), +psa_api_tool_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..', 'tools')) +psa_api_tool_path = os.environ.get('PSA_API_TOOL') or psa_api_tool_path +exec(compile(open(os.path.join(psa_api_tool_path,'psa-api-conf.py'), encoding='utf-8').read(), - 'atg-sphinx-conf.py', 'exec')) + 'psa-api-conf.py', 'exec')) diff --git a/doc/crypto-driver/pyproject.toml b/doc/crypto-driver/pyproject.toml new file mode 100644 index 00000000..52de3a0e --- /dev/null +++ b/doc/crypto-driver/pyproject.toml @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +[tool.esbonio] diff --git a/doc/crypto/conf.py b/doc/crypto/conf.py index 37e72cd8..a442a3ee 100644 --- a/doc/crypto/conf.py +++ b/doc/crypto/conf.py @@ -99,14 +99,12 @@ 'prolog_files': ['/substitutions'], } -# absolute or relative path to the psa_spec material from this file -# atg_sphinx_spec_dir = '../atg-sphinx-spec' - -# Set up and run the atg-sphinx-spec configuration +# Set up and run the psa-api-tool configuration import os -atg_sphinx_spec_dir = os.environ.get('ATG_SPHINX_SPEC') or atg_sphinx_spec_dir -exec(compile(open(os.path.join(atg_sphinx_spec_dir,'atg-sphinx-conf.py'), +psa_api_tool_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..', 'tools')) +psa_api_tool_path = os.environ.get('PSA_API_TOOL') or psa_api_tool_path +exec(compile(open(os.path.join(psa_api_tool_path,'psa-api-conf.py'), encoding='utf-8').read(), - 'atg-sphinx-conf.py', 'exec')) + 'psa-api-conf.py', 'exec')) diff --git a/doc/crypto/figure/multi_part_operation.puml b/doc/crypto/figure/multi_part_operation.puml index c73cf3cc..add60e78 100644 --- a/doc/crypto/figure/multi_part_operation.puml +++ b/doc/crypto/figure/multi_part_operation.puml @@ -2,7 +2,7 @@ ' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license @startuml -!include atg-spec.pumh +!include psa-spec.pumh skinparam LegendFontSize 12 diff --git a/doc/crypto/figure/pake/j-pake.puml b/doc/crypto/figure/pake/j-pake.puml index defeff76..cb9e4da7 100644 --- a/doc/crypto/figure/pake/j-pake.puml +++ b/doc/crypto/figure/pake/j-pake.puml @@ -3,7 +3,7 @@ @startuml - !include atg-spec.pumh + !include psa-spec.pumh participant User participant Peer diff --git a/doc/crypto/figure/pake/spake2plus-reg.puml b/doc/crypto/figure/pake/spake2plus-reg.puml index f642280a..22e42ede 100644 --- a/doc/crypto/figure/pake/spake2plus-reg.puml +++ b/doc/crypto/figure/pake/spake2plus-reg.puml @@ -3,7 +3,7 @@ @startuml - !include atg-spec.pumh + !include psa-spec.pumh participant "Prover //(Client role)//" as Prover participant "Verifier //(Server role)//" as Verifier diff --git a/doc/crypto/figure/pake/spake2plus.puml b/doc/crypto/figure/pake/spake2plus.puml index cc91b8b0..08d95cbb 100644 --- a/doc/crypto/figure/pake/spake2plus.puml +++ b/doc/crypto/figure/pake/spake2plus.puml @@ -3,7 +3,7 @@ @startuml - !include atg-spec.pumh + !include psa-spec.pumh participant "Prover //(Client role)//" as Prover participant "Verifier //(Server role)//" as Verifier diff --git a/doc/crypto/figure/pake/wpa3-sae-pt.puml b/doc/crypto/figure/pake/wpa3-sae-pt.puml index bc4b0ec4..17afd73c 100644 --- a/doc/crypto/figure/pake/wpa3-sae-pt.puml +++ b/doc/crypto/figure/pake/wpa3-sae-pt.puml @@ -3,7 +3,7 @@ @startuml - !include atg-spec.pumh + !include psa-spec.pumh participant "station (STA)" as Participant diff --git a/doc/crypto/figure/pake/wpa3-sae.puml b/doc/crypto/figure/pake/wpa3-sae.puml index bb99bed6..0c0f3e01 100644 --- a/doc/crypto/figure/pake/wpa3-sae.puml +++ b/doc/crypto/figure/pake/wpa3-sae.puml @@ -3,7 +3,7 @@ @startuml - !include atg-spec.pumh + !include psa-spec.pumh participant "STA-A" as STAA participant "STA-B" as STAB diff --git a/doc/crypto/figure/sra/dfd_caller_isolation.puml b/doc/crypto/figure/sra/dfd_caller_isolation.puml index 08ca3c24..0328b364 100644 --- a/doc/crypto/figure/sra/dfd_caller_isolation.puml +++ b/doc/crypto/figure/sra/dfd_caller_isolation.puml @@ -2,8 +2,8 @@ ' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license @startuml -!include atg-spec.pumh -!include atg-dataflow.pumh +!include psa-spec.pumh +!include psa-dataflow.pumh dfd_agent "External system" as ext dfd_agent "External system" as ext2 diff --git a/doc/crypto/figure/sra/dfd_crypto_isolation.puml b/doc/crypto/figure/sra/dfd_crypto_isolation.puml index 94da4da2..0e804843 100644 --- a/doc/crypto/figure/sra/dfd_crypto_isolation.puml +++ b/doc/crypto/figure/sra/dfd_crypto_isolation.puml @@ -2,8 +2,8 @@ ' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license @startuml -!include atg-spec.pumh -!include atg-dataflow.pumh +!include psa-spec.pumh +!include psa-dataflow.pumh dfd_agent "External system" as ext dfd_tb("System boundary") { diff --git a/doc/crypto/figure/sra/dfd_no_isolation.puml b/doc/crypto/figure/sra/dfd_no_isolation.puml index ed437af9..a80ca5df 100644 --- a/doc/crypto/figure/sra/dfd_no_isolation.puml +++ b/doc/crypto/figure/sra/dfd_no_isolation.puml @@ -2,8 +2,8 @@ ' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license @startuml -!include atg-spec.pumh -!include atg-dataflow.pumh +!include psa-spec.pumh +!include psa-dataflow.pumh dfd_agent "External system" as ext dfd_tb("System boundary") { diff --git a/doc/crypto/figure/sra/system-entities.puml b/doc/crypto/figure/sra/system-entities.puml index 061c3b3b..2e0a66f7 100644 --- a/doc/crypto/figure/sra/system-entities.puml +++ b/doc/crypto/figure/sra/system-entities.puml @@ -2,7 +2,7 @@ ' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license @startuml -!include atg-spec.pumh +!include psa-spec.pumh skinparam defaultTextAlignment center diff --git a/doc/crypto/pyproject.toml b/doc/crypto/pyproject.toml new file mode 100644 index 00000000..52de3a0e --- /dev/null +++ b/doc/crypto/pyproject.toml @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +[tool.esbonio] diff --git a/doc/fwu/conf.py b/doc/fwu/conf.py index 3dea5982..e28f8a40 100644 --- a/doc/fwu/conf.py +++ b/doc/fwu/conf.py @@ -73,14 +73,12 @@ 'page_break': 'chapter', } -# absolute or relative path to the psa_spec material from this file -atg_sphinx_spec_dir = '../atg-sphinx-spec' - -# Set up and run the atg-sphinx-spec configuration +# Set up and run the psa-api-tool configuration import os -atg_sphinx_spec_dir = os.environ.get('ATG_SPHINX_SPEC') or atg_sphinx_spec_dir -exec(compile(open(os.path.join(atg_sphinx_spec_dir,'atg-sphinx-conf.py'), +psa_api_tool_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..', 'tools')) +psa_api_tool_path = os.environ.get('PSA_API_TOOL') or psa_api_tool_path +exec(compile(open(os.path.join(psa_api_tool_path,'psa-api-conf.py'), encoding='utf-8').read(), - 'atg-sphinx-conf.py', 'exec')) + 'psa-api-conf.py', 'exec')) diff --git a/doc/fwu/figure/arch/components.puml b/doc/fwu/figure/arch/components.puml index c8314680..78af628b 100644 --- a/doc/fwu/figure/arch/components.puml +++ b/doc/fwu/figure/arch/components.puml @@ -3,31 +3,31 @@ @startuml -!include atg-spec.pumh -!include atg-dataflow.pumh +!include psa-spec.pumh +!include psa-dataflow.pumh skinparam RectangleborderThickness 0 skinparam RectanglefontColor white -skinparam DatabaseBorderColor #ArmMidGray -skinparam InterfaceBorderColor #ArmMidGray +skinparam DatabaseBorderColor #PSAMidGray +skinparam InterfaceBorderColor #PSAMidGray -rectangle "Firmware creator" as creator #ArmDarkBlue +rectangle "Firmware creator" as creator #PSADarkBlue cloud Internet { - rectangle "Update server" as server #ArmMidGray + rectangle "Update server" as server #PSAMidGray } -interface "MQTT\n CoAP\nHTTPS\n ..." as tls #ArmLightGray +interface "MQTT\n CoAP\nHTTPS\n ..." as tls #PSALightGray dfd_tb("Device ") { - rectangle "Update client" as client #ArmMidGray - interface " **Firmware**\n**Update API**" as api #ArmGreen - rectangle "Update service" as service #ArmMidBlue - database "Firmware store" as store #ArmLightGray - rectangle "Bootloader" as bootloader #ArmMidBlue + rectangle "Update client" as client #PSAMidGray + interface " **Firmware**\n**Update API**" as api #PSAGreen + rectangle "Update service" as service #PSAMidBlue + database "Firmware store" as store #PSALightGray + rectangle "Bootloader" as bootloader #PSAMidBlue label " <&key>\nTrust anchor" as anchor } -database "External storage" as drive #ArmLightGray -interface "USB" as usb #ArmLightGray +database "External storage" as drive #PSALightGray +interface "USB" as usb #PSALightGray creator -> server server <- tls diff --git a/doc/fwu/figure/arch/trusted-client.puml b/doc/fwu/figure/arch/trusted-client.puml index f25404c5..4576ad8d 100644 --- a/doc/fwu/figure/arch/trusted-client.puml +++ b/doc/fwu/figure/arch/trusted-client.puml @@ -3,23 +3,23 @@ @startuml -!include atg-spec.pumh -!include atg-dataflow.pumh +!include psa-spec.pumh +!include psa-dataflow.pumh skinparam RectangleborderThickness 0 skinparam RectanglefontColor white skinparam FilefontColor white -skinparam DatabaseBorderColor #ArmMidGray -skinparam InterfaceBorderColor #ArmMidGray +skinparam DatabaseBorderColor #PSAMidGray +skinparam InterfaceBorderColor #PSAMidGray -file "Firmware package" <> as firmware #ArmDarkBlue +file "Firmware package" <> as firmware #PSADarkBlue dfd_tb($label = "Device") { - rectangle "Update client" <> as client #ArmMidGray - interface " **Firmware**\n**Update API**" as api #ArmGreen - rectangle "Update service" <> as service #ArmMidBlue - database "Firmware store" as store #ArmLightGray - rectangle "Bootloader" as bootloader #ArmMidBlue + rectangle "Update client" <> as client #PSAMidGray + interface " **Firmware**\n**Update API**" as api #PSAGreen + rectangle "Update service" <> as service #PSAMidBlue + database "Firmware store" as store #PSALightGray + rectangle "Bootloader" as bootloader #PSAMidBlue label " <&key>\nTrust anchor" as anchor } diff --git a/doc/fwu/figure/arch/untrusted-client.puml b/doc/fwu/figure/arch/untrusted-client.puml index e5d0512b..552cf9ac 100644 --- a/doc/fwu/figure/arch/untrusted-client.puml +++ b/doc/fwu/figure/arch/untrusted-client.puml @@ -3,27 +3,27 @@ @startuml -!include atg-spec.pumh -!include atg-dataflow.pumh +!include psa-spec.pumh +!include psa-dataflow.pumh skinparam RectangleborderThickness 0 skinparam RectanglefontColor white skinparam FilefontColor white -skinparam DatabaseBorderColor #ArmMidGray -skinparam InterfaceBorderColor #ArmMidGray +skinparam DatabaseBorderColor #PSAMidGray +skinparam InterfaceBorderColor #PSAMidGray -file "Firmware package" <> as firmware #ArmDarkBlue +file "Firmware package" <> as firmware #PSADarkBlue dfd_tb($label = "Device") { dfd_align() { - rectangle "Update client" <> as client #ArmMidGray - interface " **Firmware**\n**Update API**" as api #ArmGreen - rectangle "Update service proxy" <> as proxy #ArmMidBlue + rectangle "Update client" <> as client #PSAMidGray + interface " **Firmware**\n**Update API**" as api #PSAGreen + rectangle "Update service proxy" <> as proxy #PSAMidBlue } dfd_tb($label = "Platform Root of Trust") as rot { - rectangle "Update service" <> as service #ArmMidBlue - database "Firmware store" as store #ArmLightGray - rectangle "Bootloader" as bootloader #ArmMidBlue + rectangle "Update service" <> as service #PSAMidBlue + database "Firmware store" as store #PSALightGray + rectangle "Bootloader" as bootloader #PSAMidBlue label " <&key>\nTrust anchor" as anchor } } diff --git a/doc/fwu/figure/arch/untrusted-staging.puml b/doc/fwu/figure/arch/untrusted-staging.puml index c5c6ec95..be1aedce 100644 --- a/doc/fwu/figure/arch/untrusted-staging.puml +++ b/doc/fwu/figure/arch/untrusted-staging.puml @@ -3,27 +3,27 @@ @startuml -!include atg-spec.pumh -!include atg-dataflow.pumh +!include psa-spec.pumh +!include psa-dataflow.pumh skinparam RectangleborderThickness 0 skinparam RectanglefontColor white skinparam FilefontColor white -skinparam DatabaseBorderColor #ArmMidGray -skinparam InterfaceBorderColor #ArmMidGray +skinparam DatabaseBorderColor #PSAMidGray +skinparam InterfaceBorderColor #PSAMidGray -file "Firmware package" <> as firmware #ArmDarkBlue +file "Firmware package" <> as firmware #PSADarkBlue dfd_tb($label = "Device") { dfd_align() { - rectangle "Update client" <> as client #ArmMidGray - interface " **Firmware**\n**Update API**" as api #ArmGreen - rectangle "Update service" <> as service #ArmMidBlue - database "Staging area" as second #ArmLightGray + rectangle "Update client" <> as client #PSAMidGray + interface " **Firmware**\n**Update API**" as api #PSAGreen + rectangle "Update service" <> as service #PSAMidBlue + database "Staging area" as second #PSALightGray } dfd_tb($label = "Platform Root of Trust") { - database "//active// image" as active #ArmLightGray - rectangle "Bootloader" as bootloader #ArmMidBlue + database "//active// image" as active #PSALightGray + rectangle "Bootloader" as bootloader #PSAMidBlue label " <&key>\nTrust anchor" as anchor } } diff --git a/doc/fwu/figure/intro/context.puml b/doc/fwu/figure/intro/context.puml index 971ecb5a..716b93cb 100644 --- a/doc/fwu/figure/intro/context.puml +++ b/doc/fwu/figure/intro/context.puml @@ -3,23 +3,23 @@ @startuml -!include atg-spec.pumh -!include atg-dataflow.pumh +!include psa-spec.pumh +!include psa-dataflow.pumh skinparam RectangleborderThickness 0 skinparam RectanglefontColor white -skinparam DatabaseBorderColor #ArmMidGray -skinparam InterfaceBorderColor #ArmMidGray +skinparam DatabaseBorderColor #PSAMidGray +skinparam InterfaceBorderColor #PSAMidGray -rectangle "Firmware creator" as creator #ArmDarkBlue +rectangle "Firmware creator" as creator #PSADarkBlue cloud Internet { - rectangle "Update server" as server #ArmMidGray + rectangle "Update server" as server #PSAMidGray } -interface "MQTT\n CoAP\nHTTPS\n ..." as tls #ArmLightGray +interface "MQTT\n CoAP\nHTTPS\n ..." as tls #PSALightGray dfd_tb("Device ") { - rectangle "Update client" as client #ArmMidBlue - database "Firmware store" as store #ArmLightGray + rectangle "Update client" as client #PSAMidBlue + database "Firmware store" as store #PSALightGray } creator -> server diff --git a/doc/fwu/figure/intro/fwu-api.puml b/doc/fwu/figure/intro/fwu-api.puml index d258e686..e3346d98 100644 --- a/doc/fwu/figure/intro/fwu-api.puml +++ b/doc/fwu/figure/intro/fwu-api.puml @@ -3,26 +3,26 @@ @startuml -!include atg-spec.pumh -!include atg-dataflow.pumh +!include psa-spec.pumh +!include psa-dataflow.pumh skinparam RectangleborderThickness 0 skinparam RectanglefontColor white -skinparam DatabaseBorderColor #ArmMidGray -skinparam InterfaceBorderColor #ArmMidGray +skinparam DatabaseBorderColor #PSAMidGray +skinparam InterfaceBorderColor #PSAMidGray -rectangle "Firmware creator" as creator #ArmDarkBlue +rectangle "Firmware creator" as creator #PSADarkBlue cloud Internet { - rectangle "Update server" as server #ArmMidGray + rectangle "Update server" as server #PSAMidGray } -interface "MQTT\n CoAP\nHTTPS\n ..." as tls #ArmLightGray +interface "MQTT\n CoAP\nHTTPS\n ..." as tls #PSALightGray dfd_tb("Device ") { - rectangle "Update client" as client #ArmMidGray - interface " **Firmware**\n**Update API**" as api #ArmGreen - rectangle "Update service" as service #ArmMidBlue - database "Firmware store" as store #ArmLightGray - rectangle "Bootloader" as bootloader #ArmMidBlue + rectangle "Update client" as client #PSAMidGray + interface " **Firmware**\n**Update API**" as api #PSAGreen + rectangle "Update service" as service #PSAMidBlue + database "Firmware store" as store #PSALightGray + rectangle "Bootloader" as bootloader #PSAMidBlue } creator -> server diff --git a/doc/fwu/figure/sequence.puml b/doc/fwu/figure/sequence.puml index 19109bc8..5262ddd9 100644 --- a/doc/fwu/figure/sequence.puml +++ b/doc/fwu/figure/sequence.puml @@ -3,7 +3,7 @@ @startuml -!include atg-spec.pumh +!include psa-spec.pumh participant "Update server" as server participant "Update client" as client diff --git a/doc/fwu/figure/sra/lifecycle.puml b/doc/fwu/figure/sra/lifecycle.puml index aa6ec7e8..ec41710e 100644 --- a/doc/fwu/figure/sra/lifecycle.puml +++ b/doc/fwu/figure/sra/lifecycle.puml @@ -3,8 +3,8 @@ @startuml -!include atg-spec.pumh -!include atg-lifecycle.pumh +!include psa-spec.pumh +!include psa-lifecycle.pumh stakeholder "SiP and OEM" as k1 stakeholder "SiP, OEM,\nand/or Owner" as k2 diff --git a/doc/fwu/figure/states/default.puml b/doc/fwu/figure/states/default.puml index 8ad98abb..fb16f403 100644 --- a/doc/fwu/figure/states/default.puml +++ b/doc/fwu/figure/states/default.puml @@ -4,7 +4,7 @@ @startuml ' State diagram -!include atg-spec.pumh +!include psa-spec.pumh legend **Transitions** diff --git a/doc/fwu/figure/states/no-reboot-no-trial-volatile.puml b/doc/fwu/figure/states/no-reboot-no-trial-volatile.puml index 3910f448..3e66a0f7 100644 --- a/doc/fwu/figure/states/no-reboot-no-trial-volatile.puml +++ b/doc/fwu/figure/states/no-reboot-no-trial-volatile.puml @@ -4,7 +4,7 @@ @startuml '' State diagram -!include atg-spec.pumh +!include psa-spec.pumh skinparam LegendFontSize 12 diff --git a/doc/fwu/figure/states/no-reboot-no-trial.puml b/doc/fwu/figure/states/no-reboot-no-trial.puml index cb778517..dcafbc39 100644 --- a/doc/fwu/figure/states/no-reboot-no-trial.puml +++ b/doc/fwu/figure/states/no-reboot-no-trial.puml @@ -4,7 +4,7 @@ @startuml '' State diagram -!include atg-spec.pumh +!include psa-spec.pumh skinparam LegendFontSize 12 diff --git a/doc/fwu/figure/states/no-reboot-volatile.puml b/doc/fwu/figure/states/no-reboot-volatile.puml index d8b3ebf6..beadf9d0 100644 --- a/doc/fwu/figure/states/no-reboot-volatile.puml +++ b/doc/fwu/figure/states/no-reboot-volatile.puml @@ -4,7 +4,7 @@ @startuml '' State diagram -!include atg-spec.pumh +!include psa-spec.pumh skinparam LegendFontSize 12 diff --git a/doc/fwu/figure/states/no-reboot.puml b/doc/fwu/figure/states/no-reboot.puml index 33149c36..d193ce5c 100644 --- a/doc/fwu/figure/states/no-reboot.puml +++ b/doc/fwu/figure/states/no-reboot.puml @@ -4,7 +4,7 @@ @startuml '' State diagram -!include atg-spec.pumh +!include psa-spec.pumh skinparam LegendFontSize 12 diff --git a/doc/fwu/figure/states/no-trial-volatile.puml b/doc/fwu/figure/states/no-trial-volatile.puml index f382ad6a..b477457d 100644 --- a/doc/fwu/figure/states/no-trial-volatile.puml +++ b/doc/fwu/figure/states/no-trial-volatile.puml @@ -4,7 +4,7 @@ @startuml '' State diagram -!include atg-spec.pumh +!include psa-spec.pumh skinparam LegendFontSize 12 diff --git a/doc/fwu/figure/states/no-trial.puml b/doc/fwu/figure/states/no-trial.puml index cde5f676..1055197b 100644 --- a/doc/fwu/figure/states/no-trial.puml +++ b/doc/fwu/figure/states/no-trial.puml @@ -4,7 +4,7 @@ @startuml '' State diagram -!include atg-spec.pumh +!include psa-spec.pumh skinparam LegendFontSize 12 diff --git a/doc/fwu/figure/states/volatile.puml b/doc/fwu/figure/states/volatile.puml index e8433398..16ef6079 100644 --- a/doc/fwu/figure/states/volatile.puml +++ b/doc/fwu/figure/states/volatile.puml @@ -4,7 +4,7 @@ @startuml ' State diagram -!include atg-spec.pumh +!include psa-spec.pumh skinparam LegendFontSize 12 diff --git a/doc/fwu/pyproject.toml b/doc/fwu/pyproject.toml new file mode 100644 index 00000000..52de3a0e --- /dev/null +++ b/doc/fwu/pyproject.toml @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +[tool.esbonio] diff --git a/doc/status-code/conf.py b/doc/status-code/conf.py index 662997f5..02032788 100644 --- a/doc/status-code/conf.py +++ b/doc/status-code/conf.py @@ -73,26 +73,12 @@ 'page_break': 'chapter', } -# If the draft flag is set, then include extra content and watermark - -if doc_info.get('draft'): - doc_info.pop('date', None) # Remove any release date - use build date - doc_info['include_content'] = ['rationale', 'todo', 'banner'] - doc_info['watermark'] = "DRAFT" - -# If a release candidate, then include watermark - -if doc_info.get('release_candidate'): - doc_info['watermark'] = "Candidate" - -# absolute or relative path to the psa_spec material from this file -# atg_sphinx_spec_dir = '../atg-sphinx-spec' - -# Set up and run the atg-sphinx-spec configuration +# Set up and run the psa-api-tool configuration import os -atg_sphinx_spec_dir = os.environ.get('ATG_SPHINX_SPEC') or atg_sphinx_spec_dir -exec(compile(open(os.path.join(atg_sphinx_spec_dir,'atg-sphinx-conf.py'), +psa_api_tool_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..', 'tools')) +psa_api_tool_path = os.environ.get('PSA_API_TOOL') or psa_api_tool_path +exec(compile(open(os.path.join(psa_api_tool_path,'psa-api-conf.py'), encoding='utf-8').read(), - 'atg-sphinx-conf.py', 'exec')) + 'psa-api-conf.py', 'exec')) diff --git a/doc/status-code/pyproject.toml b/doc/status-code/pyproject.toml new file mode 100644 index 00000000..52de3a0e --- /dev/null +++ b/doc/status-code/pyproject.toml @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +[tool.esbonio] diff --git a/doc/storage/conf.py b/doc/storage/conf.py index 28ee2c7f..84e3bdd5 100644 --- a/doc/storage/conf.py +++ b/doc/storage/conf.py @@ -87,14 +87,12 @@ 'page_break': 'chapter', } -# absolute or relative path to the psa_spec material from this file -# atg_sphinx_spec_dir = '../atg-sphinx-spec' - -# Set up and run the atg-sphinx-spec configuration +# Set up and run the psa-api-tool configuration import os -atg_sphinx_spec_dir = os.environ.get('ATG_SPHINX_SPEC') or atg_sphinx_spec_dir -exec(compile(open(os.path.join(atg_sphinx_spec_dir,'atg-sphinx-conf.py'), +psa_api_tool_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..', 'tools')) +psa_api_tool_path = os.environ.get('PSA_API_TOOL') or psa_api_tool_path +exec(compile(open(os.path.join(psa_api_tool_path,'psa-api-conf.py'), encoding='utf-8').read(), - 'atg-sphinx-conf.py', 'exec')) + 'psa-api-conf.py', 'exec')) diff --git a/doc/storage/figure/callers.puml b/doc/storage/figure/callers.puml index 19910fb8..516469c5 100644 --- a/doc/storage/figure/callers.puml +++ b/doc/storage/figure/callers.puml @@ -1,35 +1,35 @@ ' SPDX-FileCopyrightText: Copyright 2023 Arm Limited and/or its affiliates -' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license - -@startuml - -!include atg-spec.pumh -!include atg-dataflow.pumh - -skinparam RectangleborderThickness 0 -skinparam RectanglefontColor white -skinparam DatabaseBorderColor #ArmMidGray -skinparam InterfaceBorderColor #ArmMidGray - -left to right direction - -dfd_tb("Application boundary") { - rectangle "Application 1" as caller1 #ArmMidGray - interface "**Secure Storage API**" as api1 #ArmGreen -} -dfd_tb("Data protection boundary") { -rectangle "Storage service" as service #ArmMidBlue -database "Storage medium" as store #ArmLightGray -} -dfd_tb("Application boundary") { - interface "**Secure Storage API**" as api2 #ArmGreen - rectangle "Application 2" as caller2 #ArmMidGray -} - -caller1 -d- api1 -api1 -d-> service -service <--> store -caller2 -d- api2 -api2 -d-> service - -@enduml +' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +@startuml + +!include psa-spec.pumh +!include psa-dataflow.pumh + +skinparam RectangleborderThickness 0 +skinparam RectanglefontColor white +skinparam DatabaseBorderColor #PSAMidGray +skinparam InterfaceBorderColor #PSAMidGray + +left to right direction + +dfd_tb("Application boundary") { + rectangle "Application 1" as caller1 #PSAMidGray + interface "**Secure Storage API**" as api1 #PSAGreen +} +dfd_tb("Data protection boundary") { +rectangle "Storage service" as service #PSAMidBlue +database "Storage medium" as store #PSALightGray +} +dfd_tb("Application boundary") { + interface "**Secure Storage API**" as api2 #PSAGreen + rectangle "Application 2" as caller2 #PSAMidGray +} + +caller1 -d- api1 +api1 -d-> service +service <--> store +caller2 -d- api2 +api2 -d-> service + +@enduml diff --git a/doc/storage/figure/dm-authorized.puml b/doc/storage/figure/dm-authorized.puml index 6a962e0a..48d172ef 100644 --- a/doc/storage/figure/dm-authorized.puml +++ b/doc/storage/figure/dm-authorized.puml @@ -1,31 +1,31 @@ ' SPDX-FileCopyrightText: Copyright 2023 Arm Limited and/or its affiliates -' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license - -@startuml - -!include atg-spec.pumh -!include atg-dataflow.pumh - -skinparam RectangleborderThickness 0 -skinparam RectanglefontColor white -skinparam DatabaseBorderColor #ArmMidGray -skinparam InterfaceBorderColor #ArmMidGray - -left to right direction - -dfd_tb("Device ") { - rectangle "Application" as caller #ArmMidGray - interface "**Secure Storage API**" as api #ArmGreen - dfd_tb($label="Platform Root of Trust") { - rectangle "Storage service" as service #ArmMidBlue - } - dfd_tb($label="Replay-Protected\nMemory Block") { - database "Storage medium" as store #ArmLightGray - } -} - -caller -d- api -api -d-> service : DF1 -service <-d-> store : DF4 - -@enduml +' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +@startuml + +!include psa-spec.pumh +!include psa-dataflow.pumh + +skinparam RectangleborderThickness 0 +skinparam RectanglefontColor white +skinparam DatabaseBorderColor #PSAMidGray +skinparam InterfaceBorderColor #PSAMidGray + +left to right direction + +dfd_tb("Device ") { + rectangle "Application" as caller #PSAMidGray + interface "**Secure Storage API**" as api #PSAGreen + dfd_tb($label="Platform Root of Trust") { + rectangle "Storage service" as service #PSAMidBlue + } + dfd_tb($label="Replay-Protected\nMemory Block") { + database "Storage medium" as store #PSALightGray + } +} + +caller -d- api +api -d-> service : DF1 +service <-d-> store : DF4 + +@enduml diff --git a/doc/storage/figure/dm-exposed.puml b/doc/storage/figure/dm-exposed.puml index c85f66ae..7501975a 100644 --- a/doc/storage/figure/dm-exposed.puml +++ b/doc/storage/figure/dm-exposed.puml @@ -1,29 +1,29 @@ ' SPDX-FileCopyrightText: Copyright 2023 Arm Limited and/or its affiliates -' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license - -@startuml - -!include atg-spec.pumh -!include atg-dataflow.pumh - -skinparam RectangleborderThickness 0 -skinparam RectanglefontColor white -skinparam DatabaseBorderColor #ArmMidGray -skinparam InterfaceBorderColor #ArmMidGray - -left to right direction - -dfd_tb("Device ") { - rectangle "Application" as caller #ArmMidGray - interface "**Secure Storage API**" as api #ArmGreen - dfd_tb($label="Platform Root of Trust") { - rectangle "Storage service" as service #ArmMidBlue - } - database "Storage medium" as store #ArmLightGray -} - -caller -d- api -api -d-> service : DF1 -service <-d-> store : DF3 - -@enduml +' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +@startuml + +!include psa-spec.pumh +!include psa-dataflow.pumh + +skinparam RectangleborderThickness 0 +skinparam RectanglefontColor white +skinparam DatabaseBorderColor #PSAMidGray +skinparam InterfaceBorderColor #PSAMidGray + +left to right direction + +dfd_tb("Device ") { + rectangle "Application" as caller #PSAMidGray + interface "**Secure Storage API**" as api #PSAGreen + dfd_tb($label="Platform Root of Trust") { + rectangle "Storage service" as service #PSAMidBlue + } + database "Storage medium" as store #PSALightGray +} + +caller -d- api +api -d-> service : DF1 +service <-d-> store : DF3 + +@enduml diff --git a/doc/storage/figure/dm-protected.puml b/doc/storage/figure/dm-protected.puml index e2b3a578..49a33f63 100644 --- a/doc/storage/figure/dm-protected.puml +++ b/doc/storage/figure/dm-protected.puml @@ -1,29 +1,29 @@ ' SPDX-FileCopyrightText: Copyright 2023 Arm Limited and/or its affiliates -' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license - -@startuml - -!include atg-spec.pumh -!include atg-dataflow.pumh - -skinparam RectangleborderThickness 0 -skinparam RectanglefontColor white -skinparam DatabaseBorderColor #ArmMidGray -skinparam InterfaceBorderColor #ArmMidGray - -left to right direction - -dfd_tb("Device ") { - rectangle "Application" as caller #ArmMidGray - interface "**Secure Storage API**" as api #ArmGreen - dfd_tb($label="Platform Root of Trust") { - rectangle "Storage\nservice" as service #ArmMidBlue - database "Storage medium" as store #ArmLightGray - } -} - -caller -d- api -api -d-> service : DF1 -service <--> store : DF2 - -@enduml +' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +@startuml + +!include psa-spec.pumh +!include psa-dataflow.pumh + +skinparam RectangleborderThickness 0 +skinparam RectanglefontColor white +skinparam DatabaseBorderColor #PSAMidGray +skinparam InterfaceBorderColor #PSAMidGray + +left to right direction + +dfd_tb("Device ") { + rectangle "Application" as caller #PSAMidGray + interface "**Secure Storage API**" as api #PSAGreen + dfd_tb($label="Platform Root of Trust") { + rectangle "Storage\nservice" as service #PSAMidBlue + database "Storage medium" as store #PSALightGray + } +} + +caller -d- api +api -d-> service : DF1 +service <--> store : DF2 + +@enduml diff --git a/doc/storage/figure/dm-secure-link.puml b/doc/storage/figure/dm-secure-link.puml index f9096ed3..fd21f993 100644 --- a/doc/storage/figure/dm-secure-link.puml +++ b/doc/storage/figure/dm-secure-link.puml @@ -1,31 +1,31 @@ ' SPDX-FileCopyrightText: Copyright 2023 Arm Limited and/or its affiliates -' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license - -@startuml - -!include atg-spec.pumh -!include atg-dataflow.pumh - -skinparam RectangleborderThickness 0 -skinparam RectanglefontColor white -skinparam DatabaseBorderColor #ArmMidGray -skinparam InterfaceBorderColor #ArmMidGray - -left to right direction - -dfd_tb("Device ") { - rectangle "Application" as caller #ArmMidGray - interface "**Secure Storage API**" as api #ArmGreen - dfd_tb($label="Platform Root of Trust") { - rectangle "Storage service" as service #ArmMidBlue - } - dfd_tb($label="Secure Element") { - database "Storage medium" as store #ArmLightGray - } -} - -caller -d- api -api -d-> service : DF1 -service <-d-> store : DF5 - -@enduml +' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +@startuml + +!include psa-spec.pumh +!include psa-dataflow.pumh + +skinparam RectangleborderThickness 0 +skinparam RectanglefontColor white +skinparam DatabaseBorderColor #PSAMidGray +skinparam InterfaceBorderColor #PSAMidGray + +left to right direction + +dfd_tb("Device ") { + rectangle "Application" as caller #PSAMidGray + interface "**Secure Storage API**" as api #PSAGreen + dfd_tb($label="Platform Root of Trust") { + rectangle "Storage service" as service #PSAMidBlue + } + dfd_tb($label="Secure Element") { + database "Storage medium" as store #PSALightGray + } +} + +caller -d- api +api -d-> service : DF1 +service <-d-> store : DF5 + +@enduml diff --git a/doc/storage/figure/lifecycle.puml b/doc/storage/figure/lifecycle.puml index ff5bc138..160264b4 100644 --- a/doc/storage/figure/lifecycle.puml +++ b/doc/storage/figure/lifecycle.puml @@ -1,39 +1,39 @@ ' SPDX-FileCopyrightText: Copyright 2023 Arm Limited and/or its affiliates -' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license - -@startuml - -!include atg-spec.pumh -!include atg-lifecycle.pumh - -stakeholder "SiP and OEM" as n1 -stakeholder "SiP and/or OEM" as n2 -stakeholder "[everybody]" as n3 -stakeholder_skip as n4 -stakeholder "SiP, OEM,\nand Owner" as n5 - -lifecycle_phase "Manufacturing" as r1 { - lifecycle_state "System\nmanufacturing\nand initialization" as lc1 - lifecycle_state "Provision of\nRoot of Trust\nsecrets" as lc2 -} -lifecycle_phase "Operational" as r3 { - lifecycle_state "Boot" as lc3 - lifecycle_state "Secure operation" as lc4 -} -lifecycle_phase "End of life" as r5 { - lifecycle_state "Return to\nManufacturer" as lc5 -} - -lc1 --> lc2 -lc2 --> lc3 -lc3 --> lc4 -lc4 --> lc5 - -n1 -[hidden]- n2 -n2 -[hidden]- n3 -n3 -[hidden]- n4 -n4 -[hidden]- n5 - -lc1 -[hidden] n1 - -@enduml +' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +@startuml + +!include psa-spec.pumh +!include psa-lifecycle.pumh + +stakeholder "SiP and OEM" as n1 +stakeholder "SiP and/or OEM" as n2 +stakeholder "[everybody]" as n3 +stakeholder_skip as n4 +stakeholder "SiP, OEM,\nand Owner" as n5 + +lifecycle_phase "Manufacturing" as r1 { + lifecycle_state "System\nmanufacturing\nand initialization" as lc1 + lifecycle_state "Provision of\nRoot of Trust\nsecrets" as lc2 +} +lifecycle_phase "Operational" as r3 { + lifecycle_state "Boot" as lc3 + lifecycle_state "Secure operation" as lc4 +} +lifecycle_phase "End of life" as r5 { + lifecycle_state "Return to\nManufacturer" as lc5 +} + +lc1 --> lc2 +lc2 --> lc3 +lc3 --> lc4 +lc4 --> lc5 + +n1 -[hidden]- n2 +n2 -[hidden]- n3 +n3 -[hidden]- n4 +n4 -[hidden]- n5 + +lc1 -[hidden] n1 + +@enduml diff --git a/doc/storage/figure/storage.puml b/doc/storage/figure/storage.puml index 0947d76a..f48add93 100644 --- a/doc/storage/figure/storage.puml +++ b/doc/storage/figure/storage.puml @@ -2,7 +2,7 @@ ' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license @startuml -!include atg-spec.pumh +!include psa-spec.pumh participant "Application" as app box "Platform Root of Trust" #E0E0E0 diff --git a/doc/storage/pyproject.toml b/doc/storage/pyproject.toml new file mode 100644 index 00000000..52de3a0e --- /dev/null +++ b/doc/storage/pyproject.toml @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright 2026 Arm Limited and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +[tool.esbonio] diff --git a/tools/atg-sphinx-conf.py b/tools/atg-sphinx-conf.py new file mode 100644 index 00000000..f3086d8b --- /dev/null +++ b/tools/atg-sphinx-conf.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +# SPDX-License-Identifier: Apache-2.0 + +# Compatibility wrapper for older specification sources that still execute +# atg-sphinx-conf.py from the former atg-sphinx-spec tool. + +import os + +psa_api_tool_path = (os.environ.get('PSA_API_TOOL') or + os.environ.get('ATG_SPHINX_SPEC') or + globals().get('atg_sphinx_spec_dir')) + +if not psa_api_tool_path: + raise RuntimeError('PSA_API_TOOL or ATG_SPHINX_SPEC must point to psa-api-tool') + +exec(compile(open(os.path.join(psa_api_tool_path, 'psa-api-conf.py'), + encoding='utf-8').read(), + 'psa-api-conf.py', 'exec')) diff --git a/tools/docs/psa-api-tool-notes.md b/tools/docs/psa-api-tool-notes.md new file mode 100644 index 00000000..0d0d7478 --- /dev/null +++ b/tools/docs/psa-api-tool-notes.md @@ -0,0 +1,2516 @@ + + +# psa-api-tool Notes + +This note is a lightweight contributor and editor guide for the `psa-api-tool` Sphinx +extension and build support. + +It is intended to capture the practical behavior of the tool as used in PSA +specification sources. It is written for both human editors and automated editing +agents, so headings, directive names, role names, and examples should remain explicit +and easy to search. + +This is not a full implementation reference. When behavior is unclear, verify against +`psa-api-tool.py`, `psa-api-conf.py`, the shared `make` file, and the template files. + +## Audience and Scope + +These notes describe: + +- what each custom directive or role is for +- the important arguments and options +- where it can be used +- shared PSA API source conventions +- known limitations or gotchas +- minimal working examples + +This note currently focuses on the parts of `psa-api-tool` that are needed to edit and +extend specification source text effectively, especially: + +- API and manifest definition directives +- release metadata inputs +- terms/glossary definitions +- references and citation roles + +It also records shared source conventions used across PSA API specifications. Individual +specification repositories can add narrower local rules where needed. + +Broader setup and build instructions are in `using-psa-api-tool.md`. This includes +dependency notes, repository layout, configuring document metadata in `conf.py`, and +running common build targets. + +## Guide Map + +Use these sections as the usual entry points: + +- `Shared Source Editing Conventions` for punctuation, wrapping, recommendation wording, + graphics, API definition layout, generated headers, admonitions, and inline code + style. +- `Directive Reference` for custom and modified directives, grouped by typical authoring + context. +- `Role Reference` for custom roles and modified reference behavior. +- `Security Risk Assessment Directives and Roles` for SRA-specific structured threat + content. + +## Shared Source Editing Conventions + +These conventions are intended for PSA API specifications that use `psa-api-tool`. They +should be treated as shared conventions unless a consuming repository explicitly says +otherwise. + +### Plain source punctuation + +Use plain ASCII single and double quote characters in specification sources. Sphinx +converts quotes and apostrophes to the appropriate typographic characters when rendering +HTML and LaTeX output, so source files should not use Unicode curly quotation marks such +as `‘`, `’`, `“`, or `”`. + +Use ASCII source forms for dashes when typographic dashes are intended in the rendered +output. Sphinx converts `--` to an en-dash and `---` to an em-dash. Use these source +forms instead of Unicode dash characters. For example, use an em-dash for an explanatory +aside: + +```rst +The value is private to the caller --- it cannot be read by another component. +``` + +Use normal ASCII hyphens for hyphenated terms and ASCII minus signs in code, numeric +ranges, and literal values. + +### American English spelling + +Use American English spelling in specification sources. For example, use `behavior`, +`initialize`, `optimization`, and `synchronization`. + +### Source line wrapping and indentation + +These conventions apply to all reStructuredText specification sources, including `.rst` +files and extensionless included source fragments such as `terms`, `references`, and +`releases`. + +Use one logical paragraph per source line. Let the editor wrap long source lines for +display. This keeps the source structure aligned with the document structure, avoids +inconsistent wrapping in files edited with different window widths, and matches the +current style used in other PSA API specifications. + +Keep block indentation consistent within a source file. Use three spaces for directive +bodies, nested directive content, list continuation text, and other indented +reStructuredText blocks. + +Use the same three-space convention for `.. list-table::` directives. The directive +options and outer row list are indented by three spaces, nested cell list items are +indented by six spaces, and table cell continuation text is indented by nine spaces. + +Use bullet-list markers with two spaces after the marker, for example `* List item`. +This means the text starts after three source characters and aligns naturally with +continuation lines that use the three-space indentation convention. Enumerated list +markers already occupy three source characters when they use a single-digit number, an +alphabetic marker, or automatic numbering, for example `1. List item`, `a. List item`, +or `#. List item`. + +For nested bullet lists, use `-` as the inner list marker under an outer `*` list. +Indent the nested marker by three spaces and keep the same two spaces after the marker, +for example ` - Nested item`. The same marker convention is used in `.. list-table::` +row and cell lists. + +When making semantic changes, do not reformat unrelated paragraphs or indentation in the +same patch. If a source file has mixed wrapping or indentation conventions, clean it up +in a separate mechanical formatting pass so that review can distinguish formatting churn +from specification changes. + +### Recommendation wording + +When documenting recommended behavior or design choices, use neutral specification +wording rather than organization-voiced wording. + +Prefer: + +- `It is recommended that ...` +- `The following behavior is recommended:` + +Avoid: + +- `Arm recommends that ...` +- `We recommend that ...` + +This keeps advisory specification text independent of a motivated speaker, while still +distinguishing recommendations from normative requirements such as `must`, `must not`, +`shall`, and `shall not`. + +### Rendered graphics + +Rendered graphics are normally checked in to specification repositories. This means that +editors who only modify text can build the documents without installing every graphics +rendering tool. + +When modifying graphics sources, update the rendered assets at the same time. For +example, changes to `.svg` figures must keep the corresponding `.pdf` rendering up to +date for PDF builds. The `images` make target regenerates graphics derived from `.json`, +`.puml`, and `.svg` sources when the required tools are available. + +### C API element definitions + +The `.. macro::`, `.. typedef::`, `.. struct::`, `.. function::`, and `.. enum::` +directives are top-level directives that define C API elements. This section describes +common aspects of these directives, and best practices for using them. + +Details for each directive are provided in the Directive Reference. + +#### Rendering + +In the current tooling, each API element directive forms a section node within the +document model, and the API element name forms the title for the section. As a result, +an API element defined within a heading level 3 section will be a level 4 section, an +API element defined within a heading level 2 section will be a level 3 section. The +effect is that the heading format for an API element depends on the enclosing section +level. + +For consistency in a specification, it is important to have every API element defined at +the same level in the document hierarchy. Existing PSA API specifications commonly use +level 4, the first level that does not include a section number in the heading. This +occasionally requires creative chapter structuring so that this is achieved. + +#### Standard options + +These options are available for all C API element directives, but are used rarely, only +when required for the individual API element. + +- `:header:` (optional) - see the header file section below for details. +- `:guard:` (optional) - this adds an optional #ifdef guard around the API element + definition in the header file. +- `:comment:` (optional) - specify a comment text to include in the generated header + before the API element definition. +- `:naked:` (optional) - do not include this API element in any header file. + +#### Typical layout of element definitions + +After the element directive and any options, which can include a definition for macros, +the following is a recommended ordering for API definitions: + +- Summary directive. +- Sub-element directives, such as parameters for a function or macro-like function; + fields for a structure; or values for an enumeration. +- Return directive. +- Return value directives for a function or macro-like function. +- Description, as text within the element directive. + +If a specification or a set of related API elements has shared subsections in the +description, add them as `.. subsection::` directives. Place them consistently in the +source to aid maintenance. Examples include the 'Key format' sections in the Crypto API +key type definitions and the 'Compatible key types' sections in the Crypto API algorithm +definitions. + +When rendered, the tool fixes the ordering of the API definition (to match the list +above), then any subsections labeled `:top:`, then the general description, and finally +any subsections not labeled `:top:`. The recommended layout here matches that to +facilitate easier editing. + +Optional: it is possible to specify a document-wide ordering of return values (typically +for error codes) in the `doc_info` dictionary, to force consistency in the output +without demanding the same in the source material. This is used for the larger API +specifications, such as the Crypto API. + +#### Using `summary` directives + +Each C API element directive should have a `.. summary::` directive with a short +description of the API. The first sentence of the summary is used when generating +Doxygen-enhanced reference header file output with the `headers` make format. + +#### Placement of `.. versionadded::` directives + +After testing, the best placement for these in API element descriptions is at the end of +the `summary` directive. For example: + +```rst +.. macro:: EXAMPLE + + .. summary:: An example macro definition. + + .. versionadded:: 2.3 + + More content. +``` + +The same is true for the `.. versionchanged::`, `.. deprecated::`, and `.. +versionremoved::` directives. + +Shared PSA API convention for API definitions: + +- Use `.. versionadded::` only for new API elements. +- Use `.. versionchanged::` for existing API elements whose behavior, constraints, or + meaning changed materially. +- Use `.. deprecated::` for existing API elements that remain specified but are + deprecated. +- Omit the directive body when the marker is self-explanatory. +- If a body is useful, use at most one short sentence. +- Keep rationale, migration guidance, and fuller explanation in the normal API prose + rather than in the version directive body. +- Do not use version markup just because a version-valued macro or function now reports + the newer document version. Reserve it for API evolution that a reader needs called + out explicitly. + +### Generated header files + +The tooling builds API element definitions from the directive-based descriptions in the +source specification. This ensures that element definitions are always consistent +between the detailed description, the API signature, and the content of any generated +embedded or external header file. + +#### Associating an API element with a named header file + +The tooling supports different types of specification project. + +- If a `header` attribute is specified in the `conf.py` `doc_info` dictionary, this is + the default header for an API element. + +- If a documentation source file uses a `.. header::` directive, this defines the header + to associate with any element definitions that follow it. This directive can be used + multiple times in a single source file for different headers. + +- A single instance of the `.. header::` directive for each header file can also be used + to specify additional information about a header file, such as inclusion of a license + or copyright preamble and header guard inclusion. See the description of the `.. + header::` directive for details. + +- If an API element uses the `:header:` option, this overrides any default or directive + setting. + +Guidance: + +- For a single header project, use the `doc_info['header']` attribute. +- For a multi-header project, use the `.. header::` directive as required. +- For the odd API element that lives in a different file, use the `:header:` option. + +#### Use of generated headers + +Generated headers are used in three ways: + +1. Included inline in the specification output. This uses the `.. insert-header::` + directive, and produces an in-sequence, unannotated, cross-referenced source listing + of the canonical header. + +2. Output using the `api-db` build format, which is used by the `api-db`, `api-diff`, + and `api-update` make formats to create, review, and maintain a baseline API + definition to enable checks that changes to the specification sources only introduce + expected changes to the API. This format has the elements alphabetically sorted and + unannotated. + + By default, the `api-*` make targets use or update a set of headers in the document's + `api.db/` folder. + +3. Output using the `headers` build format, which can be used to create + Doxygen-annotated, in-sequence, copyright-commented canonical header files for + inclusion in a project repository. This output is useful for implementation + developers because IDEs can use the annotations for API tooltips and code completion. + The level of Doxygen annotation is controlled by the `header_doxygen` configuration + attribute in `doc_info`. + +#### API element sequencing within header files + +For the `.. insert-header::` directive and the `headers` build format, the API elements +appear in the header file in the order that they are defined in the source files. When +multiple source files provide definitions for the same header file, the `:seq:` option +should be used in a `.. header::` directive to ensure that the definitions from +different source files appear in the intended order in the header file. + +In contrast, the `api-db` build format used by `api-diff` and `api-update` normalizes +the output by API element identity, so it is resistant to documentation refactoring and +to incidental variation in Sphinx partial-build ordering. This is why changes to source +ordering often affect the inserted header listings and `headers` output, but do not +affect the `api.db` baseline. + +The main practical use of `:seq:` is therefore for generated header listings and +`headers` output when a single header is assembled from multiple source files. In +specifications where each standard header has a single source location, `:seq:` is +generally not needed. In larger APIs such as the Crypto API, where many source files +contribute to one header, `:seq:` is important to keep the generated header order stable +and intentional. + +### Using callouts/admonitions + +Many of the docutils/Sphinx admonitions are used within the specifications. Here are the +typical uses: + +- `.. note::` - informative material for a developer that is related to the current + text, but is not necessary for normal use of it. +- `.. admonition:: Implementation note` - informative material for an implementer of the + specification, often providing options or recommendations. This is just a titled + generic admonition element. +- `.. warning::` - call out an issue that presents a risk for the implementer or user of + an API. Use sparingly. +- `.. todo::` - identify unfinished work in the specification. These should be resolved + prior to publishing a specification. By default, these are rendered when the document + has a non-zero draft revision, but can be enabled explicitly by adding `'todo'` to the + `doc_info['include_content']` attribute. +- `.. rationale::` - provide justification for a design decision. By default, these are + rendered when the document has a non-zero draft revision, but can be enabled + explicitly by adding `'rationale'` to the `doc_info['include_content']` attribute. +- `.. comment::` - provide commentary related to documentation content. Most useful for + including notes for reviewers of the rendered documents. By default, these are not + rendered in a build, but can be overridden by adding `'comment'` to the + `doc_info['include_content']` attribute. + +Admonitions can also be included in the bodies of API element directives, subsection +directives, or sub-element directives. Use them sparingly inside API entries so they +don't overwhelm the normative flow. + +### Formatting inline code/monospace text + +Quick rules-of-thumb: + +- Use ``text`` for plain literal monospace +- Use `text` for the default reference role +- Use the `:code:` role for code-like text that should hyperlink defined identifiers + when possible + +In more detail: + +- For plain literal monospace text or code fragments, use the standard reStructuredText + double backticks: ``monospace text``. +- For single API elements or manifest attributes that are defined anywhere in the Sphinx + document, use the default reference role: `MACRO` or `function()`. This renders as + monospace code, hyperlinked to the definition of the element/attribute. PSA + documentation convention includes the function parentheses, but these are optional. +- For code that should link to API elements or manifest identifiers when possible, use + the `:code:` role: :code:`foo(MACRO,1)` or :code:`<= MAX_VAL`. This renders as + monospace code, hyperlinking every identifier or attribute that is present in the + text. +- The `:code:` role is also useful for identifiers that are moving to or from another + specification, as these will render as links when possible, otherwise as plain code. + +## Directive Reference + +Documentation conventions for this reference: + +- document the practical use of the directive as seen in these spec sources, rather than + trying to reverse engineer every implementation detail +- prefer minimal examples copied or adapted from real specification usage +- group document assembly directives by authoring context, so editors can find the + directives relevant to the file they are editing + +Use one section per directive. Keep examples minimal. + +Recommended format: + +- Purpose +- Syntax +- Common options +- Placement rules +- Output/effect +- Example +- Gotchas + +### API documentation directives + +#### `.. header::` + +Purpose: + +Specify the header file to associate the following API elements with. + +Syntax: + +The header file name is the directive argument. The directive body (optional) provides +additional content (e.g. comments) to include after any copyright or license text. + +```rst +.. header:: psa/example + :copyright: Copyright notice + :license: Source license + :c++: + :guard: + :system-include: stddef.h stdint.h + :include: psa/error.h + + /* Optional comment block + */ +``` + +Common options: + +All options are optional. + +- `:copyright:` - a copyright notice, included in the `headers` format build. +- `:license:` - an SPDX license identifier, included in the `headers` format build. +- `:c++:` - add preprocessing directives to be able to include the header in a C++ + project. +- `:guard:` - add preprocessing directives to guard against double-inclusion of the + header file. +- `:system-include:` - list of system-include files to add to the header. +- `:include:` - list of project include files to add to the header. +- `:seq:` - a numerical sequence number for the set of API elements that follow. This + enables definitions across multiple sources to be output in a defined order. + +Apart from `:seq:`, these options can only be provided on a single instance of the `.. +header::` directive for each header file within the document sources. + +Placement rules: + +Anywhere in a .rst source, outside of an API element definition. + +Output/effect: + +Associates all following API definitions with the named header, unless they provide a +`:header:` override option. Defines the order in which API elements are included in a +header file. Specifies additional material to be used when generating the header file. + +Example: + +```rst +.. header:: psa/crypto + :seq: 10 + :copyright: Copyright 2026 Example Publisher + :license: Apache-2.0 + :c++: + :guard: + :system-include: stddef.h stdint.h + :include: psa/error.h + + /* This file is a reference template for implementation of the + * PSA Certified Crypto API v1.5 + */ +``` + +Gotchas: + +When using the copyright option, license option, or including a version in the comments; +these are not affected by changing the document configuration and must be edited to +match the document setup. + +#### `.. insert-header::` + +Purpose: + +Insert the named header into the document. + +Syntax: + +```rst +.. insert-header:: psa/example +``` + +Placement rules: + +None. Current practice is to use this in an appendix. + +This directive is processed after all document sources have been parsed - ensuring that +the inserted header is always consistent with the API definitions in the document. + +Output/effect: + +The named header is generated as a source listing, and automatically cross-references +all defined API elements. + +Example: + +```rst +.. insert-header:: psa/client +``` + +#### `.. macro::` + +Purpose: + +Define a macro API element. + +For function-like macros, include one or more parameter definitions, and an optional +return description as part of the directive body. + +Syntax: + +```rst +.. macro:: PSA_EXAMPLE + :definition: (0u) + + .. summary:: Summary text. + + .. param:: parameter_name + Parameter description. + .. return:: + Return value description. + + Macro description. +``` + +The `.. macro::` directive argument is the name of the macro. The optional macro +definition is provided in the `:definition:` option. For a function-like macro: + +- The parameter names are taken from `.. param::` directives in the body, in the order + in the .rst file. +- Optionally, `.. return::` and `.. retval::` directives can be used to describe the + expected output. + +Common options: + +- `:definition:` (optional) - provide the macro definition. +- `:api-version: ` (optional) - define the macro using the document version + (from conf.py). `type` is one of `major`, `minor`, or `hex` which results in the + macro definition being the major version, minor version, or a 16-bit + `(major << 8) | minor` value respectively. This option cannot be used at the same + time as `:definition:`. + +Also, see the standard API element options. + +Placement rules: + +Anywhere in a document section, but see the note about Rendering of C API element +definitions above. + +Output/effect: + +Creates a macro definition section in the document, and adds the macro to the generated +API declarations. + +If no definition is provided, the default comment definition of `/* ... */` is used. + +Example: + +```rst +.. macro:: PSA_VERSION_NONE + :definition: (0u) + + .. summary:: This is the return value from `psa_version()` if the requested RoT + Service is not present. +``` + +Gotchas: + +The tool still permits legacy use of having the macro definition as additional lines in +the directive argument. Current best practice is to have only the name in the directive +argument, and include any definition in the `:definition:` option. + +To force an empty definition for the macro, use a `:definition:` option with no content. + +#### `.. function::` + +Purpose: + +Define a function or function-pointer API element. + +Provide the parameter definitions, return type, and important return values as part of +the directive body. + +Syntax: + +```rst +.. function:: psa_example + + .. summary:: Summary text. + + .. param:: param_type param_name + Parameter description. + .. return:: return_type + Short description of returned value. + .. retval:: value or value description + Details on specific error code, or return values. + + Function description. +``` + +The `.. function::` directive argument is only the name of the function. The function +parameter types and names are taken from `.. param::` directives in the body, in the +order in the .rst file. The function return type is taken from the `.. return::` +directive in the body. + +Common options: + +- `:type:` - define a function pointer type. Use this for defining callback or + function-pointer types instead of ordinary functions. + +Also, see the standard API element options. + +Placement rules: + +Anywhere in a document section, but see the note about Rendering of C API element +definitions above. + +Output/effect: + +Creates a function or function-type definition section in the document, and adds the +function or function-type to the generated API declarations. + +Example: + +```rst +.. function:: psa_framework_version + + .. summary:: This function retrieves the version of the PSA Framework API that is implemented. + + .. return:: uint32_t +``` + +Gotchas: + +For a void-returning function, the `.. return:: void` directive must be included in the +body, to avoid a warning about a missing return type. + +#### `.. typedef::` + +Purpose: + +Define a type alias. + +This directive can also be used to define incomplete types and incomplete structure +types. + +Syntax: + +```rst +.. typedef:: uint32_t psa_example_t + + .. summary:: Summary text. +``` + +The directive argument is the type definition. It can optionally include the `typedef` +prefix and a trailing `;`. + +To define an incomplete type, which an implementation of the API must fully define, a +comment can be used in the definition. For example: + +```rst +.. typedef:: /* implementation-defined type */ psa_example_t + + .. summary:: Summary text. +``` + +Common options: + +See the standard API element options. + +Placement rules: + +Anywhere in a document section, but see the note about Rendering of C API element +definitions above. + +Output/effect: + +Creates a typedef definition section in the document, and adds the type definition to +the generated API declarations. + +Example: + +```rst +.. typedef:: int32_t psa_handle_t + + .. summary:: This type is used for handles. +``` + +Gotchas: + +Use a `.. function::` directive with the `:type:` option to define a pointer-to-function +type. + +#### `.. struct::` + +Purpose: + +Define a structure or structure type API element. + +Provide the member definitions as part of the directive body. + +Syntax: + +```rst +.. struct:: psa_example + :type: + + .. summary:: Summary text. + + .. field:: uint32_t member + Field description. +``` + +Structure members are declared using the `.. field::` directives in the body, and appear +in the order of definition in the source .rst file. + +Common options: + +- `:type:` - define a typedef for the structure identifier, not just the named `struct`. + +Also, see the standard API element options. + +Placement rules: + +Anywhere in a document section, but see the note about Rendering of C API element +definitions above. + +Output/effect: + +Creates a structure definition section in the document, and adds the definition to the +generated API declarations. + +Example: + +```rst +.. struct:: psa_msg_t + :type: +``` + +Gotchas: + +To define an incomplete structure type, use a `.. typedef::` directive with a commented +structure body. For example: + +```rst +.. typedef:: struct { /* implementation-defined */ } psa_example + + Type description. +``` + +#### `.. enum::` + +Purpose: + +Define an enumeration or enumeration type API element. + +Syntax: + +```rst +.. enum:: psa_example + :type: + + .. summary:: Summary text. + + .. value:: VALUE_1 = 42 + Enumeration value description. +``` + +Enumeration values are declared using the `.. value::` directives in the body, and +appear in the order of definition in the source .rst file. + +Common options: + +- `:type:` - define a typedef for the enumeration identifier, not just the named `enum`. + +Also, see the standard API element options. + +Placement rules: + +Anywhere in a document section, but see the note about Rendering of C API element +definitions above. + +Output/effect: + +Creates an enumeration definition section in the document, and adds the definition to +the generated API declarations. + +Example: + +```rst +.. enum:: psa_operation_mode_t + :type: + + .. value:: sync = 1 + .. value:: async = 2 +``` + +#### `.. attribute::` + +Purpose: + +Define a JSON schema element. + +Syntax: + +```rst +.. attribute:: example_attribute + + Properties: Required. + + Description text. +``` + +Placement rules: + +Anywhere in a document section, but see the note about Rendering of C API element +definitions above. + +Output/effect: + +Generate a JSON attribute definition section in the document. + +JSON attributes do not appear in any generated C header file. However, JSON attributes +can be cross-referenced, as for API identifiers, and will be automatically linked in +inline code and code blocks that specify the `xref` language. + +Example: + +```rst +.. attribute:: psa_framework_version + + Properties: Required. +``` + +Gotchas: + +Although JSON attributes do not share a programming namespace with C API identifiers, +the psa-api-tool currently does not separate these domains when resolving +cross-references. If a C identifier and a JSON attribute have the same name, this will +result in build warnings and can result in incorrect cross reference linkage. + +Shared PSA convention for nested JSON objects: + +When documenting nested manifest objects such as `service`, `irq`, or MMIO region +objects, do not use nested `.. attribute::` directives for the sub-attributes unless the +tooling has been extended to support scoped names. Today, repeated attribute names such +as `name` and `description` would collide. + +Instead: + +- keep the top-level JSON attribute, such as `services` or `irqs`, as the single `.. + attribute::` definition +- structure the body using `.. rubric::` titles for internal grouping +- use `.. container:: apisubitem` with definition-list style content to document nested + object attributes in a compact, API-like layout + +This is a source convention rather than a formal feature of the extension, but it is +stable in practice because `apisubitem` is the same styling hook used internally for API +sub-elements. + +#### `.. summary::` + +Purpose: + +Provide a brief summary of the API element. This is typically a single sentence. + +Syntax: + +```rst +.. summary:: Summary sentence. +``` + +Placement rules: + +Must be nested under an API element directive. + +Output/effect: + +This is the first text within an API element definition section, before the prototype of +the API element. + +The first sentence of the summary is used as the Doxygen `@brief` text in generated +header files, typically used by IDEs for code completion support. + +Example: + +```rst +.. macro:: PSA_FRAMEWORK_VERSION + + .. summary:: The version of the PSA Framework API provided by the included header file. +``` + +Gotchas: + +The preferred place to provide `.. versionadded::` directives is as the last part of the +body of the API element's `.. summary::` directive. + +#### `.. param::` + +Purpose: + +Document a parameter to a function or macro. + +Syntax: + +```rst +.. param:: + Parameter description. +``` + +The type and name should be valid C syntax for a parameter declaration. + +Additional paragraphs and lists can be used in the description to describe more complex +requirements when required for the parameter. + +Placement rules: + +Must be nested within a `.. function::` or `.. macro::` directive. + +Output/effect: + +The parameters to the function or macro are rendered in a list immediately after the API +element signature, in the order declared in the .rst file. + +The first sentence of the description is used as the Doxygen `@param` text in generated +header files, typically used by IDEs for code completion support. + +Example: + +```rst +.. param:: size_t buf_len + Length of buffer ``buf``. +``` + +#### `.. output::` + +Purpose: + +Document an additional output from a function. + +Additional outputs are those written into objects or buffers that are passed by +non-const pointer to the function. + +Syntax: + +```rst +.. output:: + Output description. +``` + +The output-location is typically written as `*p`, where `p` is a buffer or output +parameter that is passed by pointer. + +Placement rules: + +Must be nested within a `.. function::` or `.. macro::` directive. In the source file, +these can be placed alongside the related `.. param::` directive, or grouped elsewhere +in the API element directive. + +Output/effect: + +The output descriptions are rendered in a list following the parameter definitions, in +the order declared in the .rst file. + +Example: + +```rst +.. output:: *buf + On success, the data is written to the buffer pointed to by `buf`. +``` + +Gotchas: + +These are used rarely in the PSA API specifications. At present, they currently only +appear in the Attestation API. + +Most of the API specifications describe the values output in such parameters as part of +the parameter description, instead of using both a `.. param::` and `.. output::` +directive. For example: + +```rst +.. param:: uint8_t * nonce + Buffer where the generated nonce is to be written. +.. param:: size_t * nonce_length + On success, the number of bytes of the generated nonce. +``` + +#### `.. return::` + +Purpose: + +Document the return from a function, or function-like macro. + +Syntax: + +```rst +.. return:: [] + Return description. +``` + +For function-like macros, the type should be omitted. + +For functions that return a status code, such as `psa_status_t`, the description is +usually omitted if the return values are described using `.. retval::` directives. + +For simple functions or macros, the computation or evaluation that is performed can be +documented here. + +Placement rules: + +Must be nested within a `.. function::` or `.. macro::` directive. + +Output/effect: + +The type forms part of the function signature. + +The return value is described after the parameters and outputs. + +The first sentence of the description is used as the Doxygen `@returns` text in +generated header files, typically used by IDEs for code completion support. + +Examples: + +```rst +.. return:: size_t + The number of bytes read from the message parameter. +``` + +Gotchas: + +Technically, this is required for a function that has a `void` return. If none is +provided, a warning is used during the build, and `void` is assumed. + +This directive is not required for `.. macro::` API elements. + +#### `.. retval::` + +Purpose: + +Document a specific return value, or range of return values from a function. + +Syntax: + +```rst +.. retval:: + Return value description. +``` + +The directive argument can be a status code, such as `PSA_ERROR_INVALID_ARGUMENT`, or +can be an expression, such as `> 0`. When rendered, any API elements are hyperlinked to +their definition. + +Placement rules: + +Must be nested within a `.. function::` or `.. macro::` directive. + +Output/effect: + +The return values are described after the return type. Return values are listed in the +same order as the source, except when the `'error_order'` attribute is set in `doc_info` +in `conf.py`. + +Examples: + +```rst +.. retval:: PSA_SUCCESS + The operation completed successfully. +``` + +#### `.. field::` + +Purpose: + +Document a member field in a structure. + +Syntax: + +```rst +.. field:: + Field member description. +``` + +The type and name should be valid C syntax for a structure member declaration. + +Placement rules: + +Must be nested within a `.. struct::` directive. + +Output/effect: + +The members of a structure are rendered in a list immediately after the API structure +signature, in the order declared in the .rst file. + +The first sentence of the description is used as the Doxygen `@brief` text in generated +header files, typically used by IDEs for code completion support. + +Example: + +```rst +.. field:: uint8_t major + Major version number. +``` + +#### `.. value::` + +Purpose: + +Document an enumeration member value. + +Syntax: + +```rst +.. value:: {|} + Value member description. +``` + +The directive argument must be a valid C declaration of an enumeration member. + +Placement rules: + +Must be nested within a `.. enum::` directive. + +Output/effect: + +The members of an enumeration are rendered in a list immediately after the API +enumeration signature, in the order declared in the .rst file. + +The first sentence of the description is used as the Doxygen `@brief` text in generated +header files, typically used by IDEs for code completion support. + +Example: + +```rst +.. value:: block = 0 + Wait for a result. +.. value:: no_block = 1 + Return current status immediately. +``` + +#### `.. subsection::` + +Purpose: + +Provide an additional sub-titled section of the API element documentation. + +This is useful for providing additional information about API elements in a consistent +manner throughout a specification. For example, the 'Key format' sections in the Crypto +API key type definitions and the 'Compatible key types' sections in the Crypto API +algorithm definitions. + +Syntax: + +```rst +.. subsection:: + + Subsection content. +``` + +Common options: + +- `:top:` (optional) - a flag to indicate that this subsection should precede the API + element Description. If not provided, the default placement is after the API element + Description. + +Placement rules: + +Must be nested within an API element directive. + +Output/effect: + +A new subsection of the API definition is output, either immediately before or +immediately after, the Description for the API element. The placement depends on the use +of the `:top:` option, but is otherwise in the same order as in the .rst source. +Subsection headings are similar to the subheadings for Parameters, Outputs, Returns, and +Description. + +Example: + +```rst +.. subsection:: Availability + + This API is optional. Use `PSA_FRAMEWORK_HAS_MM_IOVEC` to determine availability of this function. +``` + +Repository convention: + +- Prefer `Availability` for describing which framework features, service models, or + execution models an API can be used with. +- Keep the `.. summary::` focused on what the API is or does. +- Do not overload version-markup bodies with availability rules or feature + applicability. +- The use of `Availability` is established, but its canonical placement within an API + entry is still deferred. Keep placement consistent within a local edit pass, and + normalize it later once the integrated API text is more complete. + +### Document assembly directives + +The directives in this section are grouped by where editors normally use them. This +means that related producer/consumer directives are not always adjacent. For example, +`.. reference::` entries are normally authored in front-matter inputs, while `.. +reference-table::` is normally used by a template or by an appendix that chooses to +render the collected references elsewhere. + +A document can override or ignore the configured document template source. For example, +a document can avoid `.. title::` and write its title page directly, or avoid `.. +about::` and write the front matter directly. More commonly, a document uses +front-matter section directives such as `.. references::` with `:hide:`, `:replace:`, or +`:extend:` to adjust selected template sections. + +The current `psa-api-*` templates use an unnumbered front-matter chapter called "About +this document", normally listed under `.. front-matter::`, followed by a numbered +chapter 1 introduction in the main document. + +One non-standard arrangement is a bibliography appendix. In that pattern, the document +writes the front matter directly using template construction directives instead of using +`.. about::`, omits the front-matter References section, and renders the references from +an appendix source that contains the `.. reference::` entries followed by `.. +reference-table::`. + +### Directives Used in `index.rst` + +These directives are normally used in the top-level `index.rst` source. + +The `.. front-matter::`, `.. maintoc::`, and `.. appendix::` directives are based on +Sphinx `.. toctree::`, with additional output controls for the document section. They +support these common options: + +- `:numbered:` - override the section numbering depth. +- `:maxdepth:` - override the table-of-contents depth. + +For HTML output, `:numbered:` is only applied by `.. maintoc::` and `.. appendix::`; +front-matter headings are not numbered in HTML. For LaTeX/PDF output, the +table-of-contents depth is controlled by the configured document template, not per +document section. + +PDF page numbering style is also controlled by the configured document template. The +current `psa-api-*` templates use roman page numbers for front matter and Arabic page +numbers for main content and appendices. + +#### `.. title::` + +Purpose: + +Insert the template title page for the document. + +Syntax: + +```rst +.. title:: + + .. abstract:: + + This document defines ... +``` + +Placement rules: + +This should be the first directive in the top-level `index.rst` file. Any content in the +directive body is parsed before the template title page is included, so it can provide +`.. abstract::` and `.. banner::` inputs. For compatibility with older sources, ordinary +parsed content that remains after processing collector directives is treated as the +`abstract` front-matter section when the selected template defines one. + +Output/effect: + +The directive includes `title-page.rst` from the selected template directory. Templates +that use an `abstract` front-section insert the collected abstract at the +template-defined title page location. + +Gotchas: + +The directive itself has no argument. The document title and metadata come from +`doc_info` in `conf.py` and from the selected template. + +#### `.. abstract::` + +Purpose: + +Provide the content for the title page document abstract. + +Syntax: + +```rst +.. title:: + + .. abstract:: + + This document is ... +``` + +Placement rules: + +If used, this must appear in the body of the `.. title::` directive in the document +index.rst file. + +Output/effect: + +Places an abstract on the title page of the document. + +#### `.. banner::` + +Purpose: + +Provide the content for a highlighted box that is placed on the front page of a +document. + +Syntax: + +```rst +.. title:: + + .. banner:: BETA RELEASE + + Status description. +``` + +Placement rules: + +If used, this must appear in the body of the `.. title::` directive in the document +index.rst file. + +Output/effect: + +By default, the banner is only output when the document has a non-zero draft revision, +but this can be overridden by adding `'banner'` to the `doc_info['include_content']` +configuration attribute. + +#### `.. front-matter::` + +Purpose: + +A toc-like directive used in the top-level `index.rst` file to define the front-matter +content of the document. + +Syntax: + +```rst +.. front-matter:: + :maxdepth: 2 + + about/about +``` + +Common options: + +- `:numbered:` - accepted, but ignored for HTML output. The default value is `0`, which + gives unnumbered front-matter headings in PDF output. +- `:maxdepth:` - defaults to `2`. + +Gotchas: + +- Like the `.. toctree::` directive, the contents are treated as a list of content + files, and must not be reflowed as a paragraph if modifying indentation and source + layout. +- This is normally used by Arm-style `psa-api-*` templates for the unnumbered "About + this document" chapter. GP-style documents normally put `about/about` at the start of + `.. maintoc::` instead. + +#### `.. maintoc::` + +Purpose: + +A toc-like directive used in the top-level `index.rst` file to define the main body +content of the document. + +Syntax: + +```rst +.. maintoc:: + :numbered: 3 + :maxdepth: 3 + + intro + architecture +``` + +Common options: + +- `:numbered:` - defaults to `3`. +- `:maxdepth:` - defaults to `3`. + +Gotchas: + +- Like the `.. toctree::` directive, the contents are treated as a list of content + files, and must not be reflowed as a paragraph if modifying indentation and source + layout. +- In GP-style documents, include `about/about` as the first entry so the + template-provided Introduction chapter is numbered as chapter 1. + +#### `.. appendix::` + +Purpose: + +A toc-like directive used in the top-level `index.rst` file to define the appendix +content of the document. + +Syntax: + +```rst +.. appendix:: + :numbered: 3 + :maxdepth: 3 + + appendix/reference-headers +``` + +Common options: + +- `:numbered:` - defaults to `3`. +- `:maxdepth:` - defaults to `3`. + +Gotchas: + +- Like the `.. toctree::` directive, the contents are treated as a list of content + files, and must not be reflowed as a paragraph if modifying indentation and source + layout. +- Appendix chapters use alphabetic numbering in HTML and PDF output. +- The current templates use Arabic appendix page numbers in PDF output, but this is a + template convention rather than a directive behavior. + +### Directives Used in the Front-Matter Chapter + +These directives are normally used in the source that provides template front-section +content, typically `about.rst`, or in files included by that source such as `releases`, +`references`, and `terms`. In Arm-style documents, that source is normally listed by `.. +front-matter::`. In GP-style documents, that source is normally the first entry under +`.. maintoc::`. + +The common pattern is: + +1. Include or define releases, references, and terms. +2. Add front-matter section directives to hide, replace, or extend template sections. +3. End the source with `.. about::`, which includes the configured document template's + `about-chapter.rst` and consumes the previously collected content. + +Placement rule for collected front-matter content: + +The content-providing directives must appear before the directive that renders that +content, in the same source document after includes have been expanded. + +For example: + +- Include or define `.. release::` entries before `.. release-table::`. +- Include or define `.. reference::` entries before `.. reference-table::`. +- Include or define `.. term::`, `.. scterm::`, and `.. abbr::` entries before `.. + term-table::`. +- Place front-matter section directives before `.. about::` or `.. insert-section::`. +- Place `.. banner::` before `.. insert-banner::`. + +The usual pattern is to define `releases`, `references`, and `terms` in extensionless +include files, include those files from `about.rst`, apply any front-matter section +controls such as `.. introduction::`, `.. audience::`, or `.. api-status::`, and then +end `about.rst` with `.. about::`. + +#### `.. release::` + +Purpose: + +Define a release entry for the document releases table. + +Syntax: + +```rst +.. release:: + :date: + + Release summary +``` + +Placement rules: + +See the placement rule for collected front-matter content above. + +The most common pattern is to have all releases defined in a `releases` source file that +is included in the `about.rst` source. The definitions can also just be inline within +the `about.rst` source file. + +Gotchas: + +- Releases in the table appear in the same order as the `.. release::` directives in + the source .rst file. +- Keep the release text summary concise, as this is part of a table of releases. + Detailed change information is better to maintain in a document appendix, with a + reference to the appendix from the `.. release-info::` section. + + To add a reference to a change-history appendix, the following text can be added to `about.rst`: + + ```rst + .. release-info:: + :extend: + + For a detailed list of changes in each document version, see :secref:`change-history`. + ``` + + Provide a named anchor for the section reference in the appendix source immediately before the heading: + + ```rst + .. _change-history: + + Document changes + ================ + ``` + +#### `.. term::`, `.. scterm::`, and `.. abbr::` + +Purpose: + +Define a glossary entry for the document's Terms and abbreviations table. + +The `.. scterm::` directive defines a small-caps-styled term, used for terminology in +the document that has very specific meaning. The `.. abbr::` directive defines an +abbreviation-only entry. + +Syntax: + +```rst +.. term:: + :abbr: + + Definition + +.. abbr:: + + Meaning +``` + +Common options: + +- `:abbr:` (optional) - an abbreviation of the term. This will automatically include an + additional glossary entry for the abbreviation, referring to the full term definition. + +Placement rules: + +See the placement rule for collected front-matter content above. + +The most common pattern is to have all terms defined in a `terms` source file that is +included in the `about.rst` source. The definitions can also just be inline within the +`about.rst` source file. + +Output/effect: + +- Creates one or two definitions in the terms and abbreviations data. +- Creates a link target for the term, and abbreviation if provided, that can be + referenced with the `:term:` and `:scterm:` roles. + +Gotchas: + +The current PSA API templates usually render a single combined terms and abbreviations +table. + +#### `.. reference::` + +Purpose: + +Define a citation reference entry for the document's References table. + +Syntax: + +```rst +.. reference:: + :title: + :author: <author> + :doc_id: <pub_id> + :kind: normative + :publication: <date|location> + :url: <url> +``` + +The directive argument is the citation identifier used within the document text to refer +to this particular external document or website. + +For an RFC document, `RFC nnnn` is the canonical rendered form. Compact source forms +such as `RFCnnnn` are normalized so the `:rfc:` and `:rfc-title:` roles can correctly +link to the citation. + +Common options: + +- `:title:` (required) - The document title or website name. +- `:author:` (optional) - The person or organization that produced the document. +- `:doc_id:` (optional) - The publisher's own identifier number or label. +- `:kind:` (optional) - The reference classification, either `normative` or + `informative`. The default is `normative`. +- `:publication:` (optional) - The date or location of the published document. +- `:url:` (optional) - A URL to the document or the publishing organization. If the URL + text does not start with 'https://' - this is added to the anchor link automatically, + but not rendered in the anchor text. + +Placement rules: + +See the placement rule for collected front-matter content above. + +The most common pattern is to have all references defined in a `references` source file +that is included in the `about.rst` source. The definitions can also just be inline +within the `about.rst` source file. + +Output/effect: + +- Creates a citation reference in the References table. +- Creates a link target for the citation that can be referenced with the `:cite:`, + `:cite-title:`, `:rfc:`, and `:rfc-title:` roles. + +Gotchas: + +References do not have to be rendered in the front matter. To move references to an +appendix, hide or replace the front-matter `.. references::` section, then place the `.. +reference::` directives and `.. reference-table::` in an appendix source. + +The current PSA API templates render a single combined references table. + +#### Front-matter section directives + +Purpose: + +Customize front-matter sections provided by the configured document template. + +Syntax: + +```rst +.. release-info:: + :extend: + + For a detailed list of changes, see :secref:`change-history`. +``` + +Common options: + +- `:replace:` - replace the template's default content for this section. +- `:extend:` - append this content to the template's default content. +- `:hide:` - suppress this section. + +At most one of these options can be provided in a directive. + +- If no option is provided and the directive has content, the content replaces the + default section content. Prefer using the `:replace:` option explicitly. +- If no option and no content are provided, the section is treated as hidden. This is + deprecated usage, it is recommended to explicitly specify `:hide:`. + +Supported section directives are defined by the selected template. The GP template +currently provides: + +- `.. introduction::` +- `.. api-status::` +- `.. feedback::` +- `.. audience::` +- `.. license::` +- `.. references::` +- `.. terms::` +- `.. abbreviations::` +- `.. release-info::` +- `.. todos::` + +The PSA API 2022 and 2025 templates provide the older Arm-style section set: + +- `.. abstract::` +- `.. release-info::` +- `.. todos::` +- `.. license::` in the 2025 template +- `.. references::` +- `.. terms::` +- `.. potential-for-change::` +- `.. conventions::` +- `.. pseudocode::` +- `.. assembler::` +- `.. current-status::` +- `.. feedback::` +- `.. inclusive-language::` + +Placement rules: + +These directives normally appear in a consuming document's `about.rst` before the final +`.. about::` directive. See the placement rule for collected front-matter content above. + +#### `.. about::` + +Purpose: + +Include the about-chapter source from the configured document template. + +Syntax: + +```rst +.. about:: +``` + +Placement rules: + +This is normally used at the end of the consuming document's `about.rst` source after +any releases, references, terms, and front-matter section overrides have been defined. +See the placement rule for collected front-matter content above. + +Output/effect: + +The directive includes `about-chapter.rst` from the configured document template +directory. The template's `about-chapter.rst` provides the default content and structure +for front matter sections, which is modified according to the preceding directives in +the `about.rst` source file. + +### Directives Used to Construct Template Title Pages and Front Matter + +These directives are primarily used in template sources, such as `title-page.rst` and +`about-chapter.rst`. Specification sources can use them directly when they intentionally +bypass part of the configured document template, but that should be a deliberate +document-structure decision. + +The rendering directives in this section must appear after the corresponding +content-providing directives have been defined or included in the same source document. +See the placement rule for collected front-matter content above. + +#### `.. template-image::` + +Purpose: + +Insert an image from the selected template directory. + +Syntax: + +```rst +.. template-image:: logo.svg + :alt: Logo +``` + +Placement rules: + +This directive is primarily for template sources, such as title pages. It accepts the +same image options as the standard reStructuredText `.. image::` directive. + +Output/effect: + +The image path is resolved relative to the selected template directory instead of +relative to the document source file. + +#### `.. insert-banner::` + +Purpose: + +Insert the title-page banner collected from a `.. banner::` directive. + +Syntax: + +```rst +.. insert-banner:: +``` + +Placement rules: + +This is primarily a title-page template directive. Use this after the corresponding `.. +banner::` directive has been defined or included in the same source document. + +Output/effect: + +The collected banner content is inserted only when banner output is enabled. + +#### `.. insert-section::` + +Purpose: + +Insert a front-matter section. The directive defines the section title and the default +content when used in a template. + +When rendered, the content is modified, extended, or removed as directed by a +corresponding front-matter directive in the specification's `about.rst` source. + +Syntax: + +```rst +.. insert-section:: Release information + :section: release-info + :break-after: +``` + +Common options: + +- `:section:` - required; the front-matter section key to insert. +- `:break-after:` - insert a page break after the section in PDF output. +- `:class:` - wrap the section content in the named class or environment. +- `:not-in-toc:` - render a styled title without creating a section in the table of + contents. +- `:keep-if-empty:` - keep the section even if no content is available. + +Placement rules: + +This is primarily a template directive. Specification sources normally use the +front-matter section directives instead to control or modify the template content. Use +this after the corresponding front-matter section directive has been defined or included +in the same source document. + +#### `.. release-table::` + +Purpose: + +Render the release entries collected from `.. release::` directives. + +Syntax: + +```rst +.. release-table:: +``` + +Placement rules: + +Use this after the corresponding `.. release::` entries have been defined or included in +the same source document. + +Output/effect: + +Creates a table with Date, Version, and Change columns. + +#### `.. reference-table::` + +Purpose: + +Render the references collected from `.. reference::` directives. + +Syntax: + +```rst +.. reference-table:: Documents referenced by this document + :sorted: + :kind: all + :layout: by-ref +``` + +Common options: + +- `:sorted:` - sort the references alphabetically by their reference identifier. +- `:kind:` - one of `normative`, `informative`, or `all`. The default is `all`. +- `:layout:` - one of `by-ref` or `by-id`. The default is `by-ref`. +- `:filter:` - one of `with-id`, `without-id`, or `none`. The historical names `arm` and + `non-arm` are accepted as aliases for `with-id` and `without-id`, respectively, so + older specification sources continue to build. +- `:class:` - apply a table class. +- `:name:` - set an explicit target name for the table. + +Placement rules: + +Use this after the corresponding `.. reference::` entries have been defined or included +in the same source document. It can be used in another document source, such as an +appendix, if the document chooses to render references outside the front matter. + +Output/effect: + +Creates a References table. When `:filter: without-id` is used, the document-number +column is omitted in the default `by-ref` layout, preserving the table format for +references that do not have publisher document identifiers. + +The `by-ref` layout produces Ref, Document Number, and Title columns. The `by-id` layout +produces Standard/specification, Description, and Reference columns. In the `by-id` +layout, the Standard/specification column uses `:doc_id:` when it is available and falls +back to the citation identifier when a reference has no publisher document identifier. +The Reference column contains the citation form used in the document text, such as +`[PSA-SM]`. + +#### `.. term-table::` + +Purpose: + +Render the terms and abbreviations collected from `.. term::`, `.. scterm::`, and `.. +abbr::` directives. + +Syntax: + +```rst +.. term-table:: + :sorted: + :kind: all +``` + +Common options: + +- `:sorted:` - sort the terms alphabetically by their normalized identifier. +- `:kind:` - one of `terms`, `abbreviations`, or `all`. The default is `all`. + +Placement rules: + +Use this after the corresponding term or abbreviation entries have been defined or +included in the same source document. + +Output/effect: + +Creates a table from the collected terminology data. With `:kind: terms`, the table has +Term and Definition columns. With `:kind: abbreviations`, the table has Abbreviation and +Meaning columns. Each entry also becomes the target for `:term:` and `:scterm:` +references. + +If no matching entries have been collected, no table is rendered. + +#### `.. include-license::` + +Purpose: + +Include the license text selected by the document configuration. + +Syntax: + +```rst +.. include-license:: +``` + +Placement rules: + +This is primarily a front-matter template directive. It specifies where the configured +license should be included in the document. + +Output/effect: + +The directive first looks for a license source matching the configured license value +relative to the document. If none is found, it looks in the `tools/license/` directory, +using a lower-case filename with hyphens converted to underscores and an `.rst` suffix. +If no matching license exists, the built-in missing-license text is included and a build +error is reported. + +This allows a template to support multiple licenses without template changes. Selecting +a different license for a document issue is a configuration change, while the license +wording remains centralized in the license source file. + +### Directives Used in License Source Files + +#### `.. license::` + +Purpose: + +Mark explicitly provided license text as the document's license section. + +Syntax: + +```rst +.. license:: + + License text. +``` + +Placement rules: + +This directive is intended for license source files, such as the standardized license +files in `tools/license/`. A document template normally uses `.. include-license::` to +include the configured license file at the correct place in the front matter. + +A document or template can use `.. license::` directly to embed license text in its own +sources, but this is not recommended. Keeping license text in a separate file reduces +normal editing churn and helps avoid accidental deviation from the approved license +wording. + +Output/effect: + +This behaves like a section insertion helper. The PSA API templates use a dedicated +style class when inserting this section in the document and forces a page break after +the section. + +### Modified Standard Directives + +#### `.. rationale::` + +Purpose: + +Include rationale text that is useful while drafting or reviewing a specification, but +is not part of the normal published flow. + +Syntax: + +```rst +.. rationale:: Optional title + + Rationale text. +``` + +Output/effect: + +The content is rendered as an admonition only when rationale output is enabled. +Rationale output is enabled when the document has a non-zero draft revision and can also +be enabled by adding `'rationale'` to `doc_info['include_content']`. + +#### `.. comment::` + +Purpose: + +Include review commentary in the source. + +Syntax: + +```rst +.. comment:: + + Reviewer-facing comment. +``` + +Output/effect: + +The content is rendered as a Commentary admonition only when comment output is enabled +by adding `'comment'` to `doc_info['include_content']`. + +Gotchas: + +Use comments sparingly. They are intended for review builds, not as a substitute for +source comments or issue tracking. + +#### `.. code-block::` + +This directive supports the additional language `xref`. + +When `xref` is used as the argument to this directive, the code block is not rendered +using the normal highlighting engine, but instead has every API element from the current +specification hyperlinked in the output. + +The `xref` language option supports the standard `:linenos:` and `:lineno-start:` +options. + +#### `.. literalinclude::` + +This directive supports the additional language `xref`. When `xref` is used as the +language option, the code block is not rendered using the normal highlighting engine, +but instead has every API element from the current specification hyperlinked in the +output. + +The `xref` language option supports the standard `:linenos:` and `:lineno-start:` +options. + +## Role Reference + +Use the same lightweight format for roles: + +- Purpose +- Syntax +- Example +- Gotchas + +### The Default Reference Role + +Sphinx has a default reference role that is used for any text within single-backticks +without a preceding role specifier. + +The `psa-api-tool` Sphinx extension extends the capabilities of the default reference +role as follows: + +* If the reference text is of the form `[ref_id]`, this is resolved as a `:cite:` role. +* If the reference text is any of `SG.id`, `DM.id`, `AM.id`, `T.id`, or `M.id`, it is + resolved using the associated Threat model element role. +* If the reference is a single API element, it is resolved as a `:code:` role. + +### `:sc:` + +Purpose: + +Render the role text in small caps. + +This role is configured by `psa-api-conf.py` as a common reStructuredText role, rather +than registered by `psa-api-tool.py` as a domain-specific role. + +Syntax/example: + +```rst +The result is :sc:`implementation defined`. +``` + +### `:issue:` + +Purpose: + +Apply the `issue` CSS/LaTeX role class to inline text, normally for visible open-issue +placeholders. + +Syntax/example: + +```rst +:issue:`<<Document ID>>` +``` + +Output/effect: + +The current templates render the role text inline, in red. + +Gotchas: + +This role is configured by `psa-api-conf.py` as a common reStructuredText role. It does +not create or link to an external issue tracker entry. + +### `:code:` + +Purpose: + +Format text as inline code, hyperlinking any API element identifiers that are defined in +this specification. + +Syntax/example: + +```rst +Use a call to :code:`psa_wait(PSA_WAIT_ANY, PSA_BLOCK)` to wait for the next partition event. +``` + +Output/effect: + +The rendered output is formatted as code, hyperlinking every API element to its +definition. + +Gotchas: + +- Use the `:code:` role instead of the default reference role for linking to API + elements if the API definition might be in another specification. This results in code + formatting without a hyperlink if no matching definition is available. +- Use the `:code:` role instead of reStructuredText double backticks for monospace text + if the code contains API elements. + +### `:secref:` + +Purpose: + +Include a formatted, inline cross-reference to a titled section or object anywhere in +the specification. + +This role is similar to the standard `:ref:` role: the target's title is used as the +link text. It is commonly used for section references, but it can also reference titled +listings, tables, and figures. See also `:numref:`. + +Example: + +```rst +See :secref:`programming-api` for details. +``` + +For a reference to a document section, this requires that `programming-api` is defined +as a link target associated with the section heading by defining an explicit anchor in +the source immediately before the heading itself, for example: + +```rst +.. _programming-api: + +Programming API +--------------- +``` + +Output/effect: + +The target's title is rendered as a hyperlink in the output. In HTML output, the link +text uses the template's title-reference styling. In LaTeX/PDF output, the link is +followed by an additional `on page nnn` link when the target is on a different page. + +Gotchas: + +- Make sure all anchor targets are unique in a document. They do not have to exactly + match the text in the section heading. +- To reference a figure, listing, or table, these have to have a title, and have the + anchor name set using the `:name:` option. +- The former `:title:` alias has been removed. Use `:secref:` for title-text cross + references. + +### `:numref:` + +Purpose: + +Include a formatted, inline cross-reference to a numbered object in the document, such +as a listing, table, figure, or section heading. See also `:secref:`. + +Syntax/example: + +```rst +:numref:`table-error-codes` summarizes the errors produced by this API. See also :numref:`error-codes`. +``` + +For a reference to a document section, this requires that `error-codes` is defined as a +link target associated with the section heading by defining an explicit anchor in the +source immediately before the heading itself. + +Output/effect: + +The target's number is rendered as a hyperlink in the output. In PDF output, the +hyperlink text includes the target page number. + +Gotchas: + +- Make sure all anchor targets are unique in a document. They do not have to exactly + match the text in the section heading. +- To reference a figure, listing, or table, these have to have an anchor name set using + the `:name:` option. + +### `:term:` + +Purpose: + +Include a formatted cross-reference to a term or abbreviation that is defined in the +document glossary. + +Syntax/example: + +```rst +The RoT Service runs within a :term:`Secure Partition`. +``` + +The capitalization of the term in the role text does not have to match the +capitalization in the glossary. + +Output/effect: + +The rendered output is a formatted hyperlink, the capitalization of the text is taken +from the role text. + +Gotchas: + +- The term or abbreviation must be defined in a `.. term::` directive. +- To pluralize a term, an escaped-space can be used in the .rst source. For example, + `The :term:``RoT Service``\ s` will render as "The RoT Services", with "RoT Service" + hyperlinked to the glossary. + +### `:scterm:` + +Purpose: + +Include a formatted cross-reference to a smallcaps term or abbreviation that is defined +in the document glossary. + +Syntax/example: + +```rst +Providing a zero-length name is a :scterm:`Programmer error`. +``` + +The capitalization of the term in the role text does not have to match the +capitalization in the glossary. + +Output/effect: + +The rendered output is a smallcaps-formatted hyperlink. + +Gotchas: + +- The term or abbreviation must be defined in an `.. scterm::` directive. + +### `:cite-title:` + +Purpose: + +Include a formatted reference to the title of a cited work. + +Syntax/example: + +```rst +The security model is defined in :cite-title:`PSA_SM`. +``` + +The role text is the citation `ref_id`. + +Output/effect: + +The rendered output is a hyperlink containing the title of the cited work followed by +'[ref_id]'. + +Gotchas: + +- The citation must be defined in a `.. reference:: ref_id` directive. + +### `:cite:` + +Purpose: + +Include an untitled citation reference. + +Syntax/example: + +```rst +:cite:`PSA_SM` also defines the security goals. +``` + +The role text is the citation `ref_id`. + +Output/effect: + +The rendered output is a hyperlink containing '[ref_id]'. + +Gotchas: + +- The citation must be defined in a `.. reference:: ref_id` directive. + +### `:rfc-title:` + +Purpose: + +Include a formatted reference to the title of a published RFC. + +Syntax/example: + +```rst +The algorithm is defined in :rfc-title:`9910`. +``` + +The role text is the RFC number, optionally followed by a '#' and section number. + +Output/effect: + +The rendered output is a hyperlink containing the title of the cited RFC followed by +`[RFC nnnn]`. + +If a section number or appendix letter is included in the role text, a second hyperlink +follows which contains the formatted section number and links directly to that section +of the RFC document. + +Gotchas: + +- The citation must be defined in a `.. reference:: RFC nnnn` directive, or a compact + `RFCnnnn` source form that normalizes to the same citation. + +### `:rfc:` + +Purpose: + +Include an untitled RFC reference. + +Syntax/example: + +```rst +:rfc:`9910#B` discusses the security analysis of the algorithm. +``` + +The role text is the RFC number, optionally followed by a '#' and section number. + +Output/effect: + +The rendered output is a hyperlink containing `[RFC nnnn]`. + +If a section number or appendix letter is included in the role text, a second hyperlink +follows which contains the formatted section number and links directly to that section +of the RFC document. + +Gotchas: + +- The citation must be defined in a `.. reference:: RFC nnnn` directive, or a compact + `RFCnnnn` source form that normalizes to the same citation. + +### `:url:` + +Purpose: + +Render an external URL as a hyperlink. + +Syntax/example: + +```rst +See :url:`example.com/specification`. +``` + +Output/effect: + +If the role text does not include `//`, the link target is prefixed with `https://`. The +visible text is the role text as written. + +## Security Risk Assessment Directives and Roles + +The Sphinx extension includes directives and roles that support structured security risk +assessment content. + +### `.. threat::` + +Purpose: + +Define a structured threat entry. + +Syntax: + +```rst +.. threat:: Threat title + :id: T.example + :deployment-models: DM.PROTECTED, DM.EXPOSED + + .. description:: + + Threat description. + + .. adversarial-model:: + + Relevant adversarial model. + + .. security-goal:: + + Security goal affected by this threat. + + .. unmitigated:: DM.PROTECTED + :impact: H + :likelihood: M + + .. unmitigated:: DM.EXPOSED + :impact: VH + :likelihood: M + + .. mitigations:: + + Mitigations. + + .. residual:: DM.PROTECTED + :impact: L + :likelihood: L + + .. residual:: DM.EXPOSED + :impact: M + :likelihood: L +``` + +Common options: + +- `:id:` - explicit threat identifier. +- `:deployment-models:` - optional comma-separated list of deployment models for which + risk values can be provided. This also defines the order in which deployment-model + columns are rendered. + +Placement rules: + +The sub-directives are parsed inside the `.. threat::` directive body. + +Output/effect: + +The directive renders a section for the threat. The section title contains the threat +identifier, when provided, followed by the threat title. + +If no deployment-model distinction is used, the risk values are rendered as a single +evaluation for the threat. If named deployment models are used, the deployment model row +is rendered before the unmitigated risk rows, and the unmitigated and residual risk rows +use a consistent column layout so each evaluation appears under the relevant +deployment-model title. + +### Threat sub-directives + +The following sub-directives collect prose fields for a threat: + +- `.. description::` +- `.. adversarial-model::` +- `.. security-goal::` +- `.. mitigations::` + +The following sub-directives collect risk values: + +- `.. unmitigated::` +- `.. residual::` + +Risk directives support: + +- An optional directive argument - the deployment-model scope for these risk values. +- `:impact:` - required impact value. +- `:likelihood:` - required likelihood value. +- `:risk:` - optional explicit risk value. If omitted, the tool derives the risk from + the impact and likelihood matrix. + +Accepted abbreviated risk values are `VL`, `L`, `M`, `H`, and `VH`. + +If the optional argument is omitted, the risk values apply to the threat as a whole. If +a deployment-model argument is provided, the risk values apply only to that deployment +model. + +When a threat uses multiple deployment models, use `:deployment-models:` on the `.. +threat::` directive to list the expected deployment models and their presentation order, +and provide matching `.. unmitigated:: <DM>` and `.. residual:: <DM>` entries for each +deployment model. + +Example: + +```rst +.. threat:: Eavesdropping + :deployment-models: DM.PROTECTED, DM.EXPOSED + + .. unmitigated:: DM.PROTECTED + :impact: M + :likelihood: L + + .. residual:: DM.PROTECTED + :impact: M + :likelihood: VL + + .. unmitigated:: DM.EXPOSED + :impact: H + :likelihood: M + + .. residual:: DM.EXPOSED + :impact: M + :likelihood: L +``` + +### SRA definition and reference roles + +Definition roles create a definition target and render a canonical identifier: + +- `:deployment-model:` +- `:adversarial-model:` +- `:security-goal:` +- `:threat:` +- `:mitigation:` + +Reference roles link to those definitions: + +- `:dm:` +- `:am:` +- `:sg:` +- `:t:` +- `:m:` + +The long definition roles and the short reference roles use canonical prefixes: `DM.`, +`AM.`, `SG.`, `T.`, and `M.`. If the prefix is omitted in the role text, the tool adds +it. + +Example: + +```rst +:security-goal:`SG.confidentiality` + +The mitigation is described in :m:`isolate-components`. +``` + +---- + +*Copyright 2018-2026 Arm Limited* diff --git a/tools/docs/specification-lifecycle-workflows.md b/tools/docs/specification-lifecycle-workflows.md new file mode 100644 index 00000000..ae03a703 --- /dev/null +++ b/tools/docs/specification-lifecycle-workflows.md @@ -0,0 +1,83 @@ +<!-- +SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +SPDX-License-Identifier: Apache-2.0 +--> + +# Specification Lifecycle Workflows + +This guide records occasional workflows for maintaining a PSA API specification that is +built with `psa-api-tool`. + +These workflows are separate from the basic setup and build instructions in +`using-psa-api-tool.md`, and from the source editing reference in +`psa-api-tool-notes.md`. They are intended for release managers, specification editors, +and agents doing lifecycle maintenance work. + +The sections below are placeholders to be filled in once the publication process and +repository transfer expectations are settled. + +## Before Starting Work on a New Issue or Version + +Use this workflow after a specification issue or version has been published, and before +the first source changes for the next issue or version are introduced. + +Expected topics: + +- Decide whether the next work is a new issue of the current version, a release + candidate, or a new minor/major version. +- Update `doc_info` publication metadata in `conf.py`. +- Update release-history source entries. +- Reset or update draft, quality, issue, and release-candidate metadata. +- Review filename and document identifier expectations. +- Establish the expected API database baseline before semantic changes begin. +- Check whether generated headers, rendered images, or publication artifacts need to be + reset or regenerated. +- Build the clean starting point and record expected warnings. + +TODO: fill in the exact fields and commands once the publication model is confirmed. + +## Preparing a Release Candidate + +Use this workflow when preparing a candidate build for review before final publication. + +Expected topics: + +- Set release-candidate metadata in `conf.py`. +- Confirm draft and optional-content settings for the candidate review build. +- Update release notes, current status, potential-for-change, and change-history + content. +- Run HTML, PDF, API database, generated-header, and image validation as appropriate. +- Review generated filenames and visible title-page/footer metadata. +- Confirm that intentional API changes are reflected in the checked-in API database. +- Record known and accepted warnings. +- Package or tag review artifacts according to the consuming specification repository + process. + +TODO: define exact validation commands and artifact expectations for PSA API +specifications. + +## Finalizing an Issue or Version for Publication + +Use this workflow when converting an approved release candidate into the final published +issue or version. + +Expected topics: + +- Remove release-candidate metadata and set final quality/status metadata. +- Confirm draft, watermark, license, copyright, owner, and feedback metadata. +- Finalize release-history, current-status, and potential-for-change sections. +- Verify there are no unresolved rendered TODO, rationale, or comment sections unless + intentionally published. +- Run final HTML and PDF builds. +- Run final API database comparison and update generated references when required. +- Regenerate rendered image assets when graphics sources changed. +- Review generated filenames, document identifiers, version strings, and title-page + metadata. +- Create publication artifacts and tags according to the consuming specification + repository process. + +TODO: fill in the exact publication checklist after the release process is agreed. + +---- + +*Copyright 2018-2026 Arm Limited* diff --git a/tools/docs/using-psa-api-tool.md b/tools/docs/using-psa-api-tool.md new file mode 100644 index 00000000..5065f226 --- /dev/null +++ b/tools/docs/using-psa-api-tool.md @@ -0,0 +1,243 @@ +<!-- +SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +SPDX-License-Identifier: Apache-2.0 +--> + +# Building PSA API Specifications + +This guide explains how the PSA API specifications in this repository are built with the +integrated `psa-api-tool` copy in `tools/`. + +For source editing conventions, custom directives, roles, and API documentation +conventions, see `psa-api-tool-notes.md`. + +For occasional lifecycle tasks, such as starting work on a new issue or version, +preparing a release candidate, or finalizing a publication, see +`specification-lifecycle-workflows.md`. That guide intentionally contains placeholders +until the publication process is settled after the repository transfer. + +## Tool Layout + +The repository has a small top-level `Makefile`: + +```make +PSA_API_TOOL ?= tools + +ifeq ($(wildcard $(PSA_API_TOOL)/make),) + $(error The 'PSA_API_TOOL' variable is not set, or does not point to a suitable installation of psa-api-tool) +endif + +include $(PSA_API_TOOL)/make +``` + +By default, builds use the checked-in tool under `tools/`. Editors can override +`PSA_API_TOOL` to test another compatible copy: + +```sh +make PSA_API_TOOL=/path/to/psa-api-tool doc/crypto/html +``` + +Each specification has its own `conf.py` under `doc/<spec>/`. The document configuration +sets `psa_api_tool_path` to the repository `tools/` directory, allows the `PSA_API_TOOL` +environment variable to override that path, then executes `psa-api-conf.py` from the +selected tool copy. + +Each specification directory also contains a minimal `pyproject.toml` file. These files +act as project-boundary markers for editor integrations such as Esbonio, allowing each +specification to have a separate Sphinx session and live preview while using the same +checked-in tool copy. They are not used by the repository make targets. + +Older PSA API source revisions used the former `atg-sphinx-spec` name. The shared +makefile exports `ATG_SPHINX_SPEC` as an alias for `PSA_API_TOOL`, and the tool provides +`atg-sphinx-conf.py` as a compatibility wrapper around `psa-api-conf.py`. This allows +older source trees to be built with the newer tool by invoking: + +```sh +make -f /path/to/psa-api-tool/make doc/crypto/html +``` + +## Requirements + +The core HTML and structured-output build path requires: + +- Python 3. +- Sphinx. +- A POSIX-like shell and `make`. + +PDF output also requires a LaTeX toolchain that provides `pdflatex`. + +The `images` target may require additional tools, depending on the figure sources used +by the specification: + +- `wavedrompy` for `.json` bitfield diagrams. +- Java and PlantUML for `.puml` diagrams. +- `rsvg-convert` for SVG-to-PDF conversion. + +Graphviz is only required for documents that use Sphinx Graphviz directives. + +Most rendered graphics are checked in, so a text-only edit normally does not require the +full graphics toolchain. + +The PDF target uses `qpdf` to optimize generated PDF files when it is available. This is +optional. + +### Version and Platform Guidance + +The build tooling is not currently defined by a pinned requirements file or a repeatable +CI environment. Contributors should report the tool and platform versions used when +diagnosing build differences. + +The integrated tool introduction branch has been tested on macOS arm64 with Python +3.13.7, Sphinx 8.1.0, GNU Make 3.81, MiKTeX-pdfTeX 4.10, OpenJDK 21.0.2, PlantUML +1.2025.2, Graphviz 12.2.1, `rsvg-convert` 2.60.0, and `qpdf` 12.3.2. The precursor +tooling was also used successfully with Git Bash on Windows 11, and the tooling is +expected to work on Linux with the equivalent packages installed. These platforms are +descriptive, not a formal support matrix. + +The tools are maintained against recent Sphinx releases. Sphinx 8.1.0 is the current +known-good version; Sphinx 5.3 is the oldest version expected to be plausible, but it is +not currently validated. When setting up a new environment, start with the newest stable +versions available from the platform package manager, then validate with the specific +targets needed for the change under review. + +## Common Builds + +From the repository root, build one output format for one specification: + +```sh +make doc/crypto/html +make doc/crypto/pdf +make doc/crypto/headers +make doc/crypto/api-diff +``` + +The same pattern works for the other specification directories: + +- `doc/attestation` +- `doc/crypto` +- `doc/crypto-driver` +- `doc/fwu` +- `doc/status-code` +- `doc/storage` + +Build one output format for every specification: + +```sh +make html +make pdf +``` + +Build all default outputs for one specification: + +```sh +make doc/crypto +``` + +Generated output is written under `build/`, mirroring the document path. For example, +`make doc/crypto/html` writes HTML under `build/doc/crypto/html`. + +## Targets + +| Target | Purpose | +| --- | --- | +| `html` | Build HTML output and rewrite it for the repository website layout. | +| `latex` | Generate LaTeX output. | +| `pdf` | Generate LaTeX output, run `pdflatex`, and optimize the PDF with `qpdf` when available. | +| `xml` | Build XML structured document output. | +| `headers` | Generate reference C header files from API directives. | +| `api-db` | Generate normalized API database headers. | +| `api-diff` | Compare generated API database headers with the checked-in `api.db/` reference. | +| `api-update` | Update the checked-in `api.db/` reference after an intentional API change. | +| `images` | Regenerate converted or generated image assets when required tools are installed. | +| `clean` | Remove generated build output for the selected document or documents. | + +Use `INTERNAL=1` to build an internal-tagged output variant when a document uses +internal-only content. + +## Recommended Validation + +For a source-only documentation change, build the affected HTML output first: + +```sh +make doc/<spec>/html +``` + +For a change that affects API directives, generated C declarations, manifest +definitions, or reference API headers, run: + +```sh +make doc/<spec>/api-diff +``` + +If the API change is intentional, review the diff and update the checked-in API +database: + +```sh +make doc/<spec>/api-update +``` + +When publishing a new revision, the reference headers are updated using the output +from the build: + +```sh +make doc/<spec>/headers +``` + +For changes that affect title pages, front matter, page breaks, LaTeX styling, or +publication-ready layout, build: + +```sh +make doc/<spec>/pdf +``` + +For changes to graphics sources, run: + +```sh +make doc/<spec>/images +``` + +The XML output can be useful when reviewing generated document structure, resolved +references, table structure, glossary entries, and API sections: + +```sh +make doc/<spec>/xml +``` + +Treat XML as supplementary validation. It does not replace rendered HTML/PDF inspection +or API database checks. It can be helpful when diagnosing why some source content is +not rendering as expected. + +## Document Configuration + +Each document `conf.py` defines a `doc_info` dictionary and then executes the shared +tool configuration. Keep document configuration focused on document metadata and +document-specific choices. Avoid setting Sphinx configuration variables directly in +`conf.py` unless the shared configuration cannot support the required behavior. + +The current PSA API documents use the Arm-style `psa-api-2022` and `psa-api-2025` +templates. These templates preserve the existing front matter and release metadata model +while allowing the repository to build without an external `atg-sphinx-spec` checkout. + +Important `doc_info` keys used by these specifications include: + +| Key | Purpose | +| --- | --- | +| `template` | Template directory under `tools/templates/`. | +| `title` | Document title used by Sphinx and the title page. | +| `version` | Base API version, normally `X.Y`. | +| `issue_no` | Document issue or maintenance revision. | +| `draft` | Draft flag or draft revision, depending on the selected publication model. | +| `release_candidate` | Release-candidate number for existing Arm-style documents. | +| `quality` | API maturity code such as `ALP`, `BET`, or `REL`. | +| `header` | Default generated C header path for API directives. | +| `header_doxygen` | Generated header annotation level. | +| `error_order` | Document-wide order for generated return values. | +| `identifier_index` | Controls the generated C identifier index. | +| `prolog_files` | Shared substitution files included in the Sphinx prolog. | + +For detailed directive and role behavior, use `psa-api-tool-notes.md` as the editing +reference. + +---- + +*Copyright 2018-2026 Arm Limited* diff --git a/tools/images.mk b/tools/images.mk new file mode 100644 index 00000000..ff569606 --- /dev/null +++ b/tools/images.mk @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +# SPDX-License-Identifier: Apache-2.0 + +# Makefile for building image files (SVG and PDF) from sources + +# The parameter $(IMAGE) must have the location of the image files +IMAGE = . + +# This makefile +MAKEFILE = $(lastword $(MAKEFILE_LIST)) +# The location of this makefile, relative to the current directory +MAKEFILEDIR := $(patsubst %/,%,$(dir $(MAKEFILE))) +# The location of the psa-api-tool tools, containing the PlantUML configuration +SPEC = $(MAKEFILEDIR) + +# Bitfield image generation, command to run to wavedrompy +WAVEDROM = wavedrompy + +# UML image generation, command to run plantuml +JAVA = java +PLANTUML_IPATH = $(SPEC)/puml +PLANTUML = $(JAVA) -Dplantuml.include.path="$(PLANTUML_IPATH)" -jar ~/jar/plantuml.jar +PLANTUML_FLAGS = -nometadata -tsvg -charset utf-8 + +# SVG to PDF conversion +SVG2PDF = rsvg-convert +SVG2PDF_FLAGS = --format=pdf + +OSTYPE = $(shell echo $${OSTYPE}) +ifneq (,$(filter darwin%,$(OSTYPE))) + SED_FLAG = -i '' +else ifneq (,$(filter bsd%,$(OSTYPE))) + SED_FLAG = -i '' +else + SED_FLAG = -i'' +endif + +FIX_SVG_FONTS = sed $(SED_FLAG) -e 's/"roboto mono"/"Roboto Mono,monospace"/gi;s/"roboto"/"Roboto,sans-serif"/gi;s/lato/Lato,sans-serif/gi;s/inconsolata/Inconsolata,monospace/gi' + +# Images can be in sub-directories of those listed in $IMAGE +IMAGES := $(shell find $(IMAGE) -type d -print) + +# List of UML diagrams to render +UML_IMAGES := $(wildcard $(addsuffix /*.puml,$(IMAGES))) +# List of bitfield descriptions to render +BIT_IMAGES := $(wildcard $(addsuffix /*.json,$(IMAGES))) +# Build a list of source SVG image files to convert +SVG_IMAGES := $(sort $(patsubst %.json,%.svg,$(BIT_IMAGES)) \ + $(patsubst %.puml,%.svg,$(UML_IMAGES)) \ + $(wildcard $(addsuffix /*.svg,$(IMAGES))) \ + ) +# Build a list of generated PDF files +PDF_IMAGES := $(patsubst %.svg,%.pdf,$(SVG_IMAGES)) + +PUML_INCLUDES := $(wildcard $(PLANTUML_IPATH)/*) + +# Pattern rule for identifying UML files to rebuild +%.svg : %.puml $(MAKEFILE) $(PUML_INCLUDES) + @echo "Rendering $<"; \ + $(PLANTUML) $(PLANTUML_FLAGS) $< ; \ + $(FIX_SVG_FONTS) $@ + +# Pattern rule for converting JSON to SVG +%.svg : %.json $(MAKEFILE) + @echo "Rendering $<"; \ + $(WAVEDROM) -i $< -s $@; \ + $(FIX_SVG_FONTS) $@ + +# Pattern rule for identifying SVG files to convert +%.pdf : %.svg + @echo "Converting $<"; \ + $(SVG2PDF) $(SVG2PDF_FLAGS) -o $@ $< + +.PHONY: all +all: svg pdf + +.PHONY: svg +svg: $(SVG_IMAGES) + +.PHONY: pdf +pdf: $(PDF_IMAGES) + +.PHONY: help +help: + @echo "To build the graphics, please use \`make <target>' where <target> is one of"; \ + echo " svg to make the SVG image files"; \ + echo " pdf to make the PDF image files"; \ + echo ""; \ + echo " all to make all image files" diff --git a/tools/license/arm_psa_certified_api_license.rst b/tools/license/arm_psa_certified_api_license.rst new file mode 100644 index 00000000..2216685b --- /dev/null +++ b/tools/license/arm_psa_certified_api_license.rst @@ -0,0 +1,40 @@ +.. SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +.. SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license AND Apache-2.0 + +.. + The license for the text and illustrations is CC BY-SA 4.0 with and additional patent clause derived from Apache License 2.0. + The license for source code within the document is Apache License 2.0. + + CC BY-SA 4.0 is at https://creativecommons.org/licenses/by/4.0 + Apache 2.0 is at https://www.apache.org/licenses/LICENSE-2.0 + +.. license:: + + .. insert-section:: Text and illustrations + :not-in-toc: + + Text and illustrations in this work are licensed under Attribution-ShareAlike 4.0 International (CC BY-SA 4.0). To view a copy of the license, visit :url:`creativecommons.org/licenses/by-sa/4.0`. + + **Grant of patent license**. Subject to the terms and conditions of this license (both the CC BY-SA 4.0 Public License and this Patent License), each Licensor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Licensed Material, where such license applies only to those patent claims licensable by such Licensor that are necessarily infringed by their contribution(s) alone or by combination of their contribution(s) with the Licensed Material to which such contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Licensed Material or a contribution incorporated within the Licensed Material constitutes direct or contributory patent infringement, then any licenses granted to You under this license for that Licensed Material shall terminate as of the date such litigation is filed. + + The Arm trademarks featured here are registered trademarks or trademarks of Arm Limited (or its subsidiaries) in the US and/or elsewhere. All rights reserved. Please visit :url:`arm.com/company/policies/trademarks` for more information about Arm's trademarks. + + .. insert-section:: About the license + :not-in-toc: + + The language in the additional patent license is largely identical to that in section 3 of the Apache License, Version 2.0 (Apache 2.0), with two exceptions: + + 1. Changes are made related to the defined terms, to align those defined terms with the terminology in CC BY-SA 4.0 rather than Apache 2.0 (for example, changing "Work" to "Licensed Material"). + + 2. The scope of the defensive termination clause is changed from "any patent licenses granted to You" to "any licenses granted to You". This change is intended to help maintain a healthy ecosystem by providing additional protection to the community against patent litigation claims. + + To view the full text of the Apache 2.0 license, visit :url:`apache.org/licenses/LICENSE-2.0`. + + .. insert-section:: Source code + :not-in-toc: + + Source code samples in this work are licensed under the Apache License, Version 2.0 (the "License"); you may not use such samples except in compliance with the License. You may obtain a copy of the License at :url:`apache.org/licenses/LICENSE-2.0`. + + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + See the License for the specific language governing permissions and limitations under the License. diff --git a/tools/license/missing.rst b/tools/license/missing.rst new file mode 100644 index 00000000..66689649 --- /dev/null +++ b/tools/license/missing.rst @@ -0,0 +1,8 @@ +.. SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +.. SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +.. The license is missing from the document information + +.. license:: <<Missing license>> + + :issue:`<<Missing license text>>` diff --git a/tools/license/psa_certified_api_license.rst b/tools/license/psa_certified_api_license.rst new file mode 100644 index 00000000..863d7c57 --- /dev/null +++ b/tools/license/psa_certified_api_license.rst @@ -0,0 +1,38 @@ +.. SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +.. SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license AND Apache-2.0 + +.. + The license for the text and illustrations is CC BY-SA 4.0 with and additional patent clause derived from Apache License 2.0. + The license for source code within the document is Apache License 2.0. + + CC BY-SA 4.0 is at https://creativecommons.org/licenses/by/4.0 + Apache 2.0 is at https://www.apache.org/licenses/LICENSE-2.0 + +.. license:: + + .. insert-section:: Text and illustrations + :not-in-toc: + + Text and illustrations in this work are licensed under Attribution-ShareAlike 4.0 International (CC BY-SA 4.0). To view a copy of the license, visit :url:`creativecommons.org/licenses/by-sa/4.0`. + + **Grant of patent license**. Subject to the terms and conditions of this license (both the CC BY-SA 4.0 Public License and this Patent License), each Licensor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Licensed Material, where such license applies only to those patent claims licensable by such Licensor that are necessarily infringed by their contribution(s) alone or by combination of their contribution(s) with the Licensed Material to which such contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Licensed Material or a contribution incorporated within the Licensed Material constitutes direct or contributory patent infringement, then any licenses granted to You under this license for that Licensed Material shall terminate as of the date such litigation is filed. + + .. insert-section:: About the license + :not-in-toc: + + The language in the additional patent license is largely identical to that in section 3 of the Apache License, Version 2.0 (Apache 2.0), with two exceptions: + + 1. Changes are made related to the defined terms, to align those defined terms with the terminology in CC BY-SA 4.0 rather than Apache 2.0 (for example, changing "Work" to "Licensed Material"). + + 2. The scope of the defensive termination clause is changed from "any patent licenses granted to You" to "any licenses granted to You". This change is intended to help maintain a healthy ecosystem by providing additional protection to the community against patent litigation claims. + + To view the full text of the Apache 2.0 license, visit :url:`apache.org/licenses/LICENSE-2.0`. + + .. insert-section:: Source code + :not-in-toc: + + Source code samples in this work are licensed under the Apache License, Version 2.0 (the "License"); you may not use such samples except in compliance with the License. You may obtain a copy of the License at :url:`apache.org/licenses/LICENSE-2.0`. + + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + See the License for the specific language governing permissions and limitations under the License. diff --git a/tools/make b/tools/make new file mode 100644 index 00000000..5fdc3644 --- /dev/null +++ b/tools/make @@ -0,0 +1,267 @@ +# SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +# SPDX-License-Identifier: Apache-2.0 + +# Common top level include-makefile for building a suite of documents +# +# This should be included from the specification project makefile, +# with the following variables defined: + +# OUTPUT optional - it specifies the output directory, default to 'build' +OUTPUT ?= build +# INTERNAL optional - boolean, if non-empty this sets the 'internal' tag on the documentation build + +# Use the directory containing this makefile for the psa-api-tool tools +override PSA_API_TOOL := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +export PSA_API_TOOL +# Compatibility for older specification sources that still load +# atg-sphinx-conf.py using ATG_SPHINX_SPEC. +override ATG_SPHINX_SPEC := $(PSA_API_TOOL) +export ATG_SPHINX_SPEC +# For recursive invokation we need to know which makefile is used +THIS_MAKE := -f $(firstword $(MAKEFILE_LIST)) + +ifeq ($(wildcard conf.py),) + +############################### +# Document project make rules # +############################### + +# There is no conf.py - this is a project directory + +# The set of valid format targets for individual documents +TARGETS := clean html latex pdf xml headers api-db api-diff api-update images +TARGETS_ALL := $(TARGETS) all + +# The set of document Sphinx configuration files +DOCS := $(patsubst ./%/conf.py,%,$(shell find . -name conf.py)) +# compile the set of directories containing documents, and their parents +DOCS2 := $(patsubst %/,%,$(subst ./,,$(dir $(DOCS)))) +DOCS3 := $(patsubst %/,%,$(subst ./,,$(dir $(DOCS2)))) +DOCS4 := $(patsubst %/,%,$(subst ./,,$(dir $(DOCS3)))) +DOCS_ALL := $(sort $(DOCS) $(DOCS2) $(DOCS3) $(DOCS4)) + +# The full combination of all documents and targets +ALL_TARGETS := $(foreach DOC,$(DOCS_ALL),$(addprefix $(DOC)/,$(TARGETS_ALL))) + +MAKE_VARS := INTERNAL=$(INTERNAL) + +.PHONY: all +all: $(addsuffix /all,$(DOCS)) + +# Building all target types for a specific document +.PHONY: $(DOCS_ALL) +$(DOCS_ALL): + @"$(MAKE)" $(THIS_MAKE) $@/all \ + OUTPUT=$(OUTPUT) $(MAKE_VARS) + +# Building a specific target type for each document +.PHONY: $(TARGETS) +$(TARGETS): + @"$(MAKE)" $(THIS_MAKE) $(addsuffix /$@,$(DOCS)) \ + OUTPUT=$(OUTPUT) $(MAKE_VARS) + +# Explicitly using all/* to build a certain target type +.PHONY: all/% +all/%: %; + +# Building a single target type for a single document + +.PHONY: $(ALL_TARGETS) +$(ALL_TARGETS): + @if [ -e $(@D)/Makefile ]; then \ + MAKEFILE="Makefile" ; \ + else \ + MAKEFILE="$(PSA_API_TOOL)/make" ; \ + fi; \ + "$(MAKE)" -C $(@D) $(@F) -f $$MAKEFILE \ + OUTPUT=$(abspath $(OUTPUT))/$(@D) $(MAKE_VARS) + +else + +############################## +# Single document make rules # +############################## + +# There is a conf.py - this is a document directory + +# SOURCE optional - specifies the source directory, default . +SOURCE ?= . + +# FIGURES optional - a set of directories containing images to generate/transform. Default 'figure' +FIGURES ?= figure + +# Support for maintaining API compatibility (optional) +# Can be overriden by a value in the makefile that includes this +API_PATH ?= api.db + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal builds output to the .../internal path and set the "internal" tag +ifneq ($(strip $(INTERNAL)),) +override OUTPUT := $(OUTPUT)/internal +SPHINXOPTS += -t internal +endif + +# Internal variables. +ALLSPHINXOPTS := -d $(OUTPUT)/doctrees $(SPHINXOPTS) -T $(SOURCE) + +.PHONY: all +all: html latex pdf xml headers + +ifneq ($(wildcard $(FIGURES)),) +# Rebuild converted and generated images if graphic sources are modified +.PHONY: images +images: + @"$(MAKE)" -f $(PSA_API_TOOL)/images.mk IMAGE=$(FIGURES) +else +.PHONY: images +images: + @echo "No images in this document" +endif + +# Assume all content under SOURCE and PSA_API_TOOL tooling affects the output +PRUNE := .* __* skeleton guide build docs +PRUNE := $(patsubst %,-name "%" -prune -or,$(PRUNE)) +PSA_API_TOOL_FILES := $(shell find $(PSA_API_TOOL) $(PRUNE) -type f -print) +SPHINX_INPUTS := $(shell find $(SOURCE) -type f -print) $(PSA_API_TOOL_FILES) + +.PHONY: html +html: $(OUTPUT)/html/done +$(OUTPUT)/html/done: $(SPHINX_INPUTS) + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(@D) + @pushd $(@D); python3 $(PSA_API_TOOL)/rewrite-html-for-jekyll.py; popd; \ + touch $@; echo; echo "Build finished. The HTML pages are in $(@D)." + +.PHONY: latex +latex: $(OUTPUT)/latex/done +$(OUTPUT)/latex/done: $(OUTPUT)/latex/donetex + @touch $@; echo; echo "Build finished; the LaTeX files are in $(OUTPUT)/latex."; \ + echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make pdf' here to do that automatically)." +$(OUTPUT)/latex/donetex: $(SPHINX_INPUTS) + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(@D) + @touch $@ + +.PHONY: pdf +pdf: $(OUTPUT)/latex/donepdf +$(OUTPUT)/latex/donepdf : $(OUTPUT)/latex/donetex + @echo "Running LaTeX files through pdflatex..."; \ + "$(MAKE)" -C $(OUTPUT)/latex all-pdf; \ + if command -v qpdf >/dev/null 2>&1; then \ + for pdf in "$(@D)"/*.pdf; do \ + [ -e "$$pdf" ] || continue; \ + qpdf --linearize --object-streams=generate --compress-streams=y --replace-input "$$pdf"; \ + done; \ + fi; \ + touch $@; echo ; echo "Build finished; the PDF files are in $(@D)." + +.PHONY: xml +xml: $(OUTPUT)/xml/done +$(OUTPUT)/xml/done: $(SPHINX_INPUTS) + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(@D) + @touch $@; echo; echo "Build finished. The XML files are in $(@D)." + +API_REF_BUILD_PATH = $(OUTPUT)/headers +API_DB_BUILD_PATH = $(OUTPUT)/api.db +API_DIFF = $(OUTPUT)/api.diff + +.PHONY: headers +headers: $(API_REF_BUILD_PATH) +$(API_REF_BUILD_PATH): $(SPHINX_INPUTS) + @rm -rf $@ + $(SPHINXBUILD) -b headers $(ALLSPHINXOPTS) $@ +# Deal with graphviz extension stupidity of copying .css into every target + @rm -rf $@/_static; \ + echo ; echo "Build finished. The reference header files are in $@." + +.PHONY: api-db +api-db: $(API_DB_BUILD_PATH) +$(API_DB_BUILD_PATH): $(SPHINX_INPUTS) + @rm -rf $@ + $(SPHINXBUILD) -b api-db $(ALLSPHINXOPTS) $@ +# Deal with graphviz extension stupidity of copying .css into every target + @rm -rf $@/_static; \ + echo ; echo "Build finished. The reference header files are in $@." + +.PHONY: api-diff +api-diff: $(API_DIFF) + @if [ -s $(API_DIFF) ]; then \ + echo ; \ + echo "WARNING: API changes detected."; \ + echo ""; \ + cat $(API_DIFF); \ + exit 1; \ + else \ + echo "No API changes detected."; \ + fi + +ifeq (,$(wildcard $(API_PATH))) +# Handle the bootstrap case when there is no reference API +# If there is no API, create an empty diff +$(API_DIFF): $(API_DB_BUILD_PATH) + @if [[ 0 -eq `find $(API_DB_BUILD_PATH) -type f | wc -l` ]]; \ + then \ + >$@; \ + else \ + echo "** No reference API **" > $@; \ + fi +else +$(API_DIFF): $(API_DB_BUILD_PATH) $(API_PATH) + @diff --strip-trailing-cr -x .DS_Store -r $(API_PATH) $(API_DB_BUILD_PATH) 1>$@ || exit 0 +endif + +.PHONY: api-update +api-update: $(API_DIFF) + @if [ -s $(API_DIFF) ]; then \ + echo "Copying header files"; \ + mkdir -p $(API_PATH) && cp -r $(API_DB_BUILD_PATH)/* $(API_PATH)/; \ + >$(API_DIFF); \ + fi + +.PHONY: clean +clean:: + rm -rf $(OUTPUT)/* + +endif + +.PHONY: help +help:: +ifeq ($(wildcard conf.py),) + @echo "Please use \`make <document>/<target>' where <document> is one of"; \ + echo " $(DOCS)"; \ + echo "and <target> is one of:" +else + @echo "Please use \`make <target>' where <target> is one of" +endif + @echo " html to make standalone HTML files"; \ + echo " xml to make an XML document structure"; \ + echo " latex to make LaTeX files"; \ + echo " pdf to make LaTeX files and run them through pdflatex"; \ + echo " headers to make the API reference header files"; \ + echo ""; \ + echo " all to make all of the above targets"; \ + echo " clean to remove build output for all targets"; \ + echo ""; \ + echo " api-db to generate the API definition"; \ + echo " api-diff to compare the API definition against a reference"; \ + echo " api-update to update the reference API definition"; \ + echo "" +ifneq ($(wildcard conf.py)$(wildcard $(FIGURES)),conf.py) + @echo " images to update generated and converted image files"; \ + echo "" +endif +ifeq ($(wildcard conf.py),) + @echo "Shortcuts:"; \ + echo " \`make <target>' builds <target> for all documents"; \ + echo " \`make <document>' builds all targets for <document>"; \ + echo "" +endif + @echo "Specifying \`INTERNAL=1' to make will build an internal version"; \ + echo "of the documents" diff --git a/tools/psa-api-conf.py b/tools/psa-api-conf.py new file mode 100644 index 00000000..35dc1e83 --- /dev/null +++ b/tools/psa-api-conf.py @@ -0,0 +1,618 @@ +# SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +# SPDX-License-Identifier: Apache-2.0 + +# -*- coding: utf-8 -*- +# +# This is a common set of configuration options for using Sphinx to build +# PSA Certified API specifications. Project specific definitions are contained in +# the conf.py file that forms the master document directory. +# +# This script is included and executed as part of conf.py, it is not a +# standalone python module or script. +# +# conf.py must have set up: +# * a dictionary doc_info, with project specific information and +# configuration. +# * a string path psa_api_tool_path that defines the the path containing this +# file, either relative to conf.py, or absolute. +# + +import sys, os, re +from datetime import date +import importlib + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath(psa_api_tool_path)) + +# When used within the extension, psa_api_tool_path must be a relative path +psa_api_tool_path = os.path.relpath(psa_api_tool_path) + +# Validate the selected document template, or use the default one +def template_path(template): + return os.path.join(psa_api_tool_path, 'templates', template) + +default_template = 'psa-api-2026' + +template = doc_info.get('template', default_template) +psa_api_template_path = template_path(template) +if not os.path.isdir(psa_api_template_path): + print ('WARNING: Document template "{}" not found, falling back to "{}"'.format(template, default_template)) + psa_api_template_path = template_path(default_template) + +# Process any template-specific configuration +# This must provide some required config, such as font info for html/latex +# This can also override, or fill in the doc_info dictionary + +template_info = {} + +template_conf_file = os.path.join(psa_api_template_path,'template-conf.py') +if os.path.exists(template_conf_file): + exec(compile(open(template_conf_file, encoding='utf-8').read(), 'template-conf.py', 'exec')) + +# -- helper functions + +def subst_rst(item): + if type(item) is str: + return re.sub(r'(<<.+>>)',r'\ :issue:`\1`\ ',item) + else: + return item + +def subst_latex(item): + if type(item) is str: + item = item.replace('_',r'\_') + item = re.sub(r'(<<.+>>)',r'\\DUrole{issue}{\1}',item) + return re.sub(r':(.+):`(.+)`',r'\\DUrole{\1}{\2}',item) + else: + return item + +quality_map = { + 'DEV': 'Development', + 'ALP': 'Alpha', + 'BET': 'Beta', + 'REL': 'Final', + } + +def check_quality(quality): + if not quality: + return 'REL' # Default to Final + if quality.upper() in quality_map: + return quality.upper() + for q in quality_map.items(): + if q[1] == quality: + return q[0] + print("error: Invalid doc_info.quality value") + return '<<Quality>>' + +def full_quality(quality): + return quality_map.get(quality, '<<Quality>>') + +status_map = { + 'DFT': 'Draft', + 'CRV': 'Committee Review', + 'CPR': 'Post Committee Review', + 'MRV': 'Member Review', + 'MPR': 'Post Member Review', + 'MRC': 'Member Release Candidate', + 'MEM': 'Member Release', + 'PRV': 'Public Review', + 'PPR': 'Post Public Review', + 'PRC': 'Public Release Candidate', + 'PUB': 'Public Release', + } + +def check_status(status): + if not status: + return None + if status.upper() in status_map: + return status.upper() + for s in status_map.items(): + if s[1] == status: + return s[0] + print("error: Invalid doc_info.status value") + return '<<Status>>' + +def full_status(status): + return status_map.get(status, '<<Status>>') + +def make_release(v, q, i, draft): + release = f'{v}' + release_full = release + if q != 'REL': + release += f'-{q.lower()}' + release_full += f' {full_quality(q)}' + issue = '' + if i > 0 or draft: + issue = f'{i}' + if draft: + issue += f'.{draft}' + if issue: + release += f'.{issue}' + if q == 'REL': + release_full += f'.{issue}' + else: + release_full += f' revision {issue}' + return (release, release_full, issue) + +def split_version(version): + s = [int(x) for x in version.split('.')] + if len(s) == 1: + s.append(0) + return s[0], s[1] + +# -- Build PSA API specification configuration ----------------------------------- + +now = date.today() + +# Extract and build the Sphinx configuration variables and document data +title = doc_info['title'].split('\n') +fulltitle = ' '.join(title) +rsttitle = ' |br| '.join(title) +htmltitle = '<br />'.join(title) +latextitle = '\\par '.join(title) +title = title[-1] + +project = doc_info.get('project', fulltitle) +author = doc_info.get('author', 'Unattributed') +version = doc_info['version'] +owner = doc_info.get('owner') +copyright_date = doc_info.get('copyright_date', now.strftime('%Y')) +doc_id = doc_info.get('doc_id', '<<Document ID>>') +quality = check_quality(doc_info.get('quality')) +quality_full = full_quality(quality) +issue_no = doc_info.get('issue_no', '<<Issue Number>>') +status = check_status(doc_info.get('status')) +draft = doc_info.get('draft') +# Handle old-style draft/rc scheme +release_candidate = None if draft else doc_info.get('release_candidate') +if status: + if type(draft) is bool: + print("error: Mixing old-style doc_info.draft with doc_info.status") + draft = '<<draft>>' if draft else 0 + if release_candidate is not None: + print("error: Mixing old-style doc_info.release_candidate with doc_info.status. Ignoring rc") + draft = '<<rc{}>>'.format(release_candidate) +else: + # no new-style status has been set, determine from draft/rc + if release_candidate: + status = 'PRC' + draft = release_candidate + elif type(draft) is int: + # Mixing new-style draft sequencing with no status! + print("error: Document status value required") + status = '<<Status>>' + elif draft: + status = 'DFT' + draft = 1 + else: + status = 'PUB' + draft = 0 +# Enforce draft revision provided when required by status +if status in ('MEM','PUB'): + if type(draft) is int and draft > 0: + print("error: Release status must not have draft revision number") + draft = '<<{}>>'.format(draft) +else: + if not draft: + print("error: Non-release status must have draft revision number") + draft = '<<draft>>' + +status_full = full_status(status) + +status_watermark = { + 'DFT': 'DRAFT', + 'CRV': 'Review', + 'CPR': 'Review', + 'MRV': 'Review', + 'MPR': 'Review', + 'MRC': 'Candidate', + 'PRV': 'Review', + 'PPR': 'Review', + 'PRC': 'Candidate', +} +quality_watermark = { + 'DEV': 'Development', + 'ALP': 'ALPHA', + 'BET': 'BETA' +} +watermark = doc_info.get('watermark', + status_watermark.get(status, + quality_watermark.get(quality))) + +feedback = doc_info.get('feedback') +nowdate = now.strftime('%B %Y' if status == 'MEM' or status == 'PUB' else '%d/%m/%Y') +docdate = nowdate if status == 'DFT' else doc_info.get('date', nowdate) + +c_index = doc_info.get('identifier_index', True) +page_break = doc_info.get('page_break', template_info.get('page_break','appendix')) + +majorversion, minorversion = split_version(version) +extension = doc_info.get('extension_doc', None) +if extension: + if extension == True: + # Support previous syntax for this config item + extension = 'Extension' + version = '{} {}'.format(version, extension) + +# The full version, including alpha/beta/rc tags. +release, release_full, issue = make_release(version, quality, issue_no, draft) + +# Document filename +build_file = template_info.get('make_filename') +docname = build_file(doc_info, doc_id, fulltitle, release, status_full) if build_file else None +if not docname: + docname = project.lower() +docname = docname.replace(' ','_') + +# Build filename +filename = doc_info.get('filename', docname) + +# Copyright notice, default to author +copyright_text = doc_info.get('copyright', author) +copyright = ' {} {}'.format(copyright_date, copyright_text) + +# Create tags based on the content inclusion configuration + +include_content = set(doc_info.get('include_content',[])) + +if status == 'DFT': + include_content.update(['rationale', 'todo', 'banner', 'comment']) +elif status in ('CRV','CPR','MRV','MPR','PRV','PPR'): + include_content.update(['rationale', 'banner']) +elif status in ('MRC','PRC'): + include_content.update(['banner']) + +for option in include_content: + tags.add('include_{}'.format(option)) + +# Substitutions for use in source and latex documents + +doc_terms = { + 'docid': doc_id, # FPG + 'docfulltitle': fulltitle, # PG + 'docrsttitle': rsttitle, # P + 'dochtmltitle': htmltitle, # PG + 'doclatextitle': latextitle, # P + 'doctitle': title, # FPG + 'API': title, #AF + 'APIversion': version, #A P + 'docversion': version, # legacy usage + 'majorversion' : '``{}``'.format(majorversion), # F + 'minorversion' : '``{}``'.format(minorversion), # F + 'hexversion' : '``0x{:02X}{:02X}``'.format(majorversion, minorversion), # F + 'docquality': quality_full, # P + 'docissue': issue or str(issue_no), # P + 'docstatus': status_full, # G + 'docauthor': author, # PG + 'docdate': docdate, # P + 'docrelease': release, # G + 'docreleasefull': release_full, # FP + 'doccopyright': copyright, # PG + 'docowner': owner, # P + 'docconfidentiality': 'Non-confidential', + 'docfeedback': feedback, # PG + 'docwatermark': watermark, # PG + 'docchapterbreak': ('1' if page_break == 'chapter' else ''), # PG + 'docappendixbreak': ('1' if page_break == 'appendix' else ''), # PG +} +# Filter out any missing or empty items +doc_terms = dict((k,v) for k,v in doc_terms.items() if v is not None and v != '') +# Add any extra terms from the template +for fd in template_info.get('terms', {}).items(): + doc_terms[fd[0]] = fd[1].format(**doc_terms) +# Add any formatted terms from the template +for fd in template_info.get('formatted_terms', {}).items(): + tag = fd[0] + data = fd[1] + doc_terms[tag] = f':{tag}:`{data}`'.format(**doc_terms) + +logo_file = template_info['logo_file'] + +# -- psa-api-tool extension configuration -------------------------------------- + +primary_domain = 'psa_c' + +psa_api_license = doc_info.get('license', 'missing') + +psa_api_c_header = doc_info.get('header', filename) + +psa_api_retval_order = doc_info.get('error_order',[]) + +psa_api_header_doxygen = doc_info.get('header_doxygen', 0) + +psa_api_front_sections = template_info.get('front_sections',[]) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.todo', + 'sphinx.ext.graphviz', + 'psa-api-tool' +] + +try: + if importlib.util.find_spec('sphinxext.opengraph') is not None: + # Configuration for opengraph metadata + ogp_site_url = '{{ site.url }}/' + ogp_site_name = '{} {}'.format(fulltitle, version) + extensions.append('sphinxext.opengraph') +except ModuleNotFoundError: + pass + +# Add any paths that contain templates here, relative to this directory. +templates_path = [os.path.join(psa_api_template_path,'sphinx-templates')] + +# The suffix(es) of source filenames. +source_suffix = {'.rst': 'restructuredtext'} + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +#language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +#exclude_patterns = [] + +# Custom roles for specifications +roles = ['sc', 'issue'] + [k for k in template_info.get('formatted_terms',{})] +# Additional common substitutions +terms = { + 'impdef': ':sc:`implementation defined`', + } + +prolog = ['.. |br| raw:: html','',' <br />'] +prolog += ['.. role:: {}'.format(r) for r in roles] +prolog += ['.. |{}| replace:: {}'.format(k, v) for k,v in terms.items()] +prolog += ['.. |{}| replace:: {}'.format(k, subst_rst(v)) for k,v in doc_terms.items()] +prolog += ['.. include:: {}'.format(fn) for fn in doc_info.get('prolog_files',[])] +if watermark: + prolog += ['.. only:: html','',' .. container:: watermark','',' |docwatermark|',''] +rst_prolog = '\n'.join(prolog) + '\n\n' + +# The reST default role (used for this markup: `text`) to use for all +# documents. +default_role = 'any' + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, keep warnings as "system message" paragraphs in the built documents. +keep_warnings = True + +# Use a 'todo' in the include_content to indicate if TODOs are processed +todo_include_todos = ('todo' in include_content) + +# Hide the source file name in the 'todolist' +todo_link_only = True + +highlight_language = 'none' + +# Number figures and tables, using the whole document scope +numfig = True +numfig_secnum_depth = template_info.get('numfig_sec_depth', 0) +numfig_format = { + 'figure': 'Figure %s', + 'table': 'Table %s', + 'code-block': 'Listing %s', + 'section': '§%s' +} + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = template_info.get('html_theme', 'alabaster') + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = { + 'fixed_sidebar': True, +} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. +html_title = '{} {}'.format(fulltitle, version) + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = os.path.join(psa_api_template_path,logo_file+'.svg') + +html_css_files = template_info['html_css_files'] + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = [os.path.join(psa_api_template_path, 'html-static')] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + '**': ['toc.html', 'indextoc.html', 'searchbox.html'], +} + +# A dictionary of values to pass into the template engine’s context for all +# pages. +html_context = doc_terms + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +html_domain_indices = c_index +if not c_index: + html_sidebars['**'].remove('indextoc.html') + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, the reST sources are included in the HTML build as _sources/name. +html_copy_source = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +html_show_sphinx = False + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Suffix for section numbers in HTML output. This removes trailing '.' +html_secnumber_suffix = ' ' + +# -- Options for LaTeX output --------------------------------------------- + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, filename+'.tex', fulltitle, author, 'manual'), +] + +# Appendices are indicated in PSA API specifications using the .. appendix:: toctree +latex_appendices = [] + +latex_additional_files = [os.path.join(psa_api_template_path, 'psa-api-tool.sty')] +if 'latex_files' in template_info: + latex_additional_files += [os.path.join(psa_api_template_path, f) for f in template_info['latex_files']] + +# Construct the latex preamble +# Define all the project info used for the title page and footer +latex_preamble = ['\\def\\{}{{{}}}'.format(k,subst_latex(v)).replace('&','\\&') for k,v in doc_terms.items()] +# Include the PSA API-specific styling and content +latex_preamble += [r'\input{psa-api-tool.sty}'] +# Improve all the code blocks if sphinx version supports it +latex_preamble += [r'\useverbatimfortt'] + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). + 'papersize': 'a4paper', + 'maketitle': r'',#r'\psamaketitle', + 'tableofcontents': r'', + +# The font size ('10pt', '11pt' or '12pt'). + 'pointsize': template_info['latex_pointsize'], + +# font package: use the fonts specified in the template info, this should include any necessary \usepackage commands + 'fontpkg': '\n'.join(template_info['latex_fonts']), + +# Include the PAS API specification definitions and preamble. + 'preamble': '\n'.join(latex_preamble), + +# Latex figure (float) alignment + 'figure_align': '!ht', + +# Other configuration + 'sphinxsetup': ','.join(template_info['latex_sphinxsetup']), + +# Fix issue with mismatched flags when including textcomp package +# See https://github.com/sphinx-doc/sphinx/issues/4727#issuecomment-372096951 +# Underlying issue is fixed in Sphinx 1.7.2, but this is harmless + 'passoptionstopackages': ''' +\\PassOptionsToPackage{warn}{textcomp} +\\PassOptionsToPackage{linktocpage=true}{hyperref} +''', + +# remove blank pages between chapters + 'extraclassoptions': 'openany,oneside', + +# Keep the chapter titles simple + 'fncychap': '', +} + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +latex_logo = os.path.join(psa_api_template_path, logo_file+'.pdf') + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# If false, no module index is generated. +latex_domain_indices = c_index + +# Set the standard table format for specifications. Individual tables can override +latex_table_style = template_info['latex_table_style'] + +#-- Options for graphviz extension ---------------------------------------- + +# Use SVG for html output, not PNG +graphviz_output_format = 'svg' + +# Set the font used for graphviz diagrams +graphviz_dot_args = template_info['graphviz_dot_args'] + +#-- Options for mathjax --------------------------------------------------- + +mathjax3_config = template_info['mathjax3_config'] diff --git a/tools/psa-api-tool.py b/tools/psa-api-tool.py new file mode 100644 index 00000000..f1f6237e --- /dev/null +++ b/tools/psa-api-tool.py @@ -0,0 +1,2628 @@ +# SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +# SPDX-License-Identifier: Apache-2.0 + +import os.path +import re +import string +import textwrap +from collections import namedtuple +from operator import attrgetter +from typing import cast + +import sphinx.builders +import sphinx.directives.code +import sphinx.domains +import sphinx.environment +from docutils import nodes +from docutils.parsers.rst import directives +from sphinx import addnodes +from sphinx.directives.other import Include, TocTree +from docutils.parsers.rst.directives.tables import RSTTable +from sphinx.roles import XRefRole +from sphinx.util.nodes import make_refnode +from sphinx.util.docutils import SphinxRole, SphinxDirective +from sphinx.util.docutils import new_document + +logger = sphinx.util.logging.getLogger(__name__) + +def clean_dir(path): + # Remove all files at the path and in subdirectories + # Does not delete the directories themselves + for the_file in os.listdir(path): + file_path = os.path.join(path, the_file) + if os.path.isfile(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + clean_dir(file_path) + +_nested_parentheses_rs = r'(?:[^()]|\((?:[^()]|\([^()]*\))*\))*' +_nested_parentheses_re = re.compile(r'\s*\(({})\)\Z' + .format(_nested_parentheses_rs)) +def c_name_from_prototype(prototype): + """Parse a C prototype and extract the name that it defines. + + This function supports most prototypes, but has a few limitations. + The following features of the C language are not supported: + * More than 3 levels of nested parentheses. + * Arrays. + * Functions or function pointers returning a function or function pointer. + """ + # Strip optional leading "typedef" and trailing ";" + prototype = re.sub(r'\s*;?\s*\Z', r'', prototype) + prototype = re.sub(r'\A\s*(?:typedef)?\s*', r'', prototype) + m = re.search(_nested_parentheses_re, prototype) + if m: + prototype = prototype[:m.start()] + m = re.search(_nested_parentheses_re, prototype) + if m: + prototype = m.group(1) + # Remove array subscript, or bitfield size + m = re.search(r'(\w+)(?:\[.*\]|\s*:\s*\d+)?\Z', prototype) + return m.group(1) + +def nodes_text(nodes): + return ''.join(n.astext() for n in nodes) + +def autolinking_literal(text, element = nodes.literal): + """Literal text with weak references. + + Return a list of inline nodes that together form ``text``. + Isolate each C identifier (or keyword) in ``text`` as its own node, + and make those nodes a weak reference. + + By default use nodes.literal for each element, but allow the caller + to provide an alternate node constructor as ``element``. + """ + parts = [] + cursor = 0 + for m in re.finditer(r'(0[xX][0-9A-Fa-f]*)|([A-Z_a-z][0-9A-Z_a-z]*)|(/\*.*?(?:\*/))|(/\*(?:.|\n|\r\n?)*?(?:\*/)|//.*?$)', text, re.M): + item = m[0] + if m[1]: + continue + elif m[2]: + # An identifier + xref = sphinx.addnodes.pending_xref(rawsource=item, + reftype='weak', + refdomain='psa_c', + reftarget=item) + xref += element(text=item) + elif m[3]: + # A special comment element in the prototype + # Convert to a possible hyperlink if the text of the comment + # is a link target + word = re.sub(r'[ _:;,-.+=#~\/!?*]+','-',item[3:-3]).lower() + rf = sphinx.addnodes.pending_xref(rawsource=word, + reftype='ref', + refdomain='std', + refexplicit=True, + reftarget=word) + rf += element(text=item) + xref = nodes.emphasis() + xref += rf + elif m[4]: + # A regular C comment + xref = nodes.emphasis(text=item) + if cursor < m.start(): + parts.append(element(text=text[cursor:m.start()])) + parts.append(xref) + cursor = m.end() + if cursor < len(text): + parts.append(element(text=text[cursor:])) + return parts + +def autolinking_literal_block(text): + """Literal text block with weak references. + + Return a nodes.literal_block containing auto-linked text. + """ + + # When a literal node is contained within a literal_block node, some + # output formats compose the two formats and result in a messy rendering. + # + # As this is already formatted as a literal_block, we only need to use + # Text blocks for the individual pieces of auto-linked text. + def text_element(text): + return nodes.Text(text) + + # Providing an explicit rawsource that is distinct from the content + # causes the highlighter to bail out and preserve the auto-linking xrefs + # + # The meaningless '<psa-autolink>' is used for this purpose, so that if the + # rawsource is used for debugging, it can still be identified in the + # source document. + block = nodes.literal_block( + rawsource="<psa-autolink>" + text + '</psa-autolink>', + language = "none") + block += autolinking_literal(text, element=text_element) + return block + +def role_autolink(name, rawtext, text, lineno, inliner, + options={}, content=[]): + return autolinking_literal(text), [] + +class PSACodeBlock(sphinx.directives.code.CodeBlock, SphinxDirective): + required_arguments = 0 # Override in case of using Sphinx <2.0 + optional_arguments = 1 # Override in case of using Sphinx <2.0 + + def run(self): + if not self.arguments or self.arguments[0] != 'xref': + # A standard sphinx code-block:: + return super().run() + + code = self.content.data + if 'linenos' in self.options or 'lineno-start' in self.options: + start = self.options.get('lineno-start',1) + nlines = len(code) + just = len(str(start + nlines -1)) + numbers = [str(start + n).rjust(just) for n in range(0, nlines)] + code = [l+' '+r for l,r in zip(numbers,code)] + return [autolinking_literal_block('\n'.join(code))] + +class PSACodeBlockInclude(sphinx.directives.code.LiteralInclude, SphinxDirective): + def run(self): + if 'language' not in self.options or self.options['language'] != 'xref': + # A standard sphinx literal-include:: + return super().run() + + # Use the literalinclude reader to implement the filtering options + + document = self.state.document + if not document.settings.file_insertion_enabled: + return [document.reporter.warning('File insertion disabled', + line=self.lineno)] + # convert options['diff'] to absolute path + if 'diff' in self.options: + raise ValueError('Cannot use "diff" option with "xref" language') + + try: + location = self.state_machine.get_source_and_line(self.lineno) + rel_filename, filename = self.env.relfn2path(self.arguments[0]) + self.env.note_dependency(rel_filename) + + reader = sphinx.directives.code.LiteralIncludeReader(filename, self.options, self.config) + text, lines = reader.read(location=location) + text = text.strip() + + if 'linenos' in self.options or 'lineno-start' in self.options: + code = text.split('\n') + start = reader.lineno_start + nlines = len(code) + just = len(str(start + nlines -1)) + numbers = [str(start + n).rjust(just) for n in range(0, nlines)] + code = [l+' '+r for l,r in zip(numbers,code)] + text = '\n'.join(code) + return [autolinking_literal_block(text)] + except Exception as exc: + return [document.reporter.warning(exc, line=self.lineno)] + + +# some raw latex nodes for document control + +def latexnode(latex, directive=None): + raw_node = nodes.raw('', text=latex, format = 'latex') + if directive: + raw_node.source, raw_node.line = directive.state_machine.get_source_and_line(directive.lineno) + return raw_node + +def latexnewpage(directive): + return latexnode(r'\clearpage', directive) + +def latexpageref(id, directive = None): + return latexnode('\\psapageref{{{}}}'.format(id), directive) + + +# .. rationale:: directive for Information blocks that only appear if the 'include_rationale' +# tag is defined. + +class Rationale(SphinxDirective): + optional_arguments = 1 + final_argument_whitespace = True # used if derived class take an argument + has_content = True + + def run(self): + # build an internal only box using the rationale class/environment + # If provided, the argument is the title, otherwise "Rationale" is used + if not self.arguments: + self.arguments = ['Rationale'] + title, m = self.state.inline_text('\n'.join(self.arguments), self.lineno) + titlebold = nodes.strong() + titlebold += title + titlenode = nodes.paragraph() + titlenode['classes'] += ['admonition-title'] + titlenode += titlebold + content = nodes.container(classes = ['rationale','admonition']) + content += titlenode + content += m + self.state.nested_parse(self.content, self.content_offset, content) + # now wrap this in an ``.. only:: include_rationale`` node + only = sphinx.addnodes.only(expr = 'include_rationale') + only += content + return [only] + +# .. comment:: directive for Information blocks that only appear if the 'include_comment' +# tag is defined. + +class Comment(SphinxDirective): +# optional_arguments = 1 +# final_argument_whitespace = True # used if derived class take an argument + has_content = True + + def run(self): + # build an internal only box using the comment class/environment + title, m = self.state.inline_text('Commentary', self.lineno) + titlebold = nodes.strong() + titlebold += title + titlenode = nodes.paragraph() + titlenode['classes'] += ['admonition-title'] + titlenode += titlebold + content = nodes.container(classes = ['comment','admonition']) + content += titlenode + content += m + self.state.nested_parse(self.content, self.content_offset, content) + # now wrap this in an ``.. only:: include_comment`` node + only = sphinx.addnodes.only(expr = 'include_comment') + only += content + return [only] + +# directives for main document templating and control + +class TemplateImage(directives.images.Image, SphinxDirective): + def run(self): + # locate image relative to the template directory, not the doc source + file = os.path.join(self.config.psa_api_template_path,self.arguments[0]) + # The image:: directive takes a URI, so need to replace OS + # path separators with URI path separators + self.arguments[0] = os.path.normpath(file).replace(os.path.sep, '/') + return super().run() + +class TitlePage(SphinxDirective): + """ The title page directive is the first thing in the main index file + If it has content, this is taken to be the abstract for the document + """ + required_arguments = 0 + has_content = True + option_spec = {} + + def stash_abstract(self, content): + item = { + 'content': content, + 'replace': True, + } + errors = [] + sections = self.env.domaindata['psa_c']['front-matter'] + if 'abstract' in sections: + e = 'Duplicate directive "{}". Also defined in {}.'.format( + 'abstract', sections['abstract'][0]) + errors.append(self.state_machine.reporter.warning(e, line = self.lineno)) + sections['abstract'] = (self.env.docname, item) + return errors + + def run(self): + messages = [] + source = self.state.document.current_source or self.get_source_info()[0] or '' + if self.content: + content = nodes.Element() + self.state.nested_parse(self.content, self.content_offset, content) + system_messages = [n for n in content.children if isinstance(n, nodes.system_message)] + abstract = [n for n in content.children if not isinstance(n, nodes.system_message)] + messages += system_messages + if abstract and 'abstract' in self.config.psa_api_front_sections: + messages += self.stash_abstract(abstract) + title_path = os.path.join(os.sep,self.config.psa_api_template_path,'title-page.rst') + lines = ['.. include:: ' + title_path] + self.state_machine.insert_input(lines, source) + return messages + +class PSATocTree(TocTree): + option_spec = { + 'numbered': directives.nonnegative_int, + 'maxdepth': directives.nonnegative_int, + } + + def run(self): + if self.html_numbered: + num = self.options.setdefault('numbered', self.default_numbered) + else: + num = self.options.pop('numbered', self.default_numbered) + self.options.setdefault('maxdepth', self.default_maxdepth) + numfig_depth = self.env.config.numfig_secnum_depth + fig_prefix = '' + if numfig_depth == 1: + fig_prefix = '\\thechapter-' + elif numfig_depth == 2: + fig_prefix = '\\thesection-' + prefix = [latexnode('\\psa{}{{{}}}{{{}}}'.format(self.kind, num-1, fig_prefix), self)] + toc = super().run() + if self.html_numbered and self.alpha_numbers: + toc[0][0]['alpha_numbers'] = True + return prefix + toc + +class FrontMatter(PSATocTree): + default_numbered = 0 + html_numbered = False # Do not number this in HTML formats, as is not sequential + default_maxdepth = 2 + kind = 'frontmatter' + +class MainToc(PSATocTree): + default_numbered = 3 + html_numbered = True + alpha_numbers = False + default_maxdepth = 3 + kind = 'main' + +class Appendix(PSATocTree): + default_numbered = 3 + html_numbered = True + alpha_numbers = True + default_maxdepth = 3 + kind = 'appendix' + +class About(Include, SphinxDirective): + required_arguments = 0 + final_argument_whitespace = False + option_spec = {} + + def run(self): + self.arguments = [os.path.join(os.sep,self.config.psa_api_template_path,'about-chapter.rst')] + return super().run() + +class IncludeLicense(Include, SphinxDirective): + required_arguments = 0 + final_argument_whitespace = False + option_spec = {} + + def run(self): + license_path = os.path.join(os.sep,self.config.psa_api_tool_path,'license') + license = self.config.psa_api_license + rel_filename, filename = self.env.relfn2path(license) + if not os.path.isfile(filename): + rel_filename = os.path.join(license_path,license.lower().replace('-','_')+'.rst') + _, filename = self.env.relfn2path(rel_filename) + error = [] + if not os.path.isfile(filename): + rel_filename = os.path.join(license_path,'missing.rst') + error = [self.state_machine.reporter.error( + 'Invalid or unknown license name/identifier "{}".'.format(license), + line=self.lineno)] + self.arguments = [rel_filename] + return super().run() + error + +class Collector(SphinxDirective): + """ Collect structured data + options are used for inlinable entries, and content is permitted for + one multi-line item. + When run this data is parsed and the dictionary is added to the + domain data under a key named after the collector. + Only one instance of each collected item allowed in the source + """ + final_argument_whitespace = True # used if derived class take an argument + content_as = 'content' + unparsed_options = () + + def stash(self, item): + error = None + name = self.name.split(":")[-1] + sections = self.env.domaindata['psa_c']['front-matter'] + if name in sections: + e = 'Duplicate directive "{}". Also defined in {}.'.format( + name, sections[name][0]) + error = self.state_machine.reporter.warning(e, line = self.lineno) + sections[name] = (self.env.docname, item) + return error + + def run(self): + data = {} + messages = [] + if len(self.arguments) > 0: + data[self.argument_as], m = self.state.inline_text('\n'.join(self.arguments), self.lineno) + messages += m + for k, v in self.options.items(): + if v is None: + data[k] = True + elif k in self.unparsed_options: + data[k] = v + else: + data[k], m = self.state.inline_text(v, self.lineno) + messages += m + if self.has_content: + content = nodes.Element() + self.state.nested_parse(self.content, self.content_offset, content) + data[self.content_as] = content.children + error = self.stash(data) + if error: + messages[0:0] = error + return messages + +class KeyedCollector(Collector): + """ Collected data is stashed in a dictionary instead of a list. + The data dictionary name is taken from the directive name. + Derived classes can specify a different attribute to use as + the key with id_key. + By default a unique seq number is used as the dictionary key + """ + id_key = None # attribute used as a key + + def stash(self, item): + name = self.name.split(":")[-1] + if self.id_key: + key = canonical_rfc(nodes_text(item[self.id_key])).lower() + item['id'] = nodes.make_id(self.id_key +'-' + key) + else: + # Use a unique key based on source document and line + key = '{}.{}'.format(self.env.docname, self.lineno) + + error = None + data = self.env.domaindata['psa_c'][self.id_key or name] + if key in data: + e = 'Duplicate item "{}" for directive "{}". Also defined in {}.'.format( + key, name, data[key][0]) + error = self.state_machine.reporter.warning(e, line = self.lineno) + data[key] = (self.env.docname, item) + return error + +def option_choice(option, values): + def option_check(argument): + if argument in values: + return argument + raise ValueError(':{}: must be one of ({}).'.format(option, ', '.join(values))) + return option_check + +class FrontSection(Collector): + option_spec = { + 'extend': directives.flag, + 'replace': directives.flag, + 'hide': directives.flag + } + has_content = True + + sections = [] + + @classmethod + def add_directives(cls, app): + cls.sections[0:0] = app.config.psa_api_front_sections + for sect in cls.sections: + app.add_directive(sect, cls) + + def stash(self, item): + name = self.name.split(":")[-1] + errors = [] + n = sum([1 for o in self.option_spec.keys() if o in item]) + if n == 0: + # no option provided + if not item['content']: + # No option provided, and no content. Treat as :hide: + item['hide'] = True + e = 'Empty directive "{}", treating as requesting :hide: option.'.format(name) + errors.append(self.state_machine.reporter.warning(e, line = self.lineno)) + else: + # default to replace + item['replace'] = True + elif n > 1: + e = 'Only specify one of the options (:' + e += ':, :'.join(self.option_spec.keys()) + e += ':) for directive "{}".'.format(name) + errors.append(self.state_machine.reporter.error(e, line = self.lineno)) + + error = super().stash(item) + if error: + errors.append(error) + return errors + + +class InsertFrontSection(SphinxDirective): + """ The argument is the section title + The section id to insert must be provided by the :section: option + The default section content follows. + + If the section is excluded by doc_info, then drop the section. + + If content is provided in the domain data, this replaces the default + content, unless the :extend: flag was set, when it is appended to the + default content. + + If the :break-after: flag is set a page break is made after the section. + + The :class: option wraps all the content in the specified class/environment. + + The :not-in-toc: flag will format this like a section title and content, + but does not use a proper section node and does not result in a TOC entry. + """ + required_arguments = 0 + optional_arguments = 1 # The heading for the section + final_argument_whitespace = True + option_spec = { + 'section': directives.unchanged_required, + 'break-after': directives.flag, + 'class': directives.class_option, + 'not-in-toc': directives.flag, + 'keep-if-empty': directives.flag, + } + has_content = True + + def new_section(self, raw, title): + # Create the container, either a true section or a holding List + # + textnodes, messages = self.state.inline_text(title, self.lineno) + if 'not-in-toc' in self.options: + # use a styled paragraph for the title + i = nodes.inline(title, '', classes = ['sectiontitle']) + i += textnodes + p = nodes.paragraph(title, '') + p += i + n = nodes.Element() + n += p + return n + else: + # Adapted from docutils new_subsection() + section_node = nodes.section(raw_source=raw) + titlenode = nodes.title(title, '', *textnodes) + name = nodes.fully_normalize_name(titlenode.astext()) + section_node['names'].append(name) + section_node += titlenode + section_node += messages + section_node.document = self.state.document + self.state.document.note_implicit_target(section_node, section_node) + return section_node + + def run(self): + result = [] + + name = self.options.get('section') + if name and name not in FrontSection.sections: + result.append(self.state_machine.reporter.error( + 'Section "{}" not recognised for "insert-section::".'.format(name), + line = self.lineno)) + + _, item = self.env.domaindata['psa_c']['front-matter'].get(name, (None, {})) + + if item.get('hide'): + if not 'keep-if-empty' in self.options: + # Hide this section + return result + # Cannot hide a keep-if-empty section + result.append(self.state_machine.reporter.warning( + 'Cannot :hide: mandatory front section "{}".'.format(name), + line = self.lineno)) + + content = nodes.Element() + if not item.get('replace'): + self.state.nested_parse(self.content, self.content_offset, content) + if item.get('replace') or item.get('extend'): + content += item.get('content', []) + content = content.children + + if not content and not 'keep-if-empty' in self.options: + # Section is empty. Do not include it + return result + + if len(self.arguments) == 0: + self.options['not-in-toc'] = True + section_node = nodes.Element() + else: + section_node = self.new_section('\n'.join(self.content), self.arguments[0]) + + if 'class' in self.options: + section_node += nodes.container(nodes_text(content), + classes = self.options['class'], + *content) + else: + section_node += content + + if 'break-after' in self.options: + section_node.append(latexnewpage(self)) + + if 'not-in-toc' in self.options: + result.extend(section_node.children) + else: + result.append(section_node) + return result + +def resolve_references(node, docname, app): + # helper to resolve references in nodes created during deferred processing + document = new_document('') + document += node + app.env.resolve_references(document, docname, app.builder) + document.remove(node) + return node + +# .. banner:: and .. insert-banner:: directive for a title-page Information block +# that only appears if the 'include_banner' tag is defined. + +class banner(nodes.General, nodes.Element): + pass + +class Banner(Collector): + has_content = True + +class InsertBanner(SphinxDirective): + def run(self): + return [banner('')] + +def process_banner_nodes(app, doctree, docname): + _, data = app.env.domaindata['psa_c']['front-matter'].get('banner', (None,{})) + for node in doctree.traverse(banner): + if not app.builder.tags.has('include_banner') or not data.get('content'): + node.parent.remove(node) + continue + + banner_node = nodes.container(classes = ['banner','admonition']) + banner_node += data['content'] + banner_node = resolve_references(banner_node.deepcopy(), docname, app) + # build a box using the banner class/environment + node.replace_self(banner_node) + +class itemgetterornone: + def __init__(self, item): + self.item = item + + def __call__(self, obj): + return obj.get(self.item) + +class InsertTable(RSTTable, SphinxDirective): + # Base class that will consume data from Collector directives + option_spec = { + 'class': directives.class_option, + 'name': directives.unchanged_required, + } + optional_arguments = 1 + final_argument_whitespace = True + + def build_table(self, data, widths, keys=None, headers=None): + # Build a table node and preceeding colspec from the data of dictionary + # rows, given the structure defined. + # Either Headers or keys have to be provided to interpret the dictionary + # as columns. + # If no keys are provided, the headers are used lower-case as keys. + # Keys can either be strings, used directly in the dictionary, or callable + # functions that are invoked on the dictionary. + # Widths must match the number of columns in the headers/Keys + # + assert(keys or headers) + + keys = keys or [h.lower() for h in headers] + ncols = len(keys) + keys = [itemgetterornone(k) if isinstance(k, str) else k for k in keys] + + assert(widths and len(widths) == ncols) + assert(not headers or len(headers) == ncols) + + title_node, _ = self.make_title() + + table = nodes.table('', classes = self.options.get('class',[])) + self.set_source_info(table) + self.add_name(table) + if title_node: + table += title_node + + tgroup = nodes.tgroup(cols=ncols) + table += tgroup + table['classes'].append('colwidths-given') + for width in (widths): + tgroup += nodes.colspec(colwidth=width) + + if headers: + row = nodes.row() + for t in headers: + col = nodes.entry(t, nodes.paragraph(t, nodes.Text(t,t))) + row += col + thead = nodes.thead('', row) + tgroup += thead + + tbody = nodes.tbody() + for r in data: + row = nodes.row() + for k in keys: + v = k(r) + col = nodes.entry() + if v is not None: + if len(v) and isinstance(v[0], nodes.Text): # workaround early sphinx issue + v = nodes.paragraph(nodes_text(v), *v) + col += v + row += col + r['targetdoc'] = self.env.docname + tbody += row + tgroup += tbody + + return [table] if len(tbody) else [] + +class Release(KeyedCollector): + # Capture the information for a single document release, which is + # collated into the Release Information table + # + option_spec = { + 'date': directives.unchanged, + 'confidentiality': option_choice("confidentiality", ("Confidential", "Non-confidential")), + } + optional_arguments = 1 + argument_as = 'version' + content_as = 'change' + has_content = True + +class ReleaseTable(InsertTable): + def run(self): + self.options['class'] = ['longtable'] # splits over pages better if section is not in its own page + + data = (release for _, release in self.env.domaindata['psa_c']['release'].values()) + return self.build_table(data, + headers = ('Date', 'Version', 'Change'), + widths = (4,3,13) + ) + +reference_kinds = ("normative", "informative") + +def canonical_rfc(text): + m = re.match(r'\ARFC([1-9][0-9]*)\Z', text, flags = re.I) + if m: + text = 'RFC ' + m.group(1) + return text + + +class Reference(KeyedCollector): + # Capture the information for a single referenced document, which is + # collated into the References table, and can be referred via the + # :cite: and :cite-ref: roles. + option_spec = { + 'doc_no': directives.unchanged_required, # legacy alias for doc_id + 'doc_id': directives.unchanged_required, + 'author': directives.unchanged_required, + 'title': directives.unchanged_required, + 'publication': directives.unchanged_required, + 'url': directives.uri, + 'kind': option_choice("kind", reference_kinds), + } + required_arguments = 1 + argument_as = 'cite' + has_content = False + id_key = 'cite' + unparsed_options = ('kind',) + default_kind = 'normative' + + def stash(self, item): + errors = [] + # Move legacy 'doc_no' option to the current 'doc_id', without overriding + if 'doc_no' in item: + doc_no = item.pop('doc_no') + if 'doc_id' in item: + e = "Both 'doc_id' and 'doc_no' specified, ignoring 'doc_no'" + errors.append(self.state_machine.reporter.warning(e, line = self.lineno)) + else: + item['doc_id'] = doc_no + item.setdefault('kind', self.default_kind) + error = super().stash(item) + if error: + errors.append(error) + return errors + +class ReferenceTable(InsertTable): + option_spec = InsertTable.option_spec.copy() + option_spec['filter'] = option_choice("filter", ("arm", "non-arm", "with-id", "without-id", "none")) + option_spec['kind'] = option_choice("kind", reference_kinds + ("all",)) + option_spec['layout'] = option_choice("layout", ("by-ref", "by-id")) + option_spec['sorted'] = directives.flag + + @staticmethod + def resolve_ref(env, ref): + # Called during Xref resolution. The Reference table has already been inserted + # so the target document for the citation is known + data = env.domaindata['psa_c']['cite'] + if ref not in data: + return '', '', '' + _, item = data[ref] + return item.get('targetdoc',''), item['id'], item.get('title','') + + @staticmethod + def make_reference_target(reference): + para = nodes.paragraph() + para += nodes.target('', ids = [reference['id']]) + para += nodes.Text('[{}]'.format(nodes_text(reference['cite']))) + return para + + @staticmethod + def make_reference_content(reference): + para = nodes.paragraph() + if 'title' in reference: + if 'author' in reference: + para += reference['author'] + para += nodes.Text(', ') + para += nodes.emphasis('',*reference['title']) + if 'publication' in reference: + para += nodes.Text(', ') + para += reference['publication'] + para += nodes.Text('. ') + if 'url' in reference: + ref = reference['url'][0] + if not isinstance(ref, nodes.reference): + ref = nodes.reference(text=ref, refuri='https://' + ref) + para += [ref] + return para + + @staticmethod + def make_id_reference_specification(reference): + para = nodes.paragraph() + para += reference.get('doc_id', reference['cite']) + return para + + keys3 = [make_reference_target.__func__, 'doc_id', make_reference_content.__func__] + keys2 = [make_reference_target.__func__, make_reference_content.__func__] + keys_by_id = [make_id_reference_specification.__func__, + make_reference_content.__func__, + make_reference_target.__func__] + + def run(self): + self.options['class'] = ['longtable'] # tabulary formats targetted cells badly + + layout = self.options.get('layout', 'by-ref') + if layout == 'by-id': + keys = self.keys_by_id + headers = ['Standard / Specification', 'Description', 'Ref'] + widths = (6,14,4) + else: + keys = self.keys3 + headers = ['Ref', 'Document Number', 'Title'] + widths = (4,4,13) + + filter = self.options.get('filter','none') + if filter == 'arm': + filter = 'with-id' + elif filter == 'non-arm': + filter = 'without-id' + if layout == 'by-ref' and filter == 'without-id': + keys = self.keys2 + headers.pop(1) + widths = (widths[0],sum(widths[1:])) + + kind = self.options.get('kind', 'all') + data = self.env.domaindata['psa_c']['cite'] + def data_iter(): + ids = data.keys() + if 'sorted' in self.options: + ids = sorted(ids) + + for id in ids: + _, item = data[id] + if filter == 'with-id' and not 'doc_id' in item: + continue + if filter == 'without-id' and 'doc_id' in item: + continue + if kind != 'all' and kind != item['kind']: + continue + yield item + + return self.build_table(data_iter(), + headers = headers, + widths = widths, + keys = keys + ) + +def role_cite(name, rawtext, text, lineno, inliner, + options={}, content=[]): + target = text.lower() + ref = sphinx.addnodes.pending_xref(rawsource = rawtext, + refdomain = 'psa_c', + reftype = name, + reftarget = target, + refwarn = True, + support_smartquotes=False + ) + ref += nodes.inline(text, '[{}]'.format(text)) + return [ref], [] + +class Term(KeyedCollector): + # Capture the information for a single glossary term + # + option_spec = { + 'abbr': directives.unchanged_required, + } + required_arguments = 1 + final_argument_whitespace = True + argument_as = 'term' + has_content = True + content_as = 'definition' + id_key = 'term' + + def stash(self, item): + typ = self.name.split(':')[-1] + cls = ['sc'] if typ == 'scterm' else [] + item['cls'] = cls + a_error = None + if typ == 'abbr': + item['isabbr'] = True + elif 'abbr' in item: + # Add extra term for the abbreviation + term = nodes_text(item['term']) + defn = nodes.paragraph(':{}:`{}`'.format(typ, term)) + defn += role_term(typ, ':{}:`{}`'.format(typ, term), term, 0, None)[0] + dabbr = {'term': item['abbr'], 'definition' : [defn], 'cls' : cls, 'isabbr': True } + a_error = super().stash(dabbr) + t_error = super().stash(item) + if not t_error: + return a_error + elif not a_error: + return t_error + else: + return [t_error, a_error] + +term_kinds = ("terms", "abbreviations") + +class TermTable(InsertTable): + option_spec = InsertTable.option_spec.copy() + option_spec['sorted'] = directives.flag + option_spec['kind'] = option_choice("kind", term_kinds + ("all",)) + + @staticmethod + def resolve_ref(env, ref): + # Called during Xref resolution. The Term table has already been inserted + # so the target document for the citation is known + data = env.domaindata['psa_c']['term'] + if ref not in data: + return '', '' + _, item = data[ref] + return item.get('targetdoc',''), item['id'] + + @staticmethod + def make_term_target(term): + para = nodes.paragraph() + para += nodes.target('', ids = [term['id']]) + tnode = nodes.inline(nodes_text(term['term']), classes = term['cls'], *term['term']) + if 'abbr' in term: + tnode += nodes.Text(' (') + tnode += term['abbr'] + tnode += nodes.Text(')') + para += tnode + return para + + keys = [make_term_target.__func__, 'definition'] + + def run(self): + self.options['class'] = ['longtable'] # tabulary formats targetted cells badly + + kind = self.options.get('kind', 'all') + if kind == 'abbreviations': + headers = ('Abbreviation','Meaning') + else: + headers = ('Term','Definition') + + data = self.env.domaindata['psa_c']['term'] + def data_iter(): + ids = data.keys() + if 'sorted' in self.options: + ids = sorted(ids) + + for id in ids: + _, item = data[id] + if kind == 'terms' and 'isabbr' in item: + continue + if kind == 'abbreviations' and not 'isabbr' in item: + continue + yield item + + return self.build_table(data_iter(), + headers = headers, + widths = (1,3), + keys = self.keys + ) + +def role_term(name, rawtext, text, lineno, inliner, + options={}, content=[]): + target = text.lower() + ref = sphinx.addnodes.pending_xref(rawsource = rawtext, + refdomain = 'psa_c', + reftype = name, + reftarget = target, + refwarn = True, + support_smartquotes=False + ) + ref += nodes.inline(text, text) + return [ref], [] + +ref_role = XRefRole(warn_dangling=True, innernodeclass = nodes.inline) + +def role_secref(name, rawtext, text, lineno, inliner, + options={}, content=[]): + return ref_role('psa_c:secref', rawtext, text, lineno, inliner, options, content) + +def role_numref(name, rawtext, text, lineno, inliner, + options={}, content=[]): + return ref_role('psa_c:numref', rawtext, text, lineno, inliner, options, content) + +def role_rfc(role, rawtext, text, lineno, inliner, options={}, content=[]): + # Link this into the citation/references scheme used by PSA template + # :rfc:`nnnn` -> :cite-title:`RFCnnnn` + # The cite-reference would link to an entry for the RFC in the References + # table. + # :rfc:`nnnn#x.y` -> :rfc:`nnnn` >§x.y< + # :rfc:`nnnn#Z` -> :rfc:`nnnn` >Appendix Z< + # The section/appendix link would be a hyperlink to the online copy of + # the RFC section/appendix + # + url_template = inliner.document.settings.rfc_base_url + inliner.rfc_url + m = re.match(r'\A(?P<num>[1-9][0-9]*)(?:#(?P<section>.*))?\Z', text) + if not m: + msg = inliner.reporter.error( + 'RFC number must be a number greater than or equal to 1; ' + f'"{text}" is invalid.', line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + + rfc = m.group('num') + ret, _ = role_cite(role.lower().replace('rfc','cite'), rawtext, f'RFC {rfc}', lineno, inliner, **options) + + url = url_template % int(m.group('num')) + section = m.group('section') + if section: + ret.append(nodes.Text(' ')) + if section[0].isdigit(): + url += f'#section-{section}' + title = f'§{section}' + else: + url += f'#appendix-{section}' + title = f'Appendix {section}' + ret.append(nodes.reference(rawtext, title, refuri=url, **options)) + return ret, [] + +def role_url(name, rawtext, text, lineno, inliner, + options={}, content=[]): + target = text + if '//' not in target: + target = f'https://{target}' + ref = nodes.reference(rawtext, text=text, refuri = target) + return [ref], [] + +sra_elements = { + 'deployment-model': 'dm', + 'adversarial-model': 'am', + 'security-goal': 'sg', + 'threat': 't', + 'mitigation': 'm', +} + +def canonical_sra_id(typ, text): + typ = typ.split(':')[-1] + typ = sra_elements.get(typ, typ) + typ = typ.upper() + '.' + if text.upper().startswith(typ): + text = text[len(typ):] + return typ + text + +# role for SRA references +def role_sra_ref(name, rawtext, text, lineno, inliner, + options={}, content=[]): + text = canonical_sra_id(name, text) + ref = sphinx.addnodes.pending_xref(rawsource = rawtext, + refdomain = 'psa_c', + reftype = name, + reftarget = text, + refwarn = True, + support_smartquotes=False + ) + ref += nodes.inline(text, text) + return [ref], [] + +class SRADefinition(SphinxRole): + # role for SRA definitions + @staticmethod + def resolve_ref(env, ref): + # Called during Xref resolution. + # Return document, and definition label, or None if not present + return env.domaindata['psa_c']['sra'].get(ref, (None, None)) + + def run(self): + # canonicalise the text + text = canonical_sra_id(self.name, self.text) + key = nodes.make_id(text) + + data = self.env.domaindata['psa_c']['sra'] + error = [] + if key in data: + e = 'Duplicate SRA item "{}" for directive "{}". Also defined in {}.'.format( + text, self.name, data[key][0]) + error.append(self.inliner.reporter.warning(e, line = self.lineno)) + data[key] = (self.env.docname, text) + + node = [ + nodes.target('', ids = ['sra-' + key]), + nodes.inline(self.rawtext, text, classes=['sradef']) + ] + return node, error + +psa_roles = { + 'code': role_autolink, + 'cite': role_cite, + 'cite-title': role_cite, + 'term': role_term, + 'scterm': role_term, + 'rfc': role_rfc, + 'rfc-title': role_rfc, + 'secref': role_secref, + 'numref': role_numref, + 'url': role_url, + # SRA roles + 'deployment-model': SRADefinition(), + 'dm': role_sra_ref, + 'adversarial-model': SRADefinition(), + 'am': role_sra_ref, + 'security-goal': SRADefinition(), + 'sg': role_sra_ref, + 'threat': SRADefinition(), + 't': role_sra_ref, + 'mitigation': SRADefinition(), + 'm': role_sra_ref, +} +psa_directives = { + 'code-block': PSACodeBlock, + 'literalinclude': PSACodeBlockInclude, + 'rationale': Rationale, + 'comment': Comment, +} + +class ThreatData: + def __init__(self, DMs): + # set of deployment models specified in threat:: option + self.DMs = DMs + # Threat definition + self.data = {} + +class ThreatSubitem(SphinxDirective): + @property + def threat(self): + return self.env.domaindata['psa_threat']['local'] + + @property + def subitem(self): + return self.name.split(':')[-1] + +class ThreatElement(ThreatSubitem): + # Process a general element in a threat definition + has_content = True + + def run(self): + self.assert_has_content() + if self.subitem in self.threat.data: + raise self.warning('Directive "{}" already provided for this threat.'.format(self.subitem)) + container = nodes.Element() + self.state.nested_parse(self.content, self.content_offset, container) + self.threat.data[self.subitem] = container.children + return [] + +class ThreatRisk(ThreatSubitem): + # Process a risk element in a threat definition + optional_arguments = 1 + final_argument_whitespace = True + option_spec = { + 'impact': directives.unchanged_required, + 'likelihood': directives.unchanged_required, + 'risk': directives.unchanged + } + has_content = False + risk_abbr = {'VL': 'Very Low', 'L': 'Low', 'M': 'Medium', 'H': 'High', 'VH': 'Very High'} + risk_ix = {'Very Low': 0, 'Low': 1, 'Medium': 2, 'High': 3, 'Very High': 4} + risk_matrix = ( # by likelihood, then impact + ('Very Low' , 'Very Low' , 'Very Low' , 'Low' , 'Low' ), + ('Very Low' , 'Very Low' , 'Low' , 'Low' , 'Medium' ), + ('Very Low' , 'Low' , 'Medium' , 'Medium' , 'High' ), + ('Low' , 'Low' , 'Medium' , 'High' , 'Very High'), + ('Low' , 'Medium' , 'High' , 'Very High', 'Very High') + ) + + def parse_risk(self, risk): + risk = self.risk_abbr.get(risk, risk) + textnodes, messages = self.state.inline_text(risk, self.lineno) + p = nodes.paragraph(nodes_text(textnodes)) + p += textnodes + p += messages + return risk, p + + @staticmethod + def overall_risk(impact, likelihood): + impact_ix = ThreatRisk.risk_ix.get(impact) + likelihood_ix = ThreatRisk.risk_ix.get(likelihood) + if impact_ix is None or likelihood_ix is None: + return ':issue:`N/A`' + return ThreatRisk.risk_matrix[likelihood_ix][impact_ix] + + def get_element(self, content, n, default=None): + t, content[n] = self.parse_risk(self.options.get(n, default)) + return t + + def run(self): + DM = self.arguments[0].strip() if self.arguments else '' + content = {} + impact = self.get_element(content, 'impact') + likelihood = self.get_element(content, 'likelihood') + self.get_element(content, 'risk', + self.overall_risk(impact, likelihood)) + + if DM not in self.threat.DMs: + self.threat.DMs.append(DM) + for k, v in content.items(): + term = '-'.join((self.subitem, k)) + self.threat.data.setdefault(term, {})[DM] = v + return [] + +class ThreatDomain(sphinx.domains.Domain): + """Description of a Threat in a PSA SRA document.""" + name = 'psa_threat' + label = 'PSA Threat' + directives = { + 'description': ThreatElement, + 'adversarial-model': ThreatElement, + 'security-goal': ThreatElement, + 'unmitigated': ThreatRisk, + 'mitigations': ThreatElement, + 'residual': ThreatRisk, + } + directives.update(psa_directives) + roles = psa_roles + + def __init__(self, env, data): + super().__init__(env) + self.data['local'] = data + +def comma_list(s): + return [x.strip() for x in s.split(',')] + +class Threat(SphinxDirective): + """Process a Threat definition. + + This must have at least one of a Threat Id (specified using :id: option), and/or + a Threat title, specified as the argument to the directive. + + If :deployments: are specified, this is the number of deployments for which + risks are defined. + """ + has_content = True + optional_arguments = 1 + final_argument_whitespace = True + option_spec = { + 'id': directives.unchanged, + 'deployment-models': comma_list, + } + + @staticmethod + def finalize_DMs(threat): + if len(threat.DMs) > 1 or (len(threat.DMs)==1 and threat.DMs[0] != ''): + for dm in threat.DMs: + if len(dm.split())==1: + n, _ = role_sra_ref('psa_threat:dm', dm, dm, None, None) + else: + n = nodes.inline(dm, dm, classes=['sraref']) + p = nodes.paragraph(dm, '') + p += n + threat.data.setdefault('deployment-model',{})[dm] = p + + @staticmethod + def value_or_na(n): + if not n: + na = 'N/A' + n = nodes.paragraph(na, '', nodes.Text(na)) + return n + + @staticmethod + def entry(n): + n = Threat.value_or_na(n) + return nodes.entry(nodes_text(n), n) + + @staticmethod + def std_item(threat, data): + return data + + @staticmethod + def risk_item(threat, data): + n_dm = len(threat.DMs) + if n_dm == 1: + # if we have a single DM, just output the item + return Threat.value_or_na(data.get(threat.DMs[0])) + else: + # if we have more than one DM, build a borderless, unpadded table + wrap = nodes.container('', classes=['riskrow']) + table = nodes.table('', classes = ['borderless', 'colwidths-given']) + #self.set_source_info(table) + tgroup = nodes.tgroup(cols = n_dm) + for _ in range(n_dm): + tgroup += nodes.colspec(colwidth = 1) + tbody = nodes.tbody() + row = nodes.row() + for dm in threat.DMs: + row += Threat.entry(data.get(dm)) + tbody += row + tgroup += tbody + table += tgroup + wrap += table + return wrap + + threat_card = { + 'adversarial-model': (std_item.__func__, 'Adversarial Model'), + 'security-goal': (std_item.__func__, 'Security Goal'), + 'deployment-model': (risk_item.__func__, 'Deployment Model'), + 'unmitigated-impact': (risk_item.__func__, 'Unmitigated Impact'), + 'unmitigated-likelihood': (risk_item.__func__, 'Unmitigated Likelihood'), + 'unmitigated-risk': (risk_item.__func__, 'Unmitigated Risk'), + 'mitigations': (std_item.__func__, 'Mitigating Actions'), + 'residual-impact': (risk_item.__func__, 'Residual Impact'), + 'residual-likelihood': (risk_item.__func__, 'Residual Likelihood'), + 'residual-risk': (risk_item.__func__, 'Residual Risk'), + } + + @staticmethod + def description(threat): + data = threat.data.get('description') + if not data: + return [] + + data[0].insert(0, nodes.inline(text = 'Description: ', classes=['sralabel'])) + return data + + @staticmethod + def item(threat, id): + if not id in threat.data: + return [] + + f, label = Threat.threat_card[id] + t_node = nodes.term(label) + t_node += nodes.paragraph(label, '', nodes.inline(label, label, classes=['sralabel'])) + d_node = nodes.definition() + d_node += f(threat, threat.data[id]) + return nodes.definition_list_item('', t_node, d_node) + + def threat_section(self, title): + # Prepare a section + section = nodes.section(raw_source='\n'.join(self.content)) + section.document = self.state.document + + # Add title, and provide a link target + textnodes, messages = self.state.inline_text(title, self.lineno) + title_node = nodes.title(title, '', *textnodes) + section['names'].append(nodes.fully_normalize_name(title_node.astext())) + section += title_node + section += messages + self.state.document.note_implicit_target(section, section) + return section + + def run(self): + title = [] + if 'id' in self.options: + title.append(':threat:`{}`'.format(self.options['id'])) + if self.arguments: + title.append(self.arguments[0]) + section = self.threat_section(': '.join(title)) + + threat = ThreatData(self.options.get('deployment-models',[])) + + original_domain = self.env.temp_data['default_domain'] + try: + self.env.temp_data['default_domain'] = ThreatDomain(self.env, threat) + self.state.nested_parse(self.content, self.content_offset, section) + finally: + self.env.temp_data['default_domain'] = original_domain + + self.finalize_DMs(threat) + + # Process the threat definition + section += self.description(threat) + + deflist = nodes.definition_list() + for id in self.threat_card: + deflist += self.item(threat, id) + if deflist.children: + section.append(nodes.container(nodes_text(deflist), classes = ['threat'], *[deflist])) + + # Finished: discard ref to content, which is now inside the node tree + threat.data = None + return [section] + +class C_SubItem(SphinxDirective): + """Common base class for subitems. + + A subitem is part of a list of descriptions that apply to a specific + aspect of an object, for example the return values or the parameters + of a function. + + The definition of the item must immediately follow on the directive line. + The description, if provided, can start on the next line if desired. + """ + + has_content = True + final_argument_whitespace = True + + @property + def desc_data(self): + return self.env.domaindata['psa_description']['local'] + + @property + def subitem(self): + return self.name.split(':')[-1] + + def parse_spec(self, spec): + """Parse the argument for a list sub-item directive. + + Return the item list, spec and head nodes for the definition. + """ + raise NotImplementedError + + def add_to_index(self, spec, node): + pass + + def parse_content(self): + """Parse a subitem and return the description nodes. + """ + container = nodes.Element() + self.state.nested_parse(self.content, self.content_offset, container) + return container.children + + def check_content(self, arg_required=True): + """ Check that content has been provided, and that the definition is + present on the directive line. + Trim the definition from the content ViewList and return the definition + text. + """ + self.assert_has_content() + if self.lineno == self.content_offset+1: + line1 = self.content[0].strip() + self.content.trim_start() + return line1 + if arg_required: + raise self.warning('Argument missing for directive "{}"'.format(self.subitem)) + return None + + def check_subitem_permitted(self): + """ + Check that a sub-item is permitted for the API element types + + The subitem must provide a set ``valid_in`` of items that it can be used in + """ + if self.desc_data.element.objtype not in self.valid_in: + raise self.warning('Directive "{}" not valid for API element "{}"'.format( + self.subitem, self.desc_data.element.objtype)) + + def add_target_and_index(self, name, target): + self.desc_data.element.add_target_and_index(name, target) + + def run(self): + """Default run method for list sub-item directives. + + Stash the parsed definition list item in the appropriate list. + """ + self.check_subitem_permitted() + list, spec, head_nodes = self.parse_spec(self.check_content()) + + term_node = nodes.term(nodes_text(head_nodes)) + term_node += nodes.paragraph(nodes_text(head_nodes), '', *head_nodes) + description = nodes.definition(self.block_text) + self.state.nested_parse(self.content, self.content_offset, description) + defn = nodes.definition_list_item(description.astext(), + term_node, description, spec=spec) + self.add_to_index(spec, defn) + list.append(defn) + return [] + +class C_Summary(C_SubItem): + + def run(self): + self.assert_has_content() + if self.desc_data.summary: + raise self.warning('Directive "{}" already provided.'.format(self.subitem)) + self.desc_data.summary = self.parse_content() + return [] + +class C_Subsection(C_SubItem): + option_spec = { 'top': directives.flag } + + def run(self): + title, content = self.state.inline_text(self.check_content(), self.lineno) + content += self.parse_content() + if 'top' in self.options: + self.desc_data.top_sections.append((title, content)) + else: + self.desc_data.bottom_sections.append((title, content)) + return [] + +class C_Output(C_SubItem): + valid_in = {'function'} + + def parse_spec(self, spec): + return self.desc_data.outputs, spec, [nodes.literal(text=spec)] + +class C_Param(C_SubItem): + valid_in = {'function','macro'} + + def parse_spec(self, spec): + name = c_name_from_prototype(spec) + return self.desc_data.parameters, spec, [nodes.literal(text=name)] + +class C_Return(C_SubItem): + valid_in = {'function','macro'} + + def run(self): + self.check_subitem_permitted() + if self.desc_data.returns: + raise self.warning('Directive "{}" already provided.'.format(self.subitem)) + self.desc_data.return_type = self.check_content(False) + self.desc_data.returns = self.parse_content() + return [] + +class C_Retval(C_SubItem): + valid_in = {'function','macro'} + + def parse_spec(self, spec): + return self.desc_data.retvals, spec, autolinking_literal(spec) + +class C_Field(C_SubItem): + valid_in = {'struct'} + + def parse_spec(self, spec): + name = c_name_from_prototype(spec) + return self.desc_data.fields, spec, [nodes.literal(text=name)] + +class C_EnumValue(C_SubItem): + valid_in = {'enum'} + add_to_index = True + + def parse_spec(self, spec): + return self.desc_data.values, spec, [nodes.literal(text=spec)] + + def add_to_index(self, spec, node): + m = re.match(r'(\w+)(?:\s*=\s*(.+?))?\Z', spec) + self.add_target_and_index(m.group(1), node) + +class DescriptionData: + def __init__(self, element): + + # The API element object + self.element = element + + # A summary of the API element + self.summary = None + + # A list of member specifications and definitions for an enum element + self.values = [] + # A list of member specifications and definitions for a struct element + self.fields = [] + # A list of parameter specifications and definitions for an API element + self.parameters = [] + # A list of output specifications and definitions for an API element + self.outputs = [] + + # A description of the return value for an API element + self.returns = [] + # The return type for a function or typedef API element + self.return_type = None + # A list of return values and descriptions for an API element + self.retvals = [] + + # Lists of additional description sections + self.top_sections = [] + self.bottom_sections = [] + + @staticmethod + def make_subtitle(title): + if isinstance(title, str): + return nodes.rubric(title, text=title) + else: + t = nodes.rubric(rawsource = nodes_text(title)) + t += title + return t + + @staticmethod + def make_list(elements): + if not elements: + return [] + deflist = nodes.container(nodes_text(elements), classes = ['apisubitem']) + deflist += nodes.definition_list(nodes_text(elements), *elements) + return [deflist] + + def finish_list(self, title_text, elements): + if not elements: + return [] + title = self.make_subtitle(title_text) + return [title] + self.make_list(elements) + + def sort_retvals(self): + # Use the configured ordering to prioritise the return values: + # 1. Return values that aren't a single identifier and aren't + # listed in the configuration. + # 2. Return values that are listed in the configuration. + # 3. Single identifiers that aren't listed in the configuration. + # + # If there is no configured order then output the list in source order + # + # data.retvals[] is a list of (node,value) tuples + # return a list of nodes + + retvals = self.retvals + if not retvals: + return [] + + order = self.element.env.app.config.psa_api_retval_order + if order: + priority = dict(zip(order,range(len(order)))) + def get_priority(spec): + p = priority.get(spec, -1) + if p < 0 and re.match(r'^[A-Z][A-Z0-9_]*$', spec): + p = len(order) + return p + retvals.sort(key=lambda rv: get_priority(rv['spec'])) + + # extract and return the list of sorted nodes + return retvals + + def finish_returns(self): + if not self.return_type and not self.retvals and not self.returns: + return [] + title = self.make_subtitle('Returns') + if self.return_type: + title += nodes.Text(': ') + title += autolinking_literal(self.return_type) + stuff = [title] + self.returns + stuff += self.make_list(self.sort_retvals()) + return stuff + + def finish_subsections(self, sections): + stuff = [] + if sections: + for (title, subsection) in sections: + stuff += [self.make_subtitle(title)] + stuff += subsection + return stuff + + def finish_top(self): + top = self.finish_list('Fields', self.fields) + top += self.finish_list('Values', self.values) + top += self.finish_list('Parameters', self.parameters) + top += self.finish_list('Outputs', self.outputs) + top += self.finish_returns() + top += self.finish_subsections(self.top_sections) + return top + + def finish_bottom(self): + return self.finish_subsections(self.bottom_sections) + +class DescriptionDomain(sphinx.domains.Domain): + """Description of an object in a PSA API document.""" + name = 'psa_description' + label = 'PSA description' + directives = { + 'field': C_Field, + 'output': C_Output, + 'param': C_Param, + 'return': C_Return, + 'retval': C_Retval, + 'value': C_EnumValue, + 'summary': C_Summary, + 'subsection': C_Subsection, + } + directives.update(psa_directives) + roles = psa_roles + + def __init__(self, env, element=None): + super().__init__(env) + self.data['local'] = DescriptionData(element) + +def make_c_target(name): + return 'c.' + name + +class C_Item(SphinxDirective): + """Base class for PSA C API objects. + + Subclasses must define: + + * A field ``kind`` that provides the typeset description of what kind + of object this is (e.g. type, struct, function, ...). + * A method ``prototype(name, desc_data)`` that returns a pair of + (str, node.Element) for the item, used between the header and the content. + """ + has_content = True + required_arguments = 0 + optional_arguments = 1 + option_spec = { + 'name': directives.unchanged, + 'header': directives.unchanged, + 'definition': directives.unchanged, + 'naked': directives.flag, + 'guard': directives.unchanged, + 'comment': directives.unchanged, + } + final_argument_whitespace = True + naked = False + + @staticmethod + def doxy_brief(nodes): + if not nodes: + return '' + s = nodes_text(nodes).replace('\n',' ') + fin = re.search(r'(\D\.\D|\d\.\D|\D\.\d)', s) # . but not x.y + if fin: + s = s[:fin.start() + 2] + return s + + @staticmethod + def doxy_para(intro, text, indent=3): + if text: + return textwrap.wrap(text, 80 - indent, + initial_indent=intro + ' ', + subsequent_indent=' '*(len(intro)+1)) + return [intro] + + @staticmethod + def doxy_comment(lines): + if lines: + c = '/**' + for l in lines: + c += '\n *' + if l: + c += ' ' + l + return c + '\n */\n' + return '' + + def doxy_summary(self): + if not self.desc_data.summary: + return [] + return self.doxy_para('@brief',self.doxy_brief(self.desc_data.summary)) + + def basic_prototype(self): + # Return an unannotated prototype for the API element + raise NotImplementedError + + def prototype(self, doxygen): + # Return an annotated prototype for the API element + # Default implementation prefixes an undecorated prototype with + # a summary of the API element. + # Override if more complex annotation is required + + proto, error = self.basic_prototype() + # Document the basic summary, input parameters, and return values + if doxygen>0: + doxy = self.doxy_summary() + if doxy and doxygen==2: + # Document parameters of the API + if self.desc_data.parameters: + items = [('@param ' + nodes_text(p[0]).strip(), self.doxy_brief(p[1])) + for p in self.desc_data.parameters] + width = max([len(i[0]) for i in items]) + doxy.append('') + for i in items: + doxy.extend(self.doxy_para(i[0].ljust(width),i[1])) + if self.desc_data.returns: + doxy.append('') + doxy.extend(self.doxy_para('@return',self.doxy_brief(self.desc_data.returns))) + if doxy: + proto = '\n' + self.doxy_comment(doxy) + proto + if self.guard: + proto = '#ifndef {}\n{}\n#endif'.format(self.guard, proto) + if self.comment: + lines = textwrap.wrap(self.comment, 80 - 3, + initial_indent='/* ', subsequent_indent=' ') + proto = '\n'.join(lines) + '\n */\n' + proto + return proto, error + + def annotate_members(self, items, item_sep, final_sep, doxygen): + # Annotated prototypes for structs and enums, documenting each member + begin = '{} {} {{'.format(self.kind, self.item_name) + if 'type' in self.options: + begin = 'typedef ' + begin + end = '}} {};'.format(self.item_name) + else: + end = '};' + if not items: + proto = begin + end + else: + subitems = [] + for item in items: + brief = self.doxy_brief(item[1]) if doxygen==2 else '' + if brief: + brief = '\n /// '.join([''] + self.doxy_para('@brief', brief, 8)) + subitems.append(brief + '\n ' + item['spec']) + proto = begin + item_sep.join(subitems) + final_sep + '\n' + end + + if doxygen>0: + doxy = self.doxy_comment(self.doxy_summary()) + if doxy: + return '\n' + doxy + proto, None + return proto, None + + def parse_arguments(self): + # Either the API name must be the first line of argument, or it is + # provided using the `name` option. + # A macro definition can be provided as the subsequent lines of the + # directive argument body (before options), or using the `definition` + # option. + # The header for the API element can be specified as the `header` + # option, if not provided the current header in force for the source + # file will be used. + # Specific API element classes can extend this if required + argument_trail = None + if 'name' in self.options: + self.item_name = self.options['name'] + if self.arguments: + argument_trail = self.arguments[0] + elif self.arguments: + line_end = self.arguments[0].find('\n') + if line_end <= 0: + self.item_name = self.arguments[0] + else: + self.item_name = self.arguments[0][:line_end].rstrip() + argument_trail = self.arguments[0][line_end+1:] + else: + raise self.warning('API name missing for directive "{}"'.format(self.objtype)) + self.definition = self.options.get('definition', argument_trail) + self.header = self.options.get('header') + if 'naked' in self.options: + self.naked = True + self.guard = self.options.get('guard') + self.comment = self.options.get('comment') + if self.naked and self.definition: + raise self.warning('Naked API element "{}" cannot have a definition'.format(self.item_name)) + if self.naked and self.header: + raise self.warning('Naked API element "{}" cannot be added to a header'.format(self.item_name)) + + def add_target_and_index(self, name, node): + # for C API items we add a prefix since names are usually not qualified + # by a module name and so easily clash with e.g. section titles + targetname = make_c_target(name) + node['names'].append(targetname) + if targetname not in self.state.document.ids: + # If unique, use the exact target name as the id, not the output + # from nodes.make_id(). This maintains anchor name compatibility + node['ids'].append(targetname) + node['first'] = False + self.state.document.note_explicit_target(node) + inv = self.env.domaindata['psa_c']['elements'] + if name in inv: + self.state_machine.reporter.warning( + 'duplicate API definition of {}, other instance in {}.'.format( + name, self.env.doc2path(inv[name][0])), line=self.lineno) + inv[name] = (self.env.docname, self.objtype) + + def make_prototypes(self): + protos = [] + if not self.naked: + # construct the standard and annotated prototypes + proto, err = self.prototype(0) + header_proto, _ = self.prototype(self.config.psa_api_header_doxygen) + if err: + protos.append(err) + protos.append(autolinking_literal_block(proto)) + # Add the prototype to the list of API prototypes + self.env.domains['psa_c'].add_prototype(self.item_name, self.header, self.kind, + proto, header_proto, self.env.docname) + return protos + + def run(self): + env = self.env + + self.kind = self.objtype = self.name.split(':')[-1] + + self.parse_arguments() + # Prepare a section + section = nodes.section(raw_source=self.content) + section.document = self.state.document + section['objtype'] = self.objtype + + title_node = nodes.title() + title_node += nodes.literal(text=self.item_name) + title_node += nodes.Text(' ({})'.format(self.kind)) + section += title_node + + self.add_target_and_index(self.item_name, section) + + original_default_domain = env.temp_data['default_domain'] + section_offset = len(section) + desc_domain = DescriptionDomain(env, self) + self.desc_data = desc_domain.data['local'] + description = nodes.Element() + try: + env.temp_data['default_domain'] = desc_domain + self.state.nested_parse(self.content, self.content_offset, description) + finally: + env.temp_data['default_domain'] = original_default_domain + # Insert optional summary paragraph immediately after the title + if self.desc_data.summary: + section += self.desc_data.summary + # Add prototype + section += self.make_prototypes() + # Compile upper description sections + top = self.desc_data.finish_top() + section += top + # Add description + if description.children: + if top: + section.append(self.desc_data.make_subtitle('Description')) + section += description.children + # Add final subsections + section += self.desc_data.finish_bottom() + + # Finished + del desc_domain.data['local'] + return [section] + +class Attribute(C_Item): + naked = True + +class Typedef(C_Item): + def parse_arguments(self): + super().parse_arguments() + if re.match(r'\w+\Z', self.item_name): + self.definition = None + else: + self.definition = self.item_name + self.item_name = c_name_from_prototype(self.definition) + + def basic_prototype(self): + proto = self.definition + if proto is None: + proto = '/*...*/ ' + self.item_name + if not proto.startswith('typedef '): + proto = 'typedef ' + proto + if not proto.endswith(';'): + proto += ';' + return proto, None + +class Macro(C_Item): + version_opt = 'api-version' + option_spec = C_Item.option_spec + option_spec.update({ version_opt: option_choice(version_opt, ('major','minor','hex')) }) + formats = { + 'major': '{0}', + 'minor': '{1}', + 'hex' : '(0x{0:02X}{1:02X}u)' + } + + def basic_prototype(self): + error = None + definition = self.definition + if self.version_opt in self.options: + if definition: + error = self.state_machine.reporter.error( + 'Cannot provide definition for version macro "{}"'.format(self.item_name), + line=self.lineno) + elif self.desc_data.parameters: + error = self.state_machine.reporter.error( + 'Cannot provide arguments for version macro "{}"'.format(self.item_name), + line=self.lineno) + version = self.env.config.version.split()[0] + v = [int(x) for x in version.split('.')] + if len(v) == 1: + v.append(0) + definition = self.formats[self.options[self.version_opt]].format(*v) + elif definition is None: + definition = '/*...*/' + + if self.desc_data.parameters: + params = [p['spec'] for p in self.desc_data.parameters] + args = '(' + ', '.join(params) + ')' + else: + args = '' + # Very crude heuristic for line splitting. Needs work. + if len(self.item_name) + len(args) + len(definition) > 70: + proto = '#define {}{} \\\n {}'.format(self.item_name, args, definition) + else: + proto = '#define {}{} {}'.format(self.item_name, args, definition) + return proto, error + +class Function(C_Item): + option_spec = C_Item.option_spec + option_spec.update({ 'type': directives.flag }) # for function pointer typedefs + + def parse_arguments(self): + super().parse_arguments() + if 'type' in self.options: + self.kind = 'type' + + def basic_prototype(self): + if self.desc_data.return_type: + proto = self.desc_data.return_type + error = None + else: + proto = 'void' + # Issue a non-fatal error if no return type is specified + error = self.state_machine.reporter.warning( + 'No return type for function "{}", assuming void.'.format(self.item_name), + line=self.lineno) + + if 'type' in self.options: + proto = 'typedef ' + proto + ' (* ' + self.item_name + ')(' + self.kind = 'type' + else: + proto += ' ' + self.item_name + '(' + + if self.desc_data.parameters: + sep = ',\n' + ' ' * len(proto) + params = [p['spec'] for p in self.desc_data.parameters] + proto += sep.join(params) + else: + proto += 'void' + proto += ');' + + return proto, error + +class Struct(C_Item): + option_spec = C_Item.option_spec + option_spec.update({ 'type': directives.flag }) # for struct typedefs + + def prototype(self, doxygen): + return self.annotate_members(self.desc_data.fields, ';', ';', doxygen) + +class Enum(C_Item): + option_spec = C_Item.option_spec + option_spec.update({ 'type': directives.flag }) # for enum typedefs + + def prototype(self, doxygen): + return self.annotate_members(self.desc_data.values, ',', '', doxygen) + + +class PSA_C_Index(sphinx.domains.Index): + name = 'identifiers' + localname = 'Index of API elements' + shortname = 'API identifiers' + + @staticmethod + def entry_from_item(item): + return (item['name'], 0, item['docname'], item['target'], '', '', '') + + @staticmethod + def split_buckets(buckets, max_bucket_size): + """Split a bucket dictionary into smaller buckets. + + ``buckets`` is a dictionary whose keys are strings and whose values + are lists that satisfy the property that for each element ``entry`` in + ``buckets[key]``, ``key`` is a prefix of ``entry[0].upper()``. + + This function looks for keys whose value is a list of more than + ``max_bucket_size`` elements and splits the corresponding list by + moving each element to a key with a longer prefix. + """ + did_something = True + while did_something: + did_something = False + for key, entries in list(buckets.items()): + if len(buckets[key]) <= max_bucket_size: + continue + del buckets[key] + did_something = True + length = len(key) + 1 + for entry in entries: + new_key = entry[0].upper()[:length] + buckets.setdefault(new_key, []).append(entry) + + def collect_buckets(self): + entries = [] + for name, (doc, _) in list(self.domain.data['elements'].items()): + entries.append( (name, 0, doc, make_c_target(name), '', '', '') ) + if not entries: + return {} + + buckets = {'': entries} + if len(entries) >= 10: + self.split_buckets(buckets, len(entries) / 2) + return buckets + + def generate(self, docnames=None): + buckets = self.collect_buckets() + if not buckets: + return [], False + content = [(bucket, sorted(buckets[bucket])) + for bucket in sorted(buckets.keys())] + return content, False + +def option_list(option): + def option_check(argument): + if isinstance(argument, str): + return argument.replace(',',' ').split() + raise ValueError(':{}: must be a list of headers.'.format(option)) + + return option_check + +class Header(SphinxDirective): + required_arguments = 1 + final_argument_whitespace = True + option_spec = { + 'seq': directives.nonnegative_int, + 'guard': directives.unchanged, + 'include': option_list('include'), + 'system-include': option_list('system-include'), + 'c++': directives.flag, + 'copyright': directives.unchanged, + 'license': directives.unchanged, + } + has_content = True + + def run(self): + header = self.arguments[0].strip() + self.env.temp_data['header_file'] = header + self.env.temp_data['header_seq'] = self.options.pop('seq',0) + opt = self.options.copy() + if 'guard' in opt and not opt['guard']: + opt['guard'] = re.sub('[^a-zA-Z0-9]','_',header.upper()) + '_H' + opt['preamble'] = '\n'.join(self.content.data) + + if self.content.data or len(self.options) > 0: + collision = self.env.domains['psa_c'].add_header(header, opt) + if collision: + return [self.state_machine.reporter.error( + 'Duplicate header "{}" defined in source "{}".'.format( + header, collision), line = self.lineno)] + + return [] + +class header_node(nodes.General, nodes.Element): + pass + +class InsertHeader(SphinxDirective): + required_arguments = 1 + final_argument_whitespace = True + + def run(self): + # The generated header will depend on any source files with API elements + # in the header, and a source file that defines any adornments for the + # header. Tracking these does not ensure correct detection of every + # situation where this directive to be re-processed. The simplest + # reliable approach is to force this source file to always be re-read. + self.env.note_reread() + return [header_node('', header=self.arguments[0].strip())] + +def process_header_nodes(app, doctree, docname): + for node in doctree.traverse(header_node): + header = node['header'] + text = app.builder.env.domains['psa_c'].prototype_header(header, notice=False, doxy=False, db=False) + if not text: + logger.warning('Cannot insert header with no content: "%s"', + header, location=node) + inline_error = nodes.inline('', 'Header "{}" has no content'.format(header), + classes = ['issue']) + node.replace_self(inline_error) + else: + h = autolinking_literal_block(text) + node.replace_self(resolve_references(h, docname, app)) + +ApiElement = namedtuple('ApiElement', 'seq name type prototype annotated') + +class PSA_C_Domain(sphinx.domains.Domain): + """C language domain for PSA.""" + name = 'psa_c' + label = 'PSA C' + directives = { + 'enum': Enum, + 'function': Function, + 'macro': Macro, + 'struct': Struct, + 'typedef': Typedef, + 'attribute': Attribute, + 'header': Header, + 'insert-header': InsertHeader, + 'template-image': TemplateImage, + 'title': TitlePage, + 'front-matter': FrontMatter, + 'maintoc': MainToc, + 'appendix': Appendix, + 'about': About, + 'insert-section': InsertFrontSection, + 'banner': Banner, + 'insert-banner': InsertBanner, + 'include-license': IncludeLicense, + 'release': Release, + 'release-table': ReleaseTable, + 'reference': Reference, + 'reference-table': ReferenceTable, + 'term': Term, + 'scterm': Term, + 'abbr': Term, + 'term-table': TermTable, + 'threat': Threat, + } + directives.update(psa_directives) + roles = psa_roles + indices = [PSA_C_Index] + initial_data = { + 'prototypes': {}, # header -> { docname: [ApiElement] } + 'elements': {}, # name -> docname, objtype + 'headers': {}, # header -> docname, options + 'front-matter': {}, # section -> docname, content + 'cite': {}, # citeref -> docname, reference + 'term': {}, # term -> docname, definition + 'release': {}, # seq_id -> docname, release + 'sra': {}, # sra.id -> docname, None + } + + def clear_doc(self, docname): + for set in self.initial_data.keys(): + d = self.data[set] + if set == 'prototypes': + for header, elements in list(d.items()): + if docname in elements: + del elements[docname] + if not elements: + del d[header] + else: + for key, (fn, _) in list(d.items()): + if fn == docname: + del d[key] + + def merge_domaindata(self, docnames, otherdata): + for set in ('elements','headers','front-matter'): + d = self.data[set] + for key, (fn, data) in d.items(): + if fn in docnames: + d[key] = (fn, data) + + #if 'prototypes' in otherdata: + # self.data['prototypes'].update(otherdata['prototypes']) + + def get_objects(self): + for refname, (docname, type) in list(self.data['elements'].items()): + yield (refname, refname, type, docname, make_c_target(refname), 1) + + def add_prototype(self, name, header, type, prototype, annotated, docname): + if header is None: + header = self.env.temp_data.get('header_file', + self.env.config.psa_api_c_header) + seq = self.env.temp_data.get('header_seq',0) + sig = ApiElement(seq, name, type, prototype, annotated) + self.data['prototypes'].setdefault(header,{}).setdefault(docname,[]).append(sig) + + def sequenced_prototypes(self, header): + # Order the prototypes according to source sequence numbers. + # Sphinx partial rebuild results in variation in the order of + # the prototypes in the primary list. + # + p = sum(self.data['prototypes'].get(header,{}).values(),[]) + p.sort(key = attrgetter('seq')) + return p + + def sorted_prototypes(self, header): + # Sort the prototypes to enable accurate diffing of the API between + # versions of the documentation source code. Sphinx partial rebuild + # results in variation in the order of the prototypes in the primary + # list. + # + # The current ordering is by: + # - type, reversed so typedef, then macro, then function + # - API name + # + p = sum(self.data['prototypes'].get(header,{}).values(),[]) + p.sort(key = attrgetter('name')) + p.sort(key = attrgetter('type'), reverse = True) + return p + + def add_header(self, header, options): + # stash the extra header data + headers = self.data['headers'] + if header in headers: + return headers[header][0] + headers[header] = (self.env.docname, options) + return None + + def get_header_options(self, header): + h = self.data['headers'].get(header) + return h[1] if h else {} + + def prototype_header(self, header, notice, doxy, db): + # Output a string with the content for a specific header file + # If the header has no content then return None + if not header in self.data['prototypes']: + return None + + h = self.get_header_options(header) + lines = [] + postamble = [] + if notice: + # REUSE-IgnoreStart + if 'copyright' in h: + lines.append('// SPDX-FileCopyrightText: {}'.format(h['copyright'])) + if 'license' in h: + lines.append('// SPDX-License-Identifier: {}'.format(h['license'])) + # REUSE-IgnoreEnd + if lines: + lines.append('') + if not db: + if 'preamble' in h: + lines.extend([h['preamble'],'']) + guard = h.get('guard') + if guard: + lines.extend( ['#ifndef {}'.format(guard), '#define {}'.format(guard), ''] ) + postamble = ['','#endif // {}'.format(guard)] + includes = h.get('system-include',[]) + if includes: + for inc in includes: + lines.append('#include <{}>'.format(inc)) + lines.append('') + includes = h.get('include',[]) + if includes: + for inc in includes: + lines.append('#include "{}"'.format(inc)) + lines.append('') + if 'c++' in h: + lines.extend( ['#ifdef __cplusplus', 'extern "C" {', '#endif', ''] ) + postamble[0:0] = ['', '#ifdef __cplusplus', '}', '#endif'] + + if db: + apis = self.sorted_prototypes(header) + else: + apis = self.sequenced_prototypes(header) + if doxy and not db: + lines += [api.annotated for api in apis] + else: + lines += [api.prototype for api in apis] + lines.extend(postamble) + + return '\n'.join(lines) + '\n' + + def output_prototypes(self, outdir, format): + # Output the prototypes as C header files into the path at `outdir` + # * for 'api-ref' include annotaton, and use source sequencing + # * for 'api-db' strip annotations, and use identifier order + db = (format == 'api-db') + os.makedirs(outdir, exist_ok=True) + clean_dir(outdir) + for h in self.data['prototypes'].keys(): + fn = os.path.join(outdir, h + '.h') + os.makedirs(os.path.dirname(fn), exist_ok=True) + sig_file = open(fn, 'w', encoding='utf-8') + sig_file.write(self.prototype_header(h, notice=True, doxy=True, db=db)) + + + def resolve_any_xref(self, env, fromdocname, builder, target, + node, contnode): + # Check if this is a citation reference + m = re.match(r'\[([a-zA-Z0-9][-a-zA-Z0-9_. ]+)\]',target) + if m: + refnode = self.resolve_citeref(env, fromdocname, builder, 'cite', m.group(1), contnode) + if refnode: + return [('psa_c:cite', refnode)] + + m = re.match(r'(DM|AM|SG|T|M)\.[-a-zA-Z0-9+_.]+',target) + if m: + refnode = self.resolve_sraref(env, fromdocname, builder, target, contnode) + if refnode: + return [('psa_c:' + m.group(1).lower(), refnode)] + + # strip trailing parens, and check if an API item + refnode = self.resolve_apiref(env, fromdocname, builder, target, node, contnode) + if refnode: + return [('psa_c:ref', refnode)] + return [] + + # Weak reference: turn into a reference if the target is available, + # and keep as-is otherwise. + def resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + type = typ.split(':')[-1] + if type in ('cite', 'cite-title'): + return self.resolve_citeref(env, fromdocname, builder, type, target, contnode) + if type in ('term', 'scterm'): + return self.resolve_termref(env, fromdocname, builder, type, target, contnode) + if type == 'secref': + return self.resolve_secref(env, fromdocname, builder, type, target, node, contnode) + if type in ('numref', '*'): + return self.resolve_numref(env, fromdocname, builder, type, target, node, contnode) + if type in ('am', 'dm', 'sg', 't', 'm'): + return self.resolve_sraref(env, fromdocname, builder, + canonical_sra_id(type, target), contnode) + + return self.resolve_apiref(env, fromdocname, builder, target, node, contnode) + + # Try and resolve an API reference + def resolve_apiref(self, env, fromdocname, builder, + target, node, contnode): + # strip trailing parens + target = target.rstrip('()') + if target not in self.data['elements']: + return None + obj = self.data['elements'][target] + return make_refnode(builder, fromdocname, obj[0], make_c_target(target), + contnode, target) + + def resolve_secref(self, env, fromdocname, builder, + typ, target, node, contnode): + # resolve a section reference - use title for link text + # and add a latex pageref + # use the standard resolver to do the label lookup + e = env.domains['std'].resolve_xref(env, fromdocname, builder, + 'ref', target.lower(), node, contnode) + if not e: + contnode['classes'] = ['issue','secref'] + else: + e[0]['classes'] = ['secref'] + if 'refuri' in e: + id = e['refuri'][1:].replace('#', ':') + else: + id = fromdocname + ':' + e['refid'] + e += latexpageref(id) + return e + + def resolve_numref(self, env, fromdocname, builder, + typ, target, node, contnode): + # resolve a number reference + e = env.domains['std'].resolve_xref(env, fromdocname, builder, + 'numref', target.lower(), node, contnode) + if not (e and isinstance(e, nodes.reference)): + contnode['classes'] = ['issue','numref'] + return e + + # number_reference nodes are stripped in the latex writer + # so need to return a composite node which includes the number_refernence + # followed by the latex page-ref node + e[0]['classes'] = ['numref'] + n = nodes.inline('') + n += e + if 'refuri' in e: + id = e['refuri'][1:].replace('#', ':') + else: + id = fromdocname + ':' + e['refid'] + n += latexpageref(id) + return n + + def resolve_citeref(self, env, fromdocname, builder, + typ, target, contnode): + # resolve a citation reference (with optional title inclusion) + target = canonical_rfc(target) + docname, id, title = ReferenceTable.resolve_ref(env, target.lower()) + if not docname: + # If the xref cannot resolve, Sphinx writes the contnode - so + # make it show up as an issue in the output + contnode['classes'].append('issue') + return None + contnode = nodes.inline('', nodes_text(contnode), classes = ['cite']) + refnode = make_refnode(builder, fromdocname, docname, id, contnode) + if typ == 'cite-title' and title: + n = nodes.inline(nodes_text(contnode)) + n += nodes.emphasis('', *title) + n += nodes.Text(' ') + n += refnode + return n + return refnode + + def resolve_termref(self, env, fromdocname, builder, + typ, target, contnode): + # resolve a term reference + docname, label = TermTable.resolve_ref(env, target.lower()) + contnode['classes'].append(typ) + if not docname: + # If the xref cannot resolve, Sphinx writes the contnode - so + # make it show up as an issue in the output + contnode['classes'].append('issue') + return None + return make_refnode(builder, fromdocname, docname, label, contnode) + + def resolve_sraref(self, env, fromdocname, builder, + target, contnode): + key = nodes.make_id(target) + docname, label = SRADefinition.resolve_ref(env, key) + if not docname: + # If the xref cannot resolve, make it show up as an issue in the output + contnode['classes'].extend(['issue']) + return None + + n = nodes.inline(nodes_text(contnode), label, classes=['sraref']) + return make_refnode(builder, fromdocname, docname, 'sra-' + key, n) + +class BuildAPI(sphinx.builders.Builder): + def get_outdated_docs(self): + return 'api' + + def prepare_writing(self, docnames): + return + + def get_target_uri(self, docname, typ = None): + return docname + + # The API prototype output is independent of the structure of the source + # documents. So no output is generated for each updated source doc, instead + # the entire prototype API is output during the `finish()` method. + def write_doc(self, docname, doctree): + return + + def finish(self): + self.env.domains['psa_c'].output_prototypes(self.app.outdir, self.name) + +class API_db(BuildAPI): + name = 'api-db' + +class API_ref(BuildAPI): + name = 'headers' + +def alpha_section(secnum): + if not secnum or len(secnum) == 0: + return secnum + + secnum = list(secnum) + return tuple([string.ascii_uppercase[secnum[0]-1]] + secnum[1:]) + +def rewrite_section_numbers(env): + # This relies on the callbacks running _after_ the built-in + # TocTreeCollector has run, rewriting the section numbers to + # use Alphabetic section numbers for chapters in the Appendix + + def _walk_toc(node, alpha_sec=False): + for subnode in node.children: + if isinstance(subnode, (nodes.bullet_list, nodes.list_item, addnodes.only)): + _walk_toc(subnode, alpha_sec) + elif isinstance(subnode, addnodes.compact_paragraph) and alpha_sec: + reference = cast(nodes.reference, subnode[0]) + reference['secnumber'] = alpha_section(reference['secnumber']) + elif isinstance(subnode, addnodes.toctree): + _walk_toctree(subnode) + + def _walk_toctree(toctreenode: addnodes.toctree, alpha_sec=False) -> None: + for (_, ref) in toctreenode['entries']: + if ref in env.tocs: + _walk_toc(env.tocs[ref], alpha_sec) + + if alpha_sec: + env.toc_secnumbers[ref] = {k: alpha_section(v) + for k, v in env.toc_secnumbers[ref].items()} + + for docname in env.numbered_toctrees: + doctree = env.get_doctree(docname) + for toctreenode in doctree.traverse(addnodes.toctree): + alpha_sec = toctreenode.get('alpha_numbers', False) + _walk_toctree(toctreenode, alpha_sec) + + return [] + +def assign_figure_numbers(env): + # Rassign a figure number to each figure under a numbered toctree. + # But using the alpha section labels for appendices + # Each doc either has alpha chapters or number chapters (maintoc vs appendix) + # For each alpha-docs, accumulate the minimum fig index per fig-section + # Then rewrite the fignumbers using an alpha chapter, and reducing the index as required. + + if env.config.numfig and env.config.numfig_secnum_depth > 0: + alphadocs = [] + figoffset = {} + for docname, secnums in env.toc_secnumbers.items(): + if type(secnums[""][0]) is not int and docname in env.toc_fignumbers: + # This document is in an alpha-sectioned chapter + alphadocs.append(docname) + for kind, fignums in env.toc_fignumbers[docname].items(): + offsets = figoffset.setdefault(kind,{}) + for fignum in fignums.values(): + offsets[fignum[:-1]] = min(offsets.get(fignum[:-1],999), fignum[-1] - 1) + for docname in alphadocs: + for kind, fignums in env.toc_fignumbers[docname].items(): + for id, fignum in fignums.items(): + figbase = fignum[:-1] + fignums[id] = alpha_section(figbase) + (fignum[-1] - figoffset[kind][figbase],) + + return [] + +def apply_alpha_sections(app, env): + return rewrite_section_numbers(env) + assign_figure_numbers(env) + +def note_dependency(app, doctree): + # Rebuild everything when this extension's code changes. + app.env.note_dependency(__file__) + +def process_doctree_resolved(app, doctree, docname): + process_banner_nodes(app, doctree, docname) + process_header_nodes(app, doctree, docname) + +def setup(app): + # This version of the extension depends on table support only added in v5.3 + app.require_sphinx('5.3') + + app.add_config_value('psa_api_c_header', 'prototypes', 'env') + app.add_config_value('psa_api_tool_path', '', 'env') + app.add_config_value('psa_api_template_path', '', 'env') + app.add_config_value('psa_api_license', 'missing', 'env') + app.add_config_value('psa_api_retval_order', [], 'env') + app.add_config_value('psa_api_header_doxygen', 0, 'env') + app.add_config_value('psa_api_front_sections', [], 'env') + # This should be triggered on 'config-inited', but that event doesn't + # exist before Sphinx 1.8. + app.add_node(banner) + app.add_node(header_node) + app.connect('doctree-read', note_dependency) + app.connect('doctree-resolved', process_doctree_resolved) + app.connect('env-get-updated', apply_alpha_sections, 600) + + app.add_domain(PSA_C_Domain) + app.add_domain(DescriptionDomain) + app.add_builder(API_db) + app.add_builder(API_ref) + FrontSection.add_directives(app) + + return { + 'version': '1.0', + 'env_version': 14, + 'parallel_read_safe': False, # Can only verify this on Linux, not supported on MacOS or Windows + 'parallel_write_safe': True, + } diff --git a/tools/puml/psa-dataflow.pumh b/tools/puml/psa-dataflow.pumh new file mode 100644 index 00000000..33c32c26 --- /dev/null +++ b/tools/puml/psa-dataflow.pumh @@ -0,0 +1,29 @@ +' SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +!define dfd_process storage +!define dfd_store database +!define dfd_agent rectangle +!procedure dfd_tb($type = "trust boundary", $label="") + skinparam rectangle { + backgroundColor<<$type>> #FFFFFF90 + borderColor<<$type>> darkred + borderstyle<<$type>> dashed + borderThickness<<$type>> 1 + fontColor<<$type>> black + } + !if ($label == "") + rectangle <<$type>> + !else + rectangle "$label" <<$type>> + !endif +!endprocedure +!procedure dfd_align($type = "i b") + skinparam rectangle { + backgroundColor<<$type>> none + borderColor<<$type>> none + borderThickness<<$type>> 1 + fontColor<<$type>> none + } + rectangle <<$type>> +!endprocedure diff --git a/tools/puml/psa-lifecycle.pumh b/tools/puml/psa-lifecycle.pumh new file mode 100644 index 00000000..fcc20ac8 --- /dev/null +++ b/tools/puml/psa-lifecycle.pumh @@ -0,0 +1,21 @@ +' SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +skinparam rectangle { + backgroundColor #PSAMidBlue + borderThickness 0 + fontColor white +} + +skinparam card { + backgroundColor #0091BD20 + borderThickness 0 + fontColor black +} + +!define lifecycle_phase card +!define lifecycle_state rectangle +!define stakeholder note +!define stakeholder_skip label " " + +left to right direction diff --git a/tools/puml/psa-spec.pumh b/tools/puml/psa-spec.pumh new file mode 100644 index 00000000..42e6298e --- /dev/null +++ b/tools/puml/psa-spec.pumh @@ -0,0 +1,191 @@ +' SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +hide footbox +hide empty description +skinparam { + style strictuml + + defaultFontName roboto + defaultFontSize 12 + defaultMonospacedFontName roboto mono + + ArrowColor Black + + BackgroundColor ffffff55 + + RoundCorner 8 +} + +!define PSAGreen 95D600 +!define PSAMidBlue 0091BD +!define PSADarkBlue 002B49 +!define PSAMidGray 7D868C +!define PSALightGray E5ECEB + +skinparam legend { + backgroundColor e8e8e8 + fontSize 12 +} + +skinparam sequenceLifeLine { + backgroundColor White + borderColor Gray + borderThickness 1 +} + +skinparam sequenceGroup { + bodyBackgroundColor #ffffff80 + borderColor Gray + borderThickness 1 + backgroundColor #f0f0f0 + headerFontStyle normal + fontSize 11 +} + +skinparam sequenceBox { + borderColor none + backgroundColor #e0e0e0c0 + borderThickness 1 +} + +skinparam note { + backgroundColor #e8e8e8 + borderColor PSAMidGray + borderThickness 1 +} + +skinparam participant { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam actor { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam boundary { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam control { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam database { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam entity{ + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam collections { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam queue { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam storage { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam rectangle { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam interface { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam condition { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam state { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam usecase { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam cloud { + backgroundColor #White + borderColor PSAMidGray + borderThickness 1 +} + +skinparam component { + backgroundColor #White + borderColor Black + borderThickness 1 +} + +skinparam node { + backgroundColor #White + borderColor PSAMidGray + borderThickness 1 +} + +skinparam frame { + backgroundColor #White + borderColor PSAMidGray + borderThickness 1 +} + +skinparam folder { + backgroundColor #White + borderColor PSAMidGray + borderThickness 1 +} + +skinparam ConditionEndStyle hline + +skinparam partition { + borderThickness 0 + borderColor f0f0f0 + backgroundColor #f0f0f0 + fontSize 11 +} + +skinparam activity { + backgroundColor White + borderColor Black + borderThickness 1 +} + +skinparam card { + backgroundColor White + borderColor Black + borderThickness 1 +} diff --git a/tools/rewrite-html-for-jekyll.py b/tools/rewrite-html-for-jekyll.py new file mode 100644 index 00000000..3b2e7b27 --- /dev/null +++ b/tools/rewrite-html-for-jekyll.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +# SPDX-License-Identifier: Apache-2.0 + +import glob, re + +files = [] +for f in glob.glob('**/*.html', recursive=True): + files.append(f) + +url_property = r'meta property="og:url" content="{{ site.url }}[^"]*"\s*/>' +url_replace = r'meta property="og:url" content="{{ page.url | absolute_url }}" />\n<link rel="canonical" href="{{ page.url | absolute_url }}" />' + +url_re = re.compile(url_property) + +for file in files: + with open(file, encoding='utf-8') as f: + text = f.read() + if re.search(url_re, text): + text = "---\n---\n\n" +re.sub(url_re, url_replace, text) + print("{}: update for Jekyll processing".format(file)) + with open(file, "w", encoding='utf-8') as f: + f.write(text) diff --git a/tools/templates/psa-api-2022/Arm_logo_blue_RGB.pdf b/tools/templates/psa-api-2022/Arm_logo_blue_RGB.pdf new file mode 100644 index 00000000..15fb0b55 Binary files /dev/null and b/tools/templates/psa-api-2022/Arm_logo_blue_RGB.pdf differ diff --git a/tools/templates/psa-api-2022/Arm_logo_blue_RGB.pdf.license b/tools/templates/psa-api-2022/Arm_logo_blue_RGB.pdf.license new file mode 100644 index 00000000..8c3078e2 --- /dev/null +++ b/tools/templates/psa-api-2022/Arm_logo_blue_RGB.pdf.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/tools/templates/psa-api-2022/Arm_logo_blue_RGB.svg b/tools/templates/psa-api-2022/Arm_logo_blue_RGB.svg new file mode 100644 index 00000000..4cdf634c --- /dev/null +++ b/tools/templates/psa-api-2022/Arm_logo_blue_RGB.svg @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Layer_1" + x="0px" + y="0px" + viewBox="0 0 236.5 72.099998" + xml:space="preserve" + sodipodi:docname="Arm_logo_blue_RGB.svg" + width="236.5" + height="72.099998" + inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"><metadata + id="metadata11"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs9" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1022" + inkscape:window-height="690" + id="namedview7" + showgrid="false" + inkscape:zoom="1.3156566" + inkscape:cx="118" + inkscape:cy="36.1" + inkscape:window-x="0" + inkscape:window-y="1102" + inkscape:window-maximized="0" + inkscape:current-layer="Layer_1" /> +<style + type="text/css" + id="style2"> + .st0{fill:#0091BD;} +</style> +<path + class="st0" + d="M 53.4,1.9 H 69.3 V 70 H 53.4 V 62.9 C 46.4,71 37.9,72.1 33,72.1 12,72.1 0,54.6 0,35.9 0,13.7 15.2,0.1 33.2,0.1 c 5,0 13.8,1.3 20.2,9.7 z M 16.2,36.2 c 0,11.8 7.4,21.7 18.9,21.7 10,0 19.3,-7.3 19.3,-21.5 0,-14.9 -9.2,-22 -19.3,-22 -11.5,0 -18.9,9.7 -18.9,21.8 z M 88.1,1.9 H 104 V 8 c 1.8,-2.1 4.4,-4.4 6.6,-5.7 3.1,-1.8 6.1,-2.3 9.7,-2.3 3.9,0 8.1,0.6 12.5,3.2 l -6.5,14.4 c -3.6,-2.3 -6.5,-2.4 -8.1,-2.4 -3.4,0 -6.8,0.5 -9.9,3.7 -4.4,4.7 -4.4,11.2 -4.4,15.7 V 69.9 H 88 v -68 z m 54.9,0 h 15.9 v 6.3 c 5.3,-6.5 11.6,-8.1 16.8,-8.1 7.1,0 13.8,3.4 17.6,10 5.7,-8.1 14.2,-10 20.2,-10 8.3,0 15.5,3.9 19.4,10.7 1.3,2.3 3.6,7.3 3.6,17.2 V 70.1 H 220.6 V 32.6 c 0,-7.6 -0.8,-10.7 -1.5,-12.1 -1,-2.6 -3.4,-6 -9.1,-6 -3.9,0 -7.3,2.1 -9.4,5 -2.8,3.9 -3.1,9.7 -3.1,15.5 V 70.1 H 181.6 V 32.6 c 0,-7.6 -0.8,-10.7 -1.5,-12.1 -1,-2.6 -3.4,-6 -9.1,-6 -3.9,0 -7.3,2.1 -9.4,5 -2.8,3.9 -3.1,9.7 -3.1,15.5 V 70.1 H 143 Z" + id="path4" /> +</svg> diff --git a/tools/templates/psa-api-2022/Arm_logo_blue_RGB.svg.license b/tools/templates/psa-api-2022/Arm_logo_blue_RGB.svg.license new file mode 100644 index 00000000..8c3078e2 --- /dev/null +++ b/tools/templates/psa-api-2022/Arm_logo_blue_RGB.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/tools/templates/psa-api-2022/about-chapter.rst b/tools/templates/psa-api-2022/about-chapter.rst new file mode 100644 index 00000000..e2592aa8 --- /dev/null +++ b/tools/templates/psa-api-2022/about-chapter.rst @@ -0,0 +1,133 @@ +.. SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +.. SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +.. role:: anchor + +.. _about-this-document: + +=================== +About this document +=================== + +.. insert-section:: Release information + :section: release-info + :break-after: + + The change history table lists the changes that have been made to this document. + + .. release-table:: Document revision history + +.. only:: include_todo + + .. insert-section:: TODO items + :section: todos + :break-after: + :not-in-toc: + + The following items are marked up as TODO in the document source: + + .. todolist:: + +.. insert-section:: |docfulltitle| + :not-in-toc: + + Copyright © |doccopyright|. The copyright statement reflects the fact that some + draft issues of this document have been released, to a limited circulation. + +.. include-license:: + +.. _license: + +.. insert-section:: License + :section: license + :class: license + :break-after: + +.. insert-section:: References + :section: references + + This document refers to the following documents. + + .. reference-table:: Documents referenced by this document + + +.. insert-section:: Terms and abbreviations + :section: terms + + This document uses the following terms and abbreviations. + + .. term-table:: Terms and abbreviations + :sorted: + +.. insert-section:: Potential for change + :section: potential-for-change + + The contents of this specification are subject to change. + + In particular, the following may change: + + * Feature addition, modification, or removal + * Parameter addition, modification, or removal + * Numerical values, encodings, bit maps + +.. insert-section:: Conventions + :section: conventions + + .. insert-section:: Typographical conventions + + The typographical conventions are: + + *italic* + Introduces special terminology, and denotes citations. + + ``monospace`` + Used for assembler syntax descriptions, pseudocode, and source code examples. + + Also used in the main text for instruction mnemonics and for references to + other items appearing in assembler syntax descriptions, pseudocode, and + source code examples. + + :sc:`small capitals` + Used for some common terms such as :sc:`implementation defined`. + + Used for a few terms that have specific technical meanings, and are included + in the *Terms and abbreviations*. + + :issue:`Red text` + Indicates an open issue. + + :anchor:`Blue text` + Indicates a link. This can be + + * A cross-reference to another location within the document + * A URL, for example :url:`example.com` + + .. insert-section:: Numbers + + Numbers are normally written in decimal. Binary numbers are preceded by 0b, and + hexadecimal numbers by ``0x``. + + In both cases, the prefix and the associated value are written in a monospace + font, for example ``0xFFFF0000``. To improve readability, long numbers can be + written with an underscore separator between every four characters, for example + ``0xFFFF_0000_0000_0000``. Ignore any underscores when interpreting the value of + a number. + +.. insert-section:: Current status and anticipated changes + :section: current-status + +.. _feedback: + +.. insert-section:: Feedback + :section: feedback + + We welcome feedback on the PSA Certified API documentation. + + If you have comments on the content of this book, visit :url:`github.com/arm-software/psa-api/issues` to create a new issue at the PSA Certified API GitHub project. Give: + + * The title (|docfulltitle|). + * The number and issue (|docid| |docrelease|). + * The location in the document to which your comments apply. + * A concise explanation of your comments. + + We also welcome general suggestions for additions and improvements. diff --git a/tools/templates/psa-api-2022/html-static/custom.css b/tools/templates/psa-api-2022/html-static/custom.css new file mode 100644 index 00000000..9b3d5ac1 --- /dev/null +++ b/tools/templates/psa-api-2022/html-static/custom.css @@ -0,0 +1,451 @@ +/* SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited */ +/* SPDX-License-Identifier: Apache-2.0 */ + +/* CSS overrides for the html output */ + +body { + font-family: Lato, sans-serif; + font-size: 15px; + color: #1F2123; +} + +div.document { + width: 965px; +} + +div.bodywrapper { + margin: 0 0 0 270px; +} + +div.body { + background-color: unset; + color: #1F2123; + padding: 0; +} + +div.sphinxsidebar { + width: 240px; + font-size: 12.5px; + margin-top: -30px; +} + +div.sphinxsidebarwrapper { + padding: 0; +} + +div.sphinxsidebarwrapper p.logo { + margin: 0; +} + +div.footer { + width: 965px; +} + +div.watermark { + position: fixed; + width: 660px; + height: 100%; + z-index: -999; + pointer-events: none; + } + +div.watermark p { + color: #EEE; + font-size: 160px; + pointer-events: none; + user-select: none; + margin: 0px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(-45deg); + } + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Lato, sans-serif; + font-weight: bold; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Lato, sans-serif; + font-size: 20px; +} + +div.body input, div.sphinxsidebar input { + font-family: Lato, sans-serif; + font-size: 15px; +} + +a { + color: #0091BD; + text-decoration: none; +} + +a.reference { + border-bottom: none; +} + +a.reference:hover { + color: #00617E; + border-bottom: 1px solid #00617E; + background: unset; +} + +tt, code { + font-family: Inconsolata, monospace; + font-size: 93%; + background-color: unset; + color: #101010; +} + +code.xref, a code { + font-weight: normal; + color: #0091BD; + background-color: unset; + border-bottom: unset; +} + +pre { + font-family: Inconsolata, monospace; + font-size: 93%; + color: #101010; + background: rgba(187,187,187,0.12); + padding: 5px; + margin: 10px -6px; + line-height: 1.3em; + border-style: solid; + border-width: 1px; + border-radius: 5px; + border-color: rgba(187,187,187,0.3); +} + +/* Fix the specific overrides in basic.css and alabaster.css for code blocks */ +div.highlight pre, dl pre, blockquote pre, li pre { + padding: 5px; + margin: 10px -6px; +} + +sub { + font-size: 70%; + vertical-align: -10%; + font-weight: bold; +} + +sup { + font-size: 70%; + vertical-align: 25%; + font-weight: bold; +} + +/* Default table formatting is like 'booktabs', 'standard' formatting can + can be specified explicitly. + */ + + table.docutils { + font-size: unset; + box-shadow: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + /*width: 100%;*/ + border: 0; + border-top: 1px solid #AAA; + border-bottom: 1px solid #AAA; + margin-bottom: 1em; +} + +table.docutils.align-left { + margin-left: 0; + margin-right: auto; +} + +table.docutils.align-right { + margin-left: auto; + margin-right: 0; +} + +table.docutils.standard { + border: 1px solid #AAA; +} + +table.docutils.borderless { + border: 0; +} + +table.docutils.borderless.titletable { + margin-top: 40px; + width: 70%; + margin-right: 0px; + margin-left: auto; +} + +table.docutils caption, div.figure p.caption, figcaption { + font-size: 90%; + text-align: right; + margin-right: -13px; /* move permalink into margin */ +} + +table.docutils caption span.caption-number, div.figure p.caption span.caption-number, figcaption span.caption-number { + font-weight: bold; + font-style: normal; +} + +table.docutils td, table.docutils th { + border: 0; + padding: 0.3em 0.5em 0.3em 0.5em; + text-align: left; + vertical-align: top; +} + +/* Add all rules for standard table formatting */ +table.docutils.standard td, table.docutils.standard th { + border: 1px solid #AAA; +} + +table.docutils td p, table.docutils th p { + margin-block-start: .3em; + margin-block-end: .3em; +} + +table.docutils th > p:first-child, table.docutils td > p:first-child { + margin-top: 0px; +} + +table.docutils th { + font-weight: bold; + font-size: 90%; +} + +/* rule below stub rows */ +table.docutils th.stub { + border-bottom: none; +} + +table.docutils.standard th.stub { + border-bottom: 1px solid #AAA; +} + +/* rule below header rows */ +table.docutils th.head { + border-bottom: 0; +} + +table.docutils tr:last-child > th.head { + border-bottom: 1px solid #AAA; +} + +table.docutils.borderless tr:last-child > th.head { + border-bottom: 0; +} + +table.docutils.standard th.head { + border-bottom: 1px solid #AAA; +} + +table.docutils.borderless th.head { + border-bottom: 0; +} + +figure { + margin-inline-start: 0px; + margin-inline-end: 0px; +} + +div.figure div.legend, figcaption div.legend { + font-size: 90%; /* adds to the figcaption scaling */ + text-align: center; +} + +div.figure p.caption, div.figure div.legend p, figcaption p, figcaption div.legend { + margin-block-start: 0.2em; + margin-block-end: 0.5em; +} + +div.admonition { + margin: 5px 15px; + padding: 5px 15px; + background-color: unset; + border: 0; + border-left: 6px solid #DDD; +} + +div.admonition p.admonition-title { + font-family: Lato, sans-serif; + font-weight: bold; + font-size: 90%; +} + +p.admonition-title:after { + content: ""; +} + +div.admonition p { + margin-top: 0; + margin-bottom: 5px; +} + +div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { + background-color: unset; +} + +div.highlight { + background-color: unset; +} + +div.admonition div.highlight pre { + background-color: unset; +} + +div.warning { + border-left: 6px solid #FBB; +} + +div.warning p.admonition-title { + color: darkred; +} + +div.banner { + border: 2px solid #CC0; + background-color: rgba(255,255,0,0.25); +} + +div.rationale { + border-left: 6px solid #BFB; + background-color: rgba(127,255,127,0.125); +} + +div.rationale p.admonition-title { + color: darkgreen; +} + +div.admonition-todo { + border-left: 6px solid #CC0; + background-color: rgba(255,255,0,0.25); +} + +div.admonition-todo p.admonition-title { + color: #660; +} + +.scterm, .sc { + font-variant: small-caps; + text-transform: lowercase; +} + +.license{ + font-size: 80%; +} + +div.license p.sectiontitle { + display: block; + margin-top: 24px; + font-weight: bold; + font-size: 144%; +} + +.anchor { + color: #0091BD; +} + +.issue { + color: red; +} + +.term { + font-style: italic; +} + +.secref { + font-style: italic; +} + +.sectiontitle { + display: block; + margin-top: 30px; + font-weight: bold; + font-size: 180%; +} + +.sralabel { + font-weight: bold; +} + +.sradef { + font-weight: bold; + font-size: 90%; +} + +.sraref { + font-size: 90%; +} + +img.titlelogo { + float: left; + margin-top: 0.7em; + margin-bottom: 8em; + margin-right: 5%; + width: 25%; +} + +img.logo { + display: block; + margin: 1em auto; + width: 40%; +} + +div.sphinxsidebar hr { + width: 100%; +} + +dl dd { + margin-left: 3em; + margin-top: 0.2em; + margin-bottom: 0.8em; +} + +dl p { + margin-block-start: 0.6em; + margin-block-end: 0.6em; +} + +div.apisubitem dt > :first-child { + margin-top: 0; +} + +div.apisubitem dt > :last-child { + margin-bottom: 0; +} + +div.threat dt { + float: left; + clear: left; + width: 22ex; +} + +div.threat dt > :first-child { + margin-top: 0; +} + +div.threat dd { + margin-left:22ex; + padding-left: 1ex; +} + +div.riskrow table.docutils.borderless { + width: 100%; + margin-top: 0; + margin-bottom: 0.6em; +} + +div.riskrow table.docutils td { + padding: 0; +} + +div.riskrow table.docutils td p { + margin: 0; +} diff --git a/tools/templates/psa-api-2022/psa-api-tool.sty b/tools/templates/psa-api-2022/psa-api-tool.sty new file mode 100644 index 00000000..0660fa6f --- /dev/null +++ b/tools/templates/psa-api-2022/psa-api-tool.sty @@ -0,0 +1,492 @@ +% SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +% SPDX-License-Identifier: Apache-2.0 + +% Hide the warning when multiple inkscape-generated PDF [image] files are +% rendered on the same page +\pdfsuppresswarningpagegroup=1 + +% Set the ToC depth to include sub-section headings +\setcounter{tocdepth}{2} + +% Set the PDF bookmark depth to include sub-sub-section headings (API elements) +% The final level is not in the TOC +\def\psabookmarkdepth{3} + +\usepackage{apptools} +\usepackage{titletoc} +\usepackage{enumitem} +\usepackage[depth=\psabookmarkdepth, numbered]{bookmark} +\usepackage{caption} +\usepackage{ifthen} +\usepackage{xstring} +\usepackage[section]{placeins} +\usepackage{scalefnt} + +% The Sphinx DUrole command fails when multiple classes are defined +% This version splits the roles, and creates a nested set of calls to +% the sphinx command, +\let\sphinxDUrole\DUrole +\renewcommand{\DUrole}[2]{% + \IfSubStr{\detokenize{#1}}{,}{% + \StrBefore[1]{\detokenize{#1}}{,}[\myhead]% + \StrBehind[1]{\detokenize{#1}}{,}[\mytail]% + \expandafter\sphinxDUrole\expandafter{\myhead}{\expandafter\DUrole\expandafter{\mytail}{#2}}% + }{\sphinxDUrole{#1}{#2}}% +} +\MakeRobust\DUrole + +% PSA document styling +\def\psah {\normalfont\fontsize{20pt}{24pt}\selectfont\bfseries} +\def\psahh {\normalfont\fontsize{16pt}{19.2pt}\selectfont\bfseries} +\def\psahhh {\normalfont\fontsize{14pt}{16.8pt}\selectfont\bfseries} +\def\psahhhh {\normalfont\fontsize{12pt}{14.4pt}\selectfont\bfseries} +\def\psabody {\normalfont\fontsize{11pt}{13.2pt}\selectfont} +\def\psalicense {\normalfont\fontsize{9pt}{10.8pt}\selectfont} +\def\psasubtext {\normalfont\fontsize{8pt}{9.6pt}\selectfont} +\def\psasc {\scalefont{0.75}} +\def\psacitation {\itshape} +\def\psasubtitle {\normalfont\fontsize{10pt}{12pt}\selectfont\bfseries} +\def\psacaption {\psasubtitle} +\def\psathead {\psasubtitle} +\def\psalegend {\normalfont\fontsize{9pt}{10.8pt}\selectfont} + +\definecolor{psaissue}{rgb}{1,0,0} + +% Set up PSA style spacing rules +\frenchspacing +% remove justification on main text +\raggedright +% the line leading already gives us 2pt (in LaTeX spacing model) +\setlength{\parskip}{6pt plus 2pt minus 1pt} + + +% Define the heading styles for level 1-4 +\ifdefined\docchapterbreak + \titleclass{\chapter}{top} +\else + \titleclass{\chapter}{straight} +\fi +\titleformat{\chapter} + {\psah} + {\IfAppendix{\appendixname\ \thechapter:}{\thechapter}} + {0.5em}{}{} +\titlespacing{\chapter}{0pt}{*6}{*1} + +\titleformat{\section} + {\psahh} + {\thesection} + {0.5em}{}{} +\titlespacing{\section}{0pt}{*4.5}{*1} + +\titleformat{\subsection} + {\psahhh} + {\thesubsection} + {0.5em}{}{} +\titlespacing{\subsection}{0pt}{*4}{*1} + +\titleformat{\subsubsection} + {\psahhhh} + {\thesubsubsection} + {0.5em}{}{} +\titlespacing{\subsubsection}{0pt}{*3}{*1} + +% Add a watermark if requested +\ifdefined\docwatermark + \usepackage{draftwatermark} + \SetWatermarkText{\docwatermark} + \SetWatermarkScale{1} + \SetWatermarkColor[gray]{0.8} +\fi + +% remove emphasis on cross references +\def\sphinxcrossref#1{#1} + +% define a macro to provide a hyperlinked page reference, if on another page +% this is used for Section, Figure and Table references +\newcounter{testpagecount} +\DeclareRobustCommand\ifrefthispage[3]{% + \refstepcounter{testpagecount}\label{tpc\thetestpagecount}% + \ifthenelse{\equal{\pageref{#1}}{\pageref{tpc\thetestpagecount}}}{#2}{#3}% +} + +\newcommand\psapageref[1]{\ifrefthispage{#1}{}{\hyperref[#1]{{} on page~\pageref{#1}}}} + +% Define the format for the index sub-headings +\def\sphinxstyleindexlettergroup #1{{\psahhhh#1}\nopagebreak\vspace{4pt}} + +% set the size of API element names in subsubsection titles +\def\sphinxstyleliteralintitle#1{\texttt{\Large{#1}}} + +\raggedright + +% define the specification rule color +\definecolor{psarulecolor}{gray}{0.75} +\def\psarulewidth{.8pt} + +% Layout and style for tables + +\arrayrulecolor{psarulecolor} +\heavyrulewidth=.8pt + +% Set the table header rows font style +\def\sphinxstyletheadfamily {\psathead} + +% Set the table continuation style +\def\sphinxtablecontinued{\psacaption} + +% Table row spacing +\renewcommand{\arraystretch}{1.4} + +% Table captions +\captionsetup[table]{position=top} +\captionsetup{% + margin={2cm,0cm}, justification=raggedleft, singlelinecheck=false, + font=small, labelfont=bf, labelsep=space +} + +% Formatting for table cells +% * default paragraph spacing is 0pt, use a smaller (non-zero) spacing +% * reduced item spacing +% * left aligned text +\def\psacellformat{% + \setlength\parskip{3pt plus 1pt minus 1pt}% + \setlist[1]{topsep=2pt, partopsep=0pt, itemsep=1pt, parsep=2pt minus 1pt}% + \raggedright\arraybackslash% + } + +% redefine sphinx column types to be left aligned, with adjusted spacing +\makeatletter +\newcolumntype{\X}[2]{>{\psacellformat}p{\dimexpr + (\linewidth-\spx@arrayrulewidth)*#1/#2-\tw@\tabcolsep-\spx@arrayrulewidth\relax}} +\newcolumntype{\Y}[1]{>{\psacellformat}p{\dimexpr + #1\dimexpr\linewidth-\spx@arrayrulewidth\relax-\tw@\tabcolsep-\spx@arrayrulewidth\relax}} +\newcolumntype{T}{>{\psacellformat}L}% +\makeatother + +% Figure legend formatting comes after caption and may contain arbitrary body elements +\renewenvironment{sphinxlegend}{\par\medskip\psalegend}{\par} + +% format Notes to have a wider left margin +\renewenvironment{sphinxnote}[1] + {\list{}{\leftmargin1cm}\item[]\begin{sphinxlightbox}\sphinxstrong{#1}\par }{\end{sphinxlightbox}\endlist} + +% format for banner box. Use the environment defined for attention admonitions +\makeatletter +\newenvironment{sphinxclassbanner} + {% set parameters of heavybox + \def\spx@noticetype {attention}% + \sphinxcolorlet{spx@notice@bordercolor}{sphinxattentionBorderColor}% + \sphinxcolorlet{spx@notice@bgcolor}{sphinxattentionBgColor}% + \spx@notice@border \dimexpr1pt\relax + \begin{sphinxheavybox} + } + {\end{sphinxheavybox}} +\makeatother + +% format for internal-only rationale boxes. Use the environment defined for error admonitions +\makeatletter +\newenvironment{sphinxclassrationale} + {% set parameters of heavybox + \def\spx@noticetype {error}% + \sphinxcolorlet{spx@notice@bordercolor}{sphinxerrorBorderColor}% + \sphinxcolorlet{spx@notice@bgcolor}{sphinxerrorBgColor}% + \spx@notice@border \dimexpr1pt\relax + \begin{sphinxheavybox} + } + {\end{sphinxheavybox}} +\makeatother + +% Set up the PSA specification header/footer +\usepackage{fancyhdr} + \pagestyle{fancy} + \fancyhf{} + \fancyfoot[L]{\footnotesize{\docid\\{}\docreleasefull}} + \fancyfoot[C]{\footnotesize{\textit{Copyright \textcopyright \doccopyright}}} + \fancyfoot[R]{\footnotesize{Page \thepage}} + \renewcommand{\headrulewidth}{0pt} + \renewcommand{\footrulewidth}{\psarulewidth} + \renewcommand{\footrule}{{\color{psarulecolor} \rule{\headwidth}{\footrulewidth} \vskip -\footrulewidth}} + +\fancypagestyle{normal}{} +\fancypagestyle{plain}{} + +% Prevent the change of page number style from resetting the page number itself +\newcounter{savepage} +\let\oldpagenumbering\pagenumbering +\renewcommand{\pagenumbering}[1]{\setcounter{savepage}{\value{page}} \oldpagenumbering{#1} \setcounter{page}{\value{savepage}}} + +% A frontmatter environment, which continues roman page numbers, +% does not number titles, but includes them in the TOC. +\newcommand{\psafrontmatter}[1]% + {\pagenumbering{roman}% + \setcounter{secnumdepth}{#1} + } + +\newcommand{\psamain}[1] + {\clearpage% + \pagenumbering{arabic}% + \setcounter{secnumdepth}{#1} + } + +\newcommand{\psaappendix}[1] + {\ifdefined\docappendixbreak \clearpage\fi% + \setcounter{secnumdepth}{#1} + \appendix% + } + +\setcounter{secnumdepth}{2} +\setcounter{tocdepth}{2} + +% The title page command +\newenvironment{psatitle}% +{ + \pagestyle{empty} + \begin{titlepage} + + \setlength{\parindent}{0pt} + \begingroup % for PDF information dictionary + \def\endgraf{ }\def\and{\& }% + \pdfstringdefDisableCommands{\def\\{, }}% overwrite hyperref setup + \hypersetup{pdfauthor={\docauthor}, pdftitle={\docfulltitle{} \docversion}}% + \endgroup + + \noindent\begin{minipage}[t]{5.2cm} + \vspace{0pt}% required to get top-alignment of image and text + \sphinxlogo + \end{minipage} + \hfill\begin{minipage}[t]{0.67\textwidth}\raggedleft% + \vspace{0pt}% required to get top-alignment of image and text + {\psah \doclatextitle{} \docversion\par} + \ifdefined\docowner \medskip\psahhh\docowner\par \fi + \end{minipage} + + \bigskip\bigskip\bigskip + + \begin{table}[hbt!] + \begin{tabular}{ll} + Document number: & \docid \\ + Release Quality: & \docquality \\ + Issue Number: & \docissue \\ + Date of Issue: & \docdate + \end{tabular} + \end{table} + + \medskip + + \begingroup\raggedleft + \scriptsize{Copyright \textcopyright \doccopyright}\\\par + \endgroup + + \bigskip\bigskip\bigskip\bigskip +} +{ + \end{titlepage} + \pagestyle{plain} + \sphinxtableofcontents + \pagestyle{normal} +} + +% emulate small-caps: +\newcommand{\DUrolesc}[1] + {{\psasc\uppercase{#1}}} + +% small caps for special term references +\newcommand{\DUrolescterm}[1] + {{\psasc\uppercase{#1}}} + +% Other glossary term references are emphasized +\newcommand{\DUroleterm}[1] + {{\sphinxstyleemphasis{#1}}} + +% Define issue role as red color +\newcommand{\DUroleissue}[1] + {\textcolor{psaissue}{#1}} + +% Emphasise section and title-text references +\newcommand{\DUrolesecref}[1] + {{\sphinxstyleemphasis{#1}}} + +% SRA definition style +\newcommand{\DUrolesradef}[1] + {{\scalefont{.9}\sphinxstylestrong{#1}}} + +% SRA reference style +\newcommand{\DUrolesraref}[1] + {{\scalefont{.9}#1}} + +% SRA threat card label style +\newcommand{\DUrolesralabel}[1] + {{\sphinxstylestrong{#1}}} + +% associate anchor role with the InnerlinkColor +\newcommand{\DUroleanchor}[1] + {\textcolor{InnerLinkColor}{#1}} + +% Provide sectiontitle role for frontmatter titles that are not in TOC +\newcommand{\DUrolesectiontitle}[1] + {{\psahh #1}} + +\newcommand{\DUroleversionmodified}[1] + {{\sphinxstyleemphasis{#1}}} + +% define class environment for the license text (much smaller font) +\newenvironment{sphinxclasslicense} + {\psalicense\setlist[enumerate,1]{label=\bfseries(\roman*)}% + \renewcommand{\DUrolesectiontitle}[1] + {{\psahhh ##1}}% + } + {} + +% If running in a high enough version of sphinx, also +% Divert use of the sphinxalltt environment to use Verbatim. This requires +% setting a default config for Verbatim, which does not work in some earlier +% versions of Sphinx. +% +% This ensures that all literal blocks are rendered using the Verbatim +% configuration below, whether or not sphinx runs the highlighting engine on +% the block. +\newcommand{\useverbatimfortt}{% + \let\sphinxalltt\sphinxVerbatim% + \let\endsphinxalltt\endsphinxVerbatim% + \fvset{commandchars=\\\{\}}% + } + +% Set up the style for the Table of Contents + +\renewcommand{\contentsname}{\psahh Contents} +\contentsmargin{1cm} +\titlecontents{chapter}[1cm] + {\addvspace{16pt}} + {\psahhh\contentslabel{1cm}} + {\psahhh} + {\titlerule[0pt]\contentspage} +\titlecontents{section}[2cm] + {\addvspace{8pt}} + {\psahhhh\contentslabel{2cm}} + {\psahhhh} + {\titlerule[0pt]\contentspage} +\titlecontents{subsection}[2cm] + {} + {\psabody\contentslabel{1.5cm}} + {\psabody} + {\titlerule[0pt]\contentspage} + +% and remove sphinx's ToC overrides +\let\sphinxtableofcontentshook\relax + + +% This is something of a hack to get consistent, and differentiated API element +% subtitles in the specification. Simple specs might have API elements at +% level 3 headings (subsections), and complex ones at level 4 (subsubsection). +% So using a section level for the subtitles will yield inconsistent results. +% +% psa-api-tool.py uses `rubric` nodes for these sections (so don't use rubric in +% API reference for other things?). Unfortunately, Sphinx does not copy +% docutils and emit a \rubric{} command, it just emits \subsubsection*{}. +% +% As that [starred] command is not used for other elements, we replaced the +% \subsubsection command to intercept these uses and divert them to \rubric{}. +% We can then define \rubric{} as we wish - A bold 10pt runin for the main +% document text, and the original \subsubsection*{} in the appendix. +% +% Older versions of Sphinx directly use \paragraph{} for rubric, so +% also intercept level 5 headings and treat as rubric +% +% Heading 5 is used for all api subtitles +\newcommand{\subtitlett}[1]{{\normalfont\ttfamily #1}} +\newcommand{\apisubtitle}[1]{{\let\sphinxstyleliteralintitle\subtitlett #1}} +\titleformat{\paragraph} + {\psasubtitle} + {} + {0.5em}{\apisubtitle}{} +\titlespacing{\paragraph}{0pt}{*2}{4pt} + +\makeatletter +\let\oldsubsubsection\subsubsection +\renewcommand{\subsubsection} + % Only intercept rubrics in the main section + {\@ifstar{\IfAppendix{\oldsubsubsection*}{\paragraph*}}{\oldsubsubsection}} +\makeatother + + +% Set up item lists, enumerations and definition lists +% PSA current styleguide is incompatible with AIG. Will keep AIG at present +% [nb/ PSA guide is inconsistent with PSA examples] + +% Define spacing for lists +\setlist[1]{topsep=4pt, partopsep=0pt, itemsep=3pt, parsep=3pt plus 1pt minus 1pt} +\setlist[2]{topsep=2pt, partopsep=0pt, itemsep=1pt, parsep=1pt} +\setlist[3]{topsep=2pt, partopsep=0pt, itemsep=1pt, parsep=1pt} + +% Do not use multicols environment for hlist directive, but keep the compactness +\renewenvironment{multicols}[1]{\begingroup}{\endgroup} + +% AIG styleguide uses arabic, alpha for level 1 and 2 +% New versions of sphinx ignore this and set the formatting based on the +% source text +\setlist[enumerate,1]{label=\arabic*.} +\setlist[enumerate,2]{label=\alph*.} +\setlist[enumerate,3]{label=\roman*.} + +% AIG styleguide uses bullet and em-dash for level 1 and 2 +\setlist[itemize,1]{label=$\bullet$} +\setlist[itemize,2]{label=---} +\setlist[itemize,3]{label=$\circ$} + +% Remove Sphinx hack for formatting multiple terms in definition list +% It forces the list formatting, and breaks the styling done with enumitem +\renewcommand\sphinxlineitem[2]{\item[#1]\leavevmode#2}% + +% Define standard definition list format +\def\termmargin {13ex} +\def\termindent {2ex} +\def\termspace {1ex} + +\setlist[description]{% + style=nextline,% + labelindent=\termindent, labelwidth=!, labelsep=\termspace,% + itemindent=0ex, leftmargin=\termmargin,% + font=\normalfont,% + topsep=3pt, partopsep=0pt, itemsep=2pt, parsep=3pt% +} + +% Define API subitem definition list format +% psa-api-tool.py wraps the subitem lists in a apisubitem environment - so +% we can define that environment to set the description format +\def\apiitemwidth {29ex} +\def\apiitemindent { 2ex} +\def\apiitemspace { 1ex} + +\newenvironment{sphinxclassapisubitem} + {\setlist[description]{% + style=nextline,% + labelindent=\apiitemindent, labelwidth=\apiitemwidth,% + labelsep=\apiitemspace, itemindent=0ex, leftmargin=!,% + font=\normalfont,% + topsep=3pt, partopsep=0pt, itemsep=2pt, parsep=3pt% + }} + {} + +% Define Threat card definition list format +\def\threatlabelwidth {22ex} +\def\threatlabelindent { 0ex} +\def\threatlabelspace { 1ex} + +\newenvironment{sphinxclassthreat} + {\setlist[description]{% + style=nextline,% + labelindent=\threatlabelindent, labelwidth=\threatlabelwidth,% + labelsep=\threatlabelspace, itemindent=0ex, leftmargin=!,% + font=\sphinxstylestrong,% + topsep=6pt plus 2pt minus 1pt, partopsep=0pt,% + itemsep=2pt, parsep=6pt plus 2pt minus 1pt% + }} + {} + +\newenvironment{sphinxclassriskrow} + {\renewcommand{\arraystretch}{1.2}% + \setlength\tabcolsep{0pt}% + \def\sphinxattableend{}% + \vskip\dimexpr-\parskip-\baselineskip\relax% + } + {} diff --git a/tools/templates/psa-api-2022/sphinx-templates/indextoc.html b/tools/templates/psa-api-2022/sphinx-templates/indextoc.html new file mode 100644 index 00000000..549e3123 --- /dev/null +++ b/tools/templates/psa-api-2022/sphinx-templates/indextoc.html @@ -0,0 +1,8 @@ +<!-- +SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +SPDX-License-Identifier: Apache-2.0 +--> + +<ul> +<li class="toctree-l1"><a class="reference internal" href="{{ pathto("psa_c-identifiers.html", 1) }}">Index of API elements</a></li> +</ul> diff --git a/tools/templates/psa-api-2022/sphinx-templates/toc.html b/tools/templates/psa-api-2022/sphinx-templates/toc.html new file mode 100644 index 00000000..d11a6126 --- /dev/null +++ b/tools/templates/psa-api-2022/sphinx-templates/toc.html @@ -0,0 +1,11 @@ +<!-- +SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +SPDX-License-Identifier: Apache-2.0 +--> + +<hr /> +<h3><a href="{{ pathto(master_doc) }}"><b>{{ dochtmltitle }}</b></a></h3> +{{ docid }}<br/> +Version {{ docreleasefull }} +<hr /> +{{ toctree() }} diff --git a/tools/templates/psa-api-2022/template-conf.py b/tools/templates/psa-api-2022/template-conf.py new file mode 100644 index 00000000..8c6b80cd --- /dev/null +++ b/tools/templates/psa-api-2022/template-conf.py @@ -0,0 +1,97 @@ +# SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +# SPDX-License-Identifier: Apache-2.0 + +# -*- coding: utf-8 -*- +# +# Adjust or reset the template_info dictionary with customized +# sphinx configurations for this template + +template_info['logo_file'] = 'Arm_logo_blue_RGB' +template_info['html_theme'] = 'alabaster' +template_info['html_css_files'] = [ + ('https://fonts.googleapis.com', { 'rel': 'preconnect' }), + ('https://fonts.gstatic.com', { 'rel': 'preconnect', 'crossorigin': None }), + ('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400&display=swap', { 'rel': 'stylesheet' }), + ('https://fonts.googleapis.com/css2?family=Inconsolata:wght400;700&display=swap', { 'rel': 'stylesheet' }) +] +template_info['mathjax3_config'] = { + 'chtml': { + 'mtextInheritFont': True + } +} +template_info['latex_pointsize'] = '11pt' +template_info['latex_fonts'] = [ + r'\usepackage[default]{lato}', + r'\usepackage[scaled=0.91]{inconsolata}' + ] +template_info['latex_sphinxsetup'] = [ + # Use black for titles + 'TitleColor={rgb}{0,0,0}', + # Reduce margins + 'hmargin={1.9cm,1.25cm}', + 'vmargin={3.5cm, 3cm}', + 'marginpar=1.27cm', + # Format the verbatim blocks + 'verbatimwithframe=true', + 'verbatimsep=3pt', + 'VerbatimBorderColor={rgb}{0.9,0.9,0.9}', + 'verbatimborder=0.5pt', + 'VerbatimColor={rgb}{0.97,0.97,0.97}', + # format hyperlink color + 'InnerLinkColor={rgb}{0,0.569,0.741}', + 'OuterLinkColor={rgb}{0,0.569,0.741}', + # format admonitions + 'noteBorderColor={rgb}{0.667,0.667,0.667}', + 'warningBorderColor={rgb}{.75,0.5,0.5}', + 'warningborder=2pt', + # Use attention admonition for the front page banner + 'attentionBorderColor={rgb}{.8,.8,0}', + 'attentionBgColor={rgb}{1,1,.7}', + 'attentionborder=1pt', + # Use error admonition for rationale boxes + 'errorBorderColor={rgb}{.5,.75,.5}', + 'errorBgColor={rgb}{.9,.95,.9}', + 'errorborder=1pt', + # Use the normal font for headings + 'HeaderFamily=\\normalfont\\bfseries', + ] +template_info['latex_table_style'] = ['booktabs','nocolorrows'] +template_info['graphviz_dot_args'] = [ + '-Gfontname=Lato', + '-Gfontsize=12', + '-Nfontname=Lato', + '-Nfontsize=12', + '-Efontname=Lato', + '-Efontsize=12' +] + +def make_doc_filename(info, id, title, version, status): + if all((k in info for k in ('doc_id','quality','issue_no'))): + return '-'.join([id.replace(' ',''), title, version]) + return None + +template_info['make_filename'] = make_doc_filename + +template_info['front_sections'] = [ + 'abstract', + 'release-info', + 'todos', + 'license', + 'references', + 'terms', + 'potential-for-change', + 'conventions', + 'pseudocode', + 'assembler', + 'current-status', + 'feedback', + 'inclusive-language', + ] + +if 'author' not in doc_info: + doc_info['author'] = 'Arm Limited' +doc_info.setdefault('feedback', 'visit :url:`github.com/arm-software/psa-api/issues`' + + ' to create a new issue at the PSA Certified API GitHub project') +# force use of Arm copyright notice and OSS license +doc_info['copyright'] = 'Arm Limited and/or its affiliates' +doc_info['license'] = 'arm-psa-certified-api-license' diff --git a/tools/templates/psa-api-2022/title-page.rst b/tools/templates/psa-api-2022/title-page.rst new file mode 100644 index 00000000..211eba49 --- /dev/null +++ b/tools/templates/psa-api-2022/title-page.rst @@ -0,0 +1,42 @@ +.. SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +.. SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +.. only:: html + + .. template-image:: Arm_logo_blue_RGB.svg + :alt: Arm + :class: titlelogo + +========================== +|docrsttitle| |docversion| +========================== + +.. only:: html + + .. csv-table:: + :class: titletable borderless + + Document number:, |docid| + Release Quality:, |docquality| + Issue Number:, |docissue| + Date of Issue:, |docdate| + +.. raw:: latex + + \begin{psatitle} + +.. insert-banner:: + +.. insert-section:: Abstract + :section: abstract + :not-in-toc: + +.. raw:: latex + + \end{psatitle} + +.. only:: html + + .. insert-section:: Contents + :not-in-toc: + :keep-if-empty: diff --git a/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.pdf b/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.pdf new file mode 100644 index 00000000..4b9affc9 Binary files /dev/null and b/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.pdf differ diff --git a/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.pdf.license b/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.pdf.license new file mode 100644 index 00000000..8c3078e2 --- /dev/null +++ b/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.pdf.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.svg b/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.svg new file mode 100644 index 00000000..84df36bb --- /dev/null +++ b/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.svg @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 28.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="4332.3px" height="1318.7px" viewBox="0 0 4332.3 1318.7" style="enable-background:new 0 0 4332.3 1318.7;" + xml:space="preserve"> +<style type="text/css"> + .st0{fill:#080225;} +</style> +<g> + <path class="st0" d="M977.9,31.9h290.4v1247.6H977.9v-130.4C850.5,1297.2,693.4,1318,604.5,1318C219.3,1318,0,998,0,654.2 + C0,248.2,278.5-0.7,607.5-0.7c91.9,0,251.9,23.7,370.4,177.8V31.9z M296.3,660.1c0,216.3,136.3,397.1,346.7,397.1 + c183.7,0,352.6-133.3,352.6-394.1c0-272.6-168.9-403-352.6-403C432.6,260.1,296.3,437.9,296.3,660.1z M1614.1,31.9h290.4v112.6 + c32.6-38.5,80-80,121.5-103.7c56.3-32.6,112.6-41.5,177.8-41.5c71.1,0,148.2,11.9,228.2,59.3l-118.5,263.7 + c-65.2-41.5-118.5-44.4-148.2-44.4c-62.2,0-124.5,8.9-180.8,68.2c-80,85.9-80,204.5-80,287.4v646h-290.4V31.9z M2619.5,31.9h290.4 + v115.6C3007.7,29,3123.3-0.7,3218.1-0.7c130.4,0,251.9,62.2,323,183.7C3644.8,34.9,3801.9-0.7,3911.5-0.7 + c151.1,0,284.5,71.1,355.6,195.6c23.7,41.5,65.2,133.3,65.2,314.1v770.5h-290.4V592c0-139.3-14.8-195.6-26.7-222.2 + c-17.8-47.4-62.2-109.6-165.9-109.6c-71.1,0-133.3,38.5-171.9,91.9c-50.4,71.1-56.3,177.8-56.3,284.5v643h-290.4V592 + c0-139.3-14.8-195.6-26.7-222.2c-17.8-47.4-62.2-109.6-166-109.6c-71.1,0-133.3,38.5-171.9,91.9c-50.4,71.1-56.3,177.8-56.3,284.5 + v643h-290.4V31.9z"/> +</g> +</svg> diff --git a/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.svg.license b/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.svg.license new file mode 100644 index 00000000..8c3078e2 --- /dev/null +++ b/tools/templates/psa-api-2025/ARM_LOGO-2025_INK_RGB.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/tools/templates/psa-api-2025/about-chapter.rst b/tools/templates/psa-api-2025/about-chapter.rst new file mode 100644 index 00000000..3e09491a --- /dev/null +++ b/tools/templates/psa-api-2025/about-chapter.rst @@ -0,0 +1,147 @@ +.. SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +.. SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +.. role:: anchor + +.. _about-this-document: + +=================== +About this document +=================== + +.. _release-info: + +.. insert-section:: Release information + :section: release-info + :break-after: + + The change history table lists the changes that have been made to this document. + + .. release-table:: Document revision history + +.. only:: include_todo + + .. insert-section:: TODO items + :section: todos + :break-after: + :not-in-toc: + + The following items are marked up as TODO in the document source: + + .. todolist:: + +.. insert-section:: |docfulltitle| + :not-in-toc: + + Copyright © |doccopyright|. The copyright statement reflects the fact that some + draft issues of this document have been released, to a limited circulation. + +.. include-license:: + +.. _license: + +.. insert-section:: License + :section: license + :class: license + :break-after: + +.. _references: + +.. insert-section:: References + :section: references + + This document refers to the following documents. + + .. reference-table:: Documents referenced by this document + +.. _terms: + +.. insert-section:: Terms and abbreviations + :section: terms + + This document uses the following terms and abbreviations. + + .. term-table:: Terms and abbreviations + :sorted: + +.. _potential-for-change: + +.. insert-section:: Potential for change + :section: potential-for-change + + The contents of this specification are stable for |API| version |APIversion|. + + The following may change in |APIversion|.x maintenance updates to this specification: + + * Defect fixes + * Clarifications + * Editorial improvements + + Functionality additions, or any changes that affect the compatibility of the interfaces defined in this specification will only be included in a new major or minor version of the specification. + + +.. _conventions: + +.. insert-section:: Conventions + :section: conventions + + .. insert-section:: Typographical conventions + + The typographical conventions are: + + *italic* + Introduces special terminology, and denotes citations. + + ``monospace`` + Used for assembler syntax descriptions, pseudocode, and source code examples. + + Also used in the main text for instruction mnemonics and for references to + other items appearing in assembler syntax descriptions, pseudocode, and + source code examples. + + :sc:`small capitals` + Used for some common terms such as :sc:`implementation defined`. + + Used for a few terms that have specific technical meanings, and are included + in the *Terms and abbreviations*. + + :issue:`Red text` + Indicates an open issue. + + :anchor:`Blue text` + Indicates a link. This can be + + * A cross-reference to another location within the document + * A URL, for example :url:`example.com` + + .. insert-section:: Numbers + + Numbers are normally written in decimal. Binary numbers are preceded by 0b, and + hexadecimal numbers by ``0x``. + + In both cases, the prefix and the associated value are written in a monospace + font, for example ``0xFFFF0000``. To improve readability, long numbers can be + written with an underscore separator between every four characters, for example + ``0xFFFF_0000_0000_0000``. Ignore any underscores when interpreting the value of + a number. + +.. _current-status: + +.. insert-section:: Current status and anticipated changes + :section: current-status + +.. _feedback: + +.. insert-section:: Feedback + :section: feedback + + We welcome feedback on the |docfulltitle|. + + If you have comments on the content of this specification, |docfeedback|. Give: + + * The title (|docfulltitle|). + * The number and issue (|docid| |docrelease|). + * The location in the document to which your comments apply. + * A concise explanation of your comments. + + We also welcome general suggestions for additions and improvements. diff --git a/tools/templates/psa-api-2025/html-static/custom.css b/tools/templates/psa-api-2025/html-static/custom.css new file mode 100644 index 00000000..73985f7f --- /dev/null +++ b/tools/templates/psa-api-2025/html-static/custom.css @@ -0,0 +1,520 @@ +/* SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited */ +/* SPDX-License-Identifier: Apache-2.0 */ + +/* CSS overrides for the html output */ + +body { + font-family: Lato, sans-serif; + font-size: 15px; + font-weight: 300; + color: #080225; +} + +div.document { + width: 965px; +} + +div.bodywrapper { + margin: 0 0 0 270px; +} + +div.body { + background-color: unset; + color: #080225; + padding: 0; +} + +div.sphinxsidebar { + width: 240px; + font-size: 12.5px; + margin-top: -30px; +} + +div.sphinxsidebarwrapper { + padding: 0; +} + +div.sphinxsidebarwrapper p.logo { + margin: 0; +} + +div.footer { + width: 965px; +} + +div.watermark { + position: fixed; + width: 660px; + height: 100%; + z-index: -999; + pointer-events: none; + } + +div.watermark p { + color: #EEE; + font-size: 160px; + font-weight: 400; + pointer-events: none; + user-select: none; + margin: 0px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(-45deg); + } + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Lato, sans-serif; + font-weight: 400; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Lato, sans-serif; + font-size: 20px; +} + +p.rubric { + font-weight: 400; +} + +div.body input, div.sphinxsidebar input { + font-family: Lato, sans-serif; + font-size: 15px; +} + +div.line { + line-height: 1.4em; +} + +a, a:visited { + color: #0042c0; + text-decoration: none; +} + +a.reference, div.sphinxsidebar a.reference { + border-bottom: none; +} + +a.reference:hover, div.sphinxsidebar a.reference:hover, a:hover code, a:hover tt { + color: #561bc6; + border-bottom: 1px dotted #561bc6; + background: unset; +} + +pre a:hover, a:hover code, a:hover tt { + font-weight: 400; +} + +mjx-math { + color: #5b566d +} + +tt, code { + font-family: "Noto Sans Mono", monospace; + font-size: 80%; + background-color: unset; + color: #080225; +} + +h3 code, h4 code { + font-size: 88%; +} + +code.xref, a code { + font-weight: 300; + color: #0042c0; + background-color: unset; + border-bottom: unset; +} + +pre { + font-family: "Noto Sans Mono", monospace; + font-size: 76%; + color: #080225; + background: rgba(187,187,187,0.12); + padding: 5px; + margin: 10px -6px; + line-height: 1.3em; + border-style: solid; + border-width: 1px; + border-radius: 5px; + border-color: rgba(187,187,187,0.3); +} + +/* Fix the specific overrides in basic.css and alabaster.css for code blocks */ +div.highlight pre, dl pre, blockquote pre, li pre { + padding: 5px; + margin: 10px -6px; +} + +sub { + font-size: 70%; + vertical-align: -10%; + font-weight: 400; + color: #5b566d; +} + +sup { + font-size: 70%; + line-height: normal; + vertical-align: 25%; + font-weight: 400; + color: #5b566d; +} + +/* Default table formatting is like 'booktabs', 'standard' formatting can + can be specified explicitly. + */ + +table { + font-weight: 300; +} + +table.docutils { + font-size: unset; + box-shadow: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + /*width: 100%;*/ + border: 0; + border-top: 1px solid #AAA; + border-bottom: 1px solid #AAA; + margin-bottom: 1em; +} + +table.docutils.align-left { + margin-left: 0; + margin-right: auto; +} + +table.docutils.align-right { + margin-left: auto; + margin-right: 0; +} + +table.docutils.standard { + border: 1px solid #AAA; +} + +table.docutils.borderless { + border: 0; +} + +table.docutils.borderless.titletable { + margin-top: 40px; + width: 70%; + margin-right: 0px; + margin-left: auto; +} + +table.docutils caption, div.figure p.caption, figcaption { + font-size: 90%; + text-align: right; + margin-right: -13px; /* move permalink into margin */ +} + +table.docutils caption span.caption-number, div.figure p.caption span.caption-number, figcaption span.caption-number { + font-weight: 400; + font-style: normal; +} + +table.docutils td, table.docutils th { + border: 0; + padding: 0.15em 0.5em 0.15em 0.5em; + text-align: left; + vertical-align: baseline; +} + +table.docutils tr:first-child th, table.docutils tr:first-child td { + padding-top: 0.4em; +} + +/* Add all rules for standard table formatting */ +table.docutils.standard td, table.docutils.standard th { + border: 1px solid #AAA; +} + +table.docutils td p, table.docutils th p, table.docutils td .line-block { + margin-block-start: .3em; + margin-block-end: .3em; +} + +table.docutils th > p:first-child, table.docutils td > p:first-child { + margin-top: 0px; +} + +table.docutils th { + font-weight: 400; + font-size: 90%; +} + +/* rule below stub rows */ +table.docutils th.stub { + border-bottom: none; +} + +table.docutils.standard th.stub { + border-bottom: 1px solid #AAA; +} + +/* rule below header rows */ +table.docutils th.head { + border-bottom: 0; +} + +table.docutils tr:last-child > th.head { + border-bottom: 1px solid #AAA; +} + +table.docutils.borderless tr:last-child > th.head { + border-bottom: 0; +} + +table.docutils.standard th.head { + border-bottom: 1px solid #AAA; +} + +table.docutils.borderless th.head { + border-bottom: 0; +} + +figure { + margin-inline-start: 0px; + margin-inline-end: 0px; +} + +div.figure div.legend, figcaption div.legend { + font-size: 90%; /* adds to the figcaption scaling */ + text-align: center; +} + +div.figure p.caption, div.figure div.legend p, figcaption p, figcaption div.legend { + margin-block-start: 0.2em; + margin-block-end: 0.5em; +} + +table.docutils caption { + margin-bottom: 0.25em; +} + +div.admonition { + margin: 5px 15px; + padding: 5px 15px; + background-color: unset; + border: 0; + border-left: 6px solid #DDD; +} + +div.admonition p.admonition-title { + font-family: Lato, sans-serif; + font-weight: 400; + font-size: 90%; +} + +p.admonition-title:after { + content: ""; +} + +div.admonition p { + margin-top: 0; + margin-bottom: 5px; +} + +div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { + background-color: unset; +} + +div.highlight { + background-color: unset; +} + +div.admonition div.highlight pre { + background-color: unset; +} + +div.warning { + border-left: 6px solid #FBB; +} + +div.warning p.admonition-title { + color: darkred; +} + +div.banner { + border: 2px solid #CC0; + background-color: rgba(255,255,0,0.25); +} + +div.rationale { + border-left: 6px solid #BFB; + background-color: rgba(127,255,127,0.125); +} + +div.rationale p.admonition-title { + color: darkgreen; +} + +div.comment p.admonition-title { + color: #555; +} + +div.comment { + border-left: 6px solid #ccc; + background-color: #f8f8f8; + color: #555; + font-size: 95%; +} + +div.admonition-todo { + border-left: 6px solid #CC0; + background-color: rgba(255,255,0,0.25); +} + +div.admonition-todo p.admonition-title { + color: #660; +} + +.scterm, .sc { + font-variant: small-caps; + text-transform: lowercase; +} + +.license{ + font-size: 80%; +} + +div.license p.sectiontitle { + display: block; + margin-top: 24px; + font-weight: 400; + font-size: 144%; +} + +.anchor { + color: #0042c0; +} + +.issue { + color: red; +} + +.term { + font-style: italic; +} + +.secref { + font-style: italic; +} + +.sectiontitle { + display: block; + margin-top: 30px; + font-weight: 400; + font-size: 180%; +} + +.sralabel { + font-weight: 400; + font-size: 90%; +} + +.sradef { + font-weight: 400; + font-size: 90%; +} + +.sraref { + font-size: 90%; +} + +img.titlelogo { + float: left; + margin-top: 0.7em; + margin-bottom: 8em; + margin-right: 5%; + width: 25%; +} + +img.logo { + display: block; + margin: 1em auto; + width: 40%; +} + +div.sphinxsidebar hr { + width: 100%; +} + +dl dd { + margin-left: 3em; + margin-top: 0.2em; + margin-bottom: 0.8em; +} + +dl p { + margin-block-start: 0.6em; + margin-block-end: 0.6em; +} + +div.apisubitem dl { + display: grid; + grid-template-columns: 30ex 1fr; /* term column + description column */ + gap: 0 0.5ex; /* row gap, column gap */ + align-items: start; +} + +div.apisubitem dt p { + margin-block-start: 0; + margin-block-end: 0; +} + +div.apisubitem dd { + margin: 0; +} + +div.threat dl { + display: grid; + grid-template-columns: 20ex 1fr; /* term column + description column */ + gap: 0 0.5ex; /* row gap, column gap */ + align-items: start; +} + +div.threat dt p { + margin-block-start: 0; +} + +div.threat dd { + margin: 0; +} + +div.riskrow table.docutils.borderless { + width: 100%; + margin-top: 0; + margin-bottom: 0.6em; +} + +div.riskrow table.docutils td { + padding: 0; +} + +div.riskrow table.docutils td p { + margin: 0; +} + +ol { + list-style-type: decimal; /* 1, 2, 3 */ +} + +ol ol { + list-style-type: lower-alpha; /* a, b, c */ +} + +ol ol ol { + list-style-type: lower-roman; /* i, ii, iii */ +} diff --git a/tools/templates/psa-api-2025/psa-api-tool.sty b/tools/templates/psa-api-2025/psa-api-tool.sty new file mode 100644 index 00000000..a6516e2c --- /dev/null +++ b/tools/templates/psa-api-2025/psa-api-tool.sty @@ -0,0 +1,530 @@ +% SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +% SPDX-License-Identifier: Apache-2.0 + +% Hide the warning when multiple inkscape-generated PDF [image] files are +% rendered on the same page +\pdfsuppresswarningpagegroup=1 + +% Set the ToC depth to include sub-section headings +\setcounter{tocdepth}{2} + +% Set the PDF bookmark depth to include sub-sub-section headings (API elements) +% The final level is not in the TOC +\def\psabookmarkdepth{3} + +\usepackage{apptools} +\usepackage{titletoc} +\usepackage{enumitem} +\usepackage[depth=\psabookmarkdepth, numbered]{bookmark} +\usepackage{caption} +\usepackage{ifthen} +\usepackage{xstring} +\usepackage[section]{placeins} +\usepackage{scalefnt} + +% The Sphinx DUrole command fails when multiple classes are defined +% This version splits the roles, and creates a nested set of calls to +% the sphinx command, +\let\sphinxDUrole\DUrole +\renewcommand{\DUrole}[2]{% + \IfSubStr{\detokenize{#1}}{,}{% + \StrBefore[1]{\detokenize{#1}}{,}[\myhead]% + \StrBehind[1]{\detokenize{#1}}{,}[\mytail]% + \expandafter\sphinxDUrole\expandafter{\myhead}{\expandafter\DUrole\expandafter{\mytail}{#2}}% + }{\sphinxDUrole{#1}{#2}}% +} +\MakeRobust\DUrole + +% PSA document styling +\def\psah {\normalfont\fontsize{20pt}{24pt}\fontseries{m}\selectfont} +\def\psahh {\normalfont\fontsize{16pt}{19.2pt}\fontseries{m}\selectfont} +\def\psahhh {\normalfont\fontsize{14pt}{16.8pt}\fontseries{m}\selectfont} +\def\psahhhh {\normalfont\fontsize{12.5pt}{15pt}\fontseries{m}\selectfont} +\def\psabody {\normalfont\fontsize{11pt}{13.2pt}\fontseries{l}\selectfont} +\def\psasmall {\normalfont\fontsize{10pt}{12pt}\fontseries{l}\selectfont} +\def\psafootnote {\normalfont\fontsize{9pt}{10.8pt}\fontseries{l}\selectfont} +\def\psascript {\normalfont\fontsize{8pt}{9.6pt}\fontseries{l}\selectfont} +\def\psasc {\scalefont{0.75}} +\def\psacitation {\itshape} +\def\psalicense {\psafootnote} +\def\psasubtext {\psascript} +\def\psasubtitle {\psabody\mdseries} +\def\psathead {\psasmall\mdseries} +\def\psalabel {\psasmall\mdseries} +\def\psacaption {\psasmall} +\def\psalegend {\psafootnote} +\def\psacontd {\psafootnote\mdseries} + +\definecolor{psaissue}{rgb}{1,0,0} +\definecolor{psaink}{rgb}{0.03,0.01,0.145} + +% Set up PSA style spacing rules +\frenchspacing +% remove justification on main text +\raggedright +% the line leading already gives us 2pt (in LaTeX spacing model) +\setlength{\parskip}{6pt plus 2pt minus 1pt} + + +% Define the heading styles for level 1-4 +\ifdefined\docchapterbreak + \titleclass{\chapter}{top} +\else + \titleclass{\chapter}{straight} +\fi +\titleformat{\chapter} + {\psah} + {\IfAppendix{\appendixname\ \thechapter:}{\thechapter}} + {0.5em}{}{} +\titlespacing{\chapter}{0pt}{*6}{*1} + +\titleformat{\section} + {\psahh} + {\thesection} + {0.5em}{}{} +\titlespacing{\section}{0pt}{*4.5}{*1} + +\titleformat{\subsection} + {\psahhh} + {\thesubsection} + {0.5em}{}{} +\titlespacing{\subsection}{0pt}{*4}{*1} + +\titleformat{\subsubsection} + {\psahhhh} + {\thesubsubsection} + {0.5em}{}{} +\titlespacing{\subsubsection}{0pt}{*3}{*1} + +% Add a watermark if requested +\ifdefined\docwatermark + \usepackage{draftwatermark} + \SetWatermarkText{\mdseries\docwatermark} + \SetWatermarkScale{0.8} + \SetWatermarkColor[gray]{0.93} +\fi + +% remove emphasis on cross references +\def\sphinxcrossref#1{#1} + +% define a macro to provide a hyperlinked page reference, if on another page +% this is used for Section, Figure and Table references +\newcounter{testpagecount} +\DeclareRobustCommand\ifrefthispage[3]{% + \refstepcounter{testpagecount}\label{tpc\thetestpagecount}% + \ifthenelse{\equal{\pageref{#1}}{\pageref{tpc\thetestpagecount}}}{#2}{#3}% +} + +\newcommand\psapageref[1]{\ifrefthispage{#1}{}{\hyperref[#1]{{} on page~\pageref{#1}}}} + +% Define the format for the index sub-headings +\def\sphinxstyleindexlettergroup #1{{\psahhhh#1}\nopagebreak\vspace{4pt}} + +% set the size of API element names in subsubsection titles +\def\sphinxstyleliteralintitle#1{{\scalefont{1.1}\texttt{#1}}} + +\def\sphinxstylestrong#1{{\fontseries{m}\selectfont{#1}}} +\def\sphinxstrong#1{{\fontseries{m}\selectfont{#1}}} + +\raggedright + +% define the specification rule color +\definecolor{psarulecolor}{gray}{0.75} +\def\psarulewidth{.8pt} + +% Layout and style for tables + +\arrayrulecolor{psarulecolor} +\heavyrulewidth=.8pt + +% Set the table header rows font style +\def\sphinxstyletheadfamily {\psathead} + +% Set the table continuation style +\def\sphinxtablecontinued{\psacontd} + +% Table row spacing +\renewcommand{\arraystretch}{1.4} + +% Table captions +\captionsetup[table]{position=top} +\DeclareCaptionFormat{custom} +{% + \psalabel{#1#2}\psacaption{#3} +} +\captionsetup{% + format=custom, + margin={2cm,0cm}, justification=raggedleft, singlelinecheck=false, + labelsep=space +} + +% Formatting for table cells +% * default paragraph spacing is 0pt, use a smaller (non-zero) spacing +% * reduced item spacing +% * left aligned text +\def\psacellformat{% + \setlength\parskip{3pt plus 1pt minus 1pt}% + \setlist[1]{topsep=2pt, partopsep=0pt, itemsep=1pt, parsep=2pt minus 1pt}% + \raggedright\arraybackslash% + } + +% redefine sphinx column types to be left aligned, with adjusted spacing +\makeatletter +\newcolumntype{\X}[2]{>{\psacellformat}p{\dimexpr + (\linewidth-\spx@arrayrulewidth)*#1/#2-\tw@\tabcolsep-\spx@arrayrulewidth\relax}} +\newcolumntype{\Y}[1]{>{\psacellformat}p{\dimexpr + #1\dimexpr\linewidth-\spx@arrayrulewidth\relax-\tw@\tabcolsep-\spx@arrayrulewidth\relax}} +\newcolumntype{T}{>{\psacellformat}L}% +\makeatother + +% Figure legend formatting comes after caption and may contain arbitrary body elements +\renewenvironment{sphinxlegend}{\par\medskip\psalegend}{\par} + +% format Notes to have a wider left margin +\renewenvironment{sphinxnote}[1] + {\list{}{\leftmargin1cm}\item[]\begin{sphinxlightbox}\sphinxstrong{#1}\par }{\end{sphinxlightbox}\endlist} + +% format for banner box. Use the environment defined for attention admonitions +\makeatletter +\newenvironment{sphinxclassbanner} + {% set parameters of heavybox + \def\spx@noticetype {attention}% + \sphinxcolorlet{spx@notice@bordercolor}{sphinxattentionBorderColor}% + \sphinxcolorlet{spx@notice@bgcolor}{sphinxattentionBgColor}% + \spx@notice@border \dimexpr1pt\relax + \begin{sphinxheavybox} + } + {\end{sphinxheavybox}} +\makeatother + +% format for rationale boxes. Use the environment defined for error admonitions +\makeatletter +\newenvironment{sphinxclassrationale} + {% set parameters of heavybox + \def\spx@noticetype {error}% + \sphinxcolorlet{spx@notice@bordercolor}{sphinxerrorBorderColor}% + \sphinxcolorlet{spx@notice@bgcolor}{sphinxerrorBgColor}% + \spx@notice@border \dimexpr1pt\relax + \begin{sphinxheavybox} + } + {\end{sphinxheavybox}} +\makeatother + +% format for comment boxes. Use the environment defined for error admonitions +\makeatletter +\newenvironment{sphinxclasscomment} + {% set parameters of heavybox + \def\spx@noticetype {hint}% + \sphinxcolorlet{spx@notice@bordercolor}{sphinxhintBorderColor}% + \sphinxcolorlet{spx@notice@bgcolor}{sphinxhintBgColor}% + \spx@notice@border \dimexpr0pt\relax + \begin{sphinxheavybox}% + \color{sphinxhintTextColor}% + \psasmall + } + {\end{sphinxheavybox}} +\makeatother + +% Set up the PSA specification header/footer +\usepackage{fancyhdr} + \pagestyle{fancy} + \fancyhf{} + \fancyfoot[L]{\footnotesize\fontseries{l}\selectfont{\docid\\{}\docreleasefull}} + \fancyfoot[C]{\footnotesize\fontseries{l}\selectfont{\textit{Copyright \textcopyright \doccopyright}}} + \fancyfoot[R]{\footnotesize\fontseries{l}\selectfont{Page \thepage}} + \renewcommand{\headrulewidth}{0pt} + \renewcommand{\footrulewidth}{\psarulewidth} + \renewcommand{\footrule}{{\color{psarulecolor} \rule{\headwidth}{\footrulewidth} \vskip -\footrulewidth}} + +\fancypagestyle{normal}{} +\fancypagestyle{plain}{} + +% Prevent the change of page number style from resetting the page number itself +\newcounter{savepage} +\let\oldpagenumbering\pagenumbering +\renewcommand{\pagenumbering}[1]{\setcounter{savepage}{\value{page}} \oldpagenumbering{#1} \setcounter{page}{\value{savepage}}} + +% A frontmatter environment, which continues roman page numbers, +% does not number titles, but includes them in the TOC. +\newcommand{\psafrontmatter}[1]% + {\pagenumbering{roman}% + \setcounter{secnumdepth}{#1} + } + +\newcommand{\psamain}[1] + {\clearpage% + \pagenumbering{arabic}% + \setcounter{secnumdepth}{#1} + } + +\newcommand{\psaappendix}[1] + {\ifdefined\docappendixbreak \clearpage\fi% + \setcounter{secnumdepth}{#1} + \appendix% + } + +\setcounter{secnumdepth}{2} +\setcounter{tocdepth}{2} + +% The title page command +\newenvironment{psatitle}% +{ + \pagestyle{empty} + \begin{titlepage} + + \setlength{\parindent}{0pt} + \begingroup % for PDF information dictionary + \def\endgraf{ }\def\and{\& }% + \pdfstringdefDisableCommands{\def\\{, }}% overwrite hyperref setup + \hypersetup{pdfauthor={\docauthor}, pdftitle={\docfulltitle{} \docversion}}% + \endgroup + + \noindent\begin{minipage}[t]{5.2cm} + \vspace{0pt}% required to get top-alignment of image and text + \sphinxlogo + \end{minipage} + \hfill\begin{minipage}[t]{0.67\textwidth}\raggedleft% + \vspace{0pt}% required to get top-alignment of image and text + {\psah \doclatextitle{} \docversion\par} + \ifdefined\docowner \medskip\psahhh\docowner\par \fi + \end{minipage} + + \bigskip\bigskip\bigskip + + \newcommand{\psatitlecopyright}{ + \medskip + + \begingroup\raggedleft + \scriptsize{Copyright \textcopyright \doccopyright}\\\par + \endgroup + + \bigskip\bigskip\bigskip\bigskip + } +} +{ + \end{titlepage} + \pagestyle{plain} + \sphinxtableofcontents + \pagestyle{normal} +} + +% emulate small-caps: +\newcommand{\DUrolesc}[1] + {{\psasc\uppercase{#1}}} + +% small caps for special term references +\newcommand{\DUrolescterm}[1] + {{\psasc\uppercase{#1}}} + +% Other glossary term references are emphasized +\newcommand{\DUroleterm}[1] + {{\sphinxstyleemphasis{#1}}} + +% Define issue role as red color +\newcommand{\DUroleissue}[1] + {\textcolor{psaissue}{#1}} + +% Emphasise section and title-text references +\newcommand{\DUrolesecref}[1] + {{\sphinxstyleemphasis{#1}}} + +% SRA definition style +\newcommand{\DUrolesradef}[1] + {{\scalefont{.9}\sphinxstylestrong{#1}}} + +% SRA reference style +\newcommand{\DUrolesraref}[1] + {{\scalefont{.9}#1}} + +% SRA threat card label style +\newcommand{\DUrolesralabel}[1] + {{\psathead{#1}}} + +% associate anchor role with the InnerlinkColor +\newcommand{\DUroleanchor}[1] + {\textcolor{InnerLinkColor}{#1}} + +% Provide sectiontitle role for frontmatter titles that are not in TOC +\newcommand{\DUrolesectiontitle}[1] + {{\psahh #1}} + +\newcommand{\DUroleversionmodified}[1] + {{\psasmall\sphinxstyleemphasis{#1}}} + +% define class environment for the license text (much smaller font) +\newenvironment{sphinxclasslicense} + {\psalicense\setlist[enumerate,1]{label=\bfseries(\roman*)}% + \renewcommand{\DUrolesectiontitle}[1] + {{\psahhh ##1}}% + } + {} + +% If running in a high enough version of sphinx, also +% Divert use of the sphinxalltt environment to use Verbatim. This requires +% setting a default config for Verbatim, which does not work in some earlier +% versions of Sphinx. +% +% This ensures that all literal blocks are rendered using the Verbatim +% configuration below, whether or not sphinx runs the highlighting engine on +% the block. +\newcommand{\useverbatimfortt}{% + \let\sphinxalltt\sphinxVerbatim% + \let\endsphinxalltt\endsphinxVerbatim% + \fvset{commandchars=\\\{\}}% + } + +% Set up the style for the Table of Contents + +\renewcommand{\contentsname}{\psahh Contents} +\contentsmargin{1cm} +\titlecontents{chapter}[1cm] + {\addvspace{16pt}} + {\psahhh\contentslabel{1cm}} + {\psahhh} + {\titlerule[0pt]\contentspage} +\titlecontents{section}[2cm] + {\addvspace{8pt}} + {\psahhhh\contentslabel{2cm}} + {\psahhhh} + {\titlerule[0pt]\contentspage} +\titlecontents{subsection}[2cm] + {} + {\psabody\contentslabel{1.5cm}} + {\psabody} + {\titlerule[0pt]\contentspage} + +% and remove sphinx's ToC overrides +\let\sphinxtableofcontentshook\relax + +% Sphinx/pdflatex does not pick up the chpater title format from titlesec +% So hook the environment to insert formatting in the index title +\renewenvironment{sphinxtheindex}{% + \clearpage + \let\ixtitle\indexname% + \renewcommand{\indexname}{\psahh \ixtitle}% + \phantomsection % needed as no chapter, section, ... created + \begin{theindex}% + \addcontentsline{toc}{chapter}{\ixtitle}% + }{\end{theindex}} + +% This is something of a hack to get consistent, and differentiated API element +% subtitles in the specification. Simple specs might have API elements at +% level 3 headings (subsections), and complex ones at level 4 (subsubsection). +% So using a section level for the subtitles will yield inconsistent results. +% +% psa-api-tool.py uses `rubric` nodes for these sections (so don't use rubric in +% API reference for other things?). Unfortunately, Sphinx does not copy +% docutils and emit a \rubric{} command, it just emits \subsubsection*{}. +% +% As that [starred] command is not used for other elements, we replaced the +% \subsubsection command to intercept these uses and divert them to \rubric{}. +% We can then define \rubric{} as we wish - A bold 10pt runin for the main +% document text, and the original \subsubsection*{} in the appendix. +% +% Older versions of Sphinx directly use \paragraph{} for rubric, so +% also intercept level 5 headings and treat as rubric +% +% Heading 5 is used for all api subtitles +\newcommand{\subtitlett}[1]{{\normalfont\ttfamily #1}} +\newcommand{\apisubtitle}[1]{{\let\sphinxstyleliteralintitle\subtitlett #1}} +\titleformat{\paragraph} + {\psasubtitle} + {} + {0.5em}{\apisubtitle}{} +\titlespacing{\paragraph}{0pt}{*2}{4pt} + +\makeatletter +\let\oldsubsubsection\subsubsection +\renewcommand{\subsubsection} + % Only intercept rubrics in the main section + {\@ifstar{\IfAppendix{\oldsubsubsection*}{\paragraph*}}{\oldsubsubsection}} +\makeatother + +% Format lineblock text (consecutive lines prefixed with |) as a paragraph +% with forced line breaks +\renewenvironment{DUlineblock}[1]{% + \renewcommand{\item}[1][]{\par \renewcommand{\item}[1][]{\\}}% + \setlength{\leftmargin}{#1}% + \raggedright% +}{} + +% Set up item lists, enumerations and definition lists + +% Define spacing for lists +\setlist[1]{topsep=4pt, partopsep=0pt, itemsep=2pt, parsep=3pt plus 1pt minus 1pt} +\setlist[2]{topsep=2pt, partopsep=0pt, itemsep=1pt, parsep=1pt} +\setlist[3]{topsep=2pt, partopsep=0pt, itemsep=1pt, parsep=1pt} + +% Do not use multicols environment for hlist directive, but keep the compactness +\renewenvironment{multicols}[1]{\begingroup}{\endgroup} + +% Use arabic, alpha, roman for level 1-3 +% New versions of sphinx ignore this and set the formatting based on the +% source text +\setlist[enumerate,1]{label=\arabic*.} +\setlist[enumerate,2]{label=\alph*.} +\setlist[enumerate,3]{label=\roman*.} + +% Use bullet, em-dash, and circle for level 1-3 +\setlist[itemize,1]{label=$\bullet$} +\setlist[itemize,2]{label=---} +\setlist[itemize,3]{label=$\circ$} + +% Remove Sphinx hack for formatting multiple terms in definition list +% It forces the list formatting, and breaks the styling done with enumitem +\renewcommand\sphinxlineitem[2]{\item[#1]\leavevmode#2}% + +% Define standard definition list format +\def\termmargin {13ex} +\def\termindent {2ex} +\def\termspace {1ex} + +\setlist[description]{% + style=nextline,% + labelindent=\termindent, labelwidth=!, labelsep=\termspace,% + itemindent=0ex, leftmargin=\termmargin,% + font=\psabody,% + topsep=3pt, partopsep=0pt, itemsep=2pt, parsep=3pt% +} + +% Define API subitem definition list format +% This displays as a grid definition list, almost table-like +% psa-api-tool.py wraps the subitem lists in a apisubitem environment - so +% we can define that environment to set the description format +\def\apiitemwidth {31.5ex} +\def\apiitemindent { 0ex} +\def\apiitemspace {.5ex} + +\newenvironment{sphinxclassapisubitem} + {\setlist[description]{% + style=nextline,% + labelindent=\apiitemindent, labelwidth=\apiitemwidth,% + labelsep=\apiitemspace, itemindent=0ex, leftmargin=!,% + font=\psabody,% + topsep=3pt, partopsep=0pt, itemsep=2pt, parsep=3pt% + }} + {} + +% Define Threat card definition list format +\def\threatlabelwidth {20ex} +\def\threatlabelindent { 0ex} +\def\threatlabelspace {.5ex} + +\newenvironment{sphinxclassthreat} + {\setlist[description]{% + style=nextline,% + labelindent=\threatlabelindent, labelwidth=\threatlabelwidth,% + labelsep=\threatlabelspace, itemindent=0ex, leftmargin=!,% + font=\sphinxstylestrong,% + topsep=6pt plus 2pt minus 1pt, partopsep=0pt,% + itemsep=2pt, parsep=6pt plus 2pt minus 1pt% + }} + {} + +\newenvironment{sphinxclassriskrow} + {\renewcommand{\arraystretch}{1.2}% + \setlength\tabcolsep{0pt}% + \def\sphinxattableend{}% + \vskip\dimexpr-\parskip-\baselineskip\relax% + } + {} diff --git a/tools/templates/psa-api-2025/sphinx-templates/indextoc.html b/tools/templates/psa-api-2025/sphinx-templates/indextoc.html new file mode 100644 index 00000000..549e3123 --- /dev/null +++ b/tools/templates/psa-api-2025/sphinx-templates/indextoc.html @@ -0,0 +1,8 @@ +<!-- +SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +SPDX-License-Identifier: Apache-2.0 +--> + +<ul> +<li class="toctree-l1"><a class="reference internal" href="{{ pathto("psa_c-identifiers.html", 1) }}">Index of API elements</a></li> +</ul> diff --git a/tools/templates/psa-api-2025/sphinx-templates/toc.html b/tools/templates/psa-api-2025/sphinx-templates/toc.html new file mode 100644 index 00000000..878dd9d2 --- /dev/null +++ b/tools/templates/psa-api-2025/sphinx-templates/toc.html @@ -0,0 +1,11 @@ +<!-- +SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +SPDX-License-Identifier: Apache-2.0 +--> + +<hr /> +<h3><a href="{{ pathto(master_doc) }}">{{ dochtmltitle }}</a></h3> +{{ docid }}<br/> +Version {{ docreleasefull }} +<hr /> +{{ toctree() }} diff --git a/tools/templates/psa-api-2025/template-conf.py b/tools/templates/psa-api-2025/template-conf.py new file mode 100644 index 00000000..cb90d882 --- /dev/null +++ b/tools/templates/psa-api-2025/template-conf.py @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +# SPDX-License-Identifier: Apache-2.0 + +# -*- coding: utf-8 -*- +# +# Adjust or reset the template_info dictionary with customized +# sphinx configurations for this template + +template_info['logo_file'] = 'ARM_LOGO-2025_INK_RGB' +template_info['html_theme'] = 'alabaster' +template_info['html_css_files'] = [ + ('https://fonts.googleapis.com', { 'rel': 'preconnect' }), + ('https://fonts.gstatic.com', { 'rel': 'preconnect', 'crossorigin': None }), + ('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,300;0,400;1,300;1,400&display=swap', { 'rel': 'stylesheet' }), + ('https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:ital,wght@0,300;0,400;1,300&display=swap', { 'rel': 'stylesheet' }) +] +template_info['mathjax3_config'] = { + 'chtml': { + 'mtextInheritFont': False, + 'mtextFont': "Lato-Light", + } +} +template_info['latex_pointsize'] = '11pt' +template_info['latex_fonts']= [ + r'\usepackage[default]{lato}', + r'\usepackage[scale=.8]{noto-mono}' + ] +template_info['latex_sphinxsetup'] = [ + # Use black for titles + 'TitleColor={rgb}{0.03,0.01,0.145}', + # Reduce margins + 'hmargin={1.9cm,1.25cm}', + 'vmargin={3.5cm, 3cm}', + 'marginpar=1.27cm', + # Format the verbatim blocks + 'verbatimwithframe=true', + 'verbatimsep=3pt', + 'VerbatimBorderColor={rgb}{0.9,0.9,0.9}', + 'verbatimborder=0.5pt', + 'VerbatimColor={rgb}{0.97,0.97,0.97}', + # format hyperlink color + 'InnerLinkColor={rgb}{0,0.26,0.75}', + 'OuterLinkColor={rgb}{0,0.26,0.75}', + # format admonitions + 'noteBorderColor={rgb}{0.667,0.667,0.667}', + 'warningBorderColor={rgb}{.75,0.5,0.5}', + 'warningborder=2pt', + # Use attention admonition for the front page banner + 'attentionBorderColor={rgb}{.8,.8,0}', + 'attentionBgColor={rgb}{1,1,.7}', + 'attentionborder=1pt', + # Use error admonition for rationale boxes + 'errorBorderColor={rgb}{.5,.75,.5}', + 'errorBgColor={rgb}{.9,.95,.9}', + 'errorborder=1pt', + # Use hint admonition for comment boxes + 'hintBorderColor={rgb}{.6,.6,.6}', + 'hintBgColor={rgb}{.97,.97,.97}', + 'hintborder=0pt', + 'hintTextColor={rgb}{.4,.4,.4}', + # Use the normal font for headings + 'HeaderFamily=\\normalfont\\mdseries', +] +template_info['latex_table_style'] = ['booktabs','nocolorrows'] +template_info['graphviz_dot_args'] = [ + '-Gfontname=Lato', + '-Gfontsize=12', + '-Nfontname=Lato', + '-Nfontsize=12', + '-Efontname=Lato', + '-Efontsize=12' +] + +def make_doc_filename(info, id, title, version, status): + doc_parts = [id.replace(' ',''), title, version] + status = status.split(' ')[-1].lower() + if status != 'release': + doc_parts += [status] + if all((k in info for k in ('doc_id','quality','issue_no'))): + return '-'.join(doc_parts) + return None +template_info['make_filename'] = make_doc_filename + +template_info['front_sections'] = [ + 'abstract', + 'release-info', + 'todos', + 'license', + 'references', + 'terms', + 'potential-for-change', + 'conventions', + 'pseudocode', + 'assembler', + 'current-status', + 'feedback', + 'inclusive-language', + ] + +if 'author' not in doc_info: + doc_info['author'] = 'Arm Limited' +doc_info.setdefault('feedback', 'visit :url:`github.com/arm-software/psa-api/issues`' + + ' to create a new issue at the PSA Certified API GitHub project') +# force use of Arm copyright notice and OSS license +doc_info['copyright'] = 'Arm Limited and/or its affiliates' +doc_info['license'] = 'arm-psa-certified-api-license' diff --git a/tools/templates/psa-api-2025/title-page.rst b/tools/templates/psa-api-2025/title-page.rst new file mode 100644 index 00000000..297476ac --- /dev/null +++ b/tools/templates/psa-api-2025/title-page.rst @@ -0,0 +1,47 @@ +.. SPDX-FileCopyrightText: Copyright 2018-2026 Arm Limited +.. SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +.. only:: html + + .. template-image:: ARM_LOGO-2025_INK_RGB.svg + :alt: Arm + :class: titlelogo + +========================== +|docrsttitle| |docversion| +========================== + +.. raw:: latex + + \psabody\color{psaink} + + \begin{psatitle} + +.. csv-table:: + :class: titletable borderless + :align: left + + Document number:, |docid| + Release Quality:, |docquality| + Issue Number:, |docissue| + Date of Issue:, |docdate| + +.. raw:: latex + + \psatitlecopyright + +.. insert-banner:: + +.. insert-section:: Abstract + :section: abstract + :not-in-toc: + +.. raw:: latex + + \end{psatitle} + +.. only:: html + + .. insert-section:: Contents + :not-in-toc: + :keep-if-empty: