blob: f95a5a43ae912056a8bea9ffd3bc8e30cfe9dbe6 [file] [log] [blame]
Philipp Wagner80cc8232020-10-29 16:26:09 +00001#!/usr/bin/env python3
2# Copyright lowRISC contributors.
3# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4# SPDX-License-Identifier: Apache-2.0
5
6"""Build software running on OTBN
7
8Each assembly source file is first assembled with otbn-as. All resulting objects
9are then linked with otbn-ld. The resulting ELF file is converted into an
10embeddable RV32 object file using objcopy. In this object, all symbols
11are prefixed with `_otbn_app_<appname>_` (only global symbols are included).
12
13environment variables:
14 This script, and the tools called from it, rely on the following environment
15 variables for configuration. All environment variables are optional, and
16 sensible default values are provided (tools are generally expected to be in
17 the $PATH).
18
19 OTBN_AS path to otbn-as, the OTBN assembler
20 OTBN_LD path to otbn-ld, the OTBN linker
21 RV32_TOOL_LD path to RV32 ld
22 RV32_TOOL_AS path to RV32 as
23 RV32_TOOL_OBJCOPY path to RV32 objcopy
24
25 The RV32* environment variables are used by both this script and the OTBN
26 wrappers (otbn-as and otbn-ld) to find tools in a RV32 toolchain.
27
28outputs:
29 The build process produces multiple files inside the output directory.
30
31 <src_file>.o the compiled source files
32 <app_name>.elf the compiled and linked application targeting OTBN
33 <app_name>.rv32embed.o the application as embeddable object for RV32
34
35"""
36
37import argparse
38import logging as log
39import os
40import shlex
41import subprocess
42import sys
Rupert Swarbrickd5aa79a2020-12-14 13:04:21 +000043import tempfile
Philipp Wagner80cc8232020-10-29 16:26:09 +000044from pathlib import Path
Philipp Wagner6f51ecb2020-11-19 23:05:22 +000045from typing import List, Optional
Philipp Wagner80cc8232020-10-29 16:26:09 +000046
Philipp Wagner80cc8232020-10-29 16:26:09 +000047REPO_TOP = Path(__file__).parent.parent.resolve()
48
Rupert Swarbrickd5aa79a2020-12-14 13:04:21 +000049
50def cmd_to_str(cmd: List[str]) -> str:
Philipp Wagner80cc8232020-10-29 16:26:09 +000051 return ' '.join([shlex.quote(str(a)) for a in cmd])
52
53
Rupert Swarbrickd5aa79a2020-12-14 13:04:21 +000054def run_cmd(args, display_cmd=None):
55 '''Run the command in args.
56
57 If display_cmd is not None, it should be a string that is printed instead
58 of the actual arguments that ran (for hiding the details of temporary
59 files).
60
61 '''
62 str_args = [str(a) for a in args]
63 info_msg = cmd_to_str(str_args) if display_cmd is None else display_cmd
64 log.info(info_msg)
65
66 subprocess.run(str_args, check=True)
67
68
69def run_tool(tool: str, out_file: Path, args) -> None:
70 '''Run tool to produce out_file (using an '-o' argument)
71
72 This works by writing to a temporary file (in the same directory) and then
73 atomically replacing any existing destination file when done. This is
74 needed if we need to run multiple otbn_build processes that generate the
75 same files in parallel (a requirement because of our current Meson-based
76 infrastructure).
77
78 '''
79 out_dir, out_base = os.path.split(out_file)
80 tmpfile = tempfile.NamedTemporaryFile(prefix=out_base, dir=out_dir,
81 delete=False)
82 try:
83 run_cmd([tool, '-o', tmpfile.name] + args,
84 cmd_to_str([tool, '-o', out_file] + args))
85
86 # If we get here, the tool ran successfully, producing the output file.
87 # Use os.replace to rename appropriately.
88 os.replace(tmpfile.name, out_file)
89 finally:
90 # When we're done, or if something went wrong, close and try to delete
91 # the temporary file. The unlink should fail if the os.replace call
92 # above succeeded. That's fine.
93 tmpfile.close()
94 try:
95 os.unlink(tmpfile.name)
96 except FileNotFoundError:
97 pass
Philipp Wagner80cc8232020-10-29 16:26:09 +000098
99
100def call_otbn_as(src_file: Path, out_file: Path):
101 otbn_as_cmd = os.environ.get('OTBN_AS',
102 str(REPO_TOP / 'hw/ip/otbn/util/otbn-as'))
Rupert Swarbrickd5aa79a2020-12-14 13:04:21 +0000103 run_tool(otbn_as_cmd, out_file, [src_file])
Philipp Wagner80cc8232020-10-29 16:26:09 +0000104
105
Philipp Wagner6f51ecb2020-11-19 23:05:22 +0000106def call_otbn_ld(src_files: List[Path], out_file: Path, linker_script: Optional[Path]):
Philipp Wagner80cc8232020-10-29 16:26:09 +0000107 otbn_ld_cmd = os.environ.get('OTBN_LD',
108 str(REPO_TOP / 'hw/ip/otbn/util/otbn-ld'))
Philipp Wagner6f51ecb2020-11-19 23:05:22 +0000109
Rupert Swarbrickd5aa79a2020-12-14 13:04:21 +0000110 args = []
Philipp Wagner6f51ecb2020-11-19 23:05:22 +0000111 if linker_script:
Rupert Swarbrickd5aa79a2020-12-14 13:04:21 +0000112 args += ['-T', linker_script]
113 args += src_files
114 run_tool(otbn_ld_cmd, out_file, args)
Philipp Wagner80cc8232020-10-29 16:26:09 +0000115
116
117def call_rv32_objcopy(args: List[str]):
118 rv32_tool_objcopy = os.environ.get('RV32_TOOL_OBJCOPY',
119 'riscv32-unknown-elf-objcopy')
Rupert Swarbrickd5aa79a2020-12-14 13:04:21 +0000120 run_cmd([rv32_tool_objcopy] + args)
Philipp Wagner80cc8232020-10-29 16:26:09 +0000121
122
123def main() -> int:
124 parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
125 parser.add_argument(
126 '--out-dir',
127 '-o',
128 required=False,
129 default=".",
130 help="Output directory (default: %(default)s)")
131 parser.add_argument(
Rupert Swarbrick8dd960c2020-12-14 10:26:17 +0000132 '--verbose',
133 '-v',
134 action='store_true',
135 help='Print commands that are executed.')
136 parser.add_argument(
Philipp Wagner6f51ecb2020-11-19 23:05:22 +0000137 '--script',
138 '-T',
139 dest="linker_script",
140 required=False,
141 help="Linker script")
142 parser.add_argument(
Philipp Wagner80cc8232020-10-29 16:26:09 +0000143 '--app-name',
144 '-n',
145 required=False,
146 help="Name of the application, used as basename for the output. "
147 "Default: basename of the first source file.")
148 parser.add_argument('src_files', nargs='+', type=str, metavar='SRC_FILE')
149 args = parser.parse_args()
150
Rupert Swarbrick8dd960c2020-12-14 10:26:17 +0000151 log_level = log.INFO if args.verbose else log.WARNING
152 log.basicConfig(level=log_level, format="%(message)s")
153
Philipp Wagner80cc8232020-10-29 16:26:09 +0000154 out_dir = Path(args.out_dir)
155 out_dir.mkdir(exist_ok=True)
156
157 src_files = [Path(f) for f in args.src_files]
158 for src_file in src_files:
159 if not src_file.exists():
160 log.fatal("Source file %s not found." % src_file)
161 return 1
162 obj_files = [out_dir / f.with_suffix('.o').name for f in src_files]
163
164 app_name = args.app_name or str(src_files[0].stem)
165
166 try:
167 for src_file, obj_file in zip(src_files, obj_files):
168 call_otbn_as(src_file, obj_file)
169
170 out_elf = out_dir / (app_name + '.elf')
Philipp Wagner6f51ecb2020-11-19 23:05:22 +0000171 call_otbn_ld(obj_files, out_elf, linker_script = args.linker_script)
Philipp Wagner80cc8232020-10-29 16:26:09 +0000172
Rupert Swarbrick1b0529b2021-02-01 15:08:39 +0000173 # Use objcopy to create an ELF that can be linked into a RISC-V binary
174 # (to run on Ibex). This should set flags for all sections to look like
175 # rodata (since they're not executable on Ibex, nor does it make sense
176 # for Ibex code to manipulate OTBN data sections "in place"). We name
177 # them with a .otbn prefix, so end up with e.g. .rodata.otbn.text and
178 # .rodata.otbn.data.
179 #
180 # Symbols that are exposed by the binary (including those giving the
181 # start and end of imem and dmem) will be relocated as part of the
182 # link, so they'll give addresses in the Ibex address space. So that
183 # the RISC-V binary can link multiple OTBN applications, we give them
184 # an application-specific prefix. (Note: This prefix is used in
185 # sw/device/lib/runtime/otbn.h: so needs to be kept in sync with that).
186 sym_pfx = '_otbn_app_{}_'.format(app_name)
Philipp Wagner80cc8232020-10-29 16:26:09 +0000187 out_embedded_obj = out_dir / (app_name + '.rv32embed.o')
Rupert Swarbrick1b0529b2021-02-01 15:08:39 +0000188 args = (['-O', 'elf32-littleriscv',
189 '--prefix-sections=.rodata.otbn',
190 '--set-section-flags=*=alloc,load,readonly',
191 '--prefix-symbols', sym_pfx] +
192 [out_elf,
193 out_embedded_obj])
Philipp Wagner80cc8232020-10-29 16:26:09 +0000194
195 call_rv32_objcopy(args)
Rupert Swarbrick13dc5c32021-02-01 15:17:39 +0000196
197 # After objcopy has finished, we have to do a little surgery to
198 # overwrite the ELF e_type field (a 16-bit little-endian number at file
199 # offset 0x10). It will currently be 0x2 (ET_EXEC), which means a
200 # fully-linked executable file. Binutils doesn't want to link with
201 # anything of type ET_EXEC (since it usually wouldn't make any sense to
202 # do so). Hack the type to be 0x1 (ET_REL), which means an object file.
203 with open(out_embedded_obj, 'r+b') as emb_file:
204 emb_file.seek(0x10)
205 emb_file.write(b'\1\0')
206
Philipp Wagner80cc8232020-10-29 16:26:09 +0000207 except subprocess.CalledProcessError as e:
208 # Show a nicer error message if any of the called programs fail.
209 log.fatal("Command {!r} returned non-zero exit code {}".format(
210 cmd_to_str(e.cmd), e.returncode))
211 return 1
212
213 return 0
214
215
216if __name__ == "__main__":
217 sys.exit(main())