How PPROT[2:0] encodes privilege, security, and access type, the Realm Management Extension (PNSE) that adds Root and Realm address spaces, compatibility rules, and practical gating patterns for secure peripheral registers.
Modern SoCs support multiple security domains running simultaneously — a secure OS, a non-secure OS, hypervisor, and (with Arm CCA) Root and Realm worlds. Peripheral registers must be partitioned across these domains: a cryptographic key register must only be accessible from the secure world, a non-secure OS must not be able to read it even accidentally.
Without protection signals, any software running on the CPU could issue AXI/AHB transactions that propagate through the APB bridge and reach any peripheral register. PPROT (APB4) and PNSE (APB5/RME) carry the security context of the initiating transaction all the way to the peripheral, allowing register-level access control.
The protection information originates from the CPU or system bus and flows through the chain:
The AXI protection signal AxPROT[2:0] has the same bit definitions as PPROT[2:0]. The APB bridge passes it through directly — bit[0] to bit[0], bit[1] to bit[1], bit[2] to bit[2]. There is no translation needed.
PPROT is a 3-bit signal where each bit encodes a completely independent protection attribute. The bits are not a 3-bit encoded value — they are three separate one-bit flags. A peripheral can check any combination of bits independently.
The spec notes that PPROT[2] (data vs instruction) is provided as a hint and may not be accurate in all cases — some CPU implementations do not distinguish instruction fetches from data accesses at the bus level. Peripherals should not rely on PPROT[2] for security decisions; use only PPROT[0] and PPROT[1] for access gating.
The primary and most important use of PPROT is PPROT[1] for secure/non-secure gating. A secure peripheral rejects transfers with PPROT[1]=1 (non-secure) by asserting PSLVERR or simply ignoring the write.
The full 8-value encoding space of PPROT[2:0] with the most common values highlighted:
| PPROT[2:0] | PPROT[2] | PPROT[1] | PPROT[0] | Access description | Typical source |
|---|---|---|---|---|---|
3'b000 | Data | Secure | Normal | Secure normal data access — most common for secure driver register writes | Secure OS driver, TF-A |
3'b001 | Data | Secure | Privileged | Secure privileged data access — kernel-mode secure driver | Secure kernel |
3'b010 | Data | Non-secure | Normal | Non-secure normal data access — user-space driver | Linux user-space |
3'b011 | Data | Non-secure | Privileged | Non-secure privileged data access — most common for non-secure driver register writes | Linux kernel driver |
3'b100 | Instruction | Secure | Normal | Secure normal instruction fetch (hint only) | Rare — peripheral fetch |
3'b101 | Instruction | Secure | Privileged | Secure privileged instruction fetch | Rare |
3'b110 | Instruction | Non-secure | Normal | Non-secure normal instruction fetch | Rare |
3'b111 | Instruction | Non-secure | Privileged | Non-secure privileged instruction fetch | Rare |
3'b000 (secure privileged kernel → often promoted to privileged = 3'b001) and 3'b011 (non-secure privileged kernel). Most APB peripherals only need to check PPROT[1] — secure vs non-secure — and can ignore bits 0 and 2 entirely.
PPROT is presented at the start of the Setup phase alongside PADDR and must remain stable through all wait states until transfer completion — identical stability requirement to PADDR.
PPROT is optional on both Requester and Completer. The spec defines the compatibility for all four combinations:
| Completer: PPROT absent | Completer: PPROT present | |
|---|---|---|
| Requester: PPROT absent | Compatible. Neither side has protection. All accesses are treated as having the default PPROT=3’b000 (secure, normal, data). | Not compatible in general. The Completer expects protection attributes but none are provided. Exception: if fixed protection attributes are functionally correct for the design (e.g. the peripheral only ever needs to see secure access and the Requester is always secure), you can tie PPROT to a constant value and document it. Check Table 3-2 of the spec for the exact encoding to tie. |
| Requester: PPROT present | Compatible. Requester drives PPROT but Completer has no access protection — PPROT signals are unconnected. The Completer accepts all transactions regardless of protection level. | Compatible. Both sides have PPROT. Full protection gating works. Normal connection. |
The Arm Realm Management Extension (RME), introduced in the Armv9 architecture, adds two new physical address spaces — Root and Realm — alongside the existing Secure and Non-secure spaces. APB5 Issue E (2023) added PNSE (Peripheral Non-Secure Extension, also written as the NS Extension bit) to carry this additional context.
PNSE is a single additional bit that works alongside PPROT[1] to encode all four physical address spaces:
Key properties of PNSE:
RME_Support = True. If not declared, RME_Support is considered False and PNSE is absent.PCTRLCHK signal (alongside PPROT and PWRITE).With RME, the SoC has four address spaces each with different trust levels. Peripheral access control must account for all four:
| Completer: RME_Support=False | Completer: RME_Support=True | |
|---|---|---|
| Requester: RME_Support=False | Compatible. Neither side has PNSE. Standard PPROT[1]-based Secure/Non-secure model. | Compatible. Completer has PNSE input but Requester doesn’t drive it. Tie PNSE LOW at the Completer input. PNSE=0 with PPROT[1]=0/1 gives Secure/Non-secure — the standard pre-RME behaviour. |
| Requester: RME_Support=True | Not compatible. Requester may drive PNSE=1 (Root or Realm accesses) but Completer has no way to distinguish these from Secure/Non-secure. Cannot safely connect without access control violation risk. | Compatible. Both sides support RME. Full four-space access control available. |
A peripheral that should only be accessible from the secure world:
assign ns_access_violation = PSELx & PPROT[1];
assign PSLVERR = PSELx & PENABLE & PREADY & PPROT[1];
assign write_enable = PSELx & PENABLE & PREADY & PWRITE & ~PPROT[1];
When PPROT[1]=1 (non-secure), the peripheral asserts PSLVERR at completion and does not update any registers. The transfer completes as a protocol transaction but returns an error to the system bus (which returns SLVERR to the CPU).
assign write_enable = PSELx & PENABLE & PREADY & PWRITE & PPROT[0];
assign PSLVERR = PSELx & PENABLE & PREADY & ~PPROT[0];
Many peripherals have both secure and non-secure registers. A common pattern is address-based partitioning: the lower address range is non-secure accessible (status registers, debug counters), the upper range is secure-only (key registers, configuration):
wire secure_region = (PADDR[11:8] == 4'hF);
wire access_ok = ~secure_region | ~PPROT[1];
assign PSLVERR = PSELx & PENABLE & PREADY & ~access_ok;
The AXI AxPROT signal has the same bit encoding as PPROT. An AXI-to-APB bridge simply passes ARPROT or AWPROT through to PPROT unchanged. On AXI, ARPROT is used for read transactions and AWPROT for write transactions — the bridge selects the appropriate one based on the transfer type and drives the single PPROT bus on APB.
| Item | Rule |
|---|---|
| PPROT introduced in | APB4 — not available in APB2 or APB3 |
| Width | 3 bits — three independent one-bit flags |
| PPROT[0] | 0=Normal, 1=Privileged |
| PPROT[1] | 0=Secure, 1=Non-secure (LOW=Secure — counterintuitive!) |
| PPROT[2] | 0=Data, 1=Instruction (hint only — may not be accurate) |
| Most common value | 3’b011 (Non-secure Privileged Data) — Linux kernel driver |
| Most important bit | PPROT[1] — secure vs non-secure gating |
| AXI mapping | AxPROT[2:0] → PPROT[2:0] direct 1:1 (same encoding) |
| Stability | Valid when PSELx asserted, stable until transfer completes — same as PADDR |
| Req absent, Comp present | Tie PPROT to correct fixed value. Not generally compatible without analysis. |
| Req present, Comp absent | Compatible — Completer has no access control, ignores PPROT |
| PNSE introduced in | APB5 Issue E (2023), RME_Support property |
| PNSE=0, PPROT[1]=0 | Secure |
| PNSE=0, PPROT[1]=1 | Non-secure |
| PNSE=1, PPROT[1]=0 | Root (Arm CCA) |
| PNSE=1, PPROT[1]=1 | Realm (Arm CCA) |
| Req has RME, Comp doesn’t | Not compatible — tie PNSE LOW only when Requester never drives PNSE=1 |
| Req no RME, Comp has RME | Compatible — tie PNSE input LOW at Completer |
| PSLVERR on protection violation | Recommended — assert PSLVERR when PPROT[1] violates the peripheral’s access policy |