[bazel] add rule / macro to create multislot flash binaries
This adds a Bazel macro (`opentitan_multislot_flash_binary`) and custom
rule (`assemble_flash_image`) to build OpenTitan flash images with
multiple slots / stages filled for simple E2E testing.
This partially addresses #13511.
Signed-off-by: Timothy Trippel <ttrippel@google.com>
diff --git a/rules/opentitan.bzl b/rules/opentitan.bzl
index a460300..9601f62 100644
--- a/rules/opentitan.bzl
+++ b/rules/opentitan.bzl
@@ -42,6 +42,11 @@
"fpga_cw310": ["@//sw/device/lib/arch:fpga_cw310"],
}
+# Default keys used to sign ROM_EXT and Owner images for testing.
+DEFAULT_SIGNING_KEYS = {
+ "test_key_0": "@//sw/device/silicon_creator/rom/keys:test_private_key_0",
+}
+
def _obj_transform_impl(ctx):
cc_toolchain = find_cc_toolchain(ctx).cc
outputs = []
@@ -516,6 +521,49 @@
},
)
+def _assemble_flash_image_impl(ctx):
+ output = ctx.actions.declare_file(ctx.attr.output)
+ outputs = [output]
+ inputs = []
+ arguments = [
+ "image",
+ "assemble",
+ "--mirror",
+ "false",
+ "--output",
+ output.path,
+ "--size",
+ ctx.attr.image_size,
+ ]
+ for binary, offset in ctx.attr.binaries.items():
+ inputs.extend(binary.files.to_list())
+ arguments.append("{}@{}".format(binary.files.to_list()[0].path, offset))
+ ctx.actions.run(
+ outputs = outputs,
+ inputs = inputs,
+ arguments = arguments,
+ executable = ctx.executable._opentitantool,
+ )
+ return [DefaultInfo(
+ files = depset(outputs),
+ data_runfiles = ctx.runfiles(files = outputs),
+ )]
+
+assemble_flash_image = rv_rule(
+ implementation = _assemble_flash_image_impl,
+ attrs = {
+ "image_size": attr.string(),
+ "output": attr.string(),
+ "binaries": attr.label_keyed_string_dict(allow_empty = False),
+ "_opentitantool": attr.label(
+ default = "//sw/host/opentitantool:opentitantool",
+ allow_single_file = True,
+ executable = True,
+ cfg = "exec",
+ ),
+ },
+)
+
def opentitan_binary(
name,
platform = OPENTITAN_PLATFORM,
@@ -652,10 +700,7 @@
Containing all targets for a single device for the above generated rules.
filegroup named: <name>
Containing all targets across all devices for the above generated rules.
- Returns:
- List of targets generated by all the above rules (except the filegroup).
"""
-
deps = kwargs.pop("deps", [])
all_targets = []
for (device, dev_deps) in PER_DEVICE_DEPS.items():
@@ -726,12 +771,98 @@
toolchains = ["@rules_cc//cc:toolchain_type"],
)
+def opentitan_multislot_flash_binary(
+ name,
+ srcs,
+ image_size,
+ platform = OPENTITAN_PLATFORM):
+ """A helper macro for generating multislot OpenTitan binary flash images.
+
+ This macro is mostly a wrapper around the `assemble_flash_image` rule, that
+ invokes `opentitantool` to stitch together multiple `opentitan_flash_binary`
+ images to create a single image for bootstrapping. This enables efficient
+ testing by only requiring one boostrap operation to load both silicon
+ creator and owner stages of flash.
+ Args:
+ @param name: The name of this rule.
+ @param srcs: A dictionary of `opentitan_flash_binary` targets (to stitch
+ together) as keys, and key/offset configurations as values.
+ @param image_size: The final flash image_size to pass to `opentitantool`.
+ @param platform: The target platform for the artifacts.
+ Emits rules:
+ For each device in per_device_deps entry:
+ rules emitted by `opentitan_binary` named: see `opentitan_binary` macro
+ assemble_flash_image named: <name>_<device>_bin_signed
+ bin_to_vmem named: <name>_<device>_vmem64_signed
+ scrambled_flash_vmem named: <name>_<device>_scr_vmem64_signed
+ filegroup named: <name>_<device>
+ Containing all targets for a single device for the above generated rules.
+ filegroup named: <name>
+ Containing all targets across all devices for the above generated rules.
+ """
+ all_targets = []
+ for (device, _) in PER_DEVICE_DEPS.items():
+ devname = "{}_{}".format(name, device)
+ dev_targets = []
+ signed_dev_binaries = {}
+ for src, configs in srcs.items():
+ if "key" not in configs:
+ fail("Missing signing key for binary: {}".format(src))
+ if "offset" not in configs:
+ fail("Missing offset for binary: {}".format(src))
+ signed_dev_binary = "{}_{}_bin_signed_{}".format(
+ src,
+ device,
+ configs["key"],
+ )
+ signed_dev_binaries[signed_dev_binary] = configs["offset"]
+
+ # Assemble the signed binaries into a single binary.
+ signed_bin_name = "{}_bin_signed".format(devname)
+ dev_targets.append(":" + signed_bin_name)
+ assemble_flash_image(
+ name = signed_bin_name,
+ output = "{}.signed.bin".format(devname),
+ image_size = image_size,
+ binaries = signed_dev_binaries,
+ )
+
+ # Generate a VMEM64 from the binary.
+ signed_vmem_name = "{}_vmem64_signed".format(devname)
+ dev_targets.append(":" + signed_vmem_name)
+ bin_to_vmem(
+ name = signed_vmem_name,
+ bin = signed_bin_name,
+ platform = platform,
+ word_size = 64, # Backdoor-load VMEM image uses 64-bit words
+ )
+
+ # Scramble signed VMEM64.
+ scr_signed_vmem_name = "{}_scr_vmem64_signed".format(devname)
+ dev_targets.append(":" + scr_signed_vmem_name)
+ scramble_flash_vmem(
+ name = scr_signed_vmem_name,
+ vmem = signed_vmem_name,
+ platform = platform,
+ )
+
+ # Create a filegroup with just the current device's targets.
+ native.filegroup(
+ name = devname,
+ srcs = dev_targets,
+ )
+ dev_targets.extend(dev_targets)
+
+ # Create a filegroup with all assembled flash images.
+ native.filegroup(
+ name = name,
+ srcs = all_targets,
+ )
+
def opentitan_flash_binary(
name,
platform = OPENTITAN_PLATFORM,
- signing_keys = {
- "test_key_0": "@//sw/device/silicon_creator/rom/keys:test_private_key_0",
- },
+ signing_keys = DEFAULT_SIGNING_KEYS,
signed = False,
manifest = None,
**kwargs):
@@ -762,10 +893,7 @@
Containing all targets for a single device for the above generated rules.
filegroup named: <name>
Containing all targets across all devices for the above generated rules.
- Returns:
- List of targets generated by all the above rules (except the filegroup).
"""
-
deps = kwargs.pop("deps", [])
all_targets = []
for (device, dev_deps) in PER_DEVICE_DEPS.items():
diff --git a/rules/opentitan_test.bzl b/rules/opentitan_test.bzl
index caf6c4e..385a729 100644
--- a/rules/opentitan_test.bzl
+++ b/rules/opentitan_test.bzl
@@ -389,7 +389,13 @@
else:
flash = "{}_{}_bin".format(ot_flash_binary, target)
if signed:
- flash += "_signed_{}".format(key)
+ flash += "_signed"
+
+ # Multislot flash binaries could have different slots / stages
+ # signed with different keys. Therefore, the key name will not be
+ # part of the target name for such images.
+ if key != "multislot":
+ flash += "_{}".format(key)
# If test is to be run in ROM we load the same image into flash as a
# as a placeholder (since execution will never reach flash). Moreover,