Codelab: Defining a new board configuration

This codelab walks through the process of defining a new board configuration in Fuchsia using the Bazel build system. Board configurations are crucial for specifying the hardware-specific components, drivers, and settings required to run Fuchsia on a particular device.

Prerequisites

What is a board configuration?

A board configuration in Fuchsia encapsulates all the necessary information to build a system image for a specific piece of hardware. This includes:

  • Hardware identification: Name and version.
  • Code: Board-specific drivers and platform features.
  • Storage layout: How partitions are arranged.
  • Boot process: Kernel arguments and device tree information.
  • Filesystem details: Options like compression.

Board configurations are defined using the fuchsia_board_configuration rule in BUILD.bazel files, typically located within the //boards directory.

Writing a board configuration

Let's explore the key attributes of the fuchsia_board_configuration rule with examples.

Basic setup

Every board configuration needs a name, board name, and partitions config. For example:

# //boards/my_awesome_board/BUILD.bazel
load(
    "@rules_fuchsia//fuchsia:assembly.bzl",
    "fuchsia_board_configuration",
)

fuchsia_board_configuration(
    name = "my_awesome_board",  # Target name for the build system
    board_name = "my_awesome_board",  # Identifier used by tools like ffx
    version = "1.2.3.4",  # Version of the board configuration
    partitions_configuration = "//boards/partitions/my_awesome_board",
    # ... more attributes to come
)

Partitions configuration

The partitions_configuration field points to a fuchsia_partitions_configuration target, typically defined in a BUILD.bazel file within a subdirectory of //boards/partitions. This target defines the layout and types of partitions for the board, such as where the ZBI, VBMeta, and FXFS are located.

These images and partitions are often organized into different ABR slots.

For example in //boards/partitions/my_awesome_board/BUILD.bazel:

load(
    "@rules_fuchsia//fuchsia:assembly.bzl",
    "PARTITION_TYPE",
    "SLOT",
    "fuchsia_bootloader_partition",
    "fuchsia_partition",
    "fuchsia_partitions_configuration",
)

# Standard Fuchsia partitions
fuchsia_partition(
    name = "zircon_a",
    partition_name = "zircon_a",
    slot = SLOT.A,
    type = PARTITION_TYPE.ZBI,
)

fuchsia_partition(
    name = "vbmeta_a",
    partition_name = "vbmeta_a",
    slot = SLOT.A,
    type = PARTITION_TYPE.VBMETA,
)

fuchsia_partition(
    name = "fxfs",
    partition_name = "fvm", # Or "fxfs" depending on board
    type = PARTITION_TYPE.FXFS,
)

# ... (typically define all slots A, B, R for ZBI/VBMeta)

# Board-specific bootloader partition
fuchsia_bootloader_partition(
    name = "my_bootloader",
    image = "//boards/my_awesome_board/firmware:my_awesome_bootloader.img",
    partition_name = "bootloader",

    # The type is used by the board's paver driver to map to a particular
    # partition during Over-the-Air (OTA) updates.
    type = "",
)

# The main configuration, referencing the partition targets
fuchsia_partitions_configuration(
    name = "my_awesome_board",

    # `ffx` compares `hardware_revision` to the fastboot variable `hw-revision`,
    # and refused to flash the device is they do not match. This ensures that
    # the wrong image is not flashed to the wrong board.
    hardware_revision = "my_awesome_board_rev1",
    bootloader_partitions = [
        ":my_bootloader",
    ],
    partitions = [
        ":zircon_a",
        ":vbmeta_a",
        ":fxfs",
        # ... and other partition targets
    ],
)

Explanation:

  • fuchsia_partition defines individual partitions like ZBI, VBMeta, and FXFS, specifying their name, type, and slot if applicable.
  • fuchsia_bootloader_partition defines bootloader partitions, linking to the bootloader image target. Assembly treats bootloader partitions as "unslotted".
  • Define a fuchsia_partitions_configuration target that describes the partitions on the device. This is required for all boards.

Including code: Board-specific vs. platform

There are two primary ways to include code in your board configuration:

Board input bundles (BIBs)

BIBs are used to package board-specific drivers, configurations, and other files that are unique to the hardware. These are defined using the fuchsia_board_input_bundle rule. For example:

# //boards/my_awesome_board/BUILD.bazel
load(
    "@rules_fuchsia//fuchsia:assembly.bzl",
    "fuchsia_board_input_bundle",
)

