| #!/usr/bin/env python3 | |
| # Copyright lowRISC contributors. | |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| '''A wrapper around riscv32-unknown-elf-ld for OTBN | |
| This just adds the OTBN linker script and calls the underlying | |
| linker.''' | |
| from contextlib import contextmanager | |
| import os | |
| import subprocess | |
| import sys | |
| import tempfile | |
| from typing import Iterator, List, Optional | |
| from mako.template import Template # type: ignore | |
| from mako import exceptions # type: ignore | |
| from shared.mem_layout import get_memory_layout | |
| from shared.toolchain import find_tool | |
| def interpolate_linker_script(in_path: str, out_path: str) -> None: | |
| mems = get_memory_layout() | |
| try: | |
| template = Template(filename=in_path) | |
| rendered = template.render(imem_lma = mems['IMEM'][0], | |
| imem_length = mems['IMEM'][1], | |
| dmem_lma = mems['DMEM'][0], | |
| dmem_length = mems['DMEM'][1]) | |
| except OSError as err: | |
| raise RuntimeError(str(err)) from None | |
| except: # noqa: 722 | |
| raise RuntimeError(exceptions.text_error_template().render()) from None | |
| try: | |
| with open(out_path, 'w') as out_file: | |
| out_file.write(rendered) | |
| except FileNotFoundError: | |
| raise RuntimeError('Failed to open output file at {!r}.' | |
| .format(out_path)) from None | |
| @contextmanager | |
| def mk_linker_script() -> Iterator[str]: | |
| ld_in = os.path.abspath(os.path.join(os.path.dirname(__file__), | |
| '..', 'data', 'otbn.ld.tpl')) | |
| with tempfile.TemporaryDirectory(prefix='otbn-ld-') as tmpdir: | |
| ld_out = os.path.join(tmpdir, 'otbn.ld') | |
| try: | |
| interpolate_linker_script(ld_in, ld_out) | |
| except RuntimeError as err: | |
| sys.stderr.write('Failed to interpolate linker script: {}\n' | |
| .format(err)) | |
| return 1 | |
| yield ld_out | |
| def run_ld(ld_script: Optional[str], args: List[str]) -> int: | |
| '''Run the underlying linker and return the status code''' | |
| ld_name = find_tool('ld') | |
| # The --no-check-sections argument tells ld not to complain when we have | |
| # more than one section with the same VMA. Since we have a Harvard | |
| # architecture where data and instructions both start at zero, we expect | |
| # that to happen. | |
| cmd = [ld_name, '--no-check-sections'] | |
| if ld_script is not None: | |
| cmd.append('--script={}'.format(ld_script)) | |
| cmd += args | |
| try: | |
| return subprocess.run(cmd).returncode | |
| except FileNotFoundError: | |
| sys.stderr.write('Unknown command: {!r}. ' | |
| '(is it installed and on your PATH?)\n' | |
| .format(ld_name)) | |
| return 127 | |
| def main() -> int: | |
| # Only add the --script argument if the caller isn't supplying one | |
| # themselves. This argument accumulates (so -T foo -T bar is like | |
| # concatenating foo and bar), so we mustn't supply our own if the user | |
| # has one. | |
| needs_script = True | |
| for arg in sys.argv[1:]: | |
| if arg == '-T' or arg.startswith('--script='): | |
| needs_script = False | |
| break | |
| if needs_script: | |
| with mk_linker_script() as script_path: | |
| return run_ld(script_path, sys.argv[1:]) | |
| else: | |
| return run_ld(None, sys.argv[1:]) | |
| if __name__ == '__main__': | |
| sys.exit(main()) |