For example, we define a region to be secure. If a non secure app is trying to acess that region, what protection mechanism exists in hardware which will prevent that?
TrustZone provides two physical address spaces, Secure and Non-secure.
When in Non-secure state (also known as "Normal world"), _all_ virtual addresses translate to Non-secure physical addresses. There is nothing software can do to override this.
When in Secure state, virtual addresses can translate to either Secure or Non-secure physical addresses. This is controlled by a bit in the translation tables (*).
The type of address (Secure or Non-secure) is signaled for each bus transaction, If using AXI, the signal is AxPROT.
A Non-secure access (AxPROT==1) to a Secure peripheral/address range will get blocked by the memory system.
How does the memory system determine if a peripheral/address range is Secure/Non-secure? It can hard wired, or could be configurable. For example, using a TrustZone Address Space Controller (TZASC). Alternatively, many interconnects have registers to program whether a given slave is Secure/Non-secure.
Some peripherals, such as the GIC, will accept both Secure and Non-secure accesses. The peripheral internally uses the AxPROT signal to gate access to certain functionality. E.g. only Secure acceses can configure "secure" interrupts.
(* The bit is still there in Non-secure state, but ignored by hardware)
If you want a slightly longer answer here is a useful technical overview:
ARM Security Technology Building a Secure System using TrustZone Technology
View all questions in TrustZone for Armv8-M forum