# BIB for a board-specific compass driver
fuchsia_board_input_bundle(
    name = "my-compass",
    bootfs_driver_packages = [
        "//src/devices/board/drivers/my-compass",
    ],
)

The BIB can be included in the board configuration using the board_input_bundles attribute. For example:

# //boards/my_awesome_board/BUILD.bazel
fuchsia_board_configuration(
    name = "my_awesome_board",
    board_name = "my_awesome_board",
    version = "1.2.3.4",
    partitions_configuration = "//boards/partitions/my_awesome_board",

    board_input_bundles = [
        ":my-compass",  # Reference the BIB target
    ],

)

It is best practice to create a single BIB for each logical group of hardware that may also be found on other boards. This allow similar boards to share BIBs. Examples include compass, paver, rtc, etc.

Provided features

This attribute allows the board to declare that it supports or requires certain platform-level features. The assembly system can then include the necessary platform code based on these flags.

This mechanism decouples the board definition from the implementation details of common platform features. Instead of the board providing the code, it declares the need for it. For example:

# //boards/my_awesome_board/BUILD.bazel
fuchsia_board_configuration(
    name = "my_awesome_board",
    board_name = "my_awesome_board",
    version = "1.2.3.4",
    partitions_configuration = "//boards/partitions/my_awesome_board",

    provided_features = [
        "fuchsia::driver_runtime",
        "fuchsia::vulkan_support",
    ],

)

In this case, the board is stating it has hardware requiring platform-provided runtime drivers and Vulkan support.

Device tree

If your board uses a device tree, you can specify the .dtb binary target using the devicetree attribute. For example:

# In //boards/my_awesome_board/BUILD.bazel
fuchsia_board_configuration(
    name = "my_awesome_board",
    board_name = "my_awesome_board",
    version = "1.2.3.4",
    partitions_configuration = "//boards/partitions/my_awesome_board",

    devicetree = "//boards/my_awesome_board/firmware:board.dtb",

)

Filesystem options

The filesystems attribute takes a dictionary to configure various aspects of the images, such as ZBI compression. For a full list of available options, see BoardFilesystemConfig. For example:

# In //boards/my_awesome_board/BUILD.bazel
fuchsia_board_configuration(
    name = "my_awesome_board",
    board_name = "my_awesome_board",
    version = "1.2.3.4",
    partitions_configuration = "//boards/partitions/my_awesome_board",

    filesystems = {
        "zbi": {
            "compression": "zstd.max",
        },
    },

)

You can also configure vbmeta signing keys in the filesystems attribute. For example:

# In //boards/my_awesome_board/BUILD.bazel
fuchsia_board_configuration(
    name = "my_awesome_board",
    board_name = "my_awesome_board",
    version = "1.2.3.4",
    partitions_configuration = "//boards/partitions/my_awesome_board",

    filesystems = {
        "vbmeta": {
            "key": "//path/to/my:vbmeta_private_key",
            "key_metadata": "//path/to/my:vbmeta_key_metadata",
        },
    },

)

Post-processing script

If your board requires additional image processing steps after the standard assembly (e.g., to create a vendor-specific boot image format), you can use the post_processing_script attribute.

  1. Define a fuchsia_post_processing_script target. For example:

    # In //boards/my_awesome_board/BUILD.bazel
    load(
        "@rules_fuchsia//fuchsia:assembly.bzl",
        "fuchsia_post_processing_script",
    )
    
    fuchsia_post_processing_script(
        name = "my_post_processing_script",
        post_processing_script_path = "tools/sign_image.sh",
        post_processing_script_args = [
            "-i", "zbi",
            "-o", "zbi.signed",
        ],
        post_processing_script_inputs = {
            "//path/to/keys:private_key": "keys/private_key",
        },
    )
    
  2. Reference it in your board configuration. For example:

    # In //boards/my_awesome_board/BUILD.bazel
    fuchsia_board_configuration(
        name = "my_awesome_board",
        board_name = "my_awesome_board",
        version = "1.2.3.4",
        partitions_configuration = "//boards/partitions/my_awesome_board",
    
        post_processing_script = ":my_post_processing_script",
    
    )
    

Next steps

This codelab covered the basics of defining a new board configuration using Bazel in Fuchsia. You've seen how to use fuchsia_board_configuration and related rules to specify everything from drivers to filesystem options.

To continue learning, you can:

  • Explore the existing board configurations in the //boards directory, paying attention to the BUILD.bazel files in subdirectories like x64, arm64, and vim3.
  • Dive deeper into Fuchsia Software Assembly Concepts.