1. Three descriptions of one boundary
The option bytes are non-volatile and define what the flash is. The secure startup code is volatile and defines what the SAU, the GTZC and the MPUs believe on this boot. The linker scripts are build-time and define where your symbols actually land.
Nothing enforces consistency between the three. The toolchain will happily link a secure image whose .text sits outside the secure watermark. The result is a device that boots into a SecureFault before your first line of C runs, with a debugger that may already be locked out.
That is why the working order matters, and why it is the reverse of what feels natural:
- Decide the map on paper first, in sectors and blocks, not in bytes.
- Write the linker scripts to that map.
- Write the SAU and GTZC setup to the same map.
- Program the option bytes last, and lock last of all.
2. TZEN and the product state
Two option bytes decide whether any of this exists.
TZEN lives in FLASH_OPTSR2_CUR / FLASH_OPTSR2_PRG, bits 31:24. It is not a single bit: it is an 8-bit pattern, 0xB4 to enable TrustZone and 0xC3 to disable it. ST uses this 0xB4 / 0xC3 pair throughout the H5 option bytes, for BOOT_LOCK and BOOT_UBE too. Two values with a large Hamming distance, so a single flipped bit is not a valid state.
PRODUCT_STATE lives in FLASH_OPTSR_CUR / FLASH_OPTSR_PRG, bits 15:8. On H5 this replaces the RDP levels you know from earlier families. The values are, from the HAL:
| State | Value | What it means in practice |
|---|---|---|
OPEN |
0xED |
full debug, flash erasable, everything reversible |
PROVISIONING |
0x17 |
OBK writing allowed, non-secure debug only |
IROT_PROVISIONED |
0x2E |
root of trust in place |
TZ_CLOSED |
0xC6 |
secure world closed, non-secure debug at HDPL3 |
CLOSED |
0x72 |
no debug connection without authentication |
LOCKED |
0x5C |
no debug, no regression, ever |
Two of these deserve a warning rather than a description.
LOCKED is final. There is no regression from it. It is the correct end state for a shipped product and a catastrophic mistake on a development board.
CLOSED without provisioned debug-authentication keys is equivalent to LOCKED. ST's own answer to engineers who have done this is that the part is unrecoverable, and that this is the feature working as designed. Provision the debug-authentication keys before you close, or accept that you have made a paperweight.
The escape hatches are the two regression values, REGRESSION 0x9A (full) and NS_REGRESSION 0xA3 (non-secure only), and both are gated by debug authentication credentials you must have provisioned earlier.
3. Flash: watermark, blocks, hide, write protect
The secure watermark is the coarse instrument, one contiguous secure region per bank, at 8 KB sector granularity. FLASH_SECWM1R_CUR/_PRG for bank 1 and FLASH_SECWM2R_CUR/_PRG for bank 2, each holding SECWMx_STRT<a class="wiki" href="6:0" rel="">6:0</a> and SECWMx_END<a class="wiki" href="22:16" rel="">22:16</a> as sector numbers. An H573 has 128 sectors per bank.
The idiom worth knowing before you meet it in a script: start greater than end means no secure area at all. ST's TrustZoneEnabled template uses SECWM1_STRT=0x0, SECWM1_END=0x7F for a fully secure bank 1 and SECWM2_STRT=0x1, SECWM2_END=0x0 for a fully non-secure bank 2. The same STRT=1, END=0 pattern is how every ST provisioning script clears a SECWM or an HDP area. It reads like a typo and is not.
Where the watermark is too coarse, FLASH_SECBB1R1..R4 and FLASH_PRIVBB1R1..R4 give one bit per sector, four 32-bit registers covering the 128 sectors of a bank.
HDP, hide protection, is a different mechanism from both. FLASH_HDP1R_CUR/_PRG and FLASH_HDP2R_CUR/_PRG hold HDPx_STRT and HDPx_END, again in 8 KB sectors, with FLASH_HDPEXTR extending the area. There is no HDPEN bit on H5 and no enable register: the area is not switched off by writing a bit, it becomes inaccessible when the running code raises its hide protection level. That is a Part 3 subject, and it is the mechanism that lets a boot stage read its keys and then make them unreachable to everything it launches.
WRP is the mundane one. FLASH_WRP1R_CUR/_PRG and FLASH_WRP2R_CUR/_PRG, field WRPSG<a class="wiki" href="31:0" rel="">31:0</a>, one bit per group of four sectors, so 32 KB per bit on an H573. It is what makes an immutable bootloader actually immutable.
One correction to anything you may have read elsewhere, including an earlier version of Part 1 of this series: PCROP does not exist on STM32H5. There is no register, no field and no HAL function. Its role is taken by the secure watermark, SECBB and HDP.
4. SRAM: MPCBB, and why it is rewritten on every boot
Three SRAMs, three controllers: GTZC1_MPCBB1, MPCBB2, MPCBB3. Each exposes SECCFGR<a class="wiki" href="32" rel="">32</a> and PRIVCFGR<a class="wiki" href="32" rel="">32</a>, and the arithmetic is worth doing once so the register layout stops looking arbitrary.
One bit is one block of 512 bytes. One 32-bit register is therefore 32 blocks, which ST calls a super-block, 16 KB. So SRAM1 at 256 KB uses 16 of the 32 available SECCFGR registers, SRAM2 at 64 KB uses 4, and SRAM3 at 320 KB uses 20. The struct always declares 32; the rest are reserved.
Three bits in MPCBBx_CR are worth reading the manual for before you touch them:
GLOCK(bit 0) locks the whole configuration until reset.INVSECSTATE(bit 30) inverts the meaning of theSECCFGRbits. It exists for a reason but it is exactly the kind of setting that makes someone else's memory map incomprehensible six months later.SRWILADIS(bit 31) disables the illegal-access event for secure read-while-write.
The property that has operational consequences: all of this is volatile. Unlike the flash watermark, nothing in MPCBB survives reset. Your secure boot stage rewrites the entire SRAM security map on every single start.
The reset default is the useful thing to know here: with TZEN enabled, all SRAM comes up secure. ST's own template has to write zeros into an MPCBB configuration array to hand SRAM3 to the non-secure world. So a bug in that loop does not produce a device that quietly runs unprotected, it produces a non-secure image that faults on its first access. Loud, which is the failure mode you want.
CFGLOCKR1 holds one lock bit per super-block, SPLCK0 to SPLCK31, if you want to freeze part of the map without freezing all of it.
5. Peripherals: TZSC and the interrupt you must wire up
GTZC1_TZSC carries SECCFGR1, SECCFGR2, SECCFGR3 and the matching PRIVCFGR1..3, one bit per securable peripheral. Note there are two independent attributes per peripheral, secure and privileged, and they are not the same decision. A peripheral can be non-secure but privileged, which is how you keep an unprivileged non-secure task away from a timer that your RTOS owns.
TZSC_CR.LCK locks the lot until reset.
The watermark registers for external memory live here too, not in a separate block: MPCWM1ACFGR through MPCWM4BR, covering the memory-mapped OCTOSPI and FMC regions at 128 KB granularity and the backup SRAM at 32 bytes, with SREN, SRLOCK, SEC and PRIV bits in the config registers.
Then there is GTZC1_TZIC, and this is the part most teams skip. The registers are IER1..IER4, SR1..SR4, FCR1..FCR4, one bit per illegal-access source, feeding GTZC_IRQn (NVIC position 8). The SR flags are set whether or not you enabled the corresponding IER bit, so what you lose by ignoring TZIC is not the record, it is being told. A device with TZIC interrupts unenabled logs the event in SR and informs nobody.
Decide early what an illegal access means for your product. A reset? A log entry in the secure world? A tamper response? The default, which is to notice nothing, is a decision too, and it is the one that gets discovered during a penetration test rather than during development.
6. SAU and the partition file
The SAU is CMSIS, not ST: SAU->CTRL, SAU->RNR, SAU->RBAR, SAU->RLAR. Eight regions on H573.
Two details cause most of the trouble. First, RBAR and RLAR both start at bit 5, so regions are 32-byte aligned and RLAR holds the last address of the region, not the address after it. Second, RLAR bit 1 is NSC: that single bit is what turns a secure region into the callable window described in Part 1.
SAU_CTRL.ALLNS decides what the world looks like when the SAU is disabled. ST's generated partition_stm32h573xx.h ships with SAU_INIT_CTRL_ENABLE 0 and SAU_INIT_CTRL_ALLNS 1, meaning the SAU starts disabled and everything defaults to non-secure with the IDAU still deciding. That is a sensible default for a template and not one you want to leave in a product.
When the map is final, SBS_CSLCKR.LOCKSAU blocks further writes to the SAU until reset.
7. The build, and the one file that ties the two images together
The secure project compiles with -mcmse. That flag is what makes __attribute__<a href="tiki-editpage.php?page=cmse_nonsecure_entry" title="Create page: cmse_nonsecure_entry" class="wiki wikinew text-danger tips">cmse_nonsecure_entry</a> generate a real veneer with an SG at its entry, and it is also what enables the arm_cmse.h intrinsics.
At link time the secure image emits a secure gateway import library: -Wl,--cmse-implib -Wl,--out-implib= with GNU ld, or --import_cmse_lib_out= with armlink. ST's own H5 projects name it secure_nsclib.o, paired with a hand-written Secure_nsclib/secure_nsc.h. It contains addresses, not code. The non-secure project links against it and its calls resolve to veneer addresses in the NSC region.
This object file is the only build-time coupling between the two images, which has a practical consequence: move a veneer and you must rebuild and reflash both images. A non-secure binary linked against a stale import library will call into whatever now occupies that address, and if that address is no longer an SG, you get a SecureFault with SFSR.INVEP. If it is a different SG, you get something worse, which is a call that succeeds and does the wrong thing.
On the pointer validation promised in Part 1, the intrinsic is cmse_check_address_range(void *p, size_t size, int flags), with cmse_check_pointed_object(p, flags) as the sizeof-aware wrapper. The flag you want for an argument arriving from the non-secure side is CMSE_NONSECURE (which is CMSE_AU_NONSECURE | CMSE_MPU_NONSECURE), combined with CMSE_MPU_READWRITE or CMSE_MPU_READ. It returns the pointer on success and NULL on failure, and it is declared warn_unused_result for a reason.
8. A bring-up order that does not brick boards
Stated as a sequence, because the failure mode of getting it wrong is not a compile error:
- Keep
PRODUCT_STATEatOPENfor the entire development phase. Everything is reversible here and nowhere else. - Set
TZEN = 0xB4, erase, and get a plain secure-plus-non-secure application booting with the watermark set but noHDP, noWRP, no boot lock. - Add the GTZC configuration and enable
TZICearly, so that misconfigurations announce themselves instead of being silently dropped. - Add
HDPandWRPonly once the flash layout has stopped moving. - Provision your debug-authentication keys.
- Only then move the product state, and only after you have recorded whatever device identity your regression procedure will need.
Two errata are worth reading before step 6 rather than after. Product-state change is not supported on early samples, which lock permanently if moved off OPEN, and the erratum identifies them by the secure native services identifier reading 0xA7CA2047 or 0xFFFFFFFF, not by date code. And product-state regression is impossible if the flash high-cycle data area is enabled, so that feature must be disabled before you leave OPEN.
Coming in Part 3 - Practice
The boot chain: BOOT_UBE and the choice between ST-iRoT and OEM-iRoT, hide protection levels and DHUK-derived keys, the OBK area and provisioning, debug authentication, and firmware update with MCUboot.
Going further
Our one-day course ARM TrustZone for Cortex-M based devices (SEC7) covers this on a Cortex-M33 board: Armv8-M security extension, SAU and IDAU configuration, Cortex-M33 MPU and PMSA, exceptions across the boundary, security access faults, secure software design. STM32H5 (STR12), three days, takes it to the silicon: secure and non-secure images, NSC veneers, GTZC isolation patterns, TF-M and STM32Trust Secure Manager, provisioning and dual-bank update.
Sessions run online in both European and US time zones. See the course pages for the next dates, or request a session on your own dates, online or on-site.
SEC7 · STR12 · SEC5 · STR19 · STR13
References
- ST - RM0481, STM32H563/H573/H562 reference manual - FLASH, GTZC and SBS chapters
- ST - ES0565, STM32H562xx/563xx/573xx device errata - product state and regression limitations
- ST -
stm32h573xx.handstm32h5xx_hal_flash_ex.h, the authoritative source for every value quoted above - ST - STM32CubeH5,
Projects/STM32H573I-DK/Templates/TrustZoneEnabledandROT_Provisioning - Arm - Armv8-M Architecture Reference Manual, security extension, and
arm_cmse.h