On TI C2000™ F29x devices, two hardware blocks enforce firmware security.
The Hardware Security Module (HSM) controls which firmware the device accepts. Once the customer keys are provisioned, the HSM verifies the X.509 certificate of every image before it programs or launches it. An image that is not signed with those keys is refused.
The Safety and Security Unit (SSU) controls what the firmware may reach while it runs. It checks every memory and peripheral access against the security configuration stored in flash, which also sets flash write protection, debug access, and the authority to change these rules later.
This post is about the SSU and the configuration it enforces. The HSM and the security states are the subject of a separate post.
The SSU
The SSU is a hardware unit inside the device. Every access a CPU makes to memory or to a peripheral register passes through it. It compares the address against the rules in force. A denied access ends in a fault to the CPU. The check happens in hardware, without an operating system or a supervisor layer.
Peripheral registers come with one caveat. A denied read of a peripheral register is still carried out by the peripheral. The SSU only withholds the result from the CPU. For most registers this makes no difference. For two kinds it does. A receive FIFO hands out its oldest entry on every read, so a denied read consumes that entry, and the next permitted read gets the one after it. A status register whose flags clear when read loses those flags on a denied read as well. In both cases the CPU gets a fault, and the data is lost.
The device reports such a violation. The CPU forwards the fault to the error
aggregator, which latches the address and the program counter that caused it,
then raises a high-priority error in the error signaling module. TI’s driverlib
calls that event ESM_EVENT_ErrorAggregator_CPU1_HPERR for CPU1, with one event
per CPU, triggering a non-maskable interrupt. The error
signaling module can also drive the error output pin of the device. To find the
instruction that was refused, read the latched address and program counter in a
debug session, for example with
C2Prog’s GDB tools.
The SSU rules live in the security configuration, a set of data blocks in flash with one block per CPU. A block holds the access rules for memory, the flash write and erase protection, the debug access, the boot options, and the authority to replace these rules later. The blocks are always programmed together.
A security configuration is based on the following concepts:
- Link. An execution context with access rights of its own. Which link is in force follows from the memory the code executes in.
- Region. A span of memory with a start address, a length, an owning link, and the rights it grants to other links. A region that grants nothing to another link is private to its owner. This holds for peripheral register blocks as well, so a link reaches only the register blocks whose region grants it access. A CPU of an F29H85x has 64 regions.
- Stack. An execution context with its own stack pointer and register set. A group of links runs in one stack and shares its stack memory. Crossing into another stack takes the dedicated call instructions that keep the two contexts apart.
- Zone. The level at which the debug gates and the authority to update the configuration are set. Each stack belongs to a zone.
The linker decides which code belongs to which link. Code it places into a link’s code region runs as that link, and the linker command file states that placement by naming the object files of each link.
The compiler comes in at the stack boundaries. A call from one stack into
another runs through the CPU’s protected-call instructions, and the C29 compiler
emits those for a function declared __attribute__((c29_protected_call)). The
entry points that other stacks call therefore carry that attribute. The rest of
the application compiles unchanged, and the build stays an ordinary Code
Composer Studio or makefile project.
These mechanisms can be used to separate parts of one application. One can give the control code and the communication code a link each, and list e.g. the ePWM registers on the control link alone. A fault in the communication code then cannot write the PWM registers. A functional-safety case can cite this separation as freedom from interference between the two parts.
SSU Modes 1 to 3
The configuration carries an SSU mode. The mode decides how much of the configuration the SSU enforces:
- SSU Mode 1. The access-protection regions are disabled. The templates
that ship with C2Prog, such as
default_seccfg_ssumode1, are Mode 1 baselines for development. - SSU Mode 2. The regions are enforced against all code, including the C2Prog flash kernel that programs the device.
- SSU Mode 3. The regions are enforced. The debug gates, the write and erase protection and the update owners take effect as well.
In Mode 1 and Mode 2 the configuration sectors stay reprogrammable, so a new configuration can take a committed field back. Mode 3 is the production mode. A field that a Mode 3 configuration disables can be taken back only by the update owner of the configuration, and only while a path to run that update, e.g. via C2Prog, survives. A Mode 3 configuration that disables its own update owner is final.
When a configuration takes effect
The device keeps two copies of the configuration in dedicated flash sectors. A new configuration is written into the inactive copy, with a counter that marks it as the newer one. The boot ROM loads a configuration on every reset and takes the copy that counter marks, so a new configuration comes into force at the first reset after the session that wrote it. An interrupted update leaves the previous copy as the newer one, and the device keeps the rules it had.
Writing a configuration works differently in each security state. In HS-FS, C2Prog writes it directly, without the HSM and without signing. In HS-KP the boot ROM cannot read flash, so no stored configuration is applied there. Programming the first signed configuration takes the device to HS-SE. In HS-SE, the configuration changes only through a signed update, and in Mode 3 only through the update owner that the active configuration names.
Describing the configuration
Typically, TI’s SysConfig generates the configuration blocks inside the
application project. The linker places them into the .out file together with
the code, and C2Prog reads them from that file.
C2Prog adds a route of its own, in which the configuration is a stand-alone
.lua profile. The profile names the links, stacks and zones of the design. A
link is given the memory it needs in kB and the peripheral register blocks it
may reach, a stack the links that run in it, and a zone its debug and
update-owner policy. C2Prog derives the addresses, the link and stack ids and
the region table from those names and sizes.
return {
ssu_mode = 3,
flash_load_size_kb = 8,
system_links = {
{ name = "LINK2", is_link2 = true, code_flash_kb = 24, code_ram_kb = 8,
rw_data_kb = 4, ro_data_kb = 4 },
{ name = "LINK1", is_link1 = true, periphs_rw = { "*" } },
},
system_stacks = { { name = "STACK1" }, { name = "STACK2" } },
flasher = { link = "LINK1" },
modules = {
{ name = "Control", code_flash_kb = 32, rw_data_kb = 8,
periphs_rw = { "ADCA_REGS", "EPWM1_REGS" } },
{ name = "Comms", code_flash_kb = 16, rw_data_kb = 4,
periphs_rw = { "UARTB_REGS" } },
},
sandboxes = {
{ name = "sboxControl", links = { "Control" }, zone = 1 },
{ name = "sboxComms", links = { "Comms" }, zone = 1 },
},
}
In the example profile above, the control code and the communication code own a
link each, and among the application links the ADC and ePWM registers belong to
the control link alone. The flasher entry grants the C2Prog flash kernel the
memory and the peripherals it needs to program the part again later.
A profile of this kind is a text file, so it can be version controlled, diffed
and reviewed like source code. It is also independent of the application build,
which lets a configuration stay frozen while the application iterates. One
profile serves several modes, because --ssu-mode builds the same file as Mode
1 for development and as Mode 3 for production. The profile does not have to be
written by hand. An AI assistant with the
C2Prog agentic interface registered reads
the field grammar, writes the profile from a description of the application,
and compiles it; the questions at the end of this post illustrate that route.
Reviewing before you program
A configuration can take capabilities away permanently. C2Prog can therefore
report what a configuration does before it is signed. mkehx-cp --explain-only
prints the regions with their owners and rights, every field the configuration
commits, and the warnings, ranked as irrecoverable, lockout, caution or info.
Generating the report needs no signing key, so a profile can be reviewed on a
machine that holds no key material. --explain-only-json writes the same
report as one JSON document, which suits a build pipeline or an AI assistant.
A finding at the irrecoverable or lockout level stops mkehx-cp. It writes no
file and exits with status 3. Signing such a configuration anyway requires the
--allow-irreversible argument.
The report also states whether the device can still be reprogrammed once the configuration is in force. That question has to be settled before Mode 2 or Mode 3 is activated. To replace a configuration, C2Prog loads its flash kernel onto the device over UART or JTAG. That kernel needs its RAM, its peripherals and a link to run as. UART boot and JTAG each get a verdict in the report:
REFLASH PATHS (can this device still be reprogrammed?):
[OK] UART -- A UART-capable boot mode is reachable and LINK1 holds
every region and peripheral the kernel needs.
[BLOCKED] JTAG -- C29 device debug is disabled (DEBUG_CFG C29DBGEN = 0x4),
which is enforced in SSU Mode 3, so no debug probe can
attach.
Way back: The seccfg update owner is CPU1 / LINK1 (enabled) and a surviving
reflash path runs a kernel as that link, so the inactive slot
can still be written.
A verdict of BLOCKED carries the reason with it, as the JTAG line does here. The closing line states whether the configuration can still be replaced at all.
In Mode 3, JTAG needs a debug gate that opens as well. Four gates control the
CPU debug ports: one for the C29 CPUs and one per zone. A gate in password mode
opens when the debugger presents that gate’s password. C2Prog presents the
password given with --debug-password when the file was built. Without the
option it presents TI’s default password. C2Prog’s GDB
server applies a password the
same way when it attaches.
Questions for an AI assistant
An AI assistant with the C2Prog agentic interface registered has the manual and the seccfg tools at hand. Drafting and reviewing a configuration needs neither a signing key nor a board, so this work can start before the hardware arrives.
Draft an SSU Mode 3 profile for CPU1 with a control link that owns the ADC and ePWM registers and a separate communications link, then generate and explain it.
The assistant reads the field grammar, writes a profile, compiles it, and reports the findings at register level. Each revision runs the same compile and report again.
Does this application build carry a security configuration, and what does it do?
A configuration that the linker placed into the build is reviewed from the build itself, without a profile and without a device. The report decodes it line by line:
What it does:
- SSU mode: SSU Mode 1 (0x30) (debug/zone protections are only enforced in SSU Mode 3)
- C29 device debug: Password based - unlocked by presenting the password (0x2)
- seccfg update owner: CPU1 / LINK1 (enabled).
- Write/erase protection (WEPROT): 228 flash sector(s) (~912 kB) write/erase protected, 112 kB writable; not locked/committed (changeable while an update owner remains).
- RAM open policy: RAM access is open after boot (0xA5).
- Emulation boot: Enabled (JTAG emulation boot is available as a recovery path).
- CPU1 access protection map: 0 of 64 APRs active, covering 0 kB.
The same run validates the images. An update owner set to an invalid link, for example, fails validation and stops the file from being written. This build runs at Mode 1, so its debug, update-owner and protection fields are decoded from the image without being in force. They are worth reading even so, because a Mode 3 configuration enforces these same fields.
With this configuration in force, can the device still be reprogrammed with C2Prog over JTAG and over UART?
UART and JTAG each come back with a verdict, and a blocked one carries the reason with it. Ask this before the configuration is signed.
The HSM post covers the other half of F29 security. The templates and the tools used here ship with C2Prog.