# Trusted Firmware-A: a read-only mirror whose top-level directories are the boot sequence

> Arm's reference secure world firmware for A-profile processors, mirrored read-only, implementing five Arm interface standards across a five-stage boot, with the project explicitly telling users to penetration-test their own derivative and a version scheme where the patch component is reserved for long-term support releases.

**ARM-software/arm-trusted-firmware** — Read-only mirror of Trusted Firmware-A

- Repository: https://github.com/ARM-software/arm-trusted-firmware
- Website: https://www.trustedfirmware.org/projects/tf-a
- Stars: 2,262 · Forks: 1,596
- Language: C
- License: NOASSERTION
- Published: 2026-09-30 · Updated: 2026-09-30 · Language: en
- Canonical page: https://hysenlabs.com/projects/arm-software-arm-trusted-firmware

## This repository is a mirror, and the review workflow is elsewhere

The first line of the project description says it is a read-only mirror of Trusted Firmware-A. That single fact determines how you interact with it. You do not file issues here, you do not open pull requests here, and nothing in this tree is the place where a change is reviewed. The tree tells you where that is. There is a file whose name is a well-known code review system's configuration, which is how the project configures review against a different host than GitHub, and there is a developer certificate of origin file, which is the sign-off mechanism that such a workflow uses. So the development loop is upstream, on review software hosted elsewhere, and this repository exists so the code is readable and cloneable from where people already are. For a reader that is a good arrangement, because the source is public and the history is intact. For a contributor it means the instructions in the project documentation, not this repository, are the ones to follow. One detail that is worth flagging for anyone automating against this repository: the copyright line in the readme runs to 2025 while the licence header inside the build file runs to 2026, so the two are maintained separately and one of them will be behind.

## Five boot stage directories, and they are the boot flow

Read the top-level directory listing as a diagram rather than a file list, because the names are the architecture. There are five directories named for boot stages: a first stage, a second stage, an upgrade stage, and then two more. The first three are the ARMv7 secure boot sequence, where the first stage runs from ROM and loads the second, and the upgrade stage handles a firmware update path. The fourth is the important one for anyone working on a modern processor: it is the secure monitor that runs at Exception Level 3, which is the highest exception level on an A-profile part and the only place with access to both worlds. The fifth is its 32-bit counterpart, the AArch32 secure monitor, which is what lets a 64-bit boot chain keep a 32-bit exception level, a configuration that is more common than it sounds because a 32-bit monitor is easier to write correctly on hardware that supports both. So the tree is a boot flow you can read top to bottom, and the README confirms the structure by describing the software as a reference implementation of secure world software that includes an Exception Level 3 Secure Monitor, in either the 32-bit or 64-bit execution state. Around the boot stages sit the platform porting directory, the drivers directory, the libraries, the device tree sources, the services and the tools, plus a subdirectory of tests.

## The project tells you to penetration-test your own derivative

One paragraph in this README matters more than any feature list, and it is easy to skim past because it is written as encouragement rather than as a warning. Users are encouraged to do their own security validation, including penetration testing, on any secure world code derived from this project. Read that again with the subject in mind. It is not saying the code has known problems. It is saying the project's own confidence is not a substitute for your own assessment, and that if you ship secure world firmware you should assume someone will attack it and you should have found the problems first. For an adopter that sentence should change the plan rather than decorate it. A TF-A integration touches the most privileged code on the part, it runs before your operating system, and a fault in it is not a crash but a compromise. So the budget for this project has to include an independent review and an adversarial test of your own configuration, not just a functional bring-up. There is a second reason this sentence is there. The implementation is a reference implementation, explicitly described as a starting point for productisation, which means the defaults are a sane baseline rather than a hardened configuration for your board, and hardening is the part you own.

## Version 2.15.0, where the patch component means long-term support

The version scheme is defined in the build file, and one line of it is a policy statement. 

```makefile
VERSION_MAJOR			:= 2
VERSION_MINOR			:= 15
# VERSION_PATCH is only used for LTS releases
VERSION_PATCH			:= 0
```

So a patch release is not a routine bug fix; it is reserved for long-term support releases. If you see a non-zero patch component, you are looking at a maintained long-term branch rather than the main development line, which is exactly the information an integrator needs when choosing which branch to base a product on. The rest of the scheme is familiar: major and minor, with the patch slot otherwise held at zero. Now the part that will bite you. That same version string, 2.15.0, appears in three separate files: the build file, the Node package manifest, and the Python project manifest. Three files have to agree, and they are maintained by two different release tools, since the Node manifest has a release script driven by a conventional-version tool and the changelog preset used there is a custom one kept inside the project rather than a published package. Add the changelog file at the top level and you have a release process with several moving parts, which is normal for a project with contributors in several time zones and worth knowing about before you try to cut your own internal release. The commit pipeline around it is elaborate: a commit message linter, an interactive commit tool, a change-file configuration, git hooks, and a commit message convention file, all layered on top of the review system.

## Three dependency managers, and two different language floors

A C firmware project at the bottom of a boot chain manages its own tooling with three separate ecosystems, and each has a different requirement. The Python side uses a project definition with a floor of 3.8, and it declares exactly three dependencies, all of them local paths inside this repository: a device tree compiler, a memory tool, and the TLA+ model checker taken from the project's own library directory. That is a small, deliberate surface, and it is worth noticing that all three are development tools rather than runtime dependencies, which is what you would expect from a firmware project. The Node side exists for commit and release tooling only, and its floor is much higher, node 20 or later. And there is a third ecosystem, Nix, with a lock file and a directory, presumably for producing a reproducible development environment. So to contribute to this project you need a recent Node, an older Python, and optionally Nix, plus a C toolchain. The two language floors being ten versions apart is not a contradiction, it is a consequence of the tools chosen: the Python packages are small and unmaintained elsewhere, and the Node tooling follows the current release tooling. The cost is that a contributor's environment has more moving parts than the firmware itself.

## One unit test tool comes from a branch on another server, with no version

Look at the optional unit test group in the Python project definition. It contains a single dependency, and that dependency is not a published package. It is a direct reference to a git repository, with a branch and no version: 

```toml
c-picker = { git = "https://git.trustedfirmware.org/TS/trusted-services.git", branch = "topics/c-picker" }
```

Three things are worth noticing. The host is not GitHub, so your build now reaches outside the platform you cloned from. The branch name describes it as a topic branch rather than a release branch, which is the kind of branch that changes without notice. And there is no version or commit reference, so there is nothing pinning what you get. For a project whose whole job is a deterministic secure boot chain, that is the one place in the visible configuration where reproducibility is not guaranteed, and it is in the test tooling rather than the firmware, which is at least the right place for it to be wrong. The other two optional groups are better behaved. The documentation group is entirely published packages, and it includes a diagram extension, a MyST parser, a theme and a converter from SVG to PDF, which tells you the documentation is built with a diagram tool in the loop. The continuous integration group is two published packages. So the unpinned reference is an outlier rather than a pattern, and if you are reproducing a CI run, that is the line to look at first.

## One line in the build file blocks command-line variables from reaching sub-makes

There is a line in the build file that looks like noise and is actually a careful piece of build hygiene. After the default goal, the build file sets a GNU make variable to nothing, and the comment above it explains why: to avoid implicit propagation of command-line variable definitions into sub-build files, particularly the compiler flags that are reserved for the firmware images. In GNU make, that variable is what causes a definition typed on the command line to be handed down to every sub-make, which is usually convenient and occasionally catastrophic in a firmware build, because a flag meant for one image silently applies to a bootloader. Clearing it means the build file deliberately drops that convenience, and the comment notes that other options are still propagated as usual. Two more things in the same file are worth noting for a first-time builder. The build pulls in a small set of included make files from a helpers directory, and one of them provides the CPU-specific operations including the defaults for processor errata workarounds, which tells you errata handling is a per-CPU default that a platform can override. And the cryptographic library is vendored in the contributions directory rather than taken from a system package, which is the right call for firmware that has to build identically everywhere.

## Conclusion

Adopt TF-A when you are building an A-profile SoC and need a Secure Monitor at Exception Level 3 that implements the Arm interface standards rather than your own, because reimplementing the power management, boot and delegated exception interfaces is a poor use of a secure-boot team's time. Do not adopt it as finished, certified secure world code, because the project states outright that users should do their own security validation, including penetration testing, on any secure world code derived from it, and that sentence should shape your plan rather than sit in a readme. Two things to check before you start. Establish where the work happens, since this repository is a read-only mirror and the review tooling in the tree points to a different server, so issues and patches do not belong here. And pin your tooling, because the unit test group in the Python configuration pulls a single tool from a branch on another host with no version reference, which is exactly the kind of dependency that makes a build irreproducible six months later.

## FAQ

### What is the ARM-software/arm-trusted-firmware repository?

It is a read-only mirror of Trusted Firmware-A, which is a reference implementation of secure world software for Arm A-profile architectures, including an Exception Level 3 Secure Monitor, implementing interface standards such as PSCI, SCMI, SDEI and the SMC calling convention.

### What do the bl1, bl2, bl2u, bl31 and bl32 directories in Trusted Firmware-A represent?

They are boot stages. The first three are the ARMv7 secure boot sequence including an upgrade stage, bl31 is the Exception Level 3 Secure Monitor, and bl32 is its AArch32 counterpart for a 64-bit boot chain.

### Does Arm say Trusted Firmware-A is production-ready and secure?

The project states that users are encouraged to do their own security validation, including penetration testing, on any secure world code derived from it, and describes itself as a reference implementation and a starting point for productisation. Budget for your own review.

### What does the patch component of the Trusted Firmware-A version mean?

The build file states that the patch component is only used for long-term support releases. So a version with a non-zero patch is a maintained LTS branch, and a version with patch zero is the main development line, which is the distinction an integrator needs when choosing a base branch.

### What dependency managers does the Trusted Firmware-A repository use?

Three. Python project configuration for three local development tools, Node 20 or later for commit and release tooling, and Nix, with a lock file, for the development environment. The optional unit test group also pulls a tool directly from a git branch on another host with no version reference.

## Sources

- [ARM-software/arm-trusted-firmware on GitHub](https://github.com/ARM-software/arm-trusted-firmware)
- [Issues](https://github.com/ARM-software/arm-trusted-firmware/issues)
- [Project website](https://www.trustedfirmware.org/projects/tf-a)
- [README](https://github.com/ARM-software/arm-trusted-firmware/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/arm-software-arm-trusted-firmware